From e9baeba31fc6151e0ef3b635a865aa2bdaef2dc8 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 14 Aug 2024 14:10:14 +0100 Subject: [PATCH 001/440] blog: v6 announcement (#4598) * blog: v6 announcement * update --- README.md | 6 +- ...vision-funding-v5-videos-files-passcode.md | 4 +- ...-v6-private-routing-new-user-experience.md | 247 ++++++++++++++++++ blog/README.md | 22 ++ blog/images/20240814-delete-contact-1.png | Bin 0 -> 217826 bytes blog/images/20240814-delete-contact-2.png | Bin 0 -> 115115 bytes blog/images/20240814-delete-messages.png | Bin 0 -> 895313 bytes blog/images/20240814-new-message.png | Bin 0 -> 261119 bytes blog/images/20240814-play.png | Bin 0 -> 123821 bytes blog/images/20240814-reachable.png | Bin 0 -> 421662 bytes blog/images/20240814-share.png | Bin 0 -> 306879 bytes .../src/_includes/blog_previews/20240814.html | 18 ++ website/src/css/blog.css | 6 + 13 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md create mode 100644 blog/images/20240814-delete-contact-1.png create mode 100644 blog/images/20240814-delete-contact-2.png create mode 100644 blog/images/20240814-delete-messages.png create mode 100644 blog/images/20240814-new-message.png create mode 100644 blog/images/20240814-play.png create mode 100644 blog/images/20240814-reachable.png create mode 100644 blog/images/20240814-share.png create mode 100644 website/src/_includes/blog_previews/20240814.html diff --git a/README.md b/README.md index 29ee7e189e..1a500187c0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ 2. ↔️ [Connect to the team](#connect-to-the-team), [join user groups](#join-user-groups) and [follow our updates](#follow-our-updates). 3. 🤝 [Make a private connection](#make-a-private-connection) with a friend. 4. 🔤 [Help translating SimpleX Chat](#help-translating-simplex-chat). -5. ⚡️ [Contribute](#contribute) and [help us with donations](#help-us-with-donations). +5. ⚡️ [Contribute](#contribute) and [support us with donations](#please-support-us-with-your-donations). [Learn more about SimpleX Chat](#contents). @@ -150,7 +150,7 @@ We would love to have you join the development! You can help us with: - contributing to SimpleX Chat knowledge-base. - developing features - please connect to us via chat so we can help you get started. -## Help us with donations +## Please support us with your donations Huge thank you to everybody who donated to SimpleX Chat! @@ -233,6 +233,8 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: +[Aug 14, 2024. SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing](./blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) + [Jun 4, 2024. SimpleX network: private message routing, v5.8 released with IP address protection and chat themes](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md) [Apr 26, 2024. SimpleX network: legally binding transparency, v5.7 released with better calls and messages.](./blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md) diff --git a/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md b/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md index 6f32586bc1..690292d14c 100644 --- a/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md +++ b/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md @@ -40,7 +40,9 @@ Many large tech companies prioritizing value extraction over value creation earn ### How is it funded and what is the business model? -We started working full-time on the project in 2021 when [Portman Wills](https://www.linkedin.com/in/portmanwills/) and [Peter Briffett](https://www.linkedin.com/in/peterbriffett/) (the founders of [Wagestream](https://wagestream.com/en/) where I led the engineering team) supported the company very early on, and several other angel investors joined later. In July 2022 SimpleX Chat raised a pre-seed funding from the VC fund [Village Global](https://www.villageglobal.vc) - its co-founder [Ben Casnocha](https://casnocha.com) was very excited about our vision of privacy-first fully decentralized messaging and community platform, both for the individual users and for the companies, independent of any crypto-currencies, that might grow to replace large centralized platforms, such as WhatsApp, Telegram and Signal. +We started working full-time on the project in 2021 when [Portman Wills](https://www.linkedin.com/in/portmanwills/) and [Peter Briffett](https://www.linkedin.com/in/peterbriffett/) (the founders of [Wagestream](https://wagestream.com/en/) where I led the engineering team) supported the company very early on, and several other angel investors joined later. In July 2022 SimpleX Chat raised a pre-seed funding from the VC fund [Village Global](https://www.villageglobal.vc) - its co-founder [Ben Casnocha](https://www.villageglobal.vc/team/ben-casnocha) was very excited about our vision of privacy-first fully decentralized messaging and community platform, both for the individual users and for the companies, independent of any crypto-currencies, that might grow to replace large centralized platforms, such as WhatsApp, Telegram and Signal. + +> Edit: please see the comment from Ben Casnocha about this investment in [our post from August 14, 2024](./20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md). Overall we raised from our investors approximately $370,000 for a small share of the company to allow the project team working full time for almost two years, funding product design and development, infrastructure, and also [the security assessment by Trail of Bits](./20221108-simplex-chat-v4.2-security-audit-new-website.md). A large part of this money is not spent yet. diff --git a/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md b/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md new file mode 100644 index 0000000000..e81bf5516a --- /dev/null +++ b/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md @@ -0,0 +1,247 @@ +--- +layout: layouts/article.html +title: "SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing." +date: 2024-08-14 +image: images/20240814-reachable.png +previewBody: blog_previews/20240814.html +permalink: "/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html" +--- + +# SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing. + +**Published:** Aug 14, 2024 + +[SimpleX Chat: vision and funding 2.0](#simplex-chat-vision-and-funding-20): +- [The past](#the-past-investment-from-village-global): investment from Village Global. +- [The present](#the-present-announcing-the-investment-from-jack-dorsey-and-asymmetric): announcing the investment from Jack Dorsey and Asymmetric Capital Partners. +- [The future](#the-future-faster-development-and-transition-to-non-profit-governance): faster development and the path to non-profit governance. + +[What's new in v6.0](#whats-new-in-v60): +- Private message routing — now enabled by default. +- [New chat experience](#new-chat-experience): + - connect to your friends faster. + - [new reachable interface](#new-reachable-interface). + - archive contacts to chat later. + - new way to start chat. + - [moderate like a pro](#moderate-like-a-pro): delete many messages at once. + - new chat themes* + - increase font size**. +- [New media options](#new-media-options): + - play from the chat list. + - blur for better privacy. + - [share from other apps](#share-from-other-apps)*. +- [Improved networking and reduced battery usage](#improved-networking-and-reduced-battery-usage) + +\* New for iOS app. + +\*\* Android and desktop apps. + +## SimpleX Chat: vision and funding 2.0 + +### The past: investment from Village Global + +Last year [we announced](https://simplex.chat/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.html#how-is-it-funded-and-what-is-the-business-model) pre-seed funding from several angel investors and Village Global. Some of our users were very excited that we have funds to continue developing SimpleX network. But as some of Village Global LPs (Limited Partners) are [the founders of very large technology companies](https://www.villageglobal.vc), some of our users were worried about any negative influence this investment might have on the project. + +[Ben Casnocha](https://www.villageglobal.vc/team/ben-casnocha), the founder and general partner of Village Global, commented on their investment: + +> I believe in SimpleX Chat vision and team’s ability to execute it. The growing number of Internet users who demand privacy of their data and contacts will make SimpleX Chat profitable, which is critically important for any sustainable organization. +> +> We are fortunate to have LPs who founded many iconic Internet ventures. But they don’t have any influence on the 400+ companies we invested in. They are financial investors in our fund and exert no control or influence on any of the underlying portfolio companies. +> +> What's more, we believe that founders should lead their ventures, as it yields better results – our investment in SimpleX Chat has no control provisions. We are happy to help, but we don’t control any decisions nor have a board seat. Evgeny runs the company independently. + +Ben, thank you for believing in our vision – without it SimpleX Chat would simply not exist, as most other investors at the time did not believe that privacy could ever escape the niche of privacy enthusiasts – and we already see the first signs of it happening. + +### The present: announcing the investment from Jack Dorsey and Asymmetric + +The Android app recently hit [100,000 downloads on Google Play Store](https://play.google.com/store/apps/details?id=chat.simplex.app), and our users naturally ask for improved reliability, privacy, security, better user experience and design – all at the same time, and as soon as possible. This requires more funding. + +We are very happy to announce that we now have funds to move faster – we raised a $1.3 million pre-seed round led by [Jack Dorsey](https://en.wikipedia.org/wiki/Jack_Dorsey), with participation of [Asymmetric Capital Partners](https://www.acp.vc) (ACP) VC fund. + +When Jack discovered SimpleX Chat last year, he [posted on Twitter](https://x.com/jack/status/1661681076983529479): + +> Better than Signal? Looks promising. +> A few bugs and UX issues but great foundation. Love that it’s public domain. + +And [on Nostr](https://primal.net/e/note1txz9xmmc456kwcg7zrsrtqrhn7as29ptuz0qulu452k8n85hsshqq6uh6q): + +> A full day with @SimpleX Chat. Solid overall. TestFlight is not recommended. There are some scaling issues today. And not the most intuitive onboarding for everyone. Name still reminds everyone of herpes. All fixable. It’s fast and doesn’t require a phone number or email and I do believe people will eventually see the value of that. Finally, some competition for Signal, and in a permissionless way. And def a solid path so apps don’t have to build their own DM experiences. + +Jack, we are super lucky to have your support and investment – thank you for believing in our ability to build a better messaging network! It is a hard work, and we’ve made a lot of progress since your note was written, and a lot of work is ongoing! + +The ACP investment is strategically important – it is a fund that only invests in B2B startups, and SimpleX Chat currently is mostly used by individual users. Making a private communication network sustainable requires its adoption by businesses, and we already see a growing usage by the small teams. + +[Rob Biederman](https://www.acp.vc/team/rob-biederman) and [Sam Clayman](https://www.acp.vc/team/sam), the partners of ACP, commented: + +> We believe that SimpleX Chat network can grow into a de facto Internet standard for private and secure communications for both businesses and individual users, unifying instant and email-like messaging into a single product. +> +> Emails no longer provide privacy and security that businesses require, particularly given the emerging threat of AI-led phishing and social engineering attacks. We look forward to SimpleX network providing a secure alternative. + +I was lucky to have met Rob, Sam and the ACP team when I was presenting SimpleX Chat in London – thank you all for your support and believing that the future of communication requires a single product, both for businesses and individual users. + +### The future: faster development and the path to non-profit governance + +Jack Dorsey and ACP support enable us to make huge product improvements, thanks to a bigger team, and provide us with medium-term funding to get to the next stage of product and business evolution. Like with Village Global, this is a financial investment, without control or board seat provisions – so the users can be certain that SimpleX remains true to our vision of privacy first communication network. + +We already added two great engineers to the team and are about to hire a UX/UI designer. + +[Trail of Bits](https://www.trailofbits.com/about/) has just completed the protocols design security review and will be doing implementation security review in the end of the year. We will publish the first report soon. + +This year we will launch group improvements that we presented in the [live-stream last year](https://www.youtube.com/watch?v=7yjQFmhAftE). While the main problem explained in this video was solved with the current design, the issue of group scalability remains – to send a message to a group your client needs to send it to each member, creating substantial traffic. + +We will also launch long-form email-like messaging over SimpleX network this year, together with optional short public addresses that show profile you are connecting to before the connection – this is important for any public users and businesses. + +The last but not the least, we started the work with [Heather Meeker](https://www.techlawpartners.com/heather), a great legal expert on intellectual property matters and one of the earliest advocates of the open-source software development in businesses, to setup open-source governance model, to some extent similar to how Matrix did it. We believe, and our investors agree, that it would both increase the company value and also create more value for the users community. + +## What's new in v6.0 + +v6.0 is one of our biggest releases ever, with a lot of focus on UX and stability improvements, and the new features the users asked for. + +The private message routing [we announced before](./20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md) is now enabled for all users by default – it protects users IP addresses and sessions from the destination servers. + +### New chat experience + +#### Connect to your friends faster + +This version includes messaging protocol improvements that reduce twice the number of messages required for two users to connect. Not only it means connecting faster and using less traffic, this change allows to start sending messages sooner, so you would see "connecting" in the list of the chats for a much shorter time than before. + +It will be improved further in the next version: you will be able to send messages straight after using the invitation link, without waiting for your contact to be online. + +#### New reachable interface + + + +Like with the most innovative mobile browsers (e.g., Safari and Firefox), SimpleX Chat users now can use the app with one hand by moving the toolbar and search bar to the bottom of the screen, and ordering the chats with the most recent conversations in the bottom too, where they can be more easily reached on a mobile screen. + +This layout is enabled by default, and you can disable it right from the list of chats when you install the new version if you prefer to use conventional UI. + +Give it a try – our experience is that that after less than a day of using it, it starts feeling as the only right way. You can always toggle it in the Appearance settings. + +#### Archive contacts to chat later + +   + +Now you have two new options when deleting a conversation: +- only delete conversation, and archive contact. We will add archiving conversation without clearing it in the next version, as some users of our beta version asked. +- delete contact but keep the conversation. + +Also, deleting a contact now requires double confirmation, so you are less likely to delete the contact accidentally. This deletion is irreversible, and the only way to re-connect would be using a new link. + +#### New way to start chat + + + +When you tap pencil button, you will see a large *New message* sheet, that adds new functions to the options you had before. + +Old options: +- *Add contact* to create a new 1-time invitation link, +- *Scan / paste link*: to use the link you received. It can be 1-time invitation, a public SimpleX address, or a link to join the group. +- *Create group* + +New options: +- Open archived chats. +- Accept pending contact requests. +- Connect to preset public addresses (we will add an option to add your own addresses here too). +- Search for your contacts. + +#### New chat themes + +We released the new themes [for Android and desktop apps](./20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md) in the previous version, and now they are available for iOS too. + +You can set different themes for different chat profiles you have, and for different conversations – it can help avoid mistakes about which conversation you are in. + +Also, these themes are compatible between platforms, so you can import the theme created on Android into iOS app and vice versa. + +#### Moderate like a pro + + + +As much as we disagree with the attacks on the freedom of speech on the society level – all people must be able to express their opinions – we also believe that the small community owners should have full control over which content is allowed and which is not. But as communities grow, bad actors begin to join in order to disrupt, subvert and troll the conversations. So, the moderation tools are critical for small public communities to thrive. + +SimpleX Chat already has several moderation tools available for community owners: +- Moderate individual messages. +- Set the default role of the new members to "observer" — they won't be able to send messages until you allow it. In addition to that, by enabling default messages for admins and owners only you can reach out to the new members and ask some questions before allowing to send messages. +- Block messages of a member for yourself only. +- Block a member for all other members — only admins and group owners can do that. + +With this version you can now select multiple messages at once and delete or moderate them, depending on your role in the community. The current version limits the number of messages that can be deleted to 20 — this limit will be increased to 200 messages in the next version. + +Also, this version makes profile images of the blocked members blurred, to prevent the abuse via inappropriate profile images. + +#### Increase font size + +Android and desktop apps now allow to increase font size inside the app, without changing the system settings. Desktop app also allows to zoom the whole screen — it can be helpful on some systems with a limited support of high density displays. + +These settings can be changed via Appearance settings. + +### New media options + +#### Play from the chat list + + + +Now you can interact with the media directly from the list of the chats. + +This is very convenient – when somebody sends you a voice message or a video, they can be played directly from the list of chats, without opening a conversation. Similarly, an image can be opened, a file can be saved, and the link with preview can be opened in the browser. + +And, in some circumstances, this is also more private, as you can interact with the media, without opening the whole conversation. + +We will add the option to return missed calls from the chat list in the next version. + +#### Blur for better privacy + +You can set all images and videos to blur in your app, and unblur them on tap (or on hover in desktop app). The blur level can be set in Privacy and security settings. + +#### Share from other apps + + + +Not much to brag about, as most iOS messaging apps allow it, and users expected it to be possible since the beginning. + +But iOS makes it much harder to develop the capability to share into the app than Android, so it's only in this version you can share images, videos, files and links into SimpleX Chat from other apps. + +### Improved networking and reduced battery usage + +This version includes the statistics of how your app communicates with all servers when sending and receiving messages and files. This information also includes the status of connection to all servers from which you receive messages — whether the connection is authorized to push messages from server to your device, and the share of these active connections. + +Please note, that when you send a message to a group, your app has to send it to each member separately, so sent message statistics account for that — it may seem to be quite a large number if you actively participate in some large groups. Also, message counts not only include visible messages you receive and send, but also any service messages, reactions, message updates, message deletions, etc. — this is the correct reflection of how much traffic your app uses. + +This information is only available to your device, we do NOT collect this information, even in the aggregate form. + +While the main reason we added this information is to reduce traffic and battery usage, to be able to identify any cases of high traffic, this version already reduced a lot battery and traffic usage, as reported by several beta-version users. + +## SimpleX network + +Some links to answer the most common questions: + +[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers). + +[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). + +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). + +[Frequently asked questions](../docs/FAQ.md). + +Please also see our [website](https://simplex.chat). + +## Please support us with your donations + +Huge thank you to everybody who donated to SimpleX Chat! + +You might ask: *Why do you need donations if you've just raised the investment?* + +Prioritizing users privacy and security, and also raising the investment, would have been impossible without your support and donations. + +Also, funding the work to transition the protocols to non-profit governance model would not have been possible without the donations we received from the users. + +Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure. + +Your donations help us raise more funds — any amount, even the price of the cup of coffee, makes a big difference for us. + +See [this section](https://github.com/simplex-chat/simplex-chat/tree/master#help-us-with-donations) for the ways to donate. + +Thank you, + +Evgeny + +SimpleX Chat founder diff --git a/blog/README.md b/blog/README.md index 34891be6ef..03afc15f8f 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,5 +1,27 @@ # Blog +Aug 14, 2024 [SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing](./20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) + +[SimpleX Chat: vision and funding 2.0](#simplex-chat-vision-and-funding-20): past, present, future. + +Announcing the investment from Jack Dorsey and Asymmetric. + +What's new in v6.0: +- Private message routing - now enabled by default +- New chat experience: + - connect to your friends faster. + - new reachable interface. + - and much more! +- Improved networking and reduced battery usage + +--- + +Jul 4, 2024 [The Future of Privacy: Enforcing Privacy Standards](./20240704-future-of-privacy-enforcing-privacy-standards.md) + +It's time we shift the focus: privacy should be a non-negotiable duty of technology providers, not just a right users must fight to protect, and not something that users can be asked to consent away as a condition of access to a service. + +--- + Jun 4, 2024 [SimpleX network: private message routing, v5.8 released with IP address protection and chat themes](./20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md) What's new in v5.8: diff --git a/blog/images/20240814-delete-contact-1.png b/blog/images/20240814-delete-contact-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7f356645023c89032b9d7d9d19d821ef8b42af8a GIT binary patch literal 217826 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

Vr%|^>r2=9LcA}QG`o97 zqwAE$lOiq1Cl4Dqn)|Z~Uh>R>J-Cbu2@NgUuGCD+Dnk}J$6{%u8U&qUJ^DbpS$UmV7C*1>;EPWWMd*|`~;S}ltdOYTv zUf1Vct97!RB@lsld5I97Pu}_ktYtY8Xm&y7%1WP=Z1ZE^{m~c5cSYvxlA166$~bi+ zEF%~)7CS75?J!<#t=gxh;ul%4-mQDFSQX~|N+;>T!z$VQD>^<*ABaOEm2LP(gW9!e z@RZKc8gg;bBpz~hWW#U7_{>NQCrp!$tWzrYKj=XsH%jS0(Ta57A)~22%^@>%sQZj5 z7c2K;3@c;TIc za|?RoZ#BOV$~)o|FVSbtXFJES^<^j{U-_y~`&*wjyS>gCsa=y7K-_e(^NS5^ba;_0 z`<3i>_n3>KKq9~5YnoYqh<=!+@|$-r^oCM&4ZEJ{N4>%E1=6V?_aukFjBRJm>09wK za@lQ`^l#9qxhwh7ulfaR{A%iV;Sovz5Q9%uW7FTjCvP4v&ms;kh3NPIvS2R;&e{~r0)sV|% zu`c@+17qk)12`@h@aoK6I_JATQY_mB?faCWQ=?fZ-pU=CwjS=5D`oa%wCrKuESlk< z3mpG|cE9@A-d^{q!xDsY*knHmgtE8;}*^t7Ldb@-byXgE9E{2JOG!139> zL(s)T9V99#c5?sa*c<=#$}t4;fr17TqCDh)^c&bH+h5ZOA5y|D9 z^(%8;MTbWiuh%n#Z#OqJ_su?eyTbbEK_@A(<KBwwuYVZ9{1dPaY1#c-R9<)<7X=?*niIqj;WuW342mugRddcWX^9o8MH8-K`i znFfkFYQt{mY6kMh=%}dB=!wK7cAVc98fuRyDfl#^B2X=l_&{aB zqNzqJjG*#`%H1`wF!Ko{B>bJ7|It3&V@e!#KAEEx6wbGNl`SBm021H#_#nm;PSXI^;ZeQ zl474wHWH1^ojIGXlVaa51;4GeG%=&8MDEVSnz_ZXWOjIGJ$}1RN;>T0pI`TCBb&~q zX7MnT8TnXE$@?|?aaeXZNuZ_e=$yG9>Q|ntC{WaJvV>%t+vlT!$p-`?Fq#UU9n*lZ z&E~~Y&(eZ@E*WMuvPCy*>HRs2V2NMqt)-y=xKG5)q$DptdQH2ic?(p+GU#6#?M&GI z#hGM&{KH1&E9Ns;WrPTSA*EVWhnGw0WG! z+Xqw>!czpvWKdj6m(t3mTk(!&6!zi@UKlYW;?&CQeG^%wOcC3SJFrlBSP!nmnWNQ@W7C{+@T9o3WJ=J$gdF9-8 zA&q%O{KOewJ47K`H_hz=c8Z&C4NJ2zZ*G;>dUv|sl4YI%hvwAadasQ*W-W=YHWu?d z&CgUUptO^b>jSQBZ|Bk`y5oY z${Jpr&35Gm?@dQ#N$n-MKLLzl_mIQ^7x1z>v0=GOl{v_p`tbXqea+n&+a{KTJOg=` z3e3(@*UcLOYPnAZQgSMHS7BRKN8mEDYzHAO#m;%9s`9TIn5Gr0pEIr-GQ-0`Qw&)( zm$uL5?HN(po6%i7xilZeZx*dT#6)HlqZ`^f-|LpPnzTvBn~AfUEaP|Q42$Vx-{;S{ zZbkjp@~e8%rqEYUa!5}klfADUOdtU7)^XyxZU#FjbvPWMy0|V>y)M-4t!(9viTr4E zy;XSj*3)J-1|$^ZVN_mc%!T zUf0WgEUwG;9CIb$ujF60VO(KObcUzCm*-2=9otLIs{&GlP-nj zI38r+HQ4hY=Pw*l=L5X3?uMIbd4$$%dw?VY9o983F6SJn0)H4I%nc!vb5{Y~KfaLX zf!v4NdpB8!)xeG1(k;!;)NoImY}TnVn0G~q=eOiM z?Z?d_?M}NP38i;0!gB1Pqh{^27d-3eM=`B1xytJdNi7jz;;PZze;V~}{Sr6X_4yr& z#f~xaBdcK>_vj)PQdtY36r2_kf1v*(kvnqrs}z7955RL;i{*RWh(G|5%EgrB*oKsL z^pW+Oeki}#f} zl6)fl?wiXuTs*~#cUVqGKi8z*uuCiCQaJtE3~$WsKp5J^=#Y}p4HEpsQBs@Rz}|UU z%rQc$B_naj=|2`0d8gfSC{7^FWZMnrAdF zUrA#nw0@bOY=5#4QIP`%8^K%G@p4l)OO4qVUyobQ`3Ep%b5x?LCDw5Z0YQvOoX1lp z^*2enIZi{GoB{8pRIY26@be{-2kGXS-h{(qcfK}l7y*Xr_LIBs6gy0zby_3=PcMNV zMF}5kUeSi)Ro+49&q^OKr&W9WY6`ZH;BTLrv#6{l8=MnnE+FTs#ee7;E7-Q^8Y?<> zOjNVwuqA$VxplRof0sfWG}$9V<@D zfu9aneXHfXj#T}Y<59&v{0k711W%%9eI%jMnw!PYy+=e2Ep z-?5!GX4BZV-Ka?#r?G7tP14wAqc*nD*tTukc~~3vpBp>`5m6Y1eqY=af!xh%dNy!qIES>fe25nc1Hj&uk@fzUw2&&qdnLzsj}*`C!-* zF8+d&e-J*KxNYLDCIQSRa&7_dv8gO@G;?I?y93&Kf{wI$kx-W##CMf#RTCIiiPo2w zsFkvEnQ_a~$A5z14IsRe^5XcS3TS7K|7ZcQzLEaQZXM_uYX;A61u1dDBLGnCEfwi? z^<#NExHbofd`w_}Vff7Pu=gWf!u!;HRqBm%oLqf5UTw7I2hZ~06ts5uPAI`Ck{A-J z<8Kbb5g7k*D=7y~Zou%RlkjB?OCZTMG}{a2ArXK{yNE)5k8Kj%a?HlFVRzwsKb`a0 zWLG*jZ{nNYUu@Sdj4JW9H{L9mds>O~`9x@z0X0C+e1&@57{yPco+tLt1e@VK4%-o2 zf9kJ&oglav1vXrIF4$@m-Q4jfx#=Z_P>U#4JzyxijHZ6?AeVsFrpX~IZy(@*o`jde z4;O5=i)QpBYMT8#uRvl*9KYwyQJ*#OF*Qq>8yN@OXnT=YY(hgHwp z|qc{7a_g)S$#Tockb!AJZeHgs0v9cV(2@)M`^Mhn6k=F7Hxp?Pa)%9(B7txWq`VpKx{d|oI{Ea6~r>$1W(DrAY(=fBKZ_uM8 zr)o1m{Mijrc3yJ;(9aT+*Suk(>tvVycrUFH;AuHO6YmTjbK%=|q4Jps-dfSTDDLOJ zQ4Im@3*UTrC&-{Ssj-(!`YZ*Zn0X8OL-b_O{Hq~YDrjX73nSoS*g&!;Yp_8opvAj5 zAzb)QI*E3Hm$qq)RD?ZK(lRGqyAA}!@&6Nf>ZH=17B&=+On+W=AzcWS@(A&^N}SAyNZw|wzi(eI>q*1bt`PM9H8jZkon z!dQIZZ1)X=olr)V-R|a0TsJo3`L=QX=Q8csdbSev>!Mt6s@ZC*7tm>1bnZS*^ryS_ z@e%Ga8=-~pCQ;?(YUIU!euGFQ)jJFBqLeiSP|gu>SHNyLMRq`v(@aD|JHsnm_8TI9 zkWf=bC1ya$wo>*dK1+6)xD?<#_)VVHK0qaJ-AC})tl35YjiiIpx`~R(6jaKnqpO51 z!M&Brj<$zusE3?w>}mIpar+99mVk90`cl^qni`O2JM|!OC|T~;wM656qvHs9M11Jb zJoAB|@Gk4p8U>pUCtG&&!xcmmoScA9s<+TqN7&ja=pjQcmR;+l?BH%dTHTM3_yivs zn!G+fa(}$49N1E-I|1?Cch}XK;vPrD(GUl^@QLvfr`17SXbaG==@&?5nrj&TvGqvV z_Fc{s$avsCWcRg4D#4mBO@QKMiojhNOpMSdV!3f zaCbjDOD!?ah7nvsj90{gsl0x~g63k%`JyK+SPH*GFvAIaqBatz_IdzyaaWb+vuS3$ z5X>DN^Ek=}84nck2-vi`UR3YhbmvBTX6kT<81CZZJ=o+fQU9_2TkeH!Pk$eaYHTK# zEf#Mh1)~%txjr!sVt1W5;|)vDxh>)gXkz1ZdoXk}Sk)2Yea7UwJO08DWb+Ccbr_nFm!tfviQ*6{ zXSrH8TMa<{mIoN^_Kpz$f;`|`3&0fDz|39d!o~8+(JU|3OL^%OQeght5{G?PK73tB z80BOUDHRFNYHs=H71S&{^fj{;)0qg;LWd`6VpP+l?|r}_P*2Ac1wOQZY5qRbVqAivmU3lI3|*S_ zVkSSUR{-TsrfWVEqDMd0q1}xdUb;AWy;p(pwP9EUao?g5+KQkKzy=2cwwyfE83 z(?$d98W1ej{vkpM*Y6o9Mwq+QInKu;91ST`eyg8yST2uZMvbYNf_*(heqECS*V4ss zCgZ=@mYpEleFHslr5HZk`tSQ(V^1`7B5}Jc7h4E<_MuUyx^y6TgEd9@n*|G#L!r@} z2Org!Y`OOJwg;7E2^EebejVl5M0hnp7fCI%W75(6ERrrb)OOcR4-~W%W%yFh2GiHu z4Z{#8W*Y3qHnfZI9seD@)JcJX2yxWHHC(Hk-YH$w50{S4F7RX|v`++6`9y*p;9^3= z#aQb1{N-1aetAb<791ZDgYdxoz{-k!k%y|}=E`ZUBh2)1-Rk;Bd!X>r{?N6~aqB+X zA2vUL$SYbFrwhfLjS$>V;<8(&mZw;?qJ!Uc^b_$EC}>3c0TIfeek3nj%bqCx_q`r~ zTzPFS3rF$|EUz?1y!3u3@V=lAyHFax5m*9#dNO)t3`BBOyIR*5%Y0ngMKdp`LbWN- z1*0w{VuqaxU4d9ZK&c-6!Vw?n&ApxQ;%L$TkA7Jo=N_An^q1!kSLU>+jaEO;GZ z@sYLr;$qrWFKz&Z6pl7j_!H_Vfd_2GKxpP3fUkf8?t+^@mLBQ3hjkeYAOyRj5SOHq z*6h~D(fwVR}$JU!`VS>tN^Z_EPH?Y)_pZdv{D;V^ryM;opllxOp z>^#PW3pAk;)IK5`U`aiaCTea_mz`yQ(3CHa+6DI`s`fZXH|GY>lF^jL=t#svMmxdW zdHy7%K6JM;$e}Xt=fhgDCqqQEmfujgESeI1?8`pIs*BNxp6xck(e84C7_*egtzfE1-m9vY-2bDN9hUHGd#AW-R_`t-JA}{;|B3v+*BH=l9M8)zP>-jhXK4q+6A(>HKP!9n0bgi9g_|<+ zNOgLCHy01zG?c5*8}M9ZyE<`(7r`bt3oQ0{@$qx43=9>h0^+$~rrwAiS*$phjgS1* zAy#Wp>xL6l7k1LP5L=sRpa|fpV?`~Wwsic1do`dBzBQBJItG-=)1RkWR7!G9X)3qJ zgXFLEK#eRB_H}S>&kNsS^NbNVQv4mC7odayPL+i&aJ#khujd_IJu9~KBw#A=OK*1fxZGJ!OWKjiU0dQ(+MGw2JhYfU*Q5mS6sAmfl z48WYM*nuU@O7TE2N@$_*25T4C$s=#Y=o=X3d~iY0>+kGKtm|ex7sMlx^CGa7x8mXw zY1u>B;_N#d)VirG{8hy3bs)VE#uZ)kwbq1sArk6?b_8s1m!furV_b-*JHf{9M3-7s zWWEu}NG4*rYN?cef>4n@{Jz`(V1U$jepin@*;wW^fd;5-(I!yKhgx!Da+$BR*m6;% z814@WU-|Hq4)qdxQtq$8cBgk{8IM`P_l+()t7wmOMPh>uLCGJp3RHgzNNbi+(spmX zxoo!BbKVSRXUnnwz=1!%cv+pvPF3sw)1d#-Hf?u1>33(_(_q`300=grMY%BcRi}{5 z{62!&g5equ3==qkP?IJBXE*ZwWz=RW;jZfi+l34MpX}`rip_|+bMNr3_|<5xGF0qJ z=7Y>VAcH}oOg3+@H9&|n>;!!R(}IEPA2!Hn19$baeQm%8S|wW$Au6}1taXMa1v$z3 z6sk3!J)m^9eN~FTv{bOSE9Sd6p=Wg)z6p6$%fZ)5o=Q7fJ;^?+!=7KuN5M_h;oQT- zL3kT4D^ug69F7Iac7HWyh5#Zh1xr5pNDXD56qWMR5aUHPwi}b7bQO_y1^v%{ys1EM z{)FY}WGefyBR^|BN!~TzTx2~Thh-paw-3ZyY@H*tB(MxC=1QAR6fPx8$%Jm;$@;!Q zVyUFFx9rQ%Vc5nxsy=^kRxN9aHzu29EG5i+%JN6Lcm!2ROP-o8X$B-K{&Ie|QG9+u z(Ptw^dM%agtsrRcR^X}T!G8mgk+6W9^B-IRR4CU+zh$b`A|IXw3Dm1k8ziKp$l*abd`WJ%-K_62C>YE8KaDFl@hzb=xajoNlH>w6CP=$5@nd;RCa!5xP4BD zc?92+LA%%I-eol^B_pzy_(1y_0p_Zdhx{q&br>F3bjCfGE1{lCcvDXwXRxF9P`JJd zw_Q3vi1z<7W&R-oXJ`+0ca}T})YPtu1-6+g7!9fC%)C_ML&>oRGf(REwpe0{G%7=T zDj3t!cv&QxYBnuN`=kP|tjaF=+mDb`SLGE+$V_h0luN2^KlO>@w z^UkPG0F|9Cnw{1J{TAg@h&^*&%^J-%SnNk`FjgDmeM49NsWxjF)({626%s)&iTeRJ z8^&1b9uRx{&Wbri4s_x#|1Gi0Cu3O#%c8vf7!Lq zoa~p%8EXM2%}7W0G`7&$(d8T4*2nOy+k2k2gd)GszXc0F%ml%poj^QstnV>+iJzMbulr_owQehl~aG#b8nvSv{ZvAkbkm8vGd9fJw( z#PnHvwLQolK7VsNVb$|$JGMR4(P55lEWS&Fkl?i9K%jDqj`f{y6dSJdMor&Mv5ku7FNL0#P&JPP~21msmF++G?cSp1t269^Y@ackF2$<`C@ zN@jLuZtVR5oU&XDJ9XzEM0zx3W4*`7J$F=p;3(VIDj5l#1Qsh;mu=h6KRjcZbGBaJ zACI3hN%6gf9RAXp(m56)s#n8GPTvtTKZ~hBc!A*H-_6ZpQ%9Ealw-z?d~7n8}*a^ zX`2AdRj^N$Dhzz?w5-SVd-8G_j7>@X+7DvCKSw1YFLnvUF0l%Bd_v_nGu{a2fn-fC z`1Kjm+q9&Y_Zo6-#wYH!a%gLcmO|;@IgA1pi0ac0f$2kngqXE?eMWac zpBaA5ju-Eh6qI?$qM9cpBJ!q$>3McS>a8LQu(SBrF_tArS*UkWj|=;LFHAun1T!w)3!Pqi{waL_&{!(N{YBe|;|xQW_M5I{ z7vKhsTku$`ws(F;@gJs7_Bq zV(oCg7yl@ee_9BI;H%uD6Gk42C09HUBYIV+!Kr73PGIZfTo;crFz@OJW-5MUmm^N{ z+!mBpmA28c^9|x7JR*20t<(pk0g=%I%P&s$H|w-hF8VV;m69L-X{k9n7&?}}IE#L- z`d1M*zkEQo@n@o~Ma)QkyTsl(#`}>=UP)e^KtaF$tf6A9 zQ!%o;`TAGg&T%5M!O7`*G^ZaTSk$wfAZgH_Dd@WagC##$zhDr#{qmJ=yicv1$v(O+ zHU#cIC=dgVG7UTgv~$`7rMgJ z@rMM~-_2GvN3|JA(dftNWgq}UHg__dmtzcVz#WpQ#ZbY(Y<_@cO|wBj2poJzVY4?o z>ND3Ke1uk&vdZo=b6Nkqq5sLhUhICOJM@{4)<)7e5AH=QfkrLuTaYVsHqpNu!w@`J z@_Tw^L)(s%jxS-s#!meOj*NOIt*2xrifeEkM0RNvJ+2hWd8O%XI+YP$MK^=^D6c?q zr%^Ed6TL(Oe{Tf$4@1Y5Kaf~^c|IzgM1{IrTUgKld0{tG0|R+&^<3c|K#GO)O(b)A zRa#Tv&v_J>IKb{!Y9u}!tEce(>eCGB#i$HyQN53K{;dSj z(2Wmqn}8UeCG55dl_a+zwP?{vCXHfubMng5u0s4$Ub|ohUzHMB57|Q4N6%DVqSeA> z*|3dax<8Rew+JFkJN^$Zgg^s!jEC?N8^pjA85#eTt^$woHct$tA;+QA2VW1=0^&o+ zB~SQV%}bWhkfsI#0iQmyU=T#mD;tUt#R!fTF*M?ua0HF&Wi+H8A12dKik)Nkk{<$* zt?|5VFaFS12&U$q)5$00bW6^QhkIPLDx+}gs0%>YrP&$^p&^Y6JUV>qG3!&Oq z>mAZ5p@`?!B-P<+{bpXT;tygb#iB^W#@AQ|Sza-`na7x)3j6L=7Yt&ci9MvWbHrz4x^}xW_OHB z#O*fMj!}>W6X!><6`m=J0;cCW@*QkP1Tane&wdnDwn&oN`p_s25zF?4Bj!zH{#HS= z7m5TzIFhRE^W_7=ee?EKue?#2mc-Cxeso_OWf#02EyLri_xX0sXTnly8dGdIH6YRt z+X?TY?(=BXSvI;n#&bmZf$8g)SFv+azrEZrjr4oZ4dCKUb{Vmt`0;{O;z{Vsq&C)$ zqnHHMt9JjA_%8!*&J8A}!I)N4K6?~U9V1%^De3j~eZDOwq0B;AzS_ij_WM)|XtCXY zHtxT^`5XY^G0nD;%f6bA&gd=##DsS;QY}`}Jh3N~jctWi=p45>bu%M^OJUY`DGw4O@CfbKlcN&6(cKVvEmktzuR2d?Jq8oRF0?|+&rq?3SB zs40L`N)Z2kS=FM)T5Y<-fI@w-P;HoamGgWgQVOoL@&A0)g81|K>GxT-uNpKn{g&M} zmKB>h!mOW$3lUq3uq71@!e^v zzv$-zTJS8-wEH_Dx-Ufj9B%(|DBt4kfRvhZZ|O(>T46K)ti;&5tnMY6jrcIS!a-bR z`ZU|MG)?_ELm_5cUSlIaMZMT2U31QZPD0yS)0Kd{-@`-I48+;e1sg zSHixc{?`Wl)u9i_zGmZSL;X>gHkRj;>Z&9)tgiH=p7MHpW`xLo9x80-pQ2H!Q{LeW zh!+20mjAILS;(k`UwP6x*_NtkD!F8qe55y^C_X?dgAa zAn*15KQ!@QeY=fX=vr*A`q*z`xr|Zlq-R0z_wM6PXGkPrgJ@d<#TP)a-T$ZrJY35_ zMeT39_|JKS0Uid+)DkOZxZDC`x5sovy@`}dF7>x9P_8!-?5RtEw{5ScUc8qvCSTJW z9Pz&suDzLPjis6Gn@a1Bix5)FD5tL!MG4pc znFMh#Q)w7K>|K1TQJkoG_>>bi0mToo0rC3>d1Fvqj~33cBs6%^f0p&%&Go$(&iYUi zk1N$apm#T7FONl@_y0^D6tMOC@eX6$ z+-5Ta1a+;bWMIlM-dDvWsr^#z@2r8Oi_ptuE8Y$CQDpr;Prd^?X;#3EN!47^BgaY% zU&b(;%wsW`n#5D-lK2tbBoZmZhcZta91=u`+WNJ_ zic@))|2uo~PhW_IK{ub5gYvtnCEBy$GFTlj$H_61$1O-x3 zN|P`j+%Q}@^y%7372qi>DkAjHPE!W65ImMR8}4MRRiyKF5kOBCfr@X|@OhP%r0FQp7#m@9_?+4$a{PX4{M+e5IH85OjP(z2mw_A|B)_(#38 zk8~EK37L=CN%zupaRWoVU}15Iv>I%;h()6RE*>3q7zjq~W-RT~eEPd;IJsz3rdk}U z`4N|X7PTJAgAA&lfJC^x>tJEF(gY9X=AmMLJ_|9!DGZHTrNN3}6v)4GrCkJqL6cs+ zdX9Dn5%6{Ajoxk2QCkMoOygM3m`og3-IC&)0wFmu%1$y8g^T4;Wy=(QIQuDj-sU9K zEvk$8)>Q<^u zsr2Pfw&-tD0_>Q+K8!P2Mb}-DbtCM*$QTDFgHMmM0AXAyl2EbyC@9Q?gLvURPEjM$ z|D%2EDhM)y<1rn1455+`Y_AYuz}8*4Wfl(Tk;57vk}AKj7e+zO=BV41F7uNQ<_Z5h z1i(@Oee2S#cRZN+;p-1}0~P8n8P@r4+U`n$KQk#SEgWqUF{;aHBUta5$HxCh$v0~i zrLf)%Hp~!!E0>B%tpuvQ9xV55B zQZm_HOVH=3EGfb<-q#T^%JnNtS-@H9?LaKy@5n%J3`Ed=-^d>LpZ#*i4uXLsvX0hY z^?X4YBlLZG*J^+s9<&|4sL@reAX?)ov!i4#rJ(E*1O#M-SM!yY$|v-mvLQ zm#fESJBATuuLV#pf^biwtYLUk#@fGiCG2HUr1+KpO%#UcKZecMODJny&Pu48@o75J zULjTNlk6Ogii$ntWs0Z26*u^9GjD{)e>3pFUY#|HYLZ>?c_rI6fr%btKy7IFHy$R2 z7#d%c3HrD;xQI}wvL&`7)qg(n_Qf4sQ1TT;7+sHAmOP*yMlA-C@Unstmz@LD8olPi z*0+IO3syS&SIqx(Oh9`x@mjvoW;Iqu=O~ZG5M3PZ56kNWKeU8m^8=;vuavP%)$rSJ z2id>=_b~xf2nPCIXXkR-piI}QiuGD z=6}Y+(@#LPkDTk50pm~!4Hm%biQgX;K$3Ys^nsaW^bZr4f&Cg6OIXYM2juwY;RkSE zo;*LKXHSFaJnj=alAz zkycz%WJ4R=1{;#uJSumBBtm35mn8T9_R5$F3|3;`NNtdqri-^g-ex_o{q1gOBR!K^ zh%OMno6xLvyD%1Zk#LREN5~<9n6ZNBu%`VS9PN^Jd)TVIb%M$HaLpx2_xDTp5J64M zlVWTiOn97eKGuGyRr2uPx|u1Ar;`{ur?r;QNPbE!IN8~V;|V=fb5jE9vETWMhN{0_ z{c6D8eknHw$ACg6At4F>wIBB6uNR)wz>OvHw1b3S^JgSTQLsvv%Ts#{i)xy7~>Ps9-8N`Uv72i`sV6cIqENECYkcLM@uzAe9DY{kz7s>1dH;)qe z#I1V2KJNqUm27A2M=GvjfIc+!M`TN~mxM-b3g`zbzk7ATpwC zf3P5AeQeOj$H1aICfS6?NQj6a6pt~^8=W-J>7l0{H=_kpj0jV#*)TEPd|bQe!LLn6 zysU%IwBo$#sTBs=;}Iy>W!+k?(Db&6v4de5G%^l2q0-+$zHII)!K2r2Qvo;TQ6!^aXV~ijD7xni;h7|LdOoF zslG#v@WJacFn-}Zux;?8`YcV9O=MQs^1NR^W&^^f!nIMH8K9`zjV-N(qx~+TeZr#{ zX68FP7H)>9(eMvRsl@!=vLvo-^(Itbyx&&`-~AGSxrzX}HCoCPgUL;!Qv+;}bH^na`~=fUaLRh^0T&SY0?%lY3e=qlSCc zw;&nxEXR2CHQQ~2lO4tc4D}_9CY#Z7_wBNhS{HuiN5b9pVwlo=m(8kcWDFU?iF|^R zW3+wkdhHX=@-&8vof^%B24){75XH@vs=5t+i@=q`fEzFF34OT-Yr1-(ug=4) zE4L@Fh2Q|aZIvKa@)}+_CE7NW1KIXG47oor_-f%F5`XigN`74L;ksb``aIc_YGF2- zG9At&|4MW!I3_<7HsmN>q^_UNDPW$zpZT72XQi0BJ@Tg_H99-YzHK&dnO8tN4{t zHUiXTi?F|@bwxLZDF74iC1JV}^#i))Di6!2UWq3yw_2k?ta<=ICHt1h?ga(eCaUcd za%W$%@gIc#Em{jiVpxs=Hdl+CscV(ceZ(73KxK!5U#r;7aBpnwT6H@t$uI^4c%%U= z(|uDA6>bcQQaeWZEsJ132Rg2r0v1PrW4<gmL;yRrQJ6 zVg7WHa)D>@G60mcK0F+jN%7wSGTbWn2NAB@>7UQp>fC72^V(rb%)Nh*zic-8PJJ za^CRkNUb9$j_d=_u+76Zz4v_IV~Ctbha?+BHLt+HD^TH*hwQm0ypP#-Co09%^;%-> zY0h=G=AWJ=_TgCm&X?#9tsaWI^ z(068izV&`}{vqvE4q%=6ZTG2dlE9ow8svV@nF;IVs_}Oiafl6UEK~I6sNpC^YC;tA zg%AH`i@d;-@CC-3V4VNtVJs-0Fx^4#VS2tzdNCE%7@fHog@5@eRb3mm>kiF5gF)W= zmiNZQl`osblVMa~V)SlbZW7L&&7yXGXTRBg^t^SZ-za{3)Ya(usCu$EgLFT6)Uy;j zxpqr%H`ify*-38odFC}YR_;lc>wsQ+tCaS56z&&J7l@)|%-JV!7nji4AY(nH1iNgk ze!_}vH1Xr)QJHthYdt!C@FoY4FhkH_4p>nNnrwW4aB99YpeQOkpn`Qj3_fF5 z+zNn4yXjL}z$&s@_Y6=&|4TGiW3qx~R|;P&2zopyP7ztzym-Vt+3+LRunBx9S1zG~ z*HJ|;o5cM$T&q2dO`t4@?W@=Q_I^#7rzICqIq(%~LB~H#kJKBGS6|OfYgU!K@acXH z;(yY00|@rr1ebx&3GJ(YO$~4EcFsZemm_0-;0V#&C)@6ZZ^#iu)cex3`fLSHS-@K! z5cWFj{jA~!F#pRg=23&gxNuMiQFdph-m&E#_ZJQ!yv^-ffJCldr`;B!G5_*L?_!{4 zVlfr|Uq;;MJ?MjG7zyTA2VHgVc_^`z+v*0_=}QnM)&Q7y}1dN4Eh~IVtW`Vp5Oo#l;k*&&Ev`cqj{%mp6qh z%DjqFArlYpPV~`KK z&d0P21i$wIMenm7n*yIFFW^D~$v2>`1^tbgjs;3DPF>WP1cxe4#EeCrXOr3pKo#JB z@?Z3kO!nYfA3;o_9}ot!q|Yy|+^|LZW+`A=5v%T=U-zW@%cfp;63;sXR6|k%9?a_D z_82%m67SX6Rk1Pe;9$DzPO??B5sEL5YS~}%slZJZ?0u})8*JdZufRT?M#rEn40b{q zgl88#xN@?K@3pu3w8Osa`0Z&c@Ji?i7J-K7);Qf!nd^Qlb`oCuPAD;%r|5|>^Iu+F&GizR^<Hdp7mAEtOD7|4O4c7wpD3ztT13p6|RvXC;sXLf6arrLzYjMQ^lyLP}7t(V+ z$q1dMBISp70qsjf?YuD2O#qy_q%gvm5D45`!Q29jlEWagtpm#&=Z#K>7o{Ie)Ad9T zDA-Q`w;7}o=qrNieopUi@ZM)^tP-eE@>J7L%iE1)T_#L9o>&|=FTN!x) z+){dQ$PUytVC$um|H(hr0)`sg=S`eew%Igvb2X`ZGyCQNJ(RoaQK`%=K3463f6WKn zxSrIOuouj=89GM9}XFK<;c(3E?_S)j@GcCuPfcw2@7Ho&zTdCN|V z!?yYY=Wm+h=|*L|(b0&B1WWQR0zSuY()Mnz7a>Rt;GFRs_AuJsYBA2?pZ^#{Qm`vyYKs-*?By{5e)s2^*?DFi@Y{8y819lM zqpsjx#GY(_B)+au&ImV?SUzn3A^IT`zianXPl6c=aW{gf_?Jxfc3kV`5Ogn^dzDni z?GbLPx)*231-rwNH(CE(Pumq)tT?IfUpwD~4{d~5s06_o|-8MvTDJ;4RFmt($z&`+fvE0 z>UhPBaGELMGRzV24uqyJ7KjhE?=PBl;vb(Pj zY1O+c$W?VgwM^fyL$2xo$>7$#s(nQg6mkCNf>3%{@7ALjhd%vRe)nBaooU!xuSip&5LA9QHK6J=r|2+?IFpgkD*3FUYVrIvHZ$%}C~y@^rnNu;al zOgo)Vctl_~T~dwCH$g7wMi42B(J~;7r$dNn0>Pjc;mej@y~S@nQ>^1Z(jnbmky?=p zF2t|1*L*nw&l1I-j_Y8@dp)HH9uA_2mtwmIp$S%L3t~AS=07Y#(xMOHunAIOQUtn| z6x_Ax`l8<(fw+eGLP^-lo1uM99$7W@6TC2czEh%C1Y-CZV2%j{*bMbs{qT|l>Kj65 z!MT68qepNo2~%rR+);sG_0pbIS+LMxXCP8Wk z^2uZ1+Saye2yu5b-Mk>fHXm^!A36;DDvx=^dvzt5YaS;g$aa%_s)+AB_F3nBVW;8% zHPG3;u&7mMEDRW-2gs$LSpzRz(a`nBr z+kl$_kSMAM3n*@Y-=+02Gu zB-fY0hZCPSpK~t$$FT9l26$AWAXj2bVk9MUb+_rS(x*FhFvR=&dqJtKZPHm0un-6| zSUhvsvAS_>tJPN8qaLAEm*3rqePMZyV=?Y+2M$W)t+%Ne&?*n}$kpG1y0b`R39u`# zU9`mVsUPF%l~!GQ)bu-ZeZ7(#!e|T3X&|yjlb@pz@mM%0jKlWBt8*&|U6y zncxA=1yzgOb| zq>YbSqUf2#GbjRgRK#P!s=Mpi>Fqhk-Tf@gZvU`tnl|#NNYL)KX-h`Z=laav^QT`V z{H;g^Ig6LlrEgsbtjGnlEczd9dHh~#+Wg}Z+PSP^9?^M`a;HCjp=VZy;XgG;ZfDRp zRXGZv!1G;Rk_5th!t&B#(*Zeu7Eh+R`dFOTTB-MIk1VJUnhW-{jx*INp0@h0?dTZbE|*(#;+hC8bYEC^}@DuCd}9Z#^sV(S`8-OKf4dH84baMW6#LrbRP-)Rg`BV8J8;XLqPATS)Y;GtAi4$}rImLvW0u`| z>wi_0%C>LAOQ|t8m}YC;H>RtOr;gIY;`Dv~2W)_KlZ1A}<|l$K>m>AaE>D_m7n%V( ztxdyt;%|GU0S{r~h1;IWz6~l&EZxblw}xp`FWJUvB0`6HAn_W`S&ogdd89JeGc8}waQ5b)ov4!%dlaV3M zeY8jR>B57UL_1+L21U>p5ATP}N?z9Dq0@i_8=AjOlMR*gbnx0AsaY>}Bm6bFN|8#q z7R$u&Rq%2IBjm`%FP}h1GQ$cq9(tTx^TB3Q9lyMeIq8YCUu1+h8(PT0{SnEPD(79}v=eXEDR8ZR~>6ps!! z*IJphV$1*D8VnQxU+2pH%GSE&ypdL&VU+H5NQrAO+qj{1c(7R3V}l9Ga(3>z5yQZ0 zzM1kUe5J`bbF3`~Yke#y9Ct?ClSoy-bWbijsM_RYU;pu%5T)ml?7+Us@zCnDg_(6L z^gh?y(P5$1EC{1|FiiNmAJT&KzTRJ3lk3G41!+e5_WH|oo6-BEAaw*50S|0uLxVgb zgPT42!3X*^Iy(6_@why7hg+KXKq19Svf#|nm-50q(+Oc7QMK@%OG+7KXsc%Q^G*wp z!FoWFdH78-7qWeC4W&KE6|O0w{t+mwK);#HpUgqmFNn{{$x*P!@S)=VzLwB$0fB4L z5ppOnad|<#V7Iiqpoov_NQMZa;lmD+;yJh(0r}HM(F>hiw)(B2iWm`|it{MgX0~&i#e<4C4eVdvTPqu`cYD_R5i-C zNZ@hy`;64L9~qt}nv^G&J)JuiWh-2mfTNw8$8|SY+i^Q6%=0$=`$2#QdLm#U1wQ!x zAO=0{Tp1rByPOIF8<+=AvkmlOIoU%l!p&qcX%)|Oa@)J*%&SuMv<6Sc$;>YC+UMW& zBpkhJq1o_oA24XTNEuZZPo5mp{$WTS<~cB)E9ZW?oHf@DwPN}xl8WD4#@Ci5H9O%z zMKmIo-eO!C-JkbJFu~PwaRZ&KptMXM+MWigxE9X;J6;LF>jby-<+CH#>xJGY4?`}i zldEF>rFitr(ByIyN;IW+K_O8a=wc5^?{HDDy;}t2BYsN8?h+WHprNAWP=nt_7%CZC zt;dfbqXgID3Is!?1%{-7eWwftmvEv7jnhAcp8JS&fX&)&Cj_BUH{uecQ{m z0_hM*$j^7yO%wnX^gOU0ZArZz9#}or_YwA(>*jn5HL*=m1O1-ZdiKtRub=g8d_=%{ zMZgZo`B9>_gKltiLQP>eZLk7aM=%H}YH#qlRe@kLcED=p%L?kI^O#D%*TaWk@S(`5 z24sXe5W61mixE?oI~9$GeFoyJr44Xm+EL0%vgTfiY$i$Q%k!{gcu&rh3sW3w z&c3}4DSG(BQjai%fT?J=v2ZyiEb(F%b<>2$JwCZQ~J%z=z!=L`$_J?LxQ82ScQOnJ_8eH}OpwuA;7OXj)p0TDW@R48AaD zoJpub<#gLE&t$^ZyQeB$gb!tOO!mnP=bDGam%{^bpZB+C0(b9BR9{vypKvk29Lit}#Vd}m&i_mdOl0xdJ}cIMtpm+$GMEhbD0MY) zj&10D{u{*O#yr18dyTZ2h}h5{1S^%r)MD;Ct5w_&%+JibUs&-bAhL2rpqqkyD0)GT zuX7a4D3t~z>1!rLwqV!5R{X86f-sZaZnz>L(?8dZ?XIC9GDF|3fkgOxfexG#`Ed+6 zv;qjjtb=@jUg{zz1ksY0muEn{R}D2A>-qrgLq>)kU&%Q(>T?2Ubh!8Aj05+E zrmX2?2x%ob)IXj$x8d$N*uV+20zn2LE#$)}XuP#+HB;85?CIO*4$kPwTLfo;llH_r zX^9Q|%H4GpF0ZsxGyiX?kmX)kC_aP@=Z>JM?q0w>nRWipd@Ll)7dl{_#9}5T=~ffE z-~IiK48m{ldof{IEuDyP~2kez3&aj@XwMHOsV~ZXO0f~M`{V2& zf=Wy+m){wH1WtKbEjj`PA-k0(gOcv39n0$Z$N)Dp%Yn&H&>bH=JX6kUnDR{6q*#n& zZX?oMV{OL_&dcFqMWyqCnUPRUBU2&Xn1{V)$|v?1^Sx zfuEz`bvk^}gSZDPn%~ME*$vdpAtgl90k4jUF69cQhd!>KCot_`H*s$?98{ifhJ?3# z*&wa8+BBNqVypDKIWAW0y_u;HCO4QuoJ&X|! z9n9wPnS4vMrZx9;Ncw!#d2bUe2J(5oohhBaoyv%WgNv1m0xy(dbM&{Ow+s88!MIm1 zz9CqSbqo|<)~GD0gG*Aq4u+5mUR*4Ot0KVy9EKm5U-CN;i*-bA5Sr%C`32j;8w+xFq>e{}CmIhIf22oI2 zx?>$%_ieZI}d|1rnjYp*!h`TLDY zEi_?FmVcMxFoK=$21eSuJfm>Frl;ObbfoJPSe%am3id~w~x zQ-AN*lI$ur5=Gj2?Yeq19c1Zm_RU-Mgt0FproN9vEfgdL)gkq*|FdjvS(NjQtPvk9 z5_EFmGx5UPmp@~L%Z-x^SE4xz5W+K;jFA;z`h&df8jE&xHk`5%bRs7TIwD+zp_XP(-)Ph!RN)!3ZVQmb$q-_`n6o(QT~JfLi@_R!i5suAYb>y8&pOs98pl#*xJxpR%56Afba^0XMb(48?<((?pedvA*HMyin zt~Co$d|HvxF1$mk|D8{-KcKkQ?2obgoIXdnQcQ-LeSA?`l*VskAn+!mpdeo&kCa5o zT`xUFNJ}Nc8ftaJb*DdLl}ty1HUu1l09_yJp+FOk{n_&BST4x~aU0*otCh|f72()F zLT)*ihm(U1@`lO9avwuTGdR72hv~oZ?VfbDzQd#G=upwEZ*MIzUw8W{c&x93w$k>( z`$@3dKa;)}apQ|c^GwT6md5Q3I@$p>EBMGE9XH7tD=$q9d}+T1RtohqC;XD%`S7r} z+ec#idwuDF)>ifX3IXehP!9Naww1=ciO`Z*pS5sHph1isk zC08UvhVHMBP-bLgXlCzzbATbCSN4#XP=*%GgXIJ+E%16il0^pn&}DGc!JTAJl}A6P z--zII{eC_gWmdo{th_%vux27$!e{KR)jAUhwc1cgK8uF@H~4;j^Y!X2;1~8gnIBll z9yU~9fnMoA%Bhrm^fjll9Z{Rsdtu+U-wB1hmrS;p2Qkl?6`8j%3ldd%U-69ey;C2@ z+H6;W-Wi8;YEYa7rFqUQMQ`0EIrPBhP18&kF_Pr|kPpbRkj@P#e}`9dG#i^9Fhg~@ zEH$gmt4k-3$O43j?RP5L`Ht5G<5)nhQVMK2DmGaj3X~LbB;JS>-#EB}hrpm-b1o@! z%f$@b!b}hfUep4)O-;)XP@6sEycI93$Oqg>c)lH(w0>4_|=wk=Cb@_8*E@XaHF$=C%%#L3_fgB?YFlCzqRF0L6gNZ9zebIiANQB_5gfaZgiH^9FNHFtd5<#=KHdf5j#gJIi4Vh` z(b>Q&2lIv!jFTaqnEBSjvtXFjBhcPV136EK$Ky^O(ZoHC>p@vGheV~TLjI(2GVt6rg+hn%3 z?@Om+3)3_{m<%3`k!WsB`Yoc-c~mL_UiJ3N!5S_{MTfO6Dy@83abv+>hZT*q@mXKj zy58bs4U-YG&Uw-((4#E`1T6TGsOYx1A&@Eb-y-neZMrv>r=z3bDX}m7QpeFLvZ~@u zaCI7)q*IMKB$$S&3-0Pz%~_#+c&&K0`e83oSt6HM*({RbD^2FK6xZq4dr26tap7X& zL;BUhyz%ngSr}i1O)Fi_yaO4GrCb@xCUj}Lk0^Cc3Xh=Wv#L{k`ifP-GshOWIAgE9 zp#{Gep#+~|FiO|I#P+Y|M9zq1=P=oxd9CJZ7(Z6QmEz2$RYB>ldxfwG>p3Dgd!r*% zSBD|8#q=1gU1d~QI!nOT?0!XZ4YaOzE46C>yE#)Q$Dj@I3;<1r7&1k?vL;icvhjix zT$EwaRxK~wVBM|N%+Ar7Us4uHVuA!2*;Xw9FR2wxeWBO}5V0W)GtHU^<;TUl%~J z(){yx>9xqkQny*rnIe$o9ZWI3R&U~KeBaEy7h99(N@aeDrs{lPG4TcSo-wYm#|WRR z5M)vaI#_#JCE7dnMw!Cq4DF5eRz)UAxJKWx)oPk-{;ByQ&}k}d0|bLqPh0Y9>4k2= z2pel^`fOejV7(Ihnc#Dym&|LJ=WL~E?!jA_R34D7D_ld!$aw>T&!1~4-1h%gmv3ES zOyeHGX>hKzENhrZb3+K;A)RVq8ZEZb_%vf2W8@O`I{~X-2gRDZ%4r&|JnT3V(a~R!6h=6t&dDKV@p=%J?LwVM^&sbD2;W@m$8@VA8 zH?yrLf`>MX14&yo6^aw?h8``ByY||w!w4Z{E_^r#+DA}TkWQprjo2`eg!&=o25rn~ z_xWp--NHBdZ77^Ib{Ct2US96e_#paJW#)G#m1=nRXS5BetuM-aZWFjis?=F#_cD@n zXIfNElf_KaYWC*lO@@<<$k7Ck$+4}GzotXdyhg%1XS4da3R3jw^Cn@VgipVZtq z4U6(u?$NEm*yjIpI{p)Q20S#>BpR9rZ#z_?_rjCE2E+-c%@Q91mVukm^X#WEVM(AHSV zJN~hYhD|Ww8;d`$vPHpJ(niq_6i-Gh55z%DJ(ot}>FLMGvW=NWSu$#7p3mBB1T%Wu zx(9p8z@MEpa}M2m!_eF4ODzT16m)RGI03Z5NUxAt9tVrU%^>w8EO-T2y4}P9-LNdG_Cd*?-ziA?S}2Z;9v=6@q!U z^0oOo`|<`~bIKYH=!G}LS$sBN+4|Yk<+;BJ@~hyM>!Jw4NgPs(>oeMjxDwUJIE2q% z&g#6cjv|0+B37CmVOy%@%`^&{h<)AI5PE8;RP%_2IoI+)JD;pN3*Tdd*BGzAhpG$L zA1u}k%OLg*kCi-@-7UX#&oJL1MP(IOwC;09E!*I`fngwIsTrr!Kz7Pq?c7@s_<7Hm zK`BB0zi;-xNaNlA+;*YJGRIct@Rb9~K}rVvZD6#$d|?2ZZ}qd(T!AsP(ZVl(O5NQk z6*S(U@ySlPZ6f*`@STsPBId+EwO6^t;)bF%%}btaXDRnb9y2-=XVF0s6@`<_wWGjG zbIOU<2z-&kd}z9e5!~gtYI`ZgBI3)JmY=;!=3Uxaja!Nkwq;3e)^jpvon7GAxQz1= z5mcmW>TRt#$sv$HWY%sM=4+e}S!y(TUhOvcT^kCnX49&d%>RB=$GLFqshM zKA7L7oexW=u;~mU7^j!smg{VR^YuA{5lf_MvF6lR~n&WZ%E={~vuaXygH$Bn}^WJNRAg$?E(3 z;prCM!%00UB&=wOz9GTU0`rj+o}1W2C5;5IZn-}gwQqx%wm)cHzE>qMAE@24%^=hB zTHLZMZnyEew4AA}7=zltEmEd##wT)xAo(F-QzJ~E^K%g5V4h2RmmCCqxAHhfGCGG# zJ6yX&4bwV?f~@k}&YTaWmYQ|7lU;A$OMY1_&1e&HGP4=Rm^0^WCy@7-7?foDF7Qsv z@BQGC7qV6-)vk6AW7Dfrz*XFy8{kbY4f!5R3b8itxJAH&(yJ@0w_&mIx@M-~ud=*G zYh2vVO%Sk+LKwNp4}qzj{_V6%)j&Lg#`cT7`Nc9aiKiQzr&&GMP2R?>-(T2(dAzIs zU(uhx!{PsL`{=(DssHAJQ4%=&f*WWW(y!u#b#Sbt2{mt$dG$vg_k3;hY0f-N!--yh ze}*d}Af>sw`pN_q?LFhqT$z?R)n0>lfA-D;QBA=-y)>Sy(sxJk)>WB)xASGXjaJ!^ z#pYW?ZAz1fcB^x#Vmzt)A1F@>o$&eM9l=~qq3Ypdep;;;mn!*OuHRuiE$k>dv|(P* zG|j>f3sLQS2-E!HyTz&P7hHhZF)hvUh3qit*L1!4@>yQoa(3G-V#k7sklKi3M6AuGx$TfoF4_;q z+vwLC*RNMne7Ik{c%4#FViZ=-ly!XAhT{(rWms*hVeKhmf6umju)e#`ibQdj-U+fDE0) zBC4MO`ObQ$smg0%n-vs9k_K`4Z>&HGOq7>Rq`IZm3KqAQyP#vmr?w?e(4LdY;c}Nk=9is$~T~oa93M(sw-v8Vfx=JgDvolukJ);@T~(Ej8%u znXz3(WlLa#I*ig7G6dnjjI=THZhA8CxUQ)!DMyfuj<;S`;fhPacE7NT%-&j<#_1fk zJXcC$WeJ`1M!QdnWA_BoVxiJg$4>KDBReuyW9<=NO5ii@%;6*j zftquRLFLMu%xAr}o)69ve0ck|oB6NF7Ef2-7i*ILOpE_>H(&d%zeZU>@u9+4haH-P z&Lj`(8AA1o`>np`RdmA^6+3G${AT!EVZOg$@nMsI}N>T=H}smjq{2?~>8-2zM1%SLi8Fow+jW#=c!JAp&D=q#_2z zKjEDM&IQj*eGB!(E%Ry}^q2K!J~oy@EEapx($3PXx|6jy4(ZO%h9XL5DWhw)Z*SUZ zy!JNULluIEFQXRkgbta{qpVgv9N$+&yI)m(;Sd-FpY&pZx>g={BF{G4?qFYR&&MRILllEnp?4%Ykx2L3$@;8*jV|dERp0D$3&n5+GD6``WotO z^`O*@zslX8Z=U-eV_T`hR%Z}%XDtI|-zgMi0o*4xV;~VBe);E+bcKeep!H?(-`*4< z7>;A`DE!z<|4Q^G<XM5Pus#pywa2nqgWx zWtc!r7n4qPNwoL+JH5~1iRz|7)!Cc}_rb4IQ_Ub~DQNa6^<7h`Y(yHY(KDM(v#CvH zko~Tt(iE12PkES`|OU6Cnq#OEc|u=)$zxgUe1G?i5MOccAGYdmBll=H?m%d%-v z;>W-$b-T;On(>9bvb?bxyLn7GbH#NARK>~~`7!r{@d}Vb4}|{(wH9BkZ0>k^qgmlg z(wACKM;Lrr-axkR)TYfj@6&6|G8dhe(_hqfjga&A=z(qWd>UFpb|V>d3a!3z;iUb) z&+SUWXP;Brv7nIaPVS)=K{mA4lc)V zQ-`hL+7Hb+Y_wco^&5R_%*MG=mg?OPF)b$`m51|{(+$rYk0)I+Op$qf`<|0lWTNRl z)Vj7ZI}pA}mdbH=IOF^|lvB6tZNs=nroB{u>~BC-uzFT9XZmMR5Bak3?f77nrx$2C z;*KjmPG+&D@fNai=c80_;njx*cRi(A{qY+l@blNW~}EtYCJoSlOqm>xVy| z#U{A@fztn(1a~;2T1cHi{WYH&sb@&5Er<4Oi@*oTxR zTcuByF=9b%9fpF5t9FkJ9kz#R@0L7!8=&U7q8(RrobztLcgbf2H>D*T5;^Y)Un)H^ zGro{MC$xE`Vf~HWIhp-{NO%06;+y~VZKr=nH4#Y)j+W<%Pt8k#R{b?sVUPq6{{B1} zk@Q)F8x-etvMLK3&kf*JTQL3-_oI;yRFa5bx5-KUd2KPFhx<{P%q*Reykv3~fUv)E zUYS+1>wo*djTxk9Yv4H`y<+UVv>SEO23Kw0@LuaClU&)wbKCl@*_htCo8^P6l1q+u zJ}{nC1-wB?`q!k7LlmUzMo?n3(wiC0!lTlcXIKV1NkI1H)ygnJd{-paRu|A&h}>vt zzEP5fduf%UUIz>GFeUka#({cvGba{X)qtCopT8Tf>>kvZ6P`4kivz(yb)?|OOEPPs z>ol{*tApySmzEK`!Bdvsy|1M?HEaq0Q= z#82-GyTy#p4D*rg)bGYjn{S@?#i6eqE8UA}-_VwO`r!MRkh+7a~|d!cPhzWVv03@yNz?t2dVCW@R15s@UX zttR@`O67|QT>l*6!U~>ribxMsM!UQ&TB@U{-JW04uMEDb+hIm$f1)S=1Zx={nfNsz`EdKga8oXT8$ut zhlv^&fR*#ebHH6oyLp^~&77w@%OEW(stgG{Az z9)K0LEu@V{@23-38dtLZW}Ph_f2z-#E{{lh*gKmpP|5z5G5xg>XL?4x_8t}&pZ4e) zpRT0LmtL)_p`JS|DR7uA7tI(~?C>gy4`dN`d5Py1PfJpiv>!ry{G&G4Pc)W z6p=y|O|?SzE9Rt)L!|%0&ztHU#Ngb-Lm7@sFxJr*CseTQL#mo(Ze6q}hd5lcR2IGa zRT>Q?KfIx|X|-g_dCSxkrnD$>xj{Vh5$h@DU04;#$M0zeaapu?o$&?TLFpL+XnGrD zvBTEy$7#;ozyHvzL$D}!N8HP4=S|~)Vvet@!8U(2BKHf<&tM1l<+9kz{P~(=%6+c3l=+y#=^V=eGy_c~niBG5O ztz0m!0OO)wh0~K_d0lm;*53pD8_M$#Hn6bsK}B~c8T=19_d#}qSQW43jK2_8jw)CY zrkKYSQM+7e^@V+Tuc@(DTWp@aHIM7WaWIap`rpO`0@(*qgJFiG4~ucY?F$saKiSMG zXXTQIrFZg&{IM zTUR#=$Q(gb8#2)a4s)UfZ{&Mkbc!`at(_QVfL^yU4pe`*k@s8DK%8u+bB{oe;g-Y6 zJkKsTw3>#r23tH&+EC)M8QL2xEBEIr4XxsxW9JJvFhp@eUwF5cYSyw_70!JPVvuAy z^bL>1Mg_fY5N`=Xl?d;#8A%azzTM4-FS;yKGd|A+UA>Cx_4f&pOz2X}9{SDrqK_1` z#aQa$igdHKy4;&KJ~qU-i4NrRVFgV7L!E$*7V6t0Dv|^yP>>kv)#D$7m#*O}Q? ze6`%NqG+J{ZjR1Uyz*4U!>+P-)jwq$0{H`G-E=C0bkY_xCDXC>eYoxwhrUX+UtQ|3LF;PgW=j%4I<(7Tv> zE-fGu>@|RUajJUVkRr88&9n2{W&5T|A*y#-#K8F`efV)xa8e0m(wbQCdEn1t>||^D zUu1k14|<`ZH!Snp-iZ1Ms|FVq*m&*r-cLezHOT6h`5r?kyCV1Bj@=OjX%1;pY=Hqy z>6bA-i*+=w58>VteAC4xpRv$uj_3MkK=W3k=6Oq5PTGUS??ek`gsi%3Etjfthm*KR zosT&xw}vVAct&*wZQ$Qwh*GE$g+<)YpUyNku!FvDTLTpGiNJ)mZrV=Vjl#9}9S7{O z>31X%WCTIue=N8ziFg$xoRaUIXTIgJQoWh}%D2O9GFnN~{JX@V_Gm_mQAB|bdN^ku zoiAd%K249@w&lc z-@9p0{P_d>)gA;zIl8dSjK74WPzfxX6=e$}JPa=9fC##f0X-|BeuoB)&WBa=)u!Cvvx#3~VY{w+pbzfA~?8yVug#E5$C`IFvL%mln=ZCveYIKd{75 zey*^)_ltI&<^O(xT;SpL3 zzM`;ur@61zhM=ef#(~K4v&@u9cj{$3(PSr5$Xe8n{IYkYCQeI~MYJhqT6oEr@%A41tV7G)o|cMJh> z+21njAV`4J&c}nQE_yLWjx^-WeMhE9G7Dj9S>m%R?S+Mduy3^AYqtt*4PgmyIg^mfNsnlG*4H-zVYb zi)dP5nzZo}i!Vs%EZM!3hd0!`<#I85G z3ktCpZK~qix#-^Qi7>r77XUl8-0wYtFCF#7Mvu1b!Y`)>%dr(tA*UB75c}@*vkLN_kiBmbTsWmP{i@&Ygr#g~zF~3GyA-Bk=`_jufDq2ddVn@M`pir&Y~r zLMIUWWb8^3j(@SXXvVU&+WG*eDx1G&toWvW7|W074|>eA+3u$eMId@Rv3ibp8)i7*7VS3;y54JJ`zO=>QdxBC`u^{n}-c$w?JG9q!RzU%@GO6bG~2 zhK7j^X6I55gX4o5tVESsyyYT1Y&LJALuTBDF}5iCpn8m~s38C$QJ6(yH607&(58CD zX`m5NSUp;CYMM$64eTm;tEAf1etJjgjnd4Jr`N=wl+U{K!9%MoM>_k^dA~~v-p908 zCx>+^!tO?L1kO1XnH;O-@i*Qj#p8k>7vJnprNK(*P4e9@}*&&uAA#(jva7?^lmTh(%ZCH z2-uv20UH)RuW!1#m)VhRN8H=xtjg-H^esOGAD@2IhovhxZJJyD1!^I3BR2Z`yA$|V zANR^12U;$2IbsLe8Tc$`6ih&drKK(CU_^B~{>uGEv@%pxS+oYS7#)AUp`kp=8~tbE zAyWKOxmlp~RAlWcXET16y6jKf?}=5y#mDfmINPyL;6xbRshmbC^u;zkn07RjFBs2u zth3Qq=Up-L$c=J2)^@50b98ck=%m`T%UdOe-E}7l!oe=A)67(tSvX3Q2ep81;)KcB z-~|nFgy%aymQC0CChZgQV_NP_51of8^iRX9AXw(QsMiR}pY zXjy6)u35k41mj;W__y==v#{~ov;Ht{MoHa68DD(1L?;1ugY^e=h63zcT~|K9Ge?)w zk<4jJ_&3=Q0|y!b+Xl?s(K0L|FR|3FdCT%1SNNx}EMVreO4ACt#i20Wi>> zlA~VyeN)K$p4=y%Rr4oON?HV3D8PW)pKlFUV?ofyXi7n^cr9YW#=tjry%`w#2_kM) z%}75|t+vMD2B&>e07+X23ch=WS3c~VL|KH`tYz&t%P(5Lm5bfUL!`^YGrn%*YEaFr zo(J0o)@ZZG6*P)L7L%LGXx&FpN$OVAm_Ge#*&ArdjdRd}%YoNa|7UW2eUvWb;T#9_{HV~o{)y~7F6*!85oKEhHXLrV)rC3i$(T*nIR@M>2 zkvy&~(GwxFF*>y1j@I}9gEY=cW8yQyv-~t*hVg$2*06cV-#-kLju2^HJ!Hb*(`}bu$F9w+}A)I z=7kp8_AoBp$@4YQ480Z0f*BtAaxt%4bei|0aAOgUQ9p=H^JSc_^Sm*A*g1g>i$)pV zVX)X5k{inrPq=GdeG$te+4i;H8GZF-2hBAe z>2pVVTRo{HJUk+>{)g!3l%=tObz+&}EQl!{qpCwMwfp44D@M}WFEOrlJvP>1p<`+) zR~EOkxGQe_!|g9&o==pj3Yw*J6rkAayHG`7!*ENfsu;>afj*Zp@tOMVSEamX@)}nu zuYT8`9TO{@q3!Dr{1weZs{SxYzF1P3T!fDA9-zFx(a6`O6EBKv``yEkN8Jb!?w|3( zGJ;OX=X)R3)=sH3@tc-is^vqKJiM-UCYAyT4h#?B8}Rs6aH^hjGm6l_G>Rj70k10y zhs_4Nl;;<89fl~>e!Fp_AP>l5VVf=X=o!;-pQALteNH~fYvUM7!D8qJnR!Quv`9dZ z&yL_3dYr6akms*_d5+SncivZw>tl@1G$3euE8olEW)AUX6>-D%rNDe=#2piprms-l zc~6=(7Y#O~OJ1a`c1e@F;OY43(dN~~D6-__Oz6VSUT;AXMXgd>aFBvtL6i#4m9hS1 z@ZF&^k_Z^E127i4_=iNr5qB0L7YBRpjG>SnPyvYaIbC-$gXBX$(-L9Z!sY}6q+=al z;n4sbz{s^t9|pU{4?9(__GVijodDykOK4q&li>4Vek=#HfnH)V3OAE6XYZj`N3Bnf z006*yt_!UVfEmp=?vebDb(gK{G_AfWHc6WwBC`5m^MtUG;*p5ABpG!1rNW*gC8MDK z&-erjAkh~|FLf}?zl=xs%XEWa%%sAxSM(G>IoA8qwpqU&)nhj|{v*WuVb&X;gnq!8 z9PI)L0~Uw>OV5J@`eJ<(ZaZ|nE~gDn86;x+0f+zi#g#}Hr+|Vh;B()^IeG$Dl*CqQ z&6Bv}Ura%@e0=|o%OKC@vXjmdbQnsp0;~v@C(`=6EC$Vcsl|+gTG$!SxK~neHzd@L zyzS$_cq*_& z+}2bpIZ&jeo%Kgy1pfABq6duhtO+t3!{<=&vHeGh?0CIU$NEuXkML&9^eX{~xEn3{ zIhDcGd#7Ov45|mRE%`FX435qze55tfEP7%mt(S)S(#xIvKLdL)T()B*E+UgIZO6w* z7af^hlKWLSABOPS#%5lL13UQ1Slcsg(`k4Gq9!`tlaf+ zgJd&8PxcU(Uh>3RW>z7`SCZ3mmvN;+KI`dJ(1ZbJ7<^eZq{FwTMEvJd7?bghy-7uf2&<%b09G@Cq>} zIf^`D-?}KAQCW+`nR|Dku)5|)-2!6p55{@8$zDRTg}nRO7(JiWN#t16CIC8qAjhtE zx)FryMf|w%PIT+F|F~}khWMMw_{e`t0grh}&mA$mCq6wDq!P(XmUYjlr`>W;<3w5YbrcAY1b?>y+9jU2Vi_ff7gm6h;}Nyq&DjL1O{kJ z3_sm@KXD|WXKZ2+p^~C>m}Me8P?pgTU!ze2&?R2bBArtt(Ix(Dkqxgd-w!YQ`zCm1 zk`qY^VUhx#hht;5#V>w$M;ws*phDa5j(i9ua2qE$&A!o~5urIRMA07o^PNffQPD>z z;fG0F`qHz40n4-U-MBBd`I<;1p+3de@b53k0cztu-Rn^VK?komXUl;$8hUQVCwCD~ z*AW>tKkdj!GLgkY+`#j(37JOBC<0lx9OGFcFu(#+m66eN`g(p8Y_frcZzd8Y>d1Km zFrya)p)a8g%dUAw2%^`cQzH`TNyTnIX8f|-7JLoy>i9=d!yFdH%5sBm>t@iPYzpv{n|YhF^bvz^ z{`zGvEk@D8rBM3{xLa~m;xAAxs8^Eo11_)#0_8uG`JFz40iwXlM5I z#E8psZ&9m=(58&SqGO!R=s-(pr{5`slfRAHKhY44G=^xteBiXZ`Fs7Z@$UuESp#RcM1EeDExbXz(wHshk}olf3R}Wi z!#@^v^GT@ZU_sKvrODj+_`B-E40;qLTOcaLDezbd3%y5WlF&&8bK*qc0fK= z!4aLFzvN!PVjrP)tWp-Mgn5gth?YN7Gb%TqTQ8$ z0UPE@>|No6Qy}-+-ldC0J(7$2h!E&SdLTQTD|Yu%xX%?TTB<6%oN{j^!^Rs2`HR2p z3FS%L)LusIS4E+2P;@-H z4a(DJC;eHtb?>!rut&Qoj6b~-k;Yf|;q2CT*6bs_R3F5Gec2`C#h40fV_{?v+1%S) zpg(z-(T)b?L81=UA41v&z~*)Q$1rn3i@2wK;iIbbha+c?+i4Amd{S;T1tcboWnQNB z*$U~GXi@LgpB^it5p%6iC#KH(5nzMX`)LpYNg0GAwUPl~jFDKz2U#z4e11@nNFnX` z0E)z@$kX444UN_s@7w;-10eVo=={VG0e_AE155p+{i_(F9?w}Q?OiPY<*ON@V7<$@ zRjgo}$OiE@-jM4Y?127dLfPME%rhgRSS4Qjan5Ha?K64mp`3-mdM@KXl&S1dH^%GN z$4XF=09<%Q0O=vh3b=_xuGE$9qOcR@nSl>JHJNm?>Matk-D12_v2y#{cpAnd903P1w>FqqEMnq0Yqp_NiIehD~P6$^rH-H zck*Y62m0w@DRP}YeWKAXd{Eg_2|Mh31bGhd*4Q})FgLRC4Lbm?h9|%(Be~C=v(Jps z-vtm(*b%NcMpF;HVxFK0QV2ik-H&RrdmO4^9GNslE&;W-N1!=emG8(u=s!hBIoCM( z^k!ut2@8>*zzR4~!XSp|b7!=<9+ZR-WseLm)XyKf>BOl3Q9M3eM~OoQ9hj|3t-$fA zwLU^Y2FioE@o(z@w~YWiz~4F;G}l$HcCYwZTYAG5wJm_4z!WsF&lU009>B3gfJ$5_ zej-Uxn+nXvxZ8-Za`m=jU28sS*#E_aq8Uwgj0+^QZHm~?rF>*AQnk6;g)pctsZd2* z!Teg2Pr5`e5A*mQkAl$E@1@9N26ND4-TI#2xN1vmC2*Vx6H5v@a)bF*X8G$R2pb~e z;z+(N)x4z8DKERCJV+7FhYhOszn=nz=(7Cw!-x5JIp!up? zKE;eCMJ#HbE_(4_3sm=hGOm?YCEIL3(0+~fm#c6tn_zYJZ#e3uB4+4V7z3u>BE2+j zXPWyN591v5JE$nhj8NlqMi0d{TKMH4d-GQ~_m*r%BC|fC$NK?QM#fJO%oU)SkKLiF zydPavmjBuCLyP)gMzda>S;(T>X!_wT3r8)Am-3nafJ~?e{j;=A5)HxoK5?f}hCTa% zjim>-4vE0y_deGv=u6tTuWF`WkAV(kR3lPcw(y+IzG?=CFj9>5tYAe-b>jJ;e?H01 zW=d_n2u2RLU!HcY_nynsj_EX#aQF#~g`ySY#Dba zwo#dF@_{azWXQU2`*ViKch5-j6@XS{rRi5DQ9l8{Dl~uB`5Yz!f-pL}DB+P^8B8;> z54{E|tZpAv+5^gUnJ4#g#g(^7I1j?8PjcfG?->No){z>OWAJpniLf{a<=0l0X4vKmn?C~$J#UI1P{L=-b_ zS=Rl#1MqsUqTh!)QcVGHTBP>aX;Z8bOuV6Sd#KJ`MVNH;GRRacpK2g`q2XwSek74 z7bdTzQT0pV{p^}e$FX@$zL;1b8nfZ#EzQBE?_4+faFMqe=TToNa%_vQq!YYtU{#VU zcO;Fcu;LQS+Ol@%)Z9rcVb@i+b?-AEnN`%T{Q0qj$Q!-+1`a(= zAmZ@hut74tl99sohvJZt{_%As)U}_>={o&K?XU0|tvs&wKwWbK-h-m&pQz1oH%ZR& zzW2iig9rS`Uvtyz>*HWwzW5o4s8l_h3pAHC8|IySCR3m0>TSO7IOVDIfOM2K{pZ&F z%MG1xRG^KPq7G@wSMK=rT=K%qp_5K${7!SRsu^kOuzhnk-HGyzbqF4^dM9 zC3QJ1@2U$7#okCO*bBKUqy%-Bm&qazo|?BYx+&w^lOGx^3SCm(fGyO-RlU(2e?QS0 z%v5P1qu`MY@771CIox`1cyQWcM+T}x4MTvmOJe~xh6Eeq;ZL`=HXc{iSmh>gR7yui zeD(HrIhnTuJj#e|Du?AFodX~sSafn0kQB_ad;wC=I_KlgJd z%BUxP5K;;Wm^mPAmEzV!cqZG$8D#M)Q{m1(qI>ZDNHac}=a^_vZTg1%UOVsD;xv4Q zW`#b$@QY#ImN3|~<)hBhNCDo?$hd}IAZ*qve>mYLYNL7>2bJizji-E8_I?w?DEdf~ z;iaYeyUeBQZtC6Lsrg!Rx)D@{$t%^&IF; z2`n9}02-rcAQz&?8r&AUCT~Q!Fz_l>(bH$_HoBEqe4IMQezsh%g&JtYIcoJ$O87aC zg3d&3$~KNoC>ElZl<^cs2PanvE7~(_zPN03axALH0NtBNcFDyeKZLS0shf|qm zMPE+|9xk~sOExAwE=_-u;f~z)ZHcbn5(RyX z;>e&L=0PCdy}VxYVL?`s$236DqUU4y9oXghqX2M5OUQ!Jpx) zz478RRVR&?GunP3R9xaJ)P`qWkEmO?7z4Z;TR0Z}Qof%~zyaoSwS~!-9x60=rn{d> zRG`5Cj|CZO_}QhS@9pPLk1?u1suJ}3n4ZWa=>fs&TpF6M#g_jLVCdWSo>wAqzF)W7 z%WGagbbZES;rTQN%JQu@iXadkmq~-WSl7~u-`3$DFDz;8>Sv-&e6ObbBGqjW^_Jo| z4z<_AiEGPOv}zax8$#e_*uYz)tF@aK1~}hsI!XV9JC_mSzj6O9f&M&LXdM%3c>H~Z zTd%$T;xy9$15_iTY1?4=z&P)XK^rS2aI>3VA>(7$?37F(4UKxs^}_OvcHI3;=`U!@ zk5P~A^>+QZzsvz&G{-YNpGcg-RXB~0rt}3in|Jx-nEZZ@RB5BN$&n~Qq13_{9_N1! zd$@!5`XX=?j*qqcHgXc2r8=HAd3XT;_qv%5+L4UB%TL#ZVfc?=fejJ_xEzfVz zYmjvx#6!WNLi1@n0{jBf+rmxg7|DNDi0z#SGfIIfT(SEN3@}i;8TR}iuHG`N$v^)4 z1}UXO1SAy%>6A{9l2TCtNu?!6jnRmLbaxB{L8V7`=ja}6^k@c*7&)%}?)&~<*LB>F z_uxE_bD!_0zprrD1leIkg_9l|tc$+F_#IEl1z4)Pu5blrmEe^Jx>erZqnZazqgRs5wx^gp8>w>mH zb6+II;?qoz(?@BA8`Ru9e9;W_!kPE1!`xqSa*JP|1vnxXydV`8Hf6u_i?d}MXJPIB zXS4yY4}7QPB0nHic#S;3m2)|Vp z#8TEU9;P7aPP74e!7N#pegUsg0K+1wbItU2JUPfVP~c;jF>&MPb{$57=NwGT0Mo1M zl}miSaqJVGp)P!#KdKVV!N*-)UDTU+KhL`!<8xC@KZ}L@m2*l4;BoC{xk*KQ-lDW$ zCs8fHcSf>CK}W4D7>>4l{T(6buo~sR zLQgXPJD}Q&J64b=0$1d;Ns5s&eLSJ6Id1#n+wlmzzeJ$9Nrrbu9Q=bjSlVlM^Fa;% z*56FE)LFTmfJDQYI>NFg#dTyNE?wY$H2-}{Lq%zHIcT=_NOAmb+~x10j{k~A`jkdc zpIn%rOb7VtrK0@$KpN43K)RYXWlRfMZri_`1Us7p!G|9^!42f&M#b$}HR2L?P7@I& zE;7fML}As_tr~)1l>a+jreJ^$?d9u!>sSB*$R^Q)sFX$A)m%H^PkptX*md#LNT9^C zg?oVNzCe;imZuk5^eI^Tp~c;L-*B{?_)yvnZ?U3m1h;g%Y#hzKlj8mVj9l$a7-=WJ z2yIuxl&-8DY!#V~e3z@6i)Sj!%CEmTyW>Y43OKPHDf?4dOkeW*m(5b0bFuSM18&nn zcpSPoShq89r6*=!T<1{Y{E*{tu0-UX-Q&t40V8|F$)W=ZowBsk;0B=p} z$UHDpov@{Vk(b7iYdpr zq_uH+zryC;Avu9VZjR(Z0+;IX8iO(yNBG|nQVk@^9NNUpI5I^Qb#y0jJW;xJ8WH8I z&#vy?gn-J>2vWb;b$?|pVLawwqVO1*P@YxtFZ1e6j}qLsSD2*l11rDXzDNp_9)FPW z`!-)-?ZVDXaYS5M#I2ts4N{kwsnoT>vT!Dz-J>-wp)DZjTqR#iFASO~OPu|V`;$@IPm$*a;Bt^!$#7lbG>N<_f%DIZ`$9s(sQlt4XAbK^ zr5&#i#({+@sp(vndcB6<{{v};j1C18d3E#K9O!QZHhOeYaFZejB@N=4!qVL25+hvNCEYkn>-mg zN*YseWhQJXQ!y}Uc8H>{8LX~Yx&iIw5#^g@ZanDNoL@M&F8s0NbX}ii)EuDjbSdMG%yEMaN>OK*WIyqGd@uTEiBJt^HpNNHI}8kpV2RYMvs&?{$Q zljNnLLFTqXw{dqfcK6+kxU#@n=NHu+dz663f89^=91l>Lebe-{#EF^=~|$6rM7PEdFccEwf`Lc~XbDG^U@rSGZA)fy<``CJ43_l`H)?5-ZEy%6bR^9de0o}Kc$q_YR zmjSI(@^k45x{X6+LXn|a`x?iNIQ@HhtTOnst1;*Jc!b&SFu~6y^6ZbsCn4$4bOL4{ zlecbW3Mt|hTg$VtI+qByZi>$A`0?tVUQ+sCd;)=W*}Npg>5=O*ZSf88Fs1Kqpg3YylLow$sVp$ElJC%MF~J;WaLZl zX_;_!d>;ikG+&VOFp}kvYt&}P>upXQ;mYq;KiJYopCZA_ji287&A+hrey8|LLK%2p z*yCDO%bsqX9@pYki?L*SUujR$j((2@#s$)nys01!I_ek|)iby;doq0auT)QbFt~!S z#RSiT`_o1oxtk87Xxcg+->|EEz^6UA`nC(jzZb!k zY1m4&+Hfv! z6+X?_kf@7USa6%5Jvc1AxTa*QG^Mzw(6qxP?FHA7KcBoTq49J93s=Y99KoWUnT!P% zej8{w{;PN(=!s`wh@cv`n@|kUo4ofi$Dh$Bl4j$(&E;!3>v$DyO2*ZBZwbEa>>WzH z&X6a;fJ190>z(W!|DEhh|HI}Gw*iu#}_HY)v9`*6xXNf92{G;|C1?dw9ej)lA<{1UT{#R9V5i5Qo@1mKniFZ)@)l1DKqFvk*2QRgQ zfXCr_!J@X7H9?g3(89nL;Hj04tdC)~RI%U`{o@^i3v_6~A>aF~^P2Ak3dKVBo6q_1 z+G>Q0r)&&ofW1>6e*br%+Jeucc*$;M#8&tp$7(&TTgK)#rpgh1M3ZFV2CK3KaYaGJ zV|KsR{R)yO0;H95yx*k&ixYpv8B@Cg@g}as%j;nt3rj~xkZrvn_4ZPc@>3C(ad;z% z^mCNUQ4R`5n}9Gh+K$kb+2nuq;!eGL24dec?CJN=7EDQ7txciTj6%60+$TiYqaNgMQFNc>Yk z-8M^Q_*0DT+;%(fVbGr(&BD*Hh9x03slL(29z_Zl+jQsshDAmyP+JwUz&)uprvN|9BwnTd zb+nQzM)9~5$G)3ACry{4TWQA*K|P%U5>f`fzxsC|_R@ghuc37dU}ekAwAtgMNM|jQF zRy2!~p{aYm-uEomv6hQW9AQo`%2|@E6j&D}-1Nrl?H3>dlSi>@V80d3>^S1JtL%Y) zGb@S$XxF2Mz!4t#&w zzC0D&DiYy?HB29?%W`&OF9XCc#>hsPh z=!bsWC%nvfUb_2}Yg80Fr@c^~pvli4zh`S5CWW2K9-{}6WDA8>*>(BP^kp`5VJLYD zY;s5p-zh#RzI3uKTgGmv|G!1z=fVbND*9@d3B(Jl50+NgGym;s#zd>xaF^!nj6iCn8GY+uaP$S&G#;m3jP+WJpU zO43Xc$Tg|%{BPG-*ZJ{p{49dWeH0Tzv7VnML2Cirl!s)#=i`IV!W6P5oV(ri$({?*YO-p2ryEBc=e%~>uVzznI3$tb(=MLUzN78| z5&mzBzSaMUQ~1*qhyQz5EBE|mD{z`|x&981S-!G9hSei%SH_nLc(CA59Ic2@&y`<5 zwQh}u-d&WnyUa^hoQ&bd>1PCxW3` z@i!$n07(E|EDF@6D2Icn@bAMAs2mB{>>gfTowEt7%Fumj;s5AKL$X@Iqkw`{cW~{KQjDM>+*j^YOiQBC5vq4%Gn?jz#CX%U?lP zo+hP~t;(+Je^ylmh3hlwyaehRqaH2#9>-Vc1tCYxB>x1l@6EJEIh)s;H<#_#`nUF$ zeX6az^&|AO&V@KkNt*DB=adM^_xoa|y{)g7dd*QrmC#`GXJo-g_c)Yj-sj+R^?j{% z3EoP!inZ=sn?LZP$@s*SI8q z<9Q{$#x|HBRpX{6H`p<3xp^v*r$RD@Vi==iq9)l|-AIWLMjxvzuJ@fpQso{W>3~48 zYczTp@ctwcuQX3K6JzJ1gQL-FrlH=3$4<%qJ^a#35_zCNp@aYEW_4Nmv_X?PH;Tac1} z(V7M{;GIju^L=MR*#!M2PL?!Y@;lCnmCGbWoHcL#tNR_fgzz!fplD{cELP8hWoTC)G%v#vs!nc_bsgXWUm4d(#1`Mp$ z1m%DnldqzGYF3NSCVM^vuGVU<60SLDY;M2Or&#VP?womB$GKK=RR6H#HRZ9()7Cmr zsu5&!=##T^b)mbOSz{mj;o*%r;3=c=;=MTiD+p=GlRyzx#EXY-P?L>OUX$iX$J*+l zj<#0k_&{b!mr8YAW>_sa(~>icyyg*Tq|7ePMe?7C#$p4gz-w9$Yf#+ks)~cR1ga?z zjgxh)ssQD0l54+aI^uZ0^Bx9ERsQ%$eGB&xWc{!~!lyrfQ#*LUPBY!f3?h^PAz~;4 zPqjqSwztxP(GIE*ZSWBmRL!TQaQEKSPEl}aJ2bVN`95g@;zHo_P z?(8%geellGy3G8M-CClBnuM=;Mi)OCx?y3gs2AEl==p4H2pv-#BDL0sj09iX4e_A1 zj>Ue*rLALn`fdINQJpjddfn;RC~If4>8~3WSI9c&abLcTt+y+lbNDkfGbJ6TM}Wna z2!Kydd(J%R2M3^k1eQ5}d`PY@mV3wa;X%do*b~#4y7x?FGb7N;lKoFKh}-r2OVs{zP+$5flhvE80enz zacgbsYGC0Et`8m15PM+HPgwY6JCP_&!E)4JWm-LtLo8dm6rG(VKphB&XdZVw8;!O6 z_E-p9zqoLO1JPo;dc8Nd6C|Am@+`xhA+XVun9Dwjy-+6o!0=e_D~IBFc2v-S?@;XZ zftvQgX`HRe+xmNgl9La-?6O_RK9<$;6zSEY#3FX`${vA12ZDm6&emJimhWtZ@XR_r zGQRyCJOZcEd6d8CkE+!4qnshnH0qHc^jH#b9 zLSS}FCkh{mo14I>Ek*6I9J#cAe&FId551g|l3mo)*?U81rY9`Q|8mKt7YkAMTIbGq z3l<ITB5p1ZTFcy3NWt)WjM8P&;{@J-!e(!gc4f^TzUeB z9L+>ZNnZ(R9(V(AIs8q4R{k9nTKxCNmkiZ%))l`H zEy|4Xw6B15n;6;Aa|LwAoWAwouzq=y`fQN?LydrJKlg)b)V7UZ)08>pHB7;_sNr@I zK|rM-&}6pvNI#{vO02tO`PUDWn)_79u~O>|AF$1zQt61g-e*-F^Q)WEtJ?Yi(G9WQ z+LZg)G_`!;d->O24Cq9hxm~=XrN}=C5KYwgU7A15q1L?5MM!(^LCE)x@?hot?thDR zZQ6fd0$ZV5kFO=mRNn5M(9=gI*UM343pYcb(^B1K&zWYwzD+xu$ap4ZG8z+}T$!`F z|dmSa8(HN-XE&aLnPz>}Fom1R8%35QcF91r&G5>D6JDq{LDRFTJU#-m{KuV48 z&*?1$U00KTbF4W{dB;`&19vQkyvlM)?UuQiqbm|^c$Jzi+%bQJ14eR!dS5_n-4dAv zA>jDheQk#Q&83r61Gu7}z&i~r02Xw66D5Rum4?^e;Y@p|l&qgg=H7*hE1)Ue`i2xL zMFCf$oC8!c(_IP%&Lc(PxN+9DXkB-s$V1&=<0mjtAG46BuH-^}%Gs0;Ui(U5j^_RU zKBV$XjwkWd^RW9q`a_PN0|gWuF3O%{IDG4>Q`DFYKC60aEAR{@bs?!f!7NKGzN`Z$2LNL!iW} z6-{Q-m3XN>2c8Rn{17ox&7s!LS4#v0kY}qbpd;5`iZ?~qIb@?I)rn!3&)c`fSjcSHVz<2`srg$%pk@UohUVTbt#8^ThCX8jV}lDzImU zrU#>RTLDrIz=NhO1mId2sKE|Omz|D_qyR284~-;}DAUuXJs8Nq#g?o#}iZr7rfh<$v|F%&Ek$X!OCn)Xxu0MN;m%@qhc4J0n+s)RV7Xj*w*QSNDyBka(Ci2d_Nv}^CU3y44_LlTa8h{gIG|7676D0{ z2tm6cua^SDuCbu4W)Gppp^-G_Iqrd_&3$sWD5uXpf2H@MHbSOCh&-(0j$~X$_nBN0 z_o-FqIrJ{+)_N*@t!w=(&yrkbruN`E35{9f)g@3{SjkPU-E7lToWf8Jx3*pC0k1gn z-RQeCu-Tmlri|T`LmUawj`Cc140?FoT@jFC!m;?&+~dFtrIn!@bi%Cf1%wA?IhCE3 z!a4T&rHGT@XNHK)K+nAcR85k_D)$LtyJe!)M#Af5+#&yeZS%h~A0~LObFR-0X@Kls zxvlbV7)0_=$ZnRJ#nQV1G2`Vf^WJYwdiT@tTOAVF;0CE~*}l{HsD5zBB19Kj>saD3#3o?I({}^-S2^ zU?XJA2K{DRZs|rHCIra8o_J>1lmgkei7k}_%SMzPTQ63ZEh3z$I8 zXpGmMR+IyhkWa?`uKhWYoOp)*#TnQ*&nJA}cYmkWrAqpkJpjrKhsG-O3C;(k3W(Aa zo1V6q&L#(~E-o8*^bIO%VIn+&ili6z`8*C0{D?~i)BW6K#RXQ6X%@^H11nOp)NGxY zICbD%svZ1?QtNb%cTZY;f+8$lU8Wz?XJdZgeqjoFJb=)b||gY$XxmK`*oO;af7Kjb(P9e-sMhpyYOBu zvMg!s0!?}iLcdHhFb_yXg#_-T7&be)G<{&%EJk*Y9IyH4kHdEUqK|lm<!I@?&qrx7BmFRlbf;-jRqFCq?naZNY-EgWXb>PuY&?QmdR zVtU4j1Nz<{1)TdIx6y{Tp~EKVA?;UdKgi(Z#*<>|aNDJh%gX&nCrR2Bo_2Q^PXXsP zf1~$X+dj_bRv1Z)wHkXnWN?XNl;jWZVg9s8Z&Oq#^&;H&1W}{S8%V8wmQ`-(@U1XA zNQX_cb-wj_gAT3&Nw$l&tjs7+3uFf=m?aFoN0qlC=IVvem}cbcvJ~YGhlMzMBdqi^w&;&TgT- z24EWGjjnn!ixE>0TmK~KZSv&fwE@*;Ilufv#l?{<&I%#0s4g2dQZNb|=zs{*Z7La} z5^fkU^MW2+DQ`bFw!`M_ceu}Ye8HfA*{kQqvK&;IOBmK~PQAK2<<7S`;ViI%8)MkVBbe77yhw1dCJ_6p7nMtbVkicMU9U7RBhTgUUINSIw`EMu z@xVNkSZCn!9xCACEwH8KV(Wx_*JU4!X1kcr@0YJ7hQ`1SK?1H<}7OklyW^e&t31`4oS5?6Q;~Wo}0A| z=iEfn!M((y+U~H|jop-TM&{=p;Ooh`wkOaJo6Ysv-v9iMrYH-ayW5wiSS~Zp^o8m?VstY%GjRVn97NyB$@6@5Q zVPj3&6-^7Q{wr;3jP1h*)pN8k?qdxUyu{Tz%4XA73C0_X&u1Tot7VlLbV}Q_C9I zHe*u5-^|HlvYSoVUIic%!UyUiDmCXP| zEP90~I{2!xlamVD=(}BqRn4b)x*djm>@oet`#r@2R%6fpUqk8khP$5RyKjK0kny)o zg=)5OxEkZS#5JhodSk0?tu5dj2shCWDlp;ji*+?>e|t{Y-ih#E%t3$2zjOfy7&a^h z0!m7?+h=p!Cy!B#uml{W!>>qg_s9g^YiPPxPE8WHbUFy>vH8b$vcIr}P@7xc&u!Qx zmU>(FBAZ88Jo5?o*a}%pkBG}QMpK|fL5&mKFv6g){m+WJwliBpHVAY41n%PtZHzJ;g3jd8lyOE*PQL!qTm zctM=}?&u?v+;|%y_Ny~-q#!toko{a>OM~_L!TpX%P8HI3|5s?>cp~CN^HYK0#^Xk2 zXkQLUl6B_sI13kLlfYHN38q)Z97wA=sZ3hnM<*F;QLu;2TtM1yMi0hy;XVl&G zRuIt21~zD-7nGDGNM$1gx2p2{m>NF(SjvEH&GdjR@PY=0v`%bZN#ut!X1ukK4l0+^ zt240P8PeVm%uh217Bs7Pe*vr==`+YwbL)8P7^I>C` zeEH|H2IgLO>rujKzxlHeT0AVpa+TRQ)$JyRLHDE&==1UB4K*bJ?M97@k3)%xWowN3 zjL-IlHK5M4@lli}Hm%Z;^3SwCaSsYyu}u{>-eTC?{32A_#?1X1aV}j$yl{VW(ZvgV ziNYYRmoD{S>VP&mw~43qAJw1AHJJLqQe3d52ph6qQ4~1v4ud38&qB!91>w+0-6JTB z(v3;e8K=~P@8dI>Y+4rQxX~@L$Z7Gdd)8z{4$Bm4W)Z+)&gGx>|3u1;Sam!pKJWEX zg~N%kY-{yF0sBqt;HSWe8N#FYZQtWOyi0EpVh~V7;2~eV+XRgZLe?15x=T~< zGwOn>woriP+)Oew0*A_ZlB^xO=(S1U`C_4M>fYX~Hfm#vC((W{&z}Unu6yl|ZKQC4 z7Eye=l*DzXekqd6nw+bFUwDLNJsHKeGa{*yhts7dWn4~D2}>;zTN0ITsZC zZ3&Rz%gTLn`S(#VrH!s)`_CJ7ca3-)qviW?!g26q=MG z9;{naoglA7w(7?&m862|cTk0ftwUugFE-cv^%E7w8MX<)%r>_E)zYu6Z17@IHtU9* z;XWYoZSO`-Mq%ied6>=bmhtU`nG(c9koXo=pl91?aK$17(Yle>ZL@hJ1e|_C)o^R= zFjQjhU3z`hxYeL^H*nZUfLRvC$xE04HR1_Otm7A8_M9p@M#> z^}OPEYQ_imr(k`AQS=&*Q2$ zRo#r%WJ|sdMSgxxLU(CR$uB!ZE*Yohk_3L#5osfM=r@*ua$LSDzYfnS?!H zy4C)!6`_b$AM;|CYCcXX9wNwFM{pSjVJihZGV0y(Tl}}RFf}c`9?b$Cmmw{h_W$HL zuKMQMmqxM_<`hnG@)}HZMS}~cWoVIIVqlWfdP9AS(Y6n)-+wJR!sV(IhO$E^7U~CJ z`CT#u0xmn6mync3ZE0|efj19%VwkD+SB0e>hXN0R3kGMCq!6A-erfYM7guxbLMEoD z3b}Plaq*=B8w|7UWc|R>kuUixTilfXsT6IMLE`CuusxeSC}7vEnl7Gb_c=~KXc0l8 zbfJR5;D8ON7ZsqG(af|1soaM=gx=2x|HUqvY26h%;gxG6n#Hm&X0jiHdutEmk2Ev8rC!6WDy!F6nh{B z^4RiQzSRRx^rkhA1)Z?equWT}RS-HTB;*+iiB27*N7Ld=6*Tw?fL9FtbmUmkuC=rw zgE(lNbI0_o$2Avks3|v(r9T@zD_CQ6_Na#kv}ef&gk?RQLay78r25(T^@Q|BAv)Ly zAUF|VxVd&(6TKg01J7J3O>X$iU(;t)*=(SmRhQrUj%~~4eoaWfL&4n`Zv&gM>&aHb zhA3;?RI=hGL(X`%zJ2ZcI^qk6(W_a&Y=v8up_(Cv6~M|oMp$j#-6yqvxuta1ILcCC zRP{3Tc34n)%L`ZF=9rc&8;km`CyO%&Zsh=*n9!5_F6zTBQ&G&c_mD@pp!A1NWMt#<{|8}P1h?B=XMLorC`{5 z^x;)w^C7B8H)9iJ5m4c$XFFyHMG3dmdR!F_qTaxYj(>-Ymv6ta7IV&uHGdoqu_ehe^AYSjwk~2UJ|(lb_$IVG z4_pqYhPm4gDqRlhH#wF!_TA#lu-=42ZcdqsT`XOt;-4Lprd(qYmFCmp(t@2iN{4mP zPf@yc$P#Pi&{!NZsQo%;`H~fQI-$`VbX^}bW$eCQv0m-FQSaD&k$?D|rzk{fvzqz4 zQPD&?**5V& z_sf)O5Y`MX!+E0fKWeTyb z%90C;T@={sp;B^Ff^h>}jiVHYMpC8xA;UH)N2cy)GpLp*ArD8e#onm&NYAi{-gB!YF`=R3Qt6@fi`H#5;dgSmX;Q_s zMnm{xY-o2LmJ=;V>oc1pw2F(Ff;h6~!!rih(K`mH80)H<#utTL%b(u{)flXm?cC!# z`-h&^y`)vVsEP=VkuO$eaz1&&7?!FoDREHwzORFf)!l*0*p_(*;`|}~4Y4yQg8~`G zaP}eKt8Yz|E>$;ej*i9}BzD6gt3#Lha$fNY-)zYLz%!3hp^^l4lvuC$f|Vrc^O^ec zU)@>71Mi{GT;#EK#*Q;%e0`OzRo=e}`$f0kY>MdF{;(35ZC&PQaO7jZZ5niRihWaSL?8eh}YE*BOy?6Z7GHloz%m>xpgz6lK zWjF`z?%2Tr;jB-Z*Q34pdsYKK+V<8rh@vV(sk}VwqO4j2qD}Z>@7jcwmK5q;%7%|d zy{wSm?ppTu-4|JbLs%rRxx}TRQ=xv<7}ymR=dHo!-LU95nClWLZ`!15yVn@f!CUVF z|KJx(_Q!0RJq@XhyFpfIo^Gr^N(8z(-Ptu#=_g8#;?|aB?2oc$)>^}9M)s^0D&3w= zkrIGx(;K%Tg-9*qsk`hJ-CEN)IvM@I-VXdfiBV2ItBYoVt1J1h(Ip?t-ub4c2qfDP zb3-d04-CbYjWZ4WMvx%G$Zv~=qW+9CWyN3i(CI%HhLqexuaKl^Ah~1k*bfZCCL70lyAcSeX3Y>r#8 zx2&kPi%a5Ui&*@UY(#H1JDw2uRXs4-8Ut5A^2*!^b=6r6Vc*#-Lv`UlQ$+u*<(INj zB9EItu}NqEMeZ?N#d|XnFxwdN#WMFXUWSB(>|cboMe|#+%l7f~vjlWBs0cO8H-y9i z2HXW^qeA^;EJ<#O0#^p-|H_qjBJ4Y&19v=2Q=IV}_uKc%o2Ol3>H1oC%xA@s|Fvv# zLh+KI?wRAkV|_BA9iqCGgLVPlZ?rQnzmAG(Xc=3L76cpL*AnMG(dA5x_U7W*_^j_# zf50V*YmL`V4lY{X0fy?FVf)-e`G&58M_`tC^lm@Z`^P;)Nn|`>k0(*uf|_XY=x48}M?{w`_T=N1+P^M2 z+}Ut88HBMa-Diw4?U)-J8NjsP1z;ld{eO*b^CbUe3)XT+rN)e_QsU%{JqL?z&zfRD67lMu^{st zsfA|TeMW=@?$&Da7><~@U~!n-id;Wxnrp>niUEg7fse<@T5`DD9=XIb^Iw#=pogM~ zG**`HZkK0bEpBHlW=M?x3qK|Y13$S!xmu-)DT5l4b~WG`$bNMN z6}iNLYS_IB6^}xwtew)#eg9@jI%n1$7qH(B-1Z0Egv%PKyRC$f@R}ZqungqzG zYh){B@BRm|5|qvLS&N`#p;ElC>?QZjmS7o|YCDcWHLS;a1fAOj6}4F0tTBimpH5j| zkrqvcgD$VfyXS<)!O}4dW}XSx;XxN9Ug}o9S4ShRfz`3?o!K7M58ybs*WFye_2P1@ znvBy`t;O|=1O7hzPM@aECn)~DCz35&3WtLWYepA1QZh_fYBXM2R)PHstr0 zXwk}ECficR@_rVNXj}^dYX@8cZSl7CsSY0>&DOVl0N(!s7vXNq2*f=BrH8}nVHkpr z8Hk2=$hNV#^?Oq=-Q_A((4Wz34W+BMz(wc!zuRa)RIc;|PAWYo`i1>^)iFA)9X>&< zy*#RbF?9|PFUFA#6InSwrMLYz{r>zz`Pk{eL$Y@$af3T&XVZGH8|>aks!Xgg40)IN zL4_uTBrJi!>no?Hd%`$}!_xHNU^dgK7DUfq7%&Q`i<7@l&bS4c&Za1>pc*jvz~eJq zB>*|K-kj#WqRoS$pXU8F;D*{IN+YpbMjqiAGM|K-%begXGYZ%q1D6@j62xPWeewYv zXyA04Gj^JRLNF0_Nw4VOlKRhQ5%-|dsK2tCiPwYEMOG( z4_mwoC$x*qFthy$AZ-{}uWVnVuNZGruEpWdcXY%ZtZ#56xu)(a>=LG}ICkF^yXlHk z(oWqW@*7I#MG3lsqU2V;(@kIYPsHp20s(>Xp{ChCrN{3GvmIH^F>RRErqQg7fx|#d zGi5&*CYk-7#cqF9J^I;;ZJegyw)Ow80C0FfaX8fcTugU;pk+tbb@FWOVfy;-lnI8b z`Tf;p?7{M`)7pPnxc@r6ItTfzPOp^gb;nFxTR*V6IrVj}t8exzVXK-~zLA}BuiRk| z@1~C}y&ozZHg2)agcq%LCiH4`$#f;O?*1!bp=1`%uld`r+Cj*3#=LwHCWjv2WQ~<< zXIUYCc~V;uFmk)sjxG80-D;TPf%Jdq#iZ+_aHajq9o%ouCM5S|q(h~>EH$OirVUsr z=|``;=FE!6nDhE^2u)s0Vp_{9yMTOg`Ee$A+eyyhw)9I4Hd^ekonisyv)eeU$dMHz zhImX$g}<7sL^@61U%S~y8uVM4%+!_G@o+_nKP*oFVXUgNiszuQI@`gz(af<#PG4F8 zsx|>Lf|T1%(r(>7BXjK7RraQk5PXa{=py^(@OJ62G3cg0Xu3{%(@m!{1VBM0r4UVI zn_vZE;aR@flsW^EWSh7ouWnsZRhl)r|CU{02`VC(clV&)vTCUUD=T>Z$BXlN)V$kJ zw5&tfEPe}ZzllQEF+0Ld)BV2$#;kE?tcPa3iM0qMsyqpyEw1=4Tb<|V@bqYom)Sb_ z*;MK{Dr(0|b_IP9`zE0Cvm#=QjekND8(C4G$j!`PQxr~CebM%f>9@S}wquBUv+0I$ zlae;eCTxlr?hbo_v=Ls~&P{C{VNYWqRX}fl#r(N3z1D6tsxUvn-DWixsWB_s>>nz< zK6hT59tbx+Wa=7oPgOhFY)WGbXpk3>of}Cqv#=e~4(Emvg)@1{##<<)*Un)L)LO(; zet%_dX)0T$335b^r@6iD6>UMLnoQGl{_Uc%iwH4G2gqj#+DhK`3I|rn>&QJbc&HOi zX-7*E&GePU)gGjon8hIk87N{4FEx!jab&pA@ky?f&E~Sb4d}e|@V~wXDlAewi)WRn^b77n{bQ-dlb3>FrgQqG#WOo}I=|xi308H;T^<+plJJ zd-CNdpLL#E+a?>2OSP<0$jWK58o5mYa2(J^oOb-j9BUT#)HyeUSIYVunYoq&`?8c& z)e+J{k4goz-S@YO9)Jk5`4>8PN#c~S>q@iC`1`9cJkL&8K?Nuth@*j~K!f9d4A(l& zHR%UkO<5!Q70*S(>7WfyL>!~muT0v{OqxD$NhwG}i`e~|JXCviC-ei+Nlsz(i|46G z+f)2@S`o#{#f$eo7r%yC9I_ATHi)UVQkf~pZhoCyqaT@Pzq^tRd=9f0=;LPbv2}Q` z(lPP}L7k~9zYP^;k^X&ZCM+#-KEj>t)<=9nw@sEBIdK?tL=u#D3#AZ}>fIio;Lwwm zx<5Q=CA^-1H`0sNre$~Wk=8r#c@=b(+qTlMq5f|wn4E081Mm)LDt<0QDO1E$kP}^t zOLxerQhx*J2-zf6T_wrAlWb*+q;ahsMp;5>x%73`9KNSSZi^=1h{*fCuO&Cr*Cl`I zGq6a#rLPcE+S<{Y1{0D+nfbo+EfiG~q1TyG`COnPq+P2AYq<_4}*FQ*#`I3Qaql&od;dt^6B0 zs6JMZz90AvKiVQ*B?JuFcV~aLopY0S8#boWmhly19@-b7WYB)*#?hd~ab$3X!_dFZ zA?kx^w0q;@UiU@uzyb+bH$mVOU3hke{XOTGp`no{(N@?fi<_4@8rl4l!_g3lf$V^+ z4;$R~7t`r&KGKAzpA8I%=K$CMuXt7UJ%hnan)lj;_HkrgV|IhFcaKuVRw_?k7UP@@ zPHg2f&nTaii*)$5zR8N6(Kmj*7f?;NOyhH9a2{EwQ+_V9V&7xgagC#@GVb>KmA2W; z99Ls*5&;9D3hsbc4s=fOF^K7Izh5`9og+-`@Zh+a43@Qd?!e9tfDP}%*ml_W0C>x) zKq%E-eIu*`|KJhHg1DbsQ)7s->k+<*%Nv18jKUZhqk7GtfTA-R&9`Tshad3;_mW_FyJXP_(#e zGc{O~$e`I{QK%va(e%MKvCb*6K0p@4zwJ|hed^#6QA%bZq-}AY@M4YsJlQ9%H*-lN zwPas3C^w6K@x-}Q5Il8EsacO6^C%0fJvniyFKXxb$$Na z3K?0e?e6M(AyHVO*vCF+Y;5MFhvT-g{gchFPFa=DWA0=#Ey$I?8Oa79*iB?*HPj=- zd02o}C)M_An~p_EoqQFMb^*_5&Q65?P`aOT%EL;?^7)%OoGUeJH?E);+XaESXVz zV-ouWVnDmv$LuOF(ETy{t*xH}!>v78<55OIUw7;W**|YCyKHTDjCX^Rw3Lb2oiyrJ z9p|6F-6?UR8?BVNtQjN8x?ehhEor~v@1?$!SJvKsL^4GZUCESF#u_UaD6`W2j($Ou zWo11qR++uiF2w4g@u|`+zMy~4fKij@-=&KqvPZWhf{fmD@iy%1)$#ygE5W)riE1R= z4W^q=o3!>{RJwT5!Svl~`CEWOR+6UT-l`tN7IZ z=5jlLl~>HDBe+=0rY`F@KXz1C$l5ge#(wC{J(ozxmys#MT*7`hkE%+a41_jXPH5MZ z5tf$a)qhi}XL+7#+>JXOUXR_(^UBCnMZ-ouM|iDBL+Svgn(KdHlzh^OA6@-7R;!VW zSfiQ+tW~90KYwbN$E%KGRg$9?3pPcr%A$-6fCnjWXjsd{N=vdc@19=su;O}jH8B`A zV8rd7|M_D^2Wkmp)hyWt zC%C&i2Mg}*?hZi@ZU;-yAi*uTySuvwcXtTxZo$brnR)k{J9mD--c`H1tGajBTC3Jh zT0~E%awzTD#mCYupCrE<5!S@6m=k%`v%4FQ)y+VypIbl;;li<_)K*zIV)8h}6OI)r zp8cdDawR04*2&gEvQJH>mRK+8|4NALxqF0Z2m6^F*+=)AEB|8IGpugW7x5En*1y*i z(GocWoB$l8-4NjcdMv|cwlQ*&oR-ZKcml1A(8r@NTvXPeEHss)<$AOld_w0q?S^_V zj6MW#8SX`(!-nxJ&BiErV_HW*kGJB_8Y z=`h>ZLI)JGf;?6n9ubS^{dFMaopn56gqBCd z8|=iFWqa^R7^lIiQwBT4K~RsoHM6kRJ7vcAT1!=%Iko9o0Lx|bJHiM4#S^=8?dtw) zFf@rH=7Vnqtap)2N9A)3+s^AlXK2lkxeNP&K5uHo%{EbaIPPS#xr9&HcnOJ{uSq?K zJOcfP0EDFh!_^A5*)&SCv_C8#a+c&$3a+{C8zR}k&cxJlg|x*46MGqBrZIy!kvkDS zNs&2hTl}YGm@hTYN~?5pDS?6NlkT?A7U}0E{I>CUdg+=hW5-mEb$q#%dH$r9b4dvq z9tWvyzwi#HEUdTL4Q2D$-_VxSN#KrF7NdLE(A|f7L?iZp(2z|DX%%5_N3gjzq-w7+ z&&5i^j$yzzC<>wMdmAiS6Qr}W?qRr%uBe8DXxk@aoIHA;>TfzH8@lVtzkmpj#*o28 zonOH8oZ?7)hrP60Yk`d|6>9#)q1jI3eioRcfGnbJApY-i`1@LtbE5l{N!N(omMqt1 zGi2S`$ht|8KK)PanZ`Ucms zAXqWJYC3s;YrY?Mk#DzkOUyEx&CtN8%9g^>HrazLmJmt*b3pz;nro6}4Gej%O;WR5 zNZp397IXOyLvrdl&-g(~JxAb&-}CvjwY(woZnHQ|pS%>ZYa@{41yiD8fU_aP#0oU$ zx%s>W71|b&WMg(A!5vJ?n6U_b$|EwrF>U%GTsDzMIr^-T}2r2eVtAuRDK?IE&ek@ zT%3_^GYI)6&Fc!C#5-eoG&uCt>277B*zl8(RT`1)`Kp53-{8Cw5itb3VfgH=oAib= zvXBHhjGH|9{zcau+$ergvVtB!hr(5HD%CZzO%uZHzq%M`*Db?89N1mz$5_8XWPRg}XLdBe z=OdQLR1~t(|JXBQQT-*!(LZLTUy+F0LEzPz#Q}23!D%a3p|5wJIP-;_#T5d9(r5;*Vuaz~ zpoX1P$%Etw;l;|DJbo@}9t&41Lxg$VZsd0_s;|nX)rcJ-3S)gs$8qe6bJHJv@czeV z6KT!-%V3&`3~?yfehS`h0hY8rnn!I>OH(j9pO_ZdI?Oio%Zjev&wzn@D2i>igEbzA zYF9Ws3-`-&I4Q7$H{or{qZg(3QNe;&N+>$@{D;5ca)|g)Uv!ZXZ&jQ)ehMX~W!Q?s z?=qS#Z4cuVXLyoFWrOuG4d zie(bd`wOz!;Sztg2*k9m&b72i5!yEait+1OVA`IoAZGT6PYr)cU+#)c{)7&`e0Y~4 z{u&LJvFhMtygv82y(=#(osej~%(y89DSsOesFg*Ck(4r%{Awfm=LUnu;SRnjS?F!} zto8h=PtV8KKL?d}a(g>SahPhFIoo}x@!o~S40$Cm3R#oNhZx1=Q4vZVC$Ree? zbzs_5@KxKMQ>6d3PT-Ot>4NA?A@+jfMu|bG2*QP@%PD-JAC>FH#byX^4b5$$$is(_ zWsWv5SeZnj#UA>BTZWcmXx5a}gTlmU-HSdXyJI@xK;pgQC-6JyfEsX?Js#pW7f7k0 z9TOkn5^x4+eZ5oqBrU?eJ!VH1kuQ~lRE#SPZFvp8rk>Yz&XCSfNb=a~->z`S_+g0x z!1G@K-*EX36=mCj`wpHi|-}EZMz8$e|L6qE45SBja-1Br0JXr&j$~1y#+1^dr!-UIl z`Q3cDZ*U}8^IpPV#Y+o5cC`3kn%nia6}tXCrVF5NDXg6g%E`W=o*5F(W|#>L3cp&@ zuH?m#FcV;a-mF}Zk5~P0NuWNsJGkK%WFitnB%B?NPhE~@T>~65&{70lFyf-<|aTr11)7)l{+O2V~u8T($ARLj}j(>B*WI2 z#bLT%iByQ7N4NSuZucR>q7b9{?bzk8?2j+@%J;PTRmb-r35;>3)b{&ButV1Mgs;da z$r>vY4ZFAmKv|z|luB!V0**kuWCseeH$(5$Vn%l^pyMBf5xb@Iz#;oc{$br6i~69% zz+PrNFwTO)%gr3fTRY57{M6`=uQ`Wa3@YV$^sY&DpF#JUj)jBL4Tf6eQu-nNDV1lH zC$kBK0DoTu|0acV2m;8B;}wgBHvuA8B< zJ#vzF;j?8afj)JXKZo<}nP57`Q%|)NL~=k7PQ#=haT9i1K*^^c$vb>+9obt3eoQri zEV@?2^_lpZC-^y|pN0Jn_wJ6NTkBel`sj#%wa@oPbEuMdY5oiQA}k@Hh0%Y3KDUEJ zyxp5_H6ns{;!j_^PD=)q2#LgU5oK!6=T zfzKctBco$KOp4&gH5z2w=c%)+mZ<>(3Q9wwsd0H=4E#P02Uk~-9DX1my47LsxB{8^ zu}9j?1@i6nO6woA(`MblOHI!b!b_Z{UA@t#N<(_I zX5xfOMt_e1HDY8p2CY2WM>L0WPn#r@#Msb+*qnzcw~XF}w*VbYSjn z4Z?9Pb`1=ZZA|E}XKr(L%7U>!;IpO?^bWgsxqeo%(`N=x*teDW46`oH#hq~M#|>Wy zCA&D5UGt>$+3cM#e)4G+$m<{+-Mum3qEvf?HGkLVJ$Htf2%-AWzy1GQ71{J@ZcK_2 z(HuWM{O#q4jD%#4(ujd{qgkwM*cx_P@XIPJw;C~UgsqFskk`>Qa}bz0`c#+Uz6-tD zdop%2Dgb)vT*Vm^@#Qrt6i+S6i18?~)!sx2Oi7L)_PGW(I^d4YaZG^FJ6`CA! z&WRGQkT-BRcJ=|k4X4?5%t|fe? zGrFbdN#2Z~qRH)l^v^=;Avd;gG&UA#zBVJ2Awe*Y;5m83&(3~dNeu!bU7wjKBhwUp z??EH)k^E6;e))BXe;wSbR4?OmF{ii)I3tsPf8CGJL%kiZZOslBY`j;%JpbThHJo0?*BiR9 znpK)3hXZg|6(cY9JF;RNOh`(6hzuNyzK;TQxFKJOK?Ygi0=glDzbkty!=2hJEBPl3 zba)a*c^$SxgVDWB@0E@Y{FeyrOJDQRzU=(R6698CrEq+d>XwvT&E9F>RrwBZ@Q5Hn zx(Cor0rWf&#R2~Yjm?39NGdV!`r4}Ht!wx*Pslz##&?vRW53CBA}02Jzl>Cgrx}BM zkG|qZ#-HXBaA5rxE%INmcQ9iL8*)7KWyD-n-h1ihdorM0iMhSbDi8gQ5IkMmUy;J) z?dD!;gKaWAFM9oB7}WW|$I5>*58eYa(BR*>geu;^=z!fp6bGm_?t_#u2iC+fjWd+K zH5`FkSS)3V|FDVy$q(s&L-h4xN>B018pt_Zv(BtalJ&4g9p3enMznYc4J9_thRbd( z{UOzy=0AN-6YDY1O-$ykO|tq8BOW!8l7Hs@>z$q(Z6z(^$8~381D|C)l(cedAlnfF64)_0hZu|QO zC3`2M(*>LH#6=o{_`$yH=xRQwmOgk`&r8EK<-ghOKb{B?Nl{spwVa7wo-I22Ev22p z-C~#rpmXS2RhiXWN>z^)`(GCRF0m9adbh6aNW81eV5O|iSHae9kce6;jKj-jq5Y`V zMzNe^OzHSP?0gqthc8(Q|3b39wBF4$YAGrcKk4dH_cJiNg8G4L?}6a=m8FHjOi{u= z$k%`QiNpZyIskWJ=VT4~3043WyV<$pYBe-SrS91W`2{yYA&EQSJr`VL!8L61BBpdSa6{1xr(f5-t9FU|x3T|k!^{5g z-&gjxT2AF5o|k1XcE1z`O>Hvg}Q2}Xyk zpeE%^+h-IiQJ-w!t!nk$f2Mx2*nH*XYFLsf0EEN%nsn@OL?IV-oNUVI%gX$pISL{3 zBV>@*pg?VEOFdr;KJLrTtNNuGis_&~nj)OF351V*K6U@2YHtQm{7=8n7@@BW%W;h& zP|N@HCIee#0&{EeZ&~XMceudGjvsO3bCG#8-=W%J=5NtUVFC`;Fgf_UXg|{G&Rg2Q zDd=BA?~Vldn9hH~m-$lv`OD?nf&0-3nsv89mik?i%{XiE6RW76dkDH>WciM1;g^y8 z@LzXI1|R*&MU5lZLC=BAQ7hyS0-XOtKaEDm+iy{dA=x;q;G7uJ_VHeNh3)aG`#f;& zu-0VnaJGf`S-=Bs6C5h~U{c%jUYRcD=_FEHf){PE%8Y+n7!~=aJ9sBFp{Kog=jjoG zdJglDE-1B#e+C{8+P~IpsLZU(dXA=WocKr-53cn0lsqiOHN=(CMCW~>5Zp$S+5}nN znie>5%!OYh@yK;vR?HBQ2@nb=XJup{WhLMZf-NXz^10#JrfB@Ll6w#dAza3Xq6mvc z9zSkbu+GR@qU&-?FYS5z8(^61yKz<*!j8r4FH@z6Z&mZ~!&ft5lz0YHtohVQaPO+V z>?pt^eS~~s5fYy@u%Y_5{KAa3#n)+8wb05RO`6`k-Kkf3kA35t3kF9lFvt?7`y?ys zhJSAH+>g!c<&TjAV9N#ln;-ujW0_%o%v~s5wiVL3@Woo6#pd@2ZU>26Gzshxv%VDg z(k+$#fmF0}ko&)h90D@)u{^eX?fW2@xXA{b#K6gVW8^!GTW~w*$_ipyQA?&_lwtdi zkidWx_wzen+<60Km7Vo_T^iJajWx*=1-V4~ZeKv`i(_00s0lKh8_Y?h{!beH@5P7* zfIeAY7%#S8WlBzDjanTvE9JAlD6CWY{N|cTVc8h1qho2FA5LZRe?IaZeR@JZ0Pk{a zXWTQyT|eKej^jKfBJIhYGtbFfw#i#k;Qs4H$G4Wa@Bg;$|J(C=_!Q;zry7dSxK;h9 zP5Tqsiot^xI=fT0G)i{%g=dMeHhcn332>k-c$Nlgod!#5HY!D>uM=!or0MOjl;5)iZkmTwkDjp$hW z5~>%_l=T@O7}bi(;9(@}p-?%!DEeaFLOSMEm|yQr!_Qxe8m^lRTIE=~ulF0z)vXeR zC4VtBHeuIyuX&esjEQOib-8AGtiQHH_CwhQ6Ay*c}R$)1vd{t@|#2Rj;#8n%ClX)$Kf)=qS-?Cu!}`&Xq=sp!9)_#c4MYQNI)3j=VH3Eg=m-2eyHxmWO%WrFxkA7M~g z6V~kemesJa_eM+sbJQ%84OMhyi~ML>GwC%%E({~Pku35W^ZSxP33bET7Sg#*Lh3Ub zN(zM^IbLn?c6Q`u$%^Zuam0Ooef23rob@DET*A}p9JLR~ZbCMmrEp0g5^k*rE@WnI z@5L>SlJ+?AQrUmtSQetu2=aaDHhKqSjxM>JE1jzI9)Z5StxWF7(FfuH#xo>5(ee+o zw&s8T%+9{6@JXP9U0|WTOT>~YDC*Mm%pwapO$89#G{!nfMi$c4l@S#QU`JFiN6|BG zkORXQCkL(LNud&8rfiIOfJb4Q8BUk^)A=;A6JKnv(R?FW|qWer8_;@FkTaN?00?=shq+y5S9H&miXE`w%bj`E){=w%=wnO zIu!}8HAS9)t*U2VpJ|wDnou966Scp47v!X3p7Nx*04@NS#srsb-P=5MNA8M#8HJ1E4!lNLr_fw{u)>9k{ zPl*p#Tj26jn8n>ut;6U%zQfPGQq#GkoMxh|HQb(-&Q=}4I}B!UJ#;valbF_+ z9hHh!$+X`7HiPALsk4yEkOH|qvcyV;?plMt<>AjfMrU5}1I9f`roGlC2sBK_+Qp#w z(2@K6L1-zmfDDuxq+pr&4o2;sF*>=r3{}}*fAsxJV-NEambhs~D%tD8aVq3qx^#@j zf!~vIR>`KT;sZiqj&@9Ta*^Q&y?oxa zWb@abTfjr6uXuTCQy6;5zuWADIs8n^(zRJ!lccQZJioi@{>ggO)JH#x8gX5ca43Ug zDUV{P6O+Z0N=WRvqSh%AO)n?d%I!kKP8(xi)yrb4!D53+6NtZsGeA^OrLhb{kRQQ7 zu4_#H`Nh9YB7H&D!=P|?=;5oBT6QRJZupI=+SAxmiAwnm3rn$rqeoah2`Z3a-w3c$ zjsZWc!)CLDIk=fMJQ&fq7qgkcY%7elpLd&3UDq){lb=z|z|9xtGZdWS5_#PZs!GkI z?f6b6ohQ~c3}_XRHe0F1CujmhKvfGzJSkL|G7^9y-IE%>&g6r;CGKvF>te1q9J9~! zezTc~->Go?C~ttU-p4+-(=OF>xJv@4=zDUN(Bo;Di9Xs#&wXcQWI8Y5gpM`L` zVki^=BeM|7>)CGX`*NV|Tfd}^de`+*nr(*T;`fQ}j6*D$UKUtB6bkCCTfh8*sP`Rb zseV6DYP?qx@+|rFE0*K!2)#wx`XJ<5*dn3N7?H z)wiSpSQc!((Lx-lrabCGQk(f1FWYqjE&8x%#EMI9+1%e#+n_V+JEE{?|%)0%(h>p>IG*s zOMr;qPg^}N@3b)ro1#TQgXAYd9) z!7Bvm8x&NG?mJv)rH&8_`*!HpIMt@Jx+Dou>22F;hT+|?)iSeB)7Dj-V0^-SLu3ee znXhLZS~p(6^a@hr(l1uN)|$UOqsX|Cjkg@0VyxH1&I}q&Kv|uk{GBT^n?$|%>2aBB z6lGVzS;Dkn!o&aMO~Xg z)x3NG2iE*@xp@S8>WhGeL|$gmAM{or1JV_{fh@_cCpdmnQV-IKai~CO*roHvumWZ&})^9Fuue=JT~?oV!;>{F|<9v8c?J`d!v( z_`+G(h39Q;Dh(Y0z319iLB|@#IV0k`-;9lo!zYQPwo#j>^C55WS7LH)tFhM7Hl=(( z2J~s)*fb%}0yPK9AW{1Bi%}uRJnZ`Q9cS@}qQo+D5ei%cR+r z*HjlEgaxeBqtL4F$I%vbc&bVZ8o`slIgN^dH+yd}={bxaiJtQpE*}F)K$E`Vyo-Gv z%dZX<2Ke|>_2GkxP0$*RiUZvXE+-b1j)wL4MlUnX_i^3qpDM zlse^vP^2u-+q!$tP8GWL(6POkRi++wj(0UQf8lb_@)46}w6sU%NMka(0E{t}D*?70 z<5PpvRj00Y8?W`*Fva7CQhG-x5O?kutbQ#X1ydA1dNy^TG$x=8iu$*o>_O$Qir=*8 zEoeTY-7x8ty{p-E<{MCG1q| z^0rN1rrpJqBZ9QnJ`HFF^H${r zqFYu*Hrx%3r#zTm)d#Kqrn_q2Zx~HuQ#6<~N{Jz8G*27arXZ&9G}V5MRa%P^#&h1j z0SeQRG>xgnRdz_*rNixLPL-3Eo@IckZLxyGJ~rb31v%p_&d1fmlU4fR$k04Qbtea$r1%&R zHYvfrFIAMjwWNGmOfw&eM(TkTLDA-TAk^B?B}98iru(sKKm^*i_uqW|bDDuRKoxXx zJJ2x2rh~W47tAAA%oXI#DjCAwA~e&!oMlx1-0*HUNI48f=HkY>k!ln)c(Z~(S`qfg zX)Ydq&UI(=Ki$LoY-HuFXM1c(z95)cHoRhwJ@=Nd4iQn$XOB08DRW~zNcRIM_}k8TAIA-~+%=@*fo6EjcPjtz@-~DDw?#uiVSO z@q<8_BlZh?MXRF66*C`DJO&9sT1!Ree)7;=H6BudYZ3#svS$S!^NC}x5s5$48|{8d z$-Bml$t9lo!?7^ssUpaf%sg*+On)x@K<>XFG7?kPQ9!i;*uk3~woMf#V6ru*re9bQ zu@j)lPvc4CiuT)fg!AV#CKeM?()Td~)ta3C5sQzbS94qX%FK{tTKJyi{2ZCuXW@8@ z!=e@K!_jP;HDDl~p26-2=5i;JWjLv{m3aOFtq^+%(>56x7}1kF5^?*QE|&`+AyE)g z2Fg8b#K$+Xc`)HR4JH*}CYhjm~p2x;>{W4ro<2+_o6KPwLx9(`yX|Yi%u(w4|&|LJYJB ze&gRz+ZdMcW9$Jeg;W!hWIrV!DTvjdyUjNG3;Ju>GM*&kNa{8;IT*; zt~nx!LhHV`78e4NuhW(<%#n;vh_;E$MuUSg>BvXT+xkbjP^=f_ekhm?z~wKRk^IEwfG+Mwspg$j`3}rZ@0!9XN?#U#(&tT>V}k>-Gl0G^arS!Dss^r z2touN!laH*>fSJiw9XkffC6*J%R1;rRDU3|W9Sww>&4dUXitg~ejB~#e5wdbSwdi{ z+Lk;Om9gQWzsO|A42Q=QHwdmRL$cTnivwy6CopSZolyD~${Vr3L%-q;^JmYcI_$2n zUAZF>ogm@DnjW6#xJ_oSO}Bm*!O!edrS9@|Wg25hti|sVw5C-UWr>zjeoe*cBcE9v zsigk(%SOMvq`kJ>%lCj$6Zh>TtGmRI|GjQtx+$MU?t;^jslQS^X&<;=8)tHpP^ucVYLdS-)*|B0%%fjSy_;{$NBMeU;mGJ1(?pArYa}VagmKRkpPqgN{ z%b(*~Q3X=Tz!S^de_v?^S-=p}0QA^YHFP;aLGPYpcyl3IQB3M$2xTuu0Kg-AR{~xw z{PGVrD7W#glF?#dg;k*{7cRSRC;NK(xbM@g*1b%eID~lqt(MZXhg=M8EESn)`Y2ZI zhUhZOx5JZ*ucN<{y!zL+=d!*zy|o`|M@KTrBmL5#4mxBRWyBn$-9GufqgFCPsI9A4 z>=7SWp(BLqze}lmF1;4#^>Do&{c+tf38sH`51kydu*vVDRH1abeuKxoNa z1WF=C1ytp)YXm|M-GyzLh7nW9tEC}q-L%R zHeCWzmT1$puE7>U5COiwWnBhsa-FGviY|a|7}+4#r?aPV9(A%>hah!;npaCf9eV7= zKz)-&Fo|16UaJx$=&NVnaAf2lcY@U&I%vC%n9xzkyvSZCO##WJa95T?t1S$FM=f;y zHFOU7L+;Mr*y^R0dH3t;XN!2l-@IbpCiksHQAw$c`PWIRc0*cU*eHFg%-%P@$D}Mj zlV&_3B^y_X?eb|NG>DnXH&du;nmsnEebF*~O{^Pe#pieEU}Q&m*R`bhf|N*aC1h_k za5qJ`L4*2@3pXvHa57K$V@{KfxsYPKu?DG@bMa*?e+| zFq7;Aeyrz^R%q;vYA(J%i@G#1AA>Z<)>rO~*_Pnd-oUH!Vfa1KlsYcP+}EF2`Q{=0*o>*PCM>?ofvVKsg*gO=NPXP}|3Z1HR*Hw!x7 z*I@z+>#VW>#sFozEl)P#xqx#KUDbg*PMaGNE{0>x&EQAL$Tmd7h;qx4DwFvYo@=hP z3Gz@p=6O5|kZv_yD&t&f26wB%Ry1MrEwRh17BWtHfbQApii6j(&?|kpc%2dzaLWwp zP`_sqX#$Dx8Jh1(ja+vei@~5=2AI7Z3ecgaAk*q2rz6XGBElAajNoVlBmjP-({HJJ zG)bJ+YbjP_tEePsprwzo7O7z9=%2`&dvq2c_2K-2)RZjBRH4L8GjPbNJ1Jpf2%x+m zHZq|mZ?fxOU`{Qy@y2O311xA-8Rc042=&;AN1T7y)M7W;GAwZvADAm&QS(aj^HLU8 z8QB_rcwG^Rg+3VldK=HNe;tQ@k0VLRjp zu7O7BPAf)&U{yBte1paOqSTH94B!Mpk@*Ckgi1Wu+qq2!x_hRTeK$XPRyWx|!l{gk zzdG+uoark6KH7eKK~{icRU~Mxz|8lt4SCS9j?vK>!2^UN6~WFcnpD44e*aFCBYVk? zGJ5p>~V6#8dLDrt=$@3m?w~=rn=Eo63u=2rse+C)wDuGla_&! z;Um+ur5hC^W-&WGxyibeeih3>E?2avqAjy+qdF`)L`nZ|VhXv;)Udvj`+_jQ z+m3fnKdiG$h(HXhG-R8^GCmbfC%SIsc%+?#Zm(2gj`l#A6W78HLqo@thWOPmiK%MO zk6Soje(Fl+vxxy&?T50f@7_*_vSxI%G4a@kBM~7Y9rtNiSj=e!AJso}w6YEfcPcfeqZKR5 z@8d}oT*d6q&KcJ-k{Qf%QCHs4I^WOHR@bY1++f{4>=9$l@N<5Mpq)Nt}xq`FFe@mAsOOUOYA**ANs zsBppDI)Ji#SkG!6zI>^6LFOxDw8gVMEk^8wVI>)6KIFHrRj zljseqN=C&q*7kI7FY`)FCj&K@$JTNNg*-|NZ@4P^t@yi&;UkMg2OS~3LKUU3Z8JaH zdjOzQyYvdhZd;2HZbtL8?&Qn^F}`{ZBRs1p^pd@58IHt628#*sR@vWR6KHqJkLoW@ zil?F8X#!MXqOO4)tjZaa@KWwS6{4?69{L8Q4)>&O;!^i0h z5dw8F+H$-&u8}cnqo{tqN0rmsB!kwFoZ5alP;Rxp;(?WC%iPC6o26QH8=T&X5_->) z@Mqx$1i8cMR@o#!)OF-a5;EwQVkfHVQ}jy=7*pef=#A+anA!jGWoh!AMcXP%PNpU5en1<*`2+KoO8urlP>G7-ya0@^#xjeK^cZZs44<$Ccdv zTu&i2Smb+*Mc-ipDI`W#L;3!>3L8VaIoDJ#%xtj9dLI`dBC&<4 zI-yd%a2Z8m0ieyPs(%m!E1F6H+}0wmrHMe+uT0jwx5vWKo{<33WG&CCy77k1qsWd` zEIgG=1PtzkJp!JZq+-cd9`}kl{9~OR@*5c=d?pFFRYBHVQjOCMHOoBGri`%VLx(fN zOhw*wXZD-EMPjm<9e+}5YDlnspfa-<%(8fpQ}=wV`cR4U?$t5ST0%I~GT8^(9ox{~ zUscY;F{|4R&9KesX9Jf$T>ca|{kHw^p!D8EPJj(#l8`KpTA3tym&npgdRTsUAEj#6 zY0S!5a4w!@d=F|(OwDl4T+qH9i^VK}^*!(I!7Ofkdt^vD9gFQ55;I&~Lti40{suZc zUuwYAxS4`zTGj63EEsq?YdxZUUag%?!1xPP8DMuj-VuYqkNok~(u^3^Q&dn~12M2l z3&&J$EE_{s!kQB>&1P@rX2nM}HFBeo=_F_=UpN>TzZ5p|(wrIT?iOo4M-P~#I@>_5 z_ZNoxmliR{%@=`=#rEpy{pBf zjXsxFCCUbx>S_8aJV4ywOz<_*A z5b{nbZjxNrXtwif7%5 z0&QPcXy0-btu6&vdp&AvqtDuilC1q518Kg_pclb0bU4walow2mDbr`*{92(5oU7Wr zDJ6BMPfHb8bhC<)5eN~cbBu)lO>D8=S*z(51L13{hv+&fVH1i04f zvqY@fnfRmU`oHd&4%bd?=B0wL7$~EK=U0l$i15#!j`PnR$IR2mdz7GB%W~PHiKT%!k2ozfU-w>{8~JUpY^Bl=&C)Rj z`m)<%TIsUxU2?f8pz_Vt&%ZDYRiT0DYofH@YRH(h*_2g3BMd5oGMIlf%Bjq%tn29; zJ7l9<{E0(>THCSVYNO4s38o)LD1TZ_95hW}29W78@a08>?d*uP+Xu-qa>wh7<3wE2MG2S#McEc%fuPRG6g4`IlStXZ>IYiMGI*db z!@+o7S9Lx*R5mNdUH>AR_$!r*a=M%v}b zjm%)Vccs*zs`07oVzWF%e9eNBzA&=W?yMN7fquVhrqc0gE75QC^=-^#6>8nZb$R{e6zej(>x%jLC3iaTDAg7+DmmaaYaXAO|ZjRzhjI-ZlL`$5WA~_eVbKt zk~DeHI*nXpB%~uz)_FLvQMlzGur#FK>xd!O>th)xO;(ovlTXp#U{d$o(uYDzkKUEPUo>uJ4 z?jlxoN%3%5d#pYs0M{abGIv!eVX46TDX&C~vy9o^nj2i;>CL0)URnORX$GN$H2Gi_ zBY05N`kSE9i{bRSAa}-0s@+<>xNXCIZGuU+u74tpv%Ea@LWg=1c}?OX2-k3rMmcS7 z60Abg(iY0i8Jp{_pweK^&`qAVmg3jEHMy0;MnZ-DjZr?e9Ne}1B;R}8R}0%Mo{IrH zgC2o=)P@aBDlf5Pyqj~GN9u|tvW&o&rC{1w8-MVF{f;X`l2b%nLpK)yJ7HFt>Fu{g=~VF67ziXh97-tPuf)6u_LZHkA& z*IVXY)pv>FR~k>4wh(NZ*3u!)X^z2o5#5eyQ(Z8=yDN!^Gz2(`XxBRmbI;-i^mw!i^KKlx$HHA$9k! zO8xf54Ufaji}u5eT_Lb0XFH)o3;CCI0M+i+KsMXQp_BfPn69vkOW4=eQw(*WGnSU= z%$K*z@<5b>E3`+cw5bD6mEzpxgv7Je4p-wr-`fMGivUDsCrh7GMq<+TiL7(qC$Jp)Up2Az_vcd}qlUqO9R;OB zNucYZAoA5q?6hdU8&xWs)Q3gTpBEgx;m8hVDVY$6QGm^AgK|ioCr!j!%-lJQR<7wf zPIL0KW2Lo-ZBkVtetAK^x28)F{g9q&7sWw!5__m$#i|BnldIN>;?H>%QRfR&`-Y6$ zwM_0d^i9M{aO-s0j7>%zay3~*U|^RASQbG^X_6^3fc!Ji@rSv<0aJnw_ji9UGLEr( zTYCf7`O=U{*Pv1P3et>B*_$*xd)}hOW;4#LUvdySN>h4P>~vD9#Nvd+qj8-40cegY z8b^hQnU+kUOjx>7rs)G={d?Z?`I3@_VZ)&eR^Ems!+3QPU>cvHRUwWXy~HJef6ST9 z*S6nueiYzSH@>DJ6i6cvJ)-&&R94baiGpsZ{AwTcMLR5TFzh+SEE)ty>RrQUnh)Hv zi?16Z{kCoh8o<6dtDn*ux(=m`o=&}eH3vK*60=`+T%=wHyA1I;k4B*oj+{!*y1P0V zMB&iJ+#gQIZMFf+N3S&2fWO@%Cy%NOn-?qfo0Y8T*E&4vXBmvFY|nI9;Gm}=;F|ra z4?YN^mgx^uB%?wYHisiQUBSg&ayTX*36OYoy4K@oipP%jFdxkT&v-t?A?AmgB&Ee< zDHmiJK@3KdoGy+ctJ>}2fPVB!aN_V)lPa;2vS?=>x6I_UUt$VlMkCuGMt{ixrebW9 zgIZ=l0qZ=aS+mK_o4)PsXR~AL)g5oz>*OIsd9H-q_E?s0Vh??!@6${MEz?YT4a4@o z>Q>gsusjXQizNFKTPKoNldhY|D@xhkqm=-hY;`*dc+|g8-WFK^f%d9wyOr{ImBt2m zMCgKQ6ETMeRLb>Kz_I2!SKPT>jI$~se~ zZ$*cG*qY2Z3)Jxi^q^}G9pn5z0JuO$zkxjzpki5OE3`&sltv+rgCxzVY(39H53SMX z3YsLU6oN>OQNd*rtgJFNB8w}D+83HWs;OlBMb&srlLD1gDzy=1adl}{T}%Yg#rBFM zZF8ntMWb!1+KRMFo{%17FXzHRfV{YuCBbm+lvC7EzDSm$?6JolZtmQ<>J%%Toak{D*Q{BiPCRm0@-T1SJat0V`4k%Z+X6{G zIEy%sESXrfYL$ELx#!&Y3FF=TdGp=0Y17n6#F2gW*=OC8Pd=%697tw&X>-n;IqFQ( zrkAr(oy>LXM9%neZvOmSj9i{sK37%4C&;egtA~bySdk1!?%XLGjnI&Jm-G# z7k!apBF>v{x>@`0e8)S6&wS=H`ifINVcBh!Uy0_95k(yDD*Lf`BL~^<;I@NHBta8W zUujfet1p~vcm4th-SmwW-s6X6s5|YRAzFQFv9s^7vw3N)Pu9Mt+*~9R$G`O zLY!;yK_Je!yZ}c+DZfbQ^a#uVYJ&gi|5@H-L5fmEnA^UX@q(@;&@V0tP(XVOnT$A zlW-x~_(B&c;%A?Y_KGJvOjkYr0GfSfdLM^p#FlyI6C)4R2j$h4n{K>GpQiuOAN`Ro z7@s7IZfsg7QGDk+-#ProZ~TTX2x0e-JX!sUulVxeuDf15+<4=Sx>#*X20birKl-tc z4sU+bn}-j|BKL;tuN&^W?|xk=F zJh^_>vz|4)>}4+-9{&V+$6lUZgOA1Ro8SCqy$L{m;2&SQIK1$McMi{g{`2*Q1oH+@ z>wo^|e}4FaFZhDtwkO{<{L&kLS!28ZzI$b%{FvcezU5nnXFvPdx`_VJhyGe#8+z^V zxK55Y$b0!%O#j|{e{cA;U;DKIV}cux`|p2Xc!IoQ@~z+ct-46Y;#qT%8olFP?;76r zwtg=7+yGu*dh?s!G<-sCsIX|qt4eQu>syDf`?{|iZk2`kSAX?a4`1^&U!!>XTL&a0 z8)|}^CFIQus!BwT`2!mB$<<-9aZ?-FaUO0oXcYFfui@+pV3|PTR8B}7!sqf$D`M(1 zD|+Uy`6Te|d?mvj$?SiMo`!CtWmeM`Yf0!S$GjDG7;?OfK{ngK1S2*|D`rTS5IKJ6 zv%kc4Rwn~xde-Q0LR1LnDcz(ct(;UoUW3+(WVMAk-Z4LxKuaxC)FMM}%x|sW_rpP? zh_Q|nn0nC%yCeg)ZnVmM(z~=R9x*zuRh0Q$ob)n4Ugh#L)}WudWi-5OnG~n{Nd%hI zKwI||e`~B#jfhVtnZ+Nx1<_3DE)6N{gXlYL@k$?tC7-@^;<|cVJL#7&>?e~egJg}` z1m%f^EW+@*0-k{5i7TFLf8|$wm7YxTr1*D!_jh#h`LdV2R2Ji3sf)G0kZ)i4#b5kI z(K#Ak_R^QBE*Eh)hCpLce)rw?46l3b>-6dScgZ3f3&ziU=Ci|Z|MqVUKl3v`Gko9o zeV;zDzvGTOhF8D()w&qI|Ni@im%se)>0%ZORvblm{o}Q-eXZfV;yY!r`b2%An{n{k z1)hT25nPWNo-V)V-R~KG{`EgUeA735(}34?umJzW$3H&&(l7t=@UuVrv%`0P_bcSd z^iBH28n5+SIDcXIuJ8ITee#W0YX0C4{$TiJ(M4W(C5TU@AN$zH4uAS*f2ubMuX@!h zhg%+Z%karhd{T44;vRK{&NH8R$MAy_Ibsan_rCWHKlzhCsW%B<@fBY&+$6E!w-oUC zCRt!3zH6?*H|R(%?|IMgI=LZuiRALt$VHyee;m2UD=5h2m9KoIJ{kW9|L`B`jTUZt zu(-#KkUs2@g*|Yv;kOz%{I8K$gx>%D_Yc1!@4$b_mwbuNg}4zAIc5&eXzeUgpvfH{ z02GC(*;q7cO7*crpwhL#F<t3a&EQ&T#t zH{;>MvRi`?_Q9)Cs6s#{B6VxUP;=q_EvZ5n!6Fp)r`{moef%$y*FR7z#D^#RQW9P8BW}d@>}Nkq>i9yv`2ZGl@p=pL)EwmxYcNMF z@PUIT_CNT8KPYv2hScJExe0kh7t**X`MIC_S$*>Td7t}v^7_iB^fLj^lU(k)>n^>) z00y4Sf7`cx+wjIWBA1U0cieva@Yq`(tFH{<#sWFu>HGciB)wc;{CJYOy+=Oq`Gz;V zVfaG%Ru=pQ0r3Kti&s17b~sSIWQpb&Q&0~$rY z(9;$@3H@ZGarJwa%X!3JZgCh*=1O#2Zt%)ViLYp^`6|nLSn`!s@*7eKSpNA52bUJo zNDN8YU4BUh{L|T_v{e`=wV!A;&N?bcEA?7@D!no}X53p^8oe#m+vM7;(66(#MLqS{ zy3dIqh|)$jp>Apu!p36*NhyqLfVyYm5zo=Oe21oo#__mIkQoE0@TUf#an02pMQj@k zx>Iq|=^$PHtT?xLqOrJP;#LhTrm-l;QHzD~$7HdJcfawp7aLw5dGG=GR+k4K(09?X z$i%`EcD%^wNm%f)7{n7;Jjwo?Cw$Iu&%O634-0=R!m$Yczz07d3)zPi8;est&j|n) zgt{nn96X^{oX>gUaId^yj>Ru=@EXYnq*|ypapuPru`iJ9J?T zEIx6D?CDQ`nk;;8)I~h>5euG@LkBl3_{{`)yH5N* zs6MeMN3O7cSTKS2QF*5v{&91FcjF-kw&n7a;d{ULd;LS3a?>DV$vPq~yOF@}X>AiT z`M|0@DaR{0-|!9JFns&Bf4j#12o~j-C$g~S%~pG^AkJ(nJ}?w|U9)L6t3D}|V1c6r zoK)H%aQ~W*#$}NiuGwaK1Dc3Mo5vDP_#WY$9r0O;){62uj5TH1|7PEe8Z9kp#dgi; z3KqQ;&K49VIyQ!HJ=q(oMLril4d-+Mv`d($R%G1WQ5|n8z1H0+^z{@H2?`Ok1e56% zVq5K1mh(7AN^l%@#0I)N(eRUz9{ek30cTrpa!Elf8gPkP=RLwJEJbd*TYKvn028h{ z?o6Eu=w;fZ!Rm;L288gSxk^XtPmA2TUPObau*tZ|iCX_UU3fH!i!Fh~s>miedaQKV zfe;&ATd(apw{0AG&Vk76CZn~T?>FI8%2j$3@n?f^ctodh))ty#uw6x2T(488*LI{9F!yqqCF5{-;8C=B%dvN*;~1U}q|SdZi%eV_#RPIvUNaQ*CO@751_ zQqC5?DZo=_;2)aY z$Mpk;cw4owmIOW4txU5r?FA~Oku{dlVIRbfaAFS#Y?yFB@y0V&5qJC5kSK>~886%H zWz1tHo~%}nbhccF1~N8!B~88x=XRVoDG`g7dm<-=z@sb}D-d>E#>0#i5Ub*>fwvL{ z&aXHZp^bxs!D?yOtM6(HbA5Ipaam{@xt4<`**NHhyI%O}Vy7Jno&!y6P)Sn zFZ8GC95FjlUyvuj*IjqL>gl*4J`An=7@mzYeBB~X zpkE*#zQor&aswY5o+RVxvGrX}ENS(tAte@lVhypYs4?Wem^Cb0psTFeU6h*__-J!& z)HZZ+KC!q}QApev{QmF%zJBQSxzByBzH)in{Vic`~Mh`?VvqLHIg0b%i|+uLrjc1X|%bY)K7&(EZi6y`?d)m5g9RtJbp)wEVxPMM`&(=mBxb(lL55h3?J>m8>&tZVZ`4WePRy3W1aGqLo zTWw*kAHR{<5FSI+6Az+ACVr6PfTw)TM0UZNjAK%;ix;!$LH2l+2P$AjfJU1(igVkj zm^LR+BQ(ef@E9eRi}KV{bNX-KQ=~$gGvy_+)?sghLRplXfz7B{??i4(82k~jA%?6E zRo!N6`c)1~B1lV3AvfS9qbrh(O0Z0MWiCAau^fG_EXwEQLyo8!7KT`a-*NjLwuqLK z=FK6ogo6xa%%z0{_0C-WAL3^xMn$WO_#|p7*pxQP$X+V~i(E z%iJ8G9NAmjUn7itFmi0bX=5k0)vxsGtjJ%#WxY79*e>$KIg^YEnKK7B_lZ8mr+lvE ztssy~RqeRUxFw=Fw97@7&yS${+4Z@*=D6BB=6GT*=hSLr3Y;G3<3zdEIizy7xKk#S znV_Kd!#Fe4F6Aa7akj>wK`$VJ&a^ZiskDKK0>{W|(AEYB1f=ErcQ6C*CKy-w|q;k;MeC3 zEcozb84EZ3V6pkogAWbA{r2A;eqCNydBF=_pzkN!yU54#-Q2h7*Yx3Q2Jzub=;I`d z^U+_*ca6VJz9|K-T>uvgU|rCO42w!EYM&z?uEaarSoGV8+H~;x$y#6eG$8eajd2p_L2)^?tHg^tUwI}9t5%5BW6Nsk44CjmvCWG_3v~;yrY(NLr)@jlp zhexo?W5Asj5DQg1)?_gd_N~Q1SYsz|GjS%Z0POKp*m2$J)~-w%aka|y7z$)ew6td{ zYKiO{EyAv&u4jkHC{}0m$QEvSTV$s~NU+te31YI#YOkS7R+<>I8)t|`^R==d!ylNz zw{qagC>FAK|NGDX{Lf`^{nzrE$P4tFN3f8?YY=!n<$77T-gVbq!%zS8PY=KF3%}rB zGk8IsUVn0U%Uj-}--Lp17Xg+oFl7-;8jJhSmjyNcs^shB`@8XB%_qxuedB|X_`Y;} zH#)x74~u$yPdNT+=96xHlKwe({6)&AJ^g8VlW=r&lYTqNmw(xp53iN4{WF{=>q7Gn z|L6~gcfIr7g7e*mlU}u96%fz$^8M-fHkP0Ish`%be*D_!f2}OauhW~2xBSXm#H)27L8>hF(R~>$-cSg;_z2%%11#V^c>jaL zFTDO2hCly{_YeQId~g(TAYWg%GLjv6 zw*{IWf1X8KNOwJ6ksh9!^3V9|2y(8q#ClU`J>AVbdh5rs*(q8nqW)Fq6c?Lr@2HR8gjU5Ctni%mVYU9dhgo3yZfi^Fr}{DLi?N2<_7T=iFo zXw`+e#V=869&M;{N&_E1t!RZrwu9&dpfPWU($ij?URET{u=b3Fa3wwf3zzsspj6SH za_sgtux<@r0mRtniQBlaZc$H`UCr5fqVejEfQ(!EWKFG99mCrq11ri?`wYGV1z z#Xw%$c!GSR$p=61L0z1~<#~CP1Ro&86X2J;63 zdX;`f9~S3W%wd6v4+`Sz2XDCX27M(33-jycRS+zKF{u#)p3vft>|im7r`7lil_HQo z#`EOi2Y%oOhCA-K!=KWEeMw*Cz*igo=Rf?8F5hI@R2F|+$M;7wH!C(IT zfgkuMg7Zwp;Zqvq#|=Cz{BOVg4$0${n#V7`0pGzc^^u=Dzux|65NIs05%bIb&fgh6 z=ZQ}o-Ytu9dqw70d_6_$fSAEU9kAisSgyTRa&9)%T)&y+f-R~c&NX}CWtZu#dnX+ZU$oHrrA!gb(jv3Wi5!O=U z_<^3|&)#O?76|FhT00}tj5746{AV57b=n;ALX!^4YOEZB9%gbrvMsej;`2>wZmTWK zajHcU7%&?fO9{!?W&}>OX+}|a>TQIXBI)sxxFVs;p&Hp`VPz#9%0@fRF`lT zllL+PX>Zx><3TE$H1?jkbw~Sokdo`j&={;=D{lF1AC=$ay2ae4KlWE!&-;coG|K$Q zlnN-VfY>RgKs~mKfm6h13wEZ_H~*lPZ4>s3VaE1q>h>Y^a{<2cP5+cQf)krK(LXKQ-&kDfg{xA|7; zNsVj=x@Z_uXHOHh9hH+}X$Uxn_<029$^{wh9o}jSb3}4Z&XE?hRTO*8N13*GI+?U~ zsEdWn1hxi%d3V6T>|rOQhNvM z`RehY4u1BeePt9$mb;6snmPO8r2{F3y7(IbWJace0Yq#tmZlU`xjADMi;^KQ652b4 zOU>9UIu(N>`Jvpjiw&qOH;*c3f-?4cKx>=TMOW23_R7Mk+ZKRa2-hJ2$9i zxd9Xv-VDMmp6>FAtHy?%>PRDtxCdpCs?UD~P(xB&qu~Qjx)B3D%!w8YJ1i!#=^{)i zybpejQhe$PnJ)H)i-kNEnvlUB3&Cs)L+FBsMJifdKnkBYh;0cDuzDMwoC6C_ve6uy>j}mAC`ahvhxxHac(v%f;1ELr zWrtWG8|NYeYokcj0e?&hl8Uc5W~> zf?+qKHXpVEaC?+R^Ow3|BfF*1pAA_JJGBzNA#bpUgS5^1N_wY3wz0KY^Zaw@*yxw- zR{q?G4>Pv1T|?O?nm0N}NZ0vr_RuM{*plCA0o0mz)J~Ko=QC~=TIm~q-GEnHnCGuc zv?aJ5&cU_P!WRW*15-Kj)yD1NUKaI)!2&Pa`+CZKn!PETy?V}2 zZZ4}X_mk4l2dDP!ByfvO?TsvZk2w~xBGI^thEna~5lre~p-DM-U|tr z1kxtsOwrVhJUAAufz%A+f`#g(OL7C^ecESr=9GD)yE6CL%Mo^ZGlJYxje0nU1!JWP zeQ8T5H_!U`o?cSMD?ks^vkv{C&;6c`I*qto0s9j;ucaR%E6ItU4p8LkyCu(?iS zWgHvbiOsAggv!n_cl4OX2DOgmL0LJR>fEFPFUTc(9dNBcR$G|sTZ0~3J9Z@*JF(_O z*GgnpoQM@cMQ1r$WrEI*IN?z+0wRMutuAyI78?n|RCV8gQR&hHfz*~!)|ykW6~F2b ze7dbYxL(|;OxuMdg*xSWir@9^z$z2NSYRqYTcjs#5lkW&S7NnfMr&n$c^|VY&J@A~ zABDii*xChOm<18~G^Ke6jL?0l6_!|Ot2&*`@&I9>M^h4Po!@lh+C_doClP9!c3*q? z+81XV`)za?-`;IIe`Wxe#E0>-c_wp!9S$n6g-)|$##=2SRBfFmT_S7C9eZ*hY*CuA zlX4)5%odc)A(vxpm^d_0mQ_^?C>5Tn;nFfJjv<9}C^E-V;xEZt=3Izo?e!y~fAhIL zzTW4K%+`+CqAatjIAr|b&?|FcTePPrT0)+z$j(ltAWn#nP*Q~PKtQ=!Ab%8R&87Kj zSo*}gd6k8=vnMK+;9e&q0UP8eu0t!sDWx;+^rc;i=H$YLLTaIKna> z_LQDVu*;l!m^wBG4Hq894#?@vjb#Z?3kV&Q4vQA2(ehZB_FV=3~%*)u#Muus+b7TWTI^OxtZ`WYnC^I(^u)W=_E zQk`hsTQ4=@a=dmz7LYt#J7G*3<;W~r#+*&4)stntF-=QII@Vd7=`qmBaTunw{A`Ol zCtu>szkyjJu+w(oW`K(QhX?TE=c@{&+bZ?vy=oJX~^YNo)qIK)+go= zXg-|Pvp_ot{f0u4;F8chsnZ2`)I)1D>`QL}1Xc8ahqeCzgkXLGK=<<5tRBnbwS?{W z7VYmY)?Q|nsP?VtzV;;&qxyK@PD^0PYEz4SL!H}G)9PO|olkKQoGPVJGRc`paASWI zRhdy-C|Ye{E+2*0C+6z6O2jKIJUo@GL<@sFoLYN8ny0xKA=4l1@+6{>wpma|Pc?FN z;2o2^8n|%7pTnb>_LS@zQe$WXX>|JCY}BtEij%c64lk;6L07gZPs1^(J>9A_vzwq* z1G}V}NQZPMBSAJ}m7xS0KbF5O5GXfDj!FkIgHl=BtlOehifeX~qwJsKQ}HJJ0wTdo zXkEGNoO`D0j2j6SMZpT9uxjJkUa?M)BMWL_MCB*$!q7DtBub@0FXY*TA zTXlwvQ1$&?GWHL2y@feqX$onRK2o#mfp*tRV80SH0XrDDeTnd8bh2Fan9h_@3eujL z7-NBYvJ{ej(2}mnTK{0ZBTzBuQADcU?kY6GSIlCuL0lADD&u83>j_`+(UKOq@?4KJT5NOuR%RBD(me|% z7324%Z>E_3f%uEvb|d8Ky)_WFB1k=8a~VFbdWvJpoYed8)| z1??;|BqTM|yz(=1&Viz}cIx{g^SFyk(3XJn>RD>F!0g}*VJ)CVO}3P6ZaML&+4eW_ zDL^C`kzc`=e_7j zImJuu(R2|8PvELU8z^R)023`w2U$$>%-cwdk;W*yo$TpALD;F=J}$XzYa7(4FY$Pj zde3#m^G=N?y?FGVz(CfDMVLSnX>u_1U<08Yw==NVLr0Cl;g74%H3l%Lu?12eBZur7 znvMV~Hd>8a!k5;`J{Ikz&3eGW243bX^HH#4{@qliAa-tJW#sJUg;zV->G@8X7z@bx zgMu2Zm6ZVkO6#xfS8*)$FQ_*Dj)r(Z!l63F4jbaJR=Xm!?isL04i=4tw$onn1BQ?(QqkxwXouMcP0ytO@RJ1MLzQ(la7tAEue6TW(=wL5<$s7Q zQBM5qHC{AkyXqJfFj6`-ycp!6B@GcnakUK%wc9qCsfhB#KLrv;f*%c?)p( z+NOXJsr*)*B_L%?muOR`wQBp+i*}@74D55rZ9nWujBt`b1{K$&@dck8H--KzRUiKk;#d01Rog>26+S=}^ydepxXTjtAStjZDf z)G7&~wT8A3-qn*GSi)efu^{Y$!!(6sC{o5RHzX%rpwd*`@)N$qu!)_=b zp^~)tpYu_ zJMH-Nw;%r5vu53_^_;buq^-V(h#VfUF$VUHyKE2JeeI__5!bGK)}y~7E7qlAg}00% z>h1{3{OqsA+crf)xOTDw5#Z1{I+8!)79$g)or%?6WjQY^bDn@w%$f&INSsdko#rbh zuyf|y8v4xS(*!<+tYFGmRqWgeduNu)WUU1>(THW}GP}+@Zt%8R>3Q_bTF4sX5 zyhLYSiDWaitKDYgGprHy_w1#yKLc-q*vrHOjajDWJh#g=!>br7Pov^$X4y_|lqD4Q z_Yb(mTligUQ7fLdJ)?;@bl{Y$<45^{+8B5758A?rV0Q3YPy%}glwy)&5gK!^VHMpg zgJRvRwPeel(!=GNJ>*g^^%(YIyqA4)2%Lfi4|OkmiX_w2Vw)gmNNQ(48Fj|=6hyd* zucC{m%gm##);dA1wlK#NbNS;(X|v}NV8r8p$@1SG~V9ne&$tSY?dh-%E;U>ZS`bli_M z^o~a~p&SN57;`}w3aom?c1(dI2c9(bsHpY+JHQvGlX#2Nh=Ue+rM|_Vc=SXa_OG@Z zDj)^Znpxp&nt=x2W{5pwKp#-D5XVAWyCQcs?3$?uW#8109YQOdVq^a6K@BkagbisS zdC>3VzqsB=HKA_QZxv44UgPl~q|s9g2WH>c+FUz*b>%VWkkNNXcbb*s$2E2BjfFsg zFy+Jpov4VNwf&wAJiATi(S?}WeB1thaj${~5R8qE&wla1notH;z(JK&ocY;5etI*! zE~+}&-&6Lm4_zYZ6&zBj(#kQfwlLSkHE!9JOsab5iLoeFveW7GW9^kc9|c`GnWNk( zU~8F<)Fum1+l5*JNTiAysW79U22*|Qx-o+VxiSAv96wqyG_PDYs1>&n8o^^HT6Ja08|^15h~%*9qw++3w% z4JuUVt%%7fZ51;Vsca~AA|kJtO;OMs`A^C^zvmQ^?jo;(rEu6et~ZfMg6xpg?uZt# zlGB}_{7eAbvud9P0w-VoaIWYak=U_lw&k4_&dg+=>=%`Eq(lHKlAR+#8~^bBM*&?q z42&DCM(|li%Wwd0AuesNwE|0D~&&=>Np zoPHOEIZ_LI3%tnNevx!3;$$<>{ArEWtqQyVaSnA()<$=@zDD7p;4q1|KVs$xQ{=c_ zoXL00wKRw^)iW>ZEGbLCbJAoESJK^)F>QLAKJmA4U<$5=q?tI|F+wXTeNgPN9ou&~ zDcCJ73mgWmGA^C+dwk+-FC$g0 zpO2B%?{LA}?8x}yQRY*1{>tKoH0RjC;@7Ds==g1n#N>zR!ssqfHRQ7N`0Ygt0gtYuk-VkwaHiYTFphm)GJ#nbQG|A zfCNU{m~K+frac<=OWcN%D932X#k2Asbz+c2M#~>Wk>%)fYx(FiLbun(_2|#LF;3u9 z5B1i*GL(NLjptKGQi!sHZ+PH=@4DEq7j8njiZ=FBtfA>iuC^|1A)fNv{ENXjPmnt{l

S*8I466O05C|LErfdl_IzT< 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 yDX3NV21rdn9$hxp!t1oA^I5 zXKGYzJQ38k10~YkE$=DxPOECo1P|Y3S6vogy#HgTC;xA-|PBiK!Xf^;CEF|p+ zR!v-0lB!Nt(JGO$DJIkc8=z!uwX`5^^knL0oEly0JGDDQl&5EB*(z2DJ zQ8K5rCP|PMnR&2v-A0zu&LxQ!HIhjr4cqaO*wjD2HEjBmCxodRT{CQb)IGzd`#&ly z-S~*Gf|+4^r2Uisv~P4ze+CH%9*Kva{~&C~PqWY8>=yBnJ7$IR%pL=BW$;Yq7n!*PtUrU05o>UwT};$9=*^{1W9XXVt#-qp{}`VeyhfP%&HskE&wMgW zzxPA-GwyR(_A~S8FIX}{Rb~n&lvTWh%AYbn;A^jrVzYSi-L+GWngp z@PaUR=4oN-)Kkrs9jBZemJU7`H$n?YPS9Z}T4geg`yQU0OfNQ^RosaeT4(?EHDSY_ zJvYp~`7grd&i-;(I{Fw}t38DzW(LbQW-Nh`iV&m};oVvL>ltFZj^6T$!_*JY4qKjeeAsZ` zM~0=F-VE9C#xQ-s50D_^*HQoL`yrhFyRZY}lRt=KV*$P~29`8q9W`rz3gEHG!hLB+ zCTF`@G(KZ1ZW&bgvxsDS(;d*CuYJju^{hMsFMS?*9qcP)trmD_Wo_f)a`;72jEk>h z-=BkVQN$HVImX4TkL$%+oZ-WX=_Bxw3{EG4o19V5qmZp)2sbdxamQ*`^V zm9XWdFTnV^yN$1%w>!#gnbQa0M(&UwTWl;$*w{@ie#v(Bjc*7Wo{G;sUi{i{xpTi2 z7LVcacP$$mvv>+H^T~H(eD4U$$3G&>Kj0zF{-R>;v)}jU9U_6tMSB?t?bkGBGs`{z zuotmc%t{CeqyDL8X9Oz_o}cutB7I%7Mq@X14cOWxnwQ&DCpdS3*AMTbgTX`EDA&d{ zc&flhQSX;v)oW5d86ky9+AL8=L#{D;#$$+@TTaJl4*)5wHvFRRbP|pz@&FZXp(^sayq0d&RAwhwX>|l|Uu0j4Rkf#e!ijNY{-h-H z8$hozCYxz64J0BlT~cvefKrHP4<+e>CXG&{OL10M5lvTA*rPTZ7)Ccn-jw3vwC#Zp zwY9jL-+f}3{?$*y%!%&~GXWRDx*UW&<95e{9r*RZo%oR$UQp?3wndn@bAJiRUq+Zz z+G!0EEMNHU*=E7MwBhK}{0tf=y?=Z`F|G@D;5UD*aO2_l^d6U;9C>YgI`NMZKRu!| zs2D;P%fupB(&!(uHq7i@a>7%?7JLn3!)u=xroZ!(u<=8mkbu?m?|oO8zuP^+hLhrq zF2575VzYusa+dJo>9)6hG;Dd(>ulZe>?>ZKKUXK-m51Cf%s=i)@kPJ|tVgDw-0(Zr zpE@J5VNEYDFn0c7;)VWR@|efrWxXqfjcQy>3q*nq#H;A26HFMn~^{;0>sn=JTk zNg#efbqPP(v+cx_!j?C`A3jX3K=P$ zbWHv4e@AzdVGBQfM&UdX;;`;WF#kjur9mC_wFSpPledm{g&V}#DsO|V7zHkUJhK)u1aMC4j`z)54y$UZz ze>`kB<4^7W7J|9(8~^if{Ijt0F304X2=-_GPInDccoMSlX-~$x5$A-BAN^F8A+Yo9 zCt~T_eZv($h{PYoepNHY)y6(`-1c^1wisV=d@kU#rs3)bM&Ggg#|q=b!hUFM9QT;8 z`g>Oj8#p#TjOAeXE6TNTh`jW#!gd@7_C$damc~*12C} z6Kn9dTfHxAw7aqAI|`|NMG}K}ysY>=t{HBmj;g8D(9x12DeX3GyO^wzTtd_;al&@~ zA+W29jKZD9?FQP_=KJ(pNRt~J`dzuJ@o4g)Ptln8i6!P{*Bl3w58k+2>3BLHlW44a z*G9vS7s6EJW=52rd$NZq&9)Ou3fEadsqCy-Q_?}-#??iB&ek<@4b=3OxGIsBvx!|& zsx#RT1EN+7NU*MZ`J`L5XB;siUZl3mHpe}ZrD+Uyt`*;jm)bU-hsWS}k&;OaUkYPV z#2?jJx!QqY@rWbC!gcY&6+TnOM4<k}f2c{Lc71W-CeKFYxgPKuSLF@+lP7&+d4| z-`UH3OIVg-Uv=RuF?`v1=VQY1hi`;W#vOoJ>74b^<5=J}A7L+J-SWzrkV*R`h+aZFGHy95o0J=b?%{1Utm@$g5RZ_`*B#1~OluX;7SD{zo4$Kle9 zn7~5P9D`?w$<9YTE==F&0T$>tcDH5+*ZhmV!Miy>(LmW%=G!^M(6F!*l7(W-k{M8s2kA?3Sgy~=Y%#Od6Yq@cOF))Rj z5QfWqN5adv%%6-W3D9F>ygsI6gHI)J!@TsmH;1_=KFwz0r?HmbmP6r%>1B>%9KXx_ zxq2SINzV2Q_WkyUJR&UOW_Rw~?}X_eUub<_!54rQuaB|MrvZ@J@!S`N>8D&`zg}DX z5EsTLXZN>Ze9d8|d*;7?g{3pUvU7zc(U9efxbvrc3}X#Xb#N?j|MDeZ+ZT*6eAscn zW9|Ksx%1$|Pw^WI7#sY-rxhIAOZa>vzb3_VQuAa2VM#IC`LUBNM&xaucVGuLR+fYvc2NZR}E=R(8Ln+Wt%eB&TI#? z=?G|T@|v7quTZ+iK#jbMmqO~zE|QcRq@XyQB!xD*V-cMy1d~VYS~Y2tD&C$qUf_oq z%e(+0oH1Ea7uk&PfMa){=ZF`Q0uvj({2(iSg;<9`=^GdvkAbGhAq#~CdqrtCO$!GV z3NN~Rv2Px~W;lOyEK$M5&R)nP5lM)BryDnyy09$qYY@x26z1PhlhbYA$F z*kFcw#~qH27iP>3#@Tmh1Zm;MH^Gej5q2T9r3mb^6##^BVdvV{8GL_z`iJ-`3(5j@ zVM&9{cBVcV!8XoQ%Cm{Ak-0mQM;5E0m+@y}wtzoNUg#1Yee-3wY0g|C!Q;{oUKQ*M zHcWtNkA0#v=r((Yr3DLE>a!EeH)4_q7WA31WLwFXCG>OvOCFfOY{yI{{WBi>-gR=2 z+L`>|Z~4G$gzbp2>4nb$%S^bGfpnWCUd7E2lNahmVV~?jTVnjUb z+m(*|#3%n3a5EM-Gs*73f4&^Z{U2jvnrQ+h1~X#Foa~xlv~L*33Hoiu83~l>PG2#x zJ}|CK8ZrK|Kcd||@H7Z3S@Ajd7`xlj0~f`0DAj{{jEXmLu3 zDFdTI{fIelz!q?WVC(adBvZfG?0%1Yu(Nc~!7)*UyzMW>$=j4()}|YD+>qAeA|P4h zQX$h}NqO>#U4Wex98=uiEWO7RiL}Av7)z4)xyx{?Tbn%hjlI-~{l{fnt)*U+MIYJE zMSPiO5ecwjXzRPFn#*c1rsnT>wAryZbDa3Xam0Ot@x{XP!77dijms7MsM(HV?hXR} zLzaEZ@nYP#U+}kr8{YYAT*Jmm{AEuZQy7otBW>1l=o8O7I}js|yxtgbKhADIAMRww ztocBFef!LMv3G3PuQV#GkMxRd;@hRhdTDV>n`+^^Yp+4t`|Wq@Fl?B7$2M^HhzD08rz6n|vsSq5=ROrcyfqSQ}^D zn5`ry*pYj|tb>3AGqP8#m(UV5gje1eY4J*HMW~wMN^yX``izh{093-Xo>2q}gamO> z73di^(8w#Jz9t?Q zO zUB1xECS%7!W#&e*6{m8Wp$AqCnJ!vNFga? zvA8H{OgP)Ul1GfEmeS*O*^dm3|J1{qBYJN-geshoVV2}zMr@%zllWc57-E^H}Dc$I3-}94vWXrRt zRGSSXfWF`+NjG42Qv}1d@0Dc9kxHKZVK=PUxN_=+r!MuAIL63?l6}Gk)_fp%yVB?V zH#*~r_!GvTk+DX9%GPc37Fy2oUla>VudX8agC=)-V6V_yG;hS=YA7O7z^cs#Y@mIgXR9NWW- zY)YDv(l}7MHqoVMkOI#yU8*sVbgrQava*QWRY#67wo+0nW*Y`E+@oFc@oz6Ec5X?2 zXyQk)Y23qmKRIFzcCC<7g|*6OIh!Ct%l5ktbRSqo+_$sjZU)Rj_?aSWCpExyFAa%Q zM`sh8Ia#n6CT%>E#}oE6!F5eOC^s}W3635_JYk8Ey(l6KWGW2+4 zCS&f{^b*><+6Q@OI2KdWqpO7;h)_SE-0=ZA=YC{Yqi zW?tx7*|ujxdFkk3$!JTCI<)0vD07-^CJD0_;_*K&!W+KvHGFpO9mcTpVaM5*B`k?Z z36~MF>I_7M=^zqT34%pF`A?eYF(HqlhP5z6s%=w$O+K@MQp5pNGFuEA&O$!frV3+C zKx^MKX(9(znyQrekiRZwfXEEQL_5jL$Ht?mK#5~)C90Wa=NoQmZ7@-STccqUdBjNq zx!F!QjJ23xGwA}_m)y=k5(O{R#QsaZ;mME(EN)cZcq?*)0U7(jU)-WQ;v_x!@Ag+A zPnIau0f3_M10mysUraE1i?h?q;|Wxij#ZB?$8kYv8n9)3QHN?h{`hL73doZ1+@I91 zc)>EiR=JyTUQrljN=n*e4>}lwtW%t>9w$a4^ucLfA6R#8ecIM3o%V(8;UV!{r==_| zlNkYkIK`z87Qgs>on?5OMn;h(#yXL5?P(*!Qc^3VLH>cxp&i*g^T-w^&zOP{W7BbF zF~6}yHSdJ=yD+iDym19yrjA&UMPQ6A&LWDihwHTO^MEw>xFMnRHLv13;z z9ZtVjE}weHY1Fp-=*3r@-c&&{f~c(NK-0TcLgR%cmT*ITFxQ6?Te7DIikI1<&T0*eSKa<3KMl0azZXu2cfeA5WRVuNPkKp|=d- zhn$6rW$_g+!K3Lr#~D~16eRViic(Eadf5`8o8HJreI2oB$$J?O*B&K}EqPgTwNvB3 z$S_boaXTRgcF5P%x;;w&o!=TsS&csi65m+S6#L>uy(W1l-zsaSQZ&`l>9o~i?Y4wOasHcL+l-}AnkHJ?nJ{%U-V4Wuc_trO;|ZG0 z4@}7VL=$uR*WB$y4-IFa^&+|L;e^`lRlNAryPhXCOQdZtG~N$#PnWSYHHc7*9W!ikTKgSrG)O3@vLI>xQWBH7lV!3I%2FdT|WAeVd{2^Sa*B% zu!t{j@H2enYI2_-8c^4@c7l&1k?lxkIuct_AW6Dc=kW zG}``)yc0k?r+^BJm~#x<6p8n*_1!iJDnlS83djki83)lZ`Hk@o*DO z%-IK?9)E+9-~a$X07*naRC;HK*+(RsO{d0lc6V``e<>-BcS*x6`<>VHPEcJ_TTCKJ zY0}BI6|c9XQqogp!$MrLcE?)+R(nkpj>Lkc(pf7NZ_QS;!PLjsg(t4|%=C#_b?1l! zQ3^6&zx~2bH9F*LlG>g12=dOgP8GwEGNMRHqv{R~9NS2|`6ZK!U-4RFupdCempp8( zGG6|&bmuL47?#{g-WGgoNLjU38 zy8Iteo}rK(!oKw3JrChg`D(d!x4C22Q@Z6fTtu}xW-@jrEV?V^^|-Om$|^$L9T-fd z%y5o4xX35oBKqBifhCtDdYMKcPzjZFDvTDy(ycBDrtxDZD=z@J>-^PeuG_$1tZXHP zY+7NPe|u5kQ+~eunSIOvT!pWA>tteOvWYp}f`e0eCnrDc0?ZD@o~WK!FUCvtb^oS_K#X4;)`AO$ks2Dfsz=u(PTPMY+3H}D5|TXJ!P<>Ve)HT; zdF5^D4ut^wru4`hX@W%?x%Pqy_O|o|l#%uA0!H&E`-$TVv5G#*Qxd1}{`^Y7cdRB> zCb=vo@4?nAGZFaTbV;3>SU<*7WPwiMO|Eo%M66N}fE>X5&z)m++$j;)(Al;>R94hx z&7!*9FiihYm!d{Rp-Q?Nwxmy$xZ|EsQr@tOF8)+kXz^F*N+|j$-q`2d0&kKQDmA^5 z4pbHsU%biVen|U*dU=BB5bi)pO~Xzm{bgzc&V5HiRgUwnOC$?qLmTlSb7v zELid#&WMd)m*5|d(Kx74k075^i!-XD4yvkey*unz^067^%@?_D^}ZrsI6YEB@f*y% zyZR3=XlgX4%;gsAnDYHS8yu0nkGkHTj4Ge_tio|-$0`#Bz}y)LzxTyw@9;?DRnczR z!i?%C`6;bGFDI=YSHiL;PPI#}5n2-Dxtm4p*y=-pkBmG|C;ww}==iDqt=rD*XrTPH z8ro2sP6I_8P1Ua8iv3I5Secm|Hy7|pzWh1S#GKz(x4RsRCpJE=g!$(>w<`iUl1}dz z1(I(X+ocXUE+N(Ih!Wgx3F$4U68Ck61xSKz{Sil-(QZatGUeEOe2pgg`nIU?pPGx( zh(z;=Nc1?xTrr_8$#}!l$^nysmPCPw4fd#i4>X<@q78a%Xt16Xcpv}D!btL!On1|3 zBJA6vI*|DM7$IzEudmw!e;_y9>6r`k5ZC2NgNsljOFOAWKTsN8YgV{DEgyX<{crpv zG}!<3%CEQo1MH_xt=azp^3m$0aoZKGar!xeyu}rKI!^64ArgNSPnD*UM$kBpgZmtB zSN|C;;%iz=wARu^YGjc{)e+mZm3>Oq(p~(WI4Y4a8SJi%b%Xbwc$n-ecBUrLCT3V6 z5{cQ%2&PG6mx5G_UZMM&6e#>BE3O}fqN35SYgbd!^@wKLlTL=kToyayJ4F22%S4y5 zFv*Hj{b~0Vah;sgYwxa;ggePghb%)|xSzR*rDVH5HKuN76#~GJZFpuk5X9;axJF7s zF%dG}q*IhEpMaP;k(xMIWhZLx*!H?S+~Nk2W56Qh8FTh6L-aQ>urh(hmbeV#^#Bo@ z1xFMlMY_v3@-^<=2bjz3VZwWa>x4dH*0`3a= zAtv{e2Sq@~Y0h4lf-9NHXViQ;@7jNZ)L+_Yef<|+N0P!iUjHQ}Et0O=!Z)?-H<&RX zsizXbtXYIhZEwfJ#;Y-eYqNdH$B^;-tapc`PO1}2QF)$%(ASX95>!ooz%hRyfbMnW zL4A66Bc3kDLMR2>J{_waA^O_uiO!hwd#b$Uu~~Ea)bzkSw6Q>3g*W1E-k1y6L|2?c z1k;~DxfZ$NR=D-nvXN^5vY^^*_}LL~#D8hlxpJbAt7wgO5Xk5c6t?k~s$92JIE3+1 zCdrvRkwq~sU}Bxg1Y|!^DVFJMR7JBvk*niCaKZ%jSE`OgjSQt+3m{K~d1z69I0+_|*3NLIHhki*Yq>;x zh8y{sz17Y#dy{<2%iV*>u~={X#&9>=^zq+~@?)5~?67kG<1Zfc&G|D=ezcF@%T8E;f$_{TC0xH*Q`BYKUQnQX9zXF4VXCk(l5?Xr zQMVmyHz;38B}@gvWn~eFlQ?AD!^Qj9awSCwYs@EECYG4*gab`feQWI#2 z`N?rpK?44=gd8-YlTQk=deAG@td95=Ey-u5E*>NHZ1#2ej&8ML)WOq<^82Ebp#!-W z#;?Q6{itCP(0*iXfAMHz3Pnh>j1lXUNzS1zi=d9VIvi|^wzjxPgY5;Ut+rHZmlUz< zv!C?Jd}+p7BUKuf_^-EzSb2;;9#)Qv^7mV)&oO#d)7eN4$O ziK{7g)uIopO&%M&$+Ctdw0DUltiO%{O%_)q=}-|&goa(ZNp7&loSLlkXZUq&#?nJ( z^OrzV2J7hUd82?(X7h6hgd?HZmbSB42B1pECU8QlE{%fTNr*^qNo*cxx+ltYsf{Aba@f*WnXZ+VEKZf*WjCH`s$oqjjse!xg^7!uvSC;FbU4~GK zY#oleJ$^{M2Ewv5Sd%yud>B zO-r=B=L@`$BdR-`TAk?63#A`3?yxg93SUKz{##_bdl0d_&rgtLcVnBHi~w9PFSv>B zm|qEO+o%bWOSPKRMc8V>Y9}*BYil1L;}r>J?fweez>95cW0XyQZ6(smntvPs5srJ> zA8Or}rNO9bp!WVpN$C<((pNRb)om?evj~CQ7$h+QjSlDjd;U=kh)=b2pdJ55gxMiT zKFpjOyKNHOuvsE!BrO6$L%S%c$m){DD6RM_no(ZIu6U#>T8~)bOe)!Ju!KBkq}1EJ zTfX+T-Om2ENh4vpI@EQVq1ZFI( zu1E=u#iR+`-)cgz@dkN5ghRiarh27Rt7`lWXg%w8L4;OosuIMD}&WO8_;& z`1r;A;xOaQpJWU<$&J~g&1ja*IIfeCU1m7xAEC0|(+_iwRlIpgO%S4!laA6vU;d1D zTk1u;gy-10B`m__wdf-RXyPn#Q;Hes*=!Zk^R=x- zT?3*rXvuI^Bcf^%{0{D-5$E~~hi$eSwNZFMmMYYiT$3Gtgf;VFT&5fG1mtInn7=>3-f;@|=^y8L?MC;O<^WWQTJ zVfR;x0AtIY%0Jrx;ht_a$vN@lVTq%_j|C4$p3EGZmw_#NY+>9jeQig)G+V}B+!Oat zorYq8^kcXk3M3!JT)12`%C1ea1v7Tdk`LzlZn8Z}Kjar&is7Pf4KiMpM^Q8y&%~Cp z%=oE%^B4CJsLRmuzJf$`Xf!7$$wkZbNn^3lZ3Hnyiu_ky@UNp$>$5gCtnH6*ZZt4a zERK}nNLk~S_MG0~S|T?GBok33qrC$(DQCr^I=0*vgv9EQ1pS!t@Bm$v1}U~2u~boW z_WRf3^oUw1dmOH7S2RHBB5mI*X1tjwgKmj44>bI-Us|fdu|th2)I@c{ zYhonwBzdsJ5y;wH7Zp0e*`HBBvv&i6F(^J z*=^Y~8SJz_zI@iZABYjK_@|w~SX;cYv22Abq~Xs1u0T=0@QE)rutl54KiSgOz{|i! zkx*CFwlKM9s4c$9*0id>!InrR=U}eUy;FZFArA=G`s+4%a1qg;bgPBD%?@$&0{lPIPosbZL#$z=LT0{?LA;pNZiR@B$-!9-`w8H*T@6kN3P zd4)cLVNwBGdzYi6!fI||lRO5*K$k@r)uc+^*QL~CTHdB$)oZoD{lxuZV^<6m&WTs;>Pve`;hJ_}K=8vJy zO|)abn~~S_-##k^cFWxnL`rHvd#xU(Th<1%$*T>l$tQQcjNqasGk2Vj4UjYpuHItKa!XIOyl*ot!Fa*hu$?h678>DzTKnCI3}M2?Z8ZdwS&wBChjb5Em=s% zUxRh5mvXQkwic6)KKpZ82YCXh+LL~j@=jf5F0pBdI0zvRxE<3GB5c6p=- z?Bz^4DAwv`E$`bIX2j4?+YaPpFb6Zukdp_vRQW@3tsd^Patuw1+#c#f-Z=y9i%a!D z=9=h*2>bmH*pq9~qD?s#4IF@a+G_Y+web!n|8@N-ZVE{Ayt~SuA$q%9Q0VBNo>Q?8 zll)-fL}$$Ds3maO^QPCb4HpNbmps0UVCt+Jh09I>8 z275XH_Pq0wAls3n$4+B$G$^^Cgsk>mwu+}2S#uK39+8kGj7xK<&3;Stk}Yej0kPG1 zwf%J`;=DwR75hX!t0g#ME&Zzd#7e786#DoTrnX+MsY|-1r(WgGMLL_ZVPF!AmD8ae zFZM(-c?zatXlx=mF_BiwC;nRA&{-@4o$|G8=57P~mVAm$U#0H#_P{R#oqcla#XJ3exZ8|><$seU zt=H&e9;!G2)K`5cxs7&Im_egMEg)U)sd*vB9- zq)3tFr0V3Hnp9X05KnRDTyn!WL@p?U|MBNqsW@V_GwoywI}Rpg zx!D&D)OaLc#obUwgCw6XG0C!B=f+Z6`EOZ0l>y)7_-O78v$e zW0HN4s}3|ukh00JER(v z#I5x;R1B*8-7?U#Y79N(UnAL!>^M`W}ZytS4=@=34ujb>Y{UF@Z3+@Wf% zYO{81Yjt|BmQ%RAlj^_6O$HvMIqkn}X!k^3R($?!UdkQS=GTb+n~XH)4($>^R)+cy zpL|}jOPm>1shkFXlaIPUTkhtVM%6ojZ<4d`VFkW1m~zqQ=n-cOnXbnI1N_^?q{i2l zAv&y%B%((Nu2@=X`Vky?(o)-!*0}T48mKE@K%{f9ROS+!Y8erO+Ep!bEmpC$n#PZf z>d3%;x`A#;>>DyMOp$5qZI)lFf&muu4FkP1*XuqEGg|i-#=NT7G~IU1Zm|O)6D^u^ z9JFqE89;q$mS-pwC&Iq;;XM!GQTb}QUELnluRgv$eC>B1Umt!{`I>L9YY#dwMsapT zd9rbq!e%Ney&Woy-LZKV4?C7U3a-~DFYkvO@%q~}9gLQ}{eC{}tz-^kjeKy{DE5OPb(__2w5ElCS$+&-WZ zkKAg-I?5&unN+vge_P|lX6<3+^@3ODJ_48#XriMAC?I zr51Q&4q;=nH;L+X$>CD4zvX2USCwBk!p#X8Va053uMhC=~zod*jiNhzS{Hi0QBufhMww=9Areho4FCIpv<0BROH^K z;?#`LURERU{FQCN@3_#2Bx+(IRj?rvPH?J|662&!{Mm_!HTqv_NX(TAHcLJr$l5}Z zv%9od_c8hY$aMF2KXz9|V7+_dGA3^s36H+?r{m7T&^ju6cbYgQ-pja)2(rIc_2bHm zU!$dNyOZGL76~E_@T7}Gu`W-# zcsp@WU`Z_MwhO|r)?dTd^r%yFfs`ggq9LCPrU8vcend&I>1E*PdE7Q82Iy&wfj#jh z98XZ(fAK^WBhaO(#uX~%gmkLy>U~|_b!9)eS4E&6S>rl)S(8_Paj+7!6F{m(8aIR# zPk$6$hZ>5HR65T;BS}W4aplSH`zTYpqOV@3VAZnQT&p6bYpRC4l2a}DQR^%RAj5@l zH@0BuYUG*nB(HJU#XBa>dz4`S8E)xoMnl)Ct`_@lK-i5}yeM0OQY7TQBdbfu%p3pL z>)OP=%0frX|K1&oA)Nef2J?E|U!QzD$QfN9&?ed*D@%DC3*ng>QBeofNsyJv0pS0v zgFxbu77{gO+d(!xm57OU;iNOwT`XO=N-VmVeNIgX7P3Wy8k|73ABCF!w=8udD3;W< z%ydg+Y{}17+b?4l33Zg|;)|51LvS_~0Tbf}^fZvj=?@ueC$TOqUAV;Fs=9c)a4o^T z*nXd{`|0FURaDzVk=e(8OaqH`Gg>H9xYZzSZIyKqUj z*kseLHP%p+nYuL@jeY4YT_wD4l9#@5>z2ICdn02hl>*m!(}lKjj4pkSJ(Ig|AbQuv z^B+-c31n-#AE=JUxWtB?W{EXFmN?0;vP^FF-4>ZgSM!-7`EDIy_MvuS+Z!r7q&Nt8 z#3{oY;Fz(JU4Il4e?}%E@tG*5gcC1gBA6_KH5jryfD}Kn_3Aa^=o^_ET_SNNL7k}R zuDQ&5pfXWe$GK<8nV85uIy~9nI(|KJHM-G5h<`q~j$5Q8&~PP0W)-EzXNCkgI>Fk1 z;4+6)Qqsdo)6mMxczCE_V(>B^-dE&D^>aVp{s`=;2q+24*B+e|;Luu*If&)+$AtT| zMt8CoeezxSK{(R7=-H3}G1ax`tVwerFBDgDHu2aBt`K5oMiCddP#38+2hEODq%zfJ zpMt{I@JLE)Cg+{$O>M%mQ`cSvX%y+=0gfaM?XmoqUTrB39!<>%x&&{91nLi<0ah-n9m<6i2kx=iyD=YBKT6KnrQp=F*7)LKSv zO9TB9+$&6m0gNV(%}_m^2K+ojCqpk%ZuK$@AV@ZLODzg`I4K~CMRfwBY#kWMJ7yDh zLcZRys) zow?GX-X*dwoVPbf5xc`8{@7~T!pl#!yJVzGH0e^?(><-YZh1omQ)*cJ6D;0@GyMst z>$&`m^W@4LA~vSw5@>RPF0%u1&L>?PS^*uXU=*TyLSDhicP}oKYEL8Ve5D$T88(WS zsU{!gjmXB3G7j$$RqG{BO=13;ez$yYkQSN=PMJ?`#$3HO4`U+^+=ytp!2RQxB)7Zk zH*(<(+aRkvgYA}AF{_j<>G z*{euGww{4-tsqJj2Xvl3P$Zf6ilgw0Z^UM$I% zOcXvNlG_!Z;}y9H!K^4DQfcsLHswc5W4X48|KF-He@V-a^i>;XHJ9_lo^ zyzb0y32%oH`T;j{&^uVznYaU(Mye+bqwO@?G`Mk~Q9>b}+6>z)_9RqHY#TOiJRA?Y zgt1u}z-$V3a?))m6A?x+@X?TG;&F3H%9%%X4>yjld9?$vc^D%=wzXX}5O*rTNp;Y08@8Ck|C!|6td~9T}sbu8Qi9nZm}m zWny4$${mNZFH8BD8S(gua>-Hek;%(ea#n0a(|O+}%)f*eC>~=?$TEKU=z}ndF`};Z z8%p4pda#OOWLu zY@^D9&(4$NXXzTnUAV{{{rB{^jIA7bXdzAv4?|-$#LXf%$-dFNee#n}CmP99gm~NR zZSwHP6}c#s+Ny=WwAOw7?8Db_AfJjKp~CgoHn2)^r%~}qdnqzb%d7W(kFG<$M2Rwe z@&5H`_dGpTM_}oZOb1;rwM8iXZ^%4lE_1UN6nRlbRM_a?l--3)&e@43=FvC0AG^RU z-7h;-wkFq=(KK<9{>sWq`1#L&(JA14R_I$Ew-BAGfb@*%F1uoty$bClr9+XFSB*p) z)6fh`423PPwu4<{lB8eqq7ff&vNR`EDe}$895H~arss@dBf}^q@eOA8*1DBSUVZH3 z@@ga?#vVb5)a(*lUoEXNu6mf(QTh7x>`8grX7PPj+xz98&Lq0BJvJquarK^(9b=CC z1#iMjj7?>M<_0z~i%g8E%2%S~f1kWN14KzMX8XiQo_5qW!;yWUe3JEICgtUm8ee%| zh3~?NR@aHQ5OFwm$vnWu?pshB85`EIM557wFnX#l7oBE6%4SVIwLOjSup&Lf_(Cd`b&^rPz-~IQ;SRSQPh9jhdhRte^5TdF zO-ajkgOEMPSE?;pll_Jz$zvZXerDgP&7ct)&!t%7xl3M24SCh|q(# z{SmmdMZgaHboAQ+hojLBaNvmUE#zx>cr5#4n_>gSX~fB!%_KncUe>n>$5P67*%xPb zUCMj=o%}M~<(Q`l?o<+yVv#Df2Z*#u6sDlnsgX~Zi68@%mXR8Vw$9hoMRIOaZp0#2 z$SzaEMa>%6Snt{5{ii3KK>hFeOTxJkteneX*QaV$Kyd z_EKsc-Q=IfYaBAZckAoGTBPaA=FOXsm`~#X=y|LPu! zn?>=Vl0GsJ@8#4MjkV~6JBnA`L0%V1y8U^-bb65b=(u!ratE=tS+lUECiA-i?_jNX+>Y=!$7_ISI;JVcnh)%GCm;OV)Y!t3HdznKWAO z9kK+ZOttWJO}cOnj`mA0BAe@zbmzLJHS(4;COdJ!MjB(ufgDNL;*8y-vBdIEda|Jw zAgO}j#iD^s7D;Hf1xF@e#6*Mwo|zI+_$ZXN&4rE!U*s%CI{2J&%b?MwZlY}f7?AWO z99#J zf4e^d`)~yOv8n^tAOF@h9<8oaO*`d)oL{)Jqr2hvhmK+E<45{*KB(lS$&)FPT~G3T z_9dTd*%Lx-Z$PxJy5?8A;B6y`Q7k-6u{L+1tiO?iK$fJvz!6_*S2Ok3E)i`m3XW9N z7pWadsFO+2sfp{?Tx<00(*Jmk<1&`LqF6|xFIra9L@7a7E;7N7vV@5x<`zJCt9Yii zyOR1R0MOYmj+@Wj>Y@@)nw%Ur82>~6e$edtH8Y6}r2~y3bhZEUjNtUagRk%hZYx1OMpO~abPI@NO;gvh3 z2`s5is|zZmjUjP~Sr=Kgy?u|%VjcRCsCZI{Cl)fUSUQPtm&b1;Mmj*16Th7wh32 ze!bwtl6-wEMt2k~$85x*lkBJKYm0BUA(+B7Va;ZH{PQk1uprf0O9J}vHQ(?yHR-R- zT~pXE{&_s%;l(5*hFaGMzx#%38J$C>vPA7R3BqNj2oTLe9c5qHu7n~J2?LAwNBRc} z)I~zz~7xWJKtlI;Z26^9~eFiS});{BPh$w!8Fu`|H&|w!9ot095xVs@ci5HvgR=g4%x$-Qz-ut{sB>xGGA8 zQQlliSH_wi+qD%sB4@fKb{DqxYfZX-oaCe?53uo-C>TTL>8ArfuwnKQ9E?3`w)vd2U&<5jBOqF!BUx;0FF2Q+4%+9 zh)$$jM_ZHGdi|zR2-dzUgyo1c-Jk^3F#w(<=~A6^)Hh{h_3UJ0yT4Ql$tVGmYPiJ? zgy%_yy4Y-=v7+dI`Wr{zM0w?$4@hM|EFMjE`QAnMyxp)2rrPg6uh7F? zibPLp)a+s%1y{IWHmnM#q%P%B3SfWfx%gK(x;H2CW0#FA-foTRS!14G)+ zZs#!(&$&3VzjC>QW89q}{ls5~yQLRH8DfI2UPZOLvxm`Q(-RJuey2sTVTFdBPlTjo zIo?t%I+10ZMr_d(|D;iV6bR=}-4!p-C`(V%XJV3q32a|Jr)L_ zlW{4?`>zray+T{F!+)~tuS;YYPUci+{uY81Az2H~5_<&De%T|&`-~0@4LkTX97_oKG|>`H$)6Vuijq57>-2q5RmQElgd6A zaxoI*Slb^dSx;iC+lKB$CV{T1>A$CDEdb0H5LIVC$P~rBXof|Kx_DD0j98L_$dHgl zLyT5an$*Z4U8=>}FJGAF-KVjRE-y}s*o6sW?%AQ2O^+H)i+U8fhs%?{wj>?HrAPB=IM?p8G~Aai4n{=Ntcs56$e&vLD$C@u z>B-HQ+ZyvC%1oM&Hl=}UB=!OAf~_kWyAf9gd4S;-qj~{?vPE+>@`PBdLo;4|DfK>@pt3Ta<3J zu+og$P^w#M5+d?Tnn*29N(%Y}3;BY{f({Jpp+){iMz-vDVH5JyP;!va#&uds$noSI z_g*X?qtLZ={&$gw;`YlA4beb`{qY+Zp8wC@n*d68RCVI@`n~t9Ss;W!AR!5aga82o zqJqjE#RV5!22dE`FCuQ>!nlkJsKbonjLfJo{@rk61SFtnI?o$Rdjol92Vw zx4mu7?^OM&PSvg3eY^X<_xgRg^?luS>g?y-y0_1*uD-7%#&q!v4u}~q(2!2c6pc@n z(s~@zx<_!tKC(ps6mQF4CCTZ%=ZJk2s{kS1j6yl~8PN|Yh8}Djlhuq3n6UwGX%H>P zRM*-^0s0yc@%TlCTm{kS3;%(N;<;3ZQF${qPQ)56{8usJ#_8yXW-T!_{b=q_i_I*5 zLuvi?mX`V}rbS_oHNI$rx27mTKrxUp#fA@Tm)=c=)%}_q_h{Q*bd(ytXMk#qABjC_&9YACszwL5i0>*oljxEjUZJ@Tn+o z;~U^11A99Ia)!o>G+>;u^5XT2mx>sqAhdEz1pUS^4MjQ)pbevh5vY(?YGTN^quB^} zdHwv#aB+~?Pgn!R$`js&mf0t=r8i>AVaFC0q8$=O&llv02Ru`296!<0di;b|8Z=`Z zelwif9msZ*uO+;jJbJ#MS_(uxBg1-!ka&VpLLC5}69iJi z#EY`>kr{j{usv_7ob~!)i}uQ1Ie=u2*n>c3Wg7oRRt>+w3M!3&XyB?q8NpPp1pTlR zTFpky;mAiK6*T+E5oLtO!cCDn#>!Z@&fUaX84EW#>liCz;W~E{JBv(H%YYd&u%H=` z3C^!Iae~L9*-m`mgZtX!>cA@mX!!gPR4912Fctx2l{YP-bwHM;yw;H{nEfEitML|j zwNkOKGH{tr1bOcxeFuXy(A0k&MNu6^IYT3kGL#1kX@R}$NJdW>JcUQ4*!VFN`2!G? z(lH}OW7aU)R6g(qzIpty>Wl{+z+k)sXWuL~UV5R6x_uk!K_7R3G*rnm*X8e%Hqf&o zdMnH~WRJPd&uq$Dx55QPP~oy+iMt0gm+XTl#jxGKiHw$kFs5I53Xx_^2`a;45O6++ zC4-R#m9 zFE$Gk7kaXig+0hnXU9Qg`Ys+nqb(l4U%r|-!@!GjUG}x&u9q*yyRRQtrFQw-&4x-C8ICGpzs^R z0wtJPrwirK*EP4{6L-86i-x9WJCi~mM`>N>Yl&pYb2ye_uPLzxm{-GR$r0_5rK^!V z5vVFn69r`ZYUoSygxHT^jijmkt|;E?@-YzuFl69V%YdGo<_tw8pyx1p0|O})wlA&& zi{?UP!H<`|$)|+CT0ZONuZOj$)(Tx2J^KXtr63;-bhICRsrVlpRm<@$`X4af#b|cg zk3D45WHg!qU{z;?SqITj0BkITBTwB3eX3LY#_p^&dJn5e1Ff?s9sTarH`a^X57e!! zVLey!+~djC4h9zW|2;V4X>yZK4+ZrtE1B)I3VVSm=+NB`Lu_ULFyuGsgi zE%MDsLTFS>Lz8<*s8qYDu}b#3{Mg_I;*f!b%z&JAD32QF_2RvAH$ve=Z zye}e9gvXjVz*^L+j5kSnMRk<-GUM&5VK@&zwFOxH7X;%#4B)}52wjm;KJZ94u2B_v ztT=J~_hN6sgC@5@O$9=JdL58xacC;*-I-~PeVgPYdYfGTc=04SV~g_S zUx4(y>Cbv^g}FX0QI0d5&n8HW0N8M`z?EW`TjpcwOF{IWCnO<{PbUQNrMl_I%ny2M zE&J)UPLgP|0K6^8$*20VL9meFvcXsv$cditSOF!5Tv322V+x}tT{nu?{z#r^fQs3- z2!ITwv>(}m94^`&8pRVg<#XY2{zD?k2f0F^7-q`%f_&!iFc>m$s$@V<%yDNY0u?%0 zSatJ+kMd-wCC1BpRuAKX;m1LLG|(=ul&S3pN3{kBwqZE)-`XU>;>N>PLSq`w-Y+#8 zBw)Qsrbtbg`Ju5otHdgDen(m~OlNI6?iQ1Cqv3_!y`rn;z(vi$PuXKWGT2C-5$LTj zcd}%@gz8)xYY;e(pt7$3Fzzt`HMkvbDf5|jqSAMeYbx4dlm7SoXSnOOyk8$iN=XK-)ps7nNr-%uN>Pc`E&AYt(NJ`zy>hgrPxy@*V18R6{w-oie+f(Bb8G0 zwaGaRCTOGbpp-VCiKM31Zd^?{VuF0kh2T2`rT$PRob{NEKy?sTHUvH~@MC;JweY2IXS{Rnc*{+lL}J;=O8_)O40Te70~oE@`zG z0}olz^HpQ_IC9rHtUOZBaVEt7G z(u-2+B{r9|^Hsw&C{#S{#~k{r;fD;f5h ze2Qd%X34zcb@Cu*Rn zF3M^=6Xn4GFD5cZ?ck(TM9u(AjJ=_AdWnXL0#RzjxWpAAi$64nGJ9|d1M`Kilghle z)?;BlO5a8W_vp7D%HDGcQ13W$LT|y$rYL1SK}M5B0xo^Wm^_3zM$Z>i0xso&4~=3R zv#;T)wxpF~)d3`ZqkWFSKq2BVCXT(OK_)T)q*5g`-l(GN8;o-!atrXP-81sSXggQK zrN|oObu4e_Nwca;1(6nkm_K0fq8h5?SXCLZsM_ENTG>-cK8qzLU;sL2VBQ1{@|{y% zW!(1sO)xNloUT4q?!I@GnJQG;XVU{+?9Y!httjRf{>3U(I{?^>rR31qUN z5Cmd21Mk1_d43w@gy@l;=krJ>bJAyR3-#DiYLQEp;i7be#FY~hJ&yD-3rD8)A) z8lL>XVM%WkXuYeFE_?+~u$?7Ooe;j#{Mf+Lw@IyRfzV2?xR#+#5&0~pVK8K1$iN=W zK)iSc|8aprD=(ZLJI#Bkc^cV%c-ybimcmGhV+N-6p_4qBqH}Qu!Xpzyp;t!MVrWOZBehqSp}v4-%-sN3x)(`!H7RK z?C%9ap$_(%BSy{4V4L1DNbr?77_G?H*px32GI~Y68jG<%oqXWl>+;p4bU|j>>LO6^ z4Dv-nrW_9Wn=)Yq`y%JBym-OadHt<_EEh@HpjpS`&>1jQVFV zN;Ourll^FPZvh%7gAKv^p}g}cCknP_VU~z{+{z;689?v^Lmo&ahC#y3;&MMS$p=D( z^TBsgt5(MnjYN$PxEkJw5gf}#BK2`hVe~cJIJ}i@fom#(vsok$`xaF*p;f0E^V)Tl zTyC=TTnYHdQ+2?4I*SVxdD^igz`$Faq6AUlFW6Kr#Z>rNkuNyrloxOCzv92znP`7O zYmaLMR&ETYy>OlS6A4{}6{{LX)x7>ne9| zg*pDf&}Q(_rX9az^^Z%G1H6dGNgWIcwvG$j#a$eexf+a633|d^l!DjFL#B(<9dx(3 zK|IZ1%oo&>-oeCNEZ%-NNyO+@xO>H4s0&}+c>U@Sy|pH%@pO#*BsH{O@bPK?iZ-;y zc736hJT?l2jeO(y5Avse{u&b^N7(6+Q-;xyf!)jiW~P13?@L`ofKeNfO07p-g?DyYz=L?_e2O($TJg)K9aJjUM$iyBDfbAGKG~(G4r^N79 zle6>|xD-QNB@Uv*(*?%gqIUbx>Ks-j6f8HTr&3}E8U({@^(CmOCm?sK1DW)ZL@jAdOyZpj z>RH`9zUXX{K=JXO?=h!V;b0<&nzQe1#TSF>3wXROJ6K;R*Bc@)TBh!=!}N_ zIL|BcI01mpPlOgFNj_N%5APIt;1WktQR0$p;_>kh@@1}&p%QmW<}XZzGFCG~2Jn>2 zz`RF$r{!snXCTMyXE{J)AAUTG!ntH@ByI4t2CN>)GJDM{Tdvv5mC{z;^MfON`J7!V zDG7VZldBgTISRt8!=Ba6>V}oGAM0hjn~VCFKXogMB-1+|vYKX1c1Mvt3(`iut1z53 z4i~mOn3194V;4YoKdKQn9LVUg=@SuoRzU~jkkug zG8V4!)^JwF!ZqF+&dON0##_T#84K5VYd9-o;TmrZXJsrr4X>3O;2{G$mjPV;FjM)0 z<#98YS4;3U8ztE{ImXL3`MY9D|I?=tG_?EERlnz(RZo&b{CN)SDDiop$!+o0<2JnI znGN1P&C~c}bE98~*W41OrBSbI-2LrW*R3oPu?7-uE$q|;N@Eg+^mV{ADcU~XEZ5nf zo6S-LV|zeLy45<4X8ERpsS`b4kheZredH&u9237SJ)9~HTBik8GTuS}aSVs#_TJAk zMrRm7Z#0!|V7nsMBfAT>a zz7RNh*iV}b%CV@dypL6cZ2}K`?U8DxD+4&eFr)Dz z3=i#zhw}S!@w@el{a>x($Xvk-^%MDKy=>YV${;n(L)Yg{Sh>t)A7q^$iPF8&|w)Uo5ebr8nsG@YbFmXAE76}=a=0*t(1Yz(bTgV+Jl zBF|Vrwy%apFHd%msnOz8j0B^j!DDC*aFB1L$iO0Hz+RvLcR4$)Ofwcw z#kQ z+nQ|z&ysh#(9EIs@+SMt`)Vwud(j8Ka6?f}5!__}-=EvPxSJ_SK8u(5^+Q5re6tP4 zD+9HS!{Tc&a!Psj!0lSDt&=uc#G|Lvpwnr>=tJX=P&lRvj(uNZ%g8xqU-IYLnj^}w zZ;>y_!sh)af%nw8JgC4}EO08N$RywNt&Yj%>pbJIbIXeYdR?_Mmgr@b#U`bysE&ke zCXd19rhl1l^y<>vV86{!3%=oh3tt;L%C|8L_#p!elL4OO?Dfnw-j!qW85RM7^%U@@ zQT<9(*O6Y0$%0Ql`kyS)TytL5KG`fa_5KYi5yPd z#v=Zqp2bo24Ly#yEd2D?`YX)EDNQV#!-s9z5EyVij*_}d>r(5TXXAozlYnn)1AFd_$lnFD*{H|f;1R#9IBYE zMhJw1j90A{Xbz*<2-JiHM}a`*aFSSyw#7tu$>VN&z88Gl?Ho;QAG4T^Gn?b~9-Myu zPm(9+B))cAZ4__cQ>neS^hgid^z?(L2h>KZ;*l)rj=f2+fl%D^xU7}h4S8yio27Um=Q3rWFgT)d=(9D8>aB_D)BTePRL zcnAGE?}YR&2`HU4fn(~4!VFkJt1#%@sm{7Fi_Jn_PgdYUNE)rYDOEVToW-`clNbcx@xT?Q-i z)3mwmPh)Jsd1D^8B#$^g3)uF8j|-T#4ld1h0fQ7yb6fxujIqZ8%Hi4Ucs#(I(#B8{X2&6Q z!3H0ELUz&U;--AA^;k^zr%Rt>E{-O5iwTN?1^=~hVK^GwuD#(@W`b)@z)OvX?qWb> zt!FsmvgIf`P7LU6q5(Nv{Jptn)e2ST#*0iAKmEat#0$Q)7|<77wL^= ztD;hVTKmS88d$2Y@hl`xj>)RzaS3@m{dBh>d0e=*2tIPY%$l5qmY-(!CSG5XOd|w8YQ+ICE;+{GHA2T`zL^1%| zlUZ>j1Q}>ldBL*>KsNNiD-F!jEX;A)kCO+lZPdpev{K&UC@Vbbp2*>3VKJ__K#O?^ z@-Pt_7(-LgjEQGok}^GH8rxF?`*F;c$0lW*jL@P}L0-r5CRZ56$QxGxBCAyy%|<{H z3r;^Kwv;74uvv{1RUG+tOCI^zT3uCNwk~;y+j#u_-PY}HFI!gR3IXS^s(j;(Zgj)- zZuIWIbfc?Y=k~q&b#C>m9^{Vx!hdxquXv!}1D;2i!e)S$Zg2!sMuicBlcC_33Ea4d zQb^bcPR^fjV3l0wc{Fn+C-N8^rXZWW9vpivO~+(-P>X@)G8#*6mg8c zhO_i-f@6#}z~D=yZIkV;YSkT_+wwxk-5#ZX*viu9F7w_Ba|F5s&v22V*CEN=lLL~r zfDfZx?C4PG0`NG|nsgRjr9_(C2+qr=K5$!gP^7GaM30wk<+zPTQ{(vCWGCRMk|va0 zki@emp6W6@kwJ;oRpkr(<7iZw_t1<6Noz%s6W*q`rV42)W<)HDY1LM$3@+6@E16S{ zqvK=cBUc0(8h1$E*C#M0G2C*!JNBd}yY+isDl6e9WZ$_IF>-8{w^bn3>*vd=s?5LfT(2vxt4s!MmQXUh;IkT3CZSn-=%))@!Vmu z5ZSq>v=zwh(<{p{JxndojaReP;=^^z8F%rqw7Dzeke8WlZ>PNEF>A0jxseOf zEJD)~n$_t!bY3*nlGY$hMCA58yU!9S6IFC#u#waSN{(bdTxD{xL{9jwX=vkX!$%7~ zGjV7DoK|{qDp`O>WA8`1|1SQh&(Ra_pmkHOI=!`C3v)QB$3IMh&`Ch@Rg)0?wO6nt z^cKb}ij0#syQ+!~HVDxWIDApA5JrJUS@wc9U|{n6NXXb0bhfdz;Y*zrKA_S~MRE}q z$sT(G9rTtgI&PD%GEa+ya(F!^|5cSX(kjDPq+g*a0+XT6bE{fly+nA9$^P=CTYteFc+QjE(nn#fSOdD>}i<8BwYP1!P|KOhCK<`$0NRknbGFKFRMt9WY5 z%D;Tdjjp{`3cQp0qt4B9?&Q|*dY%-YD_*zWWJoC>wwL8crCNN$k2{OWTW)eIpZa7d z7KL=@bLIYHRXxxGeQN*3H*vMo{1HYiW~KOD`OK%?=$bFYAJcB!{w&SU)}cebaE745Z*sFUy6HxdM{bYkaoRd?z-^p+S9y}nos?M%U*YwRNhk2qkt1&Tx@(ladGy_)|SmTBx9&7km9Bcdt7`a`THm@%`DNkEGDN_ zy)Gf=W!^Q&64_TYQ|W;d@&ThCQWZx#=}IR74wS!GK(EC^k;m0ll7`FxMzgTT^z}~7 zr6oJ=%hvYm(b*Kt*vKp%WaijyK1^(rPDc#M;reGr+qWp4C_KX>l>VC61SGd=3t-HFG4hdcg=N6SLGoYyG+y&!uXkz%vE8N;we}g;zZBKCPQv5H;&BWIJgKqT` z|LhJw>=6L9KuW*PJ>UY_-f*UK7hmC)Kk#mM`kSwk{p>f%R);&dn_ux(w;@Hn-Yj4x zep>%S&c@CkOihUi#j3{e%9p#9xBQiJ@A%sk!+q}LHXiy|cjDWh=uUi@Y!kvKrQRI* z{Obz89D{>@CHwtfbCnyt{guvrE{ShF_F-=Q%5QMTzUf=ENXE7#6tcMBW3_wr;~#T} zANfeRqjK)Z+utWQC^x&)UhsT3`rSWDN-jUwZC?IRckFwA(4Bn16>?nV_=xOEr#!wz z@_4@FPYQ6!eW$OWpE2|BrL;|L0`mGI`q1gRgYQpY#-W;>#cE54oOt`sR=I zggk9Zo{lq58zP7J+I)PI9P2eX)(7NR|5-TJ;b}j~sR3%wKs+e}i%#YSd&va`a^?j( zur=-)+VENtgNB``&hiM_+vn#zi5{zRbG||2WNoy|K1np>$x`#n(xAeQ3A3L;UPZ0E z!0IV&CAX7yT%&6E#IYj~zF{og$j9O*W5aqyPp!AYT))VK$8@rA5T7xqfx(goRxs|^ z5RQr`Re#}Om5_PoFA9m%OL;5{H!o1rWpSQ6<;6qbNVv>&W5C-2OQ^}0`Y`sM)VQs- zMTE;};3MPkQF*T|JT4AqgFSgG^1+OHAg~T)g8LKM%O|M>Q6u}@l8ktwppVVM5vjmp z$p_zXqN7>ofXiYEEP~S&PkExr+Ld4DmVf&XWq8dmNNDjYhOy0K7XRZ9AKQ;V z0z*;0@BQy}hkxMTIrkA+*~RwfCqCY7-chy#$qMi2y>F4c{((CrujSwW9e4Ec->&mi zZ%V|sC0VIH?M1)tR(|Qf_`H186>j5k4|K@;=(C@8ORs*tTYdf8-O3wZ>yG^Luj?Z& zwKx}UpH}ft7K`1NKG-e)^FO)6fBgGy>2+^%n@@j=u0D@G|8H*T-GA;zAN^Z*_z{wy zH@``KMPPR98@jjrsXO$Cf9giBc$qsUeEzYE;vbLo``;JxBrE*L(-S05*ftgN zG_rY;n?nScAM2Wr@`;R=vU zV?+So>A&q$bj=vRSJF9VUq}H>^$G~W|Mz&A?|Oood0%QuRy~x{!zi3ci8uL?aEmC< z^q8pNrfF6G!7haLi|+2+gYMzn2V{$aY=KZ-6jYLrLx27%Uz9)a9`3~d_bhipiY{zj zK*C4Yf6*O$)hpfVpZ>Pn_v{~c8|UB69se3B-Ztyfaa*k?F1gmk$07WCVt{i^W zlf~!5ZtLqWcgKF{Id1L#@-!Tw!Hwn5ebycNz>9uOwi&(M9sbYHb+^3W*Y%Nx z`d3up3XY##|F`6akq4jm{chtczu6uAzwejhZ~>@7TX*~a<|*#LuRhN$U2(BH`1`-* z)-SnK3u^o@*ynNG<$3%`aa^izoKSL;gDoY?|B7Sv-TLFpjc< zg(iGXLds)v&f17t*;jZb971aeK6u;2vajgvfU?pSZWo2K;Pl)!58dP|nkU3xk%;z{ z7UW@{st8A)h=2e?089p~m@imUkHY!w=bm*Ci$HYV;%9?VnfB7;l9b#EInD1j`g6J$ zhl7&r@MnXrRu~r!6%Q8_d6+6a#$QHAB@ULzo%nrOpT>UvQpWa4-t+iz;xEUgEa*wn zkM_=c%72Q15!Rxx{5AR*oQekdaN&R@)DCF0iXM% zlV`X~Lyx`CGyBiUrP?qjm_HzV37+}?VU4C49 zL~d9%?{&FqIq-+StIrGHlK56-g?ryG|A=#!+|I2%>FMss)4$Jc+(o`NK%pr)*gWG* zcl2>mX#cq6|Fk>0(f@jt+yC~r_}tSK^zvYurJLkt>XI*WM}F$(-1=qmZ3c9}2k`}~ z*C)T{hn>6nt-i=p)AKUL4zdymwehEEMd ziw*GM#pn<!Ky_fcLZ)TBCy7vN9#*eYhUKsoHr zR>IC{{Vawlx&RfH*}4&7%g}C9DN#m75ngQh=w=8ldK(E5lLe0-_HD_Ya(B9{f=e5H z;q$u11^L|fp}%wPAFg*>_q);^`}%LxZ4J7W0vAbnEnyppY)Lu(4c{zVQOxw;o$f$Q(*p4DwVeli!wR_*kt$oXr)vwie zyv0AtQGNu9?G^f*ZKcQ?{1F`RO4FFP#QzV@?0>xLEAxlYS0o1c@#Bj4T-&V5?y+gl{hdUGT^>Z|a@NiKG69%T~Xc%QuDLGI?4yxiUVEB`}R z<3~qs)?=hU$`m_@Y5k&$+}35XO-9;C_PQ_O2b(evG*9o8JV~r6PvQe?!$)Z`PtRbU z?jy%q@+5xxV+~$ql{W$CH~OK?m;qi7m{^PnH&x2a2vo}Pk_(x3xh4tnNk`Mtcy)=N zp9tPI{2S{>5^RO0)4&)QJN+rtlU$(34y}xZ#|jyul@ZtuW9oq{Lr@hNw^6#YAiSU_ zxLon6R>VbWEC%}Vuh+ud|CUR>ObK3RU#kdCeTUyEs(zxFSqo3{pdj-r8?;#9DFj}^ zEv$iqbI^17QF^D%`pE^praUiIO-kuWo4h|DxqK_jf$*4 z^kHt}_O}-nxw2ptQ;KVS;VBzZ9N~!^n-6@L@-BbslTx&;>o%C&s<(kBkbr0D^>3Cf z9Ot`}_qw<80w48C;R_$|WRkTn`BK$8`nWu4L{{^$hrQ8oyz~_gobags@0;EKc+m_h zV?L25@fA;zS$eCi;>#lUn*69TuHNgX2>cO2$rB3OHQCy6%X5C(-TFPUDlRMGD6UuD z_Z|&nO?~cTVcm<$w6FTb6y}Ti8brKVU;xoZMG! z+(mwgi)QT$jKUUAWZ63Jo}MfPuf~G7z#9!J*$;^W84bq+#~m*mr$e%*|Ik5Qv4$@? zg)k?uf{lh7HaxL~d3u}4lRj!wc+}5e0C|!h!JfRFd3vVXlHWdP0j@r(*7Qc%8ZB`R zkja1^^EgJ{fx(L~h&B)5dS8W+2ZB)z~E`W9W zRr$%v3f)0Hu`|lU7k!PBr^B>`-hSv>{I0?>-}w0y{gA+d|CLXK$Tck>SJr--T+{r* z120y^tk!E+?Ev&+er7G0FSEO4B>|6{eRQ#n&*H6K%GLU=d^K6%=9O>LyHj}UKj3-t zKgMTkMhi?SG>kct8`cdEwfm2f1}DjRzX#=+3k}4E8DeRTU+(JhG!J+44Jx{cN94&i zh>;YOOR}=(#*=t23vbss6LWmP=e=WLyUxD>~_;-ilFVp+c- z5Yom)_s|;>eP+1Y6l5m9q&G!Q>Qwjnk7^F$O#t}B2Y2r2S_Er;WoYH~W=j456SlQz zgD;H_IZsOOP;8vQk74Clpm<)Exqwx0#IMI(9#uIya*JD%-zF?w`}w2-ki?^Tk}dnl z(+2W{m2}3LbLW16$GA|i4#O!kU{6DzSSsk6WGwf|+!Zmu@aNA6YSD9D4_Ly~q1=-y zPsJrl`5p|6qQYEN+9%T6_Zqjs{!}Xc5n*&HDsMc+FwWW}-Ar^Qw7Uaal=~SNjy-&p z1su7e_2yu2g*krrqKimKxL$A@heD201=m|BvncZZFrD!xP0_%1@wenFWiTcWdc10l zc=D5n<5Dl_wzqaHKk=5g29u9HdCD>Ii_n+8*|_1K%^!I28qk(Gy5V}abhQ+~(zedLy%b@x6-8E7 zm*m;n?%eyj<*Rl$savVZ0=~sQB@g#0jbY7Rc?@0-0DAk)E)ejm$>CO%VRA+Ec;Sw?U8;fi<00EZURi6 z@SO8(3sRv&QzT+BDjtQh9s4yhoY4v=K;p z&=n>kS=bGT?!mUbwE3JHkK{&wAy>@t>ER38dO~rrIMXSyh+b=qxaiF~n2j7dD_t(jGw@9EB|`hkoOQ!BVh?W1ZzmZYqo`@XR)y z!^nj;^Kn~~0gkLeV(2Dk04ek7Oc15=H0L97`;_~4uZa|BA)XooDyp*dh&0R<(5g4; zFjsgZQIQf-oH65u< z2S757L+^30AI}X^D*^#Uj)jt^yKt&(bU5JSP%db?-}}ZJ-0EMx+PO>5cAF1+gj>7& z#j;1aEci>GcuI;pOYXhCn}Gyo5(6>U_|fGT~~vXt@~slYSbJW2EK4iV%F-{|$Cxk`kcVUR zRtLtE=~JFIYW87sHyeGM#Ro6zqcX8c3awUBw3j!fX__%mtQcqC%@1FkoMQ4e1)PQ` zc=*EH1dM_QkDZe(6bD}S8aH~^E1mnN>*Nm&Jywd@)BLsyDa<$IhnZ+A@*~R8O*iRZ z-^33W@xw|yF$BGivAqJ%{?^rF>0u)t7jry-!^*e(VHx+}OWe|%-=lxs5qp>MG!fOO zx@kl13Q#0tzdN>npeWDuiXAos>a)`4@}Ej8J?zWZr}%UA?xC0KwvLrgeo|MC{kd-( z!8Zs}kT1)=`~83YPW@v<*usJvla0&ntIC)E?gLVIOPn|!WrHpGhG6CIKctk+i!Swl z0TlZev-xgpmO3~NEGX1b6#L7R^x6UuLk0a9KgJzhf4v)hO1@E%8-ulP|1P&Ca{_-r z3VX-lw?F;@Uwf@v`h3{pqT-?~%hT2J_)6r-+m1H}uqmtL$kXV$YeSyiqk8oQEV!m~ z$~!AQ3X*u@hJLCHAnm>ZN+V)xJ-CqeZrn8u2FK$!r83%2VyGO_$XJ*Pga*1am7v=8 z9!w=q<{4e*`NOqL?}0DKy=os(f-yG6KrqAUV%~mkriYfeO)(v7a`YQSZ-x1?T%&Q2 zH0{}kRtQN5{2G8k?tb4)W|}8W+GKxukl3f#fyc zfM=DbP4=+0NR586ulN?z5_sZRvJzmOz5;U^6R+B$Y9k+U`BAA@Hw>q+yvc_?ry7aG z>T-B($rCY90O>QNrMNpJf2DE%fBgwr4ZWw^eA46H@kf7+7D-yTOYyk&pfA-_F2Cco zZl65fZ}X7+fRL-zIA)?>S4m|r`r$wN19#g0{0+DI(ZBb5qPYdd9t`0}agC?AY&=l5 zM#ygx_y6T{?J?*Hp|xzpq?j~;yaOSMS%MWqBPEV>dc^(=}* zJjF#DZ#?AL*R%UCTTS#UQm@kM4Y1f}fizLRFFX*xDN7A=+RGy|1^C&TZCQsN=pTLe)e+cQZ zg|IXnCy8g@2j1_NKKu^Z7X_ryaIEE<7n`U3%oCnr&f^4Kq66*VOXaVmYMy>p$9`qj z>(Xx=G9LvG9Xr*Szgg`qw0yCu+7hGBHh7u$DT1O1Wg!}IEe0!SD@N>SSixN4tjZEDd4oLFfZX%~)WG2x;*e-H0~WTOJf~=LLT}w`u%h^t zOGsZWe2UDk4r?dOC6ApA1T^C3x7LS-c5k#0v(9%mCqgP0gwX|1g9jYVk^toqi9v^E z6@$H$x4LWUW|gO2_BGwRiKAcaE55{31s)xHq~2Hxnk?wEq8#d?>Ievq5{ay`kPk12 zYKvkKIHlxc@(?USl;Y80dx*YdU$*qvVuB}X?0@Im-D%H%p4H@9f+S|Ll(b z=ugViMb47KS+*IVIFps?llQr=+jz#)oV(=)cksVG-|c_zyLGh{E57jFG6OaE6`KzB&lfCIL+~Jq}u5NR{ z_8AnTo49$90{+0;-r^4Z{EzAnM78*q8v}i=wdm7FE1rEP-FH3u=!e{j{B=qcw|MpC zf0|t(ry7UEwIz=NlpGxYwkJ6EFY@D~&wSDy{>>Mz`Xv(M4TK8`;6cMgB$Q*)?~!rap1xpha$ z^S$rl_FeTd{cERsj3h?k|V z6!hp1Tc!9v_}afPdBS5Q&+yOFhc5UCAMPsQ2OlAa`4aEq)#^6BWs_kBI}0tJuS0VF z)Lpjcb;)6AMUEmV0_mb%u#UH1Fze9htdW5$T`l;aVFF7bVTa!SSoxZZHJnvz;Kt`6 zXhA+5y*7n{2Z`jehvUR002X$wMR$w`F~3U0o9E(Mf?=H*#L`1=Ed>!>kGlDoF5FG| zJE}<9JpdmJg{zFAKzHylNa)NJ7^`hSQ6V(6Dz!`F~Fg1L>zYbH>%N!z;pIquMl|GWHA8axF9E1!aw9*^k5 zBkh)6|2ADA#Y)5GlkV$o{SPm5>-V@=3U_&op%7x4-XxE<`25l-uGs=^g;Qv z@IUxuxBqcZbQ|}(!t;IV6K?e#Z*og-c$3;a_L^7gYB+8HwBQm&c;@wzJhUXP#D^A|h!$xXRAc&a;w zBA889lW}9PexJ+T>Sraqdu+PHzxXpE|26vK)8q2oZ~bB7to~#B4E%(>+WZMI;1BPt z{LkNX`@io8+{%$Jx-&lZk8bU;k8_*nUm%YXyiuP6zx>8mIrq^oxQ!orhWusFN9#5a z91Cn2I{Ljo;7QmwDXE^Y|6A zLN5jJiEsKgckqv2;YMeE+@1cD&vqxj@!R}yk|+19{PjEB$_rlTPT<>;5B{ASz4g6f zL-wB27H;q!VDofcB~R~m%Wu3YrCh zzt=fV0^V6J!v1=}QPcoLEuhFS0cgl4`y~0gj`+eDnP>)e5?OQsg@n+PR-ku!2)Ee? zd)H*7|A7S{-z<``hrYMM9A5&)yD9jJF{MKluZ<@}GXil^3tgfA>x9_*0+ZPCWE0{gYeN3CXw=S4*#dyV}+tgtBmwb970z z)g1lzPm`O7v)uj{|B4&E;xF7jd1dXgv)tPApY4u5@w;T@_V!-4UK_+!Sy6|*)t~z* zxAF`BSsxg(@8w?p#792tSO1Tnn9YAqme%r(nsE}WE|)C!%O5+evEqJA@w|- z{Cmlx{PCj`k9v$7$+sf=f8d#NWAGuj|5dM7U3@$7`@bW98TN5*-=E2LBWd_cp2<%E zrMOOc`qeRcdS1vAe&>K#k!R^}tmVhYwPStqBeHc$o{zqSua0|bu!}^_+7?gVXA<$0 zaV1LkXsLs;98!PnZm-(9+D+o(KZ=n zl}zUDpQDR?@N9R}q>B;S3V9C~sy6F5`uW8~IC1DijjrTTjf}O}gnHssDHsEyK{Hlw zOY)Kr6fbM{ywo{){2*2s<6*HWkso_*9+0gPXWY(hoOix26j3~vMLA@UKUsm-;#~d+ z&Mm+60=N2IPjf4u__%D@xLTIlH{9mkE|e#>oa@#uyG#maDUNidSp;!>L@&0r9G5K$ z?&O*jp%+Ub=~p$iFvM!DtgIgW*6+}wYo8QYBiTl=^u=r4)*bHXHt%wttPo%5*6wjn z;S)c!D8+G?`Ga^NFI!@c{P2&t)yF(mw&+|V-v)d^R*z+S#F@7f`?4xMtN%J7#gdrI z=A`B_#0MTJI1%cxZ}=v6QvO2es@xDPf9|vLhmEe$RqxI7?&j7mxl~`+N9}*S8a_sN z<8!wQ+%3<2zFU35QzWfBa|N==*PVYZqLo`By)Y zAi06TjaX}*WWPS1z+f8CBpLK>g1CfeJu6Nm zZ@rV_tI6rT?yWG_zn}+iHPLxSbsGy%0i9E)ce)P=O{83;by8V{VFJNNpZo+_wg~G5 zpCe6x3`~Rs*@Km^H{i#FsVeJ1q!0<^nE37JYxZe}H2A|HEKI)b=xq*Ylm1wHO&l-k zlp?p)s5n9Rxl&Th*npY>I~MphBaZ@03b*wOq>#J#9%(u-x$Naat|>yHBZZw7PQt32d* z9K4|Lpg5Nshm#L_u%8C-L9fwr(}t}im^X4<)-JoZ6yo%wLBpLJ>YVqg`D_fEjZldA%8f|daN~?;jL2! zG^>Ua6Rz1T7vaPS2w1!lwyDKp$Q!hU&T>RpXi6@4yd^pA!kDZkD9X*B2 zEHBMJ3b?Qz+ZXrH6O4jQiaGpfQ(KS=R4)&lG<{~ZDuj@~X~&}zRT)?UA9&e3yAZKz z4ZqO9CpD%vD+731|Gko+tl6o3^5B|YIrHH%X#G zZ=AG1mO&v4v-+)p6u=|!KpXM~xmtr;nol7@m%O5V@IwygARd3IW4wn%27X1$)*xk| zKX^kzNpfYovnWjX<> zxD#icX|Ktb{$k9Q%?cv5A5S4GxN6PXuyBocJkEP>s{^aA-OKS>J(b>j8{;I%X?ar3 zf=@8dVSTf_6_`dlh|m&QlP&s13VUlW;8292VF-{#Sxp3dyU^@*>}Wo?%}9L!{W;xR zVJ=UabxWeH-8BgT7d^?ooz5}P9R_qu4KCy z7=>aW@lx7PH_vq{%lqPNd*YWf6Gf*UC>%bqVv07+beVrcPd-)h&!Q5faFtp@fofW- zys_b@7Orx*isVgk)B0~zwAxo5u?B-0zrW7v=brIxti0ii@vFX+gQ|Qcmz+(!0Vle2 zC0mD}dUqAzDEjX`z_8@fn-0a}P4uY!h=?~N2GayEoo?YL13^!nl#m>ipIP6t^n7H-$VSUk6_ ztulEGx`x}8PgUmj^E|YBt$aA&)G2X}&X)Z+6+1-J9UsP)x)^fATc0giaFK@q8?O1l zbj6)dk4nQWdw1c-zN#JZ$IoOB<8vO^E@sNEyI<dD5@+^?s82VEq1Dwyi*kMe#86lU`$U*5OL-|C>wY1@LuI?j#-guAbS9kd)CnIQ5N(`)l zn-Ul!L%|E}#Z<{JDK0cNfur?y`KPP)#3~D_CzX09d6?R zu|hK_&ZjmtU{#vFg-8S$7IUL^j>}l9^3*9kUD-bQa0!S3`apQ`oNOQRBBM7+_gv5UMBFt&`GAxBvN5g!5I(twcf@otmQCZ9}D7xg5Hhax@#aV@t4 zvd}3b)zf<~E$<{m1R|^;4Lt)Ztb=?dDOu9Ypu#%juaXp#vJ9-S?)Cg-@ePALo&h^S z0XMF|mH7Nszs_l~&}a2i74eprw8=fC*h3;&vDnGFwui&`fCVpg4!wbMr)>baODdbA zYNMRZ*pHb3NBM%LTr$@!S1R#&w~KS{p?~K>Yw0J(OY#J-K3lJax#rm(mk^0ht`Hx+Wd)G-UwUprErH3yK0HAzZ=`kyZ>n5_l4%FJUF&O)5#UCBUnQGFDfB zlf1DX6-^^g`yyPG_sAu!cmd0Lwm7C(IOzWegTCW?@*gt5>YKuT=!HENWJ2i0FCBVZ zBn37tM3m(HSC7mg#W8}6mlh#uAnV}_a*AvmV2`c65As9)hWR6B#ZG$Slx+5g?A2~a#7GH*ViC@J}$A} z!kkIf9Al6UiY8f5ep}0Sq_DeAy?MkN{jG^c*U7|qIEGMU^13j@%d&tCP4MI<8GQx^ zd7TG=Sv!4`5;HJsCvRF%5{GfH6X%gP8x7AVKaKt1yhN-QuIN8J#OdpuP5uie^B)2l zO(E~`O2Lhaz7TZC1pr~m;}>of(-K`yc25Ti&0Fy>-+@c`05N%iiSm@O{UAT&Zlx6C^OzvgnM5XF#$A6C)#|2HQy=wC)6>5HRDmQh(`c#l&g-H2)aFj{ zIhJ|QCIhB%v`opg%tkT5IFu-Sq1Mp8lL&POerRKFJ@B~t)6Fg4ba20gx%jA)O5f

t_w5hzHp~JbRg0;dCVn9i&Rw}`9m_mSIM8?h|lsT z`m|zKNiVOUeS=%3ap<`R`5}LtH|))10mumx&>Rl)2ML+}{0V6sx>F(pI1BjVnb95? zICCz_LIxC4(SwH8l=Ar5-l>_|y_S45i{|+>;FB>lra*^bfDb+We3U*i27Zh)nSf{4 zLbGa&bxMW4Mc8uDzF|d;X7yY4NpN2az;LJZ!MG??j{00Lm%m{)DZuV@aZJj5f0vV_ z;P$58`B)r-Nw}ScHt~z!nA9%TC;P0$9@)Y~;}8}s9LK_kf;Nr?h!a5oD`VlvPkHb{ zP?*qBiIuT%zw+Lwx9>}EU#Lletr4h4CKm6Bz6(j?m$%%1A51O}36IbHuh|DjJ%4If z-n3VB9F!8OV(c4ZPQj_xu`-r8$V)bGw-nPB0h^{)c*x&vJAbVcV<7CY3`}?4=fRDW zC5?S9IqN8W3h|@Xep1b<)n5jod)9_1RX$S^2HKfCkcTLY33qhBvJ`V5GE8CmY1;32N_VWrjPQGDad0C zUUd2$OBLLeH9$f}dxXRLllUX=&k;G(<`nT$-{#2W1z z9QRy(eugM)0T&%lSVSvToN2~Okin|^k|i*l1xz;Xa@$oq+wj;SL#%6M(b2^ z$_zh(ix{h0Qkb)3RGwXU!|j%FOUe>EN)CChkkf>o8R~7t9M3UNKEwGU02yN2f@1sg zf2P1unB#6N`P)>}>S9UYSbB{OaVij}epF6+AV<7Sm8DTN8R@U(QJO(07{=QxZ;ir| zA9u5FWDx-GB&a0>I^KVyqahEa&HO7eibbGlzaksCo4_Dn=6}c^KlXIW;jKAsB4(IB z9p+C=-aSZ(_k^!8vn3QaUo98k)(1&TPC~|Va zj~Xn0-*DYNtXRtIR;gvCSVBTjeznWf0<#x<#SkP47UbC@nnIGUK}76p5v;Hdrcl%; zD;9BmQkW87LwcWxb4%EE(_ibY%*Rt4u_egn*1CxRGzejc!B~bJhBr2_#?KbLBSQO$ zH*5ZQWilFD8K6l@MykkX$~YM0%k#(=~5v-i)RF^6~iNZHk91~Y?v zd(z5_7jiW@U*ssI%W=Ei`6zY!-F)w2nfg6%KI$ZIu66DCEV6~6MU`ePZGscIXg-=C z>fNO+DZVzd8f~KVjxlJuv^Y%h`Tg+yxzJl-jxu+7Ro=wmYC$V8XwFkC{Y5c_Cfxnt zj_1UQ6aK4`croCNNXqaHMw`10JP`8IuaT@OzzybLqYa;2@K75c@z!1`OZzpRj`EF| z)P^YB3-a)lewMi*zLp#E3auW$(R(V(BM;0IbHct2PdfWHzBWAN*thYu;VH+yjjs() zIreRQZFtJDZ{usjQ;vNbUmKos?A!R-@RVcU#@B|Y9Q!uDHaz9ncjY60Z~eFvyNi)Q zzDt%o4|3H#UzifpNp$z5%2~LU>?f!yHwQ92vurvIbVsDd%NGH`3S`kN39!+r^a*cN zGv0o?63FA>94Dnl)79x}LD??fC#8`JDGK3V+TG%#U0OmZ&B*8Xptr(YU5Qbs>s9ai zbG@p*6A}me;SCuEvA65WCqf>6Pt7z~gL?#=aS1KA++-42&YD zq78Y2Fd*{~%?M4O(_(p)Xb=XRR$kq;^3m`Vz&LrKDR}}tA%8Nfx=zPd?)iX)WGM>x62;)(S>uV={Vcue6r_gaL3;e>uvl=cFBI7O(6zS-6Z-qIwvP8csd(7bv zLeZegm_XdYhQfS(ea)>&ah`5{DyINY6|}kzFy2A!a%^*_5&pNuTv490%1GsN ztVdRa8>Xh0-|cjlk)}yrkGI6`w)}4Augu3V9x|{y8JPD>805PhH=8G1{p^bx8dpUU zVoxpP^Ynbs&GE#%0oG87=@EQ@g^k+H=BIYv^!Ap z-`mgQ4?YEC$qQqJxz2` zp>B*u?u684n|PYYRMK56OvmHns3nEC5FLJC*;_T;rz$*=MT&EOcR{C95)?Dd5lW)l z0^v^V)?07Yedb};P{R(~&0Jbv zPRh608t_8~h79b%48#kH0Cp9$V<<_)%SUw`<c zZfx}v_EDgU@Rh`L8gp*y1QxIz=bZJC@`toGWMjGoP&gnWzOHjtEbK!yUJYMbl!6bB zNj!S=NRZkl=UVL7(00|{!o0W9;5NHPShLW?^;qLe@+F75%G~Co(3Q0NxppJDD?57h zsQw6aG=f(Zr+})&qA21K6X9ll7Y9=bZ=!QnCc;w$8)d{&gy)r?==Jt6ME|EyZ)Ggp zRfUzY@J`8hwKMPy85lCKI~l+Su6_W?1%fL?g(vokYhh&*d0%XJd0b{n9y_)$ck> zzye;DVByHv04PZx8w|UEfaln0&bo%ab&F^l9fkvZSu=|BUg|p?4m=!c!7ezCt%_OT znkzLux7>8<qD!Z}8dQxd2El`))*;Mt$yN=~I{d`Bd>+?l0jUeMfguAsmVq%>mUtboJO$iX`ZSIk`3Pzu>3Q_$i$OjsIH)2D*{gpJH)DZa=#y%tJN-`lKHI@zyL$u(O)mqu|+>y+eR`cbIjJi(ANC$&_| zF!K-EY~ki|v^>(4@*_9jDD{~BWt{m9_Nak3DqdwCN&MESCwo1vn2*9&B3K0R4x`RP3#B!&zO8JI5vIP2xwsFz23jRcN(&l33*Pg;=CN{ll8vsR0S zz~(=j{b#ht2UrZNh6(b~KQMQ&uQ{>)dxXCGVEMTaP>pUpeLS_HY+DeT3aN55f5pv) z$4zf`-ndkr#zW5`T;x1J)M}l*y&%gcIV_`1IMF8@$aoFVVa*nI!kx=}xq3f=@*3ZNmR}mK`tc|fIvT(S0 z9WK5P9jDGuRc0W}fS%>4eaHGD;9{7$k;flMg+MXfarw;PFc>m0WMFqQ5H6Gcg9TRy zLGVJN7mF~)brg7I1i@TMIK&6BqGd@;KCJx8r}QGXC2#)o%QM?`Z>n4xBdUW*&3&rzS(!F?-ng_DqjESQ3e^{RWBr0- zPdn%>Y(*(Y+`K*Ps7@#%Ajlh}C_f?l%l#kgnb*oT$ZPJ(ETJ5izfy{mtjEf{x57L= zEs=gV@*)Hr1bJ~UR)b}i5ZKak(@i(IYp=aF{onw;vWe@M%$T{_dyv$<(Zm07&Bj~f zE95!!VmPQ&z`jsIfq>?4R{28v_7CZB)qg>@gdH7QF!|`$01O!zGO+s@(CbIIh~QeG z*9&>euB$?it6yY7FL>~Q7Cxx_=TwsS1vpkm@#!nQ6zC|+d}-$8w?H%UN(=U_JnTW< zJ6X5i?mxuDo;Jv$dHWIez}WX-6_UHsjOWq5&ygR}KN88$@wENa@)QWYcJhY9i=5#Q zG>*UES(^p1=%$}faX2>s{pyDRBUvrSO8G6ig#{()ZXMOOu!2951l^LYxucIEQw#gJ zM8qUHUBZd1Cv8iR5=Z#btc78B&?wGtxZ!&Dg)dyA4lK!&TKEgxka~Zu*&X+`&uTHh zrZwcRu{fx+WocgdY5nb+zwsF}rGed+pVHldeaOI&f$hwIUPo~))lYxv1FSvZ@RAOY zuVFC2r)v6S)$&_Z;2UA(i`2js#U*iD=UU*Mp&tZ+` zm9)N#_Dl+lQC+K4|Af*evrS8&mpr6(?fa6a9_{4~HxmB%%h3+=1pXtpe1SKUGvluQ z*Z5Fd;cv*GU3bmr-Ay-qG3+a^aqlh;=YTfxSA<##4aND5H{Rf`zWQo+^5jXkyo~Pw#9uie(dP&(Vgwz0D*X?~q=TeqiShv` zV(>owvy1bL=8f*<2Sf~u(e4D5BEpPQj7-y^uIA&o0 zzvIaqK9k53NI_ENDVD918F{F)W-yFoAyA%vC@nJ7?L&ue*8hNlEMKe$jsLV0xa(cu zM@Ltvg5$d2&S1R{{=Mp#Ac^nlQ5n~?@-(P=Q&X%iwQX# z(Jc=-=f)cvc-Cc{r8VvL*SpJO+y))h67D-K6clp-f_x8^q^x{ty`zCiX_N>ev(@%v z#VZ}y47^d)o?jZlcrf1b{$dNRK0mcBn%KvA#C#Z|)`cHX@Xs}SvLvHhhUoPf=KX-wGNL?I)* zeY-+XZPEjLLtQ+RJ+~Qe02PHJ5!8q2R$(1gFHr*v`)edn!Il=Gb$Za~NW?%8Gs$3B z#KKk?3kTP}T%s9qub+o<%W`V+1l*@>u_MKRL~pj}dr0oZq%vJL3$&2gd9BreQU)5H zQ4iAMr0b0cPK^ZocQMV3qV{e>{SKMW)XQ$qE)Rhd5?&CI6Cu9Lrq}LZSj@OG%4K{K z%@%#%s=D893dY+s?&$);q}xK+Lb7>WkwqP%hn&VZ=l8ZSlrxD1od+w*D|gEFHcssF zi_z*Tbx7Pe2;?yCe~rkLqiedQW{NUXak_0fXg2XM@(7SE;>(;$H?R^K+iU5>r$Xfi zHy^ptne{tG6-USP>?m$=ig-?ve8a7|((y|EWPGtHD!>HsMww(KC=ziUFFln?b$3sw ztkDl#U2-4bf;eAO$+VjGJYJCR5iTdkiHD47>{+>i%BEvw=s5g&sp8Gy=+c~}nb<2_ z?u{hOtT{{A6wlyazO)i>Kb~vi9M}${vS$|*CY!o_S!>tMDa94IzjXWf#6081jsFu3ep(;oxf+w5#d@7AnEl*KeW@ z#xL+>HRyDRG8)2Gc3Y*fas(y>H<4eN;GzXc^oFY`an8OTbs6aL?MJVA^b$7RQ@L*P z_<)))d}dPWu&rIa!w$jmnMRg><`a<%e|*szbb=e=b$xplB08t=ARO%UefErnygi3yW+Hw+v%fzQj9DFQatvZWiaPQYzr2t#qBXO^PI!9_x z&iv%$qzBysvT*>#ab5Qpa_z`FEpolmuu!dvwDZ0`1v|c-@zb_sOm~nqEvXN9C~@49 ze*nT(-Vv30fP#blVI@DsQD7*=~EcSuRi|)SdwN3H{bu=&k;3k|Z%K z*O1SYepo6OBaNZRzcCNbY*xh!K9 z4>TyNa%=(nRTGlAEYG)>_^;d^4SI0^F!v8q8Dn~!QerQ>dat%v7CFAbg~SSRuP>lJ znli&@H-ic-m=wHw z?BqJrs+&_GWqQqN*2Tpzvt44}0y3IDYAiJ^8~Du6SvW;AB6bb2i8BJN=3sQf^Smhp z`TN-)OPx((MRt#AbW z7ic)X@AdchdJtO6!zIZRGr9Ul^ZM|^PJb27*phw_xpOu0LQ2Ve709>9erH!+Lja30oW9{IaeVp_>WL>y6nfL`TQWbmjWvdsQ$qYdR~;& z0>6y!Zy6{}q230!)~oDpjITQN?72RHsq8SS;Y*ttFj4SSUxv7^{_&~bF}W?-U#pE4 zMXJJhA-y!o${%j^Rc+Bjlk!u3;Xnhx8*8R*zgx83mhhr%Xh*Bd74dlluvNfUw08Rt z#9xFwjVeKvd zYZkx%j>QeBQeErzMFB6!PxtQJIp(cpj2C2#9LC3o&LY{3iaxiqwlx%w(f(ZP|NY|M zi$3UQCVV(2`Ccxl>cjbD!fJ$7&0+yl)o^8qMTcG=sU$-b%vRp-!{MK9`~MLa5m##v zks~GkV^-Oc9@jj!Q;a)f&T>wmz;<&Lf3HRMoC-R={r^YO&)kq-nPNI(ICvOJO&g?h zD!5%1Oru-EmJK^TL>DLQ(R+MqB69@$ACvfBagRKp@md4}q)G$c=?ZggBP-nOeYA^_ z>pc~GjuEUFv08(#mW|_{m^Z~}DW_YNIIblQy#JSV4R(GqUsv;xuUlF1f8aDUk_@T; z1_c$ks`I;XJlTG-#_p!>S&}RMazEPb_7%RE-B4;_7?j%+Gzp+Le2~b4fnVlSnE2nz za8!g?O%*T^^bt9At4#~N=19_uL6;H9?PEl_${?%yo9nK8YeZk~Md2$$Oqh^43)x-r z)zR)YjkR?Bb!X>mJBxvV!O!`FgKs@H?&f_Ju%9^ah78E>WyuvJ<%c&fM{tYk3Vo2aDO6G&``~R+q%)#{@wrAOi~781S^(_> z^jp$e-z@}4r~E1{StTqls8}Gqcm_CSIu$E4)Z6LYYTjY5E;5)9H;eAgf-~8WS`-FgN z)HSTt#2ZZr6^cYS%?r;t7e_9&>@u&v`>AmQT3x#(t>0VPdn&t61J`ejRt(W67tNkHv!!nlpTb9Xoe*d)viyfi0*z;h8 zE=Sp~T}Bui8YjF@oRFULtFldtT5aE}bp*THJ+j<2_DX`GVMeI?+6R48zQGr_a>`~s zIT#nX)pOl1O6kw%U(xG)V>8kQR!R8&q8b0AJswPUBO9upDq45qUxIZIIOoZ@HOjQN zLwRFijin#O9>NP7FfZSgjxK%kT4gmJ%7pn!eET>T;H`{p;Fia?o9U#+`uqdE%rG+W z$A5dc-_e(Y`Yo z7yf0vzdtwpKIS2UB%-)h7#9ifa!zIH0l7r)pZ}B}$rK?0Ebj}fZu<&z{9pTbNgUet zV{ow2tU*Z^R?U4g7H4^tDZZe!a->Rg$H9+|?&Xi)wido^o)Wb_-Vj0fU+=dzu&nV> z!M4i+6;GDW$S3|=$!|28JhYOh@ijqxD&sHLevcN+#9!^p{dNcfIaO94&FS3?mw(Ww z$4~z*_BoG^*1dJX)kncg1bm3Rq4hq_B18uhy6P?DxgB>s(SOag|6gzMw{!D?K4IX* zv1S#__u#)BSMt7R45lx0WW_(YTn4Wwj5HD0O|_oYHEt)R-|4^I6YtKWQ*_ z7dW~*06b=#U18R}O#gCH3T*zp>@UQgr{L5B4q4$HS??FX7}!3?L87J#$C|{_D5I6y zCe%{My(XA1)?T~vKc2i$KaSt52`$ZPyHe}ME5xXx z^Pi$rNGH5MRQQ?oneP<(Bu7hGQB|U78wnzOD=EkB}7#8$JTByly zLBm`oF-6u~q6l4Kdkcb1S;WCXEk`PlirQv%)SE6@Q9CQBB}(=47Q8 znR`F|%;r1r|49D(YgD0ryG9%qK&PFk6SRY#e)@2HTzB(BH!FlMnh)tQx@6@9%MEo5 zT)d1mNzO+LaBT*htD1&7Dpm@xeW+CAv?#{$8;yj=aeKe`cnkNJfw%x5@#%*P=$qTy zkz_{QLPR5#{_~4o#)c#dSR9BDKT@N=e%4V)u*s>(rv$z3s?+P;wht|Fl;6pz$Q#Vh z&leAwa0Y{)f96l#VK|A|?!o@)a-;_H^b|RO`F_-{?OJ1Fjre)Px4ZlorsMukdXU%U z8$dfs_>#f(EwL5HC!fdZhJoXk7`zw%rkvK#fcr~x+~k$KpPAg2{8C2q<=X59Y?0i* zRrj|BJ5f@p0E1)AynICzc{fwm-W!(o;!Sdr(W-RnGCM<$-;8n;xKWWx;0Eq#rJC9V zo@>i}O3OPevk>1QS$TtE)YUN^PoAu76YG@QUt$d73#6ilU!TgDhmi#@Q15*|b$DmS z4@5Jir#^1Z+v(*PGo!dADnf2wVTBFa7eswd&Re@ z>&+N+L&Es>?OUD4Oft47HbcjMR`2&B29vKh;WXjauR{g~#)DblPq0eB_wt`4O`;bS ziL&pfr{k5DWxxbq#zu$&Hhq~v-6 z)?42xAt-g%3q@2vF0(^9I5#(!K4P;>tsO-9Z~cU!f=>($PED)r#JhQ7D@lDaEoDO6 zPU!WPH?1^N10+`k`jN0??DngAw)PURZEuRl3b<=@rjZC57_h+W(a&W5bKycdk3@6x z(MGhk46%w46-6f!5#A-$s%R*VCr$9abJA+qZ8R2+%hn3N4>N`eiiDT%$bO_}P;vWk zTTnKHA%S~W+WY?*uT_+Druji8cQlJNt5V|#8uIFZcBtt!61Q0^94J!@pGi((PL1tK z!D|?iiI~9i1?@H5tN`%|(YV)(%u*Zge{GQk@D&@}k%JSL)I3uV%6(kILqU#|AZ`pZ znDsWxXs#cIb2{^#DUi%Cf2 z!E^Lt8bC4&^Cu_LWK?|Cj?3{DEg~$PwSa5t!@{--)pWcGSs>TNhE5{I&c}-5g@t0< z{`#FKhz%)oy1M6o`ZOchQFwrCNzyng_PKcG&Qu|2(Mv#bY<& z6*+wkqSV^SQS-0*ENB^4tmQucG#EsGTY#es&{VDjPqQ}r{g4xm$7d0V%(w?9SYd?7 zTDD5iE*BeJl|8!Z%;faU=yolmO<@@`ghL;er>pZT0S?KHOPI`Q(?Lub`X&(h4W zB=L!tF(K#8N{?!!dr)beaSa@6?{kDT}(Q+;D6%?i`~TITpcvH0WAo zs^bOn6Xw^_f@MiPe$adYH`h0vqm;j6$o@+yjBA2m_%KZYo@CzQ&o-1{$4jI2=yC#EjwT`oiW$#b~1mE}+v+14Au*C=7kpKIdwQ%6J{ zrp^(?)$7P&i*R$qX0Jt$2mUoeCkhU}zaCbar{L!kQBz2!vLFI;fUHHxS5&OsTB>vH zvcq~wafEv}GiPPAyYFMS2of@v+r;Xa>V@W9zk`$!0OBaL9;Qc8V=by33H@>m_pkjY zj|y?*>CJr@GgVz$l6)Ptp89|`2wO1x0ei_CtP<*1IP$%>kKKA zKurf81*qh5G;~4NcRm8DD5A>68Ba2gj(1szsAJAlE~M=XVNDuH8~otKnF>F{v;_S$ z@e}BK`}x0qtH60P);nA&lIeX})8aw@?_@(hajIt%BBxCyI%XRXadLRez>Tn-d)!cq z=@jm$_SYC6vWro;TOYR`__&UHu+PnrGr~^OD%#L*1Eqd}jb)2x@;KO2%$(v7?FlQ5 z%kE2Xw$Kdxo~f|9mIZI_Qv!<&JTS{;eW!>cQEiO6!(kRLEV!eVSO_C-SE*0%tfIfr-qnxHMKBX zVdjUK4^Jzk7vaxjzRx}=3WYsH7#}>dotPJ|CpL0JGWNPk*W8cie_NYB17N|BCjk|k zAa755|LX%qWUU7o5z1n0sww_hBPc8;)wE5VXYS0C-U+#cJ9{9X;jcK6ItS|}`n(4o zhySWx*eq$bMHaWhVu*@z9#e_B2)In5SVe2e5clcUPZi;)5>B@ZOl?5gydxIJQWFUe z;BZgE7(}}{1;;BWLdcgAI5RK$W=Q}~OP-!{nm+V-Mth#s|0&&oo-n+t1ltPr4g8() ziu>X1cR$p-b;5IO#zoIvYWvF5pF-U-()JvsxxJwSf{{Mqm>^11720}ZTH3DL<=oKS zux^+BcJ62?H~PLmbLBxb66(X$uvGNf5}?5ad0S!QZ4jqlt7h zUy2R4m11HXq!=^UKEQ-pOf_H5ZNYFGLtp-J^ei#}FQKjP5|8AyGQKA4_fHV8x27WW?Qq1J=Q)=DV6$4LykndZpTodz#?A7@t&J_H? zi&^@Cxcl=HdupLp8oNvl#0T8@3iSK)0r6F+>fy()sOGo82PxKm_?{ZQ5;t>y3xV`UkFScYOF>{)eJpw z$Rjy8cI+wk4dzZMU6>=+H@%GF29|jv7{+VgKttyt2DqZAjLPePk5;iC4RNq`#Hhm(A;QtT-?c4X_TY}|dS65iEo`ssck88<)81u@A8sq?GQorF|(#|=;2 zD=*5EYk8OWY(qr(davy4h|fHq;x{0Pn;G+P*IK&G#~)uU4_J7+ z7QW{N=e^b~2#*0Yj3?`Xut81~1=gkKiXUsDuDjMMrWD@)2r(Shq+>p^+_rWJyQl9E%Rk$on(NA z`8Vk&yeQK#V&7L0;+u)67>k8{ywi@}#rT!`o_$8SQI$rlsB4$HJ4bgY&iyDDT!iBr zF!Xia|7i7|n4sq8zIfOv3~KTg*(=h=Cop({Maql0V=_ZGyp7oRlL=M5YYIW9UJ4-J zuFReFs%|bHs}QZr?Y^$Aybp1P&!`%bzz8prhXV)`A24$Q9)^o|gJ*`TBp3S`c_LNL z<}8GlIbf)-??Jw2oB$Rf80f%9_q(1(MYqMFYPeGp^vcy0I(2ilgyDk`ged17>F^7eqY zEL=dwB`*iRAlO^|l7QCVs$wPok{dLq^ARqVy62y z<1jg;W3+_J>t3}?Ow+djS)~b>WK`aEz%WY+dg4*A&OIu`&tqb?U<|=4{T4^% zNOoT@dE?f6@Z~LxAjEF95pmk@ss|18o*G1czShfuut|5-ZS?r5>xsnEMmzA?*1#)x zssiW7E`)-+`d*Q7+r8Dy#>irCLeD6{+^sZB|8{L8D3gOM2=c78j~g8Mljqf^Y4Dn& zmBYMbOrSau0?r-haAz;B9RnGR(jP3$u#WJ#MF~U3;J|@Be=x}ny140Sl{QJD{Y{cN zA_`vyd5@7lClWB~i^lM%W#JO^7y7*O?b^_T{FOyO*9C9bJGorU*N?H6$o_@Cq98HN zE}slDtSrGq#@a?nkfw0_ey(ju8+_gR@s(K%aBv3jJ^3}@$@fZ7{-?NLyoth{*8o=B zX)EToKg)bgB$S9<4~bv2W2U&fFL-xbK2{+f;Ax&K;;G@bc|{JmnBaR&a^-vBG^oUQ zfm6RbM)vw6k}@n4YX1e}$;OzsN8P&pj4JlC0nEh)dFijU8PM)bY-GCU@UTed_s~>) zr1|vAXs3;vKSzCG1Hfcc)OLU+-ER~Y7i@#>yKN@Xru5_uM`Rj2rU;y8Bzm(b87yjBc=QR-eIBfVN zQ9cVXgY#=v69Ab2NpUb)tEu|*D!bl}Du6GjFH^3eX+} zcAy(jI{F7_vmikn#gdJ`?;xnB4k?nuql>uH!%ZC2a|s@Za-y@~H23!cWe>Y_O|V-< zT)z4A8WnjoetrRYlM~Jz#Iw%Y%MYi=6OjpgqsJqf_T5T<;|O;^iJa1RBs6K~a{-dB z%vpoz?D)2mlBytwZxQipK8fx`Iv*=vrb9(KQO40}o^Zlg!fkjTkOpZ}(1m0^+vH7s zEogPl#+e{@f~h${f@L|ps7(9i1hTUr;MgilX~fA%v1o6lpI;C1MRi5eaYVw=QSPTK zG3Dmi2=$9Yf(UR9i+@WKN7`&@!hG{7Dc#ldznc}N8TpNCpGL_$UnrKotU?ANB>RaC z$d0tPd_(LCn0Ty1F0^MQZE==xn0)T_=mRUB! z;t1wDvl2>|qu~jz$BuH8C*axwNC_C}nA*K zs)D^~k&+&r+9HOKX~zR(+a#BbTvpCJ{rcWQ(c8CMJTxM>?l{sx8cmUN@2&HID~ZVJ zmkWGP4ow<3xvJTF{zD}0HxgdI{sPvL!M}nPpi*4|Xp1PFA+ z?>v7o8L9h?{mvf5gyA@N*S>Jffhik+S6sNG?5R$gRr#k4(J%)xGg0cPYTuCpQrLIa6po5$_ob7A=rOhRvP;2)DleADL8s7@v_$g zaiSSPu1-?}98Ut#1x9~|HX|(huBbRU3gyrvws*8wO_g_v@rsYn`bGc}9-VxWLsB+H z;&i9|yruLG=<(5lgIN9rvkRV{HC!1YVl*!P+3;oNRHEWF4@ zL?^F4J5&=cMTg&>NRH6Q=E}qIFl*BN3>wn+XOd1y)|y|!E(v)DpkzO?!%y1Z>l~on zGkKl5F!b~uY)8_vx8oKrijif=3BW2?@ozyK&kM>mRV@448BWJge3T zUFd5#t187_05}%*WacCiVho<%u5aeWqtO;Kk`wa2xq!isa>l~}-kxTbsX2T@t`?-M zr-z3M=GOiYFIU>wFBKQzB{khl}w!r1qXO z-)EYm+B`yET)cC;HYpIg-Dig>!(*g8r&9YBQx5B(wGm!2GHR0aPjKO!%Mj^Z!hyc< zkl>$!#J^*v7f;sk6%ka4?0k<+y4g)4S<|e7z!G_hjPvCiRc-X#!?S+<8+<{*R_k_0 zrz@e|A;E*61rT{EYSG^r(u%hE8{im3g@Q7?jjU)Hh3l*tqhHt-&>c&A^SVR`iH+>W z7V3J?-Js-;YFTQfw;)s~24zx{l;{;qPQuyc9I#~y?iaqj*kvB_&LyAf#EvnwPy%`0na5EZ z_L#%W;}Ednj{U`f(7?tfS^#hI4n&cGU$c$UPHJcE;nyxUow9qT5i_1McE3ljnOsn7 z@V-@vsHBPo1IC)R9Dr46-3V_bKS8TBtU*(O-)>4VZ%4=#=El4f)qE7ADfGZQ{>jvV$xVwVrxiT=JTjX$bB4MfmeNPH6Z_|@gR zXF}a?o%wo>rAJK__X)h~^$Q`%YHUV|LYdgXm~V`vMnIiqYy@roNo{R13EIQ7D3>vV4jO?YUt{(X-5_b`kC-^}O?S9;=q-#gdVDI_)6b;eyFZ8`l`T@QexfBHlgK z`hpB8eRT#GXEDCvu^VyJ>GC}OGioQN?|F5pS`s+j5urIrWpq`^C+_1-UUyhV(6#1o zbN_;h;%ih>jJ$2I1q~ehHs6bl!>SQ#x`!Na^e1m7j0K^lu9#Z@HUI)mXmRXhOF0Hm z->&50ase6i@m{4zt%j%PxoHAf)w`@}RpGGp|_uHQ-fHB^^$DvsFuokYP z?a?yZqv<+5A;NcDDHFO7-eqnt;|4UCs29Hi{g>I2-X4o zbNIXD*PgaoE4bS$=T8tgmjy9=w{^D6L>y*8y9Cz?7akNj z)5Q_&-!dWJX;|~|bi9vv$oBX50SAVDApYi)TB&4Osbf3pIIRpV2!5v^7%dlucF8mR zo$YV;cvLJcjx8qlqMT~B>=-<_fwhoS@JJYxoN=>JL@|wE-0_Yb1I@9SCT<%zV!RO2 zM`joVeIFwdG~vuM-HJx>gK6^IzAweXClm+~eTdL|pV45nfB70x%aJULw0rJdD#R2D z{q(Zt=l~ib^+#rVu(1|yUUSEnw_SzR=B>fE%st$=_);c3S{88OR23Zf&O{~VspHEN zaU^J0Tw|x0(G!z%*S_h#)cbY$5jStjLu9*i>MzLE1Z&CNHN-6Pn#4`yaAdKLMk{lf zVK~LaVczwPfkQj59)_?-wWJ(lH#$`eRUlU<9UKv!pY^s)x683tWRw#Op2gx_^HasU zf|L!J66&F;M8wPU>q)Fj<^volwBvARj_8+~&3r3u(h7!eqV;31i!w$z^jpfv6du}5T zs#u0p=1P?8-7v<@@69&JT8>azay>FZY_<^UW7W<7=xt2Z`6h#aou{B$G-3UlO`wmv7(BR(_1(ay}ru%SYgyObp)67VyMk zH+wu24PWjHkgb#dBSw_CpMcy#8^+XFbre}`3`ThvN>Tx=d&HPw&|dP~8>4Li1M7zF zG>4meIeNe+CLtPr)upqw+~lNX!moUl&6S-&Wi+gUq>6>+bc!Q2f++juROunr#EWOV zhs+!&i)n9gQu_63AlOfLrdiwC1lG5#(%4~o`Gw=Kdr&3K&$85A-{&iv#6@raoNdH( zq>>$_)wLIjEMNIe%KlG5##<&A4gx%Mlnm5PHo&h=D)%BKCAb$}uc)yXN6~@Nn^^`| zD^KS$L@2;|dVD-HloPgpcQ+uUV0x*gr`M|&48HP_xW6)IWo3nu!??7({GwHFt4SA4 zq~QE%`PZ*A(a0vW0EyT=wyz9ZlbAsDZzPeC>`zypzVb@-@@wj~Tsh_?X!p*FSYjt#*|1EFE!~SrxzP zhSbyvE4s!YA|s33$#SxvtDy1Y44O6+7LMl0$jMg{bWS-uJ2!{hpP1NXYM2TAbM`n8 zHDtokT=B)*a~U>m*{}EJO$eAF{7Jp7KYl=K5Wy`Zv^=|NW$)G|t=H7G>>VGzX8>FHE%emP)U+-=&yHbZEotkF4wsBS*q+bDx|&{9-0x z3Jf>|C!){l`#X>KcLrIW8N!rStKjMVm|Se?(?m;L(CUEvla?-@=DiIi#%NwY0p@qx z3$Os)U;pU^pn0)yGR|aq07IE2Z!E{O0CGr~W@G*_R-BFW}s3 z9(PW%92y7ti=2=lT_q4d$Aj6q(Ly=p3Xl4c|7EQ@XO>h}RL1FR8m+byckD zLEvPj=fFdWeWf2Q0+shyzNMuah&)8nBk5QwSuXD%GQ?YCJ36dtPx>gW4K2lT9?i{p z-xZ`?I8&sYdozWdxF^|09T|~-vfq-(>s&}f+o?LQ&uJR3tybjVNsqW6E))AeHoA=~ zQVbbeqzG>IOAT}W6@~sIW#<*iIuO^L@L4%JHRgJ{E(l%dmUy8G!b4E2q^@0XJhX7p z1@q$=yw>L;F$Y9nDkQ6nvwei5Zjg3&;e5Iw?_oNs%gF~KIYfICox zHj>L};eCAkF`kKbUzQ5HW#vmht!Ms}1FDmOOaMZ;7@toNd3ba*(%w))b6%{05(#Pp zPm!`fSK&?b!qmG+N-dUxDLh_2UQsYku=vs@cOxeSZCJK>ZSH@U#<8@#X;d}--8fdg z+nGwd^F>VAnak(TmiCx#xzs-eSIlArg@AOfCB-rbJ&6MWd@76bd`|XbZ##{z8yzM+ z-QXn6R=~64Yl)f#dEi+4E&yy~WM4?srru#0smG|@i$He}MV^E~)z(p_~yzrZnXlJD5xU%Zl+bMUV5^9Mj2E9gD z`llkZt|#8PbWfA)eiA=?T-MJyKWLaBlYk9L$Rt1wvjR%~Kqqj$Vge1`^wYXW| zi`{>tv$)-2PDkh5Z`E%^Wd>lBV$;Vw#ehfy^G66ri6~W zy&(}3`0CWELK%vTj0u>TqlV3>OP$|J%H6ZErmil%t^K&jlp)#W!A=hq_4j1=ckv6?R&YhL@UqDI#B0G|n8xjUt zeNtJFC`@D?3DGY&aYety*hTOte)v|H87f;zN=y~I9f-FVFt z3%yF{FF}VcMa%JPov!+YXw{$wuR zhrcJv+V%BNSx4qPDXK16C!=&(|EgEDJhENRh9zqyQ<`aGty1C6mL}+VJ*U}#rL|JB zXu@r7{66qGuoxyZ-f`#33TN_m3%v(Iza$HLPza(OQI2vt+Hya>h)$27x3y}b;#|R# ze6^Z&q!%{q`=@8yRo0nk_#BD9!O!167=dra$RIP^v4;7y#{$(X&yPU&)w0Kx=$!0V zOghLzYM}}{%g#^NR@$|0%7ePvR9jdK-PVjsT-=;l?_J*>l(Dotn=FVC6#l0X9nuGMJiA8nuCf8Esm{$?5fMd)IX zTx-5oT}lAJo^uHa@L>bO=#n;1P4A4@8UNe<`rhnM)9DgOO0D`4_>ry?a}Ek${6S~% zpiv0PeU*Hv!KQlGS8&WM#>Yqrrp2+@eZ7LR%kfKLTzM6e?2ZqO-$XVrVW)O|a=Jy? zU)pRxdFUrU+jAny8=Kq)#|9@y>^B8M#0wfU{`+}&5#PS1|%Myhd$ z=HD{~y{T9^G813&KwZLL8O{T$1CUikM{R8A6}oSBIH&?-RZUHs>k8$um_B#9n+PIf z^SD+nd6gLLJF%gOzrulN2Dvm1a3?6M2_{H>3a=5f`kr%}wzgu~{kyEt9WYow2scLo zMG&PIB}YLA139<7Ntzw!X-!n^UDNu-eA>bNba>cN{;6DDurxCXUC3rv z1`~^Eqj!ScLV+?Qq4XvG;sTpPmB!p&17s{41{7p=79I0#Y%9>m%ezn1^+6>WAe-N;4FefuUdb51UKCL=fGZ|5&|}p)_9o0bM3cF{?KAJ+U~w2c z-w@s@=_10hw6t=1jI}K)Ge)by2%Kw#0b47ea3b5Pw_kFf{T%{!GozFRkd>Ok+Ga** zvZKt-DE2U-_&(g^B_LQuSs4}00HVct-$cL=Z400@K1%7fWb7)gOF3Ri7-J?j!L+SK z6w2;@+j=2u`LjC`mzjAc^^M{CN98*XKKMkbbjZ*hpgY;z&()AdQ1sgs zB!?7Px<)6!mq9&0?oVHU5=wi}(4n`8NJt~$TLb9FS~FfJ%kB%7ux|~FWjb4UZ9R36 zbhF=D{Z4+VTDbvA2~CfCZFC2xufO$P;w8fusE_dv(HnBd{Tz1?ztZA*xOpD6{jT8h zi;UvYzw0cGL}A^Kwt;g%TR@ZN?a1`pVo_lxeO~NZd}M-)LyDq6?Umn+HGF?yN*2FE zJZmk=Q(S8m&aSymP?8pp);fW7yf5$Y`%lA|+SOad1Okp~4=|x;+>O*F?4RG>Jn=_S z)6AAHq|f$$r?6IvPK~xeN-GfBLL*|6-OE+nDjh9;z;amM{0;2~U%iJnQRcb{f*^`k zGq#}@E601>Dwa<{I}ZSc_h{NN8(1q?^jpN z?!kh-q*8XZqL)eO)--Xfr*S%N!zuDeVx>{)fG*`40M9+cHyWU-swz7reXWv@)~|sR zSmVXxhBZONgm|eXKE6tRk2`y7BR7&7*dL%5$+HhAK_TWNAm=T2?I!DbdsC9t(PjK? zN&?2^fyV+vuDHVlCrz4;p!^u(na6zAbrD+uaXVE6+s`- zT|+jBQ%*K|!N+OFITUV(f+2;n^!auAus;36r*)P~`aOlg%MT=5`oxFCC+$)+^k|9N zySYhIJ0l5~jlrc02&_z(Wt=w#b4r(w7FI4aYuiYJu^TdB3m8V@Dt=G~5x+e~lVM_H*M4^`dO9Zw-U!JJ(poIUJ> zRoLW_hhi<(qqB51T#I&h+I^azM-lzv4wP@+BRg^Ujj=~>f#Of`fRRhJ zX+k+8EK#yCAv&)5l=aIzF9-`;vLmim7y>#YL%frMdP-74cdRi(1CMjc4=UN%$wj0I zuX(l#X)W?GphBM^$>cGAaqnkGLL&k}FX;!EkfP$MiN&eLK7|N&`w8+3HB=|_pO{5O ziiOjZ$5Kdy>AnxMLs#l{jg@AsbvpwwgmZnr;QCE)mgaw|$tcb}3xl%kg)E}Hd}ytcJ@&h}LP zyl~l5-2H%jD0KYnu+bqD$gL-gotO<(_3TGwHU+D$0!HQVjD?eCg$TlaDMmYy`^)p zgF6gzC2lPvYr52nWvR#b+l@MLmXR>)Iq#2^S`}STO=M|!zI!{$*@`_Vns5S(3L{dn zUpJ~QE%VpudKTozNvPxxRYSm&4V5q5?h2$P&s4=lwaTA)XhemN?rU~o^lR*pUo4+P z#X27pKcX6cKVzgsOwD^Cx6JX!8Mg6C*hbwLmhs|O;CGE=NwbV*5E^mH?8of%M|>YX z)Sm?vSk}`QFOoFquZe7l>yTEXWIPfqP1pF{VvNB5xY7EBkad8FU+%C=wX7O`)ZrB+i%bFeZTMV{{G&M9lJl*eP5sJI`8v5FFwA}+@m9gU-bPMzPl+uun*?u*x0^5 z+LZs$DB={IyLZeKA4YPiX5dRBw#b+KBPlts8RY-RL)~F^PwGolFM4{8El%zZUC9O( zwqq|}MWL>L#~|L(Iu?`^TA#2FP{JtDMcCh|Bv8r?lng!n_T{F<9!(>E_6R+uSZOi( zrUs40C2xL!e~*o@#!k0%Gm*`FWxFG?+!zdYKqGr?uF%vXi0AzSD{+lX%uk#t(YK?- z3hgW%0qD)|mQO=n-dgs$60xK2vN9{x-n{6ZJR?&G!}YlJfjJJKEF+G|<;t&~50;~c zTYmfXJy?p)I^Q!)7s&YiZnmjIm*$@z!He0L8=bQ)zda|c)Bw*Uq^rw;%D!+3vj>b z3&TY<#02)6u`h^}dizi7FR7gAK7flatgW7sHD4yh#B8Rm%B-R7S`0C@|M~1RDGl99 zmCI+(hUjCnvj}(y77Kim;+_5!d|P?T6o8OINF!vr_r&O)=UVq~v**3Htl~W*2mnhkBoUdP49q=# zK&3u#UW^)Wv%o8R6TcgdcDzr0Y4o*`75;l%`ZgjYh7F%S&Aw?ail5Wb;yJZSjn;?L z&8OeflJH1=ZMOaRn^&&LpyQGe`o@=O0d~-PvCAy>@$y&IRGJm~4bh`6xfs^BT!Tqm zrJHFa$@EJ-vvR6rU2;FahQRy~?BZBNNa9@+zs&#n_(=`DR_-qvk>EL|kHNS?9v2Dx zQ0wJ`?9XuJP3xP!v2E&2L8vn{9O~``^=3=1pcOr10&+gvX8#AXd0E#v-4YsH%Nl^OPi}Q*985?Z9hpVXfdM#uju9 zfMvj(AQ|(HOA(bX%`UL^j>JDH7}q@+e3YWr_p80`hrG|}hX8maV03S`1nD>)gtqSa z3xCQacA>1*6?G3Au;24sr0UIG%e&iv@dKExV=owTC0ecFH*e$< zOGHLF5iW!O3?*XFdp^f&(mUL0(v?lc55!BK|FWV`G{bptedr2gX07WfFEln+(T;Tm zT@$@ykA;5!DkJ#rZ0oCnLMVkTn(Y(RkF2X^{#M4%bwkXs1IH!S`fq}_<)qft+!E62 zCNfas^Beqwp4(=vl0|RBr z5k-PQss{G?5egX5`@H=zDoInsa<4_z;c-niH!uDy4ADi?dY}x2riQCmf*GcW$m0~o z6f;z)U#^|OPhxZ+_MVzHa z^BgQEe9~^k9|Z;7MP<7>_YZ9sSwmh`SD3eJj@*7PbL14>B5}2&K{L#(z*BL{-eA+x zI44~4({Lik!|PcsK+de4p^gwGQG#A&>w>}!V7geY&PT%L{dcgPE|LD^W`16hoIR>v zNWNs>)U0^!$7GR3L%Vt3*M$5*n>ehJRnN@46`BkH(JrX$i)^(L8(7f_iINB0`fsHH z0#Y1tfDBJ7-fC9j?+axN;MhEA2DX?4CU_K5Dw(s|O*-mnMO1OGJCTI!GkQyc8X-3^ z{PC)KF%{^u3~O73w>6gc&Knv!JnTrOPZ2p;ZI=wXK7Vm&DbwN$8wu#Djd%SLPAaT4 zQOdbfVVu`+d3j879r>{iQCP^mQ0`b!2{e1aK{w=>fy6D=w!!2>6+<-p_JP{ zNbgHC-D;u^5{p-?egHRm$t~p`7st5}fYs7pVHV;WL-6rN0GFPCW~Z(6O=>SiDU+Tw zO<7&Nds1^rb(`(Kjo3tuJ7fT&LBfd#321du3{ze33ouzf}j<9LC~r@v?Y z^)EXcVUwtJ}fsH}&228+M-lub;c z510S&&Ck#B9p&Vpa%8;A$e($Lr-HPfv8#2e&nk}T!SX8jSEnI>sjHRzHxJSqeyORw z7iKkzYh+uexxMnpeRRRwF{k#XIEK!9kpoaS%!Re?`7I7ll=50_cAX4Osk}$Q-ct6O zcr}Mta^Pr8mtih0%%O*e6oDRiD&iu@DeBNF&~5Oy5EYrWjfB#LFxSRi`o9E!%SQTz zy#B>{76JVBy^#rZxQwLoSJ-WYbKPPmV!a!G!DNA~!C6tGn9R4~S#Dp!LPLDdFBn-h zlWLtK(O-V}BajxK6i#*_oi`ukFVtwSTVBT{hk@KUhNPnKTkpJG#9tAdum&4PTN_Xx zD%e|W7pp`BlBA~6+T?$W@>=Fw{>X5Z`6eyM8p;qn;pOG!?CN+Dzus>C-rJ|K+#-nV z5nB57naljhvk_T?D&rzWG54s^#-8K|D#fm=e!(7>-LIK5zr3DDQd2fCMoNK$%A?9J z&qjyWoFzWv3ZI%s#~Gr4z1~k0flrDhc%;1jl^BB@C+8BB89zTZb&*bm+_oZ=O^I;3 zBQB!;+`L1Dq5qWN-$E|(ENFyhJVVd(c#DIJA_YePeqeXa+^QQUO}|Gc{_#F@*l$xH zY&g$@XN^ntI-NwFN*hWz>PTd^$4#5}YlVQboft!b+SlUfDr`dqVdS2HO=!3>0=lO& zvnYF-J~V~=R`N~90jT++I4r%M(fo&(FA7}J?E1>*&z0qH?Y44_0u&_q;OXeBwy5Tp zi4W5Rk94=dx&COH(o!|48B3OTQVuXaUfzbUfyd~mN96hD4E>>4hXaeS(`QqKmY5J8 zIi>vvWgHBPmG=Hs7C(1Yc1Ut~jpG+|u6~3hAkyVk(xURRClPn6om9B){oJ&@GQwoc zu_dJ}rlo=T24s(QN2aIcVgbJw;jJJ_p zqeC(sm9s}}NR@|M%cGYJ z&!0Qd(w21yJ`uIrNMQ5LE}4Bd=1)HA^5o;ey7?9s3%Hwte0}4nd;9nOd)Eh5?EN;>65~D?giSu2Njmi?{GgaI%{+P{6leLmYcnnS{zT=? zk6Zo!*#ro0>ILu*Kivxa)r&*GMe&aPq5qd|0#$wTvdL#%{o6~TEZrS@?(lp5m6(ri z6VfR*OS*R6QfUe9ddzzV5>CmNu@e5l@CjYIr+r69In1zixHcQwBUe^8o!<(UDPXDj z!t0l?m^Q9(1NRqMedyo%wBBqMR`jfnLRQdy`E8>1#`EJtul@zKS~{IBr*;G{S+tJh z1_7}utbyl&%^3r(EGi8YS)nV&#rFa$MUdMH_tDpHK1@CS%?B3@M<(7Y70+f>Hn6ua z&YMWdJE3qBUfy#X_{B2nYkx-@tGY0aP~H?GblyMZ36T!s{@0A-B8fkpyPkaV(60gy zT1VyC-J7ccU{Bw?4pwrOMjO+|(kDU#4;Cto*RH+GZI0Jk-;WK;NB*QYZw;s%i;TL+ z533%D7VM1Q-J?scs>(gNcM-Rd(9iPe*N;8+ox?C~psSThAm&fyQBECluSDmiGu(PU z)7x+;=tIqBe3o$goupTeW$^oH_4#ZTakJu*lA}H0q%dS2CYxT$?G>*@?=SB@lX3R9 zkB7W|1z)_5nH+v!~QB zp@n`}avcF170#$9!R$CaN~tr~>AL-yKdkpIqQ`TRBaQ)JKC`_1hsua??#Haqp6GAe z6;AN=_0x>psts3P)cG*!Bp|Yg3Z;6HO>5M?@iI>3RH`f^GGS~d=3rhMUshftWL2fX3^3~wq|RU2~p(>LowB-V22RPg%x5XZw{uyEuwUbsj9!qy!3W{_$L z%AG%%(A6i3q7a9%_t`f*W|G}TzbQqi!i&5HK|G&Laik33B!HbY+BmJTi~A1xNgaHW zsu#5Tb~>x*<|G`4UBy`GUjlQV&o*3@|FiPC`_HzFpNw5#=O1dM)o+yAZb*lKcsR@t zcz&^eK+Txn)X|x*xUaYp#~{tGb-ctPe-oB|uyon1_-8`u&vO>e1Ya5Pou|<|{D%pQ z_9wf3hkd299O)OChes!g-UpH@khZmmPh3=v(UGsUZY!O|S?uzo@h2YAY03re;xHzh?AiOf*Is0x~ zpzomT6_g8o*|1gBew>;k>&q(`J=?T)5?SwXLCbB8vEU6diZ~|;8`nrPEwh-ELtF39 z->pvd@kJfAZ$>oZO{VyZZDvYbzszck8C5SVZ){w2wR&yE4l?rjKQl(6y-DYHgYOrz zQ|iT9Q}Y`#8vtAN0x;JwLa=pBeiEYnbceu&Te*)T#5=(@*I&uNUUfTZ#7nwTl};%l z7PHbDKSiYyPL$a{sXveG_Pb}_E0R({lHoaeN+V?~MOfsAuF0${PTE{L0~OpF0+AD^ z??Qi}JQ{ml=<$QvyWRb?wvhhLyr7%0U1eP9^XeU#b=K zK9*6QH^q}zw6OH7o+gJtAv7$4YW!0-P1m?MWZz`pS3j7=AXSzf_9>;AW)DWA*fSTf z2?3;ti3Y4b{@bcV<XEd9-9Hloc+PTYEzHlRH5%6kuhY!%*TzcK*pN7hC2m4 zW>;^raxri*7;I)`rmQ0xm0wn7j5a)$=Q+<-vq)0*$Yot%8|>(Btb4RGs(U>n(z~2m z-R;0%4m;mVYIv#D^r>`U2k8u<+>;FtC^0jcaLnPctYV9AbX?q6URkJxcRP}lifdh5 z`i@4#WLa-u+q4}uwI!W9RLaUnELQ5Ca0(({Yy01JZzZsY8g1Z|@}`gu@*PnuQO|&A zqX0JqX(peeo)a4&+Ri(kpeL#JaK!!c;M@6U!N2`K^ukh~nR_uWiS3Y=3rva5B1~@n zUVS`)oHsM|LQctz8H|560)ifu)Z%mZ;MLuWO6ktUFtGgGBpC`#@waLa^<`~;~uL+iH1J}_i%3E)(uc&WFfo%NI z_y_w;(Y(+2wtk~+zo#QD)#4ar7a%JuxbWC2bN};%ykhc02%onn%_lQt^c#f6r}DBg zXq#hIqJDa|o0+Ffb68S<6=rir?&9LY1D@kMk^|__87(6Fcm~ZbFY2}dxG#HK0z3pbWHI2kO#H3iT zKEPE}`@U=7ZY_rCUH1yOIOdF)>_H$OhUlxn50z2A6n+Dy$?OP?5d+$FfY zu^!DqYXX4G&d&C+o=P5l=1!csBYE2AFru(qa!wS&Q(2xHBMqj|u%%!0-Nc-}*_ zEiIrN;PlOijN`zZMA*o}g3!jJ+3s6g*j(}4F-reB&UyZJY7|m#guDNi#VMbQQraDh zC>i7BZK7%iQ*igLQU|ampijOJYN<;5(Wg|BTMb@Jh5+4L9T1f*Ilw1e_k^6t{Yg*eDnx*v& z=42L^larG=ADBVR#1L+shSi$QQ|$~pKdu;?2n+v{Q<8wmT$ff>rg-u4luU@JzPS;R zcHjC{Rb221!}on}2?D8>4fD|Toqk6%e0_1CIHto~CH7?4qFynFHWTe`p!8+Tuf~c4 z&Z0H}R1)55FFQMUAx-MZTlAyPqA{OKha;CcZyunoAfhfmvz@f7W2B2ySAB>1{1%rn zFu9+%eCc>ahf~Dc-DvsHC?FasBCIK?F<9&OyeqaSLAst(mzZ;F%2;>&?|F87erf=r zT|tz9uS?pjSqwf!WpyQ7d3wP048HtWoEy3p=MzGEnn<|{-_rkljgbZNgh#e+o!#p0<^3(a$QJ!*|B@A2$-$a736Gmi z1XOE>-wSBHaW{tA+Jqu6t_$Z+LNex$CXe%6J1x2_jwE-71;65u7aPI73nm?d&4J(s zT>B|*H3>0O_tF2k+;`CTj_=qJtqFcyY%ApumFfuGvi`jbr-a9-lgbgq$213I68lcaSdN7BqHb}-oI&$RxEtu5C>|(o}=6v=+ z#5E={OR!a{_VY!+lM_XIxuEx|!cqS3-i7lUw`gB>5d213q(Qq=$xE3!LJNZty-Hq_ z@~ELQy>jdAftVj3J*sHk}$5n@27Op>+Sy{t{*YwZh&3A9oL^ns@sU&38G4*zxUJm*aKPwS?>Hja3 z1UiYwPR>(?dZJr&E!JW&)A7ygUb8VX>$7aGZI`@G^bPNa+Y)_TIKLKARCiv+q?h>V z<1U(9Ir0KG`)6iLvW|N_zJrPVe{<(9m?W~Jfx|f6WOnN3L!x(6B*IBe`T6Ee&VSyT%lqkWV0R8+!(4eYEu}5f{7G!G z0x-{m%I5d3u5oLiQOxvIi$bvpsV9*>^Tw{)bVg#|R2aZ-NyzVgiQ+^wkcpO9eI_Xf zz>q#_y%@*(!M;HaSrB8AK_d}?-ChuX_P{b3Tzr=G{W=IftQCUpRIryFo;B?GIhBOp zF$$LR^%r^=_Bw7ejXZSm4d!na!2+0I!@7U5BXagLM!g@IM~gwwcz(k0*EU_ki57Hz}zDc`s*zDOm$)Suu z5my+JlP*Be9xTnOG%E{=a5fm(j+3jPEukE{Uql_a`Ydg9+G3I5-)1y5-#zHDNn>sr5C@A>Jz8s((f((g-m0l#=%%MxUd71nUd+*92*0Dz zR_Dt^J^2Ac349q%&tuuiEU7f4THYJgv6!x`Z@xx^gb&MeDC%&4Cv0Svx!iB+Spc$p zK6Z1T=mEi|ai&%aG2A6kAH#eC`{apY$jN=m^oR)Jn{83mkJ}pzhnj^9{+!!Ry8w|H zqE7omywaTfyW0_X?jymL#Wr=(J$~tprfnCr>8}LSf-u0;z+jL;U2Ef?!?~z| z>Q}k8r^=%-FBVg&vX)?w29*JFO?&Puk*^mE&zhWw4gx|e; z*Y5l6;m@v7*UO)C3iE1jzg)a8;YCbDd%NC7%TN2d)lCUo z_sx&mZ<;i>1c;cN^Z@7j>UD@2npsJzQlmH0B4^Qucwe8ho1`w%B-}_^Ok| z#d9F_AQ%Fm;JyE}eh^^!sq|jUw2A5M@c#JzI70!m+5#N9v7B}XdwGI&xn}D+ALYP~ zp?2S(2-pD65E0oGA0M9$YBUg9YHR+r>vfvMd7h|cyv-czZ)q-7V1r)*b{>zb(5}zx z`^`U_Ai|gLt&!GX8l<=#GZ15}DopWvW&W6$h`=E(;fX|k1{Lb12)^Uw-{#}T+qLq8 zWS%hd2!^LtJp+DGKo4te@l8paX{F|vHWbonq@K;x5aejx2lqnP;1+d9c_)t@uva#h zgc)Zks1CUNUoJn^=UyAtG(xt+-aCQ4Ha#Mp(|zMFI8m_n>nQk3E;+bC`l^_Jl z$G6vxc8qv!n0u-_5Z|%WO!$O`5ca@Mu@!=`q2{=pMm7Z3)U0~%Y4d2>%Vgh9q>?yU z;SUh?Xf5qn%t=5sznQ)u3I~PE0&xIvl7a`;YwL7#`{d0HYCG=z{?(D=!;Y5aU&3$> zLU)pJnX3?V83!LPzuD@-pH9rRksYW(5UIXHO^#JbgN(tq81z3nTlk9#wE9J7D|jkR zi2DvTE}HXADqt!c=$=dJX{w?N-mUBgZ%cx8a9e`7J4s|NGXcx3b;9xA9q&dw$)6!0Q8O(qnmyCEaHSrM19!oD>@C!7a zDei%Q4r^zgeS4>0LccTOH!zx~YOnC-Uym{~%0Jx+V;d|$q8?ea3O#ZKw`Zmo>Fo9) zz58estm|qn zKQBDa(^rkOQusIPsdc-R1vD&U*^WBbc!hF*wV!HJbDV7&HF43>Tm`jS^w?zz37x*( zRvOLA8k{J(cUwXBkN`2r7T3d6AKKX}e2{c@4lKB1B=hDIF$MRf|EFMuB(cF?IsD=~ zpyrG$MIufF?tAg@GjUqU6^vje=P|Yw29wQudn3TJ!Xtfeh41;jmRJ+-@L$=*ygJhGf$)(RtV_~Nr1L7rDr3%4_zXs#tv0F7E-RtC1+g7-p+Aw*AKN{-?G`InnGMWP zN{93iUI>1oi+~3XUDhR)&}n(0lEK>6Kba7%(HXpoq};wu_GA7JI`9rNGxPIEjO|2j z;7%!5Ud4*rbi|w>cx#c;MBtC9B!f)ARZViCu$#?ON+_xm!+;2rJxNp(Y*$}dS-nX5 zr>jAHA%(iUd{0oWX5L!!hv)QkOIt#wmfwI(2v+pVEm{4?rZkp&A7pYD=jR_tF0d;8 zL@LVqs>H?X2edPMucD-NjT|bQKJgBUDP8(pX7LO6mJ!vkl5o0~tU<&xxc^-t?&{>&T z2`!>r4Zhpcv>8Y5r*A*{!18H8?j$GtHl|1;Yaz(@Wu2{FYulCLq3yepl{x^wh)azL3hr9y+O%Oi%LXbg%e)c#AN*$^c#_TtE5fh#nuu z1H>DQC*(Hf{EX?wDv4)#Z{Uo3&+M{!Mcz0=*XjzvIZl_}72lzXB;J{H&7b|!O&)xD z7mY~kl6&cG#6fs(3rXC8U^Y-cVJ0OKa2@={bCc9XZ>J3dy^U_H5KJRe(#-(@7xdoB zmoAVAiV-zoIbm&<@kS)Cxx*O{Hz#1=Xbj?Klz~`lGA@JptP7T!mH+K1*|Dlkx9YZH ziHKNe3g-FXwux&%T7`L|X0HE^FnSB#CDeiH{#$2YE`2IJcLh0v!^Jc;L+n+KmRH1d z^$4RvZ&b%ix$$WlCIZ)2bX6^&D?re`j*Wp!yQXH20*h=D;OW}8uN(J+Gr>4CjWwJ! z@K;l@F<`p>?37ajwmS}dabVkfE};j$v;}jzwr&z13h9;PIPv!DS^25zz}#g2SV;f* z9kF;s>oK7t5piE#6;2yTXhZ~DDj=4~IK8I+{^(n)#NwpwK1YcqxdD{4?No{;IWnR( ze%0OYMTrEQh<>kUMNXcWG`oZc9g@|7(1=%by8-`5G5Z2?d(lDQaW9-D1(QZ_W~H05 zPZe~2dYWY}5f206iu4B`#IEbgbXIprVC7HzC6lXI`Dm%F!aK99)5{U}Qpm~4 zEvWJ(hhZkwJ#{N2gf}}5amp5_V0^iqS!2h5|B z(+g*fzjYoAM=w+_BPM)1+0gA>_&ha(N>i@t&Nk&-e-9ED-2v7}$)nmq-=TcAk|()S zDaIZ8K{7+qxASwP$4#XuzT_DN!wiDoHCe(^_cJr&&#yiJoo+c$MK_3weUiNW%))Qx zT}O)5E!Z`c;W^E&*IFw#LDJn69EOoEDy)OG`3^UIujd zh8v-Lh5S4tEk2g3len>by!7$DwUwis@Lt-rF=S8sZYu7lZH92WDL4dthY5{mqO76_lg<5G_u8@9bz zv{hEE=(q=raF$bS;d~QDxaDvre+!mL3wMLF>xmqHJQ@`Ti0$K`?X``BXJ8eA#8SvT z2kq2r(e-MvCqJySW9h$xqRJ@h{zHkj{pX-*{oX%6=hu{Vz6S=hVUdp?j zo&w)Ybis-?CtW{FGZfJ14u-!P^S2Q5E2?NWHd%%1E$RXxe*4ySoxw(yHv`c%cA&dX zxyD6*8eg)*>0y;-?Zoxoaht_@mSm$gHR3YGG0ttYRQGHPSroZmN*MwBNgXE^QJE{ySqu%D_ZZLe26%B5F9_TH#^@uHHCi6_9%plk6$VS zfi+4|DiX#!DKf9DJxmxz(0noGBy_!h3!Xs2V}UG-*tSWkPJnqACVDP)qN znWU^WBHfy_a+;dTi9RKP2$EWesE^)F^l%^um>i+wu7QyR#N_E`39aEp{3cTbIlY3Z z&%b{OtF?+SSFPvqM)9YwS$01BW<1X?0@r4+CP<%sj!0xZkUdfFM`C+}vlShnSzh4VQ1%iq{>8k|yR^n7T`?e)ZdHL=_z*%FWK0n>b* zEJ?-A%N0j@9#>)>{1nF9{=-O5=p9vQ|Jf`Tzbt-NLkc;*>Pe+qD^S+Z)M}F=tEx4Jb)mERj!?3jKIYB3i=%dmaBEa<& zE~h<8O0r&BGt0?6BvCy9mswhF3Rl-5z2ELZb5-pp3dV&!htiTByis;Ncm0fv3vm?sJ#wK5BJ=$U?9-9rl)+*kM6!}Os@wew<5m>!Lm}8IyrOD?35?Cex8xJ`S-+5r67U+e=uH3cS?Wj5`F1oCV=7v8szp_o@5)aIU)MCu+`m)FC zb!BX7z@JX@H#ecmpMVNo<$tXFjv^YPQkGsML$Ri_MoUT;TLYiP;0LdUCjXmN%{xhd(CXTE$u~+p^MA<&C#^7IK z2H1s&BQTL*T>n(XU%sBvQ-dn^sy4;P#>dC6=W)4##|e|59nrQEQs?2;o)LmdHYLx?+k4Nw z%_Pns(_`wC09QOy(-!Jgn;9iObzI(*=ce4qwxJAkm#I4`jhD*N~o&_UKP=D5A< z{CwH-ZcGe(gKQb~FRssiVKqhBJ$E-Kb|-@@7*KwRh={O>t1xbLI%|iZtDs!qgXZ^( zmFw)bSrbD#ajuJv5;wo6AVXZaAY=2Oqu+BsZ*PF{Hvunkq7&-o&*|8j-{lch4lRk% zY4A5p*YQrhsr3Y=`huBKtNIgPYS{0@m^3o6tcvZX&+A6>8St;+VQ#?j-^v`l{?FZW ze|FfY>)|?5jDG&F%;^cUklsmNMlilMI4D{+E;oVdG!E3m$8cj1z{}ZM&;KlBtw?VF zHv@cxZ@ot1H2TBa?UGTgjA8rTjhka%RePiGUlADCjbxYB%SRpPhvKl7x2-!Gi=(OI zX=#9We>X5uTs|S)?PA#~ zLbDU#p8Qf$)iy}Q(5UyaO&X7C*V6+pgvC}5uCXOn1jN8C$i;qT9_AR6=)Jr6hMG~^ zOL-=Y#F*|j<7ZU-wp4+h(B`*S`r02mdzf|Dtvdb^_)P zAAiNdc$EkI6VM;0KPSio#$#MngAWY=AKC)uiS1<;xhLh$(vMa8voz~QBF3I+n=iL( zF$Q5@FogfPTv3$vI~C_R?LdpJzmkv5{WZIhy~7XsCN=e~ZN7YQC^foyXh$QRP>&z`%d?hj>E&^-gLsbJ;cJP;&Vg zXTvl3a860{(Eqwwc0VBu$?5z#MX=#CaPy-Ku&k(AD!3}}Ad2Z7%OX~{O)%s1a zIE)2Dh1qAYg~S=0Tx6Ykq0-|9nu1m3^Ehf;d{3&sWYX@C(Xm}j=e_-s(x+NOFjDnI+_l48!lmu)M6<;35d`D1kAYwq zRs8GeWxRfrS7WJgcEv%88P$ILeqO!`v>uyid1)}JOpgpzVP~kXG>#qwA1$gaR&?H% zyvmmQvn|XZjo7LGFq`l@ukR5Q!+2FXH#d>=`3HYq(4~>{m5Z(f=9mXouuK*u!z3i3 zKao7)t@o{+epPn%{9gIOkmXpJd5c@<4yNlU$o_}Tptt#-#p=a9@8Whssn;G^Uu|m3 z{rzbgi^j0WCvJ-P?hvcUhy(gPglZE2S)s|8~-?evezNcfwbtf+;K7~?nBcHd zhKFjlZcaj%8QNqHb{b|c8XGFszbbxDW07pRi59VP#1c@OBmWoCUs z@$LRwqSr*cW){Jq#k}`3YSqBcq}8?5{4a$^iX!N~6}nR5qzskd8L?u@jqqJXhxP0K+?ovGFjT8)s**BSji z{n;JP@iK{M7+g!;;Zn--#J&5GjsM_lpnQo0+{~iE*fmeRKp!TrB*Sg&_#)8z#-O^D zKHD<@e0bid68E<@Xj4{jyGT1YO7z4$7;QsAtDvviux9zdM_*>KAthGERn9MDV;9*H zB`yk+YCw=GXi(sP^e_UVwU?F>z?|V9E$Cj4B z{`b|hpXs}8H z$wtEhe$r5KE_MZj)($4e&t@O&G(^9qI00iuQMJ zh_9dXU469HQKK?H2lZTB{@YIQn?{o3|B+j3qNj$QE05rxStIYhuJd5B+`FCMxyn4Zr-cFxJl4L}Uf1F=QG5yA;!K9;auUv=#~p2(9wDBH_stC_9y zJdH)fNP_=u0K7NEo5k8iOSCLh`=htMChq7Tufg1EsF1dr;yK0H10N zdiUeM&+ygTf3eE1?II2Ld@ZGAm5t zZsD!P#Y!(gg^y&`lN~4IgP!g~PxQ$_#;@>Sn>=%6ORB~M`P(v0r{ys^TJ6qeW*-NA ze(Q7|uGG?!<{&0wosSVI$YV4F^By&kU?nL}0F$jM43!eqOq6jfzB6{BXL8a|T z>233eoS1CD^!hsb=xE(6i_!mTa~>XyPV*#Z#5_%9;%{NPIo0spm#_F4rp&&!clb|2 z2DE=}kD3{+ZXGv~uyPpnM%|or@o12r9J;XazgLI-{RC*ra^8{oDUD5U%9FFa%jLeg zj@vKWm=_d$*_%K3`t*PI)hog&KFz2288-fG9;zyqtz#ZmE>#zPP%nk_Sf594)4$@o z0`2-i9#5bGx~Ev+I(LYx#F`x!1v*HjwtSA3c5)SP?77&*XdRc}p@%L6YWP>Yj@yVy zrt!KxNNyJu4EIF{c45t=U{-#KZbxvIeSfSAW{!0)%+D{hFy%;v z#)^VWVslqDaW%CJPobi=8#5MTNy9lyp-ThL|4-NnOLTuC#MSoE$tIbp7!ccOl9Cn{ zik;?h?S4(IC3nV7T|CsP3L4rS=3?}YBxeZOySC)pBbZb>oZ-SE*YA?cA37X3tdVbk zdT($BopsmObgt0Ty`M#ni^O=A^v>`+G^p7D31~3$Ed$!fmX$O%o|&N5>bG%bL4V2t z_~9&|U{3)EW zW9m0>wZbFlk9<{mJp(dBps;G?2-2lh?7PIvy{QsdAkO*!``c@<`%}tWw>V4seJljf z+gfs=u2o{~XSEclp<2xPSxIhuKd029Y2Fx;W6~z$J$83nt4%)eam!g01v5R zygus5AZ3CIn0F$MmzJRQA(!mIzQ1%PTs%u;cAV&PJeyoCY&h_k!d|P&Wz&m78bC|@ z3%VD>&Au+fm&Z$c0hr^2_WFcPmgcqsUx!2W@^b&RVP~H})M4Mv1mQVi-h- z`WX0{q;62u{XE-k@PFQM|6NM=`j3!5)y0MXCfxf|-3%J{WK!GUiGtX8Qq6!(LpB9I607dXMW<3#4y`uY&)iT**XS{ zpevp9z+7*D-+?y@CKzOY(NAOkZ-g(S-WG5b=l9~$U^7|pj?Q%@Q;>b@>Zg*my;y1A z+c};SMYbl8+}zw7HGYe8=-8SQCIh9lW0006nq$V0v9Vzms~K<`*iTQ#pa}1=1x^ny z{gn#XSX(>1JlqJ^>RZ^nH?3H4Yyf-AAoCRzwsV6$KkZlDjb`~jg~$H{g(2!y>ziQc zsfF+1W~7;hbB;-$$UKU11D-q3@GMx?=3e2J#CrizDk0=gl3*GDZG$6?(ma>adUMx> zhPoNL61xfxWL*S^$pygP;{kWwObfF^?^b4$xhqGu{}pa#P$9{Q0|<;y3I_XwEFEaf zQ5~rBJ(Or~SQvVtDb-2KHSeJJJoR(MygW}KoIrQeBz)gy>2|ZX^D`28gOmh8-9{G| zUySe<^T__2$cPB*xiuLFdPT`yEBgPIPye%uoRE+|bHj!P+B`#nex^wvw#Z%(?mN7Pc(QPGS~sf{qybmc(sq5v=uNv zs&4b!LqI+$py45lsMEqX6R!ZrvE;rIWAP6p-%j}wF6wwGq9WT={RvXptHU~PEUV89k-vf zpwaj8g-OYom1{NGCVKcO;tdRt~&V#Zm}>vQdw_31+8GuVVc#=fky^ycGZNchDN zd49#+bD7Z)4OYRM+>{iX#g&zvm&5o1i$+l~yNhfUp{Ej3kgwR5-bze*$PEB2q zvawNN6XDT}j~_Ea^faYcgPLV_7JOuO*Y^500T&lO_^k0P6OlmQqY?ZeO=o=2en#HN z`NdHmTlzEK<1u^Row-26C*aqNqf1!NhXu3a6Qe~>A+r7>IkI31MzFrp0(|H5x}MZU znjny~lqj}kG>7e2V%v-64aWeXy8KTS@Si+~{YCK;(93i~uPIwQwZ8rmc;M9Y=yqy# zl|Va#{Q5Bt6W7fGdB_;)aWg^6;6W?{#TF2fA7=e88>+QfWBmA9r7Q4gzDpqZ-~gNn zhC_$f`sN;Cf8ij19|qv>@Yi2Wv%nj|+Aa4Vt{71se;!;5xqmXGFuSmD4wRLZWm;XE z*=4*rFD%cy!NLi7=sMle@yKn~59)y%dm*)V(!QKwgceFzC;ON#vpb=ou0DcA*24ka z25-35iU)|%hsV#ArYCa%@Z#cPjpoxO?f|bDDnm=w|7UAu@k=zR4HoBeZj+)TyvDqnsYvML zZHKF!Ru|B~mF5OQ>!zs=M#gpQq2Z?JYOP75Sg0;eDm}Vhgg)pN@`ak|q(S?01PeW^V&* z==ct;8mqYXq>A|6$o^kIc`J6uqJk4*c?HID7EZA#gnawGyg!X71e3zHy35KKR+`F2 z-i85ULlu5mzxw8j-Dp&FoTSdD>*!^8ZEbwsD*Alov9;CviQ)UjTg`rH{yOtk$i0qO z*wjB@*(d#kH5d$FU=%Y zHVuy-FE;bP9C^&z75JX{dhfi-)VFspV10^w zzjirSE_g-|{sp!C)F9hsz&$bkYO@R4*3kj18Zz$a>R9BTQv?dSg>e#<;Z*Tkev~}Td3SG=fJlyj_ z0EIy!P$J^$>%r@%;p|Neb0MGQX`s3vf&3i!enX^B)gweRh}+ZMeE|a#1BzBs0@QKG z{;{$&VOm>Nr6edOcEMrR-80&($%qwxQrQr~MEHMx5a0H@UZFqFPGlcFqhQ#>s|#$l z(J1T5n#oz~rJ@4;6l66M)8JvrxlDQGUI(r-oGZpu7ceLrs%GIisl*hvy@i*$SL;{y{pn)Ce}MxZ29CIu&+=;hxhOOl0}6Y%6;3<^{&HE z$q0=6Ibs$%%~wNM*W=^#Kff7eB@)cT~EaPSKwD z`Ky~ItrTN3vjZOT=h3Y)FNgfI)C|$7`FU>0Pxrbn*nfFlH*aJsqSa@sxLo&%2#>Z9 z%vpMuztq{Rqea~HAH26x?;fcIibVb=c*!FCR7E&}=gP+W#I4ZE`uwi<<3}EYo~9

4A1(Sni^BQ?TGWWzUha1XP^9R4fCi0=}8pZQ=I^j6INpG*>C0Lj4O*iSo%h5 z6|AlJ`ddp-BJQNWb^A39J*od*b6t+g?T@*Ag##&|%Kv^Hj3xc+FfjLv2rJ;r6JIv# z*hgIt7{aEwBa=?wo(FIDbom9i|0U}1_IzdOd*@%-21*n4eA0ZKAESf@oXw9lBV&@g z=aS}Z{oT(aBkw%oD3G{9`rXiNyBXn5aql{u>p0zHmy;AClwoUaJ(!S~@GWG3X3DnE z+v6^-9%&{}4cs=gexBTRQeIxMTdUFee#(Ng_8@rixg%^DQ*$)4e1>cKFMz^fcBgGd zqgzcY=s$TZ$P{JOYoUH89oh0$#KHA00L{xM$lRY=0o>0e-Ny2kfm?7)+dqU#fOIP9yhtKTKkM%xdm@5lE&NB50 zw<7NBw@h4mw02uFe0KaQ9P#5!r>5D_(XoC`AU7#eSkTBv?Ny2Fh{FVbKNqCOXgyKNi)+$hS=M}}3 zD(hF=#&^9Y#Q({ayH|uEw|5TN_99b@JO!UmB{%q16Kd-QM4e5z0C_V$GDD0;nT<#m zKT4AZ8o=(lx$52o0dbB`=w>DxW<-7FeO^w0qr{HM&u*TrXoz07m+R^~!6C1xJ7EV; zkL7B)rUw6W0uS10>gy-FCQjY_W-&4SUOzYV6{rfdHCakDKqJn3IikIdrZ~0 zv_1^C^;qP6`L}H260Qk)yUl>|`z}KaV{QGdhKNYJ*vi7@lPg#)V(+qY%5tx+}erE z93CDX*@fZ}>Pa{rY!gBu_vGT1!+ut1anNjzgT`AlOl7f5mVL>^Fi}nyle>hovJtsC z_D;lW*T4s(o<>rc|C~QsxDG-%FXG-$xKZD>mw*)4BIxhj8SsOUt9D> zUPiJ}#Qu7?5qNnu#XbwRj-bjY6SneM}RK9s@4ZUz)RqQ~!J zz5%ys&}JQR&w=c7xAmmRPU1<=PB8~AJpDC$*U4>#X>A3{@Yg-d`oAUr9 z^W!-%ymSX))nbSCb*&0dthM9Dt2~?w%C z-2RWwQ*U7L#$Cm4C7hFB^p-dKlrcT46SFz2orS-h0oB3Bx^tZ!lxL=2zBxGZy$|6}=(!{kB2neBH;W>6!JSb|0T1IA5?E{IMU zlwoEQog#k)YwleylrB|#8}^`27S*#ybtV`PPWRCTLdr>t5o;mp%Y~RYu!N><6U)A%OAzpHMvqb@bN5)USPDOVi!hE9A4 zClgjt_TM8p+Jzc^U*&~Pv;;j z8=K+GtOGXqnKtexCh^EzN$OL3K`An&Wm?I&lJoyjw?SV{n0KI((eE z2$n7qr6~gdpR!Y|-`BlAWV%00mEv4Xej#fZR2bDGrwZP?RUqds9E*OYm0vsku?VsV z;Z;+}1$D0Nqv#-MpymzDhzQf&TsKo5VDYajk+CIbexbMFJ~w1#kO%bahHbHSLd)4> z5K4|?=6T&OeF_{_z0>3ZGZxJYkF)Xsy5Gi`m;FRjv^;vn7%~O43mUnc~zfwZHeXWLEeV_cSt#b85?7SENjwk&nr$d# z0!RANqbNpuN{zbEsOZLh7&DzdGv}h2r$*3Ot@8S_QF=ZCOVKuUZAMWB+e#lI;SO@R zKx3}I9LB&BVe~ZYQ86PWI%qAL`SivbVY?H7FI^^Pb=NC90#(S}obgXba}^u2ylrtw z&x?||mu8}+JuOKKxHi(m5LYu3q2|`Mbjc}(h3h4fUyk&XwcGgBB1i>JPPKUZ36#Z0 z+(aLdMRpWmV^p=c--1pV|_C536@Bq1KRVtEZn!yaQM{8VAvhpPyR(;?gn(E0L z=@w%;#!x>PtfwCIK>jky6Zt^Q-Y_QSR=+)Ji*Xv@alauyJE8K~(^U-_>X38+7_R00 zV>Eg$T2VPyPZ~cc2$ao-k22}-iqo0l{M<}n>bpOgq4IS0Ts0^Pcx}Hf!2`IjTz}da z+(!+_D!ZA8nQ#G8KD&fHao)6GD~w~rBoQNT{iIWlc9snmTHrlO;Y@P6UiLV?xEO4? z%r$upk^x@MS_W$L+?yUh>EHLkBi}~dG!?@~VcK4;b%_pfIxZbLZaKbB3N05_2$;ga z!st%fJO700Q>x;-mlo=0KMg+@#FPp@iz$9YazSn5jLe22hXex0qA3)^-od0|zu5+1 z$M}ubjC;uD`)$|d4uO4E;+fI@J)K62dg-8La%5yDihCLM&czXwB4Fe_>DQ*L|5ym; zyL^OUD{m-i{1LVYvJTK*knKK{tz;|Q&6LLpq;jAqGh-{*Ny2r%lvzPV0`Xj@qGe?tQd$VG{yUxLMqYHa6VaD!lrb zcxNhq{iIK&IWoC)-}rXHO)c_Ad+oBn>k4w;Tr3@v9*gF9_GCilJ^1-a^58fNSKCbMrBz3NX5*ciT2YA4=Z>IhL z;s86J8^%*^Y7FW-mDtpq&;hnqR;OhXF;x;~MpgdvT_Y;yL>UV-Npy#l$w_!+pRW%T zV|r-dJm+GZes_BY25Nwdu067kZj)S(KezU^ix^lK(7DK5t$q9M`ZS&bm-kTJM4#@hG7{~es%vH zYAX-qp2%(wz;!}w7rR!X+c%8;dV3$ft73?cutb8N&fquJhXLgU4{aQX&8H*Zdb9?R zv(A}zyDCwVl=D?fOz$vx(fwZV|0&sI;re02tiB?>q35yWSfbmsDL)omp~q((`20cQ zDcg?Pi&)tqpgfPUY=!i3=>epD#-&#rMZlC?PmY^WS``vVf|Kg>01Es7MIyPpGcT_& zVQu-ls)!i*SWyP5-c(Q(^_=;#+Zt_kT)$S>tn{!0JaxhOn8FP<48 zU|4W7`X?GcBWyRMy7j!8P8(vZ5>ZimSwvn`{bM{KytqDfQ<;r1hZ|o1+X#}xa4Nbt zsT;;@oYO^V@I&bM&}bOmBn_G^IQ+9;U^bB`e6Q8EvrXGX2M-j0@yMfWqeC1zAKBa{ z-@(XR%HE)1lMZErMg^!wJ%)SfoA+5rleX9Cx^!bL*)?pQYi+m2KR1G5W1wy-J*+zr zM45H=E{6)~U{5j!vjArME{-!~+1e&wT+V63u7_zo#v;mTAlMM>MUj^-rVoM)WQ{9q zwy_$-9?uj^6bAg-dK1eIWDH3@QEF@vW$FtFJ#OD$Y_1R;qs;;_x^zN*w!NxrlB7{# zge+{|&D+SWJx~bGA{-|(sV2jTe`*Kc2$!{5Yv&l zD&nt)(AeJ73ph?TQ*^~R<=bFi)aSlZH?=LWDV zV4Y-J^!Xa)d5TKqLpwL!XIl0VOb`H;RswoPL;uCg{t*E9yFuYF6Z8o%Pn(~z!Ta^o*h!vW(Vk+1P8 zYOzm+I^PS#;r~SQ#*zm`F%PW1rS+hH5b?IvCukyofSsN(3gVt?WcL8`DjqwS>{j-d zp`5g4s$hSU&Yd1IvrGz_CR%x*CeARj5qT#!VX)ty$#V$SpR(mY9wI!O8tCb>*bCb2 zFH<*}S{@`LW4~0n7G}yY$prZK5vT1SISZQ$u_@R3E74eV?%TeYbobBJ=6{jV?+K6_ zQAS{KL~%r%L7JVeKTQerlc4BBQWmCrI_`pK7W)c1IxS!LCBFaN-y8a4A>nsRZ+@RD z3D5c@&?g{IMfUz2jmkt5zmW!RSlG|#2UF8u&6P>zy{ggj-?K~0M?s`e5^%#vp63kf z8}90{<%>pIauSjM&6^0@zT?-u-Fnjz6Zr%BaW1SmpaYImVc+t=Gs~~%uW;PAoizvu zg3Bu^ctF^g3Z>Nt8X1|V67dRujG()8Z^pBKU#`U2h{p?5143KEvz-8Fc7uH9N^ZOW z2E9@PyjYlSRy{a>KW~F4xhP<=6gb1b(O}xg0&+*g5GYRraDioT*Z5T&%C;?>-xymZ zutNqXO0gv0lVpu+dTD~G^POyg>V;*m~vDiCVjQDoA*xD_BtnAEC`Kq9LFila-%g*KY!oS_Z6AFv)X@BFg* zGfsCKtRJoAP%;m2gM6e7%S8=&)Y5mTn4s|sO=7XpTF8R>>84`WvL^jq4gwjvZf3Ku zTORg=wwiWfAYbbE_W`H~FP7iKa!@hu`A|aSA;#KjC<;0nRfyn}+&Do{p%6ib{xVhFQ;=Xu>>Ff+# zI);CfLp7|&M;@99B|B%q9({iB@&jZy2ac@MsoMw@J%lwK*AW`Gb#E$$fpsnf;(ky9 zlw;%5>T!Q|-`htf;>qGLt0q0U8e4qg?&;}@X2@!4hB^kJ&pi1Y+Y?&kS+E5VE$VVC zvb2zKQ(%g(oB#0@rH-9QO6r#%n;y%eWJsrz?77TR_mbLMt}_u#NKpHjS2H2L)LON- z*w)4$<;LcH+zeq!tQ6c+G0Qjkw&^{kb#;1a23wHZovP)*%kHKg zSgpHb(enIX_vU`+8Tf9x^T{UZ3CFf6YKZg=h`A|283y zk!QQ`)Rp%35G=W;sy4B|=!O3&gYwKeouxuL@m6xmvL4 zt7_76jyyOER$3q5|M-2baRxXL@ByR=fQc7grx5hej4B(f%i|j6%22Eury$|FN@usu zpJ>su$Vey0VV>Eo+oSUq{o+&Vy&)^vJB7$bVmp2AulI*YDxL*Kf^oFkalYwER)+4& zm>0d1(pL9lv$+m8u8?KXs$mDaS%1v}&^9U!>#<1M3V%ucbw8Mdu|eadYIr?tO$Uu< zQUr*pK3OV(XrVoM!>i4-c)7#kS~H{jsljk-g2jpuToa`e5<|u{YEC6vrIU2jjnen& zNsbzTN9z1>m2iKggEaRHIx(q&iPEVoz%4qLR&_XmY-$Z(aDfmL`(kA~Pt5bZF@DbZf$x4bXCAx}<9HXHQNMinUm^!{6aLyk@umTDi zv%%C@c*6wa0<7?KbYkgN8i3~GKim_gC^dfPx^W)`7%1)T1Xk-@ozg-OWB zNQB@~mVw2F?#9jf{e9cbLgYj+ZJcjNmYKdjRZHPJIIF@x2*U~~WYm_c8)Q=qi6Y`& z8-1Of-4*pTJ}!aamseAcJJ5JH)|Hu{8)7C>t-<8%S-TbL)DgSMHLU*8T0udfskfwe z91C;KXe_T8ox(dvpo7xI4T$jr9m`+jTi%iM@m8qNhS1}oEs+@IL38x)adbB>7d7Zf zc1wW@E$yYkE*K%0bK;tK*@Il+SWksVB)F$k%ao~}dZ-*PvH76DUXaJ$6&I>Y*Y1y% zk0NipBcR8Sj(7C2D%Or-!r>ilxmv01Vf-0u83&DK#ALpFAtmIE6tG|h;lMVb_! z{*s=P#mGf~2Sk&CY~GB_0#l|o{J_hSfaJIFUMk}Z1tD5^9Kgqv+&ce+I+S;tkoovQ z0$eRS>#jj3W*dLl=1O8kpPA}yQZ&4(c!BsqfH9BTmM1e|J%1N}xyW2L zoBWpg$+?OgU-_S;b91_J$Kb)ab735tzON{;5@Bv*gOV^S+t>aUL!}WR?EDL|y^??f z?7fibzM1vpd(k<7tt#1f=BCw2oUHpO_m5VA{b}P54>9VzLiE&BlVwaySP!TY4lH}1 zX7~qHZEafVGq|8C8=A`-kvAP}P-O2HnMQ%7MJi|7SS{iLgP#Ub=9*vFB{%m(kPOLr zo1Qr)l!+JT>|BSZbiwj3!+m9ac%3WaZ`^pTqokc-`9;gasoccy*=R!mf_cMi3kCWi zZvn2KwvlmGD06K)&Wf?8uby@Y+@$(sFMJ}8+h`f7fzE`-dZox-o-~V&(o4E_c^7@N z5R47)*c1fwR}IoCj_s7CLH;h}65tmB4^kOHWQ)6~XeMl;RZc;Zi%;06Xl=q_8xYH* zpdy31;^$dl7Y?M|A=dh~H%|_U|>J;u}^)%w+RgBm? zq13)%f#g2Jx8rP4ewX{h270znjDG&KdlRz9b$7$VX_|(nV-OSP7u$VG2e%3K}(pcAFD|4Sh zYWe6~kx?eVoiVBI#cg}L@cW1QvQyXk0c1eX1 zI*JgETJ1A`ibBhHWH=UUHklL3;y>55YkpR?1?vPUuUL-7YZ?0~&!+NaEtK(SLu&cs zETcA2zN#wnV74k0y*aEpj$;kU?XC>z zJy|cEMH7j;!!+QVk9=inPk@-%!!?;EF_slm;s(4<_GQ0Z7Jd?Yw{LGYvTa4^{QINR z_so}sou`D%h=`?aQ;u&n-2&h07!6wJa-om9w#}EF%sb|6haLNe^TAeE#__7hyBa`{ zd@Y*!^q7qyon9YPWJ4_X(RyWo4dZu_AFLRCjK&CoT9hq%$Ji_52ZG;+by7TbJMVRF zL+FK0-omE4TVwA~q9U9E{?k(;piMs@!}%_=JvB>WQK7^^480&H#|<7nthqGZ6wBry z0u*uEg%xh?|Eyc+)*a{4h!GxE|g^U;)re}D)qxG05@l1%{h;U2|e+aw@j zI*xw0<+f}~FI3A;EsH@?lsi|r=9x6n(rVSF<3_+NY=7D_0W-U(hS1t%Juoo;CVjpE zpfH)B@H_;RHip{+N!Dbd>BpT zeobCs;|&@`kCY;IQmc;g<6K>=u}fDBg(-2tZtv*$cK83QTKK zrz+#^qmL6wkJ>Mv)l3Lp}q6L*Eb9Mi6it3Ujwbl z%F;JLwi+I(u3lDANcx#Otbn!hguYvZ_i1|JxdI72mt=@NL~?b^ zCA*Qvn8jH#C#0k>CxxyMiTqxYL-VG5=aEaEwl=9xfIUBCx8IE`;Y$04wv#f;*9;*1&B8X21}8V_20ffjS8BYM2TG2wzN$}%!H>!7#GcPp!9rj zCCU5Zi)w9*SIHTTzHrCw5@{aavA}^c$hzpM%i)!CAmxj-)oG_1nH`Zfh;*X+Dg)n@EKSSpP;)GSs@3pHh7L@&C0hG1Z&BKO<1MYir83@MZ zfq+Y_UAkUF_RJc#YwgnY(LfcybeE=7rJXd2DQ`b7d>QlBo!?^5%_kMDdEHDaXRRwz z0+Wbv?Iz9(!sY4VxNQ-E6rkr17%*z6KYghNS9!o|5vI~#Ro=5Fz;C0Wevk_r(Ul}Z ztl|%c9XlNMMS$@|)ZuYL_oJJrnifeA$x$!HRBGCNt-P@yeafY!Yl7sItTypQ3ENPF0wT3C*xzTEDuz@=QLzExi3nOd6>nf7M3((`dFr^1F zKdk@qq?$Jf$FRBmV|r4QGA4=RQOTTpjJY)y^so6#jVp`fS6VPx-O|Uwo(wvH&optz z@(XFwnh|xvJ#xk2=P<4ZjI^z!v?3w+8m#?b%nCJT+$oS(^8bAPEL1O)qU4hZfZ^)aUz zfu;M3l9~B-hqa>O)YaNafwy95KzTHEw$AX~;p$@+!m3~W_-(&$=iV{zn~LV4Q~<`V z9X_d73oeNmm?ZIHvK5-_R)pl6WP8_pdDLxeo~?)gJfs9zQUL}c22=tFwBl^%0CkIP zc`agMa(cnmvhaHP7MhLU8D!(DU#n%e)vg+p;fx}aM_%hYp{zGdDzM9h6Bk_zqce?K zPZ^Ju8q?^fw(*>>09rj$aJ%!f5+I1mo|}En=nF~33n>%V4kpN zvYVD5nLnT8`==MLTZ=1*zCl#|z%VZ1ho?KNQK(YzA02dtU26!lU8ZZ@;p8Zasg79K z#-v!}A}=eGeGh{D=lX2u^wF;8!RbA@{5la=o#zMEro9N(j@!o#7v8=#Nm*#a%Ta(y zKua_27JH~!|HY2~U&Qi1xYqx5PyPSJ|BpZY&&S#2P}uzk54X$d Ui#oa-^zQ9Zl2ezhm$nT3UqAeqQ~&?~ literal 0 HcmV?d00001 diff --git a/blog/images/20240814-delete-contact-2.png b/blog/images/20240814-delete-contact-2.png new file mode 100644 index 0000000000000000000000000000000000000000..766ddd677ba3500f6acbb056a2a3bf7ae80a6451 GIT binary patch literal 115115 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%0D#+QY+spfNY0Bo^AX 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 literal 0 HcmV?d00001 diff --git a/blog/images/20240814-delete-messages.png b/blog/images/20240814-delete-messages.png new file mode 100644 index 0000000000000000000000000000000000000000..b2c65f28adc9dfa5d219822f9a5b60049c02c416 GIT binary patch literal 895313 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

q_eP!N`$V}kLSNahozv`M7CWc4S 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 &5_`}d;R!Y`fPs0t?U!QktI@ls~6o* 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�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�~P=-tKBX3W48+o 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%0A0uFv&BMh_QDJx(!;5334V1O?cEfU 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

04hF``L~4Vt!N>9z0tWp z-%){HDGH-OA}Om%iPhu?f<0j1A0ZjOtbtk?!=CzO`c9esvr4ySLjoUqEzxTCesb8a zcu)<0YtEGXM}PW<^NL?m(A=GAOM1?|QgrvuY+Lq>>D5K$q=M$!uhwl(lTBvR9)sC$ zZ*bNk^lsK_sfxCiDADJ>tGB)_A|q#5mAHYLyQ@Dd%y7lu`IhVGd^&w^wEtnF&!O`e zs+@JkD&sN1x_O=H*;hv+4F#ulTLC|;-A6X1U1sQqvD4vDCyN|z^9g=oT+BxGEV+F> zi9OV~e{AZU%0}Ky6a|kTsfc8GdbYTk0|z*E1_3gqC@E-A}Q^C zg9B2nsO`T9op{1~1rrn(V@nqjysl!=8Qi)cqQ@*rpTy?=7~UiN&}VP|pcOJ?o&7|^ zEl(*Dt^KT2M)j>s(-iD0-z^o(I)VH?sop(d4m71aZ$$j4|GrfV-zju|H@I_tYFHO| zR|2=!RQV{x1~6DS!ugt~LHl}*FM&ANqO_j28yq2aQ2Wb!>$8uai&nRgv6Zp+1repq z*_*PjDn$#=oGxIBKb$crGvv1{P#{T zhORu+l!N&iVl$LPan3&Y0a&J*ZzAhs%0 z_KoirORShyrq&VT^yxl9 z&@2Z)L^cCG=yx<|q-+#iq;ezI*34&&L zrOA^#QZ_I^oOeWBhER0aN2u`dk1H@t%gV7D6D@fT&z^xkT@;tF8R)VF<=IxY#Gvl} zGxiDQQKbNClls!qlG-xvLOQOy{*l@F zK|u#w7{8wk#zbvN)AR?|<~J)195Z<-I(6-D9watgsYpE!Vb=W zd2(JgNtIV(qq{G`mza0BnooYLBb9(mybvJ2ZA9sFi@)B4YysKD8tm`KhFE)8^=gd^ zxS(Iz%k>WO8i|zkl0{s^7u2dxvKnM})fgJAM_bWY@|kgyr{;|Oa_+J$E#iQe6as$8 zi4Y}}U@m<1<~1urkCc+fFGP>8yT8h<-RAVrp{LA6)4^%M0>?`FJNrcW57QqsvME%G zQ&LN!eUa}9_Q9MqIM4B~Zz5^Xlh`;Qz6>^@Ac=Zh)SE>iS_V4YU#gJlr_S|=-CN8<+?}Rt&tj4Uw%fj6ln#&_oB~E`Xy087bVK| zlQD2*HOnyv!QEEp*(ZG5z%Ti`Zi(pmHw91jv+7{Kou5CqzmZ+{E8xom&0kq_PDIt5 z&brfhk#&-76{fck>54}UzWFw$ZjsyNr1RdnYXh~}2HC7#23c)ui|rgLP3-#_Y(AhV zzvTSMCvQr13qvJ;VU_yz=rP&;KiPy7ALozJ7&J{X>u$Ui)10C?vO8V%61jYuc`v{C zg)U@Y?{mN0Q~vi`>mR*FxU5ab{jA>CSW!`02MZO4NWL5sTUXT)zOal#AB&_>MiqVi zhEG0Ps;3NckF}PwOZmI`i~gc~DUJThZ!gM})Aseh06iAnyOf6eeud(-1-}<*(cIBQ z!rm0AZ>lM}vz!fm>cA0r(i(qMRxkojj8xL;LWpX@*#1?nN=qJ*$&pk&6|F)VsAwlN zeqJyx&gM+V-Rix}OV#*ADqPB_>GRWbQ?+F16Dj@0*X8AG`JA{M#{X_ku)nU zgx_gYJJSM|nB4gE>0HjrD*)@UYAa&CTb5@H;y!icvwK1fg$$RTn(UYY={zzbLX6A| z2#@=#W#%P^O;+Z_JRM9WNpGEI$@LC#Yyb#aop0)@(In?meo9j+I zus(3lG&5vuwOmR{p7*l+#~)d(t<#}&O}j78Gd(n?Mb55RqfB_yevD){xBl>frwuof z(r6X>zpvs>X_)siFKg$|RKzM|)F2Br^Dne5e@rZiO~;fj3J{PQw}Sj)~h_i?&W4$!GKK8i9u0{swY za3TO|L4F<%EcRCoqzu|iK%mK){s{fZ%DnT1n2*Q%rcr5Ia^w@%QnxjJc|mxRxtq-N zeyH~GeslxR%BRN?p7G~G|0iLW{WN4=@8F~nmd{{*O z%!&Qfad2tT!CNgg`Hu z9(2e>IO})%cRu@Sxp($|Ku%Z7RWnoM|kBgO^y@t zwTi^raSA&hw)T_iHLdzg^%-bQW&}?2aY^)kW#3Wu+J5Ik%k)F+({b|B`02zRs^#P|GqDo;t&_@2q$AoJ7wB2+ zVj8yIruOSH$&Gtk_J*PCtmKy`gcHG zGp!8k8sYCh)M^uo5R59HElJ}V#FB0eEL3#bR*-}T-xXffVan`{$-;rPoIlIYBrC&8 z+~591MBHQL`+bLB>FMi{P^eXUa(@wpSJ`twEh>AidW|&*3Arzv=+m{5)#3YE`yW64 z`FO$ps!N;`ZQsdQHsQ2;NU;JjX^jw=9`yuoe-e4WnP57c2Ch@; z`9*8>{>Sn%c|2}(p+k1LUROEkSZA$+@Tl7+okiqUV}Kcq!m0UJAUiuE+O$WJ9#f2F zaTMv@vOV8+M9O5cc@2Su4|QIZh=%KO$*rMyeTbsx|B?bXAHLg%bTkiJJZx&~BBz}8 zWg+)C62nb??Pr;j^2e-e38W?;Cg5TbhJ_o5$pG);n1tn)6F&hXW)H!tX3l%9rmIY( z1n13-Okj&a2y9+X_N-DaBXF#vrQvj}D$Ln6rJ$25f0|QXP`VcSEs=w!ylMjNbA9VR zr~sxQ_3`t?JEz-@cBP@dcXOvpUVqC)*_A_vC#U}%XGTjsxjw>4sH+rC_FHnYB9a}2 zq;tjLzLue1^7vGvf3-(_Y`pEG?@SVhXH7?;4>D{25jnfQ_%*mqx_zr*%5t$1)_{%P zG#Tak$+WDJZjRs6MPnOpabJ5lp`LH&ir!_$=RanLnTO>~x@hK?T=P3oqTlahpBE8#rAd*;gC-k8OtdCW`+pgU5BLmk!NW^&VnR>}tZKN0#!Zp2 zm95V+`}~liGkP_ewUaMz%r`;pGKc{howKR3?dz84Qn~L>fGFk9&o((lbaoc~*x%wk zgIFF^>%99DWOxSk9Ck9k&8KQj6bRTzx(FB(58n|C_3{q$m|2?G5iTfL=1g%HV1_e` z*_NF@D@!e-j>>H8=H%^8O9T016De{)L>z}7`h!B@}w)XH0lC~_txOR%&G>v@RTHCjo zl8ibZT)@dGyd}d~Ol>-kTSZdS3BVaiuSI|0IWCN~-eX4)4`KexIvw+$wu~PFanS%_FrgA&@0Iw} z6Dz*($AmQUP|V>W+>TjnHlO#n-pg+iV{*@u->w^BwGyF?gv{Az`+_Ykny?!Z+^EA- zJa5Lnt^Z`Ud%OfUsXa^?q3A3=Qp)9ea0br&0MB8S+D|Aq4>U!b&05jg%t>QVedXYDH%%x_%A)mo;SgE-n;Z zRN-p1>;y|gXCJ_N`V!D^`;~~!7->Cq=r+eg);~xuF>5eqU>8qzJ$gpLXbL8N*Ck}R zNuM$q&xgaD>mkyr)5+TF>TKR)!*5h|laBAno_)&k);l&{S>Sd9?acNi(fI|y|hftZso9{&UNzy=CJVtVJi6b z@YtwSu8e+m($n;$!9K~##zGUPmBgKq)4Z8}yNwmq$f6}teOOB6^+@q5IJQF#5B~ z^cUjUmiTF5=Jrub&zWhT-m$d#{?9~I=iPOS^)|QLiH9ydIFyha9uh64jq{-CX6G78 z!S_=1vZj)#k$hTEcYzTIRX~ z%C`R~Uf;v>G+nh@3_p9U3CnH9u==rJbM%DbNn))RyD=FW>X^UT<-@0nU9lbs6Ni*GvWOsU^PXD473MmjK!aqfmGJZ@F+!4 z!C%D|Wks(NtdC9DGM~AX$lYE{LZ<2qhuNwuqvUvM3C_9YWqk6m5FyOjRmt^n#H#HI z9-(fEiYyG|CWb7^{{>1>v^F*053WSGF2|jdqjFDLkl7PKyE(l3T_N9RtRODr1iu}F z4GB>CPUp+`L{!V;*zKmiQV}(B7IC=e>051m5$21L+*i#sN&WYVCPCrAm8R%=*5YNt z4hH_{Z66|hJ*Bgpjme&tM$;(T>??bxbCXQ9n2mPe4oXj9+I*$2wBL4E;FvA%tElR- z!F;-X+XlIvsExBP>fw=K6WnTZ~Xemlx*B4HR+vjZ3yUuGIIN zKwsX^X4JnFYJZqAWta>tkjq&6eaIV`e_t>K9WYYa2mZt0qN`(D|Hs;sV!-F8f^{{Q zVf7B^+JMQ8dS)4Pr&ukF#!~=ST>k*OGci{_0Q;2%ZZvDzSuPqPvE(Jq0HjW-DaW`t~DFX%Z zP-;e@-1nextU!_65;ws(w1(;q2rF^3@4+vSp>85~t#pO;SQDoU)b3X2mzYWHpR}?uOh2d+4I^ zg_+k=36^N2-%l7Qu*FE*RdMFq#(J$DUm(-VNS)0f79?_u0xJKK4m zb66E~%2P7lDF+8(bqAp1*a$MH276nv2U)WbB%KQrA4xjD$A5`|lQ0*)&vc4D$|1c; z&4xV1TplXwS-|VVH`7K2YuBFDsoN3>#;K|&C1B_Y2J1colu!Vn>d`Uvs2tqUseZD! z$*ob0`KI55F8unETlkHQh+r1|OiB+}E{%BkgA`Hh z;n9oF+-!PQ*Q;AEwv{mog^4dlZj&wNsk@Kg+{|k%c z8?#|M0$P1Tf_Mm@keRx9To(mUE<~&Fiy$t|u3dKpO6au9+?crpQ2tD&a&iL+h1d{d z+2rt-IkDTzXBFv{9t9y)R7MBbvs##Rlnd7={5x z|HpUuY*ijG4fn!qNMzpPhIRqN88=!sE!@UvM{@Z)?blifMjSmlQ>%PyLUu}SI^6;U zTT7KvL$#(tAWBGwmDIN?RS1}bvhl`5G^$8wUkQSWg~qcM#h%7qel2X zWhd3#0lNcy<3?se2~#f1LL}Cr|-|ugEKfxSsdbFDa!Bjmf4rAL%yw z#_RHmFfes#EH}YXCP8^SfvF|+Fo#l-=H~4vZr*ORBa{Hgdz)I#vfrJJE*RsV&?}68 zcGGN~N8bg<9waMa^yA6Gh z01kgCHKsDBx_?`d08f+(XH8+aTNHGSXC2=iJQpw8G>rF;%{lNZS(BIk!dilNO6;>7 z(va~DjFde7%qTMUc0TDzA)-?0g@1!;imgwQ&Gf#y{`fhhM>r7io8$j~66+|wZn139 zK^n@3Xg%1tmMX-_N$RaueE27r14Z^<%p!lpBr?5Zv8o805M$+y5?_Fx zQBlvD*7VgL*UeXkS_a5^8zg?>fPf*^5}#!;ZIc|ch}z2!yP2H9z#{j73Ex0r0lJ@4 z(=UP$^!T?fTV#x9!URNW#e@hZ(~+$E9@Qv=hxfPvFp6zep#=2>R}^S`Pd5E1`VLXm zn+~zox%uBG@_(EPsRCXD-wSv)A{wDPV3#k`dg48GNi}^#+T9;C%zE8l$dvQU;(DWc zzO6fw9#|n+aw`F<>pwm(#tvW#hA=STNfvEb)N*g^52G6XNy>p5f^G^g2(W$P$wmm-N^CcPu_+^7{RO{tLd|$ ziOHn#uQ6UN0ztkSf$-Nd`u`tyu16SyqK)y1m6ynb*;g9r+G0>*YtI8eI6ErjTq@X& z=>GAZ$=@L%E!Izy5#hg!Dy&X{{6HqjG1ni#RPoX^l1W9_7b7FA4y-?{7-|ahAcpJs zg?tB93bwV~i-vDz0V?3*sP3FF1g9vJHLny6)W>!R$p;i{Ze^Rfm9*4^M}g+i;60Pr zc|#-0STI{+h>+45t!Rs0K^W*K(Aus~R#@^zuEIyyrDsaIr?4<3#_#M_2t%etUqLGH zRz2s4J=DgBeLXOEb4cRt=FCL|W57Vmh@IiF@{3UQIEoK6H+d3PP5+-fNn?#qrn!-6 zK#}3fT0^1rs2i|K)gJ@mv(XStFOe()*4NGT3n5e zi8T-JkJhs#dF)tM;=*ocr;&Yfp@V}RAI#cvd$ixbMfb>)@`KR*Dc2hzl+_kv65Olz zLnAl@!L{RP<7_+drqFL9!JLP;_j>a;RjPV<#@?c8dZcOSc|bp(dRbt}5i}p`U=t91 z&KI5WngO37E4?QIloVt_dRMF-bk&f2`P4c^pSMuA$ z%BqSSmE!6kx;e$FEFzsPGV~uRR@#k~AhMR*Cvj1+RxBaVg3z7J-2I=Ci!cK-Ix{*9 z&mJ#|T!Awp`7dQY1#u>e~(vb+J#qccBi_vD^TN zt!G|R{isl&HesTZ6by2L0k05q9W;_FY&SkxQa$orK-F4XxlQ0X(;Ghd7f;A$H!}9E z5dYP8|1iu{JR#l;jyKO8R{Q(}zoew2NZA+2{KdowhmnkXrq@{9NVM(*_Xug3yk+Yz zpR%@0oGmaznxPRc7khyz|5G6S-|5zYf-AH~JTTgxPsLOvuFGR!!fBg1!?J9>7xm|-$;UXG>&^CH-34pX58 z{$=|_(TgZ#aYA-2*rsTM$nzojN!o1Vcv5vj41 zW5a{ph%?v+h+Ha?hBh<%4Y(OWHH8v2#OgzdV3w&$&sz}vqKyEi3 zR}?4LlB88W$@*F{*Vx%os+On>L+t7G&nTKE%n@z>_N7oW(Z?TSwHz?p^-OJ}RbD-) z+f^(R>dsTtdR)R*-1MEUPxm~Yz)tS zrORVw>-bmNUJjoj?-;IYA|3fE2miN`$Al%YQ6v_z$@S7NcJ8n5S6Z{)aD1Sb9}XTE zRLk$Qtd|Fnl#U?E;t=SvyrSP~&RQlEIjqi_=n|(aMtO>OS$npjFE+bsdRO4TUz|At zM!#PCRQMZpKJgTYN|{nUsq|Pm@Q|ema8Cht)lCT3op~e5j2cF;Tv|@Q;kG&l-5IWs zRC@Oxr&(00nKMVudvo&LGfb8QLe9pH)u=Mt_DN26FO8&X-z%^3GKaY`1 zh*A|}YA75_orygLSX(VOuvLnxC;yJcA6r`TBbw1>1#d9hylWCAWKq2y42$#qEp<>H z9eX-f-z71pAKclsMwp{WGW+feVU$xs9>$w6;CP56lw0eID8Pkhv;o)$DWhF~Ipp4O zXeEn=1H~6)pecW0Gr!DY^f-Zw(~oflv;w4&Py=)(Dc=uTWE9T=>Li8o*u=Pb^Eha% zgj93F=qamNtKeKW)ecsr3_0$Fy0^wMMz$|h;zkA|(uZVBLW_r^ZMs(``bt*$ql<}A zuEk%#@sDIL^xGR9mu3Twd2*lcZ?{-i^npRCM((a&MP)`sB9{hp`<1Tyd~L)k?FRTi z*}PKcK20*@hwN^w8%r|NK4-q{xojkY(?gcpL(A)da`o!~g+Q(y;*yx=?yDEFpVVWn z^~$5oB2J{T9D>F3B#FK7^mt-2-?5QvGWT%X5!lu?@$gt^NP6RSI6o=&s7_cV+P=Yl zvfJpeU;sG2;0)~an+H8M``sty&aONR2Gx$(CPr73r&0upcPN2#-=k>Op1``g+hz0@ zR%b%fRr2H2)XF2M`jy+()=m*-$J3jMo3i7swYy`5O-zASSJd3ZQ`;Y`9wX`>W zu0H#K^UM>LBIpkvUXKQWrcxbbBD$0)jpeer;hGs#;YeV0@~~b({((U6TPg6oNG|b4 z=D|>5o-ralhl7bp$g+w{i$j<8)iqdRH4bfnh1!L^GuH+8)sr>3%|bsY9{sw|q7>Z) z`w&>Qd<~l5H+TAB3K2?abMM2V4pH+U3F>lHs7_D%9K0g@!1xMaz5(>JmBTg5eIrkL zJwWuH**QslRkRMwyoc6%afFpzSK?M27xp6089Aidb@o;BD8YxaTJGXO4=Rza96QU@ zc`+OkOl|!h1%vETq0#|UU6Ya3iJ&shz`twmCw{25ZDn{u&~51Pu>?{R zW0TWaN9{i-v{)R8tfe;P0X6?^3tkdY3>URES8?T^$J0WNW zOq1Br51Pz=44d$p2$)oCWi`GT!FZvACOmM%d4(5RkS*dhYMUxcV9EDMm7C ziC!^7HYk$05YOZFoWqD+u?3`B6yI{GW8f1im&^Z;7Qo9WA!ebZ0TAQO7uxHAxvtfY`+m;D$Mdx{ zt^@>0oLHsba;`*C+uoS-~MGRkG9 z(1LChrp={!alC`qI=d4?7!y3LmHJOK6&83z@2<%~n)i#>dk*CdX{?u&)oyRa<90rY zN+9n;vAVOe&}idwrxR$2u}?3MRI}i2qFsxGe&Tu1^)rfxBg7^Ta;$HQ-AxmmZ0%$N zA3l=q0p-1(h;ZveDtX$XCBYa|cWl+Sw231Mt*~*sp~jEHpVvx&mye}$ML(Ckc~PDz zSb^&id zt+V6}sW@ukSRC~@rf}wjx<(!|2X5Q6v7%pBjB!I8Q%x2F_M%L8ANR>s4{{z0r&><} zQEe;E_ql7&oM;c}$eBv~avc!@=ZUcjL~)phW8l$ZPqgVy--b{KrwvreByqT3R^q;E z#q@Cu7rNLb0M%|slf8xD*OU|-m^@+DVVH27KMCC=YvQJXu1aQ-7@@KMy4t0a*dGL=}l zCe!7F|1$6Vh0+qLY`7yP;!pgMEfc!3!RC9qhMW(->B37a(qvop6SGLX_B`5&h?$B1 zdK?J2?__&fD<@W^HVJn<+@XdU&J}nRM-ApZvsF3Af?y4seZ38cVq@koXD3$qvVP;; zpH-f|8)2mG6TXW{t%L#VU2TTT+l?z#%jW_-*d70Q>D_a5UT+@LKi&O3PT>r2MXleh z6#M;!nk*PgIKT2Niiv}ECf-*tjB}~7KQ^-|;%g$c1?%O+4B0CB@9(5Jf`XEc$Sr37 zCOQ>v?A0<0X7u{Q`Ooy~e$zzkyW#|`XO_kzUy(WX@psY)N7`7~ysQlCdcM|pM|1;} zzN^Fub(^RI1GPf905f9#{bElCs>Wd`XznJ7+6%HbW>JB99fpFF+w`=n!cI^oy)yP+ zfc;?0H*R3Sk8bLhV?=DCBV@cunEFhw$l;ns>og_x(ilOaKxNb!bG9{kPTzs#vJ!P6 zeVn9FJVA3}3<14@7--*Jg_B+NNccm5&shPfD3Zve6Pcj9x53;I<{fkHOiZ>;{oJaD zFzDVZ{|vRY{UgitVn4XGfPl&Y<_hv#dpP!LWtP}bWo?mYeFC+a-lzD@auYtDjiUJ# zWUiLC{f%*kiR~fdj!f?FcRc3WuNvBx){x_uzT|m-vGg(5$pzgohA7@|#qG3uL?E>9 zTX%m2Jv7-&9Q&jij}GY175}OY!?N$733PnP@coXs((@8+d$uL9pa5m$o?-SVMHHdMJUjyyc?15?_kZ`5VaZSBZ>g(+WGVlyVHgQ$ z@$et&4YyQuEN7Q3HaRy~fmG=3dNPhNS>Uc&emzpg*KHaScmnefmtIJaN2eIZ{2l(v zIx$dJG+8+opodAq>=?DiD|h36rJ2j37MM!YA?x)NAzI$HkIkIya6L5^;|!iM=)7#V zk*K>H)a}UkJG52(BP^Kw_W=wwV{{Gop`LZV&5cV=0aMIs+v@uLFkO3m!!an%{_q2w znWdAfvNnN@`Yw&7xkz_nTl(2&^xwMYZxFeUoxPiEXu2M2Yr42ne8Pw%3q0_h>%y8x zml@QR3^KsIneg8h^ zL%{jJC~rZ{oW~H$R*=C8X#dRO8@gaq(K}azcsNi3JsFxhn6Vz3${V^duqGz;!I(25 zv{=BmuPuKkuGK96Z|*&Rbw=7AL-Lvivo$oq>R0ti&_r7sPnWL@idf>IZiVw<(b+w- zc4Opre0E_v?-i!N55#dGp}z~NBto=$Hfa@ZEs>~f60wtV7kg~Kc599QURP|R56#B( z2DZ0?v6H)Ie6-J;GP5XY)&21_@ab;tpfkZ^mEXi^vA(oTVUsH00umfaeb297Wa!GR zmz^uPlkMl!4}9;yRi}gj#Ik&t73i8YrZ{-SAk8v8Ub`QdvyZ8C4;@rYZwyB`cK^+D z4mvQ*j`5w%r*Z8|0Lpz>PZlTlvh+a<%9Ou}n`-L387n{Us&CsQiK`f%^y)l6I==If zay@h%^pbU6;N}DHDv<{B`~c0^HNId2S*;hCwXnTu0E;l6 z1w8q!m8rmo9Efy0FSrO^+E^LQE##HGHSjDvWSi9Z-Gg`j8PjRYDo^#=M6E`-mgw4~QRCkNO6b{0>=rSP{0vNsYg$ z;#AwXYv5PCD!gWg&Rb9Q7ti}BrKuT%&`tr@qPG>pY5Wz^z05A=!pXiGrYTD7k;#`` zCog0VXRIWuYz?h{C!L)&f7SS_fFaKQ^wo^&hebN39FTQG2Jtbe`e}(;z z?YZ+k2FA5C%zy4abdh0OkcWFq=J`R+anJD9K^fjj)UCn05PqlJOTCc4HWl) z|1C^;io64s*@RY5C~ZoV5HspsD$DLD`Bv2ySCcp@)ZWK{=p2NTOz^Jzb$q~t$eKx3 zAPZ>Yl`NHdb|B>opIu9LQOr}ywF}wS2+_^S2KkG`D9*iDVLL_CSUFb5T-E)5UJ-4} z4Q)@~sTVug?4{Pua-G?7RJ~zStz|jhU+`aIMtXH#vaduvUbVdax4i2s&}{B?veu^R zmASNL?s@v-%MhTj<7DNeoNsmQ=Bmjsx%F`)S2XW#!Sot|R&FVBR1kHB1Bdi&IlwO zequcF|?M%O5TX|(V|Col^V93t9e=Nn;&haZ*St7rqNa2(p4c80sjt69!6q4JmpXU z-QU}CpAe>{k)^&d$s4u%*YW1ZGvCKD1w3hP4sD(vYFBk6HTuMV$#8#gL*V40Z6%;K z9byuER@eC)Tw8aMG0xXJIBSpZ>-<&^Rk5qRDi&-}nN%g7Fjl zFJT3Ct32)7`o^jXz>xd19S^B)o7Hx#-+Kuq{A2GFXbPh`Q?bqLDJ_|Ch1d*T&;BLl zA`jv6OZo!mOy(_C|J=c}h*|GLg89H@dKw){ZByi8(6P#}wGVO3Zz+Vw>RjjS!!75% z(rU27kqNRjsPxuCgV$7O^&{f?;5#^Z?eajtV<+dK-@x~}K;nwnmZx~8?r0{M@Xlqud^__aUms4URo zy^-((-JNKOWU3V-nrHR#E*f1lCDf@$op1cA8# zq0>MGom28``D*k;5 zL5^!o8c0Z6`}ZuY|J+|JH(~?zLu`EvfAC66u4?-L=C9vdK;2#jIbEbJk*gh3S95f$ zq(&8n%`u=gBR|Twa#>{-Dh?(Je2VADRyYWt0@oa@|&W3s<^XMQNLNaTW%7$nbx$IAKp%|QrN68 z)lUTm$XqAxOFmt!|Ai4>AmjWC+h!b{1d?(>kk4(2U-#v?VyI=e)&rxH+XhSEx|uv^ z1i$f_hxFf4-hyT2kmjvLyzDAOkLWaAHK1MOs5D3&;+ura+#|X%8MjOQw!<$I2KpUr zh5zBA)2pNIZfEYn!m+z;?C4l6Nmvp8n%`%s;oY-1V~%lKTWZJY4KV2mCWf?~I3$qR zJYB55`JHuZ46MG3f<|3y8ALGFb%gVnsODY2~& zK_*E!icV@}WdXi*K`lv$=a+DIK9``BrNB&o`lSHz90JacO_K&C0ydKlj{c^C&&F_b z?&fYDv_pQGKE$m3kv@eCfDf+ALp#QkZ?shef5CY4!LVWFAo!b=iN{S&?SN*5#U#(K zXxBwkUGopC&W{E)*QL1+H^;GFE%xkt>L-sIB%BAUnb=1>xpz$~70!Nl86{~yqV6(u zJKFvBa332t#RQWMlT^eRon1TiJtPh9H%Y})hfx-PM2A6#Xla~xZKUo`7a6*`?Gd79 z9~*pb?SWSlqYOs}J{C1%%)%rU)x-r7cM>Wc+k8v;;~ zZ~e%a<#uYFe5$zKgKF%d6pTNM<9py8;bRG8>tBVq%P+8H+E>IpMBuabXKxwcL+||B zB_;R23K$jsKdp@rp-QYrHAXH9EDsvDyN;Zou#f*#6K>ydv4=ByrwxBn7alXh7B?Ks zFs0d4d$3}}yoLplyvO(r*kz(|8JggE%UNjSV@{4?Y3O7WBI*C0FI5OmE{Fxt*A?hD z|HT{ryxx`uVUkqwG|RAhLu~b@VfoK zac1@1wC%4(-5x?FEXUS$KCfN-MWZ!61w8 zX>Y<9bV&cSv~u{SY2O5O;`~Q%cd510BC;B$Lb!3@18Q(_GeedoPElU z1G|DwjwfhPX%wMVfw19})pYW{;IpN*J&u*{)K@=z#Y{)fA`p+rIA1-$(AwPOGg5ur zqit#RF&4u_{013E!qsKPAGTc= zntSsmA&9h()yz}3-x{&vVgt8iQ0d*G5O7=n_R~(VkV~kOSFs3*Q}6o!lC-^x_(LzZw%;RpiKL_pi_!$1FEj+&mLHXU5cb|2xh8}so?!cLZWlvf?wo~Ee~ zen%~~)V>=?q)%ai{f$^=^>o|Ieb8D6N65+NVCl_^bIAA0IZBK-G9D*Ek9PqWBhJrR z--FP9o?fx*`b2R>JvZkfbkH;RdRLZ-QD6|CQ#5(f-=lCwcXln#3WV$z;z;hF>OB0+SyB==Brnf43*l3{V|Vh%RDw9Iifw*ERc-N@J`FtW$JQmY<}AKf3#`jr zt6eoPIx0;1Qw|?vI4c6KJv~lToj5^;yC?2@I4?bT_5x2qC!A`5Ntt0M7Br^Z38*(8 zz8kJyd9KjY+y@IEl}tcK{J0%2WCU9Dm9LUZ@%Ms=qV5|C+!P%vKt%6zx1Jb>2kl&vVcz~~6L=RBUR1i>F?VH@JrS@ERqPUH$9bJW zPy?gz@SD6Mb>XMy3HhCL%C;4Gzl%c#(y^MAr|8zJvjaH=lPfl^q0a50noJ*)sU(|u zx{!ra5wGX7U&k4?Ln)M*f_+6F{gD|W0311ib^P+JVGStDVGbizH>B!(i(s^u^{m4O z&e7F_);*TNMquXi9^0ES0t|$)^L?WCpOL_Ogk{o*;T}-G7%y_)V<9}#7LJUCo{9I^ zKIE%(UB_Tg8#b zW0S>bXM&&8bHAW5sMHZt~mKJl!9LX2ldG6@U{`5o~#TYz@uZAIAnOVdZ=eul+ zcv?^m`cui4SAOK|ztq+?__&apTGZa}+CWzh(PQ9c71D1pY+L9iSw*}-QFKSueNtBb z`8ao(`T4+{B8b~Sf%0XOYm7%UiIH%53gq7ZqL*g>_)?^6Zslk&u-$LXZ&m*ch=4!e z6PSMZpdyOg$B?_DSVGsdFIEn~s3%-cJ-ML-B4oAG>mx_wmN#ctK-9OV3R*4y=>BYx zxjpns_?Q7@Ac}!wU1($X>8wru`FHn7kX;48y>}g2?V<1{jFQR^2jIqx*0IIzTq&r6 zhNdw?$=&(~i_lw+(*nZ)9(ViE^sVhBCazUD_3iGUr>@8zymm3kO?70h@RY!-)BYw% z%<-?aNiAD(tm>?GO*u9^c31x4GtA*~KE-7G`YhExaxVM$s({DJ>w2k1e)c0fvC|bP^}8;3 zs=p_M69zw*EB_Z&Z{ZeY*tCr=AYBSdmw+Gwl9CIHA_5|z(v5UW2@4A>QX(y#i`0U2 zcZqa&vvlW@3%l%(=X>7ocz@qNa39BY&v{)lbI!~;GF!>jC~mEcf5*<15j(H1FoF8G zUyw(J{@ZC2W<@?+@7$|&>4Dy}Bb+Ti<2F5PT3c=DtXo}+l(qH}>nxYOBu9+=m0Mdl zB-`Rzo?&kqXRMUxnVX89F}Y)=n3nq4TXS6p`}B}05w|9qNe!ySS0{D&sUE1`Z39%t zfAMn6eE-~N4Bt2;X{&dKTfsKjF()Dk)$r~=3(xF|gyOR!A%G+PmkI&OA-M2krZb;zB+sY^uv z*bl8MLM9%U?;b1qIR^4z?&}G>PuWRWAl1<89&xbsE z7)@b-2rTnX9%n_)$=NsxRDwS%Je_IS&NOnG8F?GzF)n3df)MqZ=p<1-FkIH3MgQ#!M zg?Vguc_`x^SX2hrd2}E9A2;pS_u3oo>pu%T?>fbMtLJ}@w~+fQ|*jFEQ7DG6~T-FCQ5B;;}IINehZ z`tagA(F~gbE*kOXKGhcyZWxpaq_1nqVELSNY8vvqsHD^Rv|2jton!I2YM|SQY}1}_ z(UH&cX|3JDOzY92*T&;xhM8vVc`QG#Ors&ss-iX{4fgsqw$A6Y=&mC16PUyF6Tv^J z+6zkm+d4btSn9kF4qZilTWxo4%9se5hy7Y67Gd$ktDkQ3QT?iEE>x2l$KS5{?{r!r zHw7bMLTr}gK{-C?f9l^tx1}E>*7C!dKxcjGnr$<1gm=}Kh8t^Q1`XcG-H?T8WPYK7 zM|rGIv$c^G5yl`Okt$yuEbI zwNT!2^6F{*Q%>Asyf8o!`cSzwlKEnbZG<<$Jp1dw*Ia{v_r-49yc!N~IGq8~%&|*u zyL`(mQu?eCb2A(XWC2H2{KoLB=L z(r_m_K*qS)nUN~)FKlX3lnomwk~p*Qnw&aG|XJi zkVKiPhBqd-SCaoli$XMCL+@KWB1P6a7`Bi+L5?|t4KA1*KO1xv%jerLbBy+zHe~$> z#+9Jps;R6~E=g2=@Lco{5aIvQH#LM`}E5QG#AYL%X)t{whXWk>hfC6SqKNW zb!y10nZ~4{qFz1l#38O_-^#balCQO7aT<)AI0CQirU_W0z=qM*shuKkCm=HG}T{vktXXx%HeU3u( zW(G>F+7=J~+}(pcvz6017O$HY;MrRXc*m?@0<<+)7rNedEHPKjR|sI6$S0cFP@$N2 z+{||sT^BwK;w9GBO5?;4oz^r}{u%e_Nup0!!v4*oC>zTMEjh{Xc}`Qkloi|Bv;nGiuFWFSZ&7nek{?2CT}^ z>pY(I^kA@MR}dxRoUk2V7Fh%kAe&Cii_bG`4(g(m6L*mW5gPZpsoHpjh;hm-T-$x; za&)GhWOil`@qkBHT$^}Mj9S+uC7!DS=Kp5$H3uKV^;wCMRIO8L5LFh+2Y-V!$8)N&;eWjAsGjF}g-gtW+ z)5qh2I~{=1!C+MEm>V=^!Y?k9d5wQ3(kx!*_gED6>evbtN<5(KF$INn`G5fr#=NW5uexnl9`tG6*gWC9InUeXIobykSjgxQsV{@0f z?i^>zPJJoHy0W&MZK9?FDcR^&>(9wpwfR<(pS|-N{GlB%u?ja=ia*3IrCd}~=zfnQ z){AyVR!#7SWvg||vOY+d0tqWX>Z|bBcqqIif8x@LEM?HKkMnI);W;74lj?m;;8@FQ;You)LsRPpjiS-^7@fqV+a9=LL0jR>;>;@!=! z!mUqr4Q{F#nO5VU@wYZOAOhZHnXW|(E;GwmC^j+VMpW;Gzz0TxpCq%vs9v8h!g6*T za1*}lSmjk+SK!C}SPf4|7u{8}man5rr|+En&`@JH6xmzwV4x@Vfz-!-_23?9$`15s?U?N z%8|LEgFs14si_jFD;^ouPfX9ZQ)QkIG5G8>sz26wXHU=d8YD1^sO4dWR&>2bC2XtXxIsrOR#IoS$Z@$v8d}=I&^qBz{BLD(h1dr&c;)Hyo`s z--_p>FG^Ly9=b7GW!kZ^$l$~b6s`MWoNi!djX{hqfTMMh z!na%yk&LP5t%7?umyZfOrh#=Fig91J(Rd5);%a{{wrk+5WORvjV&Jx~9bWXG6`jSo zPB}T)n(0!=Vk6PxWnK;3Nl0z=O~ivsZa1nS|IG(n+OF=CA6sJ0crXzs%Ud<#Z&bo2 zuGCxj*3{XBz0+J577)Los3bSnDDq`T`tTOwVY52E4|j>5qoRh3@9Z>s4ncIl;UE7Z zhqY}>2?~O=^(iMy-Q@YPwzB4(eAv5E1VVJb5gotPt=p_HMBaAtSv1F7<#Ps8si^qD z4Huz7l)a$FqpWWq+aDK+%hjbvJW}bXz34lEUN80(0=$4BlI{YfUV!CXuWO&Kb7X4QpYz{cCiD$Kw@BE+!)coGK z{dour!9o{G^iO-9Xh;IS%n{#_AJ=R5zc>7>*Tgf>w)#Dl_5Bx2UsY*lFmUlsvq>M8@*Bygm_St zDgVJhM4n9KFBq1-v(7W}bawM-13*p0s69sRGznuLW37;AXy#pszuUnCP{e_pSz>Co z_Q*-wX!b~QC2`h^tRZ?aqYk@QBXQW_kkk?mRn|E358t%>Yo5TV_*l;~{ja*v$Ddy+`Otn|(d~UQ=3s&a%kC)l3iQ@f)V7-uY;nqI) z1CBuQ@k=%dQnX7n#HK|kF0mDke6^-yHbGl0+sWW>miU=qk9e-7Bh9LzkE1m)^!HWE ztbd1b0<)c z|AoHhDxowJUn);@V!Ir+kj1jb*_hCqJR(_Gq5V~DZ#I`i;n}Da>!H3G#fkbpFTk8_ z0KEJOub5#aRUoX=ru#E6`zDOnc=j19NbGj-Vxg!oYcgRFXETAK4{xpntuA-5!+(`I6~sdQ5Gg7&eLEoM#s*of`hAAan5qB*oJ7iJ#>!riFeAPs8_&ny)rQ!9D|~-nzu8K3(jUUAg&h0-k4lC78-qy^E(=!39-)Y_vgH# zTn)}s!O}P8bliV4bEPvc_n4VEwr2B^x%r6ISkH`)RYN;(zdAww^>TO2W{*G8`bEPw za9?2WEd_PPQpIEGk-K}fw_X$AZuLAzy!?_SBMY!(<$f_o&=nT*A9LINwAgVW$M5$1^48GFJQ})%UHGe1(}~C z*}fB0{Uy$~m_FevpA~u6)S-pF_@@`=%Wc@u8d}`1u>05V{v40YHqAjQFoDAi0JC&AoylZ(s~|W z$+o>I_}yzJWZPW-f=4$N;51K0OouDig7csq`r=>XUOZKML-3)m_vnL&p`8|RL}^@N z8XyP?JSnw7Lj~BJpPujbQ5rvGf|Hak}BZPg1e4tRVO@@P~=z)oEW1 zKT7xINY`K5(I|?2-2tMkVok;osASebIK0Cz&wknk<*4nrn$Un|Nnj1X$TU2CQi|Eg z;Wt|40yQDss&j?owX@@;N7V=kb~m?tem6e6BupTq;QONX#cTS^IHoG)ctK{p&p%UL z61K2(7v=dL?g#O>H1>s|rbHU8L6|;|(yG@BMf!=XfdJKV2b}h^SP@^xY41MLyL^&C z9a+kecJs68U7UMvYuxt96n)sEnV(sk?ZotAjv$==He!QlvtXQQ zsKJ01~iCM1dz+RmsUxSt7RyKABLPij&{9o0MfO} zYN5Q?2d4u#IblQok{*kk)a|JsmbpguW*#)eqH+HA5XQ?K^{JPXE!?5jSr<@W;G1;) zTLy^hcfN0lBnT-o3d;@vbqjE2V-XM5tU9wZ3eRRY{?0K9RSH-fhND;CX0^+pZmXP0 z)qCcs40c+ruU{*BSg^L~7$QI@J&c?Z@V7m^JHBdd>TgebVtV5SLuq;<5ExL`ncj$x zpJ&m+C98C*rW)Vf{7%{3jq`L;V8X@By#pD}w4M_qzV;IflMXqzRfoJeg{;W4e%zj) zAO!n$)q%!}TUb~KmKR&sGQny$Tqnh| zN_rehCQKmE!vp~z$8%EK;L9LX4Bn0Ltq5riN7(r^!X{J}Q^82Zyy+313}h-lnVZkBM#JHnq>yP9Cc$ zR&j>`3Ko#fPyOU(9v0vl09a2F*c3wK*m1SviZd!ram!^NE#h^^XS&Qs2jLOvS@w;N z&kC%>Gi~tpJuZ2>T-it@J)69p7c<9E2)`!eJhz_3At*xtr|g2jZ>b>iRJxP#~F7{ zQtm3cyX$&Y-}cNIXLKKNy>%l;oU*{6fAmUekLi5A^lUV#^|}kWOV}-2qP&u=Aih#{ z1ja1>z+Vb6IK9RT5O13*u}TD_dtDUGaP@T0c-+>i@Ornrq7%wqkB007UQEhrKP?ti zO7G5L;=(9j%L>eWP4&f(>h(Fn995YpnZ%ewf*4Z@pvHz$#7O9o3vmlwot@Wt}PdDd0?+`5*|47;i&1H^g$d)DYKn+~a8b#i-S z8f|fQ!RAblm{;m#Jih6908jPrx;GPAMGa5@xIER!M<3aP^JH!`_$q#aWk!a}WSAL# zYgd$_S>wJF`)Ynh+gJHw6IFRXe52Od{i^eR;5m>b^h!+A^qHzHKcWiINJN|)%<-oo!`2djI`RAVode3U1d)_ z%NWRS%PzgF#h-e2$;D%H%5cM+)^5A;oyZBE8HpMTzAnxB%_MW!OoaLV^h_?cbC+-# zH7!0ZNXnAm@_G3~uh@He$?le8UX zN%a^v9xs=?x#nx9{pZCm(GZV`ss;3nm)>7ej(Bb)7`cc|{Ul%j!NUTByq zqZut_c*|v%jb+X}bJKJBnW%TjOr{;JVO2C$KXLF%T4;BowN6FAPyD=PgO8ND;?!E- zdA2YvWH84pMxpVA7wigh8C@*7F^%h^4a90CEo1syv=4(qYY2>af9X4UzG|)tS7bG)b>Jt}S?@Es z8&Sr3cI&h=5$9qee~`n+lQ2OQ+9fC)!Sej|%t|J!jfFBDW^R%~I9)EKfRq|7U2& z-R&Yyt&n_pFTj<4Ixx|bk?Z!GmzCp|?By(P-mqZ{(!7&DqgMD?rdR)K?8?x;6cfz* zz13E`x|x9nv?Bb@54p}-5}Dk8^pi*0d9s=QrZ-eJ-gUuv+EwLc2r|ObcPsW4!hjJ@ z<15ya_s4m@&SOKo-{NY%FbNqtnZ{*5fcSJvf2u>J*c?ox?uaDEp5LT5-Ax{uMS~5m z+}f@OL@);7yo&+xIc|&mPo!=K%d9FJ;nFCEnS!|L(wJv_V&`dWhS(7Cy`<4RV0J;` z`R0vXbB0(R{&ADn@r`CejkV0W5T*A%A>wNIwir?FGE$l$tqsin7ca=CTV0wp~=UR|Y1eVTrLa}{~HEGfQTawlp`$ab6GFjAm+al&MH+*G^hojL?Lv4lk*6D7kWeNFuaq?cQ6 zhr49+g(M9cO|{jtDCiiuv&h3_Q{<1^CfmFK2~blNH$gJKgdt0ssHyaWV)Qh$%Gkhd zn8b0er-OY!;{$Gd=uldJAWO+y{%80|`I`O4NzBmip%=>=$U%lELUA_OQ5WIXraaK^ zMihjGV6?nDi4EtiK|Ep7MofHCy6uL9cpvsO0Bm^A$5H3|)o#tY(@^3Wf;V>rG|-^s z)TzWT7cUqjuLMSFmW2#F#vu?(WpGX-k7q^D!FN!Zgo*54Nk9N_t}KmX}g^4f=kpT z&sov7axUp6J}i{#q4HTp9}F7E0-~?w;&M*AFP6P@l2cqW5`1_ra(5f{2@EO1W~sBNE0?7A)jD44v!~CZR*s!=r!=yk!e#||Axdz zu_Gp6=zm9!LcmruE4FZ%AWGeffpWJjmuXj9zvOt-@6zJwMW#&CUP->}8w346%W1%* ziwjf0*&xT25uItP+0uMf=EW>w%LV2*MOd&+4T>Vi9drf}CnNq4Zc#QJH`aujfbH5L zb|)|B-~{n0(WI&cIPL2C=~%*Gz>7B2585J$f#;;V^_%Gn)kM6epY#E2Y~Ywm8UEO- z_0I9ZPmAZzDAOje-eMi65J9B|z#yQ$j{Zn*Sg(|y$E=T_m>H(@&LSKCYh1|_4$?-| zr;F5iiA~R`+uMdSqSJ&|dsOwkI8@9T3hTJEH-z+_jszs;U8@c!4tF#%tdLc<6Fi)! zJn^-Y^5h)6mMfTW?!jRY+~zpFwEVgpEBZJ0T8Z*l8Db#isG7em2B^6e6Ray&?S6{b zn%=P6)spRW<9QqMB0P3cB}_h_EA(W&k^Uy`%eafHg<~K}F#0h4th;J^)9tJ!&yDWe zuj07BSBX2V5ed5*$MP(I`}H!zUV}-)oDE5z)I+=z^QCsy1=RLTU;CruGPSjhO#cB~ ztcq{3*LyngUTvN*P^qd5U*N_-YWUAiw8)@q8Bry(BBk{->cjBUdS(GGD7 z312&1JFREmg@EmBzj8<$g}Q7uC3yNcnT5`9#Yrt)kH`76na#cO-xUR)*d58Y#vKL6 zsj1k{(%RN3B`c%r{|k>_i;K->amSQac)S{SDs7jH7iJaH#~*%jV-n)mH59K?|FNNI ziaMX1gzivAd^i0CPW{YkitlgzE;d)lt&J7t6=g9yVjoPwpq6|b91SDyYu?}uXDd2xkz_N!CGNRl3;33XXeEccZhuzchbKfm@x zC$;))qS^qf?AlZ==hYA{0-fZY5Y>kRK9$zYl~*q_?xQzkNbvDV{^|&D`95O^KVd<- zf*_fU_5KXF(`K{5YcID#&thhB-9w!$c4~y(jnIBiCI1;0VmW;Uy6p|;Pfs#KMJI2W zuJYpNfM#5MGND(=nE{Ok8+aeES0AJMx<}xXeKKMqH!rwX%yvmD`!3=BtF-&ZEzBb- z;0O%;2zJ!xqvUp<$tm5L!Sf8W)b%TsNb$^>(`}d5LvS;s2X-%!;*ox(p?+2qdnzIC zN4iA0&JP!Ok;%&wnDCF@0@~burDA+iSMy*yZjLg*?^MUXge#d8$89wPm2p1km`?Lk zgfH#X3luTZ?J2KMZ>30*ys*yvL;G)`ko^qt0rI8w_x=8id;)T$a8XiQH@Z)9$4JC9 z+Vu~ToN=qaN{BHIUke8Vx;(;LOg#8Q329ke_0ZhQ)vkMs_mhY?An3tQB5Hy_a)Q9H zZaPMw)yeiX>TGtfhj3kxE}AfnzMU)CbfEhO+jqdTAXiSJr-f6mf9EAi!Dp7rtTEbC zQgufS+vz@UmV987HOobxwWNz%rssFtbPoTJrdtOq#JiII6l5K((^)qD1u%g+fc=7p za#|hE#+fW64D=5G&8>#t`MiUUYYz5=GZ!}#A8-B<&!W+&%ll{S{w;BsL)dhQ=J91+ zBz`hik=j0`qTa{b2(xd$>uD&16sHGu)IY=rmQ?~yZ1R=oBa*_pz_HcGTF##bu~T=w z`xfMiBo)TM6sZ1a>g@9jM}xR*$4e*j6QF=TN&^Bg`rxZf_E zmda^zA^O@AJqZF7$4i|B`*FJ861ia57DkNW#bv&&zzOfoIfC z*Pg!y<(2DCBm&qElsjhr_S@|)`V%l~Clnn|DT*6w<87eI*m&c-t0iWf(Rs;qoy#rh zuth1cdJK3O&|mGih?gp#K!5NHpkr();POJf2QM|i;38!OC|BuR8)Cv|& zRX3Jvb;*KlF= z{rFr*VLrL5?Hcco7tYnS#Ds{Na+SqScA`|DbA`MBbS9gy7yn0>Z2_RlTEB$Z-4T{%G*rB4PrQeHFgxr(N=RM$#J`^O%0dp@Ut`)|1(Vp> z5zhXc`QR|txsF=^X+!O)pbws+l<~UaCvy#$NT46%h@|nl*3HUT9akCNpG7#6k)hT? zWoB~}k*gD&!xpOhO=t64MExV_sqeQ<2Y(AGz;ntI2A8(t4W+cdE+mS~4Zi$~vwwpp zZ}fL4ocUW?jY}=_TVm$NSMeztoOFXF6dYx0SyKyo@Kez$pco;56G_ z%BA=YjpKS;%G>MK5Hb)MX}_~X0@m>##kC)E#n$Gr1HRuz8X zE1l%_RsVJwXS{?v3y_rz;%7hY$3^E|*{Z3t2Ki(M&M4*Jvy{>lNSfJ?+w%(@clCxA zWxQ*loW{jm>N#GTK4VZ3hxI&8J>xC5UUssr(`y(n@#~gsz*Asm?)NR}Opv6D_`)>l z+ zVYx+>rIPGQ%yRqMoJ&1Hd_pkXakWO`6U*H6l9+sPy!iDxj?EsKoKX1yV`K=ycE6_f z9EAkjIg!~Cca(;_w%N-Y&4WKIp=K5GJTmGNEGmNA7js+EBS+vTtC|G+!a2pT$mL4DKec2}Aowi2hR z!zEkhIC%H!OuoW8+lx#2P{&X{sjP13sZjc___I>b z0^FQ|?@Jc7Ta*BY)m+0rR3*c5o;d&FaBk2Q9H$xA#9TOXrb8w?B_~S4N#mcHiE%XB zbuE#DFRJA@9;O0h^dn`4ZH!E#qRWOx$Hb^C*`Nsr3|4z|n+B$QIr74~Yl=O1wZ~-8 z4?Vcu!}zH(0V{{^VmZS~Yk$RH-h}^F>U28zyY9prQTtJy4~-+>{V@`)RVut;W=;BN zX50Oy?%5Ew3-nX&xWIAcrGGhW{SgCSVLV2mf0gRFY-OwO#JWET)D1%u9yn zBh3rGfu}MXk7elu)mI*kZ<%lf%YNFq1fmZAHcfDPjp56G@L9dxEn)`&D3H~Bo2yU5Z==l4E zTtqj}1f9;>!tc1mhr2ocVQ^xe3+J9#wkkavX$p0aTQGi915jKUz9c5ahf=b+t5KeP zN0Krj4}>`yXk(G_9f7=*4~vTzwa0huiU?%Ke|9FRPCtXhAa8C!#;Q9_3R%tzp$Tmh(j7OZ*z63D>uzN}Ch;5X|iGftJMQ_+!nHyIQk z3~K3`@TC5PZ*eq3?&syKA)^~1`X5C9*SIV+G|$TiA4%FDvHT-yQ3jJnU%uyfa3aJ; zPB7T#E@iPkFYsuz_enypgbFI}7G{AU6%$MpqsvYmepRNNe*0 zF-fmL;OIdWL4N{q7M`~p*XVnK8DMj1g2DZ{-NhxXNN58#c%3E7Nlp@z=#@&lUV9fv zJ6GfP#jK<|w2R9ypIeif0%a~-)Ir~9X}_rc;1ALhjaNi?4jqTxE>4g8Alax}Yq0>V zdmiXhS-+CiFz>HsuL`}Xl=3Ld&&BSI|Gly+tCl!!Tx8IjGYF&bp!4s!`M;kAvDjbf~N&#^@I{Zfl0w}cwbRR~s8 zNA9u2W{N7bm&=HqYd6L6mloZLwY_{^##=QbCDQ96;BEfCJx>DJj@vm3aJup0*d@#zdEJ$;+rKk&V+5m64++e z*kp%#Bi~qc*T5$SwrK*~&}0DQ+egY?q$Wwd8mQBkbJLsRTa5pEKPkCM!32zW zI~46{SLr|+QmLPT;&c+x6PZvq=8sEzqzBW-Su~_StYxyRAPc(CZzzWfobJt>a5xdh z6ZghvG7t2`HvV(1)%qm=+Vrz;9Rik=P2{uinJ2b`;{OXt*=KyFN5f#y)>sSP)@hWv zb>y9UTW27P?clEbN4W^I!i&U_l{wo4Q=9C)2zOortzbYz?F%My4FVe+nRc@P9*2Q) z%bh3T^UoWE4`m3yyy9}j`C-8~J~|8EC_cHz)i7B4~M|zq)kaz zmi*iVLXp`j+|D(O=-v{wUh>KCvhqsisy~cb=@jxBwIimF6}D~|M$xM$E)Z% z;GBtuYWu%5NjJZky=+l>hTny5BaiXypuf`Cz8FXw@+bICSHcFhY-IbL%KX^)>?LHt zzm;RGA4iLa8&`1C1RNnENDzPVT5=A#&y2%#duiCv{z~}yf=Vcqb)@)^y8zf!I^pg@ z0HA&%HS(0@D>+s2d6*ImFJ1Baz4O9b1k2g@F-<8+3sjHAUqA=<&&DG{hw*{TZ#5Q-5@AItXuZJDmJZF6!5r6yAVDk}GV*20SYp?{DXWVlT z!jGSGOB+bi_1^;izWaaN2aQ2HgxE8=jMU)eaD~;u_6Kg4uQ}=Co%apZM;J!dl;ZMkk>kq8OKk)K- z^+&crV8jj7>|cTm(2#r7t1cjJ7lrLW^u@(WkBQd9?WV)aO3BCIFXn1;my$Jm~i zh=w`llp?QDE5%gS!A_2KZpQK0YX{njyMXL zpO+UO{Bhe%>vC)n6b2YKqxz5SjoF(2LSHZ2e9@Y9FC4dtX)igBV?CrAejlwaw*2G6 z$bo3ptK&X$4R73bbH}d!Ok)B|Yo8lO7aVVU|2EOu8o&t!$vmFygRwYYa0`xW`KTff|NrP}#Eah16A{p#5(*9y4ZP!wK%;$A~#o@b#`!M)_VxRwbiW2<630W-% zzmP!lKf{vrnNZM&v|S;Dd>}J2)a7xtdDJoqnadX{Ry87T3Kbr+fy$-IaRP@z0QQ`U z+~6iCjbupR``eE9cw7Oj?U9)Dt5h!wH>?}9OqNJh@(qz(O98-4pdh<8!?A}=dYQo< zjhHiTWLonId}X+7awY%J2l)mr-YTU?G~6vkDoFag>9YEb_gjkF!2pyryWyfnkobx! z;u^M+-U?{s=eLLn8R8?0FUyI0bx1Wc9>y&_0umH>xxU^^`_Jhx4HPVUO86ItRi#jie*D(}I|JKz|8Lx|Vc+NaQVWID8-2=X^)D(e$l0~GwCKz5 zW({xc$F;O8&2k8fuX6%<-LBti+5;(NXC)@b#&QBFx|$MYxc7; zj(rx}mN^>O(&Jj%oq(r(hO51zTx5MBI8JYlbK#1<4%>=GU`n^{ zr>`W3qA@$sqLjcBx#}JpqW?kk%ETn@yLwB-20AWkk&efirk!M4JEZBW@D(jLlyPd& z#^LKBW__l9kf3P#MU2bM*$8+vT{$J56X-_g%L~(m@o*R%?`&I)VrVhn>CIrH{J$ddUcogelqTn zo$rZe0%9T~Y7c^bqQGys)F|E4!Q=ZXFJ01X;T;Vps$t6(rYcY-4Nrb1z2_AzQUuF= z4nE|1hOCbhAoB584flWMJ<;OFKF6?UC$}S@({<{RgNJBEmNWwZK^urhb;n%Xm(wrQ z0jKX9n*P5L!~HMfFkGtw5CvDz3^j~PuOzjO{ADuMNk&E9JfP>}?n6TQ}@zXVh>S2!4krCuKFM zP@xPhELO1P+zOt8dRc%IjYe)ZGFzrM_V9X@&6a{I<5SpVhfn>V+FrNIt~BfX3vuV9 zj!}7csTS=4uktA}bU7HLP(X%r^X>|2Hput3 z@=zY8%U&&cnpEEGD1sQCj57F5Cnl0icn;Vbn1x%!c7H_ewIUx!gHIDmhbMM}b#(Jm zJW5@Q(mlpCC4E_*h=n5_nq7Ru&RN5#4`eg+R~}B&ga4E4o!D9iB!8FSmT4Jo^QW>_ z8~FE$`K2JqbnIvCAh(xl(NV!15_9?MA=UJN`&?Ff^$(PW_7l6;HkVaekGR%p;!)7H-77eM(h5=s*7J$;D6z_?(EPJ zmklM&x1(Xy+^O;|l0k?BzqLHe$1PhzD+MYyhx}f<-F?LlK6wm(^jl$pZA0KLLsB@J zHH!%pS<{x4%(-I(n7?nM!dL%lQfge;_v|EUJI2I3Mex`tCeX7{>d73`fs{NvImfJ^ z+V{a$-5^JVXZkR+)%)A)cy<=#58kwkpZX{&|K|7}l_+k#Ui%JVBUWD8uOXI#dVn5Q zbUR_{OzX{6s`?_t?fvz(|7GTuW!w4LP|MZg1ms?JZwh$NF@JdI!wD$C4Evv#dWs$U zZ7-yOFma%;;|>G@xSBfRka^iKVv~8#5sF)9_+DCIMOIKzbuD?|d+CNH$dnFtb89Tg za2&ntjI}!J20?t?mQmZ{>X>x6_z3uj_7$x$#0BYpC5;$sUcD%@FKxP<-^wQPnWhCc zfw#6nO_KY?Ra^%4y$(1PiA-gFT4!4RyY^|}tf#$OT4m_Nts!S`dAB~og(W_r%Hm3us$NRm&NMgK1W++45$Q)`fhxSlex9d29se*gTUd2?91*>h%o z8e@FQ9Aen!(#g9^T+CVwGw9c$$a#8GN=c%wRbA7?IEU+wMW!TTcT-M_qo`FW zWfo0y8)rHvju7-vM}q>X){Mcrhv$<81*tIto(4y=z;Y6e)XGIQ-wD+!-4D6Dfk4+@($Y%!JkeOj3wGla>r~OiU2bA`*k8ozs7q$ zitX`p@t({{%OsMnTr;^(btV*SJuQqmplF8`%A1qSB$@s=7F{9BBnpY`S1^KYnunP$ zH{yPj4`p`^zhKu-FBfVG{!t?C&+1V5GeSPkkvocGh`$a}iNg9FAVP=M?ODwrregst zZhd*ggOr4H)l-{E*ad-C*i-CX9ld8?UXqwK%%>83ag23Z_P?09OW0ayJ#*$Nc&UDV zi@0jN$JvQVDmQ*RdNT?0X$u>1Pm0fu++}Xf+-+lRGH<&CZ0zgHMJHiiGPC)31hIf#^uOubGC_~F4RRCG;GujMl@ip& zxI_jy7rcc2lO@ty;HYm&hhOvm@@cDk(Eu1Lk#7Yyh^ompV-n+zKDP&$20@8|U_ z4{5Y@IrCT2<8U-2aHgpUsa@Vk^70Y7q?V`H;q z<{N!%zDxRy*_%)h3)~#;*qkcbL1-G}F->@DKrhfU8J^IUw#$hVrZ62Lf-;cQ! zFYu^dMg4Re4jC8PReLm#JjS~qGdu~5J{0kyF+Dg-OV9#>?I?jOq&CCskO0@iZLi7F z=G)iC^_(y`QKm*r;7l_%G^Dqq7Im+)-7YDK%9h@sAFBulG^Ud!~||@5O!*rp2prm z{&&`QZfAcG_n(Jh8f*gH8;xlj;=lv?keCic;7o$GQy0HeyUg!6 zW4p@)%mS-YkI_fRw!@O!>04>77t*Qzs6cFX1JC6l=(4}(75s~fIq7hu_+m_!A=nu_ z!8l>B_Rck3%ERPrxUNM@(@kqu_xAI*$OM}I3G#~Z&&zDtw0xbUAa%bsn=UcO&UYKp z%NYw>gT?7j)PJ5Hy6Bj+)Ud`JC>PzT8kb_AJ zU}G!bBerui>C5y3Lh+}7U)ZfsKf!hE@D&zw4n+6}z}`+kzB!nI-#SpnZqfQ5!PD;B zmcpr&5UdM^e~$f*wq=S?wQY}<&%<91^E0aZ+=#el;|>NOS5_{is!aE?p%ZMp9x{H z@6w5Py7$ZM?}jJ^%mZPznOA69F&H*oz@fAr*}nmn-@v5L9=S=dn5DLtwq4@U-_5ih zmWhL|VE&b;G{kM2SkgUUAd9{;MpvT#*Z1?(H!3e%iot>#(qGI*$(a<_cVCNM#Qq;* zzZdsrgCB-|D?vUyQsQ=mGa`Ll&yhh(+g)EnM$a3O#QL-1z&5gw0NsfW-hby%eJ+OIDY@8)0&5b|M-t08`4>6L2HlnC?s$H1+4XL9o8)+cGQl! zJoo?~5OO5>(QD7wyct*bk2dRQ;4ozS9}UQsS)u!B&ypu%Y4On}Q@DCVrhf-YWU~-> z>k`6h3i#Lc=F1nnZ9#m(-0K!5>4cQWwBVs{k4Eplxq$r%oGnka67{t07~jBy-U;%K zB}VZYC9Fa_76n@0I)N)u>Qe>Z_<@<_+uT=gY5iv~xAm&yf}&aPbyhtR3Dr%)3mpY6 zvbURo33bB^Y;z8HV}hG*pzvbB08fApmF-oy+Jhfkuj*8L*uNX~zGh+GRq8dV{ZcoN z9g-iGHqLmM%q{*k>fP7nd9yha%wYiC_5LH?hX?a32Go-t72EW_Ggs)0t^OXPCxOTe zml^U=&!s6KaxB`Z^~S!f6l{O6a5n+E%UNX1z>|o6A0snO`(Sqag2qqXfmlfLlpU`G zcJYrpA6{SEK07iL7yUuFz0gr?zu)Yag2^p8f{uFgNkziOaF`B=W34AV-$p8uUzDqJ zU-r5q!lawv7!?N^?&?d3_A%T7W9&`h#L9_~CD%H4m(F4)XC5X9;4!{{lIh(lqw@y? zCX!LiD&7Cg>f;5#ZCDvV&t}Yk-SPD8ozcni=Ex~quZ{PL#WNBxS0(Hs+(c{548BNp`&yY4lz6S7}Aq7dSP z&b%A67K`>iHy8F3U)tJjw)HA98|WgU|0Bk|Cc&hOySxqwVoZL6&K!Ny?bwmRxk3MP ze$J7|2`7SDRh6&7DDBWw{_!lbz>!l<0NO*{${QSEl(rnJ>32Jc)BmXp%mPR%1#D7S zJ&L)_`7824c7X>vtmW|AfyE7~ExQfg4694Eh+Fi0a&@cB5GOG59M-CUUwwu@ND|>D zD@i2x|1tFzUQvbp`u7k5N{iAB(%mtXh)9lzGz=gh(j7yCAl=;^(m8Z@Bh3uW(A^C` zp69Idp7$TvYwf-6weN3yuIu6uJAUa>=q-Dp9k-5cKeBN)o(r)@En@eXirA+OwC9c} zeAbkzh)6wWO(o2Dc0iM4j~|J3r18F*e;a-vndFO%2<|b^ryn!0MqK#iCQ4c0Q-`y& z%0|j~L(mqHJWZyMX3>$GF>ghFIf# z+N`huKf^Q%WyjXnW4Sxqn87}qQr=yry0Ip0bkK6ZL10ezGC2YL*|)L_{_KRuhbkIA z<44Bs|D22N5#+n}LdW_QH-&CiFIvJ?_K9$-`1-}SvqEv4g`5_I4;M!6!&Ik5vQ{YV zfPHC0~~u!Pf&}CSi|}Sczr45_O3U? zl7?fSL)#Gf;q0N%jR&n4N>Xd(@~VD&>K~`;kwYrc=O&^S#E09Dk5p|}PK^zn6@8^7 z=Ij)F(>FiIuBTfwh8WRy`tI@rw0LNsn_tRl_ZF*nj=B~c+=TeV7DMxdWo)G74ns54 zvI#b14lNa+dA?g8LZKOXF+K(>1Bi6+_dh_%?9$1ZHOflVH=@v&z|f1XNEv^jK%V?e zS!mq6kyV+kROty|RPD89on{B7RGZs~W5o(#pH+eg8?OzjpAVZ= zOMS6^DesWb5T|Zrvlb5|yifm`AXJ9D3KOmO*Lc`DH5z?I1jgI=+8nTXSo1%U@ z^xzU_=*S~iqA%&X1Lcrj6|{RXv)3(Ia>_V`xDjf`ja7ibk$uSAGaup#`M3LrbGxWW zd@^}A3Q}QZ?2{y!)^|HfqoxwKgV@J6|6BzJ=F-_7tlS%Kw<1l!V|W`A!_W8LxVX1U zT4)PGn3VVt@r)9eW^3^hqAL_<)prEq=PC1k=WC9NOo2ptuyN0=ZT>{CvzE9MkWn0P zvH)`X7O`31_oLuqc?o=<`U9FDn3ye7YGtN&EV`X`^X}9~rka>*%yhUCs70zT zu!TVhOESUYq`He+Wkg;)O@n_k96!Hce5)`6Q;snZL_m=@Owic2@eL0Dy{_Ps(Y>A? zwNx}{Dl20>1LPyp3Y&rF>v12-5%%nlZ1Q+iuh}TghX+a>-6{H@h0{`iQ`ny_==C4n zHY@SRZ;YbK(7I2L3)QM143G5QMD6LncQ5w2bvI3dVV?A^#m|q_n8-R#Ida<{6&^W? z+trNUBslClzZah}J3WkTbG625ld}_R7VGtY7pe5G>gKa+34*UMZ29Up92lYVT4&X! z<65gb&v$d4#o}saNFXSfTh#sggNcT2w4#+tBjc6H(_r&T`Za?<W+dm*>aRE=rl>u6KV+t zXu|Z0BEa6J%Bsg*@HEGbJ&5Qm^o!9$G;v~!><@sy5!)GSdmG0{!zxTw>f9;KIqHc$ z)9zY^)6*;ivY?Z3xZdfC!G`JZ(pilUFR%{Rx5w#b?tTG`D;pCVQ@xB^;&cTbcOsF7TYGB&5t|+A90GB zH&&~XUL^F9dAyfM?UC~A{%328mX$AK&Z8h1u9Umv3CoCgx7IXe>qV}3ilO^@v`=0C zDoGTL{Iie&u}q;=emwRs#HDjZ6`S-Vg??0mX}yn*Rey&D$qVzbiH&~p#zRA-H)xnM z8C5|0(`135$eZdpcQ7Q>73+OJrk6VTWdI?i1GHZ9G~FHqn3f&f0b-ANHU^YY5KK@) z#2^!$kd=KA{dX@5?F0S*p@oR}it?GvrretKl5lo9*&an` z@o(akU{Vy@Oss>u-6fYlg}{NfQ&`0ksQ$_cBM)miLxIym>d0#-+Y(%(W@+1NRACv> zhxmyZ<^j!bxyAOLZ&~f-H1oRDc#=Ioe8I--+lvOnTPKvNlo;!;doGE*{t=lbH@1GT z9CkQ@AB}sjg-$(zPkx;Lx718BLV83A+EGCQ>GWNc>1?Q0F&dFk;8~ZV2-)CPjt!m^ z^#d91lb+hK2HinE9{N8{wbbD95qF)VR&%|N{k%NA-or}4MlILb zr%7;ok}~&5tK1-}4^|~kEa2`^|1lmAJ|5ZMMx=bj1VcY0@V0U5-scUMAV)+ZlOPzo zevbTWpDC6>UNLQZl8RH#H{;MP%rX;5SA#4g?0dfM7jc>@!smPI1UVXF)_nhf_ z=RTZ|UoHU?r#*1JfIm#HDqo3yLI+}D1wD@2l1Md1U{eMzHXsX$?NPb{V{HOnBX;tlT9+j z{~uWqhaRi4`07K;7ipViC%4z=&I@V{jBn3GMA>?J?!V{Ox+}dJtK*!Treka!C{)s9 zidy-YgM*0^2>lia-GM#1K^&iy z>8|us=*h*)E}PpgQ%5-;4nT#b^K;+LZOcVU&TC641J=1iSt5B!2G@qs+-U;O?l&(t zPv;>NWJx2nT>3vCKMo!*ga(9h7%UEBO2O^^Nf za-p#5KRj%$JP))hpLAHS|97vRRp;}RrN8)IU+*kG*KM6%J+1IteQ#(6H13v8pRo7N zv=^m-uuYt|fsJW=mXjEk4UN9lG0^HZ8Tm(tX#A_q_W>6Z5)$9sC)!- z?5+*P*2~7@aI^Io0%AjC&ZUuYR$n9W5+1dt{%9UN-`Gt%WUjbvT>({sL*o$d0bVHx zlSodtPRvwJw>8YvanIT9-W8~;Ea1e&s@s5{QtZ|YH0%ieyvgJ`!|Kggj~4APWjFYM zVS%)L!0xx~1Blbi zZFdu!vdfvyjK$k;90RbYE1RiQ>~4rd?L`?qXs0|tt))9g@8S3c{?joAvitKVn0rEi z1`5sZ)i;s>4()v;POX{a3znVAa0n&Bq>@ZrJdh$P^)+5i2yDBm+o;m`Wa*^r1p*Y8 z@5qi#Dm2mZh)zqjiL%{$#y z!FrhnX)>8%!CdjF_*#&Vz~IT;6`O~Iwau>{@v56wGH>h67ucDE(g61%{o))8S{E%B z!)>(s1I1^GaT;JJr@rQqIghU5)_JwCx`r4}eO=&r~9p|6NY%#eeBB!$?Xc8;y7RgW`W(|5!fcZ8H^Ec}i8c+T3gn`Uo$P zUV^-BTj(|D03N`B@AxHetVSJZD<+_^m+u!9A2#g?q;`GMKxlJBx&SS(Ie?_$Y+Ef4 zrTC4eOzP|O_5KBXiAg~GjSz~_q-l%4LX;JwBrA!s4u2oXv921L{$|W5W+}9nY09@a zt;uEfL^>``<^i}qI@-GO_L#fsb(AIUO>k6X*+8E)B45H*l)b`zbvm;jsjIAbpBZ&x zOnH++vN;uu@-aQY#68&2akRTG^r)>> z`*@DpJWEUvjE7w6y}RuNZM=eA_e^Q<`)f3yhzF6ztbRGE%+2Y-hIN}L&W zk!eS;_?$PS z#Z~envVYMp@MZg%M*H8}wdajY)0eDrjG?KPjqzW;R)ju87o4k?xu?lf+^?O&18^a(wcpC=`(7^_wu9?m&`5%r?YLGP_MS`S8#IvjD`;Lc z8ZVcBjlEU`h1#E*svcAGM=K1t?SqUd>ZeR9{nW;%-&7;q8G8TSb>V`~_a3GVAG7PO zTd!ZN+RPr;=r&1hAe%~(SJC>2eU7S$(_NY45!>(6#E$e&PhGcF8Y^>_Lu-|#Wk2(I z=X8jYueIK&vmF>OBm6}f*Wc`qtlQxkZk1WjkFU=t{ZaC_BYHQ~`>m>#a@B>)04JFI z4GP_7S>>{C?9>$>A&_XG)w;TKmc&%oSesp#n+A1QQKL;d#qvEg831PH-Xv)5=q%{HbqMWkWXJ$^gE- z&jI=*dteA%zU%&h0dSEB^#QB6d=gn*6nAj0_ZFL0vh}h)iPP^-nEzN{z~7q$AV`+79G$pE9kB~<37usw?F>&PK*5;@O7cswS_MOe505WOf+!}*G~CwUHywa zO9Gzaa!N-vykIRkUZ{_Vn9WXFRrS_Y!Y#zUogS$~b+AuxPEyd{h|>)f*FiZdrW0gQ;<} z*~)uW+Wz9bYSV@oZU-U>WI$oME{Ev$E$i%pv-c(4-WOvoTMrkA!dh?F0ii8i?j_g9 z%h9QI8{Sn7A5RLMF4ir#GrAhT#(%&atVoPzRxZDqM#rgJ&UviQmpyfe6%HNu>j}dc zT-k0U2eNr&eXQ7$8)StdC%ijF`H4iXHz7{Zp5jJ$IigXB0^2x?ttZOspR{@sl$8beD-;N zw3U=-ngK?4UrSRGC_FTzoeZYlbXHs@9EZjB0gfd<{NB&r>>yx|sZ*)~FT~Gn?BuEh zQ#xH={=Inu&%G&e$iJ?h2kL0RPJu70KS@yzq_|~YB=Sp6u-(dT+rioJRO->P{$=ZZ zx#&iPIylBi()(Xa21O!qn2TIaJK4R**lFF%Ry%@Zt}jt@ndp8PyNO?2f*nc;%lUzY zHn4JWC-Dn>8=}!e^`?ofKM6O`1v(o=ztlOU@03By+Y|Yc8CQF@eAlZ-t-r;A#?%}>v8Ar8ppG{ zPDfn&e^|FjY{@tjLMB=u40yuB(Yl(>v>MFsnkdBIW#*FOsbCj-&Faaogk;F~f0B-YzCY1idw`?!LhbB4uZEGGJPnlPhS-^l}nplPNP+lxNBkrb;zn?^=B z={L=_A}y)Vx2&^_Gy+T{O1)c|yi|1K`Qylk2ej=gw4FCCE>-qx%%a=ke=}0VW4#y8 zkbNOR=FN~i_i_L^gEd;p0u_4Or6d$_SZn$L&<+?MMl$aY)QKn;$~vBV2ge z#2t)0P*)>7VB2DO3TMYWabr`Y>@GHre8-GM%r#E+>jqRE6as6;h#tS!{tlMu_|vhe zo{XQ6;tcHwM6hH~l%8Mav%ZHvtlDRSn){l?ez+HaHbm9TIeEr8DhI)&)a(533!=@_ zNQ?jMaz|eL^PcpI=|A)bNS*EqAG@mM=}2-(LGlc}HwS7m^MqEXon!Amdh4j{04ih0 zjW~QfDb=*SL;4aGc6_PYz-VQOpFEe)D&wEBsTf zjc|Us?QN&7K7Fe?G)#?Q$8tsPeQ{^8m?TJF74_A$jdXn^YKZ@n*aQ_DVSx{?Z1?3AtG9pZKZ{1P;`PHvtM~+eE)v zDzptLNe1|um0P}7+4dcNd}mOo;wFqI^fREmjWGsgH<`gF`R1$u#TLKzrF?M(!L^CiW$OiovmZ^uxZ;x`of#`Z37{w~(zx1oHoggM40T zPwb9Q>65}W?x^1hRTl#55l*X)dZ}v`psU=C$hTsQ(BMV<%pdQdMw5i^HpSOcEO@NE zcjc>!=h;St0j~+dL>+XIw#dIfL0`VKETs^BX>Ro>AuNQE<#gOR5f8#e`Yx&d&`tqU z1J2T|gldfm{~lkqF0fhq{Yj>meNsUjPROUenxyeg@;&(WO{w|Ft07f?2vN$>R^zd6X4Bxkkj;5!ULXNG9E zc4{c>PQehW$KMY?;$E?y!h6~*e0QK17R-KC+acaI>DPL!u3n0BUIEcjOSnZh&^66 z3l@r!+;{yY#}??+1GRgl@IIz2?dxnyR=daJS+SAfE&=_7IeiivwDP!zbUJt;J)jl( zE*}SPsrNpjZy)$9*YM*Iq-x^3{2tdGX(&bYE84P>gakgo@v?__LAniDO-R{gPcx`OAwKB?X`6HQlnQ!u&fbFKWIBafP_n zmmw3_f=qXg_?6Kmwg?hK>{VA8OpJSNujwGyeIT1(Va@LDRpAm+W$QdCY3RT;wFrI)@*n z4fjv!W5B3-1n&k5deKC57>=7FJWgqTaEj-5S_M|$=KoKowi;IJ11Z5BDh%*fGr({~ z7WX9@^{g}LUDQ7(Z!xg0NJpD$x!M;O+oECAsz2uDd`rYkeyMFp0#SG| zOMp{iTNW$MtQUB=-75Sn;n!E8@jk>vvk#y#47w(mFZQt7nXPzmMY;emQg+xp94{1% zMh5n2EhAviE%9sZSG>;RXr4`=msm5sCFK)>rs%@kUo(2*Ck}OuIAeLv#t1ej?vBk=-EDFlIOiL&nSji zB(SZIIBZEc)ZR8#&N#Xyg{XcP(J@W5n^ig|K5L7#DdaxbJ;|a>Wn&iLbVB>C6KOVI zc|FitLb{Wm`xLUS=*9QRm=baJ6FH)T3i-)09pGc7Q|0-gGxHCjY@t(X!}ah!yB-qf zb+acL2B)mGNf2*kaWrznjOO6bR2WqhM{7mKK=<-1<@89|b+FT?p{r%*H@lwBwuCzg zvdKZqCEqHQv%(_PxG9GmK)BA#Z%s3W^zPT*OYDb5MzB{jo!%&TZN|VMU%pZ9+3?zN zw%}Y3NBQ>o4L^wqUnXlT6N~?!aw?&!gXKK7w!qWDdCc1`LxwA!kAH4V zzGlww@s_Y)@AtXfLf3|Bob)%z6hn*xfa14k<+~>Vh}8Orc{g%u1L+}TK?MH7AQ~64 zlMh=|3Y?Y}iyHmit}9qGY+KXKfcR#rVR8Iz{!Pf##JYu?TureGX&?|s4kRPF3Iz_t zhtnMmN`J;q95I$H+z=si=uyCZKsxGJVJq(cMEHgxlw$Dh7xc{CLqgHFW3MY+g&31! zr&yAJgs3#&<~bmt@P%5N=elLnP+`o@uk4<4tVMMlRZ1Prcw8A*k&uC~>B2iD8=uto zMbK(ZO=ADWaaxo7ZQ#u~IrUEy((eBvJ^ur_Q)&SicD?zn>wg$fLq;x}4c|BvF9EoG z8$H@zbJ%6|FfpthHH8QH1O>IP6Sx+HTE<#lh@vBxj!hX|#fA2Y?ls@@3cdT)Ek7d< zu{DkPl+KuiviD`Y2J>eAXOr^f?M;=gs zPE&oPuDUUcK4305i8N`GM{)ao!-0$VSlaDlZ39H3NDMSjn=!`{HeXmS&-U8TT{gRr)lyCptI}M8v-w`F}c|MnXXK=w?aO z#g>Q=_0xFzN^d6yQWRte$9meXo^*zfid$m}Y4eSFx z6>2Inu3S>Cic_4;CWKG(+AnxXe|C$a*fDm;q!G}E>fXLUgJO^Pk5h!KCxc6DXb zx^oNF|H&v*wL2aHM|_=XX@(z%^r_( zg{!|%J^!Vq9Vo|o5ct5h^HXIFL&X8qcqr1rORe4WTTy&9t=?1Xhpfqt5TM!}lN6m| zrmlj>k!uJgO9@n70&Og5N!XkG0PcYQKwE?xB$7KHIOCJ!rsngK0npy5iU>$`cb zAC#v80D+Y1q9Q9QTzb-5`+nT3MoxVE;dWpRFG5^0qRhXUY`6^=!t zJOm=DMk)kWxH4C`%2w0+=siIwz1=pnmMwj}RcTSQc2uhus@IyATmJQLl;Y$~kqjz- zHW{y3U`rlQ_P(m|IrH=^xnMGx!GcL5{T-&v?1~)=&!Lrr-)Yt%+LG(j>=vjhwD^I|I7m z$DoIj8Uo`lO@PDT1Oa9f_hsg!Dlz++=HN9ZE~R$_vwPeW&5YL}w6T8xmB?l<(EtiK z)JydPCE4$GQns_{=`&n~QOpU_WyWaC_{}pcO|HbcYpSDEkZyB+z2jmBadNn?U#c?g~qL$)+%ovJ&-9#l2jkD8jMA&d#sJ}^i!{EYCa7{=sRKt}c zksN@sg>w%LU{`R}1Vm9Cqux*)n?SxdS@P0^illu&NnX$uK0q{IoqE&7tmO0}57B;Z zJ-k9>0h(Hbvz+YX(i8EE&g9|x#u}~?mLy)Od0O=&(}D;5CFKIqrm-#gn3T+O9cOEi z>N5~+j!T_5;6|YdI_yczNz`rge@t(`-qX zS4@0G$>RDprP7`RGD~R}s};lIc4nHO=&gEuu1)*5;tDuR@kmLon(LR62lE=U^ReZ> zIuAiQ1*KZf$vb2wihHWr6z^AnvRWqN4}O{>vbznscFF~jjQ6hWHB&xJ%%;X{Y;hu? z^Rjy=_Ptlv$@R{sE^f#GtS>0Y=iRT1v_ELusrC5t3~cyldXJdDj`e2+*UF5JeD}c` zKuvlx56Jh4uzJ1iZ#v2UmEgIC7en@_P)_^yRGY9A*%~-5GbP#Fb#DsYAD)hMP-3LO z;hekVCxGa}(d^XY6h0$R{dLF@m_>R?7Wc_WCQ2nD?h9BfA0wbDy{9<+Thmc6CP96zzF@u1=PMg+S(Yt+90f&=2RuhML%8Co&8lk

kTVHWJ~5;W|MqW15v5s%Y;_5-eyduvf}WVpc!i;V{^xARPomX=&2mfXMAj6DT5 z<1V#$)s%CHY&93OnDTrH|3SUZpg^UOj)uc=AIeLzbQVT4;ofQYNP7J3Qr-D&_I>bBqwoBZ-t(~1$M-%S=1t|8jd*@^>57hNL77R3( zmwn!!7QTMn*)}vf+Rtn{Up#N$Ch9)j^5xnc$4Nn6pZv$L65j_u{R6#N`U*n;{8952|yK5K+5zTLl^IBXY` zevIPsf!$R6a2$I|Q=6bwI_iJ~4~xNcXO*sTcR;H|9vFv1U(wxlB9rjI$N`kqF~Z?- zeP?lYW1%_Vx~-}#8G3z+0Yv5(`I5S&$;L+ui79WCr;UB3mJhv-E)2c8BGXe#ym!gB!EtZyKNs?jsRNp0 z2@Yu(APFMd>>Gyxu;5j{&#?mmrOiS7EE6QXxrXjwu`1;YJ#UfY5YHl+YcWw91+~OH z;3 z@=OdPNeJ`9j!Qb}cl1p7^7!-dJ}{uH?Xs~ffxmux_i%oCejexGI+_BRIMMY3Qm~cRrHxS$X+{Hw&gd zUef4Zr&x1VRxB9~MSkJKZ|`upDYw23L{}6(l?|zQ*f$(KTll;;jr^nMFOH~#yorJo zqeU~n%@_8LuohBMR0IKdHnNi`E!&aq?G<@&N4&8;Ri`1>-tJ4Hq3=L(C%`yn0TgH6 zu)RHN-4&c%oan#YDhu}gww8hHcC@&5L``NPSa^ZT&7Jjmll|(knmND7as9%=25aJ4 z)XO=tzM}p8b0~n7rJ(@?OtEj>m|70N5nb1J`J{I(eW+$M*5I|(G&wynn9mGRD@ZO` zhTIGP_6k_!XF{7LQKD@(|`Tw@4~khECs9C%QR2T#qKS^R%P?dhY8(I8yJO3|DN_cQHo;yf(8k3r|tM z@K?U|7LGjII?Q62CP(+L%@|gd>Fc!`UAjNlXh*_uf{FXu>*-VYblg?#HHlU)g5%|G z&3-+0yo~=Cfw3VbQZwAh(D2EpPrRqJIV&r)jgVnhy)GnAKHms5ue<y>^vDj0;^8>LLmk)NR&#S8`ykO?ecl3%&ogwoBi0mBJs0Sw-%OFHG zrE5S5t?%2QQ~e^=0e>9db^7!WYLD)wY99t)HCRloy-zrq>k)n&0osP?Q71oykXc6`ht=egA zrR0wG_6q1_qdpLamh(f8_E#u(F3Z`8mS2o}0M92i%?1XuV{!6ZFm-T_^g_yV3i9*M zB-PP&23tHl8YZU~i7>qi{fYA0IJtzTdUKm7ytN*;<*u|+Aj4W>>T%NHXMV2LW>;S~ z^AU=1wVzWv9$%_T>}>IV^){!HRE~J z!>2fQK6`*!>mKy=gmZ3Fa<_-H2v{*Le|VL{bF_LC=MR!I=wwKqLeQ$jxVS!JzN1`G zEH4&%(MXbL;+O=tcNpb@;UV8?GR>qSFKeGAKNuNX9d|<8N*@?dIJ){EXX5TolP2zc z&UWvfRLmORDWV{@6WiU>DZ^cV!8xX2RguD5@tPVfV-`cr?S`|c`G9EgiwEB?_!sE7 z>ksCaveM>jPql^bjiA0feo2E5xZd7oZmIFWXsJHIBA$t}HZ@J2^S-RMzaLPftQ{s8@mE#A=?NjCL>FNU*Rj!!Jd+=>sg-&uC?U6Le5* zOJOW0p}_08Ig92V)A!SZ3fEN6-sb1#l~7Yr89p3Bv(J=IUS{oJpCxU`F#Sr5Kbyy% zxYh~wJuO*Z@6MP67JiMad=iv}<8v&v5-oDdX-sK+m7dNq*Sy2~d=;o6@!|4B5xWfH zph4UGy&dsp;|zG3AcV@o!V>FxG$(&cM&473&f}`KCE57N&hSq^GQktbKzrwh{qO;l zCi*0Vv`JU4Y#I1+wK!vbX;C87*4W6vfMPaEZ5SLJ5?olE6N!H{nDa@irup$Xy2>-i zeV6K*;JXLkHYU0>zNjcGiw|BLZLPf~8Ubo`7?60pJ$?3yeD`@VwDf%un#ko9!BU9N zMZl7Ov#HRyv)__wmUqk-s*tY&6!y%C?-dml$M#Z|GL@kAIs5w*yZif@bxU*X;bO7& zC{8a2zx~V6c>!$14;liQ=MK=wm*-yf)p=%PmR?q_cseH@wOA2(gytMT6 z{HSN&iiCUtgkBjy!`dMYP1WUt=TIC6g9j-c-d47@rUWGJ5z$Z81=PFa8H*M6&s0dXFFy|+rzKq32-qOp~*7gtE4#X4Q@k;ux4jnToH?Ijs zfV>?B@DxJ~VD+qME_w9*X0H@OhG)Q>qXWZX`|?zPIG;{>%pun zeRGSc#ghgd;C5|T%*XpTx9{4@LYz)d3v2V!i;CD<%;>v1T9?w1jRbCDv$DYZYLP%e zBlXJtyh+k(6uu3&lMyy?prK}btp9&?;*2eA8cDC;1IgFei6oag2590 zMKq+7&{;Li^j;LzRC+`IYkBd7zI@&rgc(t^- z_8NBth|*y&tewg7{Q<^C>+Mk!|J61D^%pJePa2lmBGUN+@P!VHh?8csQ1J{V4bUwj!p|WnQkwG#qc72O^8VNzfkK1k5ut2)+!ucum>_ zORWD81?UgeR5Mblxo|1bC{W$2Aja{aQPH)D0L(Awfh&7!5LZpzlVqc zDY7#`6#THyv)5F)8-rz;hf9dN>!QQO#jIw;L6vBIDKO_4)J{R0k>b7Q(lG3mRNgI| z^5JXRQeNUfNg0C}3}liblTyiRg{2o8x<=bcj(wDF<*MHT%d(q;gPf+~+fI^afGfPA zBenSj1=~R+###9}l-^li&pR=I$1ZDyM5Eou$=yQ#Qe+ap#0d8ruvSf{5^{b;;`!|K z+tx%Sb9H59>iGD$v~sv@!H3TYC&x^SYy-vbit-1zMR^$@fIEhdIGEVj(5<BjcH_rpzSS3nvyNj1qrj;s~{Ic_&@%EB5QJV zokcv!5){;pPkt-!_2tk4;E9q~P#~NPGh+XM4FQB!(p0}4C?04Dc=1A_R!ap9*&nBp z{fdBbXX9Swp+DXw@oST)Vw!ekhRE;b`T4uGZ?xGlyzdAy&dUs8fQySSVfhr7tJ{fL zC?V0J1HLzBmR3%z()aFtt@b#ZAAhMoK_oNryB^EiasA5Q`wIj@t!5l(jBq|R7M4f3 zH%&|-HI3CaCh#J~tdujK&s3Pj$Hpo@Oq#6@34&RT@3J zYrD9S+1=ZV!`8QZQ~qT`#pedJC)OsTbiwo;?k2WHl(IOtf>==Z**o$JEi?|}o#gfTBoBOrKI0kKWRq(Ca`zUX|LBD$ z83EJJENwzd>xW1hz!L~1>aO`_4;B$q+Tj%sgxM@*f6)C*2WrDpU1@&blGZjfl6)3n zJ2zb)z5Bd~g@NHoAWU(xqS#0rT9>G=+CI-br4E4ve^E6vquD_LkO$; zmP-zaze$vkBy(G(vtz%?)3DR8ZKpPEWZ&A$UVKP-n=CP3Ynu;WU9p91(Kfcd9ceRO z_}s(7jGUPH0J8JBwPTAP-J2hK_PfsxMIr?5<>uzTdR*Gc=bbTngtjMR#$j#L{qQ1j z++X;{-3RoDfBmej^;7#|~G6E4$Pw@>e z@A`(|-OCqP&3==<+|Zm?078;(Wx$ysD4{1^k#;cw~X4{ru6rTOwDPplMQ2x+x4A8DRteir?V9Y#{E9kl=I=%V6M_;E)&51YAU zcKm}N9eOI#1h=WyKy#m$lws>qq8~W~Y^LZyGjP)Wj~@rYW>QH=h$;=kqLmDy)a{gN z&a2!pf_-e_l~S0PnBJ8%zaC&65Y3^dUwNBPpKc=HeY?Fq^WDMhBuLd6>1=AryA@+| z`=qNseN0?SOKYjtv*)zkOht63%(&7&Ss(jjZ5=->Bzz<}^eWpVYgAajZu~OsmgQyL zn8(H&?OcLhl$zhok_tqo5m>EZ4HV<8^NWUI5^tx_=t%B`x3a?Xhj(gM;c_7or&TPf z@>%x;9p#-w_}xi0v+S84aJyOz*OoVWe%gJEE_!pKv3tUwdfb6#^m^N-2Gg46`#aT< z6jBWn&p;%N!gZzo4Gpu^*D-pEOGB*R6iob(azxivl-CR(m{ZNm4=k`il|RJMAw|pd zl`x)ISWqM)+Tao5>!*H2k=xj)=kfN0=k|J*YWX7AeUpvV##(KMPDR_xE3>YQ$;{UK zF1WHX%=h5vsBikwXq(ImCOB=>zhUl;xPhVJ_0~psxD>V5>RM|G^aT#qX>r)_ot8>- z?PJtp$?I=lQLWZ&pl8KV>~rPd0Ow`7s=E|)?;HaV9a>x5WZ z4idkIf4A!t#e-I#8FC!u!jY7eB$-n!5R&=I8LeIbSH=K-0(@+9eO=l+KFDbWq?(0^ zsrRajpInu%HZ(|_mzUx8d*`Iep1&C0U2J~!a&+^Fi^Ri3IV;1*0YV+qLjL?#4t`q= z&WV`JQ>;2(BJ4UUQL4;e(Fcqii5Lo))csJc;9D1=)&eb&mceeU!Mn=7Hj%P|_-tO

O#irGm45# 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

=8udW6(Ef- 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

d7h)e+ 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 ij8j>n%Ztlmd7(J5OGqK5mkC`QFNUO9(y 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 literal 0 HcmV?d00001 diff --git a/blog/images/20240814-new-message.png b/blog/images/20240814-new-message.png new file mode 100644 index 0000000000000000000000000000000000000000..69930499e7256cbd1609f9cf7b24b835776df6f1 GIT binary patch literal 261119 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

    eFcT46$BT+MOu}SWKeSOa_$)L_& z=g1=#?Uv16rrfj0<1Ipy|;*eBy?rfQFl(G0!KCn;8C&2BhjU-g>$zabFp zqySOVN-rjgXR-|Vf8&``N7Yac4qRo@DN|1!Usz1=@zC03*ww- zglFq9U)Q(H<^X-&C#b>80r_|5hO7d<65tArUP zOA8HZ?PmK_xy)usN5^nkIXQ-~dp>2XB}Mu{ycF?u($OyvULmO`*^Qn!kfT@-9nC{j z6`SMLUb6HcC6awl*3p5N6eO!W^9i1EGBCJ%ZWlP{4gS_3C{IQ>e>}>Mmdz>ra&zof zMM=q~?Qya0wg)N(rYKV>NcN71Yc?C^&#|*fYFeh>-rq}3mMS})E-5}8DG!)%~x~EN2k2(dO$Q}Y;VC!au?r(*ux`jM1qtf?w`LJ^^V5?Yl16^vt zTSyAf_##?4joM|68NTWkQd^GdsthMW9b8WWFf!*yphM+D@FfV-`#yeM^2%5>|G}Q} zt|N7=wa>1k0E96GCEwd2G>_x(!Gg~jSK0!OZ8l3SioA-h{NOSdypsITXwI(DI=q8E_&bG=(2pO@?5xnX)F{@Cb^r>Cdjcln{IsW=p9onj3gor=B2 zTE{~470?2ps*Xnq%m37M@4e&g9*a$u-pbOu!FN<-w3Ez_l?~88RQB!f8K>S}p*lX}jh4uZI*KM960xo$-GQME-6_e* z(k?E^376B;il}#n%bkDxk&uvvLXb~pS8hOhRKV+c!`hADV3^ToQzw2Vs-_LBaNoZ& z^Qx$-4zDzxA<~S1_`Ru=pbz`elTB%%1J>5_QU1inf;OpF2e?Z`b%m4D_3lA|e zx}6(+2V!fhJdHem*#)3A{x7-Zw{Aeu<7!{6MA{%mb{ zsl=!5c`*d36GW(rcJVGXD~bmsT+@|f@gMldZur0 zq`WFV1`2L3o4|h9Ocu5h8sejCL-&nSKQ$D>X9P}O>j7=84AcFQFVY(B^anWD*eWv{ z>+4WA8S<9a)`3Viztfe{8NaeV;QX*mU(=&|!7nc=@VZ`!q)-a1TxsXpJ%S;zJ}OjEb3y3HNk-Brfx%|?2EEf1QJ z1-8N7oqvqEWxa~tXcLU5?)w>U!8S1Wh)+av^-WGaG#KkFFcVs~&50`y@&|C8L+eRb#B>j5lDiTN{ zYJ{Rzb^Q}Je$7qKbkgT*!^eGq&?(&$_0!h-n4@0d1NE948^s>eSChASx?6dy#?i0O+uHW}!V}93_i}p(4(}vm zuq8iW*FCT^pD4HzE<{?MKX;#!%OTMLJ=|>v0yUVUpWL&+x8KlF2#rDAuT|ci(NFu= zzht>VCXR>uD`ulzJ|XT|EMAN?HaKR=%{q%lT9<1!uST^7K9QKpwzNOXzax~yITX>LW2-atb=R_!X{rgJV+6CS^) zdYWmA1CR?g%*n0^N=j<|xA`A9g2r^s63EnBd+FWj!U7Z7ArilZ6$e6nT0EMRynH?7I{8A!`qFZS2Z_Z4 zX+wrYPhWX$-Fdg_yBQX=%*pyq;E)KM?(%{;woPy1RfapB36|;R7)R1zy{6Sni;+NB zSA_hag?aCwL{yFFG<}9o(Y{l8*`RCkQ zXSd?;ahu6vB%AMSv4#c`V_QR^=UbC|dr28&Hp_K5Ppl?s513*WWaP}pXJ`F=(~9fn znhlGUeP1*7P2ol+hEwNy0Cm)~iYh8WCo0mmw%G~XRB-$G1P*kVivduLp2b_c*&H=l zH4@|awI);wPTee}I9X|FDtVfE@_#NGAD#6xlrcklYmEnql{+^=fBXKyaoBDy}icv<+8@Dkh9 z$QPZ>7tQf;Ix1X4abt`77supJUH;mK0}}GN&tXM0b6huEy_c6Y8NxN*8|M5NFAqfD zb*!zeiOq2QE-Z^1VK(YkYg-mz%Ngbp~oqGQAz7*S^EiIBK zf&j(@rw#iI-%HxC9Y_5-5 zNjlKIDkw!djzeOJeumkP-F9BpyGDvLay}H`h;>ySiTan&Cwak@i9fE_5c%;iRKk zCdm}_Ky@U!jd|d>1ip!hi~xcrs`B%)bVOd4qIjJf`~*(d6!NHYbxygHuj#QFAn=r| z^e*tb@8E9n^~~R%U5-)*=TsmIwp$WDS{=JxY$T>Q8s*6c8+?kCSt^v%(4uF?%}q7qP$5h&og@W<~a*Wu(!Py0RSp{sS!>T=>At7kSKP=TTE`u6r# zv%6mH`jix~bnU6DTRbP4+wK!xp|OqJ6Lw^C%rPt04u};xF!Ii+NO{=YtPG6L%M)%l z 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 WamRl~BuZeQFPU$O64hcxf&T~QHuTy6 literal 0 HcmV?d00001 diff --git a/blog/images/20240814-reachable.png b/blog/images/20240814-reachable.png new file mode 100644 index 0000000000000000000000000000000000000000..dde3c747f9c3034876bc164cb3b2a8d5a035d882 GIT binary patch literal 421662 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νjOrTJjIt<-^54@g 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 z zIJlcnx##28k8>@fPNtM&<(`uOCIu#@If2pVUY+LYB>gGvxU{>1(cnrT zCS{?x89v&Arbzb$LIK#aWnPB%v#Obed!1eE(3uRSQs`1Zc5r|R6U2;-vI-EEOl32kNB=91VfCDcrIMjkow@2^SUqnSM6r~`6 zf&}I$0ioHMu}P%2 zz8Y?%AT)OYN?Pbxgw|Nwx+iO@Y*BlgZGU=`)z;Tq1;BYUEgR+;&J!$TLUWDgJb@|2 zA*`dwhWYR?F;d56!BwZyLMVhYs$w z!)$g@0?5-A?+hbCTH>aUjEq}v&ydwOw%7_5YKn5A-8;22XdJ4A77I#qKMt5ovDk1t z3+)D=-Ul!G{)abN+mc2WlNMP+9h)B|>ulMoRaRP+3>xqvH0auF3Dlh%m_gML)YjtA z4C!dZp+SeJA!_E*plg64Exm>%BWRdqYVt%mPiY-xJ`KQqHVwC#ixW_Yb|bUH3@mmr zz^N%Jw?(mpb&T{`f(5o+PdsRw|LrTb29ssO1EaQ_nUeQ!IMW{M>$LCh+6<6S+xZNh z*z%J$QDg_gtA`ZJ2Y@#6*zz?-8JLzb((Ke5cvKt_Y z3;N22xcJFM0QN_o*l71XbieKIIB2WZtg@D7Y>cxRkvQc|>^RxoHDud1ZMO3-y4;qo zT5Soy^bo7_#evVFCe~siEy9$RS3G)cP!z+@FnQ~tI8A7BF<*|Be#~0h8W?m`SPJes z5R?UwTqjS`go{#>(%?h02V75}JuVfsMPOWu5H#OIIx)(kMxG_i2!jvTsB`xOxU5}6 zDyLInOS16vIe(miPY31Rtj?HSP$!T_f{NTTHf(*AWt;(u7U155sj-Ln$o&eR3!59P zCSGH&?jN=Lki}!Ev{e%}jj7Br=st%Tbi^ocIrW)^pWpeTJD{fAVSB6UZP`oCw>X1#N7EL&S@K9$8AU(|HMSy2O=Zclf?aVVb*rLVDgT{LT zz$oyX#*`w6pFsix0aB9n7?lI04QOWI5h{^^l-3f04G zfg#5sIQ)YQB1Fm&KR-?WhLJ}F_f!`(OP1N2k*T=j+U@gxc;T&?Uc0!a-qyxz?bg9w zyL&WitqkBQ8LaftMh>&Yz1-NqiFg}oYtW`Mu)~IY`i|QI$Y!V(oUE&?#DdNkvn!<% z2o;@@);*c9IxQS#WRz=Z(vs^;&(e`kh@|jUkU&8K3zmSucma?vSbT+m1qrWrFxQ-V$hhZT8Jt2}x+pNlA(SD{rBGVO(RprV$)qyD2bxF* zU@mQT(6CUpoW>WRDFBM;M1fDyT(pblX=~dp`})_~V-G(LKYP?tL&JeavLDLA@X$ce zcuVW7%GeLEJJ^kl5GWK00b3FgH2d_GGIPTS_ zInhd?Fksp7nbc&CaTRGpz=EVWn8lBH`DSv~LPf=8$H31uThvI0YwAw}{&>4C$^G^2 zSdGxzZao12bB^bQt}d!?BRr=fH|zGlK?y1D!wfd^rCe8D>xWE>q~t@YNzgdnz@RRUkFsjMg@9InE5^dcp-f`V!;z>kRP-NW|l#(tFu7>Z#Q zFonj$9s-Lh^pm|wHk_55H``li$kmC06X{24V5;v1j?dfWP1IlfOf|roPZ=Ik(bk!u zfFh-%fR2M)9OG^LmV}$fP|28pd_2`Q4Mtv-FeM>=nb#_AEZ`#4>UG;pB0IW!=T)8p z)d2e09G?bf#_SC`vY(l3t$LVw=xstd;Tl zy+wlZ+@}s8I;xOy7Lh{{MY@QuXnS+1_Up!K010h=QO#pTT z-1DHpJLU?%Zbg2_pc(z4Xvl?2tFqcUXg=Unl2Q8} zARK0JXMpwsEk*Jykctll)X$t_2!(+el}oTE$Pux5Gcl6>N_z)(44Tx@(Pp&11- zGjUsFwdXJFYqHGDs*ic8wMWllatn*gh6#oi4lOSQwZ+#IUEk0Tgtz^j*1gQM;frQ9 z?ZmB*zaPNNzNw*b3r{8M><=i`$iTvky|6W^NTE=34!DY_OMf*hnitb*q`kqimY?t0 zk0C3X@0;*d&O@h+9<%(2N^_bDsTEcG7sq zFBnhB_yt|3YvMFkxi}Z`s2Jd^ zWd|7iMdkI_P^jQGo%Y;&iQt)}X6xXwQ}sA92`pCion{{|cVMe=@4U>6f)ktJHQ<0_ z#bYu0+C`PFvo4n#1AdW+PGB2vzuK$nYgo+sTWmK70nO%W?GO_TKLFEoLh%54G)s*@ zxP*9~egXPkv2a0$xLGm|2{}kSOpgAwl#mw@uHyFzFP$L?V~@e{U3+8&*jR_lntBD# zwDvRo$32v%86Dr6V%3^v9hXsvPDdc~OkMVWcH1oQF8aE4HjqRq(#17upD#qOpXcUF<3eFnjPaWxsY57n zl$SJI))X;Amsb-~epeQCzs?qoq|(h_Bf$TnQz6&gz9O3@p0%6&_+A=Z8vykkjY>T4 zKYhw~G_^XYpdUh}-0mnLsD$v?fcdRsv$oqTqEvpp7)<#*DIDaus6fA6R1Wp;$!tlF zquEMFbl2_RUEoxq4rRT5PCue|x>-12%x1I|ollX$Bsm4;tZ_**#;|d39AH?vpYTEJ z(7-Y989_=U#G=69Oiz%;=4~Fcb);x`e~kHgTkoQ7*9p7E)%Od9EfE|Khp=h+_!kU9 zGXOAZ0#LKY!8`iqTmaEamw5(xox)x~3%+RXiEH0mjJVV84eyj;qPg7-fw%sUkIzg+ zK|Ta$6^W(JNCA!@~)Qu z!%~lgGLNAm1_s;WZ$62mOkkKI+rYXV^|Mmm!9|6{{`dK^AKD^$_#QI@T5ka5+)#1d zzzk-9t7H>E17xprR-&RA7`0pr@&MJq%lkywfPCUNJI@2M0ohj97vvg2+IGXobpEfZ z=-9!FDI$+r-sj7&h7DKS#?+A_Y{~J}q1Obd# zKMRVRNymaoN6IXIJz3d4YbBi2PtGR}b*MeKnNFfyrS`-(p}IL)Oh{{JY=Xq2glo!O z322CRA$EwOS-FEmWHJkDN20Sthmm>-?er=Wytg8H-FXZ0dbiyOqBA@w0mDWuc=E{o z$LHZ}pwE${W%+MQq$MFXT=PY-e2JoYYCHnGx-HKZ&5>+{1QqD724>>aw2mAcZu8j=c%%S10s zs43-h1uZw^HLaTwov#-g0i2u|`xQ0)%RlvnI0anFG{yoNV7EE-Uy$+dvNln|ZD7~z zVt*^a)C<4A9b5`Eko}CLY(Qp+qm`m1xW2TNV-D-n;Id%9?>tm#^|c{`t3 zkM-=hl*Byh4%*T+T+Ql%opPUpI%cPCfic;SEKN7U4pb@f*OdG6TM~4iGm)i+0VC)D zZLUn}j>|@6(TyJ(;gjLloN^3g2hjenI3E1;pS_bE#^*gz?3I3zVyo@t@tZN*4jOz& zcbaMY#~~YEXaY8D{0Bktf&paJC7*ZofT0jeP>B>zcU~zcl^zy7l!v z6ia(!cdt~CgcX^V273x!obpoxGxGDV>C7s$X_n2yc?vo#rYw{X_>THK#gc%3S!JdF zYaR9gi3DDdi=uKWm~esN^=j~Fi;9=A(}3dQeXp)|-i zyBRPRBs`4>is=Vzl?5)uJqe*O_(Q8`sY99QM@2)b)#W!&O53n|3APJ)0qXuNIF_|F zjXKXS1X~|g`$j-ih;|hq#*tQDX^sTiP;Y+q!cOnd|Cm39Tl`-*q&CxS4KvDlA%$gAG zD$x<6t?>t|7>@1H7y-?U-+c0XP~DG4J%&{vfIVme+{tS1uh{=Cfdq8O;|Ay>SRjmp z?I^Ubq^rX4*Kf{2j|a3&Vtc=m_4UjI^dOS_w|}y^6tRt@oMX|j?NUd(w1r*Hi%x*~ z!0Of9mUj-Bc}s!kH3&fPzzRrIBB}jIOTqUG^!J@M7@H=X{AsXec#_k#T>k|IPi3ry z>bWnkK}O*c*byKW z7)fSvEp+?Ib9ubinj@p>K^cezPw+SpLi4s~ zyKQ!khj&^2$p`9$ec2s)yoimzqzM*tocX9cCo59vfCb$?c&qL4>5wqiOYHL5H1tSB zT|xHQRcG>hX-)WW`{~5B7Mcz;ff~s%|9ANFKgG{WqQGBGP=g|jj8A-Nq>GR;O4&O_ z$^=^$(Ce_KjG3#MA)vo|>-g_bE^dUCVva%4Y8i4t00mDu2!{l&3i~m|5kkHE_&VC_IFZ_1RZ8g828)5hp5O6MJ_Ra)a41#u0O zlPXkilKbGrKYxkP>U*E^1 za&#!@d3fy^QhP9dIHKT0LyTKuV?|YN9{Qm8Y4=ry{FNB8LP?S_RtwWa<~5sbci}a+ ztOq)nEhy>VoO66WE4e`9i8p6^yVNZvIi0UuSKR0rcj)V-64$yBSwm2fjc=lt21ZHR$H0%C&;B=u6C@Tp)Wc^T7tDQ zp3{qE)nI7-e|;u296lDDbile_S0UWDogf``tl~1~d%TCHx;xN>R==7`J}pJ!N~WWs z4!_4QKcYS>KbtQS$_zYNBQEs~GOl0_lcNJWuM|3|Z<{mY1PXV>pmnfe|L1HfjEEFc zbY$kmxNhtC^i{}4XU9N7n$*Pr!uBw|XMV{bTO{%NX+p2AEh@S%d0_VZ~YVx=$8nR53`o zR{K%xG)s&>qDP4(I(eGHY~YT4*3<*+#Hi5XUXNKsUfij+@Y~Zx#p^khl$6FxTX$$+mn`gI=p_Kg5rU54jdSz!B^r|wS1o%nA8EE+ z+>6mEFefk^_sd==~j^Y3I{{mlPw59geF*|ut;FC?^>6&G6Cz*fp zpjzyMC;2#$yE7^yIug7xa>8T`)o9W55mR^WkjdP?dB${`bfK6(r=Bo!akGLHll%KLk;JaxhQeun&V%PMB3+W9 zsH^FPD`Lt5P%!1uO*y7S)A+5d29KZhQXhDi;6ZzBSwb5~@=@S9VzWX!?+2l4Vp{zmT}1#jJItr?d7McrRV2~nU{a&c9U5-n< z(w0L=1eRp!D+xm|u4L8+VRH>E4nD>u6dJ&s`JG}AbZT%b_7X97mMR2t^^;vwz*9cJ z(k_m8#WoGe!>sm(b_S;ML_@5yj*`&pA$5i3t569?;Ka~9&{ifMAw)HWDMy+gv0=|e z`I|r}_s(q-O4(_%(nQayB$ZH!i2A)cu2dh3jWckbYi$f5jEq9^Bl$G%wp`sir|6+1 zi@tT2<2-4uNADA*c<>by_jNxyY0I5*%scwqr0nc5KF8a z`SF0xn@fJye}p9uK`Hz}g$%lcd&|lEXH7__mN)7!iq0aJPRXL{%|*cJi$+?~ifb-+CVDP!ddEHTt z{B(H;pgwiaz|UURQ)hC&8*NS^L88jGsyQ+=;-VzeCL2u#5?-()B;+!b{}m1oTsvm1 z8$jn#CMu8wer=9g0dPTM@WFUvn&5go)M|=`t(hjV>E2i(v-fO*xCGa>>qEBEf~sD%>-NVoun5&6 z28OiFUrb?j8XY(6H_4$~yAuI9i8|BVUy)5uV}VAb@OjCm*Q5MNhV$!#R&O`~B?=aK z!54-1LS{Ju>M;$+&VD3=9EXL^0k!J#g9sR06X8+Hu~ADu@Tf9M}J))t515Q(#pjA1? zfgDgr_yLS6Avpu^u;5)kKD*KDRzwH68R5=$<}n*5=*&yCaXu{CwH77^fr%+ZAqF>>I* zPpQbCI)P2SvWcR;gMzaM{9H@QWWC;)FWh5bf&aj zx7~mlBB&Z9b;QAPM`Pku4D+LEJzIG<&Zz|*b+;yzy7KNNnj$W-g>jwey#b(rpQE|T zUXs#Z7MI>ANS{b`vR};p0H-GYG%kF@i7{pYT^J^YvoOrKh;AVS_s2Ui!AgrUe5o*M68lvGV6Ep{J=I`@p=rj<$IafroYfH2>!@)f9=h}w~sdU}t``4yN;g<8;*78pF@U{meynLeF!h{201*a~|mton3 z30$2{o?)~0eyCfFROZ#3?F$aTIUiFDdRv`KWFvZhm5)NRx=lPv{7ozh$?LKmG4wdW z*fWZRe^H-5P^fRBPY$oUF~hsHu6AE8lZ>kh+`wd8uYLX-TKy8WfP!*FHZ6$hz<1v( z3_PgmurmItt3J>`EWxHtBUc-8A82P+=?{@)?AT}NI81;~hgR38jbFmO$`>w0WYe&A zB#m?kkCuLAy`$ra={Y!sn!Z<9|GaqpuPHt249|iO$buu7?)l*$JvV({MeGY?nY^T- zpFOG?rw@>1LSkc^rv*;jspNN z;RiVnUM_3eHV=d%^H+Wr`hYxV4gO;S`yVSLrOP`bbF4Y9oT?MbhUk|&_n&7cW=FW8 z(@)DS-Al_=>H~!M7s38_o!M0n%A=}+5D9*?oe(Cw+8G^~HquMu_y5leKsq~;g>J~S zTWdR{dVg6~0DRQ>FT@F|A0J3)Z~PV%x#wv8Y2xDq6F*h;}-Pm#P~MlR5DpHPE&o;)C{PvSOs3L zT0wpUF5BzX4uRKpAV02uP7~4^?3p33+dyWnU_ep~K*$}^UwXsDGzsi;M)L2?L5)v6 zV`HkL{S7QnUQa?)%bIyIDru>f5lhEJP2o(43m=uUEPS;YcWm< zDZV5P!GkFk{iF#EoF(wf3CmbIC|su2_8~C-6)iX+3xiTv@TlBycd8V-Ua9+J-?5va zx>2~{s&0jm0nhUfBQPt5*K^muQ2JvCj%G@@gB{8^EgD z*7w%_JmE$7tR2QNPGghWl1>8C+oEMAqv8kv^v?WqwPP-$W^qbrisI*GfN%kI3u&4= zdG;>C<-Z(#CarVz5}w?eXMcqn#jn=xB!{m35}jq z{K0*ly`a?1xtrRtGS)Tox|8{Gdv?{UdG)%q)Wu7{9=tb6!1>j4!m`!r-l%Oq^rLTv zzGc&()?=x%y@?TlSZL+&Z8X(~pm73^2`lVd zB>q?en3~<3V(@v}MCpEutHIKejDCxhbvJq_pCW{>*2b8nz8S8@Ahw=zE?n<6RZ?7h zQtOae8902yQucvUqhF$oNyIR^St7HknsPXMs)ZCXj)n~wsr}RA)9P?3Py=@1{x%UD z_L7&!@8i4)(J-*{V3(xVcNy2RVm2UEQ;q}oHh#cLT4}_D*Q^lS{Q%639wNHYvzQ7` zbahI_rl>geM-tn8;%7HFrZ-aI5fO4=_?3SX@9X04d7{SSA$Da7c%8ZXi3CX)Yj>E3 zw&@A9Bb6DKyU_N>wG?a3RviPx)f=65FVtm&lvTAcFjjEwQHaZLCRW5-LBL9dVyouR zKJc5c#;wITPxX$dSl732$Z2SMMFBij1y}9!qfjZXRYJJljovLg`s}J-0rb^Xbxw*8 z2}qBFk&)w7jM`SQo4y_1|6p675#wM)n9r(ysvD>lc3pZtn*v|peSd&CQ$x0B%!eZy zP=~rr>_)Qo>+22WL03q^FnkU2@;CmZVorx7;Nf=ruFE6b=1b6;lBnCt@#1#ckQKrQ zzk+(Qe{E|H{4ZkzB!Dq-&40i$7R|P43Uu!IRWV(_Nc)mGRT~-^k{;;`S|=Ov-tm44 zp3r@rPraZAS^AROI-fxd6#Wm);qvZvrFR@7w9 zjmMf%m91-S`cDo2b0ELlw5PE9qgzr4hRRie`84@Ig|7%h27MlSej21Uvz&sJ_x3kN z{4ZB2VyEWBk80PBHcU2chJCQs*wd%uFK*jjEeXrLOfw`W#z1q7rN8sv%4erV-KozDxJxuL;hQuu@AMD8wAi(Gwf1;xk;u znxU{VXj=5(R$AU+#ed#AxHx;6JTA2|9f_%aelH>lwat-6gz`M@b@bz62a7dwg7UmrRE5*H zSD@kuoK**0^5Xlst2;2S-;f;%CNfrY{9Ca1d++V_Lqtnu+c8cu2lKg);pMJM&6etT zBmzC(^ei%TKYzE%;;5fxGZ#L9wZ`_#NyiTIASL`oK@m;|pDmWLH!hOR&O!H!WQt_* z2X%Df6KG|b-*W|APwY?g%wvMg+Wqea60$`QJ5A`{H*v8FHbH@myq_}WKOy>KAuur% zP*94nz^VQHi1Vf3!68n&OkEqjpT4?T|8;WwJL;-VtXs>Gxms^WFqM(2I!obGe<2jT$pG|-`eW`Z2+d8JQ;!gk}bsH~~w zxDSQCl_-Tl#g=aEx?kwUNjj2|Kyq6`mMKi1c)PO^ECb2@x+_GCD)8(1l4R9XOAMlG z>e`VtoU6G4fvn{5wxQvY6J1^WDPX}AYy;!1v7xouaj(_sP30;8`re+D#b7Y3$jD_I zC_yOfzj(Q!{bX(t za90}v6+cx9n;^8!$x%ThL&DY8OEEn*oqUb?LP>cWJgl&AD3bO;J2Sm-Yc#GuQI3=~ z-i*4JvbC+8`{kIS^;lBs4MIxHoAs&Tofdd})h!BK1RS;cCkw!?LOK z2C&(CaNbq<_c+RM#L;gBiiwBWKd4O0xJ`_kzO#j>WBh!L=HqCa{}hN(Ntj{h!g#}| z%07b+n~wA0UKzjdtr+9D2zm%fZFYRgt9P3ru`Mvk^hrfyE^y6aGa*vy9Q%)AR=n|B zz}q1}=zi1P3vN^ECe*lHT2fE53YXGQBCe#hG`{NOYH*r!&^B+xlO{T=w|esv^ezUt zlD>EychnJ$fPtx{^v1lOtHT>9uQ8pjG>V+%8|Zd=rPK960D|i@r^R@W!YW4$0E3zE zu=YPqU8ZTqw1H5J&DGl+wB%U>B zf4y2QCj+;BO;q$B&MZAw&Kz0f*YS@K=iyQivT4^=ssBvW&7}(5ee*hq*X(zcVm6{Y zy@FxU0Z07xUUh(cwyy_48}>RYSg6OcAjep&-wCX#d=Sitq!S}A?oHA$y9K)v&}y*{ z`)a6>R8dwLIPpYYjhsn&nVC+>X6(^|vFd?@Vf$&^>m$D-Sa-shlFnpVuGNJL5AkE7 zptd^>=gUj__Ujf_aH80?CnurH4b`GAj{=$Rpq@{@FHD9EmV&{KCEQFr@b)Z4rNSip zBaDNzdFTG`K;o~x#14e8gBN0%YffSX(@Y0q{zP)Q?%yJK5W0%+SPYB)SW|@9CQ}*3 zxjB{kF+*~*UN0JTspRZsZ2n~do>K;swt41|Esyi?r6&)?z_i5P$&hs1m%V||^RC2< zk=yl@6A2nsNJk$Q(=;x}5{n0!-_y-`bXh1GVZr(0gi4ST`Wu|PTi?20xa=tAPV!i_ zH_}5Am(oHW>jPm8`x&Jt=+ZWM#9lb5fsDuBnPzP~0k3cp;_B)+S6v=UoQ)rY5v^$G zq|{j=5?-r>NKt^0RKf&9m`X9^^=reU1-5X1UA)H~U*GD5aigwSp4+gCx&frur%?WP zK?zRlr}SZnjS;(9L0D>=!P#yqrtNuPsqSPNv+x`AMBWT~D#z<=*luj`2mb z^Eb1zLCb7hI%Io`YJ~EYWaIhAoW%IGf2*diPbW!NgU*&RM4Amo;n%U=Q8UE*Q;awl zz6Zn63)Y^8e*_hMs_=g+*#NUY5}#}>DHR)|j&tx_{?#FEe^rgV!k-_AIKXU|YW+z! z?{^4(;`XpD(qT;uoq@=~V>64}E-Ys$^ZRTR{E4FSb3=(~7xlfFKga(d^c!kKU6mgN zl#r6pw@gm)i_n9gR-^4y5y)^BgJM<<d!j>~0pgB7Ch59Ew4cS4O2^))&OfMG z4+myW#!DA4C$Lyk$CAwN4i9EbM@x0RigW8z>2&fEg}AsHf$jL~S4RzI?rmP|uH{|; z?`{ld#-X1=*DHv)qp;3|XVk+JCE#z-vl)(N#k!EYeL)rX7HR&AeUs!w$Mm!`spA*! zta`RxLKZ3mS*FlDvq?`$@9p_&f{sem^i%^z6WiF6z4634X{2IS-2;FlK%| zBF2qOu8BWH*ozQa$Uy$MC|L( z!UAZK`yYF2ga?mNHXR-E(`P9_Rb@04s27^cB<~6d1h@AoDNskU1cP zz**mMY*?|n?}t;e=>@A_jqQd1PQLKi#|GVbfWj`!BeEZj<`$lAJkZU>h@$h^QTUm8s*eTojrAD!#zY=xS ztg>A+9D-D}g&(!;^L9z|FNXW7OwFDN_^U#)Q(mGrln zcPAfy*4^iRUdCmi7lonY)3Da;bTuGeA)HL6l)kng6tna(<##A3wE^X2Yhyb~4 znmra*E*-nWgz{~B4BSxSnGNJ>W)-1>1G z#%#2{fFi|9x6UkFdgv@3k%=0I#Q^o-`2q$g^52m)VQ~IACv>3)7!{6-bPU6uV2NWAoQA z@o?RHI8gP+U#-XLbM*jV$wE-;2LBPFRlu|!R#mPNz@wEnWkm6<5|2n`wY+mID#BoZ z^F&C!5``?PaH;Q&5FHPR{?1@{d}3jgx>iBeOB|@u;LN^+ZMJx(7f9&*+8gR6K|s;k z?^Wt@Xrc&>%=1wXak7GaeSO}$JD=0AUnrMfl1@8xRqsG9duu!mq+x z7l)D=vPvjTCY>cME+p#E>2M_J4(h0rwGzsd5@it6%$3V!$n5nwpEezjW`&W5u+lWz zK;Pow>v^r}0-P-8p^@xz{FSH_PWhNNI7i~-Na`&JT@$)w*0PB8>n`z&P#v#+mP_% zZq8wNH|qFNEqxxC5LmZ#=>{f;5k^J`J*6qx|8+fWdwi7hww%wZxYH9c1AEVhfhdOi zLb234U!PBZq-Gd-E-cx+4Kk6qFY2OJ8!x0Uzw>ic>FnQ~>wamxalf&_GU3lU?7pJ& zu)c@}1`5NRYff$9y*n(ddk)mm_p9#jd|bI|P)=-{WLn=>V&)33XgNlMa0`^TAdoA) z&9Je98lG2&KZ&1bFnH{#JQDylvdKL;AA?{mIxbDv&SAdw-hbB$eT?=r$l1Vo$LSf((dV z)Fz3^pibwgpQtros0@G@`xh#gLstl~tzaP-K%+mv3;%fJcp{ewf#SH%fGQ?5kvci) zE+u%8ItErXJobZQ>_{^2v^r4Roj@FS1O_CqE(=6C?z<$!j1Gd~fpxhe&bI3s1(aRR)G5Fcf;2nfC_p1lOhi zTk0bE=8>PIS``qEq{UERwZ5Wi&|v7yvD$f{OCavS4g;?)qvnp0sO1=`x1YeR2Hf%} zdSx^vJjb(qXdkwz^I3#2tS<(Cj%tTuoR&LNov83?tsLwv{RR(Ms#PO57R#Z6E^JAH zw(}kBlY^61ZLr%q)Ai8t;CYKmIF!svZ>!J({Cd+GJ6l65Hc@Bo7Q)RMc<60K0vw4;;5lypZQb77<*yNr3w|Rzu9AR+ z(Uf5|mEe^wZbwFsUYZI|Av=O4@~V6P1er>JBTj4^#_}pVO2|+!A2jDJbJIy#8tLIKi@uBBa+U32 zeJ4F8lP3#6AUnd}y|#oKgA2!-ipCZ{KDCL*6}!{7eb&%ipJyy1ATKxGZRS5MO5Ddz z4Exu1lCw@(-c>vXX>Y`2&!KB-a8PEY?>6~C#@wvONe(hF&pXT%OEbz$CdV7`Gb!J&P>NaFigPkQ=I`|J_* z`P&|{FVzUXV-%XpvG+kV$ImVfv-!bVwD$wOMVPdzNbjH-qHUu0+mTueLVy)i!j6aA z(=eyV^%C9;p*Oc!EGn0(2>7I+I;8Jv)*;1B2@reZxAM&|`e6p?9)II`+um>FiotgF zX|R{fl65f zS)&y(!J8n;G^@|1)TR_yVvc1&vqITYJ3-|Qjb#u=FJ>k#upb)JCEIa}u%WlPmbQV4_&bey3;DrH0o#Y1cDvn+z07 zU8`f%1xIvvy5cQ@o#bA0AjFS1p@Sy&P0Ig{PI{gvXqpFTL80L0l>%v@yq;Bb{bzuc z@yLnXd*LN6>=$$OLS}&!GlCs?61qAhh_gAh8M@H147|pFfAT&!gY@rgrsBHJCd96M0~wG_OPv2Hz(M0kofjuM5W&8gmwcq44e2X1tmVDk04jlm z@7AZM)RRAC8r$dfY#`uM-gAVnUmzL)8SDPVf*n$t>(g!lg)?&ZUZ^s|QzQ>>@>1^q zGo1gts&T(aD;Jh3F(}vuS%MP;+cIbtG)`9`dQyT6C;P&#LRyyVAJ=oYu8gPVFF$e1 zD!)uwx?Nq~&)h&`%z$T#b4pBN<^OVvVOaV8)&RZ z(Gi0`y6JWhO{W73wHb1L$Q(FS&to95FJsLPXWgnRZ-a!ZUb6;h5evyv@vx|v(?-$> z%iMyE;sGW0rJ~@$t;u34(-J-t>$xz zQPJkIm=51h(Q>9&5Es_ks!yQvghJ+FR>VlSvRhK6pX2fNNb1@z&>gy|?X0)cMi`smzEYGGO82>E)gu9$)eq! zdOXppt-tIytw)eut%>FTaN~G73yF+2U#z1hQcg#!3kWP9pUtAc32DvVvo>B`&J@bb zWkkHzmqq*<7lk+eOwEPY9m4lg?gMm|2SH*(X>sU4-CFCMSm|JRAyNlhQ^}~E-9Xs( z_tLl665;Dp1q&iE6dxkvQjxD{k^k|14pr85&&1$kmPkp8PBAAw*QFm=LHC;5#X{BD zDX2FzTJYtiVb6?Bap~0lNBNIN0U26Ic1LM3A;ZHAMyGXhy!_HjI$_64JscI?vlMswn73*~m69WwEg&$< z>68;S!KkjP$|`zcFdX++#@8%VAe8^Ao#o)ntxN`+#7w%CCC0LJ&{2L!oJf?3uuAt@ zLZUP-Gn*qyBWTi;_n6Rk2&YR*+e81+e@4z+YK|&_c78&%G1GgCt;TU6%@-Z@p|-U= zBR+0k(|fzhkv?KneEMUoqYq>X^zhlgYR*pATB(DLZ$Bbho1n^{Qi<{V2Fr-sHX|Eua$@Q@%^A%h8=+h^jv}=Dy<%m{$hDH`X{Flw0Gotw19n#x{=$ zW5@)V8#vTb}kfFdOU51{9@b@*a zg{TUPRfY3NBxLQ?kcH{ae8S2YA*mkxjLYCzBUO7;0<(Il_J?8N@457+(7P#>eb+8NrG;0Z&+VWknPAqqU^_V8?OLj-AV2y-mZ)iNb zmb7~P^W2(`oK^@F(*|f{zoee~x?~x6xv2<;4Iej^T4fLQ-sh0Huq@63!6joaU6S}9 zLq{d0tq8y%l)&&UcE2}5=whb~Bn4x1Vo_`ygGXv4iN3B80cLnPQAkRNvw7S53}J~L zg<&KRVHl&O>WJsSR#PEqVVwEHH#o60ZuzbsS-PNfz#G~n*pJDb#1cex^&qT=Shzf& ztc&=&ofi9ic#$jnkF8-lR&~DQFFi5Cq|pyY1vSe?@Vib3HGtcGVVWEHl+v#A&}e8d z2~-`xtf(;odSYvC2^+WR8vccOK$?Habq#iE&eo){4Z81a9aLgUH^Y;wTUDT{=TQPjgz%3|E zVBawRBUgcGp{QNcahwi7y|nu!B~?|$!`cN`+IWj?ddQVkO3xBBQH;4d4y2!HwCcd3 z^M39eGG1SI2J&qFCaGN-xjkhL#`Vq!o$Jop3qqHbcO*j6GK(1iM>=QcqVx3xUTky% z=vdk7ayObyyb(GnmxsFq(U~)vnAAF~CeFhwu_bHsI$anWkkxUnv|IrP4l zHv>U>CjGK(dZXr;9Ob0uKG5^VN-ZQX7CBCcZo6>^=s6ymxRZpu{S0x4m`i{ckw=hM z?J6M9Zn!l1W0t0trZ&!)*thVvEkj@^LNL6LMZ7BP=b&Ihs1+Nw%TA^7v{tJDl>W&H z>e(oy0g|+H?jaVgU0mzS?0O77ABqXvAQC*>54-w8E9t?(qUnm?tx|(Qf@a$1dU6S{ zjuBp~nH>5*=3!9&QY@-MBR>CJ4XE`qjeif&neWnMd}!Pbx^1khLy-)`%+e95qe7Lz zFH08tIuK@q58_8Q0>ZLQBQ`3i@=}xrj3#=`=X$VrBe7vD9Ofxl&seG&lH}5JBzVUX z(DHiLp32&4YkRko8)1V_jhd|N5VJCND2GICD>ocZE>X+8teJ$2D`BL zEmFm%L2@J2r|hqQ>DC;pccW}u@x2O}1a=}{Xwqz0)#JRvOhW0^<~9zg%s)Tk z*_nT{CVGYM@S%t0pbB)ACYX$a(IcOgjA}ihuS|#+ejU5^FJ`fD_Dv(|+s;0Xhe=hu zWw!Nmp|uhu)o~DIl2SuJbVTASMqcxRA1qi^d%M@8|6HA5ZH}<(`1I-RbLc%z0xx$et59hnn>`4u9m6s(F245gkwH(4TystCnc)}A8q6CTuIR3b#457rd z09J`7#m(YaURK1}&TXkzvHa|`%iYzmW;ELrCiEVP2ZpDcU8UM&&hdYkyi4}`vrZLp zUhm_hd@|c9KK%#7-0GgJ-SRj->; z{^Wf=L`AR;n+#9C$}@}XMI+UIclR&BViuE3g=^V)sT9Vl#qb1oCiPRKSh^R{HkO0) zwFX=e*JQQUG9bE^9sqg0$cA$?hZf*7kt+1YXZtBcEs);E)l?sx0+X@W$NOd18t|t%gF=*^JshT1EIDN>Nn7)3vtU&Q_C!9McoYT$Zi^DE;$Eg2EbYN#jgok24F!(eqUXm$>k7J5k#L3&cEGz2zlIm zNL!TrsWbTrFMVt9_}I)uwBNjDaA-$;i74a1%b?<*g9-)bD^;Kz^aTs`dowWeP zNK?sd201JmoJI)WaVIH_JG@_hyXKX~q`%JuD?((zVq?uN9lPt&0GAl$T2Y%|wc2*~ z>(Ee7^>YY|#d-v;fxA;3bm!dXp}((2PVp%$(8BS5+yzSonDreVv)6~}J$Dv^B-&?F zA`oufTm}n@yed8x?b0{pr*2l4biveUknAhIpj{T_i40I_i-^3zf_(6TH5`3IAN=2; zm(cGGvvo~f?}w$E_G&&^2%|haQPKvdtr3Cu{suE6T30&I2?feV_cbPB;*N&mmPAHBIdA`yB?B4Be$9ud9896tE z%W_ZwrI@cm5zpX_GT|4y`ay&E32}8rP`}NE@A04N64x7&c+M9{Yt^ zG^0{d3EicRy}{8_yri3gNVz@K^&9c(_+`S)$LywhT8_;3$$w&SJeKn3W-Q-6s9UZyl+`;az->4lsu8zP11 z<=`mE+w|OddAw~zbWzWTi{M|DOlnjqpT&?xR4rHQV*&8z6H2M7%!6%^3nhX{GWKpx^Y8qLGiE1G!+W{M?{m zV$S^}IwE(wC~Dz!QJM77aIgKNU(y6NWKIYV^<;ZDi|dvsCuRaO$11*Kgw`TqM?MXd zuJ9l%Lph>3=rc5YFM4lOC^Rk=BTPYS3Z%XIT}*$%Zcv9M?X)wj3Mf_!*RQ;q8smbiPyUwlE(5n6)FKX;xAxmcC;FI9x*F@=ayCV zvA}OWykG8125_}G42GfF&rdig9<{u?OiyprXUny)D~|s%OSoOPtR3o+UzN<;bC@&= zMi+i*4(to7TFR`+Io?m3xo-3kIOLrg*1|Om8dUWp* zy}?rY&Kpy7rOs9bpH?Fmrwko9T^it3XP@2{MK<2EF0=kuF6=))gy#gC{Sx!N z^EyWH?10gFKKFUfEgg&2x3*?#t0LSe8P?PeNiJ@5+o|FI_E(9@4TJh5|L~BnzRXQH z;9zm}`4sR^M10bH0Y+$~SUeuRg$lO`FpPDB+Y?W)o!0r%Ab1GeGKXJtnjF3z^Heh4 zZ>*h|K=!7)GfA4LWBxEUK4MAd_XbXydB{mRzK#La*0X`fo#V}F_6~oBB;Tv$XFz$| zHMM}SmFEKb8BXPos8E~%AxD7!)1^)xaAVgk0rBV;7FR(8A68L92|AlCxLi_}SLZGX z5hov&+cj88L8gY(#oq{cd zy_LwztNg&=*ozt!91P)eCT(6MOM_i!mfaN;*WX!4qjURADQ2s3oL>u&5o9UI2jEQA zt?$Xj4_V(BSm(0+-8g9)x3O*8YK%5%%r?f3ZKH8^ z%%-tz+qSv0W1DZzx%d3fx##^fpPpxC)~xkgoHfPy`(Fk47rxETY|}{uULDU%N;`3C zt6**;N{+QOPB~AH=74=(?wTO%vnU%Fp~uL#%&j2ZFHfH`tbeFDDh}DO+pU;D<%ya;NX%Ya0@}@v}qH)fS zjj(hK@Q8m1T82$y;Uj$%VCVVsvRzY5wvh1yHPwvcZp7MOi`)=C|GIQ=f^Zna|u_ zuW@nORQjTw>;KB9Mo!Ucn%`RtG8Se>prfM;%g>j{z({ayKU=G^tFd|2Xh$W6n{Mx^ zW-%2G->~b(@(Lgk{efI+zT!bo$qWsu9#$Vr1+rITh&h%dw{DPqz&j z$>7k-^Awa7e7utN;3Oo@-UZN~wsU;e&3Mv&)14n0Z~KVM$~E+-4mn#RM!NO7m|&4L z;fcO$ZtLTaP)uQf9Q&&SnyT;2r0=)J#L zcK5p?T2QWAunP}gw!$}_kLnOJ=eFE~8qF1m`;d((u3?gYKea2H&lIu&vSI&n=<>WLoZQ6#Sp0|osPnWKY+R4HjF z(dEP{F#@^X&I}_iPK^_4y#vNtiO7}zCv zz?0Xc%$y9?rB>s8rFu&$P4VBPqVLvU;HE5FQnRib9b<^#N_@>)J%5^kYaFxOiwNtRygFb3L1e3My09r}_*=_W~B<^)* zoIo{ZyfY$J4bL9sfwS4|q)eON*{b6>{=w^IE5pT-2*wUU5^Fda~G<5(KA5se}+X+iSkKb_oBl%4&3vK>YYeeR9)pnt3Z`=j>n8E8vg-bu z#opoJ@p!b<5A*G-BD#&D)Kt`^ni>#HM4%!Pu&5BZT^hUWbR6Z6Kvs1Hs1b!X!Uwnr zVtKt3ycW42pN5I2P+vRA&qK8x3nW1)2*k!RdKh6Ejzj3gmL zbE^II`BWyl#B=4s;vH1&TH$fUTd2AjOa5{E@`_|spFHwW6$K$Xkf9Eq5__2^3_M3k z5wuJwm|3W2<43akd8#eBM~LH#mNhh$PHV#2(&wjr>7W$VvW8^bg>m||5My^k;`k>pXWNejfl>G1TiAAwTF{!5niF@V-VnFGNptqIOLaJfW#^sBP|bXoES3y_A1-r86leMK^bE~$ zL*Q0?w7KgQb; z)y76aV=GEC<>wv@#e$N!=`)qA^J!gIHXR~leN#&DBUe3iI$is%&x*pO_wU7UXw;hA z`MMf8|ERK%7A^xTnA$bYdC12Q0?y|{gS0J^!R^O#rO#;_cTMwZ zFymROlV@lLH2Tkw-(I^+WU4fppAe|&czSl<3pG)joTM$Xp0AH@0R=tuvoY+?fha;R z;xCVLaI6-64#^PnrcPq@`AWmxk>k>|_$v0_-_6Y?Tl;Km!Kx*3d4iF1l0=xLkGZ`_ zodmB*mK&F6!yOtZ-E_{X_T>Od$;7Ec(2H}%`a1hd88mB#W+yE}Q33ly0C_K2pjB$w znuBdZeJhy09gs@bKUL$Ds;Mg5U&>HlyRUR;0T9fypgCO<%%rt2_hcZDoDBMgqZHN6 z8gSdaue7Zkl`4xT*#B&^cIIWjuXLHqr!Yz+XjPVB(=>M!JpBHY&04m<5Txbib-nGA zjuA14H8Ml3{4jGjV?bA5`xXm$;%0@GQoK`23!SyobJ>ZJ9P8L9gph$-vZ z(Ro7OL5;yMun8&g`?c2Ew~;P03EWBZf(q49Lr(P?(Q8UZSDovT?FuU z+n^!%?U+D@OZBXU5cQsOW*0W=Ly8Rk*BTWqD|HlW7mrTQBEs&rB}~c(XV-6CifOsn ze+t7XUZD{>k-vF!P%M55VaQ?KvEvTnUgL-G+>jO!zH&?ja_5wyTI0*I zlNa?)%PAg(RRNa)x5|fdqls>UXTg)Ei|2WbMIh@#!>zwN`}rdGZ3tV6)-I-Kn5qx%zeO zwcRs)jnQ;P2Ci%FboxSDiKjxIdekzu7HcqI_+curt}maidz+UD@;mbI^WC)qkI3aQ zwLrDF%m|H^5%A}8#@jw3ef}IKNrahr)5M1maz|s%_ROzhn;;=K zNs`YO$luEbveA$D&+7a(v(4$jM<$O|dH%?KgAf9xP?jnR@>a_JZ5Euk_4ltLh0!7N zKsk6SO&i}p`mjhYN)kc~8)*7^2<)Jl1gedR)jJj^eRpKC6LCM}liY&_zHi#aRThPU)q%Sq$qC_?`w@9=g}5Yk~{=6f;TbJTiJj0>WGh zXzNH%E@vyTmWMTC$qqCdXnctO48CK2?^Y=&O9Z!QFNC=Wvn!j-$m12#P79tU(d@ZH z5FTxFZdQy-C=qee&jvvcG5@5>8*xyPAoDCa{W7RI^mh$wG!|E)SgytWoK8PWjM(qh zqS+;%$xj^H*L40ulC9FSwJYnmi~65G2!q9m$<~ow=v^6Gs{%ev69A+Pd(!ew5y%GfE`+|&TuwyHWf}i>vJQOT0H%|i?p~2+XT8hR_8GPb%U`^Xg zl~_Cc_q3w$0X+2gJOs(iV_q2F65KauvP73E@sj)agV}_XcVyt@2bC1+#MH%C7(f0O zp~R!!%?=K}-~AOy&cTLUwL!?Q;7x}5cK<~|qk-NGl{r$_!cSZR!X29hlYGHh#bruYJy@x-=$>+a&Zz`sC!II z5{3%=FNgV%RbZkKM*++~z;n44)1+Xiz=B8S_|euYTlYd-kyj{wL+~u}*lt1*65@UC z8PWJ5NdR23=le_Y|GNtd1PD8IJ5{Sfy*ZVm+OizBoKsx9Sw+Jh{$M`~L^=JR1JCLP zemVlk#|M?K{|6iY{Wu*4BD-cKh+`K*M3H$B`g@eb#Xty8Yn35b_=|a;K^4V~AN=!& zUknivE=;}uVwn(JhzB~wiBq{;{~6JALY!m?j>-7hj;RgSJTyLhP39g>+h%++P~{zQ z%q#D|7M%_UL7Nyc7W*sJc!j%^CUf&42cuawAi`DI#RCn`T-{*HqXk4F1#Vum`^5K4cS`&{E$c+F7GU6F=;y)fUVcL>zS} z8sgfg6i-W0tb}k2!Qr9c23(S#I{k+7b65SJ{X$6Umy%Y%UMfWH^#FzNW*u^~(6m0V znI>W=C(-GWZ$Byf5einSPF{5p{Fj^mV(K~)VZve&9sp^1ay_;H7;}WH3N^j2g+y!g zo5QtYNWLExnhl|_hh@pvnA$GHe-n&UlsL4%k6s|w6i`A)wuh#IS9ti@EXSd5gywr} z4ZkpuopB#p+3jzP|If6%Fp$y?@fhR=B=Mb`B6@`B34Ag_yC0KO=~)&>BUd6_d0WN} zQ2vtI|LDC);e3gAmfG@FB!lM+jl(Cv>Q7M zcJMJXIl_jVa<8P8zF44t<6_7=n$kcH8~|z15O{CEAx>8V7kBnW2ksNCb%V4i?v{fu ziAApUzmHxaa_)7^kqp}8nygLHu-%8OFoY*rOmPUM5J6H|JuLS zdMANK4B$|<%&zbaj31l*s~12iPo$_e=b}sVHmPKxXK*?aj9*8gF+H|M56AvoQ{?ao z#lPVwh75IUjRmPu<+}-_=<&vbPRAeAlgjazJ3Yp0^-Vk)eY*q%q4Llr+)**TaUo9d~~f4 zs1x&>UFj~G!CW=WRcYSYHUBp_b!puBVq+=aN`y&kWl#q0Hu3nkwXkOh-*d;wQ<7X# z(rMcb%>{W*Mz(u~i6M6;hr_V|PiC5mBtROy683MwnS+EZN>9>wP^&7l>Pi=UhQqMKN<{b-alggQH?(RaE9*bvm} zccne5l`B3K=Xrkve8%V7?# zy`7ydfBb6MZ@?mM^OhWHB)E~0aAC*I16&E!yfJ2(1<)G$sgt>giRAMQfq^V zn}NlUcqS&hFR*h@3SN%d|F?k-G*cwVKD`3{k`6_P23<7urr!Fz@M{+NB%k6*N!aR) z0^1OW6=2|$<=jEaH+J($zEh)J`0C$f(nSXT1xWdz^xKpM!@pr^!V5ZlIC?pHJNnQ8 z^y}CN&j3mBY&#ibwuPecc(IX zp*pFo(&&=zJUJMHzu6qGxi9`g^>3LLc1I)bCz&S!hkk9p!JG}f4CF_^E$rr;{DI#S z#URq3ib22$c8+B?zMJD8D9U3VO%$Ma@9OuvlY}-2}le){bKsHcW-o!jf|-UMQPl zH;#Y-zJLpQ&eP5oLy((@z4GwM?wr_b5TUl}kbO++pBAw&EcAfqfbT$H(>LOXz)+d6 zm9oN0pu$VfY=h}KpN@?1@>_0+6pNJVfR_2VYvj}2gMsbB16;WnA);V1EuqTe> zQy59P`6RnMGHh{)5t@J$O%stX)l`z&7w}{gxxos~;x`I0YU!2adW8xkiHMv3(32g! zV(+vIig^pz!a#D}k<^0@aYltsnWAt8Ka=ikF%@opHZV&3z`JJa;61$2{U6^l9R=bG zKS(@XvC2s%j<|$HBK2G3y+szKfD93F4EiO#l!OStPz0UOD%{RNf_y1uX5jk#7b1s| zXTZAA--^Rm!B2R8OB?}q*olH(*iZFfaYGzj!xF5V;1k$i{@!V8$*wL0AvV+vwc6{o z502!}nN8d6pFGSPAgs0}Ztg(}mPPF%9a~>`Df+I`Mdg_)SDM|) za|N;1#`EJpCx?(EMu%>~r!q&2$iQAu(r3mF}p%m~q7=n%Da#nq&l~e$PaWZ1Ap#37+{90tqq{5h( z_gjJgwx$UriCu@t8|eovg-!9ut_RUMSb~!}Aw{7fAcHl31N7OGnj+X-8rI-A?OUw_ zxc(7K;H887=0MrvSEDO4ib17cIzFEfHxvk2wv=&{&KhOA#2m1sP_}4rghJ;nfkQf- zl&XgLNtfxvXGQHSW>@(hiN!eUyppn+1Z!B4^4Y%uGewM;ic9O_qDo6~%s`HnNl2^8 zo;2HSG_(NRL`bDs{TVmgY1xz&xvJ`SFIUr)sU9*;A$dKT#MGynyR!;18egJ+O7I4J z0NOwhU)v|ygu%4rYVv+5CpK-F(U58=bRqjChr*JAIQqEFtY`QfF-3$XyXBk2RK_=QVr>J>N<1+!K@H4s*Cc$JKE4=yJe5|k3P;K#0h5Ck zWrjXeD!OT$v>wl2wAZhT<2PueT;UX5nGy^4747iV$*b_#;K*fJPgs+L#4~JKMX}7_dPRm z+*fm`%}oE3R1*@=1If5}sfNY2Z@q!wh_V`~1Rmo&?#7xC>>?MwDZQL{Q_9qz2!MP} z@tL|g2^p`iBlbtbm#N2tQnc^6xGk%?aLCj7>nll=B3!KuiTg3Jr!FkH(aa(6j5@D& zosP8rDQWn=SMcmJT!Wl^mgR15tmCOfxf$ha>*yF^d-=(HXglYrU^~ZhrcX)jFo$2D zXtY1bwK%ylIt1=KOV496WQs}lE%2kOQFWf34M(=a{c8_@5C)>Z$$CTp^BC`KB6TLz z3im}qrNh7p#+*M*kwZSn4TbA#QUj5m!C$&CEMTH;&h7fd2i_PiN&JHQd@_u>kUxBLz!`W>Ppn83*o9#!=(tmG_nB<3-5livd*mKm7S8yfK1?i zyiTQNw@Q^Xdj#D-kXmCW%ehoDG}UMC>BRnOd)XQ|x9xzfAMlKuBXG7I8BA88#wHY6&(E zS66HqfH08|A~q|WfYdYLEtF;{1~sYYE#|4y%03Z|cn}-^$!#yzNva}wK2aYA_1MACD`)lrL zs+WK8knXzR$fz3WA?*yT=J!s8r!UzChFDMJGRg@9*ifxi%+c_9h~x?UvjNf{aAI?Y zcWs6B2e)!rO~&|>xt!ZZ0HF1jMnuE0BiAa+eY-f+L;gdG6b-x6^^;inVC3j!>qZ=) zK5uy`K7y;RZo){KppF9f1v8I+`KsYToFau#CeZnJ3qY$a8vk9rHEIjx+FV(b(385; ziW}mhLI3eiwPL=hmTOsd)^n9pjq&)O%Cf^P=v~IK$H~QHGic#BXT78IY%VsR+_PtI z)M$IsSN`5@u1JB7v60Td?ZAfz1xlWG-LvzN>CULgRJ7(m@;$+p&*u2M&c{@Q*lF?U z@G_0}2(kh}eXq{lef(CU# zIGUENCB4BY#x@55*aB%xCQa%$bIxk)Xz@Y#pe90u3hY=|SQrz^^rY2WQdXo3?j}Ws z#MRg8^Ae8mT!w#0aZ%nz;cB|*vL7Gng1M~Kc}-@QuEVeX9L)5&-|*PxTK7Nh`r#)? za*zk*v@#-<+gS2>{Kt=&ow+|EJsw`PrivA<7t*4z6flwCA=aS@m%@Bvt%M6!IAM)` z>F$bjydhmAKqK}o8?spx9&IucVIkYi4bbv4fE{V=SicYF)1i3nmT@X0w!jbZ()7JbT~2IOAnKt$oe!l2Qs%HZ-ki`^FCW^ zu9{ci8{k=g&7e;bQq(c8{al@nY|Y474;FmvInS&ZW;}nsITEL^a6h==|C13*qTsP< z@_%2xq1sS$d22XR;{5@-ZN0Upmbpa{3(1w4WA4HVsraG6ayw4)ww!i9_YI*+WGXkz z6d^-=q=JD$6uod{+HwFF@pfjBi1rUarE(hp7Gi7Q>)GeT7dbDRHJCFu|1cCEyj0xW zii$65?vDVT?H!DofOerPZ1yAr8-qFMbZB8NQH!N&6Qr@ zdnSLcQM%gZ_=ijUi;k=VOv?fCcvey_TT^qpinF%;4IW@Mofyt(%hJ{LkFs~O*u0E? zyQ>gob^2HqKE7m{N}Gtl4S6mH-2*cz@{wC3j)e6`h8UUsKwcMBJ+Z`$ziPlTx{+9u zorV!vu&fk97#?aK?Z)14B10ILJMgVIa>O`QieoGgjLQlT5H!PC+VV4&{9!|)x+Z$2dQ7Y`q6BVie(0*olMN<&oXrcRTN)xb z7Hy=6<9z(MC|Mjf_W8P*Dy68>qqH=70N*&A5tX6hD28C2U}h@Z5jCE|k23f&*~==9 zO=Gas3bRS_&d^9|nT{pdPsxXkIzoO2?48~nW$na`hookugqb?jjYabA;h=&i$J)6f zaUS_2vMh@ZHrq5CM!Ff6W!mEF%t-D2fSfk)ABJkjga5ls@4}px26q$+5MI<0_IsWr z#;to^r(lAbujPFMJSbZ`9?xc0;Mlkx%8!0rdgj`kYpsK}S@eLli14IpV+tc%v~T5D zLZn9HqG8lck)om|9`&x!4ctC{c!$I?v0-+VZ!OpL##U-;Ih3G0EX)tCVdv0YMc5TF zD2aEelNp)Hdkm*~i&T!M>&xSAP8fPuFSf;52+lXfvMQM2jJQZoSr>4aqBC0Eq}yMf za|@^yC3miEe#=x191e$4Z`w&Q+kbx@D;M|#QmHcC6fG<(i|rH(lDR!ubwnp2qIcA{ zC3mg~qa3u7uYkh@6&Do+3p}t6G)T#Ov0C?f&dte@3M(u{YH6y;lamE%-VA-Bn^{XY zOaxHD!oV#4lx=8D$kfuMlN*k~)ei1OOf+4#zqB*z@bR5O%#g>PY9usVg@NIjkgmH22Ec*mw)-}pkH*L9Zjw3F z=_@|izum}%X+)Z_1eB6^K5UGpyl5}~IB&D|>-os5aV5^eKVB9S+C#4Npo3+3PlSeV{U!h}1wbDq5!wkBkq{8lUDm6nb$6h4S6b zh-ZNBtr63DOGy~+vh$joBIDy=)Xs+IZESk|s!Sa|%QuCmF|>=3>2V+S(3aPeX4VS# zENz!D6#aQ!q^Z6P)M!rR_fm|nUh}ILtJ%o&4^$n^{Q-cwSiG`U3FGmU{Nv`ku1h2R zDAN1!V&IHwig-n%um+8m0Q@uJcq=tlnrGfoQ+Hjob+~1#bg~E5#p-invv6MGLC5dh zi!MivMWio>?PsTnJN@8vvd7~(;1RnM9gdJO8_(9SXHZI}4keMd)G0ZFA18 z^Q2wlE;w?z*PR!i0{?WrU_|Mlu=H%&j#^eZtJrLo%#ISO zZWXp`*IO><9#evzLqV%cup0Nd-(Ok+S&zfYw~YdNE_MDiF`GCcUDDgGm*_5T=*Ho4 zo(2psa?+T*V6Dy-cChSXF9nc!zuhnNP!T2+5_x|JRC{bsg8gLK(cYuFDZeTCliB9$ zTAW82nd#>|7BJOeMdRCxXwt0k0J~+*{T8CPkjWt7ASc4KSF=J0mXK5Paqeq}??qSj zYb+I!r}`_hz=i4kwY7C1raNj)EPEEbm6P4$+sjAa6nIYf&Yv)x7FB+RHT!-L%J%kd z1AF24Twu3Bey{#&D7TVv#4cJ`uqeT{Tp=f2t{_8H}&ZshL4!WGmDzCK-Re^z;Md-Yf1{6S_K zi2(>Rky@;*qN6L`xp{5YD0}UVKmYhhX!Gk^N09r);cLw6_gGLV*E%(d;5ie|*@ks7 zEMEq5-Q{eeo*%VdnfhtVGGVOV7h<5Un~0#xEy1(<>2duIS9`ly(=ByqYTa3iyvMl_ z&y&_;PuQ|&x#uqtrf96!=aLrqm3!w06v|u z*z(3ZgbO92@O~*El$p0IsGc$taokL|KPbH(WTn?vEl3hR^h2HBBhz1dybkzJf^On7 z+Qw7SC)q@)dcxMNFt}XL#NI(3d3C3ERKp30!Xs*%7UjWpWcu1cJz9;H2Tx&bz0%l|CR_hW0?`+4QIv)>)_idHh)*;{q?SZ<9RGHq= zC+>GF$-M4uD~{{_Q{ElKt_PLDpr+z-=*d!F)!4EmNQV8#(>5$BQ1IJD{Fb{1L)W`P z6HsS==2WOp{p&rW>1sQj^UL`yt?L4GAn2Rg{2&9DHW3x5{k}Uh+M4sgTRX$+`|p;g zT|UvqNTIDS9P|1lr4-s8umU!x0=7yo%R(<2MolO?$Lr;7j8XiTEtd7qF0LjQ-k_hd zUhmn$X(=vy@;Ek6y<6!NhRUC>Uk8KrO4DH- z?}zNXCG*2dJ4E(s_v1=L%WQX1jASZ*IFvYyHusXJ^Kxg46!+H$ta~Q*PIme4UQ3p* z=XF}wD`U;4G+e7Ue2aiYf~?g*9d2!GWIRD}1-@ueKc8I-9zOX6!s~S?hErx&p4~^j+G&g32^2V_n&X$J>Tys%_(gbp z#q|{G;+ZIo1WzPSyCLcTY5Cs&yVb?A5G?sksN@Tcbrs-FVg{eZWHFH1eJDS3C|8JA zZ@BH8t9}qh=)N)>UnLLs^kh}mF!;ECNM(MN_>st^BJWL8wD%GR+&m+Q0~CaXOgCpzBf$XgFTEs)_>UHvTt^{6NyLs zZAUZTYFdt51lYZyMzL`0@#o26W?>y8wlx#ac1qd*Yhs1GPj#J*?{`1WP%N znCm^QX&KLbEa4a2fNoMNoIAizC<}&IzwxXDn@gEhOs#pl=MGfEo&O%a;Enk$#K% zjPpBIM2n(p(PoOTWhOFY#l3jo*N2~uZKTWOzjqmx!2C_N`@kdcYsc3CAp$pQF;U8G z3n`)mLH7$SWDoWwf=EewG|c7h-abS9n&z32EJtyk89OkZ)Ma|%wZqz?>2}RkR&6nd zD(0T}<^!HJE3V*FJ!VoyF<*;~mqH}{NdjEzRyGGf#LL$S(mZk>VJCgi+WaY|AD?cu z3G2|mSB^#cMP`8O<2~!@A&=jcFX>SE52)X0%xB z=wDMK2JT5V%pUL8*HqMPLOofOg>o!aRBF%T`J#9>$xYK`XHVAP1wgxA93iiB2K__h z<@dR1u49c4pNJdeF)mOD(HpP~3A^QQNl-XeJ`C&gsYCe5k4WY1UkB>3zel{hDi*V~ z&4_gxlJiseJ<8x;>wn!B++wLu4}A@Lo6-BZ7F7Ln`%R|tEMiVu1T?(tw~;P-vGfdx zzeI5w%5u3JBdo~nnx=MP7sZuE#{*Vyfzra9ak}Y*@&?8j-XTOmNqg(UpKa&TfcY^d*q`1k`^kbq=%rkHdS}OzFV`y ziI~7;JA#`MFg-&>Rf(@3kkFBbVn{X;zEf z4~|4QjHz6R2q)qD(;TbdB|Lg`3T(zy zu!~%*DT$XLc{|!ySzt}XcWJf6b31rgZ6*-NXi&64?djsOP^qm*d%6Cj6=R`}UaeiA z*QmY*zNv>Ygz@eknLO{=edj&a5CJo|@&3W$`ZPefjjH*yvHk-Qi>QPZ>wBR8BNS7f zb@Q@JT{)~CZ%7T&L0ciGd+UD_op`X29`SJHW=ESa;*q~Z*3aEA5&Q~#05FdP&u_l2 z7)v3OLOctld#sHNHzX_Y<0+g441})V?3ux+Fwqvzmws4J+PSbRxwGz?#>SZ#xU8xw zrN@*Q^J7;QbTc!h`pwP+9WSRX?oI9Lr5w_vx&XX^KG>f^+H=?E8sW%6#rgC zaQ9WVHXyCG?E|2wv^2~h5iZ`Qc;|;FM29p-|Dc~r)+(KlTcHD! zyZW5teQ+p3i*R%mXd%Uub_qVPbCLLNU6WAAe9r9GCLR!F@vvr#e zFw1Q7O?Xk2D_cQe#D`*w|0OuAprIc8BG&iasCyFjMg=&fL|Jzab3d_xWdRLn-}dB+ zA#ck!s#fgZ`I%|j9hopr^fWnt1dI}VbI9y-5^RR{O@_WpdP=@Efa|gplnZ>gIiv!< zJ~nxr-WfQEn%JvwP`TURYr01s=OK}u^Wt9My9{JqT-TVL@d>I4`tS{cH)AhX>nXdz zj}&(Or&jUO!1Z47b&&}lIAh68x4F8K&8EK#FHDgkJQ@0R<@V$CSqOb^^}#y#7$r;_ ze3c13MtPoqlKX{oEE{_rk|uj&2@&t@OYrzxs7bObz<+PK7yjVKNRdkMm3}ippDa;H zl61;gJR6SS%NbNMFGv}O)4@lf?5!m93Rudy$js()vY*dhdQY4m2-j*C?#q>g#j<~~ zPerK4=Md|;u_ZUxeGj9!FfIXq z)lZ=EKzdvnzwhK}Yv;YVQDhL%XiajfL+a0gi)N5$0`3blPi+jUlP!HIqXO_j9+9UJ zH?UZ`KVG9?z9e4^_Cp$#n2!N=`WZrzsaV>sp4w}Fztzb9y4g*O=5ebYWv*nk*_RXo zA!3zBTP1}QkiU1F5YK}z7HZ?%|I`RB6SDAHiU%Tb{&?3Ro$G$moY^OIxnpoKB+K-E zm(6cvw`X`yHI8mLL2V%O2jeHLX-)LQ-@hSUC>Pa$LQheTMvFz}aub%Zv@WijRcB9H z5uQuJYHD0VKUGw!(7n@U*R&^mMZXFT+(C#ep0SOU!;o)8L_9HmghpoEvws>M&mVkN z*WWLc%v8F>J-#1Z?N=2ZdNU4AvjPz@^;Jg8ua2zTk119~hUhksZ8?^qqUf&>^#Un9 zaP7Yqxa7wf-Qv_fTcQltS!ATtWcSl2Ue9cV!9Bu6?Y9Q4SvRa>SfND;zBDr%@O&?h*rN^i$;v%_r;AFd_ey7_>{+ik};w z@QP??{NOC1>y|>k@UBOEgrL}E=D}fgpq83M(7a(dOJMQb<=;mpm17-a*M8rI-Scog zuR9x$lYB;;MEa3Tp5O^LA@1XV2Ke>M<+R?7N}>)a(f4GRC1;=%gI=8|q1PS@&qc?x z;Y?nt1@Gh6%Z~-C^}(<&71@!Kd(#Tsx-Yh`LL5Yw)oF@?_u5`w=btYI{9mKb`Hp`q zrmkM(btJvx9QE}+1ixJ8wD^2t+2?9~H(QJZ(qYY0|00dk8{15CWH`1hFndHgQxt=J`on@nmTq>3%#QnY7^Xdv+I?y0S@ zdnz#A>`1dU0wzV*%?w>m-@`TwS(kVGGb6)j8j zZ6n#l;qEAY60E0<52NDqK78FqB%Tq#fv?MNp%C{zwUiuHk1kHFBEaRXBCp8JqN870 zcsxUS^F5c{s{Cud(Z{}{C0F56yIaEECQFYf-Q0qDQ3auMZXUz-)=JI2P`j;3$)P)z z)WKEndtl31=3v!5rj|L*ke|Q6bFDG1G#kTf5}f6&m+ zS#|O~#70n`-k_0CqcF!Ldnq+!R-SgUUwrF*g4WP=Jv1Vjj@{U{z49(q%hx7@7X z2^V;%1h#OWt1J>4^X0Zan$rY@ej4l-qfvdTIU~npN_m*2rx&f!lm?i)$zrXg+>PcM z;b~xj$>P4-frE)!n80aXKl%N7^Js_Yu5rJlwp;GMzsD;(U4QWiR;|Q(TAY?_L%U2c zgbAMa-PTnPIhc69=}JuI^RM*O5<%vgxc#e|(iao$3ofBz?hq+HjKuGoydA#G_2z8E zHyy1+V8i3N*{1?VXtd>=>6ox`kY7BLk-^RV+Klbzlk=0aJ)4t&X1wi!NI(p|@pg=F z!1gVayUw?%em|GdJR~hiM8ekg^8Th{y-OxxUQAZEV3%-YTw%cG;C4K&KhvG1?B45U zp9)miPdI7s-Ec^#b6n{@Ld6wY3Ugx5k`*e{nA5*(s}cQ!*g6x z4JwTi1RmF|>#CNiTWJ96u31O{nreK5MGBH$O$P>dn;QAU#@(=kh5bYf{RFll;p^tg z_n>&8X?y3jN0ddtxPW5?TEbVwynB*UzWbVK(C^{$iR#4H9nnRPJAIx{4e1^uU#p%I zQirUqn?%M}pF6tGy4&LGIJ{7LhS)z$e?#kcHnW_flNPAGz|y`_mLKb!flx2JCjX$% zjmqor+6s#Hyp7Xab&&O_Rc(qe+^7X}bh{wVdnn((EP~CRURTd#7u|rHtRIWL+rlp1 zpP_Xg)pCfWWIDK1KiOz4aNJ3zDRjQi4JO37s}y>BgaQO#(%?3UG9G_fL3?DyXIYHv zsA`XaDYs-cud_Y>>$66d=bKaiUGyQ<@a!#NcLMpLQX~ z6#knDA&~;2Uh_1JZJOKrIo}I{k|o!T&ftz=X{PiJsj2Q?JKFW6k3<#aaz0>;re3W& zOPUb|8N)pZyiO_?6q=4&t|`kUyQSe!Gc z2MXLSs;NzZ(@Um6B zB$3A#-GF4|?!D3acO>nSkx;kkY*~TlR9^0WL_dJRna?Yqptty#{E$_ zq#61y`HMP<5&q|DIQGJLidqJZkDq}n^C)#+5pt;;Uk^Mq!k%xfvF$6W4;dq41Rfj zl#R+*jZ>eS=jeGh)p9qD)Q3)WS|*pN=`>C)9L*WGR)F{XShve=v3TnP1!@Z>@-KBJ zkxTW9r^O?onx0-I8pI#PSUBfK0gA`%c+L*RU!;p6cgbYE#`kBqiy(9B3#Fe8aL$U= z%AiZ?%^gW@q7EdK8mY$34{Y@!2IB7h{{4eqon2ju)$Dep6Jk2np`Mdj;Ls#KKFO!i zWl~h5>aCv|apLm>N{60zQaLnKludFRM^c=$mWwu44U^8qp(H8^#fZX(v2 zWY`yLX=!I1enVR9g9i8D65J)Y1qtr%GPn&EAV_cx7J`M~ z?iO?yEI7esaCaHt&3Ep(@0|C3?Vr24tE#)JYON~tT)-?}8Fl9qAu+NLPgPSYZzBWS zk+o5V1+2{y7`4}iS#8(Q5!N{J%JbhLr4uTAw?*Jq-twcMOEhEy^XuHW*yDcD|FAgU zv9LJ&{P|Pyb<0K0J4oSza;C@3jI8T`xPFc}XXLcw6zlzZ0EDfAjm>)HKF_+tV;i#8 zvp(m3AZNF?Lx|hye>)}&T^SE}zYK2v@dAS`*p7zJGyEEK-AB8qi)+HE(U-}pZc*T! zKo7(EEdIv8f|(w)-RxVSh1aNcG6?+*?}|E&Q5s<$x4bVgo$`J4GkWS-*OfryX&@}4 z3Bw-qs&uv1vcGtvvJ|`zse_(ONIH=jeeF}uric&op2@gB0a1^@>LIL*+}>@+u@%

    ?l$Yj3w6^RDyt;jC(x#%%(~ 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 JD-^9m{|7p{BxL{q literal 0 HcmV?d00001 diff --git a/website/src/_includes/blog_previews/20240814.html b/website/src/_includes/blog_previews/20240814.html new file mode 100644 index 0000000000..56d9711098 --- /dev/null +++ b/website/src/_includes/blog_previews/20240814.html @@ -0,0 +1,18 @@ +

    SimpleX Chat vision and funding 2.0: past, present, future.

    + +

    Announcing the investment from Jack Dorsey and Asymmetric.

    + +

    v6.0 is released:

    + +
      +
    • Private message routing - enabled by default
    • +
    • +

      New chat experience:

      +
        +
      • connect to your friends faster.
      • +
      • new reachable interface.
      • +
      • and much more!
      • +
      +
    • +
    • Improved networking and battery usage
    • +
    \ No newline at end of file diff --git a/website/src/css/blog.css b/website/src/css/blog.css index 897fbafb10..dcbe842785 100644 --- a/website/src/css/blog.css +++ b/website/src/css/blog.css @@ -232,4 +232,10 @@ h6 { #article ol>li::marker { font-weight: 500; +} + +#article blockquote { + padding-left: 1em; + border-left: 2px solid #c0c0c0; + font-style: italic; } \ No newline at end of file From 789c762c816a4b2c80d48959d741c86260dfb1e6 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:59:53 +0000 Subject: [PATCH 002/440] android: hide keyboard when not needed (#4689) * android: hide keyboard when not needed * revert some parts * more places * unused * delay * comment --- .../platform/PlatformTextField.android.kt | 12 ++++++++-- .../common/views/helpers/SearchTextField.kt | 24 +++++++++++++++++-- .../common/views/usersettings/SettingsView.kt | 4 ++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt index ce4bada6e1..0b17a3aadf 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt @@ -36,11 +36,12 @@ import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel import chat.simplex.common.ui.theme.CurrentColors import chat.simplex.common.views.chat.* -import chat.simplex.common.views.helpers.SharedContent -import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.filter import java.lang.reflect.Field import java.net.URI @@ -84,6 +85,13 @@ actual fun PlatformTextField( freeFocus = true } } + LaunchedEffect(Unit) { + snapshotFlow { ModalManager.start.modalCount.value } + .filter { it > 0 } + .collect { + freeFocus = true + } + } val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl AndroidView(modifier = Modifier, factory = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt index 896d929480..c6d5d6ad16 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt @@ -10,7 +10,6 @@ import androidx.compose.material.TextFieldDefaults.indicatorLine import androidx.compose.material.TextFieldDefaults.textFieldWithLabelPadding import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -25,10 +24,12 @@ import androidx.compose.ui.text.input.* import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.launch -@OptIn(ExperimentalComposeUiApi::class) @Composable fun SearchTextField( modifier: Modifier, @@ -50,6 +51,25 @@ fun SearchTextField( keyboard?.show() } } + if (appPlatform.isAndroid) { + LaunchedEffect(Unit) { + val modalCountOnOpen = ModalManager.start.modalCount.value + launch { + snapshotFlow { ModalManager.start.modalCount.value } + .filter { it > modalCountOnOpen } + .collect { + keyboard?.hide() + } + } + } + KeyChangeEffect(chatModel.chatId.value) { + if (chatModel.chatId.value != null) { + // Delay is needed here because when ChatView is being opened and keyboard is hiding, bottom sheet (to choose attachment) is visible on a screen + delay(300) + keyboard?.hide() + } + } + } DisposableEffect(Unit) { onDispose { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 68315aec2c..02716fa17e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -100,10 +100,14 @@ fun SettingsLayout( ) { val scope = rememberCoroutineScope() val closeSettings: () -> Unit = { scope.launch { drawerState.close() } } + val view = LocalMultiplatformView() if (drawerState.isOpen) { BackHandler { closeSettings() } + LaunchedEffect(Unit) { + hideKeyboard(view) + } } val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current From 1e4479f736cb99478d824914face60db8acb34a1 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 14 Aug 2024 18:08:08 +0000 Subject: [PATCH 003/440] android, desktop: fix loading more items when fast switching chats (#4685) Co-authored-by: Evgeny Poberezkin --- .../kotlin/chat/simplex/common/App.kt | 2 +- .../simplex/common/views/chat/ChatView.kt | 33 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 834b2b95ba..1486fa8314 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -333,7 +333,7 @@ fun StartPartOfScreen(settingsState: SettingsViewState) { fun CenterPartOfScreen() { val currentChatId = remember { ChatModel.chatId } LaunchedEffect(Unit) { - snapshotFlow { currentChatId } + snapshotFlow { currentChatId.value } .distinctUntilChanged() .collect { if (it != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 3a062cb757..a5541a315b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -86,21 +86,17 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - .collect { chatId -> markUnreadChatAsRead(chatId) showSearch.value = false + selectedChatItems.value = null } } } - KeyChangeEffect(chatModel.chatId.value) { - if (chatModel.chatId.value != null) { - selectedChatItems.value = null - } - } val view = LocalMultiplatformView() val chatRh = remoteHostId.value // We need to have real unreadCount value for displaying it inside top right button // Having activeChat reloaded on every change in it is inefficient (UI lags) val unreadCount = remember { derivedStateOf { - chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }?.chatStats?.unreadCount ?: 0 + chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == activeChatInfo.value?.id }?.chatStats?.unreadCount ?: 0 } } val clipboard = LocalClipboardManager.current @@ -257,9 +253,9 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - } } }, - loadPrevMessages = { - val c = chatModel.getChat(chatInfo.id) - if (chatModel.chatId.value != chatInfo.id) return@ChatLayout + loadPrevMessages = { chatId -> + val c = chatModel.getChat(chatId) + if (chatModel.chatId.value != chatId) return@ChatLayout val firstId = chatModel.chatItems.value.firstOrNull()?.id if (c != null && firstId != null) { withBGApi { @@ -561,7 +557,7 @@ fun ChatLayout( back: () -> Unit, info: () -> Unit, showMemberInfo: (GroupInfo, GroupMember) -> Unit, - loadPrevMessages: () -> Unit, + loadPrevMessages: (ChatId) -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit, deleteMessages: (List) -> Unit, receiveFile: (Long) -> Unit, @@ -593,12 +589,11 @@ fun ChatLayout( ) { val scope = rememberCoroutineScope() val attachmentDisabled = remember { derivedStateOf { composeState.value.attachmentDisabled } } - Box( Modifier .fillMaxWidth() .desktopOnExternalDrag( - enabled = !attachmentDisabled.value && rememberUpdatedState(chatInfo.value).value?.userCanSend == true, + enabled = remember(attachmentDisabled.value, chatInfo.value?.userCanSend) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.userCanSend == true) }.value, onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) }, onImage = { // TODO: file is not saved anywhere?! @@ -924,7 +919,7 @@ fun BoxWithConstraintsScope.ChatItemsList( linkMode: SimplexLinkMode, selectedChatItems: MutableState?>, showMemberInfo: (GroupInfo, GroupMember) -> Unit, - loadPrevMessages: () -> Unit, + loadPrevMessages: (ChatId) -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit, deleteMessages: (List) -> Unit, receiveFile: (Long) -> Unit, @@ -964,7 +959,7 @@ fun BoxWithConstraintsScope.ChatItemsList( } } - PreloadItems(listState, ChatPagination.UNTIL_PRELOAD_COUNT, loadPrevMessages) + PreloadItems(chatInfo.id, listState, ChatPagination.UNTIL_PRELOAD_COUNT, loadPrevMessages) Spacer(Modifier.size(8.dp)) val reversedChatItems by remember { derivedStateOf { chatModel.chatItems.asReversed() } } @@ -975,6 +970,7 @@ fun BoxWithConstraintsScope.ChatItemsList( scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.lastIndex, index + 1), -maxHeightRounded) } } } + // TODO: Having this block on desktop makes ChatItemsList() to recompose twice on chatModel.chatId update instead of once LaunchedEffect(chatInfo.id) { var stopListening = false snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastIndex } @@ -1327,14 +1323,17 @@ fun BoxWithConstraintsScope.FloatingButtons( @Composable fun PreloadItems( + chatId: String, listState: LazyListState, remaining: Int = 10, - onLoadMore: () -> Unit, + onLoadMore: (ChatId) -> Unit, ) { // Prevent situation when initial load and load more happens one after another after selecting a chat with long scroll position from previous selection val allowLoad = remember { mutableStateOf(false) } + val chatId = rememberUpdatedState(chatId) + val onLoadMore = rememberUpdatedState(onLoadMore) LaunchedEffect(Unit) { - snapshotFlow { chatModel.chatId.value } + snapshotFlow { chatId.value } .filterNotNull() .collect { allowLoad.value = listState.layoutInfo.totalItemsCount == listState.layoutInfo.visibleItemsInfo.size @@ -1354,7 +1353,7 @@ fun PreloadItems( } .filter { it > 0 } .collect { - onLoadMore() + onLoadMore.value(chatId.value) } } } From f6ef57534f347b492f7e2ea4d325c8ee71a5a271 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Wed, 14 Aug 2024 21:29:13 +0300 Subject: [PATCH 004/440] ios: add text to share extension link preview (#4683) * ios: add text to share extension link preview * remove maxHeight contraint --- .../Chat/ComposeMessage/ComposeLinkView.swift | 2 +- apps/ios/SimpleX SE/ShareView.swift | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift index 66cb9edcf8..f7f1a89299 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift @@ -55,7 +55,7 @@ struct ComposeLinkView: View { .foregroundColor(theme.colors.secondary) } .padding(.vertical, 5) - .frame(maxWidth: .infinity, minHeight: 60, maxHeight: 60) + .frame(maxWidth: .infinity, minHeight: 60) } } } diff --git a/apps/ios/SimpleX SE/ShareView.swift b/apps/ios/SimpleX SE/ShareView.swift index 51a2f1f3a4..1f502ffcff 100644 --- a/apps/ios/SimpleX SE/ShareView.swift +++ b/apps/ios/SimpleX SE/ShareView.swift @@ -132,7 +132,7 @@ struct ShareView: View { switch content { case let .image(preview, _): imagePreview(preview) case let .movie(preview, _, _): imagePreview(preview) - case let .url(linkPreview): imagePreview(linkPreview.image) + case let .url(preview): linkPreview(preview) case let .data(cryptoFile): previewArea { Image(systemName: "doc.fill") @@ -160,6 +160,29 @@ struct ShareView: View { } } + @ViewBuilder private func linkPreview(_ linkPreview: LinkPreview) -> some View { + previewArea { + HStack(alignment: .center, spacing: 8) { + if let uiImage = UIImage(base64Encoded: linkPreview.image) { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 80, maxHeight: 60) + } + VStack(alignment: .center, spacing: 4) { + Text(linkPreview.title) + .lineLimit(1) + Text(linkPreview.uri.absoluteString) + .font(.caption) + .lineLimit(1) + .foregroundColor(.secondary) + } + .padding(.vertical, 5) + .frame(maxWidth: .infinity, minHeight: 60) + } + } + } + @ViewBuilder private func previewArea(@ViewBuilder content: @escaping () -> V) -> some View { HStack(alignment: .center, spacing: 8) { content() From 46a60d979b32c34f8a1473bd2a78da424e98b203 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Wed, 14 Aug 2024 22:14:40 +0300 Subject: [PATCH 005/440] ios: fix messages reseting position, when app is brought from background to foreground (#4686) * ios: fix messages reseting position, when app is brought back to foreground * minor --- apps/ios/Shared/Model/SimpleXAPI.swift | 6 ++++-- apps/ios/Shared/SimpleXApp.swift | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 76d043b2aa..df76409316 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -332,13 +332,15 @@ func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, sear throw r } -func loadChat(chat: Chat, search: String = "") async { +func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async { do { let cInfo = chat.chatInfo let m = ChatModel.shared let im = ItemsModel.shared m.chatItemStatuses = [:] - await MainActor.run { im.reversedChatItems = [] } + if clearItems { + await MainActor.run { im.reversedChatItems = [] } + } let chat = try await apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search) await MainActor.run { im.reversedChatItems = chat.chatItems.reversed() diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index fe306b944f..c40f9edf45 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -136,7 +136,7 @@ struct SimpleXApp: App { chatModel.updateChats(with: chats) if let id = chatModel.chatId, let chat = chatModel.getChat(id) { - Task { await loadChat(chat: chat) } + Task { await loadChat(chat: chat, clearItems: false) } } if let ncr = chatModel.ntfContactRequest { chatModel.ntfContactRequest = nil From 5bff5a855ef98cdcca591c0e60f779dca47e4ef0 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 14 Aug 2024 20:30:11 +0100 Subject: [PATCH 006/440] core: 6.0.1.0 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index aaf35b4011..80994a6a59 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.0.7 +version: 6.0.0.8 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index a166d1c1b4..4566048091 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.0.0.7 +version: 6.0.0.8 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From edfcced1fa2b4b6497c01edcf27110dabb4a9986 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 14 Aug 2024 21:01:28 +0100 Subject: [PATCH 007/440] website: translation (#4692) * Translated using Weblate (Ukrainian) 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/uk/ * space --------- Co-authored-by: Bezruchenko Simon --- website/langs/uk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/langs/uk.json b/website/langs/uk.json index f37feb55ef..cad05772da 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -78,7 +78,7 @@ "smp-protocol": "Протокол SMP", "chat-protocol": "Протокол чату", "donate": "Пожертвувати", - "copyright-label": "© 2020-2023 SimpleX | Проект з відкритим кодом", + "copyright-label": "© 2020-2024 SimpleX | Проект з відкритим кодом", "simplex-chat-protocol": "Протокол чату SimpleX", "terminal-cli": "Термінал CLI", "hero-header": "Приватність переосмислена", From 25938af62f734324a091bf25865e16da3e829ed9 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 14 Aug 2024 21:12:50 +0100 Subject: [PATCH 008/440] ui: translations (#4654) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Dutch) Currently translated at 100.0% (2030 of 2030 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Vietnamese) Currently translated at 28.5% (580 of 2030 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% (106 of 2030 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 100.0% (2030 of 2030 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Vietnamese) Currently translated at 28.5% (580 of 2030 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% (106 of 2030 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% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2030 of 2030 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 29.3% (595 of 2030 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2030 of 2030 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2030 of 2030 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Vietnamese) Currently translated at 29.9% (607 of 2030 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 30.5% (621 of 2030 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (French) Currently translated at 97.4% (1981 of 2032 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 97.8% (1989 of 2032 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 98.7% (2007 of 2032 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% (2032 of 2032 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% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (German) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (French) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (French) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Italian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Vietnamese) Currently translated at 31.3% (638 of 2032 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% (107 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * process localizations * update ru --------- Co-authored-by: summoner001 Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: billy appetie Co-authored-by: mlanp Co-authored-by: No name Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com> Co-authored-by: jonnysemon Co-authored-by: 大王叫我来巡山 Co-authored-by: Random Co-authored-by: Bezruchenko Simon --- .../de.xcloc/Localized Contents/de.xliff | 2 +- .../es.xcloc/Localized Contents/es.xliff | 20 +- .../fr.xcloc/Localized Contents/fr.xliff | 88 ++++++- .../hu.xcloc/Localized Contents/hu.xliff | 34 +-- .../it.xcloc/Localized Contents/it.xliff | 2 +- .../SimpleX SE/fr.lproj/Localizable.strings | 12 + apps/ios/de.lproj/Localizable.strings | 2 +- apps/ios/es.lproj/Localizable.strings | 20 +- apps/ios/fr.lproj/Localizable.strings | 200 ++++++++++++++- apps/ios/hu.lproj/Localizable.strings | 34 +-- apps/ios/it.lproj/Localizable.strings | 2 +- .../commonMain/resources/MR/ar/strings.xml | 2 + .../commonMain/resources/MR/de/strings.xml | 2 + .../commonMain/resources/MR/es/strings.xml | 32 +-- .../commonMain/resources/MR/fr/strings.xml | 96 +++++-- .../commonMain/resources/MR/hu/strings.xml | 22 +- .../commonMain/resources/MR/in/strings.xml | 2 + .../commonMain/resources/MR/it/strings.xml | 4 +- .../commonMain/resources/MR/nl/strings.xml | 2 +- .../commonMain/resources/MR/uk/strings.xml | 236 +++++++++++++++++- .../commonMain/resources/MR/vi/strings.xml | 69 +++++ .../resources/MR/zh-rCN/strings.xml | 2 + 22 files changed, 736 insertions(+), 149 deletions(-) diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 6461ee91bc..29adfbabf0 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1057,7 +1057,7 @@ Better networking - Bessere Vernetzung + Kontrollieren Sie Ihr Netzwerk No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 38d8c82fd1..8c8416857f 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -947,7 +947,7 @@ Archive contacts to chat later. - Archivar contactos para charlar más tarde. + Archiva contactos para charlar más tarde. No comment provided by engineer. @@ -1102,7 +1102,7 @@ Blur for better privacy. - Difuminar para más privacidad. + Difumina para mayor privacidad. No comment provided by engineer. @@ -1617,7 +1617,7 @@ This is your own one-time link! Connection and servers status. - Estado de conexión y servidores. + Estado de tu conexión y servidores. No comment provided by engineer. @@ -4819,7 +4819,7 @@ Requiere activación de la VPN. Pending - Pendiente + Pendientes No comment provided by engineer. @@ -5125,7 +5125,7 @@ Actívalo en ajustes de *Servidores y Redes*. Proxied - Con proxy + Como proxy No comment provided by engineer. @@ -5906,7 +5906,7 @@ Actívalo en ajustes de *Servidores y Redes*. Sent directly - Enviado directamente + Directamente No comment provided by engineer. @@ -5941,7 +5941,7 @@ Actívalo en ajustes de *Servidores y Redes*. Sent via proxy - Enviado vía proxy + Mediante proxy No comment provided by engineer. @@ -6538,7 +6538,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The app will ask to confirm downloads from unknown file servers (except .onion). - La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto .onion). + La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto si son .onion). No comment provided by engineer. @@ -7087,7 +7087,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use the app with one hand. - Usa la aplicación con una mano. + Usa la aplicación con una sola mano. No comment provided by engineer. @@ -8208,7 +8208,7 @@ Los servidores SimpleX no pueden ver tu perfil. expired - expirado + expirados No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 48a58b075e..c05098980e 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -524,7 +524,7 @@ Abort - Annuler + Abandonner No comment provided by engineer. @@ -752,6 +752,7 @@ Allow calls? + Autoriser les appels ? No comment provided by engineer. @@ -941,15 +942,17 @@ Archive and upload - Archiver et transférer + Archiver et téléverser No comment provided by engineer. Archive contacts to chat later. + Archiver les contacts pour discuter plus tard. No comment provided by engineer. Archived contacts + Contacts archivés No comment provided by engineer. @@ -1054,6 +1057,7 @@ Better networking + Meilleure gestion de réseau No comment provided by engineer. @@ -1098,6 +1102,7 @@ Blur for better privacy. + Rendez les images floues et protégez-les contre les regards indiscrets. No comment provided by engineer. @@ -1152,6 +1157,7 @@ Calls prohibited! + Les appels ne sont pas autorisés ! No comment provided by engineer. @@ -1161,10 +1167,12 @@ Can't call contact + Impossible d'appeler le contact No comment provided by engineer. Can't call member + Impossible d'appeler le membre No comment provided by engineer. @@ -1179,6 +1187,7 @@ Can't message member + Impossible d'envoyer un message à ce membre No comment provided by engineer. @@ -1294,6 +1303,7 @@ Chat database exported + Exportation de la base de données des discussions No comment provided by engineer. @@ -1318,6 +1328,7 @@ Chat list + Liste de discussion No comment provided by engineer. @@ -1407,6 +1418,7 @@ Color chats with the new themes. + Colorez vos discussions avec les nouveaux thèmes. No comment provided by engineer. @@ -1426,7 +1438,7 @@ Completed - Complété + Complétées No comment provided by engineer. @@ -1451,6 +1463,7 @@ Confirm contact deletion? + Confirmer la suppression du contact ? No comment provided by engineer. @@ -1510,6 +1523,7 @@ Connect to your friends faster. + Connectez-vous à vos amis plus rapidement. No comment provided by engineer. @@ -1588,6 +1602,7 @@ Il s'agit de votre propre lien unique ! Connecting to contact, please wait or check later! + Connexion au contact, veuillez patienter ou vérifier plus tard ! No comment provided by engineer. @@ -1602,6 +1617,7 @@ Il s'agit de votre propre lien unique ! Connection and servers status. + État de la connexion et des serveurs. No comment provided by engineer. @@ -1656,6 +1672,7 @@ Il s'agit de votre propre lien unique ! Contact deleted! + Contact supprimé ! No comment provided by engineer. @@ -1670,6 +1687,7 @@ Il s'agit de votre propre lien unique ! Contact is deleted. + Le contact est supprimé. No comment provided by engineer. @@ -1684,6 +1702,7 @@ Il s'agit de votre propre lien unique ! Contact will be deleted - this cannot be undone! + Le contact sera supprimé - il n'est pas possible de revenir en arrière ! No comment provided by engineer. @@ -1703,6 +1722,7 @@ Il s'agit de votre propre lien unique ! Conversation deleted! + Conversation supprimée ! No comment provided by engineer. @@ -1792,7 +1812,7 @@ Il s'agit de votre propre lien unique ! Created - Créé + Créées No comment provided by engineer. @@ -2046,6 +2066,7 @@ Il s'agit de votre propre lien unique ! Delete contact? + Supprimer le contact ? No comment provided by engineer. @@ -2155,6 +2176,7 @@ Il s'agit de votre propre lien unique ! Delete up to 20 messages at once. + Supprimez jusqu'à 20 messages à la fois. No comment provided by engineer. @@ -2164,11 +2186,12 @@ Il s'agit de votre propre lien unique ! Delete without notification + Supprimer sans notification No comment provided by engineer. Deleted - Supprimé + Supprimées No comment provided by engineer. @@ -2253,6 +2276,7 @@ Il s'agit de votre propre lien unique ! Developer options + Options pour les développeurs No comment provided by engineer. @@ -3815,6 +3839,7 @@ Erreur : %2$@ It protects your IP address and connections. + Il protège votre adresse IP et vos connexions. No comment provided by engineer. @@ -3881,6 +3906,7 @@ Voici votre lien pour le groupe %@ ! Keep conversation + Garder la conversation No comment provided by engineer. @@ -4060,6 +4086,7 @@ Voici votre lien pour le groupe %@ ! Media & file servers + Serveurs de fichiers et de médias No comment provided by engineer. @@ -4154,6 +4181,7 @@ Voici votre lien pour le groupe %@ ! Message servers + Serveurs de messages No comment provided by engineer. @@ -4368,6 +4396,7 @@ Voici votre lien pour le groupe %@ ! New chat experience 🎉 + Nouvelle expérience de discussion 🎉 No comment provided by engineer. @@ -4402,6 +4431,7 @@ Voici votre lien pour le groupe %@ ! New media options + Nouvelles options de médias No comment provided by engineer. @@ -4574,6 +4604,7 @@ Nécessite l'activation d'un VPN. Only delete conversation + Ne supprimer que la conversation No comment provided by engineer. @@ -4813,10 +4844,12 @@ Nécessite l'activation d'un VPN. Play from the chat list. + Aperçu depuis la liste de conversation. No comment provided by engineer. Please ask your contact to enable calls. + Veuillez demander à votre contact d'autoriser les appels. No comment provided by engineer. @@ -5122,6 +5155,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Reachable chat toolbar + Barre d'outils accessible No comment provided by engineer. @@ -5397,6 +5431,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Reset all hints + Rétablir tous les conseils No comment provided by engineer. @@ -5531,6 +5566,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save and reconnect + Sauvegarder et se reconnecter No comment provided by engineer. @@ -5675,7 +5711,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Secured - Sécurisé + Sécurisées No comment provided by engineer. @@ -5755,7 +5791,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Send link previews - Envoi d'aperçus de liens + Aperçu des liens No comment provided by engineer. @@ -5765,6 +5801,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Send message to enable calls. + Envoyer un message pour activer les appels. No comment provided by engineer. @@ -5929,7 +5966,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Server requires authorization to upload, check password - Le serveur requiert une autorisation pour uploader, vérifiez le mot de passe + Le serveur requiert une autorisation pour téléverser, vérifiez le mot de passe server test error @@ -6054,6 +6091,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Share from other apps. + Partager depuis d'autres applications. No comment provided by engineer. @@ -6093,7 +6131,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Show last messages - Voir les derniers messages + Aperçu des derniers messages No comment provided by engineer. @@ -6108,7 +6146,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Show preview - Afficher l'aperçu + Aperçu affiché No comment provided by engineer. @@ -6228,6 +6266,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Some file(s) were not exported: + Certains fichiers n'ont pas été exportés : No comment provided by engineer. @@ -6237,6 +6276,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Some non-fatal errors occurred during import: + L'importation a entraîné des erreurs non fatales : No comment provided by engineer. @@ -6346,7 +6386,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Subscribed - Inscrit + Inscriptions No comment provided by engineer. @@ -6376,6 +6416,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. TCP connection + Connexion TCP No comment provided by engineer. @@ -6739,6 +6780,7 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Toggle chat list: + Afficher la liste des conversations : No comment provided by engineer. @@ -6748,6 +6790,7 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Toolbar opacity + Opacité de la barre d'outils No comment provided by engineer. @@ -6934,6 +6977,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Update settings? + Mettre à jour les paramètres ? No comment provided by engineer. @@ -6958,7 +7002,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Upload file - Transférer le fichier + Téléverser le fichier server test step @@ -7043,6 +7087,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Use the app with one hand. + Utiliser l'application d'une main. No comment provided by engineer. @@ -7112,7 +7157,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Video will be received when your contact completes uploading it. - La vidéo ne sera reçue que lorsque votre contact aura fini de la transférer. + La vidéo ne sera reçue que lorsque votre contact aura fini la mettre en ligne. No comment provided by engineer. @@ -7404,6 +7449,7 @@ Répéter la demande d'adhésion ? You can change it in Appearance settings. + Vous pouvez choisir de le modifier dans les paramètres d'apparence. No comment provided by engineer. @@ -7443,6 +7489,7 @@ Répéter la demande d'adhésion ? You can send messages to %@ from Archived contacts. + Vous pouvez envoyer des messages à %@ à partir des contacts archivés. No comment provided by engineer. @@ -7472,6 +7519,7 @@ Répéter la demande d'adhésion ? You can still view conversation with %@ in the list of chats. + Vous pouvez toujours voir la conversation avec %@ dans la liste des discussions. No comment provided by engineer. @@ -7538,10 +7586,12 @@ Répéter la demande de connexion ? You may migrate the exported database. + Vous pouvez migrer la base de données exportée. No comment provided by engineer. You may save the exported archive. + Vous pouvez enregistrer l'archive exportée. No comment provided by engineer. @@ -7551,6 +7601,7 @@ Répéter la demande de connexion ? You need to allow your contact to call to be able to call them. + Vous devez autoriser votre contact à appeler pour pouvoir l'appeler. No comment provided by engineer. @@ -7862,6 +7913,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. call + appeler No comment provided by engineer. @@ -8236,6 +8288,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. invite + inviter No comment provided by engineer. @@ -8295,6 +8348,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. message + message No comment provided by engineer. @@ -8329,6 +8383,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. mute + muet No comment provided by engineer. @@ -8465,6 +8520,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. search + rechercher No comment provided by engineer. @@ -8553,6 +8609,7 @@ dernier message reçu : %2$@ unmute + démuter No comment provided by engineer. @@ -8602,6 +8659,7 @@ dernier message reçu : %2$@ video + vidéo No comment provided by engineer. @@ -8944,10 +9002,12 @@ dernier message reçu : %2$@ Sending a message takes longer than expected. + L'envoi d'un message prend plus de temps que prévu. No comment provided by engineer. Sending message… + Envoi du message… No comment provided by engineer. @@ -8957,6 +9017,7 @@ dernier message reçu : %2$@ Slow network? + Réseau lent ? No comment provided by engineer. @@ -8971,6 +9032,7 @@ dernier message reçu : %2$@ Wait + Attendez No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index dbe3c50ceb..771c2f9405 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -219,12 +219,12 @@ %lld messages blocked - %lld üzenet blokkolva + %lld üzenet letiltva No comment provided by engineer. %lld messages blocked by admin - %lld üzenet blokkolva az admin által + %lld üzenet letiltva az admin által No comment provided by engineer. @@ -1067,7 +1067,7 @@ Block - Blokkolás + Letiltás No comment provided by engineer. @@ -1077,12 +1077,12 @@ Block group members - Csoporttagok blokkolása + Csoporttagok letiltása No comment provided by engineer. Block member - Tag blokkolása + Tag letiltása No comment provided by engineer. @@ -1092,7 +1092,7 @@ Block member? - Tag blokkolása? + Tag letiltása? No comment provided by engineer. @@ -2961,7 +2961,7 @@ Ez az egyszer használatos hivatkozása! Error synchronizing connection - Hiba a kapcsolat szinkronizálása során + Hiba a kapcsolat szinkronizálása közben No comment provided by engineer. @@ -3541,7 +3541,7 @@ Hiba: %2$@ If you can't meet in person, show QR code in a video call, or share the link. - Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás során, vagy ossza meg a hivatkozást. + Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás közben, vagy ossza meg a hivatkozást. No comment provided by engineer. @@ -5971,7 +5971,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Server test failed! - Sikertelen kiszolgáló-teszt! + Sikertelen kiszolgáló teszt! No comment provided by engineer. @@ -6271,12 +6271,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Some non-fatal errors occurred during import - you may see Chat console for more details. - Néhány nem végzetes hiba történt az importálás során – további részletekért a csevegési konzolban olvashat. + Néhány nem végzetes hiba történt az importálás közben – további részletekért a csevegési konzolban olvashat. No comment provided by engineer. Some non-fatal errors occurred during import: - Néhány nem végzetes hiba történt az importálás során: + Néhány nem végzetes hiba történt az importálás közben: No comment provided by engineer. @@ -6603,7 +6603,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The old database was not removed during the migration, it can be deleted. - A régi adatbázis nem került eltávolításra az átköltöztetés során, így törölhető. + A régi adatbázis nem került eltávolításra az átköltöztetés közben, így törölhető. No comment provided by engineer. @@ -7893,12 +7893,12 @@ A SimpleX kiszolgálók nem látjhatják profilját. blocked - blokkolva + letiltva marked deleted chat item preview text blocked %@ - letiltotta őt: %@ + letiltotta %@-t rcv group event chat item @@ -8589,7 +8589,7 @@ utoljára fogadott üzenet: %2$@ unblocked %@ - %@ feloldva + feloldotta %@ letiltását rcv group event chat item @@ -8714,7 +8714,7 @@ utoljára fogadott üzenet: %2$@ you blocked %@ - blokkolta őt: %@ + ön letiltotta %@-t snd group event chat item @@ -8759,7 +8759,7 @@ utoljára fogadott üzenet: %2$@ you unblocked %@ - feloldotta %@ blokkolását + ön feloldotta %@ letiltását snd group event chat item diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 137476db50..c7f9ad6e58 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -6780,7 +6780,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Toggle chat list: - Cambia elenco chat: + Cambia l'elenco delle chat: No comment provided by engineer. diff --git a/apps/ios/SimpleX SE/fr.lproj/Localizable.strings b/apps/ios/SimpleX SE/fr.lproj/Localizable.strings index 41dda79fbd..46a458b471 100644 --- a/apps/ios/SimpleX SE/fr.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/fr.lproj/Localizable.strings @@ -82,15 +82,27 @@ /* No comment provided by engineer. */ "Selected chat preferences prohibit this message." = "Les paramètres de chat sélectionnés ne permettent pas l'envoi de ce message."; +/* No comment provided by engineer. */ +"Sending a message takes longer than expected." = "L'envoi d'un message prend plus de temps que prévu."; + +/* No comment provided by engineer. */ +"Sending message…" = "Envoi du message…"; + /* No comment provided by engineer. */ "Share" = "Partager"; +/* No comment provided by engineer. */ +"Slow network?" = "Réseau lent ?"; + /* No comment provided by engineer. */ "Unknown database error: %@" = "Erreur inconnue de la base de données : %@"; /* No comment provided by engineer. */ "Unsupported format" = "Format non pris en charge"; +/* No comment provided by engineer. */ +"Wait" = "Attendez"; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Mauvaise phrase secrète pour la base de données"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index bb8e730bce..bab14eda6b 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -677,7 +677,7 @@ "Better messages" = "Verbesserungen bei Nachrichten"; /* No comment provided by engineer. */ -"Better networking" = "Bessere Vernetzung"; +"Better networking" = "Kontrollieren Sie Ihr Netzwerk"; /* No comment provided by engineer. */ "Black" = "Schwarz"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 7645a8e2b6..1caf7246b8 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -596,7 +596,7 @@ "Archive and upload" = "Archivar y subir"; /* No comment provided by engineer. */ -"Archive contacts to chat later." = "Archivar contactos para charlar más tarde."; +"Archive contacts to chat later." = "Archiva contactos para charlar más tarde."; /* No comment provided by engineer. */ "Archived contacts" = "Contactos archivados"; @@ -713,7 +713,7 @@ "Blocked by admin" = "Bloqueado por administrador"; /* No comment provided by engineer. */ -"Blur for better privacy." = "Difuminar para más privacidad."; +"Blur for better privacy." = "Difumina para mayor privacidad."; /* No comment provided by engineer. */ "Blur media" = "Difuminar multimedia"; @@ -1089,7 +1089,7 @@ "Connection" = "Conexión"; /* No comment provided by engineer. */ -"Connection and servers status." = "Estado de conexión y servidores."; +"Connection and servers status." = "Estado de tu conexión y servidores."; /* No comment provided by engineer. */ "Connection error" = "Error conexión"; @@ -2048,7 +2048,7 @@ "Expand" = "Expandir"; /* No comment provided by engineer. */ -"expired" = "expirado"; +"expired" = "expirados"; /* No comment provided by engineer. */ "Export database" = "Exportar base de datos"; @@ -3253,7 +3253,7 @@ "peer-to-peer" = "p2p"; /* No comment provided by engineer. */ -"Pending" = "Pendiente"; +"Pending" = "Pendientes"; /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "Las personas pueden conectarse contigo solo mediante los enlaces que compartes."; @@ -3439,7 +3439,7 @@ "Protocol timeout per KB" = "Timeout protocolo por KB"; /* No comment provided by engineer. */ -"Proxied" = "Con proxy"; +"Proxied" = "Como proxy"; /* No comment provided by engineer. */ "Proxied servers" = "Servidores con proxy"; @@ -3956,7 +3956,7 @@ "Sent at: %@" = "Enviado: %@"; /* No comment provided by engineer. */ -"Sent directly" = "Enviado directamente"; +"Sent directly" = "Directamente"; /* notification */ "Sent file event" = "Evento de archivo enviado"; @@ -3977,7 +3977,7 @@ "Sent total" = "Total enviados"; /* No comment provided by engineer. */ -"Sent via proxy" = "Enviado vía proxy"; +"Sent via proxy" = "Mediante proxy"; /* No comment provided by engineer. */ "Server address" = "Dirección del servidor"; @@ -4352,7 +4352,7 @@ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo."; /* No comment provided by engineer. */ -"The app will ask to confirm downloads from unknown file servers (except .onion)." = "La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto .onion)."; +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto si son .onion)."; /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "El intento de cambiar la contraseña de la base de datos no se ha completado."; @@ -4709,7 +4709,7 @@ "Use the app while in the call." = "Usar la aplicación durante la llamada."; /* No comment provided by engineer. */ -"Use the app with one hand." = "Usa la aplicación con una mano."; +"Use the app with one hand." = "Usa la aplicación con una sola mano."; /* No comment provided by engineer. */ "User profile" = "Perfil de usuario"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index bfa94e35a4..b4f256762c 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -314,7 +314,7 @@ "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." = "Une connexion TCP distincte sera utilisée **pour chaque contact et membre de groupe**.\n**Veuillez noter** : si vous avez de nombreuses connexions, votre consommation de batterie et de réseau peut être nettement plus élevée et certaines liaisons peuvent échouer."; /* No comment provided by engineer. */ -"Abort" = "Annuler"; +"Abort" = "Abandonner"; /* No comment provided by engineer. */ "Abort changing address" = "Annuler le changement d'adresse"; @@ -472,6 +472,9 @@ /* No comment provided by engineer. */ "Allow calls only if your contact allows them." = "Autoriser les appels que si votre contact les autorise."; +/* No comment provided by engineer. */ +"Allow calls?" = "Autoriser les appels ?"; + /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Autorise les messages éphémères seulement si votre contact vous l’autorise."; @@ -590,7 +593,13 @@ "Apply to" = "Appliquer à"; /* No comment provided by engineer. */ -"Archive and upload" = "Archiver et transférer"; +"Archive and upload" = "Archiver et téléverser"; + +/* No comment provided by engineer. */ +"Archive contacts to chat later." = "Archiver les contacts pour discuter plus tard."; + +/* No comment provided by engineer. */ +"Archived contacts" = "Contacts archivés"; /* No comment provided by engineer. */ "Archiving database" = "Archivage de la base de données"; @@ -667,6 +676,9 @@ /* No comment provided by engineer. */ "Better messages" = "Meilleurs messages"; +/* No comment provided by engineer. */ +"Better networking" = "Meilleure gestion de réseau"; + /* No comment provided by engineer. */ "Black" = "Noir"; @@ -700,6 +712,9 @@ /* No comment provided by engineer. */ "Blocked by admin" = "Bloqué par l'administrateur"; +/* No comment provided by engineer. */ +"Blur for better privacy." = "Rendez les images floues et protégez-les contre les regards indiscrets."; + /* No comment provided by engineer. */ "Blur media" = "Flouter les médias"; @@ -727,6 +742,9 @@ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"call" = "appeler"; + /* No comment provided by engineer. */ "Call already ended!" = "Appel déjà terminé !"; @@ -742,15 +760,27 @@ /* No comment provided by engineer. */ "Calls" = "Appels"; +/* No comment provided by engineer. */ +"Calls prohibited!" = "Les appels ne sont pas autorisés !"; + /* No comment provided by engineer. */ "Camera not available" = "Caméra non disponible"; +/* No comment provided by engineer. */ +"Can't call contact" = "Impossible d'appeler le contact"; + +/* No comment provided by engineer. */ +"Can't call member" = "Impossible d'appeler le membre"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Impossible d'inviter le contact !"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Impossible d'inviter les contacts !"; +/* No comment provided by engineer. */ +"Can't message member" = "Impossible d'envoyer un message à ce membre"; + /* No comment provided by engineer. */ "Cancel" = "Annuler"; @@ -836,6 +866,9 @@ /* No comment provided by engineer. */ "Chat database deleted" = "Base de données du chat supprimée"; +/* No comment provided by engineer. */ +"Chat database exported" = "Exportation de la base de données des discussions"; + /* No comment provided by engineer. */ "Chat database imported" = "Base de données du chat importée"; @@ -848,6 +881,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Le chat est arrêté. Si vous avez déjà utilisé cette base de données sur un autre appareil, vous devez la transférer à nouveau avant de démarrer le chat."; +/* No comment provided by engineer. */ +"Chat list" = "Liste de discussion"; + /* No comment provided by engineer. */ "Chat migrated!" = "Messagerie transférée !"; @@ -899,6 +935,9 @@ /* No comment provided by engineer. */ "Clear verification" = "Retirer la vérification"; +/* No comment provided by engineer. */ +"Color chats with the new themes." = "Colorez vos discussions avec les nouveaux thèmes."; + /* No comment provided by engineer. */ "Color mode" = "Mode de couleur"; @@ -915,7 +954,7 @@ "complete" = "complet"; /* No comment provided by engineer. */ -"Completed" = "Complété"; +"Completed" = "Complétées"; /* No comment provided by engineer. */ "Configure ICE servers" = "Configurer les serveurs ICE"; @@ -926,6 +965,9 @@ /* No comment provided by engineer. */ "Confirm" = "Confirmer"; +/* No comment provided by engineer. */ +"Confirm contact deletion?" = "Confirmer la suppression du contact ?"; + /* No comment provided by engineer. */ "Confirm database upgrades" = "Confirmer la mise à niveau de la base de données"; @@ -965,6 +1007,9 @@ /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "se connecter aux developpeurs de SimpleX Chat."; +/* No comment provided by engineer. */ +"Connect to your friends faster." = "Connectez-vous à vos amis plus rapidement."; + /* No comment provided by engineer. */ "Connect to yourself?" = "Se connecter à soi-même ?"; @@ -1031,6 +1076,9 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Connexion au serveur… (erreur : %@)"; +/* No comment provided by engineer. */ +"Connecting to contact, please wait or check later!" = "Connexion au contact, veuillez patienter ou vérifier plus tard !"; + /* No comment provided by engineer. */ "Connecting to desktop" = "Connexion au bureau"; @@ -1040,6 +1088,9 @@ /* No comment provided by engineer. */ "Connection" = "Connexion"; +/* No comment provided by engineer. */ +"Connection and servers status." = "État de la connexion et des serveurs."; + /* No comment provided by engineer. */ "Connection error" = "Erreur de connexion"; @@ -1079,6 +1130,9 @@ /* No comment provided by engineer. */ "Contact already exists" = "Contact déjà existant"; +/* No comment provided by engineer. */ +"Contact deleted!" = "Contact supprimé !"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "Ce contact a le chiffrement de bout en bout"; @@ -1091,12 +1145,18 @@ /* notification */ "Contact is connected" = "Le contact est connecté"; +/* No comment provided by engineer. */ +"Contact is deleted." = "Le contact est supprimé."; + /* No comment provided by engineer. */ "Contact name" = "Nom du contact"; /* No comment provided by engineer. */ "Contact preferences" = "Préférences de contact"; +/* No comment provided by engineer. */ +"Contact will be deleted - this cannot be undone!" = "Le contact sera supprimé - il n'est pas possible de revenir en arrière !"; + /* No comment provided by engineer. */ "Contacts" = "Contacts"; @@ -1106,6 +1166,9 @@ /* No comment provided by engineer. */ "Continue" = "Continuer"; +/* No comment provided by engineer. */ +"Conversation deleted!" = "Conversation supprimée !"; + /* No comment provided by engineer. */ "Copy" = "Copier"; @@ -1158,7 +1221,7 @@ "Create your profile" = "Créez votre profil"; /* No comment provided by engineer. */ -"Created" = "Créé"; +"Created" = "Créées"; /* No comment provided by engineer. */ "Created at" = "Créé à"; @@ -1329,6 +1392,9 @@ /* No comment provided by engineer. */ "Delete contact" = "Supprimer le contact"; +/* No comment provided by engineer. */ +"Delete contact?" = "Supprimer le contact ?"; + /* No comment provided by engineer. */ "Delete database" = "Supprimer la base de données"; @@ -1392,14 +1458,20 @@ /* server test step */ "Delete queue" = "Supprimer la file d'attente"; +/* No comment provided by engineer. */ +"Delete up to 20 messages at once." = "Supprimez jusqu'à 20 messages à la fois."; + /* No comment provided by engineer. */ "Delete user profile?" = "Supprimer le profil utilisateur ?"; +/* No comment provided by engineer. */ +"Delete without notification" = "Supprimer sans notification"; + /* deleted chat item */ "deleted" = "supprimé"; /* No comment provided by engineer. */ -"Deleted" = "Supprimé"; +"Deleted" = "Supprimées"; /* No comment provided by engineer. */ "Deleted at" = "Supprimé à"; @@ -1455,6 +1527,9 @@ /* No comment provided by engineer. */ "Develop" = "Développer"; +/* No comment provided by engineer. */ +"Developer options" = "Options pour les développeurs"; + /* No comment provided by engineer. */ "Developer tools" = "Outils du développeur"; @@ -2461,6 +2536,9 @@ /* group name */ "invitation to group %@" = "invitation au groupe %@"; +/* No comment provided by engineer. */ +"invite" = "inviter"; + /* No comment provided by engineer. */ "Invite friends" = "Inviter des amis"; @@ -2506,6 +2584,9 @@ /* No comment provided by engineer. */ "It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Cela peut arriver quand :\n1. Les messages ont expiré dans le client expéditeur après 2 jours ou sur le serveur après 30 jours.\n2. Le déchiffrement du message a échoué, car vous ou votre contact avez utilisé une ancienne sauvegarde de base de données.\n3. La connexion a été compromise."; +/* No comment provided by engineer. */ +"It protects your IP address and connections." = "Il protège votre adresse IP et vos connexions."; + /* No comment provided by engineer. */ "It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Il semblerait que vous êtes déjà connecté via ce lien. Si ce n'est pas le cas, il y a eu une erreur (%@)."; @@ -2548,6 +2629,9 @@ /* No comment provided by engineer. */ "Keep" = "Conserver"; +/* No comment provided by engineer. */ +"Keep conversation" = "Garder la conversation"; + /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Garder l'application ouverte pour l'utiliser depuis le bureau"; @@ -2659,6 +2743,9 @@ /* No comment provided by engineer. */ "Max 30 seconds, received instantly." = "Max 30 secondes, réception immédiate."; +/* No comment provided by engineer. */ +"Media & file servers" = "Serveurs de fichiers et de médias"; + /* blur media */ "Medium" = "Modéré"; @@ -2689,6 +2776,9 @@ /* No comment provided by engineer. */ "Menus" = "Menus"; +/* No comment provided by engineer. */ +"message" = "message"; + /* item status text */ "Message delivery error" = "Erreur de distribution du message"; @@ -2725,6 +2815,9 @@ /* No comment provided by engineer. */ "Message reception" = "Réception de message"; +/* No comment provided by engineer. */ +"Message servers" = "Serveurs de messages"; + /* No comment provided by engineer. */ "Message source remains private." = "La source du message reste privée."; @@ -2833,6 +2926,9 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "Différents profils de chat"; +/* No comment provided by engineer. */ +"mute" = "muet"; + /* swipe action */ "Mute" = "Muet"; @@ -2866,6 +2962,9 @@ /* No comment provided by engineer. */ "New chat" = "Nouveau chat"; +/* No comment provided by engineer. */ +"New chat experience 🎉" = "Nouvelle expérience de discussion 🎉"; + /* notification */ "New contact request" = "Nouvelle demande de contact"; @@ -2884,6 +2983,9 @@ /* No comment provided by engineer. */ "New in %@" = "Nouveautés de la %@"; +/* No comment provided by engineer. */ +"New media options" = "Nouvelles options de médias"; + /* No comment provided by engineer. */ "New member role" = "Nouveau rôle"; @@ -3012,6 +3114,9 @@ /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un **chiffrement de bout en bout à deux couches**."; +/* No comment provided by engineer. */ +"Only delete conversation" = "Ne supprimer que la conversation"; + /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Seuls les propriétaires du groupe peuvent modifier les préférences du groupe."; @@ -3168,6 +3273,12 @@ /* No comment provided by engineer. */ "PING interval" = "Intervalle de PING"; +/* No comment provided by engineer. */ +"Play from the chat list." = "Aperçu depuis la liste de conversation."; + +/* No comment provided by engineer. */ +"Please ask your contact to enable calls." = "Veuillez demander à votre contact d'autoriser les appels."; + /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "Veuillez demander à votre contact de permettre l'envoi de messages vocaux."; @@ -3348,6 +3459,9 @@ /* No comment provided by engineer. */ "Rate the app" = "Évaluer l'app"; +/* No comment provided by engineer. */ +"Reachable chat toolbar" = "Barre d'outils accessible"; + /* chat item menu */ "React…" = "Réagissez…"; @@ -3535,6 +3649,9 @@ /* No comment provided by engineer. */ "Reset" = "Réinitialisation"; +/* No comment provided by engineer. */ +"Reset all hints" = "Rétablir tous les conseils"; + /* No comment provided by engineer. */ "Reset all statistics" = "Réinitialiser toutes les statistiques"; @@ -3610,6 +3727,9 @@ /* No comment provided by engineer. */ "Save and notify group members" = "Enregistrer et en informer les membres du groupe"; +/* No comment provided by engineer. */ +"Save and reconnect" = "Sauvegarder et se reconnecter"; + /* No comment provided by engineer. */ "Save and update group profile" = "Enregistrer et mettre à jour le profil du groupe"; @@ -3685,6 +3805,9 @@ /* No comment provided by engineer. */ "Scan server QR code" = "Scanner un code QR de serveur"; +/* No comment provided by engineer. */ +"search" = "rechercher"; + /* No comment provided by engineer. */ "Search" = "Rechercher"; @@ -3710,7 +3833,7 @@ "Secure queue" = "File d'attente sécurisée"; /* No comment provided by engineer. */ -"Secured" = "Sécurisé"; +"Secured" = "Sécurisées"; /* No comment provided by engineer. */ "Security assessment" = "Évaluation de sécurité"; @@ -3764,11 +3887,14 @@ "Send errors" = "Erreurs d'envoi"; /* No comment provided by engineer. */ -"Send link previews" = "Envoi d'aperçus de liens"; +"Send link previews" = "Aperçu des liens"; /* No comment provided by engineer. */ "Send live message" = "Envoyer un message dynamique"; +/* No comment provided by engineer. */ +"Send message to enable calls." = "Envoyer un message pour activer les appels."; + /* No comment provided by engineer. */ "Send messages directly when IP address is protected and your or destination server does not support private routing." = "Envoyer les messages de manière directe lorsque l'adresse IP est protégée et que votre serveur ou le serveur de destination ne prend pas en charge le routage privé."; @@ -3869,7 +3995,7 @@ "Server requires authorization to create queues, check password" = "Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe"; /* server test error */ -"Server requires authorization to upload, check password" = "Le serveur requiert une autorisation pour uploader, vérifiez le mot de passe"; +"Server requires authorization to upload, check password" = "Le serveur requiert une autorisation pour téléverser, vérifiez le mot de passe"; /* No comment provided by engineer. */ "Server test failed!" = "Échec du test du serveur !"; @@ -3949,6 +4075,9 @@ /* No comment provided by engineer. */ "Share address with contacts?" = "Partager l'adresse avec vos contacts ?"; +/* No comment provided by engineer. */ +"Share from other apps." = "Partager depuis d'autres applications."; + /* No comment provided by engineer. */ "Share link" = "Partager le lien"; @@ -3971,7 +4100,7 @@ "Show developer options" = "Afficher les options pour les développeurs"; /* No comment provided by engineer. */ -"Show last messages" = "Voir les derniers messages"; +"Show last messages" = "Aperçu des derniers messages"; /* No comment provided by engineer. */ "Show message status" = "Afficher le statut du message"; @@ -3980,7 +4109,7 @@ "Show percentage" = "Afficher le pourcentage"; /* No comment provided by engineer. */ -"Show preview" = "Afficher l'aperçu"; +"Show preview" = "Aperçu affiché"; /* No comment provided by engineer. */ "Show QR code" = "Afficher le code QR"; @@ -4054,9 +4183,15 @@ /* blur media */ "Soft" = "Léger"; +/* No comment provided by engineer. */ +"Some file(s) were not exported:" = "Certains fichiers n'ont pas été exportés :"; + /* No comment provided by engineer. */ "Some non-fatal errors occurred during import - you may see Chat console for more details." = "Des erreurs non fatales se sont produites lors de l'importation - vous pouvez consulter la console de chat pour plus de détails."; +/* No comment provided by engineer. */ +"Some non-fatal errors occurred during import:" = "L'importation a entraîné des erreurs non fatales :"; + /* notification title */ "Somebody" = "Quelqu'un"; @@ -4130,7 +4265,7 @@ "Submit" = "Soumettre"; /* No comment provided by engineer. */ -"Subscribed" = "Inscrit"; +"Subscribed" = "Inscriptions"; /* No comment provided by engineer. */ "Subscription errors" = "Erreurs d'inscription"; @@ -4171,6 +4306,9 @@ /* No comment provided by engineer. */ "Tap to scan" = "Appuyez pour scanner"; +/* No comment provided by engineer. */ +"TCP connection" = "Connexion TCP"; + /* No comment provided by engineer. */ "TCP connection timeout" = "Délai de connexion TCP"; @@ -4363,9 +4501,15 @@ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Pour vérifier le chiffrement de bout en bout avec votre contact, comparez (ou scannez) le code sur vos appareils."; +/* No comment provided by engineer. */ +"Toggle chat list:" = "Afficher la liste des conversations :"; + /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Basculer en mode incognito lors de la connexion."; +/* No comment provided by engineer. */ +"Toolbar opacity" = "Opacité de la barre d'outils"; + /* No comment provided by engineer. */ "Total" = "Total"; @@ -4468,6 +4612,9 @@ /* authentication reason */ "Unlock app" = "Déverrouiller l'app"; +/* No comment provided by engineer. */ +"unmute" = "démuter"; + /* swipe action */ "Unmute" = "Démute"; @@ -4489,6 +4636,9 @@ /* No comment provided by engineer. */ "Update network settings?" = "Mettre à jour les paramètres réseau ?"; +/* No comment provided by engineer. */ +"Update settings?" = "Mettre à jour les paramètres ?"; + /* rcv group event chat item */ "updated group profile" = "mise à jour du profil de groupe"; @@ -4508,7 +4658,7 @@ "Upload failed" = "Échec de l'envoi"; /* server test step */ -"Upload file" = "Transférer le fichier"; +"Upload file" = "Téléverser le fichier"; /* No comment provided by engineer. */ "Uploaded" = "Téléversé"; @@ -4558,6 +4708,9 @@ /* No comment provided by engineer. */ "Use the app while in the call." = "Utiliser l'application pendant l'appel."; +/* No comment provided by engineer. */ +"Use the app with one hand." = "Utiliser l'application d'une main."; + /* No comment provided by engineer. */ "User profile" = "Profil d'utilisateur"; @@ -4612,6 +4765,9 @@ /* No comment provided by engineer. */ "Via secure quantum resistant protocol." = "Via un protocole sécurisé de cryptographie post-quantique."; +/* No comment provided by engineer. */ +"video" = "vidéo"; + /* No comment provided by engineer. */ "Video call" = "Appel vidéo"; @@ -4619,7 +4775,7 @@ "video call (not e2e encrypted)" = "appel vidéo (sans chiffrement)"; /* No comment provided by engineer. */ -"Video will be received when your contact completes uploading it." = "La vidéo ne sera reçue que lorsque votre contact aura fini de la transférer."; +"Video will be received when your contact completes uploading it." = "La vidéo ne sera reçue que lorsque votre contact aura fini la mettre en ligne."; /* No comment provided by engineer. */ "Video will be received when your contact is online, please wait or check later!" = "La vidéo ne sera reçue que lorsque votre contact sera en ligne. Veuillez patienter ou vérifier plus tard !"; @@ -4822,6 +4978,9 @@ /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Vous pouvez accepter des appels à partir de l'écran de verrouillage, sans authentification de l'appareil ou de l'application."; +/* No comment provided by engineer. */ +"You can change it in Appearance settings." = "Vous pouvez choisir de le modifier dans les paramètres d'apparence."; + /* No comment provided by engineer. */ "You can create it later" = "Vous pouvez la créer plus tard"; @@ -4843,6 +5002,9 @@ /* notification body */ "You can now chat with %@" = "Vous pouvez maintenant envoyer des messages à %@"; +/* No comment provided by engineer. */ +"You can send messages to %@ from Archived contacts." = "Vous pouvez envoyer des messages à %@ à partir des contacts archivés."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Vous pouvez configurer l'aperçu des notifications sur l'écran de verrouillage via les paramètres."; @@ -4858,6 +5020,9 @@ /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Vous pouvez lancer le chat via Paramètres / Base de données ou en redémarrant l'app"; +/* No comment provided by engineer. */ +"You can still view conversation with %@ in the list of chats." = "Vous pouvez toujours voir la conversation avec %@ dans la liste des discussions."; + /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "Vous pouvez activer SimpleX Lock dans les Paramètres."; @@ -4909,9 +5074,18 @@ /* snd group event chat item */ "you left" = "vous avez quitté"; +/* No comment provided by engineer. */ +"You may migrate the exported database." = "Vous pouvez migrer la base de données exportée."; + +/* No comment provided by engineer. */ +"You may save the exported archive." = "Vous pouvez enregistrer l'archive exportée."; + /* No comment provided by engineer. */ "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Vous devez utiliser la version la plus récente de votre base de données de chat sur un seul appareil UNIQUEMENT, sinon vous risquez de ne plus recevoir les messages de certains contacts."; +/* No comment provided by engineer. */ +"You need to allow your contact to call to be able to call them." = "Vous devez autoriser votre contact à appeler pour pouvoir l'appeler."; + /* No comment provided by engineer. */ "You need to allow your contact to send voice messages to be able to send them." = "Vous devez autoriser votre contact à envoyer des messages vocaux pour pouvoir en envoyer."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index b5d0b8b895..449f21a805 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -209,10 +209,10 @@ "%lld members" = "%lld tag"; /* No comment provided by engineer. */ -"%lld messages blocked" = "%lld üzenet blokkolva"; +"%lld messages blocked" = "%lld üzenet letiltva"; /* No comment provided by engineer. */ -"%lld messages blocked by admin" = "%lld üzenet blokkolva az admin által"; +"%lld messages blocked by admin" = "%lld üzenet letiltva az admin által"; /* No comment provided by engineer. */ "%lld messages marked deleted" = "%lld törlésre megjelölt üzenet"; @@ -683,28 +683,28 @@ "Black" = "Fekete"; /* No comment provided by engineer. */ -"Block" = "Blokkolás"; +"Block" = "Letiltás"; /* No comment provided by engineer. */ "Block for all" = "Letiltás mindenki számára"; /* No comment provided by engineer. */ -"Block group members" = "Csoporttagok blokkolása"; +"Block group members" = "Csoporttagok letiltása"; /* No comment provided by engineer. */ -"Block member" = "Tag blokkolása"; +"Block member" = "Tag letiltása"; /* No comment provided by engineer. */ "Block member for all?" = "Mindenki számára letiltja ezt a tagot?"; /* No comment provided by engineer. */ -"Block member?" = "Tag blokkolása?"; +"Block member?" = "Tag letiltása?"; /* marked deleted chat item preview text */ -"blocked" = "blokkolva"; +"blocked" = "letiltva"; /* rcv group event chat item */ -"blocked %@" = "letiltotta őt: %@"; +"blocked %@" = "letiltotta %@-t"; /* marked deleted chat item preview text */ "blocked by admin" = "letiltva az admin által"; @@ -1999,7 +1999,7 @@ "Error switching profile!" = "Hiba a profil váltásakor!"; /* No comment provided by engineer. */ -"Error synchronizing connection" = "Hiba a kapcsolat szinkronizálása során"; +"Error synchronizing connection" = "Hiba a kapcsolat szinkronizálása közben"; /* No comment provided by engineer. */ "Error updating group link" = "Hiba a csoport hivatkozás frissítésekor"; @@ -2363,7 +2363,7 @@ "ICE servers (one per line)" = "ICE-kiszolgálók (soronként egy)"; /* No comment provided by engineer. */ -"If you can't meet in person, show QR code in a video call, or share the link." = "Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás során, vagy ossza meg a hivatkozást."; +"If you can't meet in person, show QR code in a video call, or share the link." = "Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás közben, vagy ossza meg a hivatkozást."; /* No comment provided by engineer. */ "If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen törlődik!"; @@ -3998,7 +3998,7 @@ "Server requires authorization to upload, check password" = "A kiszolgálónak engedélyre van szüksége a várólisták feltöltéséhez, ellenőrizze jelszavát"; /* No comment provided by engineer. */ -"Server test failed!" = "Sikertelen kiszolgáló-teszt!"; +"Server test failed!" = "Sikertelen kiszolgáló teszt!"; /* No comment provided by engineer. */ "Server type" = "Kiszolgáló típusa"; @@ -4187,10 +4187,10 @@ "Some file(s) were not exported:" = "Néhány fájl nem került exportálásra:"; /* No comment provided by engineer. */ -"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Néhány nem végzetes hiba történt az importálás során – további részletekért a csevegési konzolban olvashat."; +"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Néhány nem végzetes hiba történt az importálás közben – további részletekért a csevegési konzolban olvashat."; /* No comment provided by engineer. */ -"Some non-fatal errors occurred during import:" = "Néhány nem végzetes hiba történt az importálás során:"; +"Some non-fatal errors occurred during import:" = "Néhány nem végzetes hiba történt az importálás közben:"; /* notification title */ "Somebody" = "Valaki"; @@ -4394,7 +4394,7 @@ "The next generation of private messaging" = "A privát üzenetküldés következő generációja"; /* No comment provided by engineer. */ -"The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem került eltávolításra az átköltöztetés során, így törölhető."; +"The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem került eltávolításra az átköltöztetés közben, így törölhető."; /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Profilja csak az ismerőseivel kerül megosztásra."; @@ -4553,7 +4553,7 @@ "Unblock member?" = "Tag feloldása?"; /* rcv group event chat item */ -"unblocked %@" = "%@ feloldva"; +"unblocked %@" = "feloldotta %@ letiltását"; /* No comment provided by engineer. */ "Unexpected migration state" = "Váratlan átköltöztetési állapot"; @@ -4973,7 +4973,7 @@ "you are observer" = "megfigyelő szerep"; /* snd group event chat item */ -"you blocked %@" = "blokkolta őt: %@"; +"you blocked %@" = "ön letiltotta %@-t"; /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Hívásokat fogadhat a lezárási képernyőről, eszköz- és alkalmazáshitelesítés nélkül."; @@ -5105,7 +5105,7 @@ "you shared one-time link incognito" = "egyszer használatos hivatkozást osztott meg inkognitóban"; /* snd group event chat item */ -"you unblocked %@" = "feloldotta %@ blokkolását"; +"you unblocked %@" = "ön feloldotta %@ letiltását"; /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 3a44a63ce9..bb8d97a13d 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -4502,7 +4502,7 @@ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Per verificare la crittografia end-to-end con il tuo contatto, confrontate (o scansionate) il codice sui vostri dispositivi."; /* No comment provided by engineer. */ -"Toggle chat list:" = "Cambia elenco chat:"; +"Toggle chat list:" = "Cambia l'elenco delle chat:"; /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Attiva/disattiva l'incognito quando ti colleghi."; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index f715077b86..b027be6e82 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -2065,4 +2065,6 @@ نزّل الإصدارات الجديدة من GitHub. ترقية التطبيق تلقائيًا صفّر كافة التلميحات + يُرجى التأكد من أن رابط SimpleX صحيح. + الرابط غير صالح \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 5fb8684ccf..64b7f9d8f7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -2149,4 +2149,6 @@ Sie können das exportierte Archiv speichern. Alle Hinweise zurücksetzen Neue Nachricht + Bitte überprüfen Sie, ob der SimpleX-Link korrekt ist. + Ungültiger Link \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 7cabbb8c9c..d5bceef30b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -1378,7 +1378,7 @@ Abrir Cifra archivos almacenados y multimedia Error al establecer contacto con el miembro - Recuerda
    : los servidores de retransmisión están conectados mediante SOCKS proxy. Las llamadas y las previsualizaciones de enlaces usan conexión directa.]]> + Recuerda
    : los servidores están conectados mediante proxy SOCKS, pero las llamadas y las previsualizaciones de enlaces usan conexión directa.]]> Cifra archivos locales Nueva aplicación para ordenador! 6 idiomas nuevos para el interfaz @@ -1804,7 +1804,7 @@ Proteger dirección IP Sin Tor o VPN, tu dirección IP será visible para los servidores de archivos. ARCHIVOS - La aplicación pedirá confirmar las descargas desde servidores de archivos desconocidos (excepto .onion o cuando se active SOCKS proxy). + La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto si son .onion o cuando esté habilitado el proxy SOCKS). Colores del chat Tema del chat Aplicar a @@ -1947,7 +1947,7 @@ Mensajes recibidos Mensajes enviados Sin información, intenta recargar - Pendiente + Pendientes Servidores conectados previamente Servidores con proxy Mensajes recibidos @@ -1961,20 +1961,20 @@ Restablecer todas las estadísticas ¿Restablecer todas las estadísticas? Mensajes enviados - Total enviado + Total enviados Archivos descargados Errores de descarga duplicados - expirado + expirados Abrir configuración del servidor otros otros errores - Con proxy + Como proxy Reconectar Aseguradas Errores de envío - Enviado directamente - Enviado mediante proxy + Directamente + Mediante proxy Dirección del servidor Tamaño Conexiones activas @@ -1984,10 +1984,10 @@ Bloques eliminados Bloques descargados Bloques subidos - Reconocido - Errores de reconocimiento + Confirmaciones + Errores de confirmación Conectando con el contacto, por favor espera o revisa más tarde. - Estado de conexión y servidores. + Estado de tu conexión y servidores. Conecta más rápido con tus amigos Controla tu red Protege tu dirección IP y tus conexiones. @@ -2023,7 +2023,7 @@ Contactos archivados Servidores de mensajes Servidores de archivos y multimedia - SOCKS proxy + Proxy SOCKS No Continuar Algunos archivos no han sido exportados @@ -2036,7 +2036,7 @@ Enviar mensaje para activar llamadas. Elimina hasta 20 mensajes a la vez. Barra de herramientas accesible - Archivar contactos para charlar más tarde. + Archiva contactos para charlar más tarde. Puedes guardar el archivo exportado. Fuerte Los mensajes serán eliminados para todos los miembros. @@ -2046,7 +2046,7 @@ Aún puedes ver la conversación con %1$s en la lista de chats. Puedes migrar la base de datos exportada. Conexión TCP - Usa la aplicación con una mano. + Usa la aplicación con una sola mano. La dirección del servidor de destino de %1$s es incompatible con la configuración del servidor de reenvío %2$s. La versión del servidor de destino de %1$s es incompatible con el servidor de reenvío %2$s. Tus contactos @@ -2056,10 +2056,10 @@ El servidor de reenvío %1$s no ha podido conectarse al servidor de destino %2$s. Por favor, intentalo más tarde. La versión del servidor de reenvío es incompatible con la configuración de red: %1$s. Ningún contacto filtrado - Difuminar para más privacidad + Difumina para mayor privacidad Crear Alternar lista de chats: - Aumenta el tamaño de la fuente. + Ajusta el tamaño de la fuente. Reproduce desde la lista de chats. Actualizar la aplicación automáticamente Invitar diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index eb2c76d2bb..02487f8ba7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -89,7 +89,7 @@ L\'application récupère périodiquement les nouveaux messages - elle utilise un peu votre batterie chaque jour. L\'application n\'utilise pas les notifications push - les données de votre appareil ne sont pas envoyées aux serveurs. SimpleX service de fond
    - il utilise quelques pour cent de la batterie par jour.]]> Cacher - Afficher l\'aperçu + Aperçu affiché Nom du contact Contact masqué : nouveau message @@ -342,7 +342,7 @@ Vos serveurs ICE Configurer les serveurs ICE Paramètres réseau avancés - Paramètres réseau + Paramètres avancés Utiliser un proxy SOCKS \? Utiliser une connexion Internet directe \? Si vous confirmez, les serveurs de messagerie seront en mesure de voir votre adresse IP, votre fournisseur ainsi que les serveurs auxquels vous vous connectez. @@ -370,7 +370,7 @@ réponse reçu… confimation reçu… connexion… - Protocole et code open-source – n\'importe qui peut heberger un serveur. + N\'importe qui peut heberger un serveur. Pour protéger votre vie privée, au lieu d\'IDs utilisés par toutes les autres plateformes, SimpleX possède des IDs pour les queues de messages, distinctes pour chacun de vos contacts. Plus d\'informations sur notre GitHub. Collez le lien que vous avez reçu @@ -440,11 +440,12 @@ en attente de confirmation… connecté terminé - La nouvelle génération de messagerie privée + La nouvelle génération +\nde messagerie privée La vie privée redéfinie - La 1ère plateforme sans aucun identifiant d\'utilisateur – privée par design. - Protégé du spam et des abus - On ne peut se connecter à vous qu’avec les liens que vous partagez. + Aucun identifiant d\'utilisateur. + Protégé du spam + Vous choisissez qui peut se connecter. Décentralisé Créez votre profil Établir une connexion privée @@ -453,8 +454,8 @@ si SimpleX n\'a pas d\'identifiant d\'utilisateur, comment peut-il transmettre des messages \?]]> chiffrement de bout en bout à deux couches

    RwVlwW+RI6U5Qe{GRwL1qSkyP zR45?2Fq)Xaxzt6)JbESbUl8R_xyl6b|$zT0~3+@eB6Yj1Nfm| zRZAb|2*M8k1?0(VAy4nnnj!di$-I8ZC}pDe$N)M8#$o|oNZfvx zIn_9OtKVjsCdPq7qL-yLDL(hd-t_+Nycehb=$i9$$BwwZ?uF*C0v z4n68+W#~I=I!r=b2g`sf0n8&Zk%Ulu@?`-%I{wON3ZKu$u-4$DW+!RpE7Gr&tvS%S6FQX3!$y7 zZMdhvUnKvvHv%_;^3kTYtMozn`^DD+X9y0gPfl}FeQU9`Q^Rp_tPJY-eD}? zQ>22UxL9+$a}KAPr$_dsO{`lryX3!c5&z(cypZ6vd6PT-0)N53U)R9D1jNAb4x348 z^+Es|={*m{Jd*k5L3Pv^Xp1&oZ-V@Z9aWIlzs84=DtI&%1bC(go<$~5_J$tt!>1&D zl#vNQKvI;!DAtw<+`{lg_3Ze zU1#Q0vV5mD`t%_siqFYiZeF*A76fGfBL_^J-s&$>D~42y`Kjz<`0ZH=4m_wVur{`S z4taI!Y;Wif6|JG2M_x_@PI+)Kb684K(TSw2MYZ{Yh(_N|IU7^-^oBVg0le4SAmJAA zz?(GjQRd-CIOgwB%>2Axk!5@FxRJ2n|Ml@^a*&`_x zJ;gHXL6Okz#D(=dC4ni=o}JUpd0*jNqM)8(s6guFbKtx7s*dEg`kg>r%v@#h>SIFUiTJgC zX?l#jjFmM1y)4-3XjqXD{#FfotdlWOa^RA-SHw!R;YY!6lRzJ&dQCAdx>ZBI&+G8+ z1Nemqx@rF{I?NdG_z^s6aNU<1q5Our{~T|FV)s<4SR@*K&p#uEfdlv|)}sGyIxv+< zWzcSPNr>It2F=Hi4eai0_7_OVJrTir*$6Vnl6y$m>rZ^T-rsSnHWan{UyJ*<;Wc8z z@99u|yFV~#j@VLkS=I&oHHM+vv$Q)2&a5>z7k~S`Fcv7Y<>&ohwf$RrA|&u7_0s7( zuwgn>%yl#k_v<5!==4~y0O&DGbm9cbTY+#<<;X{;PCr-D&glQ&AW^)Jj3I2 zdk8GEt6fPE^!V=kIxAzkR$e%L{uOZVt&QaWzszPjycTX$9BR|Y0 zUkWP2YO1$7v>18yr@wWT9rAxG(HzYS4Z&hGZ)5ZaGeY;}M?LYzv}9)`$-1i!>lh{h^Gi_LJ~_-lyhafzwe0{UEWwjEC8&umn*6_my#4 zhER&T?lre?87Ii05`z6d4*4%Z7Rk{3!1k`{)r30`Mb0nE99Unyv2Ui~aN!vHDM7|! zEEvgMq!-#!6wec3LH_rn))w95ytC3n0eF=t&_C}E8_uw>I;`?W^@5lAip;I(A5}l^ zE69%_tM4{uIVpZ**k}DhFRdXo7yrX&^uP8XEJ3E%NCeX}PtWaBa31?wvzG3s>Wl}X zIBU%t-X;AO1J*;fVpt34SvK|H;I(E@X&P6?C&}(F^plA}9NiMGae%lIJ)j1eF}D;` z=GV-^5?O2OCHDiPMI#KJte=Re6=mc+yiY-O7|vyCXsx%sT-;yDvVxTghel(9#b*kf z*9Al@)rEuHFsuQ2^reOy5fiDVMKpyj|F-sjefE)%IwL!2xJc%0lWmBctanR&ce2N8 zMm$Ar!MW{3Nk61e_HL8va}hw%;(W?+_SzZ4=|X_wkCz{v@@F?yQiw>=bo#d15UoY2 z=g$!ob-wV`Zi0V*CE74T2FPss2^SA5SO@et@D?rz9eny&nE@oYUg@Q7f3tDi{3)sb zk^_T-}%v| z_!%VyHS8J8>Vr$f>UJI4g0HMD{uraNvH&ln%4e( zJU)s;R~s00b%k1M)$5;Q&odYFN_TFe#Y(ikX`}sEqcI|{umScH8yNJEIRI>AT(c{G zny@Xnb?O1@FV-)c>JKiH4rAJx*S9wN0&+~z$K0Y)v(SNa`AOJW6cmyH}IvbI%h zn3*cG@P=b%gLh)CpCWW32Aa2Q9{wYKFWv|#JVAw}KEB|BE+~F(a)jUMg{9$WVHD_( zC^aaLvYSTpYt@9b17K!cIuB?c-EDJuZlJ*b_ba`v7r9~sONw>U0VUkn=C>$q-&X0zyh-|B= zz3}y+YxB=XX`S4b^hq>r|F0#XA&p4f%K_S}$JU!wtIYrn(BP+3n#O(`5k(g@E0b&M z6*dBZ`Yxx5uCmlFa z4UOn23rWoOC3-~wfGKU=@t<>e$^uOYkdAnlZtU-{ zngjSVy$%|Bv0FKBpKrAHx)3#-Xr-o5sc2p#?OolbU#Y zCC9>b$ijbfGhrrDC?IN&9wt>fz)=`5(pQIGavjKwzbBdJoW(%4&2Ro@S>f}Q6yAZ!l(ssRN2ltMq})sUhEH42 zvk5?T)4C0?9b#j$%JlH(-VGGGwRY(1nktjp>vF(7WVf1<#_2yrigZV%<@N8j=DSY9J3zJT(Og&PeQnhG1S z8j5KllMB@;^eRkXtEU&!1S(#W7z%V@M37%DmyJGdO7d8${mhSv`lvLlH1KVw)G5W4 zcD7~*T*g%)V7ZxONAIdS6?<;E7|spbk_szZuK`@Dv-@^RPq6oreZl2M4P~N%^2aq# zVtkepm@)Zu@rcBSJa@aGJKsa3?b)Omq~`G-N%zr^?*HV`-KR~i+=m4PAGqy=t^m#H z^v^`6BTrHoa9gph#Pq9 zniMI3vJG$KxA!Fz7kz|_{-2K+<>ki?tf(;i z&=B>c+4r5^4n&K7{<>m$zW86as*kN7@FB8cmkJ{)s39$D?PCyyuUDWI^!( zkf;!F5u4pUZ?c7JhR0@y`EQ|%gunp2WxOWupXcKW*3Kf+KWGY=cj{2E;u3?Y4w)#Z zRh<8yH(Mhn{9A;F^2gw|S1?TFc*12xB-kgg6Y|T=wlEKQe{Qdt-9Y!o?FZHL*Hb z-aMk&_4}(1%Bj9VpirOt^^7Jgaae*ws6|vr;UP^{^XCgjqKqM96{EoaKF484E-WNs zD2_a53$f}~ZF8Ox2cTuhuKY=6tNEA^x^u|1AzAF``L-G4#vH7HLYVH0!E%@62bd5w z!6pCSUI5!O@_96w*ODftzlXmt3G9NZz^7>^-u%1MTYZiW6?_8nj{HsXo5P-lduI7r zr6ra*qOez7qS{MwEvvU>Q~c#07E>e!re^y87#BB^VHjMS9F?(t^9G_!OHH@N5G3tD z@4b=chvn2LKLZwHebZksOe!vH6vU~hLDd2!q&r33JjL_O6F6}{2^#2Jo0cpqgE}m4 zUp_vz)Ru|@qO8^RCaNh4uo7D`oWy{+$z(nEa611m64dL&E)tr2#OMU-&*{PM(ie$F zwO|lhc(1qxFOZQT`rC8-uxjE?DoPID&is#MW3|(MYgq5SwCbJHVNhr{Z+?$Xf+s1l z?m0O_6xb3*n2@A0F5Uen|6W(GXg!2u!IM=Tv6B#Q^*;7}7MS}qdcg<-vdL4OO(}Ik zyxv+~3-5iHDKxIK*~x?qeq*fHP?v0TJE5;{vW+$vH?l`}5qK3C;CIu)(emQ;Qteg~ znaS@T8I8s5erG>pIucDi`4Rr5VM+wa(b2IuK%r@N_PnMPWCjFQ6O!y5jC6W4`T`ws zXfQB6cb(_`W~L!GM4(B9S2WJhcZ`-j6%`e!KNggcz zyIvIrY?i*k!Z4y|n;{6tr0gg)-Z~dtiG=9*;(7uQ_FXKWE}K%jUCGXk&f=zs^q!H_t&b)YARZZC zHcM(V+EbDEVx%DE+w|M@d|R09lP1NVDd|*Z#|1@JO3IqAr--ul=6CmlZi<(FMOKAB zJ_h~J^-05l_lf%Z*Rbt+STZTwt&5Sdgi(2Fbuend=iy#G??B0xc+_AsVY-8 zk`+Uv;l=zPCrhpfm%8HK4{mA2$5GU)JU~xN<$am!W2MDnL{h2?Kc}TS4 zlhC{GbGg*CkE0uj;Db0Ii|kb(NQK-qH5h!S`XF7D$LQ2NcO+DmIojlHnJYRhFfr;q z#-oUg?x>8$;$CT`MgN)43deUEm_yS`KJfz?b0?joAQPgJsT>@fcf6=sqI<=WBLEKe zU1yQJpAdxf2K!_No_4+;4KsRU@FUiogxSFA9XtE9%eCYw2X@Em>@!YM-8R$ElO;;J zcS1ZFT1r}R!_HP{DVm$WTJ}RT)~Nw|&*R`^36&f5b%1<_-J8MR>m)aqM5*lkiAmJEjr7K^?|p-rzxoU^^cb6 ztU}cv(HXNA<0n1{W~e_3^6F8X8za!t=PaIz*HTu_2Fx(_1`lno7JY*s~`2Ge6FtNm~2&iC^6msGfuh*6zZpq^ZrtAc+TW zRyy@St zS(|<=WkHnLKJ7gPrc|Id)ra(SH4(0v)rXzkKP{Zs(*i6LgCTV;Zs30YD{F^A0Ga-@ zk>e4Ko%<6>ewORy`N>+EsAm)d$-#N8+cuUGyLb0H zO@9_7k7^^@DSn=#B-fC%C@LwTZ%r|MQRV*|_`HyOHnc*{tPDyFyK}#vpe4c0Ty|9XbOg0p6=D^K_@@l zd;;iiGLX%97Vo01AAMu{@qXfBEiF3m5>~t%g+8j)ip-9Vj;6y{9nk@8miyQ`%A8pAeq zQ{V-1>uM*B3Nc68<;!#O3!3nLlB~`3VzQRraw9b%A)%tTT9MexNI#6ubO#>7KC&5G z^Z?xd8MZf>E$UtaExcXwqkSO9IbHH=sFlaioFDqS+*rMr4^(b;S!R7w&1Q|J_HH~D z^=9SlhP|(@QOXcek{3*awRr-bzAntIhrA-`n$BfTzoF*$t)FjoU1%IxnZ=swde^84yu$W`X|{en(m_iZm5E zC;g5OT3!y>+-N4_F6YT>SlU)S&w_W%uN%srsA+6$2Gy*!rKh1&$`eluM>Sm>8e)&N zV?b2h7U>AchAeI>3T>G&HN7T37-y86{k7}PbsT(j7`tv9;nd17Kgpl^{7)*;7HxyL zPX*vTXXxMM6B!RaLARvpakJJ{JkpK?8FJ=R9=-jwvr)ggF|@Vj{>;J-LjT= z$b%KR0JcBi@NAyU+&)Z<5q~PGX(CS>kyoEdH-6gcOWqAKYJ!Cm+Hl>%bnvl}UcaMe z1_+kbZPECU6I~fUf7=P~dn27fLQuJM7DQ9{j ze)P_EG8uD1-01l)nc&IB0Ssq!2R><8`z`D5KGZ_A%V&uDWkk~LrGx7AtgdWl#3}))4_!pQwlJsPyQjmDx%IT^jBun)p9*>w zqSK8T0;n^-PA!lwm0=8;W!7H$bur%?eR$}Y?K34^Z~qeXFuEs6kGVIV8e9teVB2+5 z3f-#~#LO5SvYb#q3#}C+r{L<;B?+)_WLQNK-n`f|EF=^9jxndB5=n317f4w z8gt9ay`XsaUE%PQLZa;Qb{#^c;ul|Y?;qr^Q0BJ#==BA@BAzcte;P8OxF0e%kFUBp zBwOz-$%JYp*{8iU9RtSgWnMm?YWluBxjr23sV``2b2D!O(-hhy>tFfW9-2k1*-_As zwIB3HLr-37j9xUleP4vXQa~#G+1IiCI&1iaGb4j7__@CcJH@AE6!GsQPco zEpQX>e5Gj>9u}3@>>$z3$S9H1PEz^PJC>f?8o|-rk_xF`1sMic#GuKL2P0TVR(b#^ zG31?5i|(%+c#gmd-Rg9Qz_Zd~XwB=LMmB2PE~17Q$CWwu9#8**#H#@eKM_H|*xt!* z)~t+^UtgATY4&nz=ObcBEUZIpIv@zmjnmKWq=X)bB^5v*H0hESeU7}8rJfWbB%MRmxX3Gb+ikp=Fs9NfOTjB149*^d{%QA6LZ zkhaXZ^anFH;g6Oyn+P(Wy7+d( z@gs(+tzbMy0Oa!eP!ME2YC2Vp$N4^ZXPf#pE9fj*@(&VM*Li0K^8AXoDST*cOIp2ny(Ti(`;FO#z>S(zn*Z9<6b_Yh(U>{7NP??hn}tvSe`eA2H^1# z!R7|aqtYt}yW2M}gEci_=}*RF!U0*$AgSw?_Y#5aD+rhZ{p+uf%#ETZ4VTuN;c|dJ z!HMa6GgHqh3ZD_ajWtBr?DOo{u+2oyHk|~KTfes3L$ipfo)Wgc;#6%+bYMPk%L5N$ z2T9%c?b@`}Mq+z44OGj(47t=6ewdxPh&qsH9qZ(jYnAso{+W(XuK-$q^)53`xP2X& z?YgOgdpdnteBILPsV;#5)@8K}Vl>QT&14`Dp`CMjhzOQ(g7~3(cH=Tq)4&_25DE6o z6LQ$yzl-MXpyyE8cf|<(3=#{e(7L=OX-9UF9*ydL;W@o5FE22kyPMWTQZVml`Ch6Qr-QPtwn^`nn zw4B*Lr$?F}8X1TVb$>{vRgYiMG+?Cee5I1e_iW1V+4wE~DV#qJnTv_pJ6SvTkCoAgL;xWIAPxq@C zebX2!O!DyEsRggAXAvWb87KO3Q6Bu~9uvb1oiV9Q4%u-e90N3nNA?Ruznrb!C#fAx zt(*?m++IF#g~$cmyi184HM~JDOZ2j`%BS!d&^&jL)TL@YGu40QO6|H`YEY77YztSe zeGK^?`SNhqGGa<|U;9UqhY6jx){WZloKN@)Y?i+4+D2wG9&z-WN=AS#!K z^}y4VOl}1$k!t*ueHh)3z`@vJU*_+Nb6^)x{_Ff3azR%ba^INqBa?C1N#kpMJyKX7 z8Jt9k(FZO>y|scBPhDhs>*DVD7fNM##|Pq8^&+b}C^ueET9S^j)MeD+IC>ktNZexw zgj_0Hm(7Z5vz}-a2Da`sfWGi=JE%NCGQ$IxBr#>dn0Ze`V(WF5K67+`aE@5$Zz%ETNHZ zeVuLJZl9OV%q?xP*)wvv4;2|WB97_^>6z6|L)j3Oh@PYOe*YNl4W5qmKAN|*T%G|S zeK{HVlUqr`Oxw8Yxn(<GTh0iHMBQFogMFJ5mXPHy?!;(a`WA+Gatm1jmvt-^8AF-qc$>i!jDkbD<)e z_rqg&Z%c()Jl&pLVxf>@wj>EHf=PN4T()F7gSQNe z*RD?XYi|9>T;2P-AGSOvNAO_;zJ7&x`)Rwk+<@Xw`PSK2aPyUhEFBS9o*zfKA@tPv z0jzc6K}#zHd$2|x_HWfOs;VV0Gkm(OlqrKu)pI!q%>QWc+B#0GO6m2H)9bBOwyY2E zYgIfGWQN}^aopR_9j3GK^q6C!q>=uRMrkkSc1NZeO{KWM4ZQ%d%`%iVSiy1;_LK-g z0=)1%JO^=={9|R5u8l#y377mv!bGcdD9ev&OTuzV5P+Hxuy2)5Oad)FN0Rokn{@W zR))r%i6E!a>Rykhs@F^b>KI|s{pKpy;Pd>hbAGw?`$A*q5CSSgO>Ja#PSs>=k4_L% zl&Z~DhT(2${IzF(<1*EBi<|$bo;rh41d1?1hJKMKoj_W0L@Hw(u>*tT;jGrrp$~E) z_|Y>W6_ue=E_3_NgPiQ13{P+=LMbD>*PovfdzKh8C+}aLQSJG9j1^KkEgt~lwtKAPiVv66X(et9eO{cpWZ$4cN{ z4+OX8p{ZT5gmsOaA!YL54l#xPWD!<@{~HNc^9(h<{{|scP)?afAAI~R9b=%LU0JbO z`RCx&X;Un(T_emRbwyW?)`^^TgCK{##J}WT&bppAk5&IxcgRlp*T!b7uyff4Yx$+k zTM|m1;eO?Ubiea}@V#^`0-2ajfox^F1531fy|vsV!Qh_Y5(0foiCop(PI6rpf4J_Q zJ5am=b+ShOB}kvj&0|+9Qt}nzHXECxN~~DjHZR0nkbJzsAs*zpp5A|3@B?D(Ggo7w zB=OO#twF5)hkM?&nF+AG)GjrEZXlgC&3lmH%h@>-1eDhDGCbKl#nY%^aYHa7@LqdK zh6F=}8EAYGNo{*L*J0OrXr4H)X4pG}vexkt&wC%%{)XwP-^?|%VxV!uN+ae&vQzO) zxQOfT4?)-Awf7Z$bKi?`JC+hm=%_dvwJo;>=nMFJ!z5O)nn?GHAN7eYEY>T3{+#j( z^26&h?FzRU(ZcgIi!>w2m016#q@sBBW~H*(YDu--g|X$$Rcg}vDew3QIYaR4`LY?9 zzo%0MM|exI?eN?BL1YRV($-C$Net_3H_Bw{! z=y==Hg_2=&zqsbxVuZgvWaO4P8T8WNw+eA}|ErV?L!m!zWU66bes&3&?T7zug)0sx zm~`W=Mj8ufRn6<*Iq1c5P|9jO+ipt20`lQrrrtf3U>l`yv@K3g^Ws%vn4|_S)`lew zKHvu~9sco`9I)0!Q3n)PZ!WWPiQeuZu)B=#Z;(Y{Q`4ea)|(M+rRwbzT_IgHe1$$P zEUKY(nzq{dHd~R9h@58Zdc+kt0VjLJtytSDwa@-o4O@+$E7;5oeRY>4BW%!Za9;Z$ z6l%mBl21Ga!CTTBP9QHE_9EF7i*cUEVZ1)p)AhiY*Oisc*;ceM8?+=>u zrOGRlhb>oPo)v^Ky4(-7S1QKcT znq7408jOyEoBazihZHKpe>~kJ3q5w-@Mz`o;*VzvnEoGFtGR!G?-@-NT2^L!O#az{dFUuy0 z;{z_lBZYOB_6Ul;yQlCoKx)D}%eSwVT!M+uYw=_GKy)-K&&RbeiQc0=4V?LOUA~fk z*}^bg=o3r624dV3C|}OXT7`c1nE@{;p(?LrMtM!LMd$Qyn8vD`Svk?;lgjYI8UTCZ zm5N$!8QM(~-IVl&N zTHz_|j;v(;^9?$C9MvocTMhI8thIy%TI7=Y`EuT|f;1j+ z-$mbKIDJWC4_I2sq3PCKbV(GiyWSDL&l>9jELXMk4tHyLSo!Bt2q6w6RZB5b1^YE1 zWu$&+vkefi8ur-&e8z&dL+VRZyb4bK7;k*de|Fy3kp6ZItD34Hqp>4i5r@HrJT^}5 zUqB?EVmfahRlOZ9TzKu)EFb2$fYp0FfLp<0y#gvNm= z3V6)Cy*avtFnVCTGmID=MT68@3#^zey9q0JdTyj~8Kh4zc&>@-({HC4br;u=a z`llW%faI6SRP%0(Ev-w186;N8oKFp~b2cik0uKh-;OklUWh(5s82-$@5ngDw6k%xE zGd6N+0ej}GrFfik6yypjMx|kgEop!2R;C-nnyG77x9skI{25-TmdMRxr<@N)dj^ryq={X*3tr<5=Dqtpz^ zw>MH{8mlV6`&G`Y%>-`KhB$~vGiMe;{D-mfrpC`Ip6t(fW+Qa;k zS?2PKM+NjmBP0d?IpAPAp5$c_pUB5>4X>?p69&v4F?%`FZ5?+`-^LMDQk2kZD;$ambAwmBZXgmJ z9lHSiTTU!N| zTRX%j1=@G(Wj%kuiJ-Q`K@!UvX~}L=u;i19ontUG_Gp9l{?d*kb7S`1QUd5Zj1TLH z`XVUgdt&CAc6kZNh7&AXH}z(mSATs!|LgTf(82pM$I*_{5XGT*N|t4w)~wE_Q@}$F zFixo#)8+3=RW#VhyZg|Wmxo8IhKfg`hRUj~@ z@Pxk%2&d}iR`khLNuchK(>meQM0@J*?Th%N*CV2$??FJ^JuUgh$xHHT7ORN?xHVHY zLKS#|V>$hkT8xZ8pc49qc<8+BPvXNq`ejKC&?MnuUqRY$g*S#_FVz>uJY(p*tdE=P zir}QelI-3A1UW*3{4XztuNOl=NvK|RBHLqJBZ_!ThP-YFbr+kx2hpPobLo76EjbPcmXsz5n;%ZlP`yrzL4tW7e+xS zBp~fhmZQSWKO`t^*YaU3yI`1=JL?|Z>}l6EpwmJ?V8@!$2S>2^6XGtrxWLcKtfjg;XR0wuW!i&>`-Ng63bJHF>uq^>$7});R?co>YK4D8*w? z%F69YUS@Twn(pVZ{y^L(xZnve_-XKO#B=}MrUkwHC`-WuyRmlE@ZS_j@LRXs35hc! zcM?(k8pfeI@yU*jj>o#!+WWyBYL{(l|N) zDZA1bBKcK$u&i9TPYM;fa~bHi$SVUdq@|xzY5qRudZXK1h7FQqx$;3fpFcypFZaeL zNBnap{$Vp5e$Y9SC#XXrBiTC&@q#o=i?-BsF4SrQr_X3qGa@@o-}Y+`Mfz@k-pudc zLwmIhJ2{p8jZVFE>(aX4oFM}+69nwkdJ4GdUo3!uz)#OD-GLTsztY)ulsXT`hh@JE zB-aGkbzfC?LJyY@nLDjP1i`&9EgP{>3%cgA>(@$A`E9@p9~7rHa!SOD(${`%4rPy8 z{#A5XHTZVhSorh6N=;v$F^QWh^c?Sk3B@=){3*T+jMO93hI6Fg$xGhTJ0fWqVu7TK zu&ac${5h4PVl#0<$tV3&y2wRUr0DEgdY3!RIrNxFmmt5Rjmx&=~?Acyi_{bo+kyU|(Xx z6T6nATOav|khM`*!4psKI`wkoG>?4aulS^nLlFU)vN-dB zLT9T%87IEI()am8FPAkTE5@(eaWEg^p!B+_X9PQxSacnJNU1-^qRXi-#NsVcMc@(Q zNw}9{#@1n{AU)Y4*zR|5mgGlhJjU1SqGs4U^u#ktxo{WJkWwU8B#@7%U@AI4@N_q2 z6ldVZlE_=%TOgTR5Bg2YF`4XZi1clK^UA&ACKQT8&Z8sW`zh)NuiLKxeF^489yNfO zg#kqC+V0Q6s za)kIXezI(FJ9IGuW&i2A0U*G1@F@IA%GBcAKJd$zO2t*HCKcMgLH%(R>cc~jYc zN(#Nw5VdLIOmmxpV&J$mEjA(OqTBdi7nCS|FaI#x4W534uUSaVtN_59xLSa+XLs2^ zHi@$79u#tjA=RQj8?o;=>+jyj-DVA&eR$!m0PaS-T`br2OaXNB-w#7yM?!P8{M#(5 zjK%LOc;kyH1bITJ6Hqcw^Awi0_xkl zy5W`t8T<;!D%jIXFM@0ZvE~I+Rwb+1;Gh`@g?N8AVtsfg>&eTDi&#fnzDwPsI-117 zvM|t>n8~(W>-i{TvSiwQ_Sr_G<;;UIVE}Yj0IGl-Wd1Rcx zW7%tSe~JQI`yZQ$)m&_b*G>w()B#PYK@QqCMj3Nc#vvNN`f>j89dESrZwyNmb5>co zt7oZ~j?c=1v<=>ajXd5^e|^+x3BHy@!U*EEt=N=Q$~LyHlb&7$uFD*#Omhot2_gC!7q} zx%%6?u*#d+`&%?UA^g6apV~HC-6=Qy*37?QjxEdw7>$x=u*`@AWoI{9iFBnLP4m9r zCt*)?2sSEVlceoN4tg{FHw>1oX+(!#8&szV%B9d2GHB-43HvNB@0}~w=fdmLvV&9O zvI;nK=!$3AXKQisSAV(K6#o`|7rW1Kr=F|RvleLsY9<0qpz49Ie=A)Ets*>Zo^yfs z^ZV(1EtD8#ePiiEZX(#kkVSQxMgrJ4c8@bzgHlE=P^r!CYR`NswUp+T3;)9sIz~WC zGMnvllqfDX?tXs_=B=<0pFd29KV+cWN5kH#EqZ;}jwea~%Fo+n-x4B_&1E?|_s z1aoh8>U4{5Iv;3%3=hN1WLHn=q4f6ItE6IloBu|(*A`zx5VUc`9;5WvkC2RKQae?+ z`1}oBZAApy^Cmpw@gffBE&IReaIjBr_1DlA)ezeO5J0--%}!hUr#~rIp+8wHu8OgZ zRzFn;+iP&{YSLQvgckyK#xN~}L$t^v5mC=xDL=7knh^liZ;g$bq&b6bSle8;-@~w5 zc>d^bBx>^RWyuztKI>TJ$C*j+{A%y;7KW#1PX8Jh^3iI2e=*|7O7}6#lL;QOpqVwQ z!a+(xl7DQR*sr;+2=Tuw(85qxu>VNBXoKA|s5V7Seyj&NAd7CQ~0wqnlV${lwa6|vVLz`BNDlOO^ zNBy$*_m9{v3iFZl#F4qUWUF6FZp*o$@ZMm-=vdR4gQ;6rX_Kfn35mu~8zZe984U3` zI)1j!^3(}vcK*4_TC~}HX}K-`aJFOCR|Dlv0%RJpH5JU$;%!x~d~JC{o{ewNZcL|u zwf~AH?S|!pSeIuMch)75;_zQuhK0uPXi1H_{eZ=u+7@HJm!SLH0_1`#$o=_8Y_xzd zf@Q@D>~~2;qTk2)LbgJka`h~`G7cEu?3ACJfz$MQSTlP(sybUNs%I_2snCOd@Gs+g z8}>htFh^J$B*c4mcnsS~$2h^;CH2X_Yi&r?h z@yn|_WGt+=>_uHPTF93ZFT8UTr){a0P^0c6C5cQG+<81q8tRI8X}(zrytMpZzVJUt zEd_chfGK8629>+h!{4k)P;tG-WpsMWNc=ZpUJkc+x-Kk9Z-YqDu3K(?B-Qo(ts>uL zLd0Fs>-~u@4Zzu*>8(NH*J7=Ch=X#<_EyUrIRQ5PVbLRQO`d%2QUxdPY>}GWaauQ88X#%Y;v1xvAKn zeRo#A@_7-KUuv;=X<<<3L9?A4S^kTflqnZlO^s!oZEfz+7W99}daHmqo26?s0fIXO zcMtCFHn_XHyL%wGySoIE;O+!>C%DVtI=JRc_P@XV%DI}W2U@DCS65f9<$Z(eV)k1M z7>dL+CYGI-7;bB^YZoLpeDWhSPx>BL`0eR6mJbQsi)Jnr|5CdxnCy<|aj8;W9!!NWcwPP)+K*{N!Og;1?NT{n!okHa0T|m_ohXRMUBaZB!vNVVm3LD9@I2fX~aFWZ}*f%Q7^Myfd2>jG-E0=JU<&40c<_wMMC+j`o8y9V*U{-{C^Y1W`N zvY=4S=y^mGoOHyk7U!2jjZ>WLGfg<g#f^nl02=AK35V@CLV(2rOgE8}rY?TCa7(;#=N#45!W&3At87~3^u ztT-#msQ95i&Bd@|Hh;Ao=f($mDk}ZF;JpvZ6V>_(xWyqebqPDH?^K_<*PwLB?XtNS z$BLuGb5asxjVpkja@&(tb(@SwUq=4*)3qUDu_(A%nApM3P@3x0EKr$s6}&hl8Y3iK z*+oBA1^_rYIPW=@y&)uak5}{l9_jgD-Y3;?U)l82GO|-}el_9)w0TwL(T4$J^BX&@ z(4^MiA~CT5ZW2+<;jK>f1C>zP&-XB49 zkv0EoJ;H?V#R~YG$$&r9zD26M*IiWQhJ(329B$CvV$RBYUq=0rBNUy${DDif)y)#F z;ifva2+imqWwxZ#BMwKS{2z1t7A6-sR}Jn zmD25IYu>3Orw=|B8J3qXo7mqCtjdJW&HGL)^q){mAMwv9Qe0h4EQKxSyDQGNm$?;a zRO2;Hq-3L&ota#x3vsIJu9q-ww)8YNr!63i&mv>z8d1GpHi*7~~*p6AX0!(mWMLk57J_NaoAe9f&)2HKa3yU2$soE5PXTPN2m!dbRug&)o2<;v<-dJaXZ{0fV>c6*i=*%UVQ zR^XmenS|&xY04~Jl3Et6RLUx)?5~q`F(1(4)9T~|FoymQlYyaGkOkUdfL=rl)@SK7 zO~4?Wgp(+jbYzyqkH=&%n}U{l&@+7eyZ{Wyr}?@?wDy;NKp85H2V#Og5uWP*pdB+* z8xO>u@Sf=UC+@D56esGb6uVzcpG#R~n~`Frj46l}oeY(Ea#glnC%GHrXPFuUfl<9E+1F{l|arFD%$aBRNn$pFq_ckWtWC=7#GhS}ycY&WiI zle_fKrRu-CC?58%6`4ozKKtf~)TKjxgy*6BpvE}vU~v%S0~O-%^*^0d;7orx^~wxVU3fz^5S zhH}OzsknDS!ak~v|32AY)8n&=_v3Qpf)epOYcEazYe0W)SaS&(NxN>ADnzz27hOrh z`$^xt6&i|@54&FATJc1`&@mv55m|j#E_7o7wl5>6BaPAxCyusBen+~Ie-GfATlobp zcm6LIL(q9xV3~aZ$K|IX6YTbPg^|xG#T*znw^l70RGYCbPnKZi_XRkfHl+_L-r3jU zUp#8<)}6x%o2h-Ak#s8Bx|F+TFbq6Sd8OQj7C3VM`m2$`89qtn2?^LizucM|N3(4O z+>PM^w=hSbisE8eG}Cjes6T@nYfrYvlEj8^WG>#Qla$zLd3+%S^^a4nkrU3{;s{$& zF~oW%zG78>l$w6@2>Sd`-Q$RHgDer|fswTIw>`pvU?3I#LWlqd!?*x&mhB^3qzre~ zqxL+9#{%JS_8>dMhVPB!J8V#>(1^$?5Kb<)y@G91zz*-oD$fW_QKt~9*R?;9|JW*b zSf7i7&%@xGr>_Z7jQI|QKK?R_p`R%}$3+{1V-mP_@&9pl@Zkq>=R5iCnJQ2@+@J`p zd5pz>XheTw-qf%}MbY?1AdkVK1$JvNd+f2U26+x2!UyU{w;(ur)F3V0(hv$B*2g7g z_Wx}GIAhTWi<3W$$r7$cW)KN*Aej@EwG}6AQ`i1wcCOEs1&^Z%b&!=8Cq+m%_&1mN zvx8Pb5J)!aVK9*2ZHup?vS0a_`HSQxNr7+mo~$}{)xxYZ2uDm3^r&;H3+ zf&&4bNX-ey{fNPCm<5wQvX}Rq*5{_i)nV=~g*QYfhnoOyTsv(Pu$_CTF#HhU$HXGxHmW84_md9nlVBC#OWR$_ zbPUUS2PVKGeEbkqnd6yrNq^3uQyjAj&U3CFfyI`;u-i_vhQ6%h|GDvhy&0@@{FL4Y zlEW(}Z7OcyoMrk9)&u{9E}x+ zWrS@(INn4{iI;%&|DX4UM3^@_tsXk039Mvk4US4LAZm#ie?6Me3c@l5ckqlcwa{SLnDI8SUhN+a&K}(Nbqa~OgR{z?AQ#&Z(Ap!FHRZKDt z$tQ18ufdXU#q!Fx8Oc9f=$;iSgQ9_1^gRZ{-@D{fb*TO^Gw^$g36$FpY94!G>k<8< zD>TcN$+a9C87p6LFlCo2>_O2^qtnf5rit^IUbWkwYIkk&hSvJ15m%s!b zkqCkg+CPV!NVc_X$Eqn`Ba}RCqMn8+NM@f#37%~gJr4JD>Q`j|N?eHVh>3$JwEVkw z!tbQTMbvhqmyjcU54)-V(*^NgNB_@HjVhW|Nw|ki)hLF@aACN&n42FbIBTn!1|}vF ztE&v*W26A!8-#mRXpa0N836uRn3FV+$sc;h3wf-M3d^luLb8;cF&YHD0H!#8fomvY zS!ZHN&ghQ&x9fp#N!i%AO0G_eP@?C!d;@kBkZdTb_je;ilp`pJzy!{(el=MzSI*Dg z&mqlg|D1c6a5%|Uq^(kmYtLQ9ntiL5iAR-bmBPLOsK1i7h>?+Tqa3rOF7{s*m7ous z&<-tW?y{UQuLsx8x4lzj+BBm*{#qsk_q{pe*&ZXKShYZU2J3Gy`9J>`d~L`fm^M>c zplaYC`mV+$n(Xb?zeYz)wmWG)AknW*;)p^CdbIohu}-~*F#AzE4+*(~=lUi^b&OVb z1RD*pZDBpy4atg(^ddfm^HEWQ3Za_|?ca+71;y`;)Iukq<(IN)I}Z2kLcQlOCsNYC zmihFlZjPkKGH(=W#j9?ED)OJ%KJaj-dtklL^Q=_wYO6p5uiZ*+U&Lj`bL}uUzjtqn zUw!f_@`NnF1E@xB}N`*ohuF?r)Khw?vcV?IlVC2FI%Sb_=T?X}#|-ceBY^WC6-pQmVK z!llR9EXv6EJBO2xa9=cH@(1W-?loj$c zxtS=1mbEpA0$O7J>?IS{E&x-$01N<>l6=D4?2{(R{=Fr+QSX+yp)PYc_LI-ZZn!2V z$&=YxqrC+(dEBK~Lgy22g?*~*^T_1Q@U%t#ZLNH$5zY=Sx@lr>qp^HZspH@C6BDgd zvWV$;z7W24ClG6gjFO|pJ3Gg4kLOZCDS#U!BHe>a;pqPs;Ob!C*JNVmP)9?tE3ZTg z*PkIwhFxb-h#IDWZ?Bf0+X}_si4}210?s6UoB*qWHf8*}@ExDPSv?I_ zejmIkw=9YzDGo(t)Hyy;ya@>&eb91W(cQt~q={Ksc$7e*XfXmL zFR@d-^%Fiy%0Ofe?5mcNjTx3J9M$Jiu5dmfSH;lv@lh z{Uf=WkFQ=$P3?Gi$uOqU0d4GWUbPPYe(1ByFDt+oO%08e%A1p>QLhbno9y8euP>i3 zVMiDu)LvoN=ShhwA5Z;*$l#KS$e(5;i(KHox81yF0Q%|A>))Qt#+vs21*CD`gCIdr zpbrSkv4O7L9;|ii{Ne5_&g2cz@x;xV7c(2u6U3{UCdf%_{QLxAD$hsHE31kWgb8$Pj(_C;0lmzz`t5&}C;*N5(1*pE&e=WB+Hk;5FL6FOb4t35XV%Wkwm zKv4MYA?=}H)nNxg;~3!bi%?g`!g&75z^@k`QfxG3;x&oG9U5)!yg`jBQlV}vq9aA2 zkpERdxMK;>@Fh$oJTp@H-!S#z-tVF;uj-Wi*hn~SwUg7CGVO78bp_SX%DV0G*QsmM zRJgdLZbVkeY!4xEZw>acpQ|-L-Wp!B+j=)!tEl$>pM@McK@H>chi`>Pcu0~kxe~}VC|79w1=kKmVH1QB0B%$)_h$TDB z>@)C5xzW(C3tF?JF0yCxFLjAG8=E@{{`rK01;v{TFv=@Jw^JN!Hd&97z?a~(51#Vn zD4|9RopK9Mjzc_TU~Q|GvL-Ep81Q>*I$Wl_4xyogYe9(F6Z|>a_d(I6h&==F091@P z#N(|B#a{n}ei$TGtwZ01aKX+l{_s|WuxnKGKf4SQK0!M~Boa}Oj+5*=ztc&~jK&Af z@Q|S}Vr4ddWKB+4+*Xv|UBV&}s~zqkImpMMcKKC8uxwc(W!ydO?=7`#AMB#8s3mQM zwniH8N=w}~mMmCEWxJ@@fOSpL>IHsv0|c4$lEXTX!f)+xKR0hCnC>A6{kT4#JrQfcf?##0l4IWuf1gq>`<&>bk4=JRffpRSBvYS zDcEXlb?8(CZd~u=|Ca$X!j>diuNQ)s8g7?rNe?zMyz(l3IU`Jgmwn*Y>ZNmXKzQTa za%6bB6v7(jl17!inro(}2QK}zM|<1n7&V_0?{Zg*OGyvBCsoau9)drF=IbZnf=S8d zgY6k4><eOAXCiczICH@q-~pu$qlXb9G?cwurZ$|LVE()TEE}S=j{ya9~XP z4gioC#2gU6PCfh>gY{WuY0CM(vq+ee4E002)K}~=%JhuZ3gyzZP|Qp6{Am~pg0#yI z407@0y1_%yzf8(l3mjgB`=1Ls#thI}QOw^Cei7iV{`5_DZOjn+Y;x49LncA3C>t8E zx=U7{p$Y})>SR{XYB09K93&?KRD`CjaC}~xetfLLZF1bC!e4Mqu<`rR3>Kl@9UJwL z`Wf(A(o)h7>$7NQlDzUi=Td{}pnPtWh-E$FR72{!7$zdS)*;!OfNOmp5&(%F6WZv% zz^k?XdxpmKX976}w1=aMf?V$lXJ#MgyVEc1cQ^Z&a-NL;0l!oO1W)ZLvl88ZhZIb0j9JiiG zKZe^1k6r|#l2`?I-=#GLr-Z9(N4Q9K`*c;vnC%e8*6ob(ur_GklOMcKR-d11LRm%1 zLskYe(DJNi>nVJM^U++A&r=?~O(1!@j8RF=RZg>QYDzL|tL*r_(GC`lof>^|w>9f7 z=wSsnzZ`KH1*W8?jg-pco9{l|*4fO%d*1{-1N%@m(!n$oI?yP%5IaOjJeImj0dLAg z#5kLT>y{l==aPpjQzv$-RguEFPt4CjQ$zMmq2HdqFS4n=vdZTx2bR#kBISP zj>0v1TXt|5#V})q_O-;GDY|l7H$?aMw`uOzpr)lAI>Aepvi+HmKdieAQ7+yit@!9_ z#f5>DDQ z#$dzm)&21u#oOESd8%T-lNv6&mlMTa&TT@VrurFb0A5DjOk-(DiB>cJq~y-*ibWan zFL$;LsS1NmEJxC(K8bI7?vD1_X@c#jR`$gE!A|SE$X=Rb(}iLspMM8v&kYrymeG%) zRPCbt{$&qv05h#I4q~i$tc;1!NPJgxJ)*rXqTc|+7>EkVv3Ys6YY>X>$6;_HuwB(w@z<_Ut!xn ztjQ6a3$S6ihhB8^r^g~*wQk?jPD(~eT!1R2{$yC|*UAqre*F|`4(_PWBf+3j_!Uk= zEQoZA^b}}Dy+!v&*?aT~Oq1Y0ItBzDy`mJxtaYywd>alyvEx6 zOT}jg>a|?mxo`n#pvOSeN1PJpD}$N$I-G_05<--W1Xfeo&BD!n;V2jeV+5zv_nt)8 zNNX>T2u}WLBN;5kZUzV=9xggNDKdhk(ddh6?hgT}^7iI+I1tJt(=vF8O)_bHtLJ_X zpFdv@Na8}@qql)|D4>N-LS2;+9i?wuvc~W*n_@ondV-a#BMCT_&EDO05>zz%LV-HC z7r|x8;cWvbui<3yf07DftxKP{Jz!xr8gPkom$qly7X*p9=fV2NU?HmExF0e+=OM{v zJ0&AtUM}vRcMje`nXn8zOq+FC!w^w`X;9NL9lJ>DH!~?=h}@jUdq2wNY$K29)|z~! zH|P+LRMXb}eseUZEA0g)tBUXmrPphbYI0bo#h{9b*Pddd7oQU%KKtOQIP8Ib~ zE{v%3C9jphqSqqn^u153H63#M#%X`lM-e~H1$AzOC>DmeU>gUrl#M9LS5ZW+dgKfY z$qt^$6YxC_WS494*YCit&~H~5@!jn5H%+@!%Xr}5J{ca82rXw~(gVj5^55tE{M^SatTI8qRTQar{$CN1| z)C0y$?2Lg=w=<~pk7wF;auPZHf|?XDP>b*WyoFVC*f|Prs>P#Dc8s)1;k~T?oIcXG zNavo`=|b%{e_Ly<1B)AtotBgU11nVyh#%8_K4y zWkJLivb*zr-N1!turz9n!jN0u1$Sgft~^-GLn4)}HN9uiNS;0@^}%vbB!D|c@T0`{^xy?t(c7TkRsEqFJz07zo{3bguH2g z#KmbhsgDnXzCUwZ&idLqDMsNdz)^6kQe2SkkOAO{%{)le%T-zO+wU z+*kPRj*$l-aDYX25I?%2Z+bGNDmi!^|ExxdU0?3Cv74dr=Hxxn!m+4vxpgXNToZPw6FG1gD{{Q&q+aVx2e59)PEBcedG7@-@9q6N-WiNsNy*f%E@jYPHoWdDW zWtAlI^sX72PL_8|5uorb1o8L^tju;vvwY*0=4-+4B}{F+o|4iXi_O>>j3d>n&2zkKb$g443H00=zk4$|nn z)!NM!tjVbXj1koCiRJBmF`s4CQdKP%uLq!1{Wgfxy#M;i{EE9mrxBjje#V1_h6c*{ zD0K1ngszH!FEc_{#^hUm5GFRpSKp*^)yrdQoA?o?J>N#=0}|z@4UjIB2j%(Ey2}H zf0ONAi6~od?@g$D^W&M7uHsZEq4CWH!EwKo_}5788sfwX6~~?JCq1I4HK()k{7j@*WO7g0HW}%<&9QPvD-=#)^3!729 zHn_dhWrvV^Z8QXYQmpf>7^`#HpxW3V-hTKK_vgG6K%=oQ+Ybn)%XU1^OD=cdbHm2@ zuQYA->36T1Ck7ydxuqgX2{x zte!Vn4crBx1c&~ri~tW3K|e`lzq);CaW+k{;(N@b+n~dy*P<9>fm=jTu(4?(+0>z= z-5rdD6qL&sRH8=~pvCm=4CI1wpQp#AH|$>9*T4< zYCYYZk-P4X&-X|vD;F-bz7KAkFJFW`DZz()4o^=u%h5{2LHj(az_WOglv5t`dkIIQ zBL85ybX*KwB4-J!KgUs-(_yV7V2|RCf6Bv*2q1IPfFz}`Yk zvbv$Sa?P7~FA+Sz-P-|=UEvg;i>_xg`_+$h8lRNQ`0&jg6pi$AvXk%@)qNPQ`4F%7 zC&YwC7Q0kb>p!;4pO9G3mPEGEegw*6OPzcF&f)VEgx7;vq!#>5cdtD_l3e)DX`!!cZZv6gsI%ZLnR-q{;qTF!Q%1G2K2uI(9Ye&7jMR?I7^O5243Ot(rA!83DyZEW$XJ=T>?KqhM`M8OCt5 zux2!N@Rh$J{fyhBzb0ejWU;!>$n>jDInQIfdmTUe!TsiMVITpQLj^j=u_BK(Pw7T9 zHq$&ONumWwS2PxbWEM1KNuC}X3XC}dHscpTBogoTlhst~xjRjWlfC0gNQN}A94+YP z$}_&B8p~EN%o`sP*6zhXSZ3mH`W5e@ki%_Ixz_}=oNo?Z zQO_!|bghejA>CTH_S~(o^1*vus*8ul^yi-YXb-gGYzL8dtkTr}r2@4kcNe7|&o>-_ z6(lun?9H6o3pBo+u^<#k^Sk#WH*Lntjb6T2+u_%0cO%KvKce+rGagyNuM(&5uv;2T zCru)%N|2wax94oxs>*`A_FUAlK%mx~g+VC(OzSNUxIymsP4MS+4{ zpph0d`XePiEzlbNbjY;}Kau#{(Q7{enK-Skp+-x33I`$VB2iv4)I!;s5hosdPF^e- z`wNC$npR)rfj0vxtt9igApvAOe1o_~jrMgu7E4!0D|15#N7^iNKNv+2N+hT~PKQk2 zU$5MMwggVbO^fq7p(IH!Q7V}^VaYWWQD?xhr{PB5vN`H1tt+X114h|dX? zwmO3c(2kB;3(+VoZcHy3#c-7mAsgL#oBhGeKW6V;j(DJ%n86j%7 z0wW=*)5h>qg)ZWxdv!KF)e=hb?{16@G0j~*3sQ9fNuudSYlJRW+@6p%Jqf!+j104w zjs5K2NiH(nDc)_v?KKL1sjY|8iMiCR!oDz3fcu!2_{}h&H-sMK! zcVEf!O-QGkZ=%%G7J`B8Vp2YsDF*z&$9pEJ_Uv+wg297Y>kQ{kN^yNRVVmw4q80Z? zBnTF@yRpbtdMoRmecX{p#1((1+{pGB%5MV3^wrtFAz7^f^9B!7yGvrvSZgoZIr1D9 z{th@-mP6kOQDS&uoSCw!(#iMOu{cqD^e9{xnltrE(>dPf+3g`#@OI$`UxKJ0vC(2G zKxN)`jz0oJN(|}?i%)nY4uFlmg$Ii&qGBTZxFP(gSH0|Mw3>nVG~?a^Q$iWXfcdo~ zBqWBIU8mk67M-mvHNh*PBC&hsi@d1hXOkqRK|WAVV;sPipZGAw(rERrZ%<9fTH4MU z>0YpzA~+@AIs!5>421x;22rQ4;q%t91=o&XWmbde+|H;UA`y(v%bK^z;hMcy+);)n zY7J$i$#TqY8c@aJctCTjf{i|1QPCx>13p#*{Y5FD>4P+cc|kx$vWpLHMhYVo#jtL( zgQf`(2KKv~Y1jA{a+_a4L3)aKiPL4x-b3k^B02@)#(!t@z=yh_MBqVx;KV9obv zs7PxqX#e45%_Ys>`JMNH3wz(r{ZIC!XJaI1X<}x_t=OZ;;o#njHbKBbB?+6sd`Yie z?8|WLjA_LV569ld+wEtC7W#s8kr^!#2#YQfh$;d_sx-+{HuBhiakg2tk=yk)%j3^( zS)$K&0A?F)fBX2f+3u-1gl+zu@=G}{k-m3Vi;BH`=s4qi$vPAPc*h#1&Cka(x)%wf zC|98$67<0zrfO;2)xj){{F+kmQ>GM4jJx}$CaU7-cNZgOjF(|WUn^|)>9Pt5J4dN! z9b-MpF}L1t!QLhp^Gi5T?c9O~BQFueJdgm_yfTgLm{MD<&7MxYoa;QpH0YjLnSnkYAX?pP5|JgI+f0d#gj*5UdlQw@PtakkC<#qaYxPo& zYr1@JKa~=V+XJ*CQCUG$`i<7Q=}lV`PfDfr+vFxB6-5Up@$Z#&wAeKgRe0l)V4LF> zy(Hea^g$A>ecRG$lqctV5e_8fNjUq#OUl`CIV4+YB+t7NPcFbk_!9KF-(03)cU7f` zXpSQ!g7H^v&s>VGW2byRiBWG)Eat-u^vP$?^!=9Y#~sl7^sk9tWEzw~aOTMg1pn%iCjeb=Ao=y7WY#Yd-lU zqJ;4NE%W^yYiwOAw@D$JECj$j7TsU%5ZfQm80e-xTw8)f;XTumEPcz|+TY)IbDu;> zZ-C&E=v%`Tk7BFohDCb-GF;6zY_nL}c#3FsV}<{&=71JMrNt(vV6VV4O_sk8V5HDP zL_zmG-&N#ic{#=wLizawWa`yE=n!s9ldQYOCV+mdgQ=lLSRHM|J z>l9vo$7b>o&S9-ElvxV1DSxTJHKarAa&VC91|l98j`LU@8Hs+^$R!DPydZXkSI(%0 zk_p3kXJ%ZZ^~H;fs8t$4`k-@I*jthpRHvlU zYCqqo9xi$IlL2|c+9WrT8w0_Df52!laU#1J{ylBhy9B|zAoK|;(Do&kh7O=UwnLU`P6~h8S|@SIQ!6}AZ;F?X+TcOF1O>nBhZPi zthvkPT=g=zMgd!BugEcTZYfR7!#fFMGssJ6BG(Jgh+CtvHIBKjSWlyzCwwg?ojPBy z>^fIJSqUd5o{4T^-6b;M9G0IAZHv{1x$PUk?7Svr$%WD2v`4}aLlE3hyEr`g31-w* z6BMYquVPt|5nWUdj}rCzG!L?gIu$77+<(j&B{HjT-n|={Uzb>ZWZlm)tk$P2bV-nj zVV_>nk~fxVZRe|u%_wJqM9)t6`gG*Sn@%whmWnPop%{yq8OMdops9>vTQo{Z9$$fj2_(eGW`Mmk(QUejAXT=;K34v&GnCw=>!}-@(sL%Y5 z)Vlab1{<+h$&adG-#+H3W$O?f4ZQ#r;z8Mx>8ux>_ktWp-Sid%QRE?ONV3|HMY#l+ zbwX84&Kj{X`x4{}5(azp7;?v>e6w3*rP||&1+oZ*hDC^#j;Y_VRU~+8#@hFrfTpX@ z#miDB>>AX!tYLB|vHTfr1+A8dmm%+vuE^v0OD~Z3sF}^Dg*s%JdE4~tCt0k6w!rCY z={L?%`32)6g!amk_qHqW23AM4L+2i+TEEp(*Pn4Vs$466RPgqWzFM1BJa^eN)(tfp zl|DEJPw?S}6V&P1?d>8G#Op10M` z+d%VbBkO+o5|d9#%<(qv4k{|^I2}1z_ih%OrN@-5E$5W_=J&mxuFr~5R|$#HRrF?G zI-s5@J>l0#(|*?e((6_KUZn=Rd=1QtQzv~pEC4Z{A=EHobaIr(N2oeB<2uaI$5>{i zSCkkZEtIqNY^lA)HvpMAXk1ijO5lQW8?5nw9&ndT90~veyBn!4*=~tmD;R$L#@4T^!-A4e~`#2j+X7zPO_TVCJkvE z98<5!MiGlZfh}>o3){+yZx6~K&z@_r3mdd(s7 zEW>XOBs@Svc;e}5o<3VLj#E-ht*@Ow!5JJR@Em{-=+y7wP=h#tU-_qb%|8a&{O8iTO!-}=@;&vuobHD60}Vz~z#Fy9Ck zKADOJ#z<_`X2^T3jhmfXVcY=Gjy8oQocL2*74s!S(cfY! zd0-CrsYeP)S&cwTt4fTHbY({Eag_8S7ZmK^QN8!=n8Tus)-r2)q?&o4eRpl$cE1Rg z@o879C{c_-m2jS2I{)gMRoW2ELB)XG`P8_Lt@+Bn7H~4Lea`2I0`@`rQw9riRKj_M z(Xmi`F5%&OxH5r@o6Bzh`_RhW>^=srbs8_O-sOWFx+W9ngA@bCZ&qL&YtYVl9zO8_ z8IZ4xToS|VL9pHHP=YSc8L~8ns*F zDI;(wIwanp3*Lh|me;+k-*RCK#YI?3M#(mB&!i9n;=wh6x)HqF2>EUptSf~o zb6#o}SzU39IRDofzY%_RWaKFmM-V5UhZ!E-Oj5~py@^GKT zt|-%Qi;{eXnLdy-++4<&Wi+Amlwcq{YW78!M3(1TWAUk^kkkc;uhLa6m93Kh!Uizn zN;Sf^Yue+xctPl;GrZ>8#$<8CO^T*8yzx5>f>G1$^14EwiTTOg@#Oxq91rAb2tse< zI@lF&-tbdx0YzQVvFsMDo);%1I4I@@d4~ret|=BS|o0$Y<3gN z%kSk^!MD2-Y-f;?5OQz8oP6`QqbU6~7F|*#^Fo1e=KjjthrkJ5{}cs3-u=ds1)+xI zy5-7Hzf(eap5boqis;uFVkI7I`fey{pY7Ehr|{6!QUG<}?; z;wUuVjp(FT*5!#C)942SRlC*~EaH~71_LrxKyo-|xM^HpNc0lC2jA790&sL?L^qgi zYgcV|aIhEkJ%jHkk8rrwE~h0O#F?WDa`+1yTu4u>xTIxYf5Bayc$_x$89kYD2P|WH zoBVFR*!5s_i;FJT2OELMz!e%$+~D%!qYy}$s3ZrUW1nBKm85k|&sanJGwS4;MOS~z zi=MT^C;O;Lrv}#r)ED<1yj3Hd8h2{0;x(-rL6$+Ba)~uo@3jM1epOVX1s1OKh=3DC z#RLp*RDYuZHObeayRD?VBFp>GInA)kRY2IKF2h^iPnp?V+DviJA;ClSdSAn1G?O=9 z>L^i}mt{QCZQmNx@AbgA##nb)OXKf~ef3K6?7{gf?EAyrgy;w*?(lf}t|656GOH%e zQ@_Y_$3qi#N9W7?w&^s!-MN${lsG^BVh;&_{W;^5XrafR4MXVW)Us8i86x}BBeH)A zrFqS-7-9^eB!NUK8Yz!fFS0nk1xGA}boR6*sMPLHdM1NZy@)-4+=%- z#fkD9h5s@EogEeuSY&KICNaGFL!C~2OyPODPRz(#8}p1{?_CnW$AA&Y!V$lmC~yHX z{Y0#wUm~F!28~+zP}#gu2rQE5>xbMKo)`cDPv;1lyNZzz1shs9C9i=y)H!v0`iJt+ zqk|TBDz0M;%|8&0!Vpib`c&cW`u3^FHn}QSG}6kB2_CHvX1~e;2?KTA^PL-3th#!V zd3#om&GZPowpdfcvvFV>ttF2>vnlt8cca< zFol$(iN32>C(n|9$@p1dJao(IQ6V>x@m!df8Dkt(l;4whmEd{yOzH4Zd(!mLG*iQ% zF7SRyGlg;jW07CsXb3F0l>2TUo4dA?{B?*xU=Q`#OEYD{i9H!>kZw1aCsf}r1yVbz zD%Gzfi>NAXs;2&l4BBHb(K)gM)ny}ktJEddF8^?kKR)R>{|HvEMq~eh3Ewz5gB3B- zlb@@x#vwr;V}25dAcO#>m+x(*c7>DKLV~S*UZ_N;Nmmr4Ra1y;dU058f5BYFf2}Cq zT~VRkv39|$<{KE4^2HFQJr9bK_T?^*%^17Y@id>A*V8;Vfw5L<1^A-6RZ)G_*NZ7V zY2n@>S$|-`4I1AflcwoPfqPVOd2=B>Fv+jldl|&PVZ_RIW!%`aJ%~6EZSuKIv{`xg zX^`T*%FqYgiBB$qn z!%7PO0&YJnU$nSp=;zKAl0b?(u@7--R_CDV9H97$YeLsH}~vi@NJF8dQTu2)PU9QF!4m!~+7 zYqyMeYCN$Rym5a5-y=pHchSjDx1mmQN*W6Y@`#XSSk`7=k zhO7k9AhW3yYQqnx{PSlSSD6LcN1sgJEy%ZVgnby|tHRC{V_RhR8P5>0!ABWJ+xJ`@ z{J3;wjnL9FsUS2%N>8p`!c!bR@8ut(MfxQo25db$yZU~Yqq*cYWn>A&_iWaXwkX%G zk4dO+C(X~jCqGGvXLV@}pJLK}SkIhLPM9pSJOpQa(_`pyURK;iAcDvI_`K6U*nvt- z4Kw1>qTX=*`?*L^o_s8q)_Uyd?#6Y2$Q4wf)QR^c{lr%nmBG?;XeEMYvExR%g_Fcq zoL0tALJP08%;zvQvw4h)Di>}cvYLt<$? z|F6DK6b8J0G$hRUdQvo0_J~sJ<7;fI&S8!|qeilY-=#-?|x+ zqGhrZefyM;zzWRu+JRf)H2XrYv=B*MHO~rb3$%j4({5-@;UiVVqe;d`kU}@#mUEB` zpJrFJsMSv_DexA2+{57TC%V_T#fU>3OFJv0XrzZUgQS-d^CoDw*Dd4y6dg}<1;M1_ zvJy2C)hRB;&)DQI0|#;*A}m%!Pk@U_2z5)abLa=PB!mg-q~5p^5GM{NrZ-nI+au42x6VHn1nkpcN-LN(b3|m{0ayI z+sQyG2;6esDRTl1FtbvJIf^{6_>ya8_kyv2 z{t+enh(#vrnAt%ZC8*b;eOb)8<7UJo+PulNgn5{cGxTzC3T``eNb&|_eR8gd4umuB~x+Mkqh1X36iqMhwB+<$VZuCXY-7wP0PDexlJ{xMfEM#t<>K6zsdn z-XE~>RtMUg-|oxP9i!-|F>$nwb{+=m`fnKi;`GOt5tD-l~sd z9EiJWukTv=%XkA_V8{4&92}% zA4H(O4IvaGYo{f;b(Rlz4SfKv;;&*o5BJUyl`}`@NSoSH>|61oE2~&X7g^UBe5E&L z2~gxK{-j6X5q^R}Hc-3<2|USX@*ODe)%V=^WQZ2)i-G_CP?B-a(wO;+g?EL1ZWds} z&N#A_p*l5HXt0gdzUV7Mx=7xS8b9s*sr1A}HJz5P^`>Pmd;_{XkHzy68@y)2>Fcri zydPNPSH4dj+V0!%=(l@dVE?Z3TiCDj({N}94V#+pUpLdT<(HmPqnO|FhL!J?!;dX4 zGIr_fb;US5|4{YrPoGPO*JkONQTOZMn0%im+Ui3w9Vqxqiuj1R`0lu1Swgp>k7VFTjCW4}$Yh$r(g@c@<^A`aa4wdZ%s`q;gh-M{ft9_uv!T6C1g6bu4UuYe2~$x&TSN0y0S z6_$t3kWAV@iRokKRwh)}ZWO>Rt-&G8_qgGWn9OTWdiMQ(!sorrq=1f5gc5PJu*Jo+ zb(Pt3Dq16*Ch1KEVXv?Caqprb_KAmvqnnplntkA$7d~9U^O9}(&!dqkZh^-F2T?yj z9+N{{>j<0PRmkLbJQRD0U59}nex@|zG8TMK@=SSH#nAC7ZShI8Kb(>1*)IevfTtO?w15yek6a*woxnL~=qssaNt6 zZ5Y?=#y{ZR#cZrxf9=OgCuDOnpNkdv>8@Cf!x4m5%Tj79lpgo#D5AtxW2{XU-HgQG&siai53Yi=RJ- zy+A;6u#!3! zCZ~>5>0$j=%Y}D6Pd;|e$QaVszU*h^$&g1TMTPT!76fdYjtbnT-?g|x<;g%<_@aHL zT?M&Sk3AnBZS-vj0*v-y`9JdaV)B#%1r-Eg-PU^iw_~SJj9>30y{wY`u5{UV?KVDC zYSWawD0zFkMOux*xI*c{1)TfsS8lq>GUJ%-ZnU=Q@02jR@(NZX4GHHVBqzBZ$#Yn4 zw;bGwCGR~l3}%rDbb}89g7<*``~-2r@~86mHmsq#MF0~LE+K)}{~uXj84%Z!w4IQU z;O_43?t=w)cY?dShXfzoo#5^kB#_|lZV5KH+e{eXo4fn%ySsOP&5t>!rMl~>>Z&?V z=e<8)G!Wl+#!@z+2G?o=L@jLFul4OrhRh@|NSZNFGq+Wph;Dw&ds%9>`r#~DYSv;( z$h&ebX`E%r(xo~K-cR&+RFeLy{mtlC(CtbZ(2@@IVJ`7in}9HrQwlk4DIOR_G~2vm zOy;x8_{Y~;8Py4yKt0VBdPW%Qzl#Gx%<4G>$S*V3_h6Rc&kbnBPN~m=Td2C7!|ql|RC_PPzzQuYT)AKd8WyaZN~!!i zU25T2mE8i*FGUb?ZS|p8bmTupq3|{rpw-7^12>MOOS_9W%Zr^J(G6uRSgBD4!FD_r z{k(7{dNqB2axLUMckgbvWFU5=h1pAqwf>WdA*}r`6 z<3av9pXoOhP+3Jo+w*%F3(Qz5O{`{>o|;>HJ)BEiDLQ1Q-}o}!QJf7Zm=xa>{O9}s zXl4d6G~zmV+K_T>pE{<5csOfTP2=kC1Y^p&%{2=5vN8fvS!2PLhCNi zd9Apl5hxO$&F_+btBM(J&IIuI_hQ>{!Ck%uf6i%~xKpR-ZcU!gN*5#}KIPOm|G{my z%7Wp6sU_Hfv4ADJ_%UfIo*Ofe4P@N#u}37G$Zj^tL)|=M^xyKzXL?KF|5s~W9dY6% z4Oh!b`Y~9teCx}mR_q!|tK<4{T}^VWBLy~_@`^j8kuZnpxxD#2aJvt_Q+T}2xCIer}lknvyS;jkOFwDtxP)jUhhsCRm zb_@WpPb)J$zkGRPb}(g38Jzr>HS2uh01)~=@ri56e1603f6bPiQ-3O0EJ^5SKbCy8 zS_99Cf_x)nd`9H45Bg&PC##XY<+;H4M8XCf<>%qSk&zwlTUp7(x&`ljZJn78%Sm6V zeIWR!w!D`8I4$ut$3f^Zwg3G}&|v*lfXXi_My)ob;k2!?$%ba3yM4+9xrq-P9kA~? zvmFr4Ro#Zw5t@feY6A@XGsA8`afjsD9*;T6`0zjE&E(IAnh?qQ3(`ON7XP0@ijss@ z*v5(sjqu6#3En?ER7a4oLe8IkXUNd*oN>w5?4NSfTKTRQh8-NJL4hCTMtw*c7yAsy zk5hU(^92dk>^{rby8EzuG@!C2|68;Ka7nT2S>y#K2m@zW&3uIN(~?CAp73WLf)WV_ zupN2NIV~V4nZDAV%SWRml^4S2r8!U+rmx9_{%@@KOQn4kFh-)B%#U8B;FjkujW~Yi z1GT2}(VmJR#Wv8nhE1jJPjYf@h5SoT|Ahh_p|8pYlg6{xc0~>8-Q;GW><;Uhk zo)1#vPT6kU-LL-E=~uD0$*hVRiixQ`Rk8%EC48tgrOI{}u>`8QQ*%?_@kcnPq=vrN=bq(162NwOiJ8^C}1#OS6tjzCzp?zcW z)Q1^?*~ppJajmHCXc5$Fgtc9YbyYjU4}6M|){;h+`YPqCwMYp2H`) z1puW7-+YgdvUZPrz0KKas}r_ zLVz$`wi`B*df^_=*tuA4I(zJih z3B&}4Xo_3=E!%S3?mpjA()ZxhJNhFNPp_)egkh6M%QM6Cfj#;+qrVHXj27RSn4e3X zsCuO2EvexdY+n+2Y_dcU1JmIYXqrwoW#U*bi~hSWlF(I2aj-*Yw0x0~ju6^~wnyTh z5x>7&JxBdSRV$_7>Px%P^=hnu%zrEHKh~~+7$Yi|uUcTv#iGldu0)6R!`kH*i;qta zY-iD!KDP*ICcsW@LDHf~F^o2~ zx!8}Kf@?quV9TL|AD7ABQ7YX!VlKe@?<(Q^Rtd-gH9Tt1l^Wb-rzB$PJyp~zfSIkE zYkDW!9|K9+#>ev0T)PmOX!5@cOfW)xEAWGl6(;1Gj%{pV(Wpf1CrlBUG~9JD2Y2(S zwsZ#s^tyQEM9%-`&6rnXd%10bNgAV$x4>sVg?rBW6s2Vyycnn$qcU;Q?q(S`y_{Vq zJk8|Im%quIqa6dIuc|v&bfVZ&&_5?;jef6v2t8y;_IfcDcH;S?wbxDGHbI8EXYoJ# zd;J!5p~?~Ox}vNV&2HNDW=Z-;n15EjxJIoN*^(C!H&5DXZ?N5nr~C<`P>YU zqa4$fRqd(gsa)f%23vp9RR4<$py;}!`x3--ID_Yae(=BYiWYBX5}fwfZi%am?y$2k z2`^tgIK<-?G{g%+Y+?Em`FH&BcpXZ@uKFusBHRr0xm7N&u5D)}ExaqLs9W8pE+9kE zvW#WAysBByB>nFr<$jx8BbL0w^)9Djt@2rDQuUY__#n2cSm2ig)H1Tzqc6)L{FuM~ zcQb(ccI&6 z;0!ci>mbGwF8_wvVvgp!ul-+e{x#$vzDTt_el(0F#Ipkbwdj9%BmdC6OD(F8mfe+8 zpI0L*zG|OraCvFotGtF8%=LZ};k0k17J+x%xQo92L^JX{2|j9sh$%Sa8u?ga(Ebdvq2w-@SFl z8~Myl66r8g?=4Zc=cSTTm|fT{FR}loYah=k)EW%r2ke(v=}*7YHNJOXiu3^#7j~Ekx@eWR5EUp)coBu;|b_jt*M$B$oOvfier1==d2a_6tEb8>+%eguh92tQZ zBp=f6%R7R9S5tjiifaP_$Gt{nT&Bu!h2>%A6Kar|0{p?^qVEI7;Pfx4nFgWo1YU6c z-_uD$V3)V_>Lj!hdD*{G8fE-a7vejsu1?KH67-V<>~z)ml}WqApS{XZT3 zzkt62F;uQM{2OS*sc#y{%aFpBbY~uuKLy(qDMg&qqxncl;XlWkyW)951O7hZj)+%E zM&BF)N_}aK3%nI!uh>o)I!(d~MaD@#1o7jp*)p%kKRvbb+O2){m-(*+3N1$q)!zEyqrhvf^JNxgaWr8I*>>@2h$0AH+bY^j`!i)7XkYebJZUew;Q9Cjm~>R%1J2%? zzqZIXxRG_!;;SXka{p1bNM%Ts@TyR9mVP8Vw}SoMfYBN#lcKEAU9@+Xez$KpVVc|& z5cqU^63G@3FmXH0cfekIZMSLn~RL(huZlE?D^@j>`Z27hWLUSCSBc**?o zvM`2m(@p2VKqH`0Z?LiNVPRw+>{K9G;EBOy>$EY6;0j8(qi?i>(U=vbt_ZPmTCgbC9Wz0u^u{oT}XJ%S6X3pF|kjh!w4kmTz-PC`6({apLWgSgnRaAf=v)aF2L zQOQ4Uw#W2zbhoN!lOC1=R-@l_(o;7_I3HV97E*B5c8tE*&;n@3MZav3N{3MWw-G-i zdtIub-W=(U$-0-{XZq*yvG2!^Pxkd)F0KcuPa1SOgs}2z6d?WuE7E~&;p<@%0uqGv zHYY^IhfVq#!9Nu3gQE|1DT4`{77V3gX1ynKp?8x@YX=nFb=Z*?{+ zg3FnqqCBvpej5Z;N1SY^Sf?#!n43on&p2uCMwvj{%%MF~lLa1Tf!fV$N#dz^8n}Os zu{Q7NR~wX%%;`XCB&SWoRSr0Y+0FNM%Ww256}ESWhtUzh(8_Gmy#D;78+r)_La}bI z@}+lh$i$Yvlckay27i8Z#N701Nb6U(tV-ymF*vD%Kgw;OyilMr-Kwlq!qVI8fN0G> zEGtfYh);&DL1dGU0<~QpPF3`$#DoJ{EMm+J)!CsM?)mrhU%$A+VKG4qoN^Pd$dR(r} z@M(ty#40r ztY+MgB+FAa{xxZt{F^#ao_?WMCw`E73%XjlpV{)1@+OQx&g?ZOoA1U_+g|wU*&;$6 z-;nVjcjUuh{1CedC89kDDS#X>Uoa>Z68H>;UO~8x-yZ7$+_`6zWU+MAN-P(&ckt(N zyR3RNAl!TGNkZ*4jv!1Sui^s)BaFwdB>6^k`9wJTHiwDsnQs zoIgN|s%~L(l29mI@3jFLedC78YW^6QL7NVrV1+M^F1T5w)*^d8U8-XICPV$`dE@Hl zYWJr65u%}TbTfRAFkNt6bNIXgN@#pLn$<_xPsN|&np~wQil?@oRCOcMes`JCBGEcZ zh|DGbQ|eibEyMoRPn=$j4Np~vh0Ni&&702*>bjqac{>Jp`I!rfeL{o+v06HtQpRLV zHOveYV&##t?z$Sba;gOr`G`rPQ)8VW^XWAFjCMPAODR_4_Tzp2SANeXGxjPcU|x$k z#i-q7WP?dN(q3yNSUef(FHtR6ySp4K&rTUpKst)7`$_GZzT8gh_^9-A{v2MF)I0YB zh$2d~mA$3v;Bv0srQE9+Ej+dy1ns~H3ihZadPi8CurQQzvmj8dE%B#b{!l|uT#^U0 zxiL?94S8tfXIXip_S=-J(A{@bg&mw9mR5;6doahnF<2FzdcgepCinzDH?K-4wy zD;;x#pofs4bwZY0r;w)+*?njI%$_>EwtE6x7T?WqybU>R9JbQDhnVdSS)cin#?5kF zm$X4_<|!Iq6;!Bv-jba4exy{I#p0>s@=wjboF zd{OT&YJ{pX6vpmk|5$%crL90pII92&??b;AsE&|P=9?yOHpU_20aE(OzMJE_b_{^Wv>I0?14LojV&1Q=R&E|(# zx36T$k)^Ibltom4DM7^ij>+tw(5B{sL8EFhr&?A=P|fp0IcLJ$(Qija1I~&`Eg63n znE_NSqSoqLZ0dZD7DcBu297}XUGp@(K(F%}zUcxpGd*oHb2o3E!0I`jaD6dh|K})a zTpew*h#XQu;;GS2<45ITJZw4SR!{8Y6r9pN@i^ zy6_mP1|b?*`RfzFU5YgA6r5>m#R2z@lp;)WG9na1J{L^)=aRs38?J_W$i0(ce_>=T z3J{M4?PRGIog7jgqS!ozXzn~m;yxl_RuRI4qCmY8Mj9140AMKK31>k=qb*~}?3jpY zmfuv~UK%};ljpWW@Yc1SPac}!L|4YZ$318~yW;obksg*CGFFM%o8#%Osk9 z&pNiek-D(qFXcA#F6H;)8cc3$>uHDc9(Y0}z9LmuaSYcJg)-Gt^;LNYKmYZQ=*^{~ zPcF8=1i4_xyNXI5NmheU?k87f0Ud^F={>Wgl=5~2JIEjzbAD;GMgngByM>)-8PQo! zD_2*!u~SXv#KP@DTU4yFy%qd;z@N(ouLG@bFVF8CD%;yK0Q;JyL0#UC$k^CdC$ei4 z5+oW^H+&j#3Rr;-p2Sy=bqzqP(F(uNElRu1;R5McNM6|ND<~RDd$6(}xnHZGJTpxU}Oa z`sez*LfkAZ5yke|wm+KB$qrm@PR3#qjp=Qx{!utfM4ZVYLsoYP1=^PbQ${@qogTyY z9BkI?ULCN=g#RUYj=o6V!t>PxnqFF5d)0N2cGe7>Ax)~gISRYRloS31NLs}?SlWP; z$*=Gx*g;{=5sll$OdDQDonLnP7pg+-yg+bm;}PA^)ycckB|DPdr4>)?U9i^|jT4@D zQwPUv3-NJ1RS_9zDt398sN%elqzmPe@FCjbC}zFbzOKA^*s( z2q16jm`Xzo)6CmZl3=e9bz;to1E3=A?-Cywx>`Pp_Z8vOw|aeb9&S#Sak=RboDHe+kjrbiBbuJPQ3W|*Vc5S9d%%8?I4g7SiksKjk7;|YolP2jjII@=uZ@w zJJb>Fh~RSk7C?7~qv$b|Z@|{!LXJw?B8vWqF zOJd(|TDV8y`D&6n02|QlD1ghJGkw%t&|X^X>jyo?Q6AOZ+P7J#CBCr=2P5r?6?|N* zR+BK&?}5qOS9$pa4N310ybsFnKG-YZUqLVo4|_S@ z3cW1ude^t8dK-DRUYv-GAK|*zROt#zO4$zEPcdVsJS@gmFKg-Lu^ZrZFc@GEW`A^l zI)lK!9@tK)4;%|k?D8=zOl?bO)%6B5JIKnhC4;r+?S2GP*@J| zQxkqfz2^x*nIS^Jt}71eXBMTvd}wa)cPuc)0Zs4f+psG~X!-@UP$$xetxiG;tAv2+3Zg(nEQ1qjiUK=CMdUZC^a zm#f12Ce9jTuoTtSk`vPQ++)dOh$zPM$2%=BxlL?%ad<@n{z%7Y^6*68n6ZuBnCzTB zLGUy>(&Q8e6S=&d8JHBHX6qfH4v|wp?!N$sN;Y%Hj>&2m5CjjS<7DTVX^ADz^<4@F zf1No+DZWlh#f$iL^(Nffh9E7WB-RX13W0&iZm0l5sjvKuVIvnOKfT_nzoJM%$1kk} zNRp@qy{^vBWCpeM2@Gj*hVkIm#q(+5C_2Hb$Pf+uq%E=?H_sd#024x&WG0G6)yY^hl&oO&#e+I4sf#!i0 zWu$g1BefgS|8ba=qn=3FwYfM%F9@=dqV-;I@!!RwAJch;V?&NlssY zAz-^+jJQh?`{rv~^(WnXNWjJMuf>0jitD zePUj+%BqpIbvm0uYPaY@8iTX+iyEC`8nY$VPs+cxoJn*VZ#&z*qF;)fG;FVo=yqf_ zNw4Z`VOcnxTrF}hcKq~mxO>t+a2W5JV>@Q*x+;et{MhHtso$X+F5}_`H{u{wIeLB zcCwMPBpgS6sU@t+($2EF3Ak>oW zUzLr29IjXVYCz)wHFz~|)v&uvtF}0LeVO$OOoJbY+8h-_HC965gZ7iY9Mi3cYIT~v ziOa)l?>M1wQ3s#XhOGeigJ@;1iF2CzwbA$Ew6-x!dFlggO8YM6idI}EhEpSwXF6FN zOl>#i=q?nwv8tN-ajH4XC_1h+Qq#Kjd00^N&wlA7X_t()VvomiX7YhhPJRYF>O<5@ z;;$ZOGw_@g_4W(|busE-zU4-0iqr2W+0#(u`#pHgcSd6G>BBz-TH*))V{;yv7q!Uk z{PT>1mr@M;SOEBx8viv++E6bEkW_`O$v8CJJVN+eHPP&&< z)>6BL_@iU&e;c)BcfnBz!knzMOh@M>rDAPL-Dds1Vr?x;5GvcchB6zpZ*`gaFp z`)P+`CHHW-BE7=H^7fN_4P^w+v&ZqdtT$4NbU0gNrat$bTJn(&CX6NtJ(~P09Io4)ww!Y@NR&7Vk|Mib!+VC=Z`pQ;beL+v~Qe&`8AM9GpmIXOl4Sw-?+|~nl>|OTC||vnsM8E^(rG`kdtQ+a`4&8{hVX{+ zl;g6CRebKsok6(W=qc>fW9pn`E*vu}3(X%EM&;L{@q#jmA5o56oxHAzDTz0QXApfW z7%cipJ89vn`c-KpMgMd>C$nYtS!r6jJkGK|P9U;lC$@}820 z+|Idxe)XEWRjVQw&<*2q9_I{>E?Jr2wRCkV?LuY!aMB&Urp>6 z4mc!-+Fde2GeuI5HY{2?qs-X7hPv~6`6sR`Nkhm(#6qsQi}-t|$qN9}0}yImZ#%wJ z?C+Fsg9yU&8TNYaa|&H|rLHG&1YN>o!xLK0IPnVy{3ScTa+b1&mgH3Zl8$>rVV4WP z6jYKN&sOlRHxEW(gVvY^Kt9tmO8(I%wLy`6-GbdUnX6b%TALtCv2w-yHS7#cmjDs8ixU;i+bC6o8`LT1e0)AMM`s`S6XfIfp!+C0$l2 zM)Wh{Cxi*F6r#GTe6Y$8Co86Sh~`oQXMnZZSQL{5kmZ5~i&C1$kVZ9>NRbbt-8W2RY1Ar8!VdC#MWeJfrkX^3 z6_F6>l7F6^qCO=Q=o15)+N^TqN5l2fIIfE7#*@US!yT56mj@@t*zf(lt*^8_N|{*3 z34`(hJ8I$D+L^n$(qFf(ETApqw1v3J;m75G3HH}S*26}o@>>%h=13~D7@#9GHv~7h zO1gCWw9Q@nc7BO*)S1rR&1L%vi9+!PM#o8?ESIQj-g?~p;H*S+*%^_mkTLwomj9K; ztFSS>C3n8h2Ex#kDKLpeQU9AEFd>I}HgCy#Ok?2TA-I~F#3ieo9Q)#)jU3K3a3a#p2k7=~swlFzq>tz@2Rl*+A8B5Rq zt5rqrRFS_PQ&^pw|EpEIh~)CW9%D50U(0P%UCQC?IM8jP0KzG5%!pI7Br=br8pRKN zAh$rj%U%j%ZSR<9>7@Hd2W7GH;;@c5rcL9-|B4eVcHAt_vxFZ^HyrL^KU3o0N@Q6) zaSeONj&>wkZifgiqfG}IZK+O=h-m+r#|I%3+Mv1MC_{AX$kFPB+*ktd7rXil*|YFX zLZH3^+h67LZtM{EH0kpP$a(iu#{eWm)>m%a5?uT0c*Olwm0v%#SWd1!mus#`f4vXC zC2^=7tG5G19$YzobhmG+rv!d9eIZ%M1SYP-AC3-bNks|z_{#GiDX?+g;sV@F=+Z=s zwX=T8C|FrCa{LDAln`|BFmzDzY+`&eH`yG z`1U8(h|y&u9Usd4-uA)D*s5J!^x)qWiYd@0oN>RVU_0qSRUTF#nES=)d$rseo$dKU zK^^n8BzXFQ45jzsY}>V{n_!9k7u&jzIyFge3+B?PFfLkpZ`$>+qJ`~mic5i|+54w? zMiZdj%`Rkpcm17EoI4am6>Rj_c(KJobb7HNLc;dF9N?J3?wc@vmr16Wm$y>dW9*}l zD-w%N9KHu&TL!b6a^U5A9a^q@t7Z?}i`l+R=p{h$0j%P0c`fOIZ>znJlz&w=hL9cZmD-yLaze;z-<#CTf!8i?YbC zgBIIb@DS&NKicx%$$Ld7yRP4#T~ijyQ;wfiI(EEby9~H!Rg)K_WBw5NHjBIFUcae5 zb6hEcRB%#o*CwzDK=S?N{_=QVxIcqDn;b2ZAD1kF?nR%HHE_IC@f=s><-M$OICi>6 zISIWEa8KSi)D31hMVWmC5M%2Czkw$npo^jh@oLU?y7Q$(~b%e-A}dT$rK4TO4;3d|`} zQNsLQdnyu2nmlb+9(^f;{_BMi_M#S7lMv*OohaVW&bL(`nyjIEDKn{3V=t$3NtY1A z{XQkGZv`6^8S>`9&pBUeQ8+c9Mwy5DD8UrN)x$7E)WOZ&hd$BE(L$Gf)I zl;wLJ$dV`FNYR6#=~y1HtK#erCqf)&F1?iRd^TB~u3L6{pLQ^c3Yj z`pw#B+n>*;u~!WlsfGB9=@_BP<0H}_Z@aZY{gm~8#AB%8&N%GxAC!Kun>c=pCd&t~ zsCEs#*;@lqlvyH!rqOqs$}rx$wa-rNOc>nslRvB7s11$M7m(a<1-KZ0Z653*9Xi`F z*i8sjAIj|gj`h`;^lT^Q&clfe_oj_IAq!!-*NV$A1)1-_qR1}5RnruFCH1s@l5iJU zKNdOKpGb>ydTfDVauAxzJ(THz^WRq%dFTXVbKw3GRi zcAjj|uq!F&Vvq^29DH4`%DbOC%F?~}%}rfKdM+y&b5fd(`q&i)l@b)k zbegv(W2tXt>L}B=N(SU(23)d6iakZ?sH1}f7KoZ^=rwcYPXbl!97?nOjK4$Ju5D=SAuL<`40_q?Xv@#YS- zTro}2YdsPL8st&J(Hb~ZGN6SWdv1nPk-mYO)bbJhhPc|O*|NXcbZpe#Dd2rKXJTJ=*Q}fNxYJftM1Uw!>agJ#`^aO+Hn^G7M|z7X|$GYZm-P~pkRO{inSxzWOBS|pn-8Cf{9=vMI87`<{ z?LO>K=2?+AzcHl(nI&G=UVo zuVU+A)Ks{r5rv^9@GdxUhy97xU|KUFe^oWMdaOW|^2)nvct@W~iSmQ@++hlDCz?ui zV7%Azg!XUy7Q?hjErvKkj2paE5eBPrqLgb(*H`J4Z8!Yn~~wudj#xJ*O@ZT2ot}0L?UEWDkOIVzyw5_i5_diiUk%H*WZMH%}8b~ z(vOT$dpt{D9!on(j(#RY(c^11%#QpHNqUTp=oNgLy_MkGUi$X3;CB5|vNmx-+%s+T|dg!f%*+X1`+?Z@1?8{&x7`q^l z!wg1ayuB50fjUg;(+y6*I0M^)VXH2ywbB?Q`|Yw1x&J`yarLtEmtP*njbqjMTB|1L z$@UwJ(2s`|FwqY+npo=9Im2(7mZpPwlhjx6*DUB(T+`%1x9Vxbb9JV@Cas4p%yqRN ziEpf$EW>5}$|3;{J0kkjBa3&PaOf$ zSy82rHaakv*^Ai0k#QnRGGS0c+}E7pf=W-;qCK@Q-Gf%8axY>U{9DXy^fTtHC2wEd zt$Opeg{;QzG{+oN8Iax+NK<&=1NFWDn@Nxoks!fn;E^u;m%BV~*$ms1TnO`+bk{NS(_$V0YLFOAUD3HHwm4agm^A$)}Hj zr#wd9ql!OcYTHY}g1O@T8Ur89`>vT+ozLof`AK@*AH)TAnfte|k);G4D-c`N%bh;h zyxym-L?|}Hlhs?*H-g9s!D4Y|uXkmoV{hh$T>WC(h1*32sc+KQ{hhHjo}VuU@I{q1 zpLsj<#Z22iZ1An&KVQWpU7oN~c0={_1Ke+v_FoIR>5tBq_hOa5H5}#hzvW|Njr6Qx z+F|KsLAWx@UQA|>^t=QKFjmHCt=X@_XcZQ}B&( z))nH`=g5mG3G*J0a>qJ)d+Vr#G{C z11tCpVZm_=P7>z4xFnin`~h=WD=RHfHHxFYqSdkFM=IGatWTf8dAp}Vn7jr}#ic5U zj%%_$V?eV0n-9S-wwz*c`X{VO)5MDY=>02bd_i0|np7EvlV6!|@Qmwi|c!5XFD`jGXpo@J^7YhhO!3x#iB|>7-xXk_ONtU@TD; z9aEsm9NxgfKwihiDs8hP4ae<$MFL3>cNUNQK05wAq(AQR3vonWxM_M$JZ)Tq!h)c~ z9w)dNJs)XWP&)q`DHkEyhM`y!b}BZ810|UIm#_{<8fBK!R`qcH$gO^iuW>41F)vjZIX$tx!$N!h}w-kS#tQ1E{sk zaQ(PI_f;@MlXeeQFEtTIGdU$`*jjK+^d?+u?Rrv(mxAugC1VO4;DpVPHa;P) za_`z{vg2W2wWETkq(pqd%u0)p<+1^EIG zHQAbT9rGvB%BGswOf9eW5~$)GC(he zxpm*x9$vgef=eV>F&P&-B20CC;b8h`;JeRkYWLG$1jZNCA1MYdbls762)E4XAZ{xP z+NK9U_L@@ktu-3W&m=?HGK#b&6AqEFoti(?F9EUiY%#D>X>{$UC$qT zUWcD32aEN7%HV}^F+vdXsx0Gm>v#{D*nML{IQi3|YrL5~J!W*?xTw_Dut?_fT zM>*)PZcD8_GQ^j_CvkACVr2hheGZcBD6nKDcYRF@qvb3*;ic`;Gu9I-B)P!%^K^UJ zcyzOLEakd_aoXe-DpX=*a*HFzNC*4iG_1ROQUp{SanmoIc@dU9}W(RYQfcNBy*0#!CIy!DS|v8C1<}IhsCiHdG{giY(11g;^2%)Fdcl(?f*tX;5^r33h~D3 zWO$sdi^a%i(+g(gE+@^apEI*>Y;){nO_%O7nIEkL2Wigr&B>p0_?s;>5mci@k5h6U z`!`_;ZQun7LIz-leCyBVbAv|94mob^I&wm~Y?piCV2hQ~EnZ+aSo9!1BCMpDfY;NH`e|G8dKoDxQ_>jTh$5a)-tKymDI$YS{ zm&q58VR2W!>ZJ70{CGMc=EH}NLztxifDPl7p-@{lY$P&^t|9} zy2z(5x{yu_W1(|dZPIE}4D=LcmyRClrZgPAy&JdCBZ=FV7o!G= zoR^|?BqU8KcNrq_hGgtJ_>3i>F{!NWW$@^PWR&73C;4QYeh}m0s^P~ioz;^w^y&lJ zxA=AXY5*o%5OuU=QQo|PFY`)nmR${xY!;);Z8Kk{KcFxIEX zg+IWv`$~{Nxa28D63vaLlf7PlMrVAf?)}5eitQfw9pkr8_b%yn7`6%m|CVoY@+06+ zF<_3B=%VZ58IBQl_lH>N(fyi3{U;+BsD#wBmz(2xKz!KI_oOs~_tSK%j>dAfZt0xf zthvmYANQfYAs6}lt#}cz6Ggq(g|xn3x;R~FEMhm)SrGCQ-fTAlb({U=n!Vqn;BmvX zY$NkDwH)DIBlv`I;)AZ^@IK)~6wA>gOXBy$54%rL>Os1@y`IaQ_wV|8jYV;K7=;^* zd`Dfumq$%1SnZMG9p5*mhoF2>@XElWMLZuK7i z4I48P@jHFJv;@z_-9?)(Ed0LN9!dGgI_d3#%8wXAAzelU_^i3Vusi6;UEqcjh}^n_ zMe%c1OhSI6t} zBXc`GnJ8wy*NP?h!XcthI9QBe>n7!AgM3ZU7PWM4#x&(YH%`nXr2Ip1@a%GApJ5#t z?7|?GvR}$yYnJMqEKeNmVh>acGt-B!a_}7O%FeG&#H8qvHh_9%7RkIht_! zi5|?UofIce?yMirNK@zwuelDv3k%=Qj5i5iG1m-oc8s+|ol*MkkELlQi2Z3`aFb2T ze8b)+dyS90LxSJ_2583p#7lhRwB9zyDerdX-%O$_l2FFt=Gr2~kEY}l3srOBOT?xW zBBNMY$#>pt6J^n7F-5~c_3qM8(ztfo>QtWmcw4MY>QJTfV__^eEkE4zpqXn;eyfgV zntav`x)ztb_?bj#x=UQoyDQL>wnF?tQ%an_m6E<~VUIC~hj4dC<3_jhK$e8n)! zTyN+aFUjmjMPr-GGaTu1-$z<(c)8BdJyER7>{SC01wPxpN%+X!^>FV|up+VA=A_iJ zUUNWgo-vj`lKD+;v58sHbj8pF}9mDdLw$8=MUYnV%W>ZFYR?dZ)6?I){)~)jls(G@2Q?ON3m-iAJPT_ zKU5U0r5^C?hx^w&I)3K(Y1M<}c=S=qs(e*$ESup-JG`#Ld@7YZjhpTu+Z3uFtde@{ zV4KW>O@+!3P(y?H`U_%f`ZCX?WJa^Mo3p~+XZ<#+bXjjkz}zcee#u3PUMf?-LuqxtX;K)e>-xI!lHPb*YM?W5SAvl87cL73ijqCGm9 z`pDHg%~&EKxMC5c!&Sb+?Ek3d^`--J&P7^@a=nj^zv&iz&X!qzBR?FH9_%&|ASZ~% z6&DS(|6Ph$BfsL6!@d8SHz{5aebibvP1efAePLs@=w-k5N$?0|Bts~DkWRkYOe{c| z9FO=ahnx_BywjfHl$z+eRk6H}^SyHT?%w13M>d>3wy*#rxq#juZlM^T>Vbjmy^ah} z_St-LRyub4kzqyOSxtdJJ%+c>sO7PdsvDH#WSv$N-Nz-NuD7qerbmfU@8PJj zz?1aRmF-Og7DO@cI;+ax90^!RGRZtxsT$v=wA1KG!fZ?xfiWlSv@_ zq9SQFT{6)xv_6krHiH|*R<03s4QoCc`jjhc&K6_aGlBC*X5YYUpxdgsHIXg&m;w}m zS5@xR{d-`T-?e+e_PYud5f_6&nGi2XW`f^oqk^c>?*_yykw98wetVgc2&-Xg8Lt>o zq~)d^UXZY<&v;5(r41k6{B8M#_?1(l_*=dxJXF-K@Ya+@&0LdAWbs+oTrWEjV6V0r z#21?1%c~P_$|p9qJ2YkE0Ue2}=-H={ZOr-2oQ+|A|9*NXpfXthQ?yMTCO75LIRDwi zqTkKVazJZPa$<{6WUc;a()eOxP4p?k&1^z2PvE&n0dr|oISiyf`ob#B=Zv(YJ)h*M zGW#%|VW&fY5p%xusDyjEX-R@^ITb*_6NKd5rpB>Byp{80 z(FykFjvB>l#0w5O*~UGQhndRD`1zrJ!6JC_ROZt#3>d@8t5fi0qjJ!;lWr7gN1)~5 za->qkKCpaYi3Bbf`ZHx)H9i}u06ZxUZd}PBryUUK1pP@t5VYj7GP5f9f7Ja|P+VWs z2MXfu?(Xic!QI_mgS%UBcW)qQAi>?;J!o)foZ#M&>HqiLJNMqIsd<{0c{x?5cXwBv zwfEWbleJD#V*itBAHU#Yu74bk(=bfg*v&TbiMLO`?~`{F22X)|yUD;#zCyY+z~{cF5a&TOHHVVy>#eW^YRl;{ZL-Oi++QA{cDDwXv)T+qf^MV z+Mt37p7nUSYUATy>}4rn3($Kr{VCumpA*4V=ORTPv;T*6_6;ZJwk#p*`%q$>fkm5% zUOjHs(wgMVG8JqaY4xM;B>lPj@G$imUNpq$sA8)k>x~NX`JmNiwH)wEo99V#gY~3k z=bo;&rH;O*jQh;U``|k2tOHAw?SjL1$+j=e0}f<)JOzBNxCN^wcT{UV98@WQ!_lJg zxhB}W^(!ka)8Aey&0LeVAEg;OJV8PRO9wv;wf=v|oEw7qc$Qi9JDg>%s?PA;(0?}h zF0Xe|nH_fr$BpA;)bUY^OQ=3Ql6EfUQptpfYe~>(`jtNW?u#k4&==^tY74Mo6hBta zt$Gk;$yTN0Uz>{`S&-4{{by^3n*kq%Za0JQjE#Nof4hf`cHdTc#28t8T}&CxrV_tq zXdg(FZcsQ2hJbupaI9nhOC+XuvT6Kt1(B5&>Dd>QVD`Xpi0{Rev3~L!Xo0Nx3Zq=a8wJ#=z3L>ilLDfF2Xt z=R#4~qDE~@A7I35mX-p0%ahQPb~zPLI(p}nm35xBe6EjZ)5}PcVnflMox&LZg4nuS zc|?S(wf;2%$ev9KoVd%eY9My@4vQ9IX`2l)+YlYP+u_pkwJ525IdSZ~i zb!VxG_tB4=rJO#VpbMj2&6g0+A^8PzPGJMy85Lc~Dvq+o-_8>k+OB;4kpFlM+WBuX zN%D-lY|1@7Bj4!QbFzyjZbsQ{HZMXy)2F1iX4`cjO0Zj(eA2FdXa<3(eq<5uda@OL{3B!TA@BLVsJ0T>J zmH0?`Vu7ufSqv0KtT6AbRS0L`^~@LK!2trQ9d=9qle(OIw4}XNvc}JDFXqGSV>$wT z+(BmK)E6Pdu;4Z0$7KZOHR!gS%3bXu><0F*Z#`Ov`nC0lAl1|xRu~;Fn*LpVWf{AF zj_;tvPf0f0ehIfP=El8cD=tkV!+X1pbvAihbsD9nvrD~mjjaUrKryDg>hCwb>HwLu z>pd8jvs>=OpM28Q(6Q3d!cQgX$^^K-jGe?Nd{bi1XrgSV;&L7_amq#vWFVUSgMaNiaz^}kGLeqi)EOo zk|CqUbr+qr)uv)Fwf>n2S!<-I$hA2LfLZCArQzogsmw7%f!g>#a{(MwfX=4Gw}Ya? zPxxsneZ9awJ(Wu+n0+`p}J<2x7FDm$n?0za+i)7;UXLQ@L_UzrvmsRr7J(2)ypq<^vZ( z#*Q58$(pWr%KdB6SC*m;j4l>vd2indaOzNP0cY=jjbiw6RO`LO=2Oxs(WV#`D>dc7 zEZn|64Y<|Z{IN=qEcoH`nt#$( z3BmG>P{=X_Yfc4hh|VAjy9%1jR|GRqlG~4@;Qd~DmFU{gP@bMsf(;{LmClYJjhCMU z!j5H2DPy!d{pBuO*^Ef%%f2fC{AaKed5_jr5128?soa3@$_gY@T0T&%RZtdub?L?wBcRjGA z42*wnG#Z$Qb%xHx&Ojy?#xob0U$tCaFitP-HFn$JLK$a+C7f*8msH-F_s`1U;e*OJ z`s&HqK?CXY;O*F$HgjGP^m%7->ulSf4V=eEeh%)`}yYM9~#*^{_nW{A06mw_w#3No`})O9{Imp@{3Je zm6N+#!-#fRg*CT*#u?0BeHpDjXfy$iNsJ*KaN_Xo$!`vREAt_ErjI}LDsKJJo0@y6 z*j<}~4_u`K|9xDCnwe2;)AD0tJk$q&T!eqjs$pv7rG$d8e^jdz`y2Q=kRP)OY`TFE z$7`dDJyafog8g5;TNKJ(%XrOr)K-%}t6$0R1MhNvlA@D`7TYh`T!C)b;U1YIdSdw~ z4R3pCu4+@TQl$SKg@=4#RmYZ*lUds3aOWU%=A1goSoJ9_O|-JNH?$?fP!!#u}&ThP^{um_e;U7C8m`h@Fhl+T-5U`l{tw zitP!QK9xaXcR;mIsjkUo`hDq9P!x44%u#5PIIeXPqNRFff-w*TWYHA|@^|aIijnNt zUGojW8?4zLu!z%fITNWjj9^ei1DU6{jAJ)^qdP~c4?Lk)nFNSQrzK~kE{A8PAlg{e<%4&6@8Z~1G_VoI!O-axD7TPw5xzO zJW^MceyS!I#6buQFxfx;R~gaCACjww;X1)eM?((?0*lKswiQip-&Vn+{c5rY+NpVy@Z*A^vdAct>ieSJ-;JXD zzMo3-dap-TZW{Ip3oBjgq4uupAYioI+i=F8x1qQu;%QBTzHu7nrz89#9?Z|f)$p5y znD^l%bhK#Clq}YfC=L{Pus~WbyFs3;QGAo#Vvz>!>b+!y6?-|G0$s;-cc3ld7FxE> z-5md)W~n0Cfsx>U3|!t}Oe?iVm(O7V4`*@O97*8hZ4ji0i&2--v06Fh$!P0b8y zA%p1_B1ZOKcI8p=bhn0y3tlb|>90i13P>W2M6?eq@kdcda(1_`AUwO)fo}-S$_nB6 zEZ5-0iP~Qe0iP9;#Vqj}nA1dYoM26jrfkje%IjiRk&KX1p(5<#;yNG<`#GK=gfW-? zsQH!h>(7y(GR{T~pE^n8-{UeEcjMo+FAT!y5@k~+NaaY`*p3=$TE1j%Z+!*kv%tRO znFV`f5@ycVzL|8AbowYZ)& z9DAPGcWXyLPk&t3czU)bcVomJC_lcOri46vzlrAWF%{!`Fb8wN?2G*^P`4!1?A;;9 z%YfCEpo+YzTp7K>{`DF3*sF7X7XqaEk2Vo5e6G*% ztrhZLU_+{r6GyMp#OE<32}^e)OHPq-2t(8-=c}-zxPQ2|dcuzXlF>i( zIq@BBGqbnY?st##YoSG#qYfFxxF>4dy4#6>mIfp>&k(U4nV)sSxybGPpV~Ni=t~Dc zIRl?BBh$A-=$Um&I={1p?&7WfXsx0m;I^2YMNPoQRT)eFcI4qR`+|~0xoGx7QnB6b zC(}nb{NTTtjV_0iWkI%dSFHgv|7ZB5&TPgN&!~RQlQvePEN}&gs#RL=hLR~qK5ji` zP5?>4)>3LaX4~|5$k-8^rJrB%UmLNC?s92~##uWDHZDmQU07>OJ1K-HyB~jjHHp9SY$(oEd~HD!&wp?Yx-STr%a@Fw(Mi9FGxRDkX+P&Y2=+| zaZsd3Va9w^Y@3X!X@+&rn|cx58M~}cs^#q7#xDDXPKY6&L>};rtp{XLd6hQ=`|MsN zOE(B*fz*twDE)`<|Cbrh=n&s~{SCmj>dTu|$~6Oy$hxD7OKZ7> zS39V4Ov>aC{4o>7T?!=lL$NbbA4d~`HCb6+L>WifP_@4>J(OcF2ST)AwML&KtLslJcr~! zp~_4_gkzl?Z}RXwsi56{`)89x6y^Lo%Z4zEVTXjfxd^rk9aEM+?T$5-XmXpy7j9Q; z{mV!E35bo-_W~{5+lbo9W%7d!=AsWl?~)1zLPH?60pfSd1nRm=mR~_MMPPZ!@!X5# zJ0zzJw#V50JVF&BUC(iX5ieH^ zLvgE1Xd`Fn;vG=_`vu&6(Rhu((Y9;<2N=aK!k-g7aaJJD!ec;N|CI)sEiWu=3|5hI zAU_HUC3A~cJ>5TwZnL`X2n2o_K_Qlf^OCP%zr+rbtA9ZSflg0a2pd`qL~EEEMZ_nXQk3hDNE_&SZ_%~xqRxUD@^45LWH4l zveTH#6^o|*W~ARoNYlQ8vq#{Gsd0qM($fbmjAKE{5>h0(IQ$c*nONWqwr$+wp4rC;DQoWJH{X!fedKD9}E znyk<*NtEUMZEiQFurcBmOtBVWt)x$hr-&Sh9nz$H&#iJ^Z!mBcmkBIJd#KZ2q8f%; z{H-Ofa<*gOI9&f;$XRicA|Md5lHgi(t9|{lII4%IZAx%rEBH@GolZbHpHbWIOim{8 zgPy!e9=5U$R7k&DE+YCzthl1F%7BmcO2&JLavsC$m}T?n>QB4EMq_XGJQKC}6ui;B z{5R=rG(i?JR$-z55`LHo<7^#jBanI<3${fu%gS|VxN~ISt6PWcjOyp><*nQKeufA4 zFq)Ls0Nb=wIQ-j`Pty?5Q0eL0=FXMr)5$v#+!qZwX8!HBjJu`FnyLZ2 zD5}_HfFoqC?5n+~-3PTmb`5*Hq6FU|S75ucemO6Gzp;exiwHt8~ zr!(ckpTOMOk~&GN;(F{+6bq5kOyBf31L}%dbQ=~a$pM^Kn?`?01l|jU+Q}`h`hiws=;lx{rZlpXB4Q{&W;~AHbFUsVu zGOM>CwchF0vaukYnY#PGz5?@tdZ8L2;P|NcDx;rZi- zn%?8b`$LUZixY1DsBvV#fYn>dD#7wHe0wgrMs7b19#%zyZUHcbE^p-SCkQJ;i|5Db z-F>#aA*X`0jww?+L<3F7NQETQ%h*Dm>x|wJO2gv;hQDunpX& zUWR!N8l(2H?7r2c=aeAVGb%;C{pm=J920l&peRX)+U(QVxZR1W@%5?A$9Zb~? zG(y2`VWCV}ktUPzgiH`7DubO8^GuW=V%o=Wm@K)uu2zp1q7#tXgt82Uj-??6K*%81 zZ@26_?F#g5OeO~*+`tUc4AHy&onL;*dcx|YoT&@mGMg3ku>~!0;~|FKQ~9{-kCX(( zf21Na_;#VjK^6{UPwPVr$nG$(t0Uj`qPGy06o;*v_|X5JslH`-WA`=;3H&!mkn5ly zc1xWxd`7*8*>7@w;!Wts{0(3#5n~T7-?+*qzgFj#yQIKx6=$Ok_!YNntzMsiMre7gdgGq!61ynuh{7qg$b9VR*0FQQ=Lp2&1 z(j}dOn=#Y`X}dO{OceG#%z}FZhj_~Fl0GdxpKpp{$61i*3a2#!=1>s!7+lR~fMd7h z&zpaRqbqy;(|$v2c&D?$tV5V41h|Ul*abvXC&UlGj|5o#E7Bqs2Iv{;3toI;uKQ$O zh5N7}j#QnICCW3rfXcY?1KLY6R5fjT^8U_h+MMklg9;zc%6dIT17Xik=$=p?Irn>J z$|4T~(c2G@=+d$UrW2bUllQvW>O^*>%Pf>|C^F?_KiQaou5Vl?H5a@W~$tHhkX3FoT_^w4}%Hp zJw;>ke1j^Cp9u4Ad2}wFd40Bjb6tN@GoY18m`ngn;d;=`led%p{1J8li+e252(&%R z{NqV2;9_UV8g~%FNWw^M+_G}#2+#%EtEz}y5#FRqElpXueJ$8j3^c{((N=R<*~Ny{ zu!PsWQ7OH~B#qq}GsH0R8(X^J8Os<{D zgc_5J-q#!WI3Wo4Q1OwweBo=u>;7Vj0En(p-8UtE_jPG2!T0LZ4x&{tG7IoUhlFi-)g z^i;{;5us7iN+i)P{CX$W=F1leC}^*F-M+Ho4a7L5=oTC~Wt}EbpzqP6$x6m%YN6Xf zpU~?vzeIfDacPN*`NTOtzr-2q=@Geg#?@9T^GBM2daKM{av59yYb+8b(Skv(ikJLH zxchyhdw|Q)>F}{^E&~C7o}qeSU~A4u1NkNCo!OGD4TX6j8i2K^v?!_+nsJ5k1_d~R zIJqj?9!eh7hd7t{25b2@BD@Y?eB&*Pte0o7oI}BKCR9DjARUWg zMp}%P16CT4f2iqB$#TI@{_3Ui5FV@Hew>>zU4HY`D$3hGkvunjfaY>+h4O8x=z776sb`+Cg*~_L-E%8}ecUV`K!Y~!0+*akT6G%Y zAE&uWO0OI_Sh1VSh2=l<#H5W*iek}qx7smc)s@A~&=LGvuox0_29m(+-7MYIpe;?$ z+WRSFn;olZaz^y}%T%=Vlq@i*O7YrJp&KH3*LXNiN-25gu5|T+m_EOiLT(NP@sM&w z^8I4teQ`gUM`+MzPwFNpo^3`F0#AR4tagnpuly%35|KhCNcDHN`z zuuX0(Dyg$jIwdxlDmJ4~cRok2sJJ%pJ@T(?0^9+_Np#NdtK! zIr`BKzy38c4S{60CAaB6o2|jt_vPkWJmBF~vF1LFXqc%OGfu49@H*fyyTc60pRnzZ z(|W~K(!^smWFh*Rt3xqBMgnQehTw@f^W6W@<~(aSRNyvn))Sc2DKmR}wMHZ@>Z-n^9l2zB!ac=1(BO^iWp8sv`gRlOLXmJ(?n zd6&6E4i66b>+6F+EFXi~*~kBXHj=Uou?#z}LaFW%L>5bM_x_S!cbrlwF2GYApMilO z@7lJI>nHwIa2uJa5(6YFpW_wAkny=Y5}GA946}h*Lv6-O-Dfs-yhCB&e%+I2$-Tc> zhB~|nkPQgzzn$R1A0YW^ZbsCx6+PPpXI}fz;x%OvG^%(~vzooiQ+Q#|pnb9+psA6Z zHxBOB3Valf4J7f36yD>FZRaixt>%2ZW))REJ@`NQ%|5wmY&mKytV!Dt6@>gelhEU5 z^odn;&XGw!TlekB{y0+4!yfP5ni{Ja%Bowx8Y>XwdxjlTt(rndJ>Pv<(12S(T6kZl z3ku^mk2od~hWIj^G#Kq+-@k}HfdZJsa9tRDI(I8U@Al3NhdZ$sskgqvHvQG{j9J`K zLc;5J1c6C|%L3kokcum)2;f%J4Gcu=WtEO!v?xIUmc&)G63WNJ_5TF8A%I9Hyrs8mI>2TEpFxR5{Ds z^62L?V8+4~IBs@0$ASk!NlAM|Z&@NNHT3-<>T&UKqOuAs;|B03+#q+Bwh(GD_%IYm zZ8&^1oMvP|S65fiz$_d1uI}!?=k~5vGNFowA(E6T#llLAdC9jTPE(DW-b)6MiP*(o zB}jjSr}PL2VU)EG1oJqF`_oWd5eF<19%kCBh=i2SjV3g2><}lP#Z&_Z1ulm;RW-!s zB#6mT_((}U -CuO^^N<_}}Ye}fCK{f0cgp}AQrAOBJT&mGJ94<+i@qVet?!3s`+ zxt|bWcGwblf;ongt{cLQksmWUHBX(`F?qJcWj{JCP&CFWlP7`6uaw%nM?T5DJ{-<$ z0fI>6WOcJ?vJ%3NHPvc}2JAuvLCY>=YVhQDg=whAt&*tPM_0|vA}KQNKz|k>vxnMK z7x51mXBIIA_E)b!UQT%$L>75!TFbk+g)7=e?Wiy zURn~t%}2rv>-3C+@q;QH+T~~S4GAem;3gtaX+b9tc_TVpDD@q!B+YlkUP$+K^IzVC zI@v#S9itRuZ`nM>#_P{!W6u7TH#WCrKVbtJhgs$Tdt9e@{2C??*H{}YVro#FS0P7i zFwO*FiYhZ@Sk+aNtrka^mYFQU*Jh9`RHHRc?ab8t6gpNcudHR(#;w(O3aNO`1;~A8 z5PpmNrFh^)jw5!9%xz{*@IZGE7$yk9RR4=Zgtg!aiADoGUIHz6ZUJ*zu3;|GTRLc8 zKz5MH37wg$-|csr>KHQcLK3q#VH$kmpISQVZjBiE}OS&-5h7aF}rg*celN zlMqk)WZ>jd8YCP)&z7GF6bI@(j{QO@FM8u7k}>hrn6fgwCoakt6g-GK;)B zTfa@nZ%FgGf3_5Y4xVK8yTqXJ9j-$7ssEzb4AKqp0jT%httbESq&m=nfG zCTrM6ER#P?K$(B88o4U89f(&b@JL~OwplXx4U|@(J+}s%q!Ct!;8N)_PSY$SuD_ZTiWMb|BEK5Y}GaF3A+;E#{ z0{n?mlxYq@u2M<4fvE$TgeI8s`-xP^@SvYE_7dYBhwOwR1U&>IB{?3G==`GwQ%TAG zk;j4eJr1Inv&|qTL~Ntiyg>DG?Ja8IkeMy5xb@fX=^Twk;y#}*CsDpp>+j>B9dXOq zFEc6#-iuL6j8R!_G&8M?2@@ffRISCFC+bR`o{DyM-RmJI!?DF?uzN?P+pNd*DWlG{kyk31 zC78uuAr|U?{mOPcKR=fqzTDHDlUjQ?be)_WpBSnxP>{eS>Odmw(ad4Q&`++Cau|Nj zoG5~kk1>XE7Jl>#j4)J(Bl2(FbC_Rnm+kmBaqAfBkxzx*Y z<`nuEzt`5>MYST{!~WS~<3!>$oO#n3(U?;!w;qH6VU-(74(z4j1!3r+50dtwljB9H-(eK$aN`;>&e+ zfb%#wE_waCW~X;FQMl@|LSc z(aC9YaV3%_^+zhSlTC845A@X5)~2k0ssaN7tMha|Gk7FNQD$a=VNU%DidOvm71K%` z{EW_#rhCwfX+_G%)&2cu6J~zZ>_Ao?Op;7WAsG(9v0(@`BC|Lt z=Hw`NLb`=UTz}2j>d|l%4o#%Fd|AXyGp;*~>6DI2CP}-3B$Hg?9i=!zCnwlCIKbCH z$S->_`NE4(CU~;?qK_Njz>n^@Yr%o31;GcoHdsj_fG+6{kjnHbmk>{y)!|y7CN15tTfEecc9`~(bm=ey~l&sd!g_gJ?{!9a;x8k6`IZ11J~Y?U*%^ngG?ci* z&Yg%xN+~Y{B=V21^d2_LI$0+*uY|(Mll&Tc1S)r^f=s><`2k_tb%vwjv<4`>qJl#o z-+$utQ^_7lI|0~3f5JVk-__FC;u*cpD!WYC0ISk-4V;1e+?|X$SpY>#+s3TjnWIYM z<}C6xY8}7x+dc9OVrcO&3CILp$dEzuzOGi|?&S4Uq0}fu2<;(|gj6)b#+!R=#fVpk zrkjbDR>q#lUT>%2i-C>-?JO&hH`PtE43B`r#-Izw&pVxHeof8i*~`C;kQ?aW2!%kjd8w>sKLP|CXQ;p_ys?Y?q#Jwj$v0byf`;)RX1!+Q2=R--(zyq zc36jxa3r(Tg(Cj(7hXIooXnoGsj_p!QqCH}BdWc}6C$&cR>@Y+!;Dp`gfV*-BV=UJ z++F3BGFATE9i7AWvQDc{DryT8i9{ax4ga%bMV%NeA^snm$1?|P+lG6)&*PLxTAGML zR4xq=g-otbPH4829ZOtttr?KlBb) zKFO>hr44LkoJoCXL}DKphKz8P{j_hspz>zBxg~_aMPfqYz%>WYIroP`y$MoW>g(S% zGr`W0+*|5qfGJ@b6&ovj+SU9RzXGj7nqp}rzm*HiC)xDrHW36S62aVk{3rnd0fsI1 zz89*brd6_fdMwnt8$2@6eQB8PN$(iS6lyZ_30`Q$^c@YxLmXfa98Q|DvPd{CVn%bDRjLnvgAm6k=ga!G-OoWo-w3TPf7a0dh2#4CvQWtLO*_Dz$(xG@@2zz? z%wRp&&FK~qTdwZ*LK6w|3SQh#kM#XeZ1wjR%P~vOx8z1{Ut^d9>qadt3yVGh_j%x~ z$JqF+?xSIvxbFVz{l|E4aB%u#B)Qm^uRRdFtgPF<`yjntQMkTu3J7pe==`B89*M@J zed*~apYeW5d{nGQZf-awg{Y~BU zKl3J4I_p5%SlkIeLAG@Kz@|Gbg&CH%yMnx}%;_mLTFz!ay1V;3dqXQw+52q*6lxBg zu9?Y?qZ?!hftd_2e0drp4l3m-^<&o!yq)@O4SUQi*wc@)DrzZ3$&e;him1!fj4wG> zeF;V4mtCvmFf}=uho8c7dJzP9{~$*m%)`Tn7tmK#pEFl1CMqbzsi?G(O+t^fe*jIH z*|>q&vYDkbHfzChQ*^3@xph1cBZ%^VPA=qZs(|o@+9e`XqD*+h3I(TX9cLZW-=e%l zPPHbv%QcgVhMg-(V(W#aIb-&PJ*iU)w{wSDoIQE_Tmx~Fn`FmzKS3|;cX=ypD-Jt) zPquh*W?7EWD|lOcqW18LSr1-_{~H-`{7Pjx^Io42o-q6PzxhL7U{W`Hlzt zo4qp-W)RIyO-yKsMON#o`p;|CuJ?g(;(Y z11;vb&TH2abkkO?2ptmLUkAg*^OweL$3WtS(1 z-eM4>XJhB2vQnUf2UEq#OpR7e66#5n2i}P$r_-!OwW}#+)OwE%Bk-8zfIgGOQ`N)U zHZ(v~R8(^kTwR?(O;_Jq=mY8vUP#KCUJ1_R%Qgo!iWFwu5}aKeok+xPP40Jjp_}`0 zMznEA-U(%iqB2VUM#vYm&<>*CXS$5=nvIkEa z)4CH2_EK}A+N|ZU(1}5r`Pf1tmv*-WZvlGNqnNBEXDUS@B7oO#c?n#HH;Gyh)nDDT zB|ZWSIkAK`z(iQ%X5!C~2K{6W<#AA%M^D#L-|8$qFjKSw8)VExS3768a=2Zeg6=qE3Uw*vr;tfLYT9_O-Lk;ks zhf$|1?`&|3i|4HS7&p{F9{0ejDKH-Xmhmd~B@{I?0}KT|cv;(2FmnLzzX1dQ;9zyY zlpT;05$*7Ea4FI!lUIC(hcGeMVsQNBwPR#w$ED?HIa^{pS&1N-c}S=)V?%lYCvW%y zN*yqKPmVd@n_JuL@N~#B*v`E$%ETIbg1coSr(w;W(h49Nf!l!^17C354s^~ck)jYQ|tzDGD}9UGE>wW z)Xe?Xw0H{r4R_o>8HwC2t)V!XCoDLqUgB<(QkKr%W^Rx%aGj^a4@2IT0g5IAzRN!; zsAzGn&V1~Yz5SP#!(YuQ4ahE$+b1y=; zNg=yAnM)$sN>TDRzdEvIEQ2M74=H=n{NfTn)*PjuG)DGWQl2=+fnUs^l3CW_-`cZVt37v(#4|c5uqMka}T z>?ubYlxQDmis2E3^J|{o?WG#nMVLY4H%ijFZGnx^R2{0g^~F0hMcQ=+Gr2f)s}(lW zMfVm7$*GN}`eSH{0e(`Y-(%31DVX!%-{qbJP-hc@1<#As%-NVicCyY^(iU%5yFBf* z=86QR&T7uI4Ndo7Vxv&eyH%{o)24G+ZOqa(EIrX6YX;J@TVfzf0KRtVg;m7?in`a9 z!<5)OPaO7KiBY`N0;`fgZh4qQyxl3IS@=k~^taWCa}w!DKhdcD%v3bt?>L%B<=KK! zJg34^jt;oANb*?aG>}DF8tpB!9B}`;8iY`X>vP5H*aGc<@Q5Qs)QwY@U>0XQ89b## zTw@M!$?|=Ah}kMbC0eP17hOk;<0OP-@HRj$&rf*n&t_am6srVrI=27e zPycUJg=Oahy626Pdp40Tx4|tg3`b!vETKaz8Mdj!@wH^jjW)upQhyL>ZF9hNz+vz z?nA!0CMNmZxH%Q*4ZJaTG;-o*1)mclhG^%O<8LVqbakhWGJgG$7T@%uNpp^(r!tn0 zvIA^m!)F#q5;m4_Z@^U`X{k}vY9+@&gC<|JzhvHyuXj){tLL5|Ko1QWx7Xl#lZv>RBN1eq(Eat<{ATOFGZf@Fv*^%BJ!#bQqIZa$@pU|f zOvMKJt)?!~nW?_IJTdXWiI~OpWSN+=P>(A7O*bfi^5};AO4Qb6BA!sVPkn-`XuR|M|Mv6z*KB};R-lC; z&~=f_@z()82{Dok)8qj@goph!F){HO*!Sk-V0>Hne3_wAONcz+>+?j##Ku&z1pLK( zjmTHP$jEA?m2LT>UYy_iVPgM5<0)f(SG;S@!D*)jSRL9IF8E84={^6Fkc{lZMotf3 z+`osEh>W!CJl}B2yi{35Woj8ick7o?cmCK*))&}FpR+AvKfC>S^8#%hozZ&cF^A{B zRR)K3(DM2F?A(5CBT|aV)`bqY#-sMp;%Pq%lox~V-^oa)EM2HuHmOG#R@S`~cStA+ z#i}MYNtu!tRO?EDJ_$r7nn8`_C#E{g;?(q6(8=I=<$nA>V3Il;<{@N1yGu*+2xDI1j!$!qbBc?@x2X>^9_IeI-rGt&wj}x* zFd3LKiP2Ef*iTGvq`WpA&}J4C@E>BwkF9TRGRc57ArM94p<)MA9VJtsp!1)U4Zm`~ zg_ES^g@lkmr+T6UG8=zo!I6QxqY(Wrq(Hg|)ZCSyQxbzo377gY3kP_?29>&i!$C zU)x6FhuVQE>0Y?Jl}N2K$7aWkr|+VD`zaLQG*tN|0skacN6y!mzW04DeDZUU^8D-~ zu&J~Z(r9X!SZSeVu0uRz>>I>!*&Vt~#WlF(`&Y5a?yx3>X>CJABJs33>)o{DBnaJ& zgDjb^Nt|tF-L^^2eC29V?ost2an|8GrfEufB!C0I4_vb~hf0d|O!__eUz<1a?PY+0(&ljY)b z@CaPP=3D^kSi`=8#(@AEwTNpnfmAg9xbMRmDMC3Dkj9864TPAiFdX9w<#&Oxl{-71 zX1>o4OTzFw#g`Y?n8R;{TgH%ko9{oSkQQc=-93v36V@xdE>zMI!#` zz5!F#?>;Gs{&g0KHDf|plJhF;Tl<%l<+g-dDAh$z8s{Mb!1YzXfQz$KtsLnR4v1Z; z0fi%YPq`%-;imsVE~E=cZ||?bq}TWy;N_l%=v$FE{xt}3jC_KZ{8JUHU&5kbRM@>( z$XoG-$c^~sk1@<>CbN?hqMz4r7w=Z~n$a$f&e|_)@ncGTzo@zUu95xHMf}*YT`yG! z)2nIsWMs&hMYLb&*PA?dAEo;mLJgszQ=MJhm*hPpQn zoqGor$npwYK=l5@k z`s=`~Xj%H|_V(*Ps((DI7?a((kYQ(lh==!pe_LA6Qc~B2VxB^eMeR>&(Uo6X%6xFm zxDSirj#>0+yNdzl`WP(%k3?r(V*4WkDNzZAhKAK$UHN1|H{|xaeZf$_7k|MKs?IHM zUlyykj_v8ku)F9lRcfpRdTpY?;>)hv!|LJFuYF}jv&giZCYRwrv^9FAWvFJb)(z2s z$uMurGK+|a|6&Wo|Gzcml!1DJbpb3Is`ELh%y1Cyd(H6VLF(Y%1o!2(xZTC7lf9o` zZkx--%+pcAC^vA@$5=)IC^9|v;OjkB5$V1~&6ax_zp;M#eUird=bmG;?CoIv$ z9hzz60I<-{ZhT(ZHhKTQq540VL0%0EThhCnmk91gsL9R7&Z6Xi9YXw?EfXa>fXPCM zz|+>Q;=c0BN(8$_V|bGc%94}b&(h)@mw@}$HM(vC$*tw(@sc12vG(vBJ8pwslej!n z2J>XqiHxxq`!Tg>*)bus-aw8dy>3;Ta+@Wh9e+d@?AwtD@Rr# z(73X-9(OD(!}rFR)<1-H zGf}*sE^!L5NsPaLKEh`OULY{F|L^MvFay&QSPjqUdU*Lw|Bcy}XEko(nGZwZ>(1z3>B1I34RB){r@m44xC6!ShHxZ3@vd%kx= z@c4`_b}*yct1je!!fGmSj#lFtC^fq+D%yX3e)fIMG}ybTxjX5H?(Zh*=Zd*#DaHe# z#t8iIW0pC(j%G;+bHFqNz6cNqd;lNs($Po+DA1wTuN+*AZXX4L42BWu1~`tacoGp# zCL6;~OhcBrL;v;gB2w#@gahyj;VOZF+?n&cNcMQ?yMO3a*sb63-#}mA16H=zP5<41 ze7+qhnXufiC7bPj+m{j!!FK_v=JaL#8x{XFAA+j54EIGtV1pK`6*nXI;uPlxQZ(3O%k8YQx=I5)J8*ysM7?!!^)H+6C8RwTH)yF;M3yL)k$;Cj>VyYD;qn>%Ob$)C(TdiFVc?X}kK5K(1$m6~MI zA~~KEH;?is%KXkLXzqTA0nonEkId!J2|S(LOHTDh}EDK*+g(;vx(MUhF@5J+>ZuA{ca7t z!G77lHI{XBz($mjzYWuOBniOuO;us~_??pr-VdFD|7^^`Fl! zTkg*`kh$PECT3v7q9?){>~LF78!C3;N!EE0%8zVV?if-BvK}57c<9|Zp|xIY7R;$N z!Nap}Y{-yEO)g*Mfq5lMN_v*IMy0foRoo>qo?42jD}LFDm`?&fUauo95%IM~6jjy_ z3|<4g_?1bWgKj#4dqu2Kx=dUZXA_OmWdd2tAO-qC5$!)xR?z9-@V&SJQU79S|oSh@K{L@ao*Cgc({qf8c7vJaJ#;U zcWxilpp6cgj?a>xN<2Q=rZ@h=Atv{vnN3O7ftR^^KpOgVWJ_0K%^6@KEo!r#&OtsXpDT88yQ;ZKBqmN=RHHC z1M&=bO}$|_ZB6cDL@cG8Z$-j~;J%5d{a?~h_FfTwJFJVZrj=Y3SW&>Gl|Dr6kNUZq zCe{~##dZpBcynW(4G2JyF#pG-NO}f8OBY_Qb|E-q7|(@)Nxh1>t-;7U=N_;v)%yQE zDjmqWbeZ7rViNyvP9v5muJz3bDVid=g$N|1a3He^n!yMa<>BEWx2<`8^q##rduJXj z$=77dx`Q6xDI|3%J-kn9Y1S|w$J5PSyGe2omXk<9Inq%pGC8*-c}TrP@B$BHw{N({ z)ucy1;+5gkAmTeYIVrL{wRCrM8qJi>mqtZFo~~*+{T2F6LcC417R*idaUH{T8RcTBP5E;}*iHn1~1BJr65%;B~(;P5g} z1@}@V|4`F;MZXp&*zo5?U&Kqiz}HBqB#H*I;q;Ll)o?WH^~3e&w|5&He1|Ny$XNZ_ zV$8E{dtyI4c70P?3k-dogV^3b4G{8PXpQoTg$uj*cUvf>hEsQ{LDit{$2b%~2?(s? zdJItsry+Z~Hl6H9JUc(zxVwZDzV>Ng&IPUK5-W?Pq9Z0&p z;c4b%X5i4#9ag{xRI3WR65uOOqVk&e#YyS1vT!&#;VRs~3m#4pW^&na!{df!l$AwT zSX%BTn5M1;`7k(jT!4Cysq|dwFKFAYFh`4)1m5{xk=&b5C=GF;IVt1oHZ)w?O9PGs zuK|nxII#&a384pxmTdj zoH2oXMLlSC>_?*wP3WigSiJFfMAELu^tvvoi8r930~^SzKqV;Oy0YO!L@h;}nr( zF=d-S->Cnf6#~P@&8VE@<*bkD#?20_fy^VDyev6m*JqocEjn|*sMg!5vhkINrIjz% z)@)N#Q#S$JMMiEA6i)cfRuW66#%bVwZRLY|tXbbTS1~dEZsC!#Y_^3`V@1}you-vX zwVmtTsscPbHg91ep~fW2Af`AB-SYw_&o{memuu9urwu7BWs{^^+d=%cbIDqRP24rS ze50#v2CtGiHoMZinz;z6S|x{8+Jz}X)tURns!Yl^;my2NfX8e0Ii%jTE> z&(`-Dxn37@h$FT)jq(Rjv1vI_J)ey`sMmiVuGM_+X;5sSv_}xwap()D>gnH(s2TL= zh$nbV7ixj*z%GZ9XxI*Oq}*HY$_{Ug&PQr(H*I_L4{r9;K<-xC5iQ>I4o{r|!6ZsS zu5w+OQVB5rsLR-88cSW5N6FlftaPV`fA;KYx08AKwY71>siKs}e)OvNR@h2K>%2FJE^zdtkMs@i<)s+2ZR_o^ zHAro~);62S$=a|_exUfi_#TOFK%G9)lj)ZtOc~cvMEdh8)Dw}@y6C=nn;Iy3hrrT9 z8TMtQ3{hMKuClI{G~hRG4{Z&pe~~uD(se!Lt1MS3X00b8vWl&+7;?Wf){{X7{|wW# zIi9%*@Q0zHL;dX%G2wbJS!b=zrdF?nx)WF+)6&JP}5-cI~&*)F(Xt=&?+eb(vZTYp(B z7Ure4lX;ZcREN*cbwED^FwJ^icz^Eyl4P0sBA5kB^F00HBn*A^wfEO&fJXWss=SwL zMj)_zONGo>GGjZAQNR`fSJjg7~F6TiP4s5_IDQy9xHzRSm6nT zcWw}e?HNX|$lap7M#g}VnMY_oy`K@h*{|Tf0W&6#Q*R*q-vV~NM&KRa59~Ky%bWMT zgP6GET8oI>S~mr@yD8w8=zM7p1=j`6uqjkBih@8vr7O-n1pc3I`-~zK#2(Hk%!k;l znGgt^N6Lkkh>_~UhQCdb6po@;yOK|*Q%Y}MYT$5E^z=u zv*Ocg#|zb@E-yogwU?ONIffkB!|OGF8h%!2)fH8R#&PH?z!Ac5$%vp9jFMGvIF-9@r)KCHJO|`sM4} zXXN11Zi!}x5o?CEY-%tMHJF`Qk?DX8O$0*+!xJN!4~8P}B0c%4_l*@JFBiXzaA}iT zBH>~;3jh(OZfh+4R;v1G2xK;*+*Dv|@G6e-DMhpsm-`v3;Fb~LXYk9L#!(8#^B1V% z;62Qspg0qb{#K~xcY$h~^}tDzDd5eYurH2A{uEUhF23~dXMeo3p9pzu zr@(n#brvi|Rx)_@ukKG|`?xxcV!$P;bAJ`F>vg#`==|jWeZ!hVuK_llhW|HZ|M70& zm#gP)ZN!lO>M6R`0nOt{(0NpZ=a2sWer(H|t#P(Y8A228HWSxHdttot9$1QSZAF7z ztbriP;jG-xA=BF9dfyqA+R>|4gwjq}-DMshf2~;G5iz*uLi~%T1gYnqAT24St!+1k zOsGffsM}6iQiW z(`-+MOe%n1`H?MQBQY=)O-=La&l-?@EX>m3X=45Mm;}64lFa(ji}q4RVcG9(P5m6B zP}cH?QqBRJnd(==g4qEdUkYI3_8aEQ$-g+1^^Ob;H-J-qbS~TqM60qC5PzSkyqpnU{kZL|5D*X`()RIM8e=uNjLF||%#SY22YU|nTe1j?oj4velFH3L zsp8q%4-@om8HREyxJB>irG0UG^ki}GYzYfDcr0t~%u;wKKlz6VJ$aAl81uzY?Gub2 zzF2(CrebSLlOka6x2`uP@Zp7;_@z}Td12MPLo{}zm7%5@6xMiua)(0jf)8jho3ily zF(qkZMF2{--x48BjSIVc=(%Cos7l&>(8e(G2CKa6>#Cv#w@z=k@OHu9D96Ocb|s$b z-HyW^H9cd|z(}B8?<6V0ayp396qA{Qo!#U_e`udbc=zG>F4Ew=hFErb@{kt4t`~KZ zIBX&+3u_k!zJuhq6fw1AwB7(0eZB5|H@h)r;v`>@;c!aptI+D(T39@CgpwIAofaWo zZVjgr`F1(YHJhMx)Ix4P94@s0EUWd~WntSaX__Yt>-lO;A3=byjR}C#ky`0WP*1ek zVN-_u`zP-;9Q{a2q(6K4*mZBZ)4Z!q;g-w#veS6OSkP$u`RQDO#+uz?sdTCR6J>9L4Ejn!tCIAwK z#Rl#G9_yvL&6cK2EmxPi)!AmjW?NIH5Qr258lq%!Vn*s0o8&Q-P|WiD{HYDZ_{Nly zf@1VV#bUfygo(bttIAMo?c7YdB5TsScQPx*1@8tPv%pY;J-Fxfm2$d-+hamrXEp<# z%Lm+;(+`)!?XJ7AUUz~iEN$=gO$S?nW0v^^1ur7Qhr*%Py#(ex7s7uocPx1~G|zPbNp%M%L2O_HshJ{u90^1a-23Sd;NOFLDd3>mOny(cFuZ83rR$d0q0D zuwnVI{OY3qWujiuc;<(R@`J%wvfzi)_2`-+wcI7A2@kV<=a~r{4;LP6{AWIG zU9nIN)vfw`^WdysMKq78#^i_ z|AuVv&~{D<bz+n>(>09BJbok7&1?ZdR_!49qLpw?hZkN3*hx-8;xeOZy{5KN!7 z{fieh+~^eNOB?Kr_T$Nnr2`$DDC0{wEixI(x$61%AwXL-dd99*N%fX#wKiCwT;{Qq zP>vJdOsDT|KVBo-BtiE5DSV^1h+_5|JJ_^Ka~JPD6Q74w+#teyv=~pCpI4woS(Yv- zDVUey@YF%u}s3%{XAG_j1err35a;l3ASB1P&Yzw?+xYKBbBtgMJB9g8_zpC0da ztGpjH3(Jbg?7~~uc3+@KYLpB1m=u2sC+=PSrcEkJM!4gKYWrToY9Zk(%udI4nj`C%)}8C8Y5`( zeL5J{glH=_g-j!v#Y>`x=bcE$J@>m5|6Tc^l8g0>Ma2>I6%*~_`^=ey3w*jya$2`{ zSWm?TIo2yc4tTTK)ShZI+~H*kc6Ki>XpcSO@gUb$)VRAf3$^jMicRr%x}Fs zNR}=wQZP7Us3gi(f4?@>tfH+gtADpQvw^mKa|{{r*x0pqa++-WZpvx|pyaf%0Q?A) zk87W#4dI7WclFK=*j8^0j#%$Z+7m^29({=)ACh@jLwH{?e*%+$E{i->Tx}S}vgooV zY9JjLz|G=vXTzopIkUzVrY>l*6~nTGn%w0s1Yb{is3{%~z?p z)QywJV8qx{x{&{OEON}TLMx)MtSPUGlOCZS4deEQp1F6hclPNFSi?7)M2e%fUOSS1 zutijQXmw+w+iQl{$q-Vc&JKCCwkzyO^2-m)+WBs~U%w3gy~5p;I?(c7Ygr2G5(+W;JaG_((jtw`~-NZsl+ za$jM7+X23n)ash|I3L2LC9Ux>X}CuA#x6pM}wtjqt?jzS>KyZ@#wM+Ar(89%l+PjmAopvYD=8SvX8 zc%q(xV=;O$I;jroaWewH39`@z@Fx*Ixh4se>~rc8-fr3buzzyqHUWDFzAXX&3Qm?R zb_`d|b?(UwR^?Z}*CH^i4&uB+sEgw#S^KOg=P69UANS#v+p!#(Hxl}M&-}P7 z&7S>3Ve_qj%UF_at5jLb1v`>Vnv3K=gt9f_=O#G6v1e}{_Qiau7>bSbKCatAx>3SK zYSv;0(EXhbsDx%DEO2M`0p1X7aoONS^kz|+xa9%i=*!=m1bg~Qsc=0}eB^SZ?C$+9 z6mM@&tp9%lqL`?QITy#c8v<)_P|tmr!Tqc+Z)c;A$Wx@2mY`~$j#B%XPQlAIYf2C7 z)tV>vOXkzZt0zXdZ&$bby5U<+UG}4U=K;U7#qnO1fAt!Amme9v~Wg8iH87Hjv32@kU5b>wT+wP>CTsw45SXO^g_8{qK1k-eSm5o7!S zoHcgVwxn#AT=K;9ahM8(AA7l;9uqHAz(}fsT7}W|SsMLN#<*Z;dpAOYN$9s6aaqxr%s(t{|XBXvRblQK005VhdY?noy_r5 ziNd-2?ajrk!Sk#(E}!N!kjCQGUeP)HX*bObS!FOrKVWXa#p=$ z$!_BlLOWMkF%c(*byjkN*|)jj|IbD9do zJ4+;5<3Zr;L@>J3OW4vYuFJ9W-kIWXttQ-J1bjOdG2Vk*ALlEbaW+qU{63$}qrTnQuHYh=2@WPL!^=7C$^U@(LxM>k(@n$FPouP1%{m!IxUW;~CAkY_`h&9v zjG&n(6q#Df#hhalqq3*|A-qN6tK#pER9!Vy_CyGJq*9Aug!M5H1LK1p4UY9czO6WV zfF|5T81)evZ$XL4v+&svGuxtS6yD8B_sgy5Ib$3@IybO5k5JEW)wuDI#yVI#D^Cwe z=PAdY>K1=YG-lmiXqde5b6;4fab#lCReMiln6WDx%4}Efk;R;=T0W4% z=Jxj3p0njwnSpm^Klvu8t*x6qDDFVpd5mU5mX^r8w2>p~6>sn6yeSUA4+_Vq>SnWdQ&Geq4z13-9{He1#{2-~NW zKfXFg43&YZ%)Ag=mQT$Py~lKi(YIi>}5QUKj_Q9OS;d6*y?ic5{Vo{(*6XQZdob zj-1Q?Tg823^gl&+@F0yOU9?>F#+Wx6`^sI!)e+^xYigtrcYEwk{kwI$G*pWN9%ufG zBJy$^eX8*M(JkZJ|8`?Y5?*b)eBXOc59E7cVC>WK4bZ#dwl06G|8yO3UZCg|vb;95 z1L%Ddp~kQK<>0Lx;yNe&-hG)TH^%jdYG*&!1AH#gZxklFe1?h$tJ3bwX+~_9YTFQ5 zUEEiB?5x>(0i9Ya7!I$4C!X4t?#LeVGhJVM2;UWQU>Jl~b(%@&6F50vY;X)~HWxLl zdFEF;LH7FjissBc$^BQ^_jdR2{T*?ODQZ2C)VUrqwooq(c2n&v$4tQ5 zX>`#I6i=(SF~kI)rY^A_QWrWFbeoVdT~Ck&a8Gk(pYcOXaLpQ$m?pm=mxU~>PjvOw ziTrHLY&~%+Kl`I%qmpmb5+FuEFpmpu^nm5PU`N|WE_xpiF+W#md~&btldUAL`%uJ2 zxX4!u@02K&v52iuZ^nny{t?wyfaLdz2LW+*tA8p!soOjFlgI4<{9e}G;|W?=T1uKc zzPQNB*f2KYY$Hn?V`R@%y_S+va-MeF5)|@ESVBAE@x4)MQj#QS`S!JzmdYOjqOwsC z{P7}qJx?)@25i6e1o+Hs-irsVJ|%9b9B$kM_LoKKX_+6dY75xps1rldqyYCuNj z#;-=%TR#o=H4+Aq)T_!&l;CVSN|4!PD%mU+EQ>x1K;PaGA(aB8lOc$xb*`Jw_JEa9 z=srT2q}AJ2vK(c`oD!qnUKH}^*?Da=LXU2bluJ3w*miek3VNUw;AH0nSt5<-#btYv z!aXZ%4s!SUZ?xqwqbwq_maf(xrlr`}_I!D>_%^Kx8-@8=niGuRXFtwxzh4*`hDH;3kIfX!!}Qh zbSfK4*JV#&EJ6g3OiIC8Cw&^Q z`Bg}7Z_E~U?ld}d=H`}#+JSz`YcKQ6G`VW(cqeG@30|C)?F>yGuB;x>0!zFZcMN!g^Y5CM#pB|B$ z)>uvyJ;w7!@R@jc8?l3)%(u$ZlJ&7S#CzXc*)bnAysnlbTXw$>kBw_R)A*mE#5_{5iQ8~kZu!TY!Y^HhN&s1jdKmC(n_C&DKTU{PU*h*j77Ru{CAes>Jl zKfx9k;N3{KwzVX%g_2Cpdv2g4Nql?8L*fSXyOA-K20k!N$G~D(iON&^H={ij$jQx} zI^^rhU%K#3oqmiV@IoYIw0P*tlz~?k=f~!}K>lZHM|9<-6%z>&_#^fyJk$)1i(%P? z{yr7LW}B=qwF^vQr)@NOWqLKf^Ycl&Ib=|IuNF~}Yn>#=;z_Tqx$T0Q zmLQHTfrs0f5!tL;iVygt*Es$Jz|#yVEav0g-PqT}r0f~s$8~U~woOC8bg?E~BGhW+ z*Jo6a_{XGDB9e3}*o#{SrXNI!Tnlcu9~4WwafZ@#Y}so5^MP%?{kJApMIL6csdd<^v$Wil9FW(T3Tdk2Tk$p4Dw|Eer2>b1QBC%>H=fu>H6F7o#a z_pJPxY93Atg;p>P&rohr{eb|}da9q_7FJenj;N}#bF}UcUj>R?26@M;X;;N*X(jE% zl6kY`(z%iN<-G^k>FmJtpt0d8A_J*kNGFsPMHzHEC)NX8h=Ro0U~Dd2CCDNt;S!qn z8DOb?|0Bxol0RQgX{T^cg7Q<~A!m(y=m`BjxSw|-!7j@C}vH0~^Lg=MIEeIZZ z*=g1DZxjA+7|N{5G(WA&H@*l#HS^J8-Xla)rlk?5vfUj?1CJQoqa)GYf%BR&FShrI zqv7k;VlF)JNR8ei=9c}^tFKx@$3DVb@2@;pL7n~LL+VKG7Zh%@&8`JMdS`KLm3s`n zKiNQv>Eq@{CuO$xD4?N=C)@X5ieK5clHIlEJRf|cPI4Wls=7I%78Cp3YxMz6-gvy8 z_Nk~ipTzF*EOew|t6A|N2GH+3+B#hHYemd0=8Sx`^?Y0p^CZwDWAU+*f;e7<{63^0 zGEE0%eB{(m)poY8%-N>#lyTmE$>rTPTQU#`#2BR%u8@PM;GZ^xsHLcD7)9~j{j?)& zw3uo5D0N8HDf0{rh zrQ(D}ylWQ;fpe?2XImZfb|3Qr)!2Yix1#X6V?4a=u9UU?GWZ+H-gccXN(&1M^j}7Y zl-If&OZCKn=Sm%1_U z`_;SX2;`wazJ%K8)@ez)KI9l7&GojhIs1=X`W)kZqX}G&T|i% z7LJle3zC{zzKK?t9&K}OyA+m2mq9l$MKUO26v1&6omU>sq}XdoJ-Yf=e+Q)yl{9kL z%WsBM;H-zp+QXxXRJIwqyc@5Z6RY3505}W zd)L0K`o2`8_`Y1BD}hZ!>EPOa^+yVLOD~w~+cAx!-ILSSECco0$9DK@5zu+unkY5E zCT86=4Zh)uDG10qlSl8k6`qlUkC3sAAn4J4%B9O zk}eVS_RO$dXb+N8T>Oj7{U}}GvRqxcCVAPh=g<)6_@ec9yDe&I(>Jmb&88@!My=g3 zX(;DShxF@jO3Mm)3Mu|qv9>4D@~Ic+90eMIe;B6LcT-gDpz5lsa~#0_xI9V6$Edp} z?fH0B~k+n`&hPji+o@5UW4a6=kFa-)*8i}${^!jvG6Ctf+EkyHT%q*s7B_F`fnZ_i z!M#1b!7&Xh_M#R?+k>+0)LOe+6cf6Euoo|w1JlW4f>}+-fG9rauThNY)+^e1&)fVo z2|iLI!*<4G4WvB$E2#~t)g78R`w1$sJ2RG^_!612(mhJR4&RJbj%tw`im9&XQawUz z-erh1PQI`g)hWGicsO(4wmHDUQcYRf6z2iU|4iEZJWHjPg=txQC)(5IiN+gX2spo< z*&oMEO-q(VpeO4A1>i8g?TAI|1*9sg_B%R^M^*WPOieUp1%&l6H7dK8F9 z`BQXG>2%!iv<#nS-)<28qviktxzSO~oz0WLbc;Xj{sc^i#kV)#S$V&q4`_f5a`J;& zCOjzERusv@*tJ?WBVxpP1*HZeHq<_?j)bKi=lz;isP;rUzxkRugv8PGQ9lT2K6cuD z`1dFH;>IyJE%d@IM4}vJ8ZZZly%tB{ND~`TMTCX*pLTxuB=`Atc8bTUSy*U(?!pm< zFlVZF6LUW|8zkyt3_RJwJi((4f3?LzMoD;;`h(2qTNIPX6dwpQwshPQ=JQ1*i$F= z^QkUf)mURNI|s0@;UZ3(@p1L>ZUSW68i*N$xl=NupQGG-jl#c(9nblSijI!1`uGC0 z+dHNlqaUe=tm<#(ja8v4Tp;`6h}qk&0OBfk(6%ws@yj>_KDI$z5dPRU&w20$V6bvc zdA<5a+)Gb?seHz3iAoQvuR`iE%PTmrWqo}s=w_%Me1K-M76wjF4xN)1Q6$X?ojztp z+UoTyc1L(G?o@NpJbS<9_uHG$N`HM`IgXLBt^P^Vg}V%!ag5+ZWIMDw zG>*9MSa1)}+jA@k5CE`-`>HP1&GA^9Ki-@mja|?m^oA+Hf1HfX+h#dVYFRTDY#@&>MvWYRtAx6Jf=_u@=LR}kyIw$Rr^*+i( zw@&{5-4_1uk)8vk*|+!;^m(X&mg6BF1wKeNA zrU2Tuf4TF%8h?#$pC!tI`DO@r<3p|Xb-PiN0Y2Yx`$9k=3D68Vd;OZ<*Cv5Kt$19o zjUp%8KzeiZkRAmzqR9OR7}k9A{Cnn_1KO4V2gddSj7|S`eJ`Z4aEOv$&U2&nZrrx@ z5I+@H)9IkM$!WC&gkffwZct8VFwP6A8^!U^&pld}aQPhn`It(e4J}XJXFn%b6c(M> zPR1CZfa?Q6ADWm=VnV+hK;p6g8RGN+446f^JZ=vT7^<;ovx?wCu&WijZ+?CG!Dz}&6c+F_4|#+mCZ4T33s{&xFz`?=(ZRBV;idIM29I-`j5 zs|)MRUo}19RLCkwm}0D2Os?#=e1RXYV#%3_02`XpSv&4#{6k%KN;OtR>+9g9 zoU=}x#Qt;%$prq=n}H(pnUB_1|GnmaS8tai3wZVHxm^5uOeNcmJZNPwb?vO6s4As& zr8U5&>B8ic9ZM=7LKGpDYGf$WjL6kCyV$#!oLW+V-`^SIvDBH)ttJ|Dsnxi=4+}E z-3x9}rYtF99?7q7j$E0Z)}v^0CP92TsbY}J)ARFU56GA$&cIbA#^x(w7!PO9%{Q)^ z;>pdaZv=X)fC^{lAl-4Y_6i4d5g!C$uB- zxvaHbFuS`v{3~njX%&A|Z%w8*DaXc{Lo&9?;9Iz1iyx$W!s`bTY4f4Nh`2fI9rkl@$M0sBeZ zW4rI)b!q${#_sN~GBU6n71a`YpYzYl)K$?BKl^LThNG#fw{LE4s@U-Sycu%FUL1lG&Z}OvQGB6~#ADl)AN+Y+coy zjG(O>@35Br)bCJZRPEO)+(&1*!)p6)EadF?(SM42TH0MDsuZ8{;5M^9TgP>m`V65MH1>` z|5GCRPe@vIi1cCK`%t{RSB_60))yKQ7c$r1I0Zyjfo-y;4qm=^tpVuw4QoRSgOJ{t ztT-IHG#&-&#=z2g?s}*)X*{olqrvVQQ8889loP|LMIHf%eaD87mQI@W*Tc?A-hw~5 z_5^i4w^6-UYC=TyjcyoGrz}Z&w%D4RoA(Pq&UZQ|RhW%;EC0sq$xNrJs|Syka}qZx z@-m4r+3AN%`7389*RufDk6okKHE{?auiBPVi?k;wXYK z;cWsxLDuukCO`}fSu;DkNSP3vS*uOP#@oH2%Dy+O_=JG|47N1GR-_Q)Uzu zC55L6AQugh_nM)kp-+hLaD4ZxCI4p8qw-N^@tJnkg#WW-AZN?lSU_97MWeHX={t|| zCS((H$oBA8`g1qeFUk zqoC^7wX#%|n1F!zHu>bt)ReCZD(}(RMz3Ph^rBRbp%D<}zrz=Y4aLXa)5p=kB{KtA z&U4uxG^z{gO^bh(MH0JNZ9HItxiOFsB(QRTKot)Mh=)^)s+Z|XQ}XM}Lj-Ro=a)Zm z9DCXC1*hHfI-Q)XogXbjyFZ5!RAIG%jp9%q0g)XCfc^;}J0MbTxBc#e%ldBy$dl67 zua!mlv@nu_dt}7|fhhZeI#E$&p6bMu9iQxGJevkHmR>>75hWGxb$tlwpSJ#Ny zv@`+dKv%rO+~mcsm~Q{-=$aiS#gy#j<<|1S_AD(Yobe9Bif8}5GZdgN#%~`#djkqp zs!a2P$p)Jx_jh{TiFV6?{4~)SHkRJC8kZUs?JBGl^_MJBkP;HPbcwA`q% z7nmF5XN6b*N)*KeH+g@io{ugSZt*ocNcw6_Lc&nBxz#fF2To@}`@i+0*hJn1=D%FO znk9t&2T=Im7`P>aT1X;Y+(zMyMp&{^o+={~hhP+MsI0Yda*By#V@n@O0+rwGysM=d zi32^BLPG7y!`#QmzZNuShVbh-^Too{$K1?*DWnK|91%sx?d;~I%9xv3$sFz+ zIFfqgLXH&#rjGwsRw1Vs*m8|;wSb6bGRtShVp3IzzZOZy`Oo;If(lr{ZX@}Grp{a| zF2=iFQv)ohuaDQIHW};fWwz`MF2Oj1Fr7PKu211khDyD7#)+>t=}%DJ_sX)DXBHJ; zlc!#`)P~Pn*kBccG<1?GL$>FdOkHY1VjNToNc)9vSmcKun3$O4krPE`N&aLW3CY0g z1ysZ?84Y(k?U*_Xh=i2XGo!x#r1sCfj^1iHyVL%mYxbhU-C==7TVyv*U{j;;ruoTv z9}#aS|E77dg!*9}z9*OCnoNa}nW7>AUtM*d-&pvtUIKb(WmOkxQC(dS($8RLv7ymi z6fqqfY~Xv#`(h6Fzu&YkP!IKaB1-2Azva%3CLK+4@}{5c->(8Rd4+^NN_P0uYnVPz zT#D++1js(0%aG#tw{pudDtnjd#Op)lZ0JvGK;)*SSr=tJG^a19Wo4G1r+(QgaI=^y zs(gS=S5jo_Bh=KmvTJtyn5J`2bRLXWK22gN%l;Dk8Wp?djRxxD%)2WRGO`E#W&tPp z<-b<*jmDNcaSOJxbI_#=)X^#)GG#>$%hsFS^6;7!-4yDtmLESd=7=MP6k$T?VFj{< zBHvt`oI)C8C5?CxS3GvxAR%Ruoo~-OXrXRyKN}%m%q5K^f6U>$>*?(Hp%hul)gM{= ze|9WqF#t}0pPyY=#u)w|fNE^4ArM)$09tO8SHD~pqN58tb7RvnmLgYGQ&W1j5N%pj zN$Bjh&ux2(GTNhQ^g0u%@+>Sa{l)>b3=4|udnTms8h6;c5HfHTW3| z^C~$|yOl*Y5MBU_J>4Ue+%wnPG}L1#KZvdT@Wa?T?IMO|BDG)r$9ucC{+k0bk3U=i zOB(SoNlK74?%`q$(70z2i|A!a_vR=$`mFct6^Cw@A}KB{&IBX^TWf(mQVW?-Pddw`QfDf0|RC( zwwM08165>fFKr#%{K38Af^>ATX9x#Py4K`47;9FJZ0Ha6PS>J`nY4Y^uvuf}(A;1Up6oSdG%DI-cqSlQ@u zmRHa(yY5OED^1waHYol5lO_mjB3C|%TQOPSHkd*7`P1(q9ScV?iQ3Y%Iaa-ga%R zi(buQyVeCMg+b(NL%ZGXss4a*^vo- zWcHcX7$C&jgbY|CS)_o{3U3{qTT|na|J{Zm*^{gA2=|Dn%hy?^^;f9kMdVh+%O+qs@QV#i3h9aVXkoYBDda zzW0JJ9-)tVIX?dFVRACnJGQd#9KfQ2U+cfBk=f<`Z)X?WUdexie22dq!78gUsms3j zE;J8F`sp?_LEhCP*%2Ekm|{dJ73G7m^#z@Xf%5D*9J(g;5^|usnT6Dowv^F=uNsR7 zc?%8h3aw{s2y|T^!YAdE$`lGp3o@iM8#1MAEzL7N);3v~R1weqUgvwsyBm)lpBzst z#AWx(vnoD@Z`10ppKc9OCIuO->Q2_>c5!r%1abQ5&YtwXN*W;afUbS+E6d4s%S`B_ z{yjCV@kLaK`;Sux@1V#@nOGZ++W!zF{}Ykx>**th=J!QV7DLTm+@Xw^V9<(W7|;+S zJ|^3v8KL`_uA+S;)Jbi>T-B^L`$Fn}A%eMrp`sM27=VFc=z@wM_Gfu3uQJ$yvKrQo zm&vsqlLxCv`N1G={#0#wfE*MySU>)`6V?{Ruzd!6mv}OaKNsL z3)c_os;}Py|HNXUI3Gu|G7`uctU~PxX)xyjN1Oq^Tb^$_qa&C~=3auN7@ZpH9lj^I z%85_cC-I*+g+J?4>~VWfni_0prDw^-mRbm@418||C&hf!!a^XTQOw%G!33$qZbCAY z$p%XR{LMvy-=2%#SNBz(NA9AJz99nPHZ%CNh|>IkL`B|Sf5W`dNm#poT4;*fst-p9 z@w@_{fBLza^pauV_kD=5w#!a-b7gFyNPp7z*nV*D@hJF&KdHFtGyYK71@Aa-cVO=F@S&P@r_4)$A@Y#L8BU5O>3flzfe=>C*SF0NnH4iQTRS2jJQiF z;7j&znl3z)0rB)ss1L4?$KO8h_sQ+*{F!F&1(~`#UopR~4Y>XGIa?FU@No8EIL?tu zsd%6|+N-hzKR7^TF1(#TDt>70T1D)+C_ zijJzWcZGGEi z{@T$UC&kG+|Fyl+;*2u>n?B+GzZ_0&>|&Z@pMm3>sc)*X-%8BuX-ml4GdvrYIjbp2 zufph2KL|>$F!h|%=BA*F+ zSXNHZ+_B)G6OCB2h=B81Cjh=|`SA{WZ(v|R@>7<*|cCh5z?qN$98+*sY z*!+A7T-bT<8m+XA%%sk*tOrId) zAXSm4cgmJj@f|15v?rdp;YwJTqIRiv$JRvcohj|A>H-ny%LIdpNp5MjAZ3E z#c6C(vp<6_^si=gs55&-R4z4RsCW=jSEQ%F9{^R&_vjhd`~bG7 z_Cc}R!;y;<3E@A(j0siN-;L~MKc{bgSIhjuFDCoP_kq$fk=G`qep+#J{CEG0&{gd; z`-1jf)fjey;+#R%Q$HQVj`XRj=M?bEbg(~Gydu>VlR>8Z&ImbQj)OelHKJM-;#E)c= z9z_c9O2dZ$do}~o$7h52j(<-EqZQ4ShCv>cB#Ul;`(tI@_HQ_to*w4C<)KIvV7US!c6+Yg$bA;8@>ZUmcYrWqbGfngO)(f&=`$dK&Omt6N zJ_vG|UNyz;m1vu8F`rr}@-)>WQfMf7<)%r#77ETZ&tulzwo6 zyA>!D54033QYaKCF2SV`+}jk_7I!GcC1`MWf)pth+#LcWSfF3t%zN(-ct5}WZD)6O zXYSpZow?`SbH=wa6PjHO2uJWi?R}x5lPwCqi5mzJvxq*a3&t7Af^n(NO8>57*SS%9 zl7C2Q(UVIJq4^eFNrKx;i;k`T@Hki?3K(9riiu>)H<3+E4;s)>k}bwqDEMMPAUV>b zn2j`f(8v0NB9H@rSZP8{Ry(R$qpt6&05$qOjg)A&DE8f-8bc&z)|IH`Q5 zWFzHkHC5!Uy7&jrh@InHZ|9?A>4@NGuFyi|!ab>~MO29&c_O9w^;ux@M`wAepy42N$2rcdr-G$vx_8a0MqL{8PHC?MM31x+sK6S1{I5pcZZX) z(nBbedg(8wxstYG{1l%Jx-zyz0Ab3mi$g}%H^E@NxCfb#_4d&x4R`A2YLa^x%tjZY zZhZ?pkU0@Cwm#=J)y#u?n6UiPYWZ8|&=Bxb(*Dg63% zJZjgN75o#&jsK&hTa@EWyd~|?hPii(Xj7?om2pPL4G^kj_OD;4D2q1ua3r}~z)2H{ z1xbpHtz=qm@x9B1HSPvU&^Vc@b?hbKoQuEB!Mc$!l!P!1s!dl?tYKw0r3@CPrsSAr zsXySujUVV%115w7#^Y1T1orIrCITO#6RA7TlZo&T!MVQZg>`$G;{aTvXR*p8_IVv~ zsg!{)4er)jT+*cHf8aSb)_ZfOMxEJgKRrrEjcW{-OliRBb(g7G>teuBHAjBqkgsg6 z0V6{Ur79LeQr{I>O4ZiuSTCb6k5CiL*6+Did|kml*8v!Y!S~8*zRY}oJ6t{V{hv;- z!M?@0iJVs62DPAP>s?TbU*L%{G}xmxDmyN4$_Brt^S**yHYJ_gq&QB!?DEkS>Wfe1 ziO|R2hdP7gw9I=tRiRQKzqN)K(*E{`Z>!ppdC%tpYSXvSi$|;U{pO6p`6)WU@iXdx z)$khtWOLD5pGrzL2(W*IvnKGD=XHU<{|YyaW7fcmkqfFmA$o$#G!dgI zWMv+H&d_5Ohfi$eBE&2Ft#!Ii8IqJD(&s`eogwx`I^EUsTp+yh`2} z5v;H5&UyA5-c%mO{cu9O9z!jySr=Gk4kYj!A;>ftoa<7KFY01D#!TC1t(xPCchXmY zLJg#dS(MSZMPF7H7ck$*u42ylaJ6__CX8v8M4$q7h#7?@dP@ADO0;dw=-POj7oBFf zn`Vicca!1vqYEy^R~BZ0Ndyx@l1z@K+zDJ+JLQ`Ewnr{|pesv(_q#x*j>zuo)_K|! zxggKg`Nf^Bd7;_UmAk>QyTsrwfO}^}z$xRY?&v3s9iay_C*G#OeoQIkQN(ldt<$5= zOn6u@?6z+tcP&g32z)Km@e)9$xmL6e`IY+*L+zClypG53eRCAy`lrX~Hi6uDbi$%l zaY@nmXV3Cy33;=@w|M~(Ng%n=VUCx6m4f~hm$+;JA?NrzKPkLqwrwBUw-hhbwUV#XH^(m(O+UV!tZHe2|(qGjEoEXHjPxvopkIJ(wFf+h3+&O(_dyj6ZN4cW)4TnPSZ@LS!zq|&5*Pn7 z4uR9|z83VbpGpShO^|Rn^iSZcdPm&vXAc%RIR) zzdB^Z#HY1&l_e+R6L%)^;FbQy)mHD>2KJqkc4^4dqpjM=Wdj(K(|)(Qpw_y2tCaLp zs%IHh*xsX~1u<;?M4^Y{-;!I@^h1ks&hF;pvIwYg z(0h=>dwhaBYXU0gp>$v!J73UkH3!RKq>*wmq28zTFn~2qoaVwtlmHQjaKA#wOEt;E zz}8j~%Sy4IEQF71%vB*kCOe*^+R&qj4~u=giB{mCX99RL?VQ#eN7Z}cG>Ltm zoa9_nM4{u11PCt4TwGm;$6}E>{;1a9tiMe&BIoAcgtdA+F;w{q{mGWK>hnYu91w_a z#hhv_^Gtx47==R%>Q?F=Y}ObrRZCFs5OY8ykb7Y&Q`_Ae(|2uG9yjg9or5F_XWw>g zhEk@mS3_kFTJIeUJXa-B>|QzyE98g6+#(!#Z>8PQ?-!VGo#2v?Qo-R8EG$cTixJ^a zDEHihAuJFc_QLZ+nR9~nS0ng)s+8LHoGM*k`Pv+4=3;Q%bQ5Rrj4CInV3vXfX=397 z6dT)?g_U{XF(-T>O|JYr3QNnZY6>XPBE^kqMU@mVzh^O+p^U|pIrW=rYPS;4NpVtY z!=jL&DiN?qa>4I^`Mp~DRb#uq>1;c#u*Ok4@H9N&H{c2E{QF3_Ft=IXi9oBFu$x^dy6S8~p1-*tk)VM>Y`5LiGDrC~~%KP}Z5$TAb1FS6drNHMGK(^jbAxNgx z&bpi4yUau;tMn0&(st9MU~YFV?CPP-gs5R-Ps*!u4R?Oq6?;J+AMWF zDeGyU^cJ>T={IV#%=T@2$M5CVotN|Pi{n5QUz@8W{qw7`lMUu~#ffyIaqk86zkPWM)Rt0GY>=!Rnd3U9%vGZb8f2eH+vo> zkxuq-Ti`nl^aoJz;&D=4?8d}xT({duN6dE}jZ~-F_F_ieicottjvr&96wR9+E-9AP zxr<7?@Yf@OSq==2-u_$LA^5D;;VzwlQp;ham7mjfr}=p2XT^ELo_I6c_zS77R?D((M?TXlM52aDp55Nr!h;~(>(O72`=^T2G%`AJrV<~qz8f6J;YORSp{dW2tx zpu08QqX@R0gy=PhXDMZmREo>$((EI1j%i;?KJBLEEd8sIz(vLa7nv{O*@eytckPTj zW_{5aZ`k>4BnO>%eKOV4utJiSWn{b-jaT!IHRabA@c`sAd!MyLBUZ;V#$2TIb=Ho* z`pJ6gB?^V&VVduoO4>(Vtz;VaR=Mi0`Z=B)m+Ob0ZZtP|Vp=*v6|PT`5d4}e%ZrOa zU@&+ScnX}q8D8)I2sAY7^eaHT)ZqTvNA8nd3+U8i!ofy~UD1rbP9$J9Kf1>S+vdgT zFXt>RE!`~TZzJ_nsZu*60OXx`MrOU>xyP1z5ba&^c|*%!HSW%JB&82{Zg~IElcnep zsLMJJ#bKzh(pX5q@#+xwCem`VBA;P#^&|6Y9u0+fb3KK*o$gyuV`K)LDK@!8xBj*? zmLU{kC;-aeLH8%s=CM=7vQ5C&!CJOn~CY`8JZGN@}HfmjViyvQ@9! zQq>Zf+UL=k3wV*t&kEp0XO36uNga!8OOlNwvRGJcj@lXJR$GjjA)pIL>icJoG!DcB<6A^`~?}FK67aPN3^o6-{5D8G} zAzM25Y$N4v5!yE;-j=c- zdT({-gjUw`2Gd&onYz!kswar9b73-^c(bL-e52tBgGBnx?$1-!!THS9{t`T`+-7Tj zj)S{6e)$TEKm+1dgS0P`N{t<0&U&xXkn~D`2bBK!I6r7+;P0}OPgotmRV6m4LbEU% zEl2`HmI@-n#oTDCKFKqRQM+h~+wvGk1_JY9Ge}&jn+IrSKyy!QB+$7DE6##oKlsX6diYyb-bzrYq%LlHOgaHs zv1O>+SkeZ=e#!PHCxf|Q#f_a(K!yh-W&?BKb1^%&JJ4(UMHIS`W0nKqw3$c!WN|6& zyH}2{0`!h^l@Had9PLf+*Zj3=PR z@mxpG=qn-T8~>Xli$@<^^tZtC%{-QK+zXptcrie%pa}cVk&%U#t=E>rXyVR3)u2CY zpJvI`SRCKul4K_@u)r;wW(#M8Vf8^Raq zr<9EQOXq^;%W?dd1zHq#{ENcNF|sFGCsqH$0sP%^0J!ov%Ok~><8T%e0p&B9HuiE+ zoja8KJ7g3lxt(=1*YaC=VfSop{%!8yBDSL&$ZI0ovEQZ=)R}K$ZCr~sb#1s}-G+tA zRVts$TB%M)%kUI_+KP&`Of**hE^+w|>s6$Zsg`bXB>yAFTvZSg7i;$W{_s_B&5plR z6T^Q(Bv~#_OSv1qH9ThA5rw7GD8%90QIB|+)i$vf&qK}?J$Jqwf!?l6ktG)&>Qnyo zHTd+2M@xfr`btm8!*Vu3E3|v%ky5kOaFd*z8+3(OHNAVd>O6hYXkk7+tWYG*2apKZ z7<3m4+Q_fR@*_N3I-8hY^v6Ghww;gc`LEusOvc7$+YW3_UR*>Ma8VDCOvDWo6z_%= zBwp(TxkxR@LdLCVr&prW(g9X7v6ZwnLf10EoV|cjzXF;S-hnLtf^#m>dq!-a^_lo2 z&sOnmx^qxWfC8|yP#~ws7-z6D_nhSCOZmAYtUWsO;wRD~9OK+)yV70`mH&cLk?R7}iV0 zt%c9Am)}hSvJ-b}Q5xxgUErdpx$pP76+3}2F$YCSwdOL)5fytkA(te+ABLRVeMw9A ztc4zX1h)sliafe0^Urg0BiKqhW)Hqz3EC9glnV#R0&b~o_;DEBQ!H=9dGBVJdih4=W8Ka>)d z;ME9W%jxIn*$RmsB3#A4JYa5a;NGVIy5+X!n8DJC4aeKvv-gX2G}%o35m$t`@dea= z#O=GZ0fwgi$1O4E*e4&`be$x^Ai`O0%OrAJo9~DZMz8b9-^I52%17lQ)gmz1d796a z<`pv8c+;$KdvX^$km0KgGFKSzL7$p3(cuvuau0RQGmRfg@v+3JvOa1O^Tx1S|e_91pijh9Fg%k^1C!Yah(Kelyir5K-Y? z_wbrZtLSpXsvcv*B2v-557!ohLIG`$l34q%3NLo@2CF5V*@y ze*4ShVxtV={0Hv|qB6}gmPg?0^P02I%7(!|^BHxAl@;c2!Hc`Vvk!f_vXH^xb1gE; z)b<}$lV3?k*d^SY6Z?iXs0B>JXLhMkO#{msle7h$@s({&V zi23Z)YJ2)b2I%chzbLNGSP=)%Mq?9e)xOtgpp0uqr;0~v7(A`Ihs!zXCWd{#KfzhR#JiD#_O=dE<|G>TGu6GJIjrt-Y`ARiv>NAFP~NPE=N zkM@hlE5&23o54TrvFR@OUz$oII14 zlIatw27ZO$4%h3_Lv|TRxo3}jb+{8HuXe*Kw>teXF~<-2%@X^i!#3yKP%2;K?)v3T zIMUYWzORHJtd$82yp8!JbI|Os+e|Bn=8z@JWV9vkUS~OqE7fW{XMxB*L*xkPX9FuL z1Dk{HEpE4%XjThod^~8q_$J%0;ntI=af<^#oqv!UcjC1>o}%Yx7Lr+Hjc10#*|9&G zim#uU@p%hp%Prw&-%#Nru8KC1%fk4}*Pgcko5k1F6&@tgHS1r?yV2#)juDJDCB<B-;eTK8fA;HiA&|9dpn{z+p0Bk|FF VL#Y>91jc$Cs_!%vtKOJ@`yaW;VhaEO literal 0 HcmV?d00001 diff --git a/blog/images/20240814-share.png b/blog/images/20240814-share.png new file mode 100644 index 0000000000000000000000000000000000000000..b486fe23566809ee4b822370277502a1aad5e2e3 GIT binary patch literal 306879 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

    .]]> GitHub repository.]]> - Batterie peu utilisée
    . Le service de fond vérifie les messages toutes les 10 minutes. Vous risquez de manquer des appels ou des messages urgents.]]> - Batterie plus utilisée
    ! Le service de fond est toujours en cours d\'exécution - les notifications s\'affichent dès que les messages sont disponibles.]]> + Batterie peu utilisée
    . L\'app vérifie les messages toutes les 10 minutes. Vous risquez de manquer des appels ou des messages urgents.]]> + Consomme davantage de batterie
    L\'app fonctionne toujours en arrière-plan - les notifications s\'affichent instantanément.]]> %1$d message(s) manqué(s) ID de message incorrecte PARAMÈTRES @@ -567,7 +568,7 @@ Vous avez envoyé une invitation de groupe a quitté Haut-parleur ON - Envoi d\'aperçus de liens + Aperçu des liens Erreur lors de la suppression de la base de données du chat Erreur lors de l\'arrêt du chat Erreur lors de l\'exportation de la base de données du chat @@ -1018,7 +1019,7 @@ Trop de vidéos ! Vidéo Vidéo envoyée - La vidéo ne sera reçue que lorsque votre contact aura fini de la transférer. + La vidéo ne sera reçue que lorsque votre contact aura fini la mettre en ligne. En attente de la vidéo En attente de la vidéo La vidéo ne sera reçue que lorsque votre contact sera en ligne. Veuillez patienter ou vérifier plus tard ! @@ -1028,8 +1029,8 @@ Supprimer le fichier Erreur lors de la sauvegarde des serveurs XFTP Assurez-vous que les adresses des serveurs XFTP sont au bon format, séparées par des lignes et qu\'elles ne sont pas dupliquées. - Le serveur requiert une autorisation pour uploader, vérifiez le mot de passe - Transférer le fichier + Le serveur requiert une autorisation pour téléverser, vérifiez le mot de passe + Téléverser le fichier Serveurs XFTP Vos serveurs XFTP Comparer le fichier @@ -1240,13 +1241,13 @@ Envoyé le : %s Message envoyé aucun texte - Des erreurs non fatales se sont produites lors de l\'importation - vous pouvez consulter la console de chat pour plus de détails. + L\'importation a entraîné des erreurs non fatales : Les notifications ne fonctionnent pas tant que vous ne relancez pas l\'application Arrêt \? Mise à l\'arrêt Redémarrer APP - Annuler + Abandonner Erreur lors de l\'annulation du changement d\'adresse Abandonner le changement d\'adresse \? Annuler le changement d\'adresse @@ -1357,7 +1358,7 @@ Utiliser le profil actuel Utiliser un nouveau profil incognito Brouillon de message - Voir les derniers messages + Aperçu des derniers messages %s et %s sont connecté.es %s, %s et %d autres membres sont connectés %s, %s et %s sont connecté.es @@ -1640,7 +1641,7 @@ Erreur lors de l\'envoi de l\'archive Tous vos contacts, conversations et fichiers seront chiffrés en toute sécurité et transférés par morceaux vers les relais XFTP configurés. Appliquer - Archiver et transférer + Archiver et téléverser Archivage de la base de données Avertissement : l\'archive sera supprimée.]]> Remarque : l\'utilisation d\'une même base de données sur deux appareils interrompra le déchiffrement des messages provenant de vos connexions, par mesure de sécurité.]]> @@ -1782,7 +1783,7 @@ Autoriser la rétrogradation Mode de routage des messages Jamais - Relais inconnus + Serveurs inconnus Non Lorsque l\'IP est masquée Oui @@ -1872,11 +1873,11 @@ Tous les profiles Reçu avec accusé de réception La mise à jour de l\'app est téléchargée - Terminé + Complétées Profil actuel Erreurs de déchiffrement désactivé - Supprimé + Supprimées Désactiver Téléchargement de la mise à jour de l\'appli, ne pas fermer l\'appli Téléchargement %s (%s) @@ -1970,17 +1971,17 @@ Erreurs d\'accusé de réception Chunks téléversés Connexions - Créé + Créées Erreurs de suppression doublons expiré Ouvrir les paramètres du serveur autre autres erreurs - Sécurisé + Sécurisées Erreurs d\'envoi Taille - Inscrit + Inscriptions Erreurs d\'inscription Inscriptions ignorées Fichiers téléversés @@ -2007,19 +2008,19 @@ rechercher vidéo Contact supprimé ! - Vous pouvez toujours envoyer des messages à %1$s à partir des chats supprimés. - Discussions supprimées + Vous pouvez envoyer des messages à %1$s à partir des contacts archivés. + Contacts archivés Pas de contacts filtrés Coller le lien Vos contacts - Interface utilisateur à une main + Barre d\'outils accessible Le contact est supprimé. Les appels ne sont pas autorisés ! Vous devez autoriser votre contact à appeler pour pouvoir l\'appeler. Impossible d\'envoyer un message à ce membre du groupe Vous pouvez toujours consulter la conversation avec %1$s dans la liste des conversation. Autoriser les appels ? - appel + appeler Impossible d\'appeler le contact Connexion au contact, veuillez patienter ou vérifier plus tard ! Impossible d\'appeler ce membre du groupe @@ -2027,4 +2028,45 @@ Inviter Veuillez demander à votre contact d\'autoriser les appels. Envoyer un message pour activer les appels. + Archiver les contacts pour discuter plus tard. + Rendez les images floues et protégez-les contre les regards indiscrets. + Connectez-vous à vos amis plus rapidement. + État de la connexion et des serveurs. + Exportation de la base de données des discussions + Poursuivre + Les messages seront marqués comme étant à supprimer. Le(s) destinataire(s) pourra(ont) révéler ces messages. + Supprimer %d messages de membres ? + Message + Augmenter la taille de la police. + Créer + Rien n\'est sélectionné + Lien invalide + Nouveau message + Nouvelles options de médias + Inviter + Nouvelle expérience de discussion 🎉 + Téléchargez les nouvelles versions depuis GitHub. + Il protège votre adresse IP et vos connexions. + Maîtrisez votre réseau + Supprimez jusqu\'à 20 messages à la fois. + Serveurs de fichiers et de médias + Serveurs de messages + Veuillez vérifier que le lien SimpleX est exact. + Les messages seront supprimés pour tous les membres. + %d sélectionné(s) + Aperçu depuis la liste de conversation. + Les messages seront marqués comme modérés pour tous les membres. + Afficher la liste des conversations : + Vous pouvez choisir de le modifier dans les paramètres d\'apparence. + Rétablir tous les conseils + Mise à jour automatique de l\'app + Barre d\'outils accessible + Utiliser l\'application d\'une main. + Choisir + proxy SOCKS + Vous pouvez enregistrer l\'archive exportée. + Sauvegarder et se reconnecter + Connexion TCP + Certains fichiers n\'ont pas été exportés + Vous pouvez migrer la base de données exportée. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index d820af323c..39dc6cfb82 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -614,7 +614,7 @@ Hiba: %s Eltűnő üzenetek SimpleX zár bekapcsolása - Hiba a kapcsolat szinkronizálása során + Hiba a kapcsolat szinkronizálása közben Hiba a cím létrehozásakor engedélyezve Hiba a részletek betöltésekor @@ -673,7 +673,7 @@ ICE-kiszolgálók (soronként egy) beolvashatja a QR-kódot a videohívásban, vagy az ismerőse megoszthat egy meghívó hivatkozást.]]> Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen törlődik! - Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás során, vagy ossza meg a hivatkozást. + Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás közben, vagy ossza meg a hivatkozást. mutassa meg a QR-kódot a videohívásban, vagy ossza meg a hivatkozást.]]> Megerősítés esetén az üzenetküldő kiszolgálók látni fogják az IP-címét és a szolgáltatóját – azt, hogy mely kiszolgálókhoz kapcsolódik. A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését. @@ -912,7 +912,7 @@ Adatvédelem Az ön SimpleX címe Jelentse a fejlesztőknek. - Ön dönti el, hogy kivel beszélget + Ön dönti el, hogy kivel beszélget. Az eltűnő üzenetek küldése le van tiltva. Csak ön tud hangüzeneteket küldeni. Frissítés @@ -1270,7 +1270,7 @@ Beállítások Csevegési adatbázis %1$s eltávolítva - Sikertelen kiszolgáló-teszt! + Sikertelen kiszolgáló teszt! Kapcsolat ellenőrzése Tudjon meg többet A fájl küldője visszavonta az átvitelt. @@ -1466,7 +1466,7 @@ Koppintson az inkognitóban való kapcsolódáshoz Jelmondat beállítása az exportáláshoz Kézbesítési jelentések le vannak tiltva a(z) %d csoportban - Néhány nem végzetes hiba történt az importálás során: + Néhány nem végzetes hiba történt az importálás közben: Köszönet a felhasználóknak - hozzájárulás a Weblaten! Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címet. Rendszerhitelesítés helyetti beállítás. @@ -1607,19 +1607,19 @@ Csökkentett akkumulátorhasználattal. Magyar és török felhasználói felület A közelmúlt eseményei és továbbfejlesztett jegyzék bot. - %s letiltása feloldva - %s letiltását visszavonta + feloldotta %s letiltását + ön feloldotta %s letiltását letiltva letiltva az admin által Letiltva az admin által - letiltotta őt: %s + letiltotta %s-t Letiltás mindenki számára Mindenki számára letiltja ezt a tagot? %d üzenet letiltva az admin által Letiltás feloldása mindenki számára Mindenki számára feloldja a tag letiltását? - letiltotta %s-t - Hiba a tag mindenki számára való letiltása során + ön letiltotta %s-t + Hiba a tag mindenki számára való letiltása közben Az üzenet túl nagy Az üdvözlő üzenet túl hosszú Az adatbázis átköltöztetése folyamatban van. @@ -2061,4 +2061,6 @@ Meghívás Új csevegési élmény 🎉 Új üzenet + Érvénytelen hivatkozás + Ellenőrizze, hogy a SimpleX hivatkozás helyes-e. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index cd62881dad..59d1e68afc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -98,4 +98,6 @@ Terhubung Selesai Ponsel yang terhubung + Hubungkan secara otomatis + menyambungkan… \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index dd334f6f3e..abf9345a8c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2055,7 +2055,7 @@ Protegge il tuo indirizzo IP e le connessioni. Salva e riconnetti Ripristina tutti i suggerimenti - Cambia elenco chat: + Cambia l\'elenco delle chat: Puoi cambiarlo nelle impostazioni dell\'aspetto. Riproduci dall\'elenco delle chat. Aumenta la dimensione dei caratteri. @@ -2067,4 +2067,6 @@ Crea Scarica nuove versioni da GitHub. Nuovo messaggio + Link non valido + Controlla che il link SimpleX sia corretto. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 28418dd80a..ff06893e4a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -1118,7 +1118,7 @@ Titel Wanneer mensen vragen om verbinding te maken, kunt u dit accepteren of weigeren. U raakt uw contacten niet kwijt als u later uw adres verwijdert. - SimpleX cím + SimpleX adres Maak een adres aan zodat mensen contact met je kunnen opnemen. Maak een SimpleX adres aan Delen met contacten diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 7b6087e113..ee77905611 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -80,7 +80,7 @@ Активована оптимізація батареї, вимикається фоновий сервіс і періодичні запити нових повідомлень. Ви можете знову увімкнути їх у налаштуваннях. Назад жирний - Добре для акумулятора. Сервіс фонового запуску перевіряє повідомлення кожні 10 хвилин. Ви можете пропустити виклики чи важливі повідомлення.]]> + Добре для акумулятора. Додаток перевіряє повідомлення кожні 10 хвилин. Ви можете пропустити виклики чи важливі повідомлення.]]> Аудіо та відеовиклики Аудіо вимкнено Аутентифікація недоступна @@ -248,7 +248,7 @@ Збережені сервери WebRTC ICE будуть видалені. Налаштувати сервери ICE Мережа та сервери - Налаштування мережі + Розширені налаштування Використовувати SOCKS-проксі? Використовувати прямий підключення до Інтернету? Використовувати .onion-хости @@ -266,7 +266,7 @@ Введіть своє ім\'я: дзвінок в процесі запуск… - Перша платформа без ідентифікаторів користувачів – приватна за конструкцією. + Ніяких ідентифікаторів користувачів. Децентралізована Використовувати чат Це можна змінити пізніше в налаштуваннях. @@ -411,7 +411,7 @@ Ім\'я профілю: очікування підтвердження… Приватність перевизначена - Люди можуть підключатися до вас лише за допомогою посилань, які ви надаєте. + Ви вирішуєте, хто може під\'єднатися. Як працює SimpleX Докладніше читайте в нашому репозиторії на GitHub. зашифрований e2e аудіовиклик @@ -659,7 +659,7 @@ отримувати повідомлення, ваші контакти – сервери, які ви використовуєте для надсилання повідомлень їм.]]> шифрування на двох рівнях.]]> Приватні сповіщення - Споживає більше заряду акумулятора! Фоновий сервіс завжди працює – сповіщення відображаються, як тільки повідомлення доступні.]]> + Споживає більше акумулятора! Додаток завжди працює у фоновому режимі – сповіщення відображаються миттєво.]]> Вставте отримане посилання Відео вимкнено Завершено виклик @@ -1053,7 +1053,7 @@ підключення… підключено завершено - Стійка до спаму та зловживань + Стійкий до спаму %1$s хоче підключитися до вас через зашифрований e2e відеовиклик Ігнорувати @@ -1172,8 +1172,9 @@ кольоровий дзвінок завершено %1$s помилка дзвінка - Наступне покоління приватного обміну повідомленнями - Відкритий протокол та код – кожен може запустити сервери. + Наступне покоління +\nприватних повідомлень + Кожен може хостити сервери. Інструменти розробника Експериментальні функції ДЗВІНКИ @@ -1234,7 +1235,7 @@ Використовувати .onion-хости на Ні, якщо SOCKS-проксі їх не підтримує.]]> Показати: Сховати: - Під час імпорту сталися деякі невідновні помилки - ви можете переглянути консоль чату для отримання більше деталей. + Під час імпорту відбулися деякі непередбачувані помилки: Цю дію неможливо відмінити - будуть видалені повідомлення, відправлені та отримані раніше вибраного часу. Це може зайняти декілька хвилин. Вам потрібно вводити ключову фразу кожен раз при запуску додатка - вона не зберігається на пристрої. Змінити ключову фразу бази даних? @@ -1834,7 +1835,7 @@ Посилання SimpleX заборонені Показати статус повідомлення Щоб захистити вашу IP-адресу, приватна маршрутизація використовує ваші SMP-сервери для доставки повідомлень. - Невідомі реле + Невідомі сервери Незахищений Коли IP приховано Так @@ -1851,4 +1852,219 @@ інформація про чергу на сервері: %1$s \n \nостаннє отримане повідомлення: %2$s + Адреса сервера призначення %1$s несумісна з налаштуваннями сервера переадресації %2$s. + Файл не знайдено — ймовірно, файл був видалений або скасований. + Сканувати / Вставити посилання + Налаштовані XFTP сервери + Бета + Статус файлу + Повідомлення надіслано + Статистика + Попередньо підключені сервери + Помилковий ключ або невідома адреса чанка файлу - найбільш імовірно, що файл було видалено. + Недійсне посилання + Будь ласка, перевірте, чи правильне посилання SimpleX. + Адреса сервера несумісна з налаштуваннями мережі: %1$s. + Помилка підключення до сервера переадресації %1$s. Будь ласка, спробуйте пізніше. + Адреса сервера переадресації несумісна з налаштуваннями мережі: %1$s. + Версія сервера переадресації несумісна з налаштуваннями мережі: %1$s. + Помилка приватного маршрутизації + Версія сервера призначення %1$s несумісна з сервером переадресації %2$s. + Будь ласка, спробуйте пізніше. + Помилка сервера файлів: %1$s + Учасник неактивний + Повідомлення переслано + Поки що немає прямого з\'єднання, повідомлення пересилається адміністратором. + Вибрано %d + Повідомлення + Помилка файлу + Тимчасова помилка файлу + Контакт буде видалено - це неможливо скасувати! + Видалити лише розмову + Архівовані контакти + Нагадати пізніше + Стабільна + Новий досвід чату 🎉 + Адреса сервера + Підтвердити видалення контакту? + підключитися + Сервер переадресації %1$s не зміг з\'єднатися з цільовим сервером %2$s. Будь ласка, спробуйте пізніше. + Повідомлення може бути доставлено пізніше, якщо учасник стане активним. + Нічого не вибрано + Відкрити налаштування сервера + Версія сервера несумісна з вашим додатком: %1$s. + Повідомлення будуть позначені як модеровані для всіх учасників. + Оновлювати додаток автоматично + Не вдалося надіслати повідомлення + Вибрані налаштування чату забороняють це повідомлення. + Статус файлу: %s + Статус повідомлення: %s + Нове повідомлення + Створити + Запросити + Перемикнути список чатів: + Ви можете змінити це в налаштуваннях зовнішнього вигляду. + Статус повідомлення + Архівувати контакти, щоб поговорити пізніше. + Відтворити зі списку чатів. + Нові опції медіа + Розмиття для кращої конфіденційності. + Збільшити розмір шрифту. + Він захищає вашу IP-адресу та з\'єднання. + Перевірити, чи є оновлення + Завантажуйте нові версії з GitHub. + Дозволити дзвінки? + Контакт видалено. + TCP з\'єднання + Масштабування + Розмір шрифту + Будь ласка, попросіть вашого контакту увімкнути дзвінки. + Зберегти і перепідключитися + Видалити до 20 повідомлень за один раз. + Доступна панель інструментів чату + Користуватися застосунком однією рукою. + З\'єднуйтеся з друзями швидше. + Керуйте своєю мережею + Завантажено + Помилка скидання статистики + Перепідключити сервери? + Надіслані повідомлення + Статистика серверів буде скинута — це не можна буде відмінити! + Статус з\'єднання та серверів. + Докладна статистика + Отримати помилки + Надіслано безпосередньо + Надіслано загалом + Надіслано через проксі + Сервер SMP + Починаючи з %s. + Отримані повідомлення + Отримано загалом + Розмити медіа + Середній + Вимкнено + Слабке + Сильна + вимкнено + неактивний + Інформація про сервери + Файли + Ніякої інформації, спробуйте перезавантажити + Активні з\'єднання + Підключено + Підключені сервери + Підключення + Всього + Сесії передачі даних + Ви не підключені до цих серверів. Для доставки повідомлень до них використовується приватна маршрутизація. + Поточний профіль + Деталі + Помилки + Отримання повідомлення + Повідомлення отримано + В очікуванні + Проксіровані сервери + Показувати інформацію для + Починаючи з %s. +\nВсі дані зберігаються лише на вашому пристрою. + Перепідключити сервер для примусової доставки повідомлень. Це використовує додатковий трафік. + Скинути всю статистику + Скинути всю статистику? + Помилка + Помилка повторного підключення до сервера + Помилка повторного підключення до серверів + Перепідключити всі підключені сервери для примусової доставки повідомлень. Це використовує додатковий трафік. + Перепідключити всі сервери + Перепідключити сервер? + Помилки підтвердження + дублікати + інші помилки + Підтверджено + Підключення + помилки розшифрування + Видалено + Помилки підписки + Помилки видалення + Розмір + Підписано + Підписки проігноровані + Завантажені файли + Помилки завантаження + Завантажені файли + Помилки завантаження + Частини видалені + Частини завантажено + Частини завантажено + Ця посилання було використано на іншому мобільному пристрої, створіть нове посилання на комп\'ютері. + Помилка копіювання + Будь ласка, перевірте, що мобільний пристрій і комп\'ютер підключені до однієї локальної мережі, і що брандмауер комп\'ютера дозволяє з\'єднання. +\nБудь ласка, повідомте про будь-які інші проблеми розробникам. + Налаштування + відеодзвінок + повідомлення + дзвінок + Зберегти розмову + відкрити + пошук + Ви все ще можете переглядати розмову з %1$s у списку чатів. + Контакт видалено! + Розмову видалено! + Видалити без сповіщення + Ви можете надсилати повідомлення %1$s з архівованих контактів. + Налаштовані SMP сервери + Ніяких відфільтрованих контактів + Інші SMP сервери + Ваші контакти + Інші XFTP сервери + Показати відсоток + Перевірити оновлення + Вимкнено + Завантаження оновлення додатку, не закривайте додаток + Завантажити %s (%s) + Відкрити розташування файлу + Пропустити цю версію + Доступна панель інструментів чату + Не можна зателефонувати контакту + Підключення до контакту, будь ласка, зачекайте або перевірте пізніше! + Дзвінки заборонені! + Вам необхідно дозволити контакту викликати вас, щоб ви могли самі їм дзвонити. + Надіслати повідомлення, щоб увімкнути дзвінки. + Не можна зателефонувати учаснику групи + Не можна надіслати повідомлення учаснику групи + спроби + XFTP сервер + Перепідключитися + Створено + закінчився + Захищений + інший + Проксірований + Надіслати помилки + Завершено + Всі профілі + Скинути + Завантажено + Видалити %d повідомлень учасників? + Повідомлення будуть позначені для видалення. Одержувач(і) зможуть розкрити ці повідомлення. + Вибрати + Повідомлення будуть видалені для всіх учасників. + Вставити посилання + Вимкнути + Щоб отримувати повідомлення про нові випуски, увімкніть періодичну перевірку стабільної або бета-версії. + Продовжити + База даних чату експортована + Медіа та файлові сервери + Сервери повідомлень + SOCKS проксі + Деякі файли не були експортовані + Ви можете переїхати експортовану базу даних. + Ви можете зберегти експортований архів. + Запросити + Оновлення додатку завантажено + Встановлено успішно + Встановити оновлення + Будь ласка, перезапустіть додаток. + Скинути всі підказки + Доступно оновлення: %s + Завантаження оновлення скасовано \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index d370a82310..7747722d5f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -567,4 +567,73 @@ Kết nối nhanh hơn với bạn bè. Cơ sở dữ liệu SimpleX Chat đã được xuất Lưu trữ các liên hệ để trò chuyện sau. + Ngắt kết nối máy tính? + Xóa tối đa 20 tin nhắn cùng một lúc. + Đã ngắt kết nối với lý do:%s + %s với lý do: %s]]> + Ngắt kết nối các thiết bị di động + Đã ngắt kết nối + Địa chỉ máy chủ đích của %1$s không tương thích với thiết lập máy chủ chuyển tiếp %2$s. + Phiên bản máy chủ đích của %1$s không tương thích với máy chủ chuyển tiếp %2$s. + Xóa mà không thông báo + Có thể tìm thấy qua mạng cục bộ + Xóa %d tin nhắn của các thành viên? + %d tháng + %dm + KHÔNG sử dụng định tuyến riêng tư. + Khám phá và tham gia nhóm + %dmth + Không gửi lịch sử đến các thành viên mới. + %d phút + KHÔNG gửi tin nhắn trực tiếp, kể cả khi máy chủ của bạn hoặc máy chủ đích không hỗ trợ định tuyến riêng tư. + %d tháng + %d phút + %d tin nhắn bị chặn + %d tin nhắn bị chặn bởi quản trị viên + %d tin nhắn được đánh dấu là đã xóa + Tên hiển thị không thể chứa khoảng trắng. + Khám phá qua mạng cục bộ + Tải về không thành công + Không tạo địa chỉ + Không hiển thị lại + Tải về tệp tin + Đang tải về kho lưu trữ + Hạ cấp và mở SimpleX Chat + Tải về + Không bật + Đã tải về + Đang tải về bản cập nhật ứng dụng, đừng đóng ứng dụng + Các tập tin đã tải về + Lỗi tải về + mã hóa đầu cuối + Tải xuống các phiên bản mới từ GitHub. + %d giây + %ds + Đang tải xuống chi tiết liên kết + Tên hiển thị trùng lặp! + %d tuần + %d tuần + %d giây + tin nhắn trùng lặp + %dw + cuộc gọi thoại mã hóa đầu cuối + các bản sao + Tải xuống %s (%s) + đã bật cho liên hệ + Tai nghe + Bật cho tất cả các nhóm + Chỉnh sửa hình ảnh + đã chỉnh sửa + Bật cho tất cả + đã bật + đã bật cho bạn + Đã bật cho + Cho phép truy cập camera + cuộc gọi video mã hóa đầu cuối + Cho phép nhận cuộc gọi từ màn hình khóa thông qua Cài đặt. + Cho phép + Chỉnh sửa hồ sơ nhóm + Cho phép xóa tin nhắn tự động? + Chỉnh sửa + Thư điện tử \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index fb821773d6..9e038995b3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -2066,4 +2066,6 @@ 邀请 新的聊天体验 🎉 新消息 + 请检查 Simple X 链接是否正确。 + 无效链接 \ No newline at end of file From 82c4d77c739b8f4283acfc9b4ba2b9ef9b92cf00 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 14 Aug 2024 23:10:06 +0100 Subject: [PATCH 009/440] 6.0.1: ios 233, android 232, desktop 62 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 80 +++++++++++----------- apps/multiplatform/gradle.properties | 8 +-- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 4157ad3e5f..0351c1c1ce 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -219,11 +219,11 @@ E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; - E5E218492C6813750013B4C6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218442C6813750013B4C6 /* libgmpxx.a */; }; - E5E2184A2C6813750013B4C6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218452C6813750013B4C6 /* libffi.a */; }; - E5E2184B2C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218462C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a */; }; - E5E2184C2C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218472C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a */; }; - E5E2184D2C6813750013B4C6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218482C6813750013B4C6 /* libgmp.a */; }; + E5E218532C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */; }; + E5E218542C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */; }; + E5E218552C6D4C0F0013B4C6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218502C6D4C0F0013B4C6 /* libgmp.a */; }; + E5E218562C6D4C0F0013B4C6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218512C6D4C0F0013B4C6 /* libffi.a */; }; + E5E218572C6D4C0F0013B4C6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -604,11 +604,11 @@ E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; - E5E218442C6813750013B4C6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E5E218452C6813750013B4C6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E5E218462C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a"; sourceTree = ""; }; - E5E218472C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a"; sourceTree = ""; }; - E5E218482C6813750013B4C6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a"; sourceTree = ""; }; + E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a"; sourceTree = ""; }; + E5E218502C6D4C0F0013B4C6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5E218512C6D4C0F0013B4C6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -647,14 +647,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E5E218492C6813750013B4C6 /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E5E2184D2C6813750013B4C6 /* libgmp.a in Frameworks */, - E5E2184C2C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a in Frameworks */, + E5E218542C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E5E2184B2C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a in Frameworks */, - E5E2184A2C6813750013B4C6 /* libffi.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, + E5E218572C6D4C0F0013B4C6 /* libgmpxx.a in Frameworks */, + E5E218562C6D4C0F0013B4C6 /* libffi.a in Frameworks */, + E5E218552C6D4C0F0013B4C6 /* libgmp.a in Frameworks */, + E5E218532C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -731,11 +731,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E5E218452C6813750013B4C6 /* libffi.a */, - E5E218482C6813750013B4C6 /* libgmp.a */, - E5E218442C6813750013B4C6 /* libgmpxx.a */, - E5E218462C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a */, - E5E218472C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a */, + E5E218512C6D4C0F0013B4C6 /* libffi.a */, + E5E218502C6D4C0F0013B4C6 /* libgmp.a */, + E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */, + E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */, + E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */, ); path = Libraries; sourceTree = ""; @@ -1883,7 +1883,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1908,7 +1908,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1932,7 +1932,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1957,7 +1957,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1973,11 +1973,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1993,11 +1993,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2018,7 +2018,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2033,7 +2033,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2055,7 +2055,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2070,7 +2070,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2092,7 +2092,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2118,7 +2118,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2143,7 +2143,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2169,7 +2169,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2194,7 +2194,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2209,7 +2209,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2228,7 +2228,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 232; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2243,7 +2243,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 6.0.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 144a39cd46..35b9d90fc4 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.0 -android.version_code=230 +android.version_name=6.0.1 +android.version_code=232 -desktop.version_name=6.0 -desktop.version_code=61 +desktop.version_name=6.0.1 +desktop.version_code=62 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 1d0d7bbd014cdd5671fa9a080ec439e1bfbc1ce8 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:43:57 +0400 Subject: [PATCH 010/440] core: batch send file descriptions (#4684) * core: batch send file descriptions * fix useMember * fix result interpretation * remove comment * refactor --------- Co-authored-by: Evgeny Poberezkin --- src/Simplex/Chat.hs | 70 ++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 54b877dc6a..2d3ed3ca84 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -3902,15 +3902,22 @@ processAgentMsgSndFile _corrId aFileId msg = do case (rfds, sfts, d, cInfo) of (rfd : extraRFDs, sft : _, SMDSnd, DirectChat ct) -> do withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) - msgDeliveryId <- sendFileDescription sft rfd sharedMsgId $ sendDirectContactMessage user ct - withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId + conn@Connection {connId} <- liftEither $ contactSendConn_ ct + sendFileDescriptions (ConnectionId connId) ((conn, sft, fileDescrText rfd) :| []) sharedMsgId >>= \case + Just rs -> case L.last rs of + Right ([msgDeliveryId], _) -> + withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId + Right (deliveryIds, _) -> toView $ CRChatError (Just user) $ ChatError $ CEInternalError $ "SFDONE, sendFileDescriptions: expected 1 delivery id, got " <> show (length deliveryIds) + Left e -> toView $ CRChatError (Just user) e + Nothing -> toView $ CRChatError (Just user) $ ChatError $ CEInternalError "SFDONE, sendFileDescriptions: expected at least 1 result" lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do ms <- withStore' $ \db -> getGroupMembers db vr user g - let rfdsMemberFTs = zip rfds $ memberFTs ms + let rfdsMemberFTs = zipWith (\rfd (conn, sft) -> (conn, sft, fileDescrText rfd)) rfds (memberFTs ms) extraRFDs = drop (length rfdsMemberFTs) rfds withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) - forM_ rfdsMemberFTs $ \mt -> sendToMember mt `catchChatError` (toView . CRChatError (Just user)) + forM_ (L.nonEmpty rfdsMemberFTs) $ \rfdsMemberFTs' -> + sendFileDescriptions (GroupId groupId) rfdsMemberFTs' sharedMsgId ci' <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId CIFSSndComplete getChatItemByFileId db vr user fileId @@ -3922,15 +3929,12 @@ processAgentMsgSndFile _corrId aFileId msg = do where mConns' = mapMaybe useMember ms sfts' = mapMaybe (\sft@SndFileTransfer {groupMemberId} -> (,sft) <$> groupMemberId) sfts + -- Should match memberSendAction logic useMember GroupMember {groupMemberId, activeConn = Just conn@Connection {connStatus}} - | (connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn) = Just (groupMemberId, conn) + | (connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn) && not (connInactive conn) = + Just (groupMemberId, conn) | otherwise = Nothing useMember _ = Nothing - sendToMember :: (ValidFileDescription 'FRecipient, (Connection, SndFileTransfer)) -> CM () - sendToMember (rfd, (conn, sft)) = - void $ sendFileDescription sft rfd sharedMsgId $ \msg' -> do - (sndMsg, msgDeliveryId, _) <- sendDirectMemberMessage conn msg' groupId - pure (sndMsg, msgDeliveryId) _ -> pure () _ -> pure () -- TODO error? SFWARN e -> do @@ -3945,20 +3949,27 @@ processAgentMsgSndFile _corrId aFileId msg = do where fileDescrText :: FilePartyI p => ValidFileDescription p -> T.Text fileDescrText = safeDecodeUtf8 . strEncode - sendFileDescription :: SndFileTransfer -> ValidFileDescription 'FRecipient -> SharedMsgId -> (ChatMsgEvent 'Json -> CM (SndMessage, Int64)) -> CM Int64 - sendFileDescription sft rfd msgId sendMsg = do - let rfdText = fileDescrText rfd - withStore' $ \db -> updateSndFTDescrXFTP db user sft rfdText - parts <- splitFileDescr rfdText - loopSend parts + sendFileDescriptions :: ConnOrGroupId -> NonEmpty (Connection, SndFileTransfer, RcvFileDescrText) -> SharedMsgId -> CM (Maybe (NonEmpty (Either ChatError ([Int64], PQEncryption)))) + sendFileDescriptions connOrGroupId connsTransfersDescrs sharedMsgId = do + lift . void . withStoreBatch' $ \db -> L.map (\(_, sft, rfdText) -> updateSndFTDescrXFTP db user sft rfdText) connsTransfersDescrs + partSize <- asks $ xftpDescrPartSize . config + let connsIdsEvts = connDescrEvents partSize + sndMsgs_ <- lift $ createSndMessages $ L.map snd connsIdsEvts + let (errs, msgReqs) = partitionEithers . L.toList $ L.zipWith (fmap . toMsgReq) connsIdsEvts sndMsgs_ + delivered <- mapM deliverMessages (L.nonEmpty msgReqs) + let errs' = errs <> maybe [] (lefts . L.toList) delivered + unless (null errs') $ toView $ CRChatErrors (Just user) errs' + pure delivered where - -- returns msgDeliveryId of the last file description message - loopSend :: NonEmpty FileDescr -> CM Int64 - loopSend (fileDescr :| fds) = do - (_, msgDeliveryId) <- sendMsg $ XMsgFileDescr {msgId, fileDescr} - case L.nonEmpty fds of - Just fds' -> loopSend fds' - Nothing -> pure msgDeliveryId + connDescrEvents :: Int -> NonEmpty (Connection, (ConnOrGroupId, ChatMsgEvent 'Json)) + connDescrEvents partSize = L.fromList $ concatMap splitText (L.toList connsTransfersDescrs) + where + splitText :: (Connection, SndFileTransfer, RcvFileDescrText) -> [(Connection, (ConnOrGroupId, ChatMsgEvent 'Json))] + splitText (conn, _, rfdText) = + map (\fileDescr -> (conn, (connOrGroupId, XMsgFileDescr {msgId = sharedMsgId, fileDescr}))) (L.toList $ splitFileDescr partSize rfdText) + toMsgReq :: (Connection, (ConnOrGroupId, ChatMsgEvent 'Json)) -> SndMessage -> ChatMsgReq + toMsgReq (conn, _) SndMessage {msgId, msgBody} = + (conn, MsgFlags {notification = hasNotification XMsgFileDescr_}, msgBody, [msgId]) sendFileError :: FileError -> Text -> VersionRangeChat -> FileTransferMeta -> CM () sendFileError ferr err vr ft = do logError $ "Sent file error: " <> err @@ -3980,18 +3991,16 @@ agentFileError = \case SMP.TRANSPORT TEVersion -> srvErr SrvErrVersion e -> srvErr . SrvErrOther $ tshow e -splitFileDescr :: RcvFileDescrText -> CM (NonEmpty FileDescr) -splitFileDescr rfdText = do - partSize <- asks $ xftpDescrPartSize . config - pure $ splitParts 1 partSize rfdText +splitFileDescr :: Int -> RcvFileDescrText -> NonEmpty FileDescr +splitFileDescr partSize rfdText = splitParts 1 rfdText where - splitParts partNo partSize remText = + splitParts partNo remText = let (part, rest) = T.splitAt partSize remText complete = T.null rest fileDescr = FileDescr {fileDescrText = part, fileDescrPartNo = partNo, fileDescrComplete = complete} in if complete then fileDescr :| [] - else fileDescr <| splitParts (partNo + 1) partSize rest + else fileDescr <| splitParts (partNo + 1) rest processAgentMsgRcvFile :: ACorrId -> RcvFileId -> AEvent 'AERcvFile -> CM () processAgentMsgRcvFile _corrId aFileId msg = do @@ -4573,7 +4582,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xMsgNewChatMsg = ChatMessage {chatVRange = senderVRange, msgId = itemSharedMsgId, chatMsgEvent = XMsgNew msgContainer} fileDescrEvents <- case (snd <$> fInvDescr_, itemSharedMsgId) of (Just fileDescrText, Just msgId) -> do - parts <- splitFileDescr fileDescrText + partSize <- asks $ xftpDescrPartSize . config + let parts = splitFileDescr partSize fileDescrText pure . toList $ L.map (XMsgFileDescr msgId) parts _ -> pure [] let fileDescrChatMsgs = map (ChatMessage senderVRange Nothing) fileDescrEvents From c159c2ede345aa041337c0da16fa241cb5af2e3c Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 15 Aug 2024 13:15:24 +0300 Subject: [PATCH 011/440] ios: fix pending connection sheet styling when opened via icon; fix tappable area of pending connections and contact requests (#4694) * fix new contact sheet styling * fix contact request tappable area --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- apps/ios/Shared/Views/ChatList/ChatListNavLink.swift | 2 ++ apps/ios/Shared/Views/ChatList/ContactConnectionView.swift | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index c8424ef332..d2a93b9bd1 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -355,6 +355,7 @@ struct ChatListNavLink: View { .tint(.red) } .frame(height: dynamicRowHeight) + .contentShape(Rectangle()) .onTapGesture { showContactRequestDialog = true } .confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) { Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } } @@ -392,6 +393,7 @@ struct ChatListNavLink: View { } } } + .contentShape(Rectangle()) .onTapGesture { showContactConnectionInfo = true } diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionView.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionView.swift index 3c7bf97af9..f5156d86b8 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionView.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionView.swift @@ -16,7 +16,6 @@ struct ContactConnectionView: View { @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize @State private var localAlias = "" @FocusState private var aliasTextFieldFocused: Bool - @State private var showContactConnectionInfo = false var body: some View { if case let .contactConnection(conn) = chat.chatInfo { @@ -32,7 +31,6 @@ struct ContactConnectionView: View { .scaledToFill() .frame(width: 48, height: 48) .foregroundColor(Color(uiColor: .tertiarySystemGroupedBackground).asAnotherColorFromSecondaryVariant(theme)) - .onTapGesture { showContactConnectionInfo = true } } .frame(width: 63, height: 63) .padding(.leading, 4) @@ -72,9 +70,6 @@ struct ContactConnectionView: View { Spacer() } .frame(maxHeight: .infinity) - .appSheet(isPresented: $showContactConnectionInfo) { - ContactConnectionInfo(contactConnection: contactConnection) - } } } } From 3c694e28417cd7feb62d42e6e2883715a5d219a6 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 15 Aug 2024 20:21:22 +0300 Subject: [PATCH 012/440] upgrade code scanner (#4650) Co-authored-by: Evgeny Poberezkin --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 0351c1c1ce..32f68f1ca3 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -2320,7 +2320,7 @@ repositoryURL = "https://github.com/twostraws/CodeScanner"; requirement = { kind = exactVersion; - version = 2.1.1; + version = 2.5.0; }; }; 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */ = { diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d3e61c88f9..c8623a95cb 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/twostraws/CodeScanner", "state" : { - "revision" : "c27a66149b7483fe42e2ec6aad61d5c3fffe522d", - "version" : "2.1.1" + "revision" : "34da57fb63b47add20de8a85da58191523ccce57", + "version" : "2.5.0" } }, { @@ -23,7 +23,6 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { - "branch" : "master", "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" } }, From c823a4fa6c918ea5a88a91a499686594201115e4 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 15 Aug 2024 20:43:30 +0300 Subject: [PATCH 013/440] extend chat view material behind keyboard (#4698) Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/AppDelegate.swift | 19 ----------------- apps/ios/Shared/Model/ChatModel.swift | 2 -- apps/ios/Shared/Views/Chat/ChatView.swift | 1 - .../Chat/ComposeMessage/ComposeView.swift | 6 +++++- .../Views/Chat/Group/GroupChatInfoView.swift | 1 - .../Views/Helpers/KeyboardPadding.swift | 21 ------------------- .../Shared/Views/NewChat/AddGroupView.swift | 2 +- .../Views/Onboarding/CreateProfile.swift | 2 -- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ---- 9 files changed, 6 insertions(+), 52 deletions(-) delete mode 100644 apps/ios/Shared/Views/Helpers/KeyboardPadding.swift diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index d52c950d81..dd91d4af68 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -15,31 +15,12 @@ class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { logger.debug("AppDelegate: didFinishLaunchingWithOptions") application.registerForRemoteNotifications() - if #available(iOS 17.0, *) { trackKeyboard() } NotificationCenter.default.addObserver(self, selector: #selector(pasteboardChanged), name: UIPasteboard.changedNotification, object: nil) removePasscodesIfReinstalled() prepareForLaunch() return true } - @available(iOS 17.0, *) - private func trackKeyboard() { - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) - } - - @available(iOS 17.0, *) - @objc func keyboardWillShow(_ notification: Notification) { - if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue { - ChatModel.shared.keyboardHeight = keyboardFrame.cgRectValue.height - } - } - - @available(iOS 17.0, *) - @objc func keyboardWillHide(_ notification: Notification) { - ChatModel.shared.keyboardHeight = 0 - } - @objc func pasteboardChanged() { ChatModel.shared.pasteboardHasStrings = UIPasteboard.general.hasStrings } diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 347ffa7a8f..4b391b727e 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -183,8 +183,6 @@ final class ChatModel: ObservableObject { @Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source @Published var draft: ComposeState? @Published var draftChatId: String? - // tracks keyboard height via subscription in AppDelegate - @Published var keyboardHeight: CGFloat = 0 @Published var pasteboardHasStrings: Bool = UIPasteboard.general.hasStrings @Published var networkInfo = UserNetworkInfo(networkType: .other, online: true) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 7bd13a3fa7..655dd8aaed 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -53,7 +53,6 @@ struct ChatView: View { if #available(iOS 16.0, *) { viewBody .scrollDismissesKeyboard(.immediately) - .keyboardPadding() .toolbarBackground(.hidden, for: .navigationBar) } else { viewBody diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index e059d72e98..78cae78cf5 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -382,7 +382,11 @@ struct ComposeView: View { } } } - .background(ToolbarMaterial.material(toolbarMaterial)) + .background { + Color.clear + .overlay(ToolbarMaterial.material(toolbarMaterial)) + .ignoresSafeArea(.all, edges: .bottom) + } .onChange(of: composeState.message) { msg in if composeState.linkPreviewAllowed { if msg.count > 0 { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 50eb883e92..9385633060 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -185,7 +185,6 @@ struct GroupChatInfoView: View { logger.error("GroupChatInfoView apiGetGroupLink: \(responseError(error))") } } - .keyboardPadding() } private func groupInfoHeader() -> some View { diff --git a/apps/ios/Shared/Views/Helpers/KeyboardPadding.swift b/apps/ios/Shared/Views/Helpers/KeyboardPadding.swift deleted file mode 100644 index 45d766ddfd..0000000000 --- a/apps/ios/Shared/Views/Helpers/KeyboardPadding.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// KeyboardPadding.swift -// SimpleX (iOS) -// -// Created by Evgeny on 10/07/2023. -// Copyright © 2023 SimpleX Chat. All rights reserved. -// - -import SwiftUI - -extension View { - @ViewBuilder func keyboardPadding() -> some View { - if #available(iOS 17.0, *) { - GeometryReader { g in - self.padding(.bottom, max(0, ChatModel.shared.keyboardHeight - g.safeAreaInsets.bottom)) - } - } else { - self - } - } -} diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 113691abb3..33c187b64b 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -59,7 +59,7 @@ struct AddGroupView: View { .navigationBarTitle("Group link") } } else { - createGroupView().keyboardPadding() + createGroupView() } } diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index a62f609833..487f4ccdeb 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -77,7 +77,6 @@ struct CreateProfile: View { focusDisplayName = true } } - .keyboardPadding() } } @@ -128,7 +127,6 @@ struct CreateFirstProfile: View { } .padding() .frame(maxWidth: .infinity, alignment: .leading) - .keyboardPadding() } func onboardingButtons() -> some View { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 32f68f1ca3..46cf768a9f 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -135,7 +135,6 @@ 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; }; 5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE227DE9246000BD591 /* ComposeView.swift */; }; 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; }; - 5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; }; 5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; }; 5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; }; 5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; }; @@ -474,7 +473,6 @@ 5CE6C7B42AAB1527007F345C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 5CEACCE227DE9246000BD591 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = ""; }; 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = ""; }; - 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = ""; }; 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = ""; }; 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = ""; }; 5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = ""; }; @@ -786,7 +784,6 @@ 18415DAAAD1ADBEDB0EDA852 /* VideoPlayerView.swift */, 64466DCB29FFE3E800E3D48D /* MailView.swift */, 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */, - 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */, 8C7F8F0D2C19C0C100D16888 /* ViewModifiers.swift */, 8C74C3ED2C1B942300039E77 /* ChatWallpaper.swift */, 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */, @@ -1435,7 +1432,6 @@ 646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */, 8C74C3EA2C1B90AF00039E77 /* ThemeManager.swift in Sources */, 5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */, - 5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */, 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */, 5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */, 8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */, From b0e0b0beb8e8266fcaecac814976c5b8779edee2 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 15 Aug 2024 22:08:51 +0300 Subject: [PATCH 014/440] remove text slection context menu from chat item (#4699) --- apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 258e2e34dc..313ec0d419 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -294,7 +294,6 @@ struct FramedItemView: View { .padding(.horizontal, 12) .overlay(DetermineWidth()) .frame(minWidth: 0, alignment: .leading) - .textSelection(.enabled) if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth { v.frame(maxWidth: mediaWidth, alignment: .leading) From 3740805125120b23cbb24c675cd6d670f3abf6a7 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 16 Aug 2024 11:55:22 +0100 Subject: [PATCH 015/440] core: batch connection subscription transactions (#4701) * core: batch connection subscription transactions * simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 23963491b9..642831aeb8 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 0dd52dc69f197a86f2f03e47b339bf3a94a596a3 + tag: f229e135e33ec5ee1f6d0978bd903d84b0b60efa source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 246969b98d..05253078f1 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."0dd52dc69f197a86f2f03e47b339bf3a94a596a3" = "1dzfpk9bq8y885i2z419k79f2nyagrqkqh8rw3gzmf1hy5w3fbjs"; + "https://github.com/simplex-chat/simplexmq.git"."f229e135e33ec5ee1f6d0978bd903d84b0b60efa" = "0p4n4bghg24a98py9mij6s24fvsix4a73lwkg6skqf4rx8zp392v"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 3a0921c0936fa4fb3ffdf41c054b012f8574a594 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Sat, 17 Aug 2024 15:26:56 +0300 Subject: [PATCH 016/440] ios: asychronous subscription updates (#4707) * ios: asychronous subscription updates * cleanup --- apps/ios/Shared/Model/SimpleXAPI.swift | 4 +- .../Shared/Views/ChatList/ChatListView.swift | 39 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index df76409316..ed2c8e23d6 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1420,9 +1420,9 @@ func apiGetVersion() throws -> CoreVersionInfo { throw r } -func getAgentSubsTotal() throws -> (SMPServerSubs, Bool) { +func getAgentSubsTotal() async throws -> (SMPServerSubs, Bool) { let userId = try currentUserId("getAgentSubsTotal") - let r = chatSendCmdSync(.getAgentSubsTotal(userId: userId), log: false) + let r = await chatSendCmd(.getAgentSubsTotal(userId: userId)) if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) } logger.error("getAgentSubsTotal error: \(String(describing: r))") throw r diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 8c9d090890..ec49c7b4d1 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -324,7 +324,7 @@ struct ChatListView: View { struct SubsStatusIndicator: View { @State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs @State private var hasSess: Bool = false - @State private var timer: Timer? = nil + @State private var task: Task? @State private var showServersSummary = false @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false @@ -343,10 +343,10 @@ struct SubsStatusIndicator: View { } .disabled(ChatModel.shared.chatRunning != true) .onAppear { - startTimer() + startTask() } .onDisappear { - stopTimer() + stopTask() } .appSheet(isPresented: $showServersSummary) { ServersSummaryView() @@ -354,25 +354,28 @@ struct SubsStatusIndicator: View { } } - private func startTimer() { - timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in - if AppChatState.shared.value == .active { - getSubsTotal() + private func startTask() { + task = Task { + while !Task.isCancelled { + if AppChatState.shared.value == .active { + do { + let (subs, hasSess) = try await getAgentSubsTotal() + await MainActor.run { + self.subs = subs + self.hasSess = hasSess + } + } catch let error { + logger.error("getSubsTotal error: \(responseError(error))") + } + } + try? await Task.sleep(nanoseconds: 1_000_000_000) // Sleep for 1 second } } } - func stopTimer() { - timer?.invalidate() - timer = nil - } - - private func getSubsTotal() { - do { - (subs, hasSess) = try getAgentSubsTotal() - } catch let error { - logger.error("getSubsTotal error: \(responseError(error))") - } + func stopTask() { + task?.cancel() + task = nil } } From 3b98032371eef3ae1bf9bc1fad50a67dae0c2c87 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Sat, 17 Aug 2024 17:52:35 +0300 Subject: [PATCH 017/440] ios: filter agent subscriptions from chat console (#4708) --- apps/ios/Shared/Model/SimpleXAPI.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ed2c8e23d6..00ac88b071 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -112,9 +112,9 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = return resp } -func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil) async -> ChatResponse { +func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil, log: Bool = true) async -> ChatResponse { await withCheckedContinuation { cont in - cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl)) + cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl, log: log)) } } @@ -1422,7 +1422,7 @@ func apiGetVersion() throws -> CoreVersionInfo { func getAgentSubsTotal() async throws -> (SMPServerSubs, Bool) { let userId = try currentUserId("getAgentSubsTotal") - let r = await chatSendCmd(.getAgentSubsTotal(userId: userId)) + let r = await chatSendCmd(.getAgentSubsTotal(userId: userId), log: false) if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) } logger.error("getAgentSubsTotal error: \(String(describing: r))") throw r From 75a468434c43370101e9c66166456d5d165d4cce Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 18 Aug 2024 23:00:34 +0100 Subject: [PATCH 018/440] core: only start message delivery workers when there are pending messages (#4713) * core: use threads instead of async (reduce memory) * simplexmq * core: only start message delivery workers when there are pending messages (#4714) * core: only start message delivery workers when there are pending messages * update tls * simplexmq * update ios, simplexmq * simplexmq --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 14 ++++---- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 46cf768a9f..5d525004b9 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,15 +214,15 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; + E58C91472C72458500EADB92 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91422C72458500EADB92 /* libffi.a */; }; + E58C91482C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */; }; + E58C91492C72458500EADB92 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91442C72458500EADB92 /* libgmpxx.a */; }; + E58C914A2C72458500EADB92 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91452C72458500EADB92 /* libgmp.a */; }; + E58C914B2C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; - E5E218532C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */; }; - E5E218542C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */; }; - E5E218552C6D4C0F0013B4C6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218502C6D4C0F0013B4C6 /* libgmp.a */; }; - E5E218562C6D4C0F0013B4C6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218512C6D4C0F0013B4C6 /* libffi.a */; }; - E5E218572C6D4C0F0013B4C6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -550,6 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; + E58C91422C72458500EADB92 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a"; sourceTree = ""; }; + E58C91442C72458500EADB92 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E58C91452C72458500EADB92 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a"; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -602,11 +607,6 @@ E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; - E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a"; sourceTree = ""; }; - E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a"; sourceTree = ""; }; - E5E218502C6D4C0F0013B4C6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E5E218512C6D4C0F0013B4C6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E58C91492C72458500EADB92 /* libgmpxx.a in Frameworks */, + E58C914B2C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a in Frameworks */, + E58C914A2C72458500EADB92 /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E5E218542C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E58C91482C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E5E218572C6D4C0F0013B4C6 /* libgmpxx.a in Frameworks */, - E5E218562C6D4C0F0013B4C6 /* libffi.a in Frameworks */, - E5E218552C6D4C0F0013B4C6 /* libgmp.a in Frameworks */, - E5E218532C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a in Frameworks */, + E58C91472C72458500EADB92 /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E5E218512C6D4C0F0013B4C6 /* libffi.a */, - E5E218502C6D4C0F0013B4C6 /* libgmp.a */, - E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */, - E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */, - E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */, + E58C91422C72458500EADB92 /* libffi.a */, + E58C91452C72458500EADB92 /* libgmp.a */, + E58C91442C72458500EADB92 /* libgmpxx.a */, + E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */, + E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */, ); path = Libraries; sourceTree = ""; diff --git a/cabal.project b/cabal.project index 642831aeb8..05f5935554 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: f229e135e33ec5ee1f6d0978bd903d84b0b60efa + tag: 571d148bdf9e29403b9bcec6f7daeff815a4db38 source-repository-package type: git diff --git a/package.yaml b/package.yaml index 80994a6a59..2455d4284e 100644 --- a/package.yaml +++ b/package.yaml @@ -45,7 +45,7 @@ dependencies: - stm == 2.5.* - terminal == 0.2.* - time == 1.12.* - - tls >= 1.7.0 && < 1.8 + - tls >= 1.9.0 && < 1.10 - unliftio == 0.2.* - unliftio-core == 0.2.* - zip == 2.0.* diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 05253078f1..2802bfda6e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."f229e135e33ec5ee1f6d0978bd903d84b0b60efa" = "0p4n4bghg24a98py9mij6s24fvsix4a73lwkg6skqf4rx8zp392v"; + "https://github.com/simplex-chat/simplexmq.git"."571d148bdf9e29403b9bcec6f7daeff815a4db38" = "1p7m7i4wdyvd2wypm04cx4w960vnyqa9wkfnlpv72cz83k0xirxf"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 4566048091..0f1564f618 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -226,7 +226,7 @@ library , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* - , tls >=1.7.0 && <1.8 + , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* , zip ==2.0.* @@ -289,7 +289,7 @@ executable simplex-bot , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* - , tls >=1.7.0 && <1.8 + , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* , zip ==2.0.* @@ -352,7 +352,7 @@ executable simplex-bot-advanced , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* - , tls >=1.7.0 && <1.8 + , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* , zip ==2.0.* @@ -418,7 +418,7 @@ executable simplex-broadcast-bot , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* - , tls >=1.7.0 && <1.8 + , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* , zip ==2.0.* @@ -482,7 +482,7 @@ executable simplex-chat , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* - , tls >=1.7.0 && <1.8 + , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* , websockets ==0.12.* @@ -552,7 +552,7 @@ executable simplex-directory-service , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* - , tls >=1.7.0 && <1.8 + , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* , zip ==2.0.* @@ -652,7 +652,7 @@ test-suite simplex-chat-test , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* - , tls >=1.7.0 && <1.8 + , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* , zip ==2.0.* From 885aa9cfa55a7ba66ff9318f40022154b9206c0c Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:43:54 +0000 Subject: [PATCH 019/440] android, desktop: scrolling moves title to app bar (#4703) * android, desktop: scrolling moves title to app bar * one place should be without padding * scroll related changes for both platforms * adapt code to universal ColumnWithScrollBar * show in center * small adjustments * new chat sheet fix * divider + mix background color for desktop * coerce * different transition * desktop title starts from left * host starts from left too * different coefficient * settings title --- .../common/platform/Resources.android.kt | 3 + .../platform/ScrollableColumn.android.kt | 62 ++++++- .../kotlin/chat/simplex/common/App.kt | 2 + .../chat/simplex/common/platform/Platform.kt | 3 - .../chat/simplex/common/platform/Resources.kt | 3 + .../common/platform/ScrollableColumn.kt | 6 +- .../chat/simplex/common/views/TerminalView.kt | 8 +- .../common/views/chat/ChatItemInfoView.kt | 4 - .../simplex/common/views/chat/ChatView.kt | 38 ++-- .../views/chat/group/GroupChatInfoView.kt | 1 - .../common/views/chatlist/ChatListView.kt | 9 +- .../views/chatlist/ServersSummaryView.kt | 32 ++-- .../views/database/DatabaseEncryptionView.kt | 165 +++++++++--------- .../common/views/helpers/CloseSheetBar.kt | 86 +++++++-- .../common/views/helpers/CollapsingAppBar.kt | 44 +++++ .../simplex/common/views/helpers/ModalView.kt | 23 ++- .../views/migration/MigrateFromDevice.kt | 14 +- .../common/views/migration/MigrateToDevice.kt | 11 +- .../common/views/newchat/NewChatSheet.kt | 14 +- .../common/views/newchat/NewChatView.kt | 118 ++++++------- .../common/views/onboarding/HowItWorks.kt | 7 +- .../usersettings/SetDeliveryReceiptsView.kt | 39 ++--- .../common/views/usersettings/SettingsView.kt | 134 +++++++------- .../usersettings/UserAddressLearnMore.kt | 9 +- .../views/usersettings/UserAddressView.kt | 2 +- .../common/platform/Resources.desktop.kt | 3 + .../platform/ScrollableColumn.desktop.kt | 129 +++++++++----- .../kotlin/chat/simplex/desktop/Main.kt | 32 ---- 28 files changed, 583 insertions(+), 418 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt index 91d19759ea..73c920b940 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt @@ -52,6 +52,9 @@ actual fun windowOrientation(): WindowOrientation = when (mainActivity.get()?.re @Composable actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp +@Composable +actual fun windowHeight(): Dp = LocalConfiguration.current.screenHeightDp.dp + actual fun desktopExpandWindowToWidth(width: Dp) {} actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt index 199e719703..6851970b81 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt @@ -4,14 +4,21 @@ import androidx.compose.foundation.* import androidx.compose.foundation.gestures.FlingBehavior import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import chat.simplex.common.views.helpers.* +import kotlinx.coroutines.flow.filter +import kotlin.math.absoluteValue @Composable actual fun LazyColumnWithScrollBar( modifier: Modifier, - state: LazyListState, + state: LazyListState?, contentPadding: PaddingValues, reverseLayout: Boolean, verticalArrangement: Arrangement.Vertical, @@ -20,7 +27,24 @@ actual fun LazyColumnWithScrollBar( userScrollEnabled: Boolean, content: LazyListScope.() -> Unit ) { - LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + val state = state ?: LocalAppBarHandler.current?.listState ?: rememberLazyListState() + val connection = LocalAppBarHandler.current?.connection + LaunchedEffect(Unit) { + snapshotFlow { state.firstVisibleItemScrollOffset } + .filter { state.firstVisibleItemIndex == 0 } + .collect { scrollPosition -> + val offset = connection?.appBarOffset + if (offset != null && (offset + scrollPosition).absoluteValue > 1) { + connection.appBarOffset = -scrollPosition.toFloat() +// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + } + } + } + if (connection != null) { + LazyColumn(modifier.nestedScroll(connection), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + } else { + LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + } } @Composable @@ -28,8 +52,34 @@ actual fun ColumnWithScrollBar( modifier: Modifier, verticalArrangement: Arrangement.Vertical, horizontalAlignment: Alignment.Horizontal, - state: ScrollState, - content: @Composable ColumnScope.() -> Unit + state: ScrollState?, + maxIntrinsicSize: Boolean, + content: @Composable() (ColumnScope.() -> Unit) ) { - Column(modifier.verticalScroll(rememberScrollState()), verticalArrangement, horizontalAlignment, content) + val state = state ?: LocalAppBarHandler.current?.scrollState ?: rememberScrollState() + val connection = LocalAppBarHandler.current?.connection + LaunchedEffect(Unit) { + snapshotFlow { state.value } + .collect { scrollPosition -> + val offset = connection?.appBarOffset + if (offset != null && (offset + scrollPosition).absoluteValue > 1) { + connection.appBarOffset = -scrollPosition.toFloat() +// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + } + } + } + if (connection != null) { + Column( + if (maxIntrinsicSize) { + modifier.nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max) + } else { + modifier.nestedScroll(connection).verticalScroll(state) + }, verticalArrangement, horizontalAlignment, content) + } else { + Column(if (maxIntrinsicSize) { + modifier.verticalScroll(state).height(IntrinsicSize.Max) + } else { + modifier.verticalScroll(state) + }, verticalArrangement, horizontalAlignment, content) + } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 1486fa8314..94ca307529 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView import chat.simplex.common.model.* @@ -50,6 +51,7 @@ data class SettingsViewState( @Composable fun AppScreen() { + AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() } SimpleXTheme { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index b48f7cebec..44fcddb54c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -28,9 +28,6 @@ interface PlatformInterface { fun androidRestartNetworkObserver() {} @Composable fun androidLockPortraitOrientation() {} suspend fun androidAskToAllowBackgroundCalls(): Boolean = true - @Composable fun desktopScrollBarComponents(): Triple, Modifier, MutableState> = remember { Triple(Animatable(0f), Modifier, mutableStateOf(Job())) } - @Composable fun desktopScrollBar(state: LazyListState, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean) {} - @Composable fun desktopScrollBar(state: ScrollState, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean) {} @Composable fun desktopShowAppUpdateNotice() {} } /** diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt index 8e45ded4f0..fd712624bd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt @@ -30,6 +30,9 @@ expect fun windowOrientation(): WindowOrientation @Composable expect fun windowWidth(): Dp +@Composable +expect fun windowHeight(): Dp + expect fun desktopExpandWindowToWidth(width: Dp) expect fun isRtl(text: CharSequence): Boolean diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt index d4fa2fe125..532bfddfcf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.unit.dp @Composable expect fun LazyColumnWithScrollBar( modifier: Modifier = Modifier, - state: LazyListState = rememberLazyListState(), + state: LazyListState? = null, contentPadding: PaddingValues = PaddingValues(0.dp), reverseLayout: Boolean = false, verticalArrangement: Arrangement.Vertical = @@ -29,6 +29,8 @@ expect fun ColumnWithScrollBar( modifier: Modifier = Modifier, verticalArrangement: Arrangement.Vertical = Arrangement.Top, horizontalAlignment: Alignment.Horizontal = Alignment.Start, - state: ScrollState = rememberScrollState(), + state: ScrollState? = null, + // set true when you want to show something in the center with respected .fillMaxSize() + maxIntrinsicSize: Boolean = false, content: @Composable ColumnScope.() -> Unit ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 259b7e2320..1afbb0bdcb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -125,19 +125,13 @@ fun TerminalLayout( } } -private var lazyListState = 0 to 0 - @Composable fun TerminalLog() { - val listState = rememberLazyListState(lazyListState.first, lazyListState.second) - DisposableEffect(Unit) { - onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } - } val reversedTerminalItems by remember { derivedStateOf { chatModel.terminalItems.value.asReversed() } } val clipboard = LocalClipboardManager.current - LazyColumnWithScrollBar(state = listState, reverseLayout = true) { + LazyColumnWithScrollBar(reverseLayout = true) { items(reversedTerminalItems) { item -> val rhId = item.remoteHostId val rhIdStr = if (rhId == null) "" else "$rhId " diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index c7df983324..a5593693c0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -277,7 +277,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools @Composable fun HistoryTab() { - // LALAL SCROLLBAR DOESN'T WORK ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) @@ -302,7 +301,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools @Composable fun QuoteTab(qi: CIQuote) { - // LALAL SCROLLBAR DOESN'T WORK ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) @@ -316,7 +314,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools @Composable fun ForwardedFromTab(forwardedFromItem: AChatItem) { - // LALAL SCROLLBAR DOESN'T WORK ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) @@ -379,7 +376,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools @Composable fun DeliveryTab(memberDeliveryStatuses: List) { - // LALAL SCROLLBAR DOESN'T WORK ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index a5541a315b..e90eed547d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -504,24 +504,34 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - } is ChatInfo.ContactConnection -> { val close = { chatModel.chatId.value = null } - ModalView(close, showClose = appPlatform.isAndroid, content = { - ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close) - }) - LaunchedEffect(chatInfo.id) { - onComposed(chatInfo.id) - ModalManager.end.closeModals() - chatModel.chatItems.clear() + val handler = remember { AppBarHandler() } + CompositionLocalProvider( + LocalAppBarHandler provides handler + ) { + ModalView(close, showClose = appPlatform.isAndroid, content = { + ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close) + }) + LaunchedEffect(chatInfo.id) { + onComposed(chatInfo.id) + ModalManager.end.closeModals() + chatModel.chatItems.clear() + } } } is ChatInfo.InvalidJSON -> { val close = { chatModel.chatId.value = null } - ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = { - InvalidJSONView(chatInfo.json) - }) - LaunchedEffect(chatInfo.id) { - onComposed(chatInfo.id) - ModalManager.end.closeModals() - chatModel.chatItems.clear() + val handler = remember { AppBarHandler() } + CompositionLocalProvider( + LocalAppBarHandler provides handler + ) { + ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = { + InvalidJSONView(chatInfo.json) + }) + LaunchedEffect(chatInfo.id) { + onComposed(chatInfo.id) + ModalManager.end.closeModals() + chatModel.chatItems.clear() + } } } else -> {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 6e39e8756e..ea07231eb0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -284,7 +284,6 @@ fun GroupChatInfoLayout( if (s.isEmpty()) members else members.filter { m -> m.anyNameContains(s) } } } - // LALAL strange scrolling LazyColumnWithScrollBar( Modifier .fillMaxWidth(), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index b1c353d420..6f595e2ccf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -185,7 +185,14 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf scaffoldState = scaffoldState, drawerContent = { tryOrShowError("Settings", error = { ErrorSettingsView() }) { - SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) + val handler = remember { AppBarHandler() } + CompositionLocalProvider( + LocalAppBarHandler provides handler + ) { + ModalView(showClose = appPlatform.isDesktop, close = { scope.launch { scaffoldState.drawerState.close() } }) { + SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) + } + } } }, contentColor = LocalContentColor.current, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt index 334335a80f..f5343efc18 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -615,14 +615,12 @@ fun ModalData.SMPServerSummaryView( ColumnWithScrollBar( Modifier.fillMaxSize(), ) { - Box(contentAlignment = Alignment.Center) { - val bottomPadding = DEFAULT_PADDING - AppBarTitle( - stringResource(MR.strings.smp_server), - hostDevice(rh?.remoteHostId), - bottomPadding = bottomPadding - ) - } + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.smp_server), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding + ) SMPServerSummaryLayout(summary, statsStartedAt, rh) } } @@ -709,7 +707,7 @@ fun ModalData.XFTPServerSummaryView( @Composable fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableState) { - Column( + ColumnWithScrollBar( Modifier.fillMaxSize(), ) { var showUserSelection by remember { mutableStateOf(false) } @@ -760,14 +758,12 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta Column( Modifier.fillMaxSize(), ) { - Box(contentAlignment = Alignment.Center) { - val bottomPadding = DEFAULT_PADDING - AppBarTitle( - stringResource(MR.strings.servers_info), - hostDevice(rh?.remoteHostId), - bottomPadding = bottomPadding - ) - } + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.servers_info), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding + ) if (serversSummary.value == null) { Box( modifier = Modifier @@ -827,7 +823,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid ) { index -> - ColumnWithScrollBar( + Column( Modifier .fillMaxSize(), verticalArrangement = Arrangement.Top diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt index a2aeac21a2..b73a0ca0bc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt @@ -107,101 +107,104 @@ fun DatabaseEncryptionLayout( migration: Boolean, onConfirmEncrypt: () -> Unit, ) { - val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() - val scrollState = rememberScrollState() - Column( - if (!migration) Modifier.fillMaxWidth().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier) else Modifier.fillMaxWidth(), - ) { - if (!migration) { - AppBarTitle(stringResource(MR.strings.database_passphrase)) - } else { - ChatStoppedView() - SectionSpacer() - } - SectionView(if (migration) generalGetString(MR.strings.database_passphrase).uppercase() else null) { - SavePassphraseSetting( - useKeychain.value, - initialRandomDBPassphrase.value, - storedKey.value, - enabled = (!initialRandomDBPassphrase.value && !progressIndicator.value) || migration - ) { checked -> - if (checked) { - setUseKeychain(true, useKeychain, migration) - } else if (storedKey.value && !migration) { - // Don't show in migration process since it will remove the key after successful encryption - removePassphraseAlert { - removePassphraseFromKeyChain(useKeychain, storedKey, false) - } - } else { - setUseKeychain(false, useKeychain, migration) - } + @Composable + fun Layout() { + Column { + if (!migration) { + AppBarTitle(stringResource(MR.strings.database_passphrase)) + } else { + ChatStoppedView() + SectionSpacer() } + SectionView(if (migration) generalGetString(MR.strings.database_passphrase).uppercase() else null) { + SavePassphraseSetting( + useKeychain.value, + initialRandomDBPassphrase.value, + storedKey.value, + enabled = (!initialRandomDBPassphrase.value && !progressIndicator.value) || migration + ) { checked -> + if (checked) { + setUseKeychain(true, useKeychain, migration) + } else if (storedKey.value && !migration) { + // Don't show in migration process since it will remove the key after successful encryption + removePassphraseAlert { + removePassphraseFromKeyChain(useKeychain, storedKey, false) + } + } else { + setUseKeychain(false, useKeychain, migration) + } + } + + if (!initialRandomDBPassphrase.value && chatDbEncrypted == true) { + PassphraseField( + currentKey, + generalGetString(MR.strings.current_passphrase), + modifier = Modifier.padding(horizontal = DEFAULT_PADDING), + isValid = ::validKey, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + ) + } - if (!initialRandomDBPassphrase.value && chatDbEncrypted == true) { PassphraseField( - currentKey, - generalGetString(MR.strings.current_passphrase), + newKey, + generalGetString(MR.strings.new_passphrase), modifier = Modifier.padding(horizontal = DEFAULT_PADDING), + showStrength = true, isValid = ::validKey, keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), ) - } - - PassphraseField( - newKey, - generalGetString(MR.strings.new_passphrase), - modifier = Modifier.padding(horizontal = DEFAULT_PADDING), - showStrength = true, - isValid = ::validKey, - keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), - ) - val onClickUpdate = { - // Don't do things concurrently. Shouldn't be here concurrently, just in case - if (!progressIndicator.value) { - if (currentKey.value == "") { - if (useKeychain.value) - encryptDatabaseSavedAlert(onConfirmEncrypt) - else - encryptDatabaseAlert(onConfirmEncrypt) - } else { - if (useKeychain.value) - changeDatabaseKeySavedAlert(onConfirmEncrypt) - else - changeDatabaseKeyAlert(onConfirmEncrypt) + val onClickUpdate = { + // Don't do things concurrently. Shouldn't be here concurrently, just in case + if (!progressIndicator.value) { + if (currentKey.value == "") { + if (useKeychain.value) + encryptDatabaseSavedAlert(onConfirmEncrypt) + else + encryptDatabaseAlert(onConfirmEncrypt) + } else { + if (useKeychain.value) + changeDatabaseKeySavedAlert(onConfirmEncrypt) + else + changeDatabaseKeyAlert(onConfirmEncrypt) + } } } + val disabled = currentKey.value == newKey.value || + newKey.value != confirmNewKey.value || + newKey.value.isEmpty() || + !validKey(currentKey.value) || + !validKey(newKey.value) || + progressIndicator.value + + PassphraseField( + confirmNewKey, + generalGetString(MR.strings.confirm_new_passphrase), + modifier = Modifier.padding(horizontal = DEFAULT_PADDING), + isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value }, + keyboardActions = KeyboardActions(onDone = { + if (!disabled) onClickUpdate() + defaultKeyboardAction(ImeAction.Done) + }), + ) + + SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) { + Text(generalGetString(if (migration) MR.strings.set_passphrase else MR.strings.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + } } - val disabled = currentKey.value == newKey.value || - newKey.value != confirmNewKey.value || - newKey.value.isEmpty() || - !validKey(currentKey.value) || - !validKey(newKey.value) || - progressIndicator.value - PassphraseField( - confirmNewKey, - generalGetString(MR.strings.confirm_new_passphrase), - modifier = Modifier.padding(horizontal = DEFAULT_PADDING), - isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value }, - keyboardActions = KeyboardActions(onDone = { - if (!disabled) onClickUpdate() - defaultKeyboardAction(ImeAction.Done) - }), - ) - - SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) { - Text(generalGetString(if (migration) MR.strings.set_passphrase else MR.strings.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + Column { + DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase, migration) } + SectionBottomSpacer() } - - Column { - DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase, migration) - } - SectionBottomSpacer() } - if (appPlatform.isDesktop && !migration) { - Box(Modifier.fillMaxSize()) { - platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) + if (migration) { + Column(Modifier.fillMaxWidth()) { + Layout() + } + } else { + ColumnWithScrollBar(Modifier.fillMaxWidth(), maxIntrinsicSize = true) { + Layout() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index 0ff68de3a0..080edd22b2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -6,59 +6,90 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background +import androidx.compose.ui.draw.* +import androidx.compose.ui.graphics.* import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.* +import chat.simplex.common.platform.appPlatform import chat.simplex.common.ui.theme.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource +import kotlin.math.absoluteValue @Composable fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Color = if (close != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, arrangement: Arrangement.Vertical = Arrangement.Top, closeBarTitle: String? = null, barPaddingValues: PaddingValues = PaddingValues(horizontal = AppBarHorizontalPadding), endButtons: @Composable RowScope.() -> Unit = {}) { var rowModifier = Modifier .fillMaxWidth() .height(AppBarHeight * fontSizeSqrtMultiplier) - + val themeBackgroundMix = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) if (!closeBarTitle.isNullOrEmpty()) { - rowModifier = rowModifier.background(MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f)) + rowModifier = rowModifier.background(themeBackgroundMix) } + val handler = LocalAppBarHandler.current + val connection = LocalAppBarHandler.current?.connection + val title = remember(handler?.title?.value) { handler?.title ?: mutableStateOf("") } Column( verticalArrangement = arrangement, modifier = Modifier .fillMaxWidth() .heightIn(min = AppBarHeight * fontSizeSqrtMultiplier) + .drawWithCache { + val backgroundColor = if (appPlatform.isDesktop && connection != null) themeBackgroundMix.copy(alpha = topTitleAlpha(connection)) else Color.Transparent + onDrawBehind { + if (appPlatform.isDesktop) { + drawRect(backgroundColor) + } + } + } ) { Row( modifier = Modifier.padding(barPaddingValues), content = { Row( rowModifier, - horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - if (showClose) { + if (showClose) { NavigationButtonBack(tintColor = tintColor, onButtonClicked = close) } else { Spacer(Modifier) } if (!closeBarTitle.isNullOrEmpty()) { Row( + Modifier.weight(1f), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { Text( closeBarTitle, - color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.SemiBold, + maxLines = 1 ) } + } else if (title.value.isNotEmpty() && connection != null) { + Row( + Modifier + .padding(start = if (showClose) 0.dp else DEFAULT_PADDING_HALF) + .weight(1f) // hides the title if something wants full width (eg, search field in chat profiles screen) + .graphicsLayer { + alpha = topTitleAlpha((connection)) + } + .padding(start = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + title.value, + fontWeight = FontWeight.SemiBold, + maxLines = 1 + ) + } + } else { + Spacer(Modifier.weight(1f)) } Row { endButtons() @@ -66,11 +97,24 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co } } ) + if (closeBarTitle.isNullOrEmpty() && title.value.isNotEmpty() && connection != null) { + Divider( + Modifier + .graphicsLayer { + alpha = topTitleAlpha(connection) + } + ) + } } } @Composable fun AppBarTitle(title: String, hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) { + val handler = LocalAppBarHandler.current + val connection = handler?.connection + LaunchedEffect(title) { + handler?.title?.value = title + } val theme = CurrentColors.collectAsState() val titleColor = MaterialTheme.appColors.title val brush = if (theme.value.base == DefaultTheme.SIMPLEX) @@ -81,23 +125,37 @@ fun AppBarTitle(title: String, hostDevice: Pair? = null, withPad Text( title, Modifier - .fillMaxWidth() - .padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp,), + .padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, top = DEFAULT_PADDING_HALF, end = if (withPadding) DEFAULT_PADDING else 0.dp,) + .graphicsLayer { + alpha = bottomTitleAlpha(connection) + }, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.h1.copy(brush = brush), color = MaterialTheme.colors.primaryVariant, - textAlign = TextAlign.Center + textAlign = TextAlign.Start ) if (hostDevice != null) { - HostDeviceTitle(hostDevice) + Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer { + alpha = bottomTitleAlpha(connection) + }) { + HostDeviceTitle(hostDevice) + } } Spacer(Modifier.height(bottomPadding)) } } +private fun topTitleAlpha(connection: CollapsingAppBarNestedScrollConnection) = + if (connection.appBarOffset.absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 0f + else ((-connection.appBarOffset * 1.5f) / (AppBarHandler.appBarMaxHeightPx)).coerceIn(0f, 1f) + +private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection?) = + if ((connection?.appBarOffset ?: 0f).absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 1f + else ((AppBarHandler.appBarMaxHeightPx) + (connection?.appBarOffset ?: 0f) / 1.5f).coerceAtLeast(0f) / AppBarHandler.appBarMaxHeightPx + @Composable private fun HostDeviceTitle(hostDevice: Pair, extraPadding: Boolean = false) { - Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) { + Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) { Icon(painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), null, Modifier.size(15.dp), tint = MaterialTheme.colors.secondary) Spacer(Modifier.width(10.dp)) Text(hostDevice.second, color = MaterialTheme.colors.secondary) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt new file mode 100644 index 0000000000..4410f7ada5 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt @@ -0,0 +1,44 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.* +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.unit.Velocity + +val LocalAppBarHandler: ProvidableCompositionLocal = staticCompositionLocalOf { null } + +@Stable +class AppBarHandler( + listState: LazyListState = LazyListState(0, 0), + scrollState: ScrollState = ScrollState(initial = 0) +) { + val title = mutableStateOf("") + var listState by mutableStateOf(listState, structuralEqualityPolicy()) + internal set + + var scrollState by mutableStateOf(scrollState, structuralEqualityPolicy()) + internal set + + val connection = CollapsingAppBarNestedScrollConnection() + + companion object { + var appBarMaxHeightPx: Int = 0 + } +} + +class CollapsingAppBarNestedScrollConnection(): NestedScrollConnection { + var appBarOffset: Float by mutableFloatStateOf(0f) + + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + appBarOffset += available.y + return Offset(0f, 0f) + } + + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + appBarOffset -= available.y + return Offset(x = 0f, 0f) + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index d44476b91b..bfd61a2add 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -2,11 +2,12 @@ package chat.simplex.common.views.helpers import androidx.compose.animation.* import androidx.compose.animation.core.* +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import chat.simplex.common.model.ChatController.appPrefs @@ -48,13 +49,15 @@ enum class ModalPlacement { START, CENTER, END, FULLSCREEN } -class ModalData { +class ModalData() { private val state = mutableMapOf>() fun stateGetOrPut (key: String, default: () -> T): MutableState = state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState fun stateGetOrPutNullable (key: String, default: () -> T?): MutableState = state.getOrPut(key) { mutableStateOf(default() as Any?) } as MutableState + + val appBarHandler = AppBarHandler() } class ModalManager(private val placement: ModalPlacement? = null) { @@ -139,7 +142,13 @@ class ModalManager(private val placement: ModalPlacement? = null) { fun showInView() { // Without animation if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) { - modalViews.lastOrNull()?.let { it.third(it.second, ::closeModal) } + modalViews.lastOrNull()?.let { + CompositionLocalProvider( + LocalAppBarHandler provides it.second.appBarHandler + ) { + it.third(it.second, ::closeModal) + } + } return } AnimatedContent(targetState = modalCount.value, @@ -151,7 +160,13 @@ class ModalManager(private val placement: ModalPlacement? = null) { }.using(SizeTransform(clip = false)) } ) { - modalViews.getOrNull(it - 1)?.let { it.third(it.second, ::closeModal) } + modalViews.getOrNull(it - 1)?.let { + CompositionLocalProvider( + LocalAppBarHandler provides it.second.appBarHandler + ) { + it.third(it.second, ::closeModal) + } + } // This is needed because if we delete from modalViews immediately on request, animation will be bad if (toRemove.isNotEmpty() && it == modalCount.value && transition.currentState == EnterExitState.Visible && !transition.isRunning) { runAtomically { toRemove.removeIf { elem -> modalViews.removeAt(elem); true } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index 152befb82d..dbb805971e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -4,7 +4,7 @@ import SectionBottomSpacer import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.* +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -17,7 +17,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase @@ -147,20 +146,13 @@ private fun MigrateFromDeviceLayout( ) { val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) } - val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() - val scrollState = rememberScrollState() - Column( - Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).height(IntrinsicSize.Max), + ColumnWithScrollBar( + Modifier.fillMaxSize(), maxIntrinsicSize = true ) { AppBarTitle(stringResource(MR.strings.migrate_from_device_title)) SectionByState(migrationState, tempDatabaseFile.value, chatReceiver) SectionBottomSpacer() } - if (appPlatform.isDesktop) { - Box(Modifier.fillMaxSize()) { - platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) - } - } platform.androidLockPortraitOrientation() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index f8a82d010d..4fa36b06f0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -155,20 +155,13 @@ private fun ModalData.MigrateToDeviceLayout( close: () -> Unit, ) { val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) } - val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() - val scrollState = rememberScrollState() - Column( - Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).height(IntrinsicSize.Max), + ColumnWithScrollBar( + Modifier.fillMaxSize(), maxIntrinsicSize = true ) { AppBarTitle(stringResource(MR.strings.migrate_to_device_title)) SectionByState(migrationState, tempDatabaseFile.value, chatReceiver, close) SectionBottomSpacer() } - if (appPlatform.isDesktop) { - Box(Modifier.fillMaxSize()) { - platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) - } - } platform.androidLockPortraitOrientation() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 759c090bc7..3146c14589 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -68,10 +68,10 @@ fun NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) { Column(modifier = Modifier.fillMaxSize()) { NewChatSheetLayout( addContact = { - ModalManager.start.showModalCloseable { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll ) } + ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll ) } }, scanPaste = { - ModalManager.start.showModalCloseable { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) } + ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) } }, createGroup = { ModalManager.start.showCustomModal { close -> AddGroupView(chatModel, chatModel.currentRemoteHost.value, close, closeAll) } @@ -194,7 +194,15 @@ private fun NewChatSheetLayout( (appPlatform.isAndroid && keyboardState == KeyboardState.Opened) ) { 0 - } else if (listState.firstVisibleItemIndex == 0) offsetMultiplier * listState.firstVisibleItemScrollOffset else offsetMultiplier * 1000 + } else if (oneHandUI.value && listState.firstVisibleItemIndex == 0) { + listState.firstVisibleItemScrollOffset + } else if (!oneHandUI.value && listState.firstVisibleItemIndex == 0) { + 0 + } else if (!oneHandUI.value && listState.firstVisibleItemIndex == 1) { + -listState.firstVisibleItemScrollOffset + } else { + offsetMultiplier * 1000 + } } else { 0 } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 007f474265..a872f2548d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -5,6 +5,7 @@ import SectionItemView import SectionTextFooter import SectionView import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.pager.HorizontalPager @@ -96,66 +97,61 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC } } - Column( - Modifier.fillMaxSize(), - ) { - Box(contentAlignment = Alignment.Center) { - val bottomPadding = DEFAULT_PADDING - AppBarTitle(stringResource(MR.strings.new_chat), hostDevice(rh?.remoteHostId), bottomPadding = bottomPadding) - Column(Modifier.align(Alignment.CenterEnd).padding(bottom = bottomPadding, end = DEFAULT_PADDING)) { - AddContactLearnMoreButton() + BoxWithConstraints { + ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.new_chat), hostDevice(rh?.remoteHostId), bottomPadding = DEFAULT_PADDING) + val scope = rememberCoroutineScope() + val pagerState = rememberPagerState( + initialPage = selection.value.ordinal, + initialPageOffsetFraction = 0f + ) { NewChatOption.values().size } + KeyChangeEffect(pagerState.currentPage) { + selection.value = NewChatOption.values()[pagerState.currentPage] } - } - val scope = rememberCoroutineScope() - val pagerState = rememberPagerState( - initialPage = selection.value.ordinal, - initialPageOffsetFraction = 0f - ) { NewChatOption.values().size } - KeyChangeEffect(pagerState.currentPage) { - selection.value = NewChatOption.values()[pagerState.currentPage] - } - TabRow( - selectedTabIndex = pagerState.currentPage, - backgroundColor = Color.Transparent, - contentColor = MaterialTheme.colors.primary, - ) { - tabTitles.forEachIndexed { index, it -> - LeadingIconTab( - selected = pagerState.currentPage == index, - onClick = { - scope.launch { - pagerState.animateScrollToPage(index) - } - }, - text = { Text(it, fontSize = 13.sp) }, - icon = { - Icon( - if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code), - it - ) - }, - selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = MaterialTheme.colors.secondary, - ) - } - } - - HorizontalPager(state = pagerState, Modifier.fillMaxSize(), verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid) { index -> - // LALAL SCROLLBAR DOESN'T WORK - ColumnWithScrollBar( - Modifier - .fillMaxSize(), - verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top) { - Spacer(Modifier.height(DEFAULT_PADDING)) - when (index) { - NewChatOption.INVITE.ordinal -> { - PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq) - } - NewChatOption.CONNECT.ordinal -> { - ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close) - } + TabRow( + selectedTabIndex = pagerState.currentPage, + backgroundColor = Color.Transparent, + contentColor = MaterialTheme.colors.primary, + ) { + tabTitles.forEachIndexed { index, it -> + LeadingIconTab( + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + }, + text = { Text(it, fontSize = 13.sp) }, + icon = { + Icon( + if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code), + it + ) + }, + selectedContentColor = MaterialTheme.colors.primary, + unselectedContentColor = MaterialTheme.colors.secondary, + ) + } + } + + HorizontalPager(state = pagerState, Modifier, pageNestedScrollConnection = LocalAppBarHandler.current!!.connection, verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid) { index -> + Column( + Modifier + .fillMaxWidth() + .heightIn(min = this@BoxWithConstraints.maxHeight - 150.dp), + verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top + ) { + Spacer(Modifier.height(DEFAULT_PADDING)) + when (index) { + NewChatOption.INVITE.ordinal -> { + PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq) + } + NewChatOption.CONNECT.ordinal -> { + ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close) + } + } + SectionBottomSpacer() } - SectionBottomSpacer() } } } @@ -228,18 +224,18 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection } @Composable -private fun AddContactLearnMoreButton() { +fun AddContactLearnMoreButton() { IconButton( { ModalManager.start.showModalCloseable { close -> AddContactLearnMore(close) } - }, - Modifier.size(18.dp * fontSizeSqrtMultiplier) + } ) { Icon( painterResource(MR.images.ic_info), stringResource(MR.strings.learn_more), + tint = MaterialTheme.colors.primary ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 3f2c035335..9c7e2bdce7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -23,9 +23,10 @@ import dev.icerock.moko.resources.StringResource @Composable fun HowItWorks(user: User?, onboardingStage: SharedPreference? = null) { - ColumnWithScrollBar(Modifier - .fillMaxWidth() - .padding(DEFAULT_PADDING), + ColumnWithScrollBar( + Modifier + .fillMaxWidth() + .padding(DEFAULT_PADDING), ) { AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false) ReadableText(MR.strings.many_people_asked_how_can_it_deliver) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index f0960c1511..2f6c0395ec 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -73,33 +73,30 @@ private fun SetDeliveryReceiptsLayout( skip: () -> Unit, userCount: Int, ) { + // This view located in the left panel which means it has to have a padding from right side in order + // to see scroll bar. And this padding should be applied to upper element, not scrollable column modifier val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp - val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() - val scrollState = rememberScrollState() - Column( - Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).padding(top = DEFAULT_PADDING, end = endPadding), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - AppBarTitle(stringResource(MR.strings.delivery_receipts_title)) + Box(Modifier.padding(top = DEFAULT_PADDING, end = endPadding)) { + ColumnWithScrollBar( + Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AppBarTitle(stringResource(MR.strings.delivery_receipts_title)) - Spacer(Modifier.weight(1f)) + Spacer(Modifier.weight(1f)) - EnableReceiptsButton(enableReceipts) - if (userCount > 1) { - TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled_all_profiles)) - } else { - TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled)) - } + EnableReceiptsButton(enableReceipts) + if (userCount > 1) { + TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled_all_profiles)) + } else { + TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled)) + } - Spacer(Modifier.weight(1f)) + Spacer(Modifier.weight(1f)) - SkipButton(skip) + SkipButton(skip) - SectionBottomSpacer() - } - if (appPlatform.isDesktop) { - Box(Modifier.fillMaxSize().padding(end = endPadding)) { - platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) + SectionBottomSpacer() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 02716fa17e..6c07a9ceeb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -111,85 +111,73 @@ fun SettingsLayout( } val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current - Box(Modifier.fillMaxSize()) { - ColumnWithScrollBar( - Modifier - .fillMaxSize() - .themedBackground(theme.value.base) - .padding(top = if (appPlatform.isAndroid) DEFAULT_PADDING else DEFAULT_PADDING * 2.8f) - ) { - AppBarTitle(stringResource(MR.strings.your_settings)) + ColumnWithScrollBar( + Modifier + .fillMaxSize() + .themedBackground(theme.value.base) + ) { + AppBarTitle(stringResource(MR.strings.your_settings)) - SectionView(stringResource(MR.strings.settings_section_title_you)) { - val profileHidden = rememberSaveable { mutableStateOf(false) } - if (profile != null) { - SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { - ProfilePreview(profile, stopped = stopped) - } - SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden, drawerState) } } }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true) - ChatPreferencesItem(showCustomModal, stopped = stopped) - } else if (chatModel.localUserCreated.value == false) { - SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.create_chat_profile), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.center.showModalCloseable { close -> - LaunchedEffect(Unit) { - closeSettings() + SectionView(stringResource(MR.strings.settings_section_title_you)) { + val profileHidden = rememberSaveable { mutableStateOf(false) } + if (profile != null) { + SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { + ProfilePreview(profile, stopped = stopped) + } + SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden, drawerState) } } }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true) + ChatPreferencesItem(showCustomModal, stopped = stopped) + } else if (chatModel.localUserCreated.value == false) { + SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.create_chat_profile), { + withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { + ModalManager.center.showModalCloseable { close -> + LaunchedEffect(Unit) { + closeSettings() + } + CreateProfile(chatModel, close) } - CreateProfile(chatModel, close) - } } }, disabled = stopped, extraPadding = true) - } - if (appPlatform.isDesktop) { - SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped, extraPadding = true) - } else { - SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal{ it, close -> ConnectDesktopView(close) }, disabled = stopped, extraPadding = true) - } - SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } }}, disabled = stopped, extraPadding = true) + } + }, disabled = stopped, extraPadding = true) } - SectionDividerSpaced() - - SectionView(stringResource(MR.strings.settings_section_title_settings)) { - SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }, extraPadding = true) - DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) + if (appPlatform.isDesktop) { + SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped, extraPadding = true) + } else { + SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal { it, close -> ConnectDesktopView(close) }, disabled = stopped, extraPadding = true) } - SectionDividerSpaced() - - SectionView(stringResource(MR.strings.settings_section_title_help)) { - SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true) - if (!chatModel.desktopNoUserNoRemote) { - SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true) - } - SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true) - } - SectionDividerSpaced() - - SectionView(stringResource(MR.strings.settings_section_title_support)) { - ContributeItem(uriHandler) - RateAppItem(uriHandler) - StarOnGithubItem(uriHandler) - } - SectionDividerSpaced() - - SettingsSectionApp(showSettingsModal, showCustomModal, showVersion, withAuth) - SectionBottomSpacer() + SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped, extraPadding = true) } - if (appPlatform.isDesktop) { - Box( - Modifier - .fillMaxWidth() - .height(AppBarHeight * fontSizeSqrtMultiplier) - .background(MaterialTheme.colors.background) - .background(if (isInDarkTheme()) ToolbarDark else ToolbarLight) - .padding(start = 4.dp), - contentAlignment = Alignment.CenterStart - ) { - NavigationButtonBack(closeSettings, height = 24.sp.toDp()) - } + SectionDividerSpaced() + + SectionView(stringResource(MR.strings.settings_section_title_settings)) { + SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }, extraPadding = true) + DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) } + SectionDividerSpaced() + + SectionView(stringResource(MR.strings.settings_section_title_help)) { + SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true) + if (!chatModel.desktopNoUserNoRemote) { + SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true) + } + SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true) + } + SectionDividerSpaced() + + SectionView(stringResource(MR.strings.settings_section_title_support)) { + ContributeItem(uriHandler) + RateAppItem(uriHandler) + StarOnGithubItem(uriHandler) + } + SectionDividerSpaced() + + SettingsSectionApp(showSettingsModal, showCustomModal, showVersion, withAuth) + SectionBottomSpacer() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt index 63baece5cf..1ac0cd7ecd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt @@ -13,11 +13,12 @@ import chat.simplex.res.MR @Composable fun UserAddressLearnMore() { - ColumnWithScrollBar(Modifier - .fillMaxHeight() - .padding(horizontal = DEFAULT_PADDING) + ColumnWithScrollBar( + Modifier + .fillMaxHeight() + .padding(horizontal = DEFAULT_PADDING) ) { - AppBarTitle(stringResource(MR.strings.simplex_address)) + AppBarTitle(stringResource(MR.strings.simplex_address), withPadding = false) ReadableText(MR.strings.you_can_share_your_address) ReadableText(MR.strings.you_wont_lose_your_contacts_if_delete_address) ReadableText(MR.strings.you_can_accept_or_reject_connection) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index b595bd4e0e..e6d3202cd0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -174,7 +174,7 @@ private fun UserAddressLayout( saveAas: (AutoAcceptState, MutableState) -> Unit, ) { ColumnWithScrollBar { - AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId), withPadding = false) + AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId)) Column( Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally, diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt index 9d39753f23..951185dc98 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt @@ -77,6 +77,9 @@ actual fun windowOrientation(): WindowOrientation = @Composable actual fun windowWidth(): Dp = simplexWindowState.windowState.size.width +@Composable +actual fun windowHeight(): Dp = simplexWindowState.windowState.size.height + actual fun desktopExpandWindowToWidth(width: Dp) { if (simplexWindowState.windowState.size.width >= width) return simplexWindowState.windowState.size = simplexWindowState.windowState.size.copy(width = width) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt index 91e9c70a20..e6b26f9290 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt @@ -13,16 +13,18 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.* import androidx.compose.ui.unit.dp -import chat.simplex.common.views.helpers.detectCursorMove -import chat.simplex.common.views.helpers.mixWith +import chat.simplex.common.views.helpers.* import kotlinx.coroutines.* +import kotlinx.coroutines.flow.filter +import kotlin.math.absoluteValue @Composable actual fun LazyColumnWithScrollBar( modifier: Modifier, - state: LazyListState, + state: LazyListState?, contentPadding: PaddingValues, reverseLayout: Boolean, verticalArrangement: Arrangement.Vertical, @@ -30,44 +32,6 @@ actual fun LazyColumnWithScrollBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, content: LazyListScope.() -> Unit -) { - if (appPlatform.isAndroid) { - LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) - } else { - val scope = rememberCoroutineScope() - val scrollBarAlpha = remember { Animatable(0f) } - val scrollJob: MutableState = remember { mutableStateOf(Job()) } - val scrollModifier = remember { - Modifier - .pointerInput(Unit) { - detectCursorMove { - scope.launch { - scrollBarAlpha.animateTo(1f) - } - scrollJob.value.cancel() - scrollJob.value = scope.launch { - delay(1000L) - scrollBarAlpha.animateTo(0f) - } - } - } - } - Box { - LazyColumn(modifier.then(if (appPlatform.isDesktop) scrollModifier else Modifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { - DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout) - } - } - } -} - -@Composable -actual fun ColumnWithScrollBar( - modifier: Modifier, - verticalArrangement: Arrangement.Vertical, - horizontalAlignment: Alignment.Horizontal, - state: ScrollState, - content: @Composable ColumnScope.() -> Unit ) { val scope = rememberCoroutineScope() val scrollBarAlpha = remember { Animatable(0f) } @@ -87,20 +51,95 @@ actual fun ColumnWithScrollBar( } } } - Column(modifier.verticalScroll(state).then(scrollModifier), verticalArrangement, horizontalAlignment, content) - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { - DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) + val state = state ?: LocalAppBarHandler.current?.listState ?: rememberLazyListState() + val connection = LocalAppBarHandler.current?.connection + // When scroll bar is dragging, there is no scroll event in nested scroll modifier. So, listen for changes on lazy column state + // (only first visible row is useful because LazyColumn doesn't have absolute scroll position, only relative to row) + val scrollBarDraggingState = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + snapshotFlow { state.firstVisibleItemScrollOffset } + .filter { state.firstVisibleItemIndex == 0 } + .collect { scrollPosition -> + val offset = connection?.appBarOffset + if (offset != null && ((offset + scrollPosition).absoluteValue > 1 || scrollBarDraggingState.value)) { + connection.appBarOffset = -scrollPosition.toFloat() +// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + } + } + } + Box(if (connection != null) Modifier.nestedScroll(connection) else Modifier) { + LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { + DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState) + } } } @Composable -fun DesktopScrollBar(adapter: androidx.compose.foundation.v2.ScrollbarAdapter, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean) { +actual fun ColumnWithScrollBar( + modifier: Modifier, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + state: ScrollState?, + maxIntrinsicSize: Boolean, + content: @Composable() (ColumnScope.() -> Unit) +) { + val scope = rememberCoroutineScope() + val scrollBarAlpha = remember { Animatable(0f) } + val scrollJob: MutableState = remember { mutableStateOf(Job()) } + val scrollModifier = remember { + Modifier + .pointerInput(Unit) { + detectCursorMove { + scope.launch { + scrollBarAlpha.animateTo(1f) + } + scrollJob.value.cancel() + scrollJob.value = scope.launch { + delay(1000L) + scrollBarAlpha.animateTo(0f) + } + } + } + } + val state = state ?: LocalAppBarHandler.current?.scrollState ?: rememberScrollState() + val connection = LocalAppBarHandler.current?.connection + // When scroll bar is dragging, there is no scroll event in nested scroll modifier. So, listen for changes on column state + // (exact scroll position is available but in Int, not Float) + val scrollBarDraggingState = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + snapshotFlow { state.value } + .collect { scrollPosition -> + val offset = connection?.appBarOffset + if (offset != null && ((offset + scrollPosition).absoluteValue > 1 || scrollBarDraggingState.value)) { + connection.appBarOffset = -scrollPosition.toFloat() +// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + } + } + } + Box(if (connection != null) Modifier.nestedScroll(connection) else Modifier) { + Column( + if (maxIntrinsicSize) { + modifier.verticalScroll(state).height(IntrinsicSize.Max).then(scrollModifier) + } else { + modifier.verticalScroll(state).then(scrollModifier) + }, + verticalArrangement, horizontalAlignment, content) + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { + DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, false, scrollBarDraggingState) + } + } +} + +@Composable +fun DesktopScrollBar(adapter: androidx.compose.foundation.v2.ScrollbarAdapter, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean, updateDraggingState: MutableState = remember { mutableStateOf(false) }) { val scope = rememberCoroutineScope() val interactionSource = remember { MutableInteractionSource() } val isHovered by interactionSource.collectIsHoveredAsState() val isDragged by interactionSource.collectIsDraggedAsState() LaunchedEffect(isHovered, isDragged) { scrollJob.value.cancel() + updateDraggingState.value = isDragged if (isHovered || isDragged) { scrollBarAlpha.animateTo(1f) } else { diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt index 8e1643571b..f0679c0fa1 100644 --- a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt +++ b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt @@ -48,38 +48,6 @@ private fun initHaskell() { initHS() platform = object: PlatformInterface { - @Composable - override fun desktopScrollBarComponents(): Triple, Modifier, MutableState> { - val scope = rememberCoroutineScope() - val scrollBarAlpha = remember { Animatable(0f) } - val scrollJob: MutableState = remember { mutableStateOf(Job()) } - val modifier = remember { - Modifier.pointerInput(Unit) { - detectCursorMove { - scope.launch { - scrollBarAlpha.animateTo(1f) - } - scrollJob.value.cancel() - scrollJob.value = scope.launch { - delay(1000L) - scrollBarAlpha.animateTo(0f) - } - } - } - } - return Triple(scrollBarAlpha, modifier, scrollJob) - } - - @Composable - override fun desktopScrollBar(state: LazyListState, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean) { - DesktopScrollBar(rememberScrollbarAdapter(scrollState = state), modifier, scrollBarAlpha, scrollJob, reversed) - } - - @Composable - override fun desktopScrollBar(state: ScrollState, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean) { - DesktopScrollBar(rememberScrollbarAdapter(scrollState = state), modifier, scrollBarAlpha, scrollJob, reversed) - } - @Composable override fun desktopShowAppUpdateNotice() { fun showNoticeIfNeeded() { From 70991debfd0cf3235bcce4950ca3552af8d9e608 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 20 Aug 2024 09:29:52 +0100 Subject: [PATCH 020/440] ios: optimize performance of updating chats (#4723) * ios: optimize performance of updating chats * even simpler * always use updateChats * make chats readonly from outside of model --- apps/ios/Shared/Model/ChatModel.swift | 23 +++---------------- apps/ios/Shared/Model/SimpleXAPI.swift | 9 +++----- apps/ios/Shared/SimpleXApp.swift | 2 +- .../Shared/Views/ChatList/ChatListView.swift | 10 ++++---- .../Shared/Views/Database/DatabaseView.swift | 2 +- .../Views/LocalAuth/LocalAuthView.swift | 3 +-- apps/ios/SimpleXChat/ChatTypes.swift | 6 +++++ 7 files changed, 20 insertions(+), 35 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 4b391b727e..0ac1a9cacb 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -143,7 +143,7 @@ final class ChatModel: ObservableObject { @Published var contentViewAccessAuthenticated: Bool = false @Published var laRequest: LocalAuthRequest? // list of chat "previews" - @Published var chats: [Chat] = [] + @Published private(set) var chats: [Chat] = [] @Published var deletedChats: Set = [] // current chat @Published var chatId: String? @@ -357,25 +357,8 @@ final class ChatModel: ObservableObject { } } - func updateChats(with newChats: [ChatData]) { - for i in 0.. some View { struct ChatListView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() - chatModel.chats = [ - Chat( + chatModel.updateChats([ + ChatData( chatInfo: ChatInfo.sampleData.direct, chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")] ), - Chat( + ChatData( chatInfo: ChatInfo.sampleData.group, chatItems: [ChatItem.getSample(1, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")] ), - Chat( + ChatData( chatInfo: ChatInfo.sampleData.contactRequest, chatItems: [] ) - ] + ]) return Group { ChatListView(showSettings: Binding.constant(false)) .environmentObject(chatModel) diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 6b6d3796dc..f5b5287971 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -491,7 +491,7 @@ struct DatabaseView: View { appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) do { let chats = try apiGetChats() - m.updateChats(with: chats) + m.updateChats(chats) } catch let error { logger.error("apiGetChats: cannot update chats \(responseError(error))") } diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift index 5e64f7fb0d..27bb95b599 100644 --- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift +++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift @@ -65,8 +65,7 @@ struct LocalAuthView: View { // Clear sensitive data on screen just in case app fails to hide its views while new database is created m.chatId = nil ItemsModel.shared.reversedChatItems = [] - m.chats = [] - m.popChatCollector.clear() + m.updateChats([]) m.users = [] _ = kcAppPassword.set(password) _ = kcSelfDestructPassword.remove() diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 233f4c19ee..1a9cf4a216 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1500,6 +1500,12 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike { public var id: ChatId { get { chatInfo.id } } + public init(chatInfo: ChatInfo, chatItems: [ChatItem], chatStats: ChatStats = ChatStats()) { + self.chatInfo = chatInfo + self.chatItems = chatItems + self.chatStats = chatStats + } + public static func invalidJSON(_ json: String) -> ChatData { ChatData( chatInfo: .invalidJSON(json: json), From b52dfee078c345562ab4c6339702d2dd2fa490ca Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 20 Aug 2024 13:28:06 +0300 Subject: [PATCH 021/440] ios: asynchronous api calls when entering foreground (#4710) * ios: get call invitations asynchronously * async update chats * async user list on appear * move model changes to main thread --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 21 ++++++----- apps/ios/Shared/SimpleXApp.swift | 20 ++++++----- .../Shared/Views/Call/CallController.swift | 35 ++++++++++--------- .../Shared/Views/ChatList/UserPicker.swift | 15 ++++---- 4 files changed, 50 insertions(+), 41 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 3abd1e92d1..4e24c616d9 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1218,8 +1218,8 @@ func apiEndCall(_ contact: Contact) async throws { try await sendCommandOkResp(.apiEndCall(contact: contact)) } -func apiGetCallInvitations() throws -> [RcvCallInvitation] { - let r = chatSendCmdSync(.apiGetCallInvitations) +func apiGetCallInvitations() async throws -> [RcvCallInvitation] { + let r = await chatSendCmd(.apiGetCallInvitations) if case let .callInvitations(invs) = r { return invs } throw r } @@ -1517,7 +1517,7 @@ func startChat(refreshInvitations: Bool = true) throws { try getUserChatData() NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers()) if (refreshInvitations) { - try refreshCallInvitations() + Task { try await refreshCallInvitations() } } (m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken() _ = try apiStartChat() @@ -2161,22 +2161,25 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async { } } -func refreshCallInvitations() throws { +func refreshCallInvitations() async throws { let m = ChatModel.shared - let callInvitations = try justRefreshCallInvitations() + let callInvitations = try await justRefreshCallInvitations() if let (chatId, ntfAction) = m.ntfCallInvitationAction, let invitation = m.callInvitations.removeValue(forKey: chatId) { - m.ntfCallInvitationAction = nil + await MainActor.run { m.ntfCallInvitationAction = nil } CallController.shared.callAction(invitation: invitation, action: ntfAction) } else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) { activateCall(invitation) } } -func justRefreshCallInvitations() throws -> [RcvCallInvitation] { +func justRefreshCallInvitations() async throws -> [RcvCallInvitation] { let m = ChatModel.shared - let callInvitations = try apiGetCallInvitations() - m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv } + let callInvitations = try await apiGetCallInvitations() + await MainActor.run { + m.callInvitations = callInvitations + .reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv } + } return callInvitations } diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 621f58dc0c..7f2c3b5866 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -83,9 +83,11 @@ struct SimpleXApp: App { if appState != .stopped { startChatAndActivate { if appState.inactive && chatModel.chatRunning == true { - updateChats() - if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { - updateCallInvitations() + Task { + await updateChats() + if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { + await updateCallInvitations() + } } } } @@ -130,16 +132,16 @@ struct SimpleXApp: App { } } - private func updateChats() { + private func updateChats() async { do { - let chats = try apiGetChats() - chatModel.updateChats(chats) + let chats = try await apiGetChatsAsync() + await MainActor.run { chatModel.updateChats(chats) } if let id = chatModel.chatId, let chat = chatModel.getChat(id) { Task { await loadChat(chat: chat, clearItems: false) } } if let ncr = chatModel.ntfContactRequest { - chatModel.ntfContactRequest = nil + await MainActor.run { chatModel.ntfContactRequest = nil } if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo { Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) } } @@ -149,9 +151,9 @@ struct SimpleXApp: App { } } - private func updateCallInvitations() { + private func updateCallInvitations() async { do { - try refreshCallInvitations() + try await refreshCallInvitations() } catch let error { logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))") } diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index 64b565e8e6..4fcc7db4e1 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -186,29 +186,30 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse logger.debug("CallController: started chat") self.shouldSuspendChat = true // There are no invitations in the model, as it was processed by NSE - _ = try? justRefreshCallInvitations() - logger.debug("CallController: updated call invitations chat") - // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") - // Extract the call information from the push notification payload - let m = ChatModel.shared - if let contactId = payload.dictionaryPayload["contactId"] as? String, - let invitation = m.callInvitations[contactId] { - let update = self.cxCallUpdate(invitation: invitation) - if let uuid = invitation.callkitUUID { - logger.debug("CallController: report pushkit call via CallKit") + Task { + _ = try? await justRefreshCallInvitations() + logger.debug("CallController: updated call invitations chat") + // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") + // Extract the call information from the push notification payload + let m = ChatModel.shared + if let contactId = payload.dictionaryPayload["contactId"] as? String, + let invitation = m.callInvitations[contactId] { let update = self.cxCallUpdate(invitation: invitation) - self.provider.reportNewIncomingCall(with: uuid, update: update) { error in - if error != nil { - m.callInvitations.removeValue(forKey: contactId) + if let uuid = invitation.callkitUUID { + logger.debug("CallController: report pushkit call via CallKit") + let update = self.cxCallUpdate(invitation: invitation) + do { + try await self.provider.reportNewIncomingCall(with: uuid, update: update) + } catch { + _ = await MainActor.run { m.callInvitations.removeValue(forKey: contactId) } } - // Tell PushKit that the notification is handled. completion() + } else { + self.reportExpiredCall(update: update, completion) } } else { - self.reportExpiredCall(update: update, completion) + self.reportExpiredCall(payload: payload, completion) } - } else { - self.reportExpiredCall(payload: payload, completion) } } diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index eeb7bf14f4..e1b2356bed 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -85,13 +85,16 @@ struct UserPicker: View { .padding(8) .opacity(userPickerVisible ? 1.0 : 0.0) .onAppear { - do { - // This check prevents the call of listUsers after the app is suspended, and the database is closed. - if case .active = scenePhase { - m.users = try listUsers() + // This check prevents the call of listUsers after the app is suspended, and the database is closed. + if case .active = scenePhase { + Task { + do { + let users = try await listUsersAsync() + await MainActor.run { m.users = users } + } catch { + logger.error("Error loading users \(responseError(error))") + } } - } catch let error { - logger.error("Error loading users \(responseError(error))") } } } From b2d18f69604fd54e6a50499dc3188a1dda44bf4e Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 20 Aug 2024 15:54:52 +0300 Subject: [PATCH 022/440] ios: fix visual artifacts when opening a chat on iOS15 (#4726) * ios: fix visual artifacts when opening a chat on iOS15 * fix separators * cleanup --- .../Shared/Views/ChatList/ChatListView.swift | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 041b45492d..8ad03236f1 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -217,14 +217,31 @@ struct ChatListView: View { .listRowSeparator(.hidden) .listRowBackground(Color.clear) } - ForEach(cs, id: \.viewId) { chat in - ChatListNavLink(chat: chat) + if #available(iOS 16.0, *) { + ForEach(cs, id: \.viewId) { chat in + ChatListNavLink(chat: chat) + .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) + .padding(.trailing, -16) + .disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id)) + .listRowBackground(Color.clear) + } + .offset(x: -8) + } else { + ForEach(cs, id: \.viewId) { chat in + VStack(spacing: .zero) { + Divider() + .padding(.leading, 16) + ChatListNavLink(chat: chat) + .padding(.horizontal, 8) + .padding(.vertical, 6) + } .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) - .padding(.trailing, -16) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets()) + .background { theme.colors.background } // Hides default list selection colour .disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id)) - .listRowBackground(Color.clear) + } } - .offset(x: -8) } .listStyle(.plain) .onChange(of: chatModel.chatId) { currentChatId in From 5f0ccb9f17d403a12efbf4db5f6c6a3a791400ec Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:04:00 +0000 Subject: [PATCH 023/440] ios: fix interface color without callKit (#4727) * ios: fix interface color without callKit * call area * better hiding sheet when making a call without CallKit * invert condition --- apps/ios/Shared/ContentView.swift | 8 ++++---- apps/ios/Shared/Theme/Theme.swift | 4 ++-- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 9 ++++++++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index d93625986e..1f41e7c31d 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -36,7 +36,7 @@ struct ContentView: View { @State private var waitingForOrPassedAuth = true @State private var chatListActionSheet: ChatListActionSheet? = nil - private let callTopPadding: CGFloat = 50 + private let callTopPadding: CGFloat = 40 private enum ChatListActionSheet: Identifiable { case planAndConnectSheet(sheet: PlanAndConnectActionSheet) @@ -151,12 +151,12 @@ struct ContentView: View { } } .onAppear { - reactOnDarkThemeChanges() + reactOnDarkThemeChanges(systemInDarkThemeCurrently) } .onChange(of: colorScheme) { scheme in // It's needed to update UI colors when iOS wants to make screenshot after going to background, // so when a user changes his global theme from dark to light or back, the app will adapt to it - reactOnDarkThemeChanges() + reactOnDarkThemeChanges(scheme == .dark) } .onChange(of: theme.name) { _ in ThemeManager.adjustWindowStyle() @@ -207,7 +207,7 @@ struct ContentView: View { CallDuration(call: call) } .padding(.horizontal) - .frame(height: callTopPadding - 10) + .frame(height: callTopPadding) .background(Color(uiColor: UIColor(red: 47/255, green: 208/255, blue: 88/255, alpha: 1))) .onTapGesture { chatModel.activeCallViewIsCollapsed = false diff --git a/apps/ios/Shared/Theme/Theme.swift b/apps/ios/Shared/Theme/Theme.swift index d4dcc8097f..e2641eb8dd 100644 --- a/apps/ios/Shared/Theme/Theme.swift +++ b/apps/ios/Shared/Theme/Theme.swift @@ -91,8 +91,8 @@ var systemInDarkThemeCurrently: Bool { return UITraitCollection.current.userInterfaceStyle == .dark } -func reactOnDarkThemeChanges() { - if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.colors.isLight == systemInDarkThemeCurrently { +func reactOnDarkThemeChanges(_ inDarkNow: Bool) { + if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.colors.isLight == inDarkNow { // Change active colors from light to dark and back based on system theme ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index ff46b9619c..ea3b04c2ff 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -671,7 +671,14 @@ private struct CallButton: View { InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) { if canCall { - CallController.shared.startCall(contact, mediaType) + if CallController.useCallKit() { + CallController.shared.startCall(contact, mediaType) + } else { + // When CallKit is not used, colorscheme will be changed and it will be visible if not hiding sheets first + dismissAllSheets(animated: true) { + CallController.shared.startCall(contact, mediaType) + } + } } else if contact.nextSendGrpInv { showAlert(SomeAlert( alert: mkAlert( From e04f74738e82909fdfd1360e7699143b68e5ed1b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 20 Aug 2024 18:24:53 +0100 Subject: [PATCH 024/440] Revert "ios: asynchronous api calls when entering foreground (#4710)" This reverts commit b52dfee078c345562ab4c6339702d2dd2fa490ca. --- apps/ios/Shared/Model/SimpleXAPI.swift | 21 +++++------ apps/ios/Shared/SimpleXApp.swift | 20 +++++------ .../Shared/Views/Call/CallController.swift | 35 +++++++++---------- .../Shared/Views/ChatList/UserPicker.swift | 15 ++++---- 4 files changed, 41 insertions(+), 50 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 4e24c616d9..3abd1e92d1 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1218,8 +1218,8 @@ func apiEndCall(_ contact: Contact) async throws { try await sendCommandOkResp(.apiEndCall(contact: contact)) } -func apiGetCallInvitations() async throws -> [RcvCallInvitation] { - let r = await chatSendCmd(.apiGetCallInvitations) +func apiGetCallInvitations() throws -> [RcvCallInvitation] { + let r = chatSendCmdSync(.apiGetCallInvitations) if case let .callInvitations(invs) = r { return invs } throw r } @@ -1517,7 +1517,7 @@ func startChat(refreshInvitations: Bool = true) throws { try getUserChatData() NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers()) if (refreshInvitations) { - Task { try await refreshCallInvitations() } + try refreshCallInvitations() } (m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken() _ = try apiStartChat() @@ -2161,25 +2161,22 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async { } } -func refreshCallInvitations() async throws { +func refreshCallInvitations() throws { let m = ChatModel.shared - let callInvitations = try await justRefreshCallInvitations() + let callInvitations = try justRefreshCallInvitations() if let (chatId, ntfAction) = m.ntfCallInvitationAction, let invitation = m.callInvitations.removeValue(forKey: chatId) { - await MainActor.run { m.ntfCallInvitationAction = nil } + m.ntfCallInvitationAction = nil CallController.shared.callAction(invitation: invitation, action: ntfAction) } else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) { activateCall(invitation) } } -func justRefreshCallInvitations() async throws -> [RcvCallInvitation] { +func justRefreshCallInvitations() throws -> [RcvCallInvitation] { let m = ChatModel.shared - let callInvitations = try await apiGetCallInvitations() - await MainActor.run { - m.callInvitations = callInvitations - .reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv } - } + let callInvitations = try apiGetCallInvitations() + m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv } return callInvitations } diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 7f2c3b5866..621f58dc0c 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -83,11 +83,9 @@ struct SimpleXApp: App { if appState != .stopped { startChatAndActivate { if appState.inactive && chatModel.chatRunning == true { - Task { - await updateChats() - if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { - await updateCallInvitations() - } + updateChats() + if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { + updateCallInvitations() } } } @@ -132,16 +130,16 @@ struct SimpleXApp: App { } } - private func updateChats() async { + private func updateChats() { do { - let chats = try await apiGetChatsAsync() - await MainActor.run { chatModel.updateChats(chats) } + let chats = try apiGetChats() + chatModel.updateChats(chats) if let id = chatModel.chatId, let chat = chatModel.getChat(id) { Task { await loadChat(chat: chat, clearItems: false) } } if let ncr = chatModel.ntfContactRequest { - await MainActor.run { chatModel.ntfContactRequest = nil } + chatModel.ntfContactRequest = nil if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo { Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) } } @@ -151,9 +149,9 @@ struct SimpleXApp: App { } } - private func updateCallInvitations() async { + private func updateCallInvitations() { do { - try await refreshCallInvitations() + try refreshCallInvitations() } catch let error { logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))") } diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index 4fcc7db4e1..64b565e8e6 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -186,30 +186,29 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse logger.debug("CallController: started chat") self.shouldSuspendChat = true // There are no invitations in the model, as it was processed by NSE - Task { - _ = try? await justRefreshCallInvitations() - logger.debug("CallController: updated call invitations chat") - // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") - // Extract the call information from the push notification payload - let m = ChatModel.shared - if let contactId = payload.dictionaryPayload["contactId"] as? String, - let invitation = m.callInvitations[contactId] { + _ = try? justRefreshCallInvitations() + logger.debug("CallController: updated call invitations chat") + // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") + // Extract the call information from the push notification payload + let m = ChatModel.shared + if let contactId = payload.dictionaryPayload["contactId"] as? String, + let invitation = m.callInvitations[contactId] { + let update = self.cxCallUpdate(invitation: invitation) + if let uuid = invitation.callkitUUID { + logger.debug("CallController: report pushkit call via CallKit") let update = self.cxCallUpdate(invitation: invitation) - if let uuid = invitation.callkitUUID { - logger.debug("CallController: report pushkit call via CallKit") - let update = self.cxCallUpdate(invitation: invitation) - do { - try await self.provider.reportNewIncomingCall(with: uuid, update: update) - } catch { - _ = await MainActor.run { m.callInvitations.removeValue(forKey: contactId) } + self.provider.reportNewIncomingCall(with: uuid, update: update) { error in + if error != nil { + m.callInvitations.removeValue(forKey: contactId) } + // Tell PushKit that the notification is handled. completion() - } else { - self.reportExpiredCall(update: update, completion) } } else { - self.reportExpiredCall(payload: payload, completion) + self.reportExpiredCall(update: update, completion) } + } else { + self.reportExpiredCall(payload: payload, completion) } } diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index e1b2356bed..eeb7bf14f4 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -85,16 +85,13 @@ struct UserPicker: View { .padding(8) .opacity(userPickerVisible ? 1.0 : 0.0) .onAppear { - // This check prevents the call of listUsers after the app is suspended, and the database is closed. - if case .active = scenePhase { - Task { - do { - let users = try await listUsersAsync() - await MainActor.run { m.users = users } - } catch { - logger.error("Error loading users \(responseError(error))") - } + do { + // This check prevents the call of listUsers after the app is suspended, and the database is closed. + if case .active = scenePhase { + m.users = try listUsers() } + } catch let error { + logger.error("Error loading users \(responseError(error))") } } } From d5eb7b78111a2336c668736bfc397ed4aa052ede Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 21 Aug 2024 10:27:58 +0100 Subject: [PATCH 025/440] core: api to change user of pending connections (#4681) * core: add api that enables change of owner user id for pending connections * old user sends request, incognito handling and coverage * call agent inside set connection api * only set user id if servers match * simplify * reduce test noise * return invitation when a newone is created * add test for profile on different server * refactor namings * update simplexmq * refactor * test improvements and simplify * remove fdescribes * simplify and reduce vars scope * put if back * refactor, change error * refactor view * refactor --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 35 +++++++++ src/Simplex/Chat/Controller.hs | 3 + src/Simplex/Chat/Store/Direct.hs | 14 ++++ src/Simplex/Chat/View.hs | 16 ++++ tests/ChatTests/Profiles.hs | 125 +++++++++++++++++++++++++++++++ 7 files changed, 195 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 05f5935554..08973f9be8 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 571d148bdf9e29403b9bcec6f7daeff815a4db38 + tag: 1cbf8c0015a8014bcc9d1894055d734947176684 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 2802bfda6e..a77e8ee167 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."571d148bdf9e29403b9bcec6f7daeff815a4db38" = "1p7m7i4wdyvd2wypm04cx4w960vnyqa9wkfnlpv72cz83k0xirxf"; + "https://github.com/simplex-chat/simplexmq.git"."1cbf8c0015a8014bcc9d1894055d734947176684" = "01x8fvwzahz32fmxc2mjvxl1z6agwr3952hl4qjswym76r1dpc1s"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 2d3ed3ca84..5899be6445 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1655,6 +1655,40 @@ processChatCommand' vr = \case case conn'_ of Just conn' -> pure $ CRConnectionIncognitoUpdated user conn' Nothing -> throwChatError CEConnectionIncognitoChangeProhibited + APIChangeConnectionUser connId newUserId -> withUser $ \user@User {userId} -> do + conn <- withFastStore $ \db -> getPendingContactConnection db userId connId + let PendingContactConnection {pccConnStatus, connReqInv} = conn + case (pccConnStatus, connReqInv) of + (ConnNew, Just cReqInv) -> do + newUser <- privateGetUser newUserId + conn' <- ifM (canKeepLink cReqInv newUser) (updateConnRecord user conn newUser) (recreateConn user conn newUser) + pure $ CRConnectionUserChanged user conn conn' newUser + _ -> throwChatError CEConnectionUserChangeProhibited + where + canKeepLink :: ConnReqInvitation -> User -> CM Bool + canKeepLink (CRInvitationUri crData _) newUser = do + let ConnReqUriData {crSmpQueues = q :| _} = crData + SMPQueueUri {queueAddress = SMPQueueAddress {smpServer}} = q + cfg <- asks config + newUserServers <- L.map (\ServerCfg {server} -> protoServer server) . useServers cfg SPSMP <$> withFastStore' (`getProtocolServers` newUser) + pure $ smpServer `elem` newUserServers + updateConnRecord user@User {userId} conn@PendingContactConnection {customUserProfileId} newUser = do + withAgent $ \a -> changeConnectionUser a (aUserId user) (aConnId' conn) (aUserId newUser) + withFastStore' $ \db -> do + conn' <- updatePCCUser db userId conn newUserId + forM_ customUserProfileId $ \profileId -> + deletePCCIncognitoProfile db user profileId + pure conn' + recreateConn user conn@PendingContactConnection {customUserProfileId} newUser = do + subMode <- chatReadVar subscriptionMode + (agConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOn subMode + conn' <- withFastStore' $ \db -> do + deleteConnectionRecord db user connId + forM_ customUserProfileId $ \profileId -> + deletePCCIncognitoProfile db user profileId + createDirectConnection db newUser agConnId cReq ConnNew Nothing subMode initialChatVersion PQSupportOn + deleteAgentConnectionAsync user (aConnId' conn) + pure conn' APIConnectPlan userId cReqUri -> withUserId userId $ \user -> CRConnectionPlan user <$> connectPlan user cReqUri APIConnect userId incognito (Just (ACR SCMInvitation cReq)) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do @@ -7790,6 +7824,7 @@ chatCommandP = "/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> ((Just <$> strP) <|> A.takeByteString $> Nothing)), "/_connect " *> (APIAddContact <$> A.decimal <*> incognitoOnOffP), "/_set incognito :" *> (APISetConnectionIncognito <$> A.decimal <* A.space <*> onOffP), + "/_set conn user :" *> (APIChangeConnectionUser <$> A.decimal <* A.space <*> A.decimal), ("/connect" <|> "/c") *> (Connect <$> incognitoP <* A.space <*> ((Just <$> strP) <|> A.takeTill isSpace $> Nothing)), ("/connect" <|> "/c") *> (AddContact <$> incognitoP), ForwardMessage <$> chatNameP <* " <- @" <*> displayName <* A.space <*> msgTextP, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index dbfa361c70..c4f056c778 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -403,6 +403,7 @@ data ChatCommand | APIAddContact UserId IncognitoEnabled | AddContact IncognitoEnabled | APISetConnectionIncognito Int64 IncognitoEnabled + | APIChangeConnectionUser Int64 UserId -- new user id to switch connection to | APIConnectPlan UserId AConnectionRequestUri | APIConnect UserId IncognitoEnabled (Maybe AConnectionRequestUri) | Connect IncognitoEnabled (Maybe AConnectionRequestUri) @@ -628,6 +629,7 @@ data ChatResponse | CRVersionInfo {versionInfo :: CoreVersionInfo, chatMigrations :: [UpMigration], agentMigrations :: [UpMigration]} | CRInvitation {user :: User, connReqInvitation :: ConnReqInvitation, connection :: PendingContactConnection} | CRConnectionIncognitoUpdated {user :: User, toConnection :: PendingContactConnection} + | CRConnectionUserChanged {user :: User, fromConnection :: PendingContactConnection, toConnection :: PendingContactConnection, newUser :: User} | CRConnectionPlan {user :: User, connectionPlan :: ConnectionPlan} | CRSentConfirmation {user :: User, connection :: PendingContactConnection} | CRSentInvitation {user :: User, connection :: PendingContactConnection, customUserProfile :: Maybe Profile} @@ -1191,6 +1193,7 @@ data ChatErrorType | CEAgentCommandError {message :: String} | CEInvalidFileDescription {message :: String} | CEConnectionIncognitoChangeProhibited + | CEConnectionUserChangeProhibited | CEPeerChatVRangeIncompatible | CEInternalError {message :: String} | CEException {message :: String} diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 508a992543..c0f007b6ac 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -64,6 +64,7 @@ module Simplex.Chat.Store.Direct createAcceptedContact, getUserByContactRequestId, getPendingContactConnections, + updatePCCUser, getContactConnections, getConnectionById, getConnectionsContacts, @@ -424,6 +425,19 @@ updatePCCIncognito db User {userId} conn customUserProfileId = do (customUserProfileId, updatedAt, userId, pccConnId conn) pure (conn :: PendingContactConnection) {customUserProfileId, updatedAt} +updatePCCUser :: DB.Connection -> UserId -> PendingContactConnection -> UserId -> IO PendingContactConnection +updatePCCUser db userId conn newUserId = do + updatedAt <- getCurrentTime + DB.execute + db + [sql| + UPDATE connections + SET user_id = ?, custom_user_profile_id = NULL, updated_at = ? + WHERE user_id = ? AND connection_id = ? + |] + (newUserId, updatedAt, userId, pccConnId conn) + pure (conn :: PendingContactConnection) {customUserProfileId = Nothing, updatedAt} + deletePCCIncognitoProfile :: DB.Connection -> User -> ProfileId -> IO () deletePCCIncognitoProfile db User {userId} profileId = DB.execute diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 3d519c3b5c..e154b5b902 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -172,6 +172,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRVersionInfo info _ _ -> viewVersionInfo logLevel info CRInvitation u cReq _ -> ttyUser u $ viewConnReqInvitation cReq CRConnectionIncognitoUpdated u c -> ttyUser u $ viewConnectionIncognitoUpdated c + CRConnectionUserChanged u c c' nu -> ttyUser u $ viewConnectionUserChanged u c nu c' CRConnectionPlan u connectionPlan -> ttyUser u $ viewConnectionPlan connectionPlan CRSentConfirmation u _ -> ttyUser u ["confirmation sent!"] CRSentInvitation u _ customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView @@ -1498,6 +1499,20 @@ viewConnectionIncognitoUpdated PendingContactConnection {pccConnId, customUserPr | isJust customUserProfileId = ["connection " <> sShow pccConnId <> " changed to incognito"] | otherwise = ["connection " <> sShow pccConnId <> " changed to non incognito"] +viewConnectionUserChanged :: User -> PendingContactConnection -> User -> PendingContactConnection -> [StyledString] +viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId, connReqInv} User {localDisplayName = n'} PendingContactConnection {connReqInv = connReqInv'} = + case (connReqInv, connReqInv') of + (Just cReqInv, Just cReqInv') + | cReqInv /= cReqInv' -> [userChangedStr <> ", new link:"] <> newLink cReqInv' + _ -> [userChangedStr] + where + userChangedStr = "connection " <> sShow pccConnId <> " changed from user " <> plain n <> " to user " <> plain n' + newLink cReqInv = + [ "", + (plain . strEncode) (simplexChatInvitation cReqInv), + "" + ] + viewConnectionPlan :: ConnectionPlan -> [StyledString] viewConnectionPlan = \case CPInvitationLink ilp -> case ilp of @@ -2025,6 +2040,7 @@ viewChatError isCmd logLevel testView = \case CEAgentCommandError e -> ["agent command error: " <> plain e] CEInvalidFileDescription e -> ["invalid file description: " <> plain e] CEConnectionIncognitoChangeProhibited -> ["incognito mode change prohibited"] + CEConnectionUserChangeProhibited -> ["incognito mode change prohibited for user"] CEPeerChatVRangeIncompatible -> ["peer chat protocol version range incompatible"] CEInternalError e -> ["internal chat error: " <> plain e] CEException e -> ["exception: " <> plain e] diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 23004677c9..43ad5ba841 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -63,6 +63,11 @@ chatProfileTests = do describe "contact aliases" $ do it "set contact alias" testSetAlias it "set connection alias" testSetConnectionAlias + describe "pending connection users" $ do + it "change user for pending connection" testChangePCCUser + it "change from incognito profile connects as new user" testChangePCCUserFromIncognito + it "change user for pending connection and later set incognito connects as incognito in changed profile" testChangePCCUserAndThenIncognito + it "change user for user without matching servers creates new connection" testChangePCCUserDiffSrv describe "preferences" $ do it "set contact preferences" testSetContactPrefs it "feature offers" testFeatureOffers @@ -1557,6 +1562,126 @@ testSetAlias = testChat2 aliceProfile bobProfile $ alice ##> "/contacts" alice <## "bob (Bob)" +testChangePCCUser :: HasCallStack => FilePath -> IO () +testChangePCCUser = testChat2 aliceProfile bobProfile $ + \alice bob -> do + -- Create a new invite + alice ##> "/connect" + inv <- getInvitation alice + -- Create new user and go back to original user + alice ##> "/create user alisa" + showActiveUser alice "alisa" + alice ##> "/create user alisa2" + showActiveUser alice "alisa2" + alice ##> "/user alice" + showActiveUser alice "alice (Alice)" + -- Change connection to newly created user + alice ##> "/_set conn user :1 2" + alice <## "connection 1 changed from user alice to user alisa" + alice ##> "/user alisa" + showActiveUser alice "alisa" + -- Change connection back to other user + alice ##> "/_set conn user :1 3" + alice <## "connection 1 changed from user alisa to user alisa2" + alice ##> "/user alisa2" + showActiveUser alice "alisa2" + -- Connect + bob ##> ("/connect " <> inv) + bob <## "confirmation sent!" + concurrently_ + (alice <## "bob (Bob): contact is connected") + (bob <## "alisa2: contact is connected") + +testChangePCCUserFromIncognito :: HasCallStack => FilePath -> IO () +testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $ + \alice bob -> do + -- Create a new invite and set as incognito + alice ##> "/connect" + inv <- getInvitation alice + alice ##> "/_set incognito :1 on" + alice <## "connection 1 changed to incognito" + -- Create new user and go back to original user + alice ##> "/create user alisa" + showActiveUser alice "alisa" + alice ##> "/user alice" + showActiveUser alice "alice (Alice)" + -- Change connection to newly created user + alice ##> "/_set conn user :1 2" + alice <## "connection 1 changed from user alice to user alisa" + alice `hasContactProfiles` ["alice"] + alice ##> "/user alisa" + showActiveUser alice "alisa" + -- Change connection back to initial user + alice ##> "/_set conn user :1 1" + alice <## "connection 1 changed from user alisa to user alice" + alice ##> "/user alice" + showActiveUser alice "alice (Alice)" + -- Connect + bob ##> ("/connect " <> inv) + bob <## "confirmation sent!" + concurrently_ + (alice <## "bob (Bob): contact is connected") + (bob <## "alice (Alice): contact is connected") + +testChangePCCUserAndThenIncognito :: HasCallStack => FilePath -> IO () +testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ + \alice bob -> do + -- Create a new invite and set as incognito + alice ##> "/connect" + inv <- getInvitation alice + -- Create new user and go back to original user + alice ##> "/create user alisa" + showActiveUser alice "alisa" + alice ##> "/user alice" + showActiveUser alice "alice (Alice)" + -- Change connection to newly created user + alice ##> "/_set conn user :1 2" + alice <## "connection 1 changed from user alice to user alisa" + alice ##> "/user alisa" + showActiveUser alice "alisa" + -- Change connection to incognito and make sure it's attached to the newly created user profile + alice ##> "/_set incognito :1 on" + alice <## "connection 1 changed to incognito" + bob ##> ("/connect " <> inv) + bob <## "confirmation sent!" + alisaIncognito <- getTermLine alice + concurrentlyN_ + [ bob <## (alisaIncognito <> ": contact is connected"), + do + alice <## ("bob (Bob): contact is connected, your incognito profile for this contact is " <> alisaIncognito) + alice <## ("use /i bob to print out this incognito profile again") + ] + +testChangePCCUserDiffSrv :: HasCallStack => FilePath -> IO () +testChangePCCUserDiffSrv = testChat2 aliceProfile bobProfile $ + \alice bob -> do + -- Create a new invite + alice ##> "/connect" + _ <- getInvitation alice + alice ##> "/_set incognito :1 on" + alice <## "connection 1 changed to incognito" + -- Create new user with different servers + alice ##> "/create user alisa" + showActiveUser alice "alisa" + alice #$> ("/smp smp://2345-w==@smp2.example.im smp://3456-w==@smp3.example.im:5224", id, "ok") + alice ##> "/user alice" + showActiveUser alice "alice (Alice)" + -- Change connection to newly created user and use the newly created connection + alice ##> "/_set conn user :1 2" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" + alice `hasContactProfiles` ["alice"] + alice ##> "/user alisa" + showActiveUser alice "alisa" + -- Connect + bob ##> ("/connect " <> inv) + bob <## "confirmation sent!" + concurrently_ + (alice <## "bob (Bob): contact is connected") + (bob <## "alisa: contact is connected") + testSetConnectionAlias :: HasCallStack => FilePath -> IO () testSetConnectionAlias = testChat2 aliceProfile bobProfile $ \alice bob -> do From 0438f35539c9713b7f1da35d28b7b26e5afed4c0 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Wed, 21 Aug 2024 17:05:59 +0300 Subject: [PATCH 026/440] ios: fix notifying the recipient when hanging up a call in ios15 (#4731) Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Views/Call/WebRTCClient.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/ios/Shared/Views/Call/WebRTCClient.swift b/apps/ios/Shared/Views/Call/WebRTCClient.swift index 0b4917c103..79e0dea16c 100644 --- a/apps/ios/Shared/Views/Call/WebRTCClient.swift +++ b/apps/ios/Shared/Views/Call/WebRTCClient.swift @@ -411,6 +411,15 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg } func endCall() { + if #available(iOS 16.0, *) { + _endCall() + } else { + // Fixes `connection.close()` getting locked up in iOS15 + DispatchQueue.global(qos: .utility).async { self._endCall() } + } + } + + private func _endCall() { guard let call = activeCall.wrappedValue else { return } logger.debug("WebRTCClient: ending the call") activeCall.wrappedValue = nil From 5cb8badb22ad6c749cf886ceb2d80998720006e4 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:46:42 +0000 Subject: [PATCH 027/440] android, desktop: layout changes for settings items (#4732) * android, desktop: layout changes for settings items * section paddings * toggle * padding and border * padding --------- Co-authored-by: Evgeny Poberezkin --- .../common/views/call/CallView.android.kt | 6 +- .../views/usersettings/Appearance.android.kt | 14 ++-- .../usersettings/SettingsView.android.kt | 6 +- .../chat/simplex/common/ui/theme/Theme.kt | 2 + .../simplex/common/views/chat/ChatInfoView.kt | 8 +-- .../common/views/chat/ChatItemInfoView.kt | 2 +- .../common/views/chat/ContactPreferences.kt | 8 +-- .../views/chat/SelectableChatItemToolbars.kt | 6 +- .../views/chat/group/AddGroupMembersView.kt | 14 ++-- .../views/chat/group/GroupChatInfoView.kt | 6 +- .../views/chat/group/WelcomeMessageView.kt | 8 +-- .../common/views/chatlist/ChatListView.kt | 4 +- .../views/chatlist/ServersSummaryView.kt | 26 +++++--- .../common/views/chatlist/UserPicker.kt | 6 +- .../common/views/database/DatabaseView.kt | 4 +- .../common/views/helpers/DefaultSwitch.kt | 6 +- .../helpers/ExposedDropDownSettingRow.kt | 2 +- .../simplex/common/views/helpers/Section.kt | 60 ++++++++--------- .../common/views/helpers/ThemeModeEditor.kt | 10 +-- .../newchat/ContactConnectionInfoView.kt | 2 +- .../common/views/newchat/NewChatSheet.kt | 12 ++-- .../common/views/newchat/NewChatView.kt | 2 +- .../usersettings/AdvancedNetworkSettings.kt | 4 +- .../common/views/usersettings/Appearance.kt | 6 +- .../views/usersettings/DeveloperView.kt | 3 +- .../views/usersettings/HiddenProfileView.kt | 5 +- .../views/usersettings/NetworkAndServers.kt | 40 ++++++------ .../views/usersettings/PrivacySettings.kt | 8 +-- .../views/usersettings/ProtocolServerView.kt | 4 +- .../common/views/usersettings/SettingsView.kt | 64 +++++++++---------- .../views/usersettings/UserAddressView.kt | 2 +- .../views/usersettings/UserProfilesView.kt | 3 +- .../resources/MR/images/ic_account_box.svg | 2 +- .../MR/images/ic_account_circle_filled.svg | 2 +- .../commonMain/resources/MR/images/ic_add.svg | 2 +- .../resources/MR/images/ic_add_link.svg | 2 +- .../resources/MR/images/ic_add_photo.svg | 2 +- .../resources/MR/images/ic_add_reaction.svg | 2 +- .../MR/images/ic_add_reaction_filled.svg | 2 +- .../resources/MR/images/ic_all_inclusive.svg | 2 +- .../MR/images/ic_arrow_back_ios_new.svg | 2 +- .../resources/MR/images/ic_arrow_downward.svg | 2 +- .../MR/images/ic_arrow_drop_down.svg | 1 + .../resources/MR/images/ic_arrow_drop_up.svg | 1 + .../resources/MR/images/ic_arrow_forward.svg | 2 +- .../MR/images/ic_arrow_forward_ios.svg | 2 +- .../resources/MR/images/ic_arrow_upward.svg | 2 +- .../MR/images/ic_arrows_left_right.svg | 2 +- .../resources/MR/images/ic_article.svg | 2 +- .../MR/images/ic_attach_file_filled.svg | 2 +- .../MR/images/ic_attach_file_filled_500.svg | 2 +- .../resources/MR/images/ic_avg_pace.svg | 2 +- .../resources/MR/images/ic_back_hand.svg | 2 +- .../resources/MR/images/ic_backspace.svg | 2 +- .../resources/MR/images/ic_backup.svg | 2 +- .../resources/MR/images/ic_battery_2_bar.svg | 2 +- .../resources/MR/images/ic_battery_3_bar.svg | 2 +- .../resources/MR/images/ic_bluetooth.svg | 2 +- .../resources/MR/images/ic_blur_on.svg | 2 +- .../resources/MR/images/ic_bolt.svg | 2 +- .../resources/MR/images/ic_bolt_filled.svg | 2 +- .../resources/MR/images/ic_bolt_off.svg | 4 +- .../MR/images/ic_brand_awareness_filled.svg | 2 +- .../resources/MR/images/ic_cable.svg | 2 +- .../resources/MR/images/ic_call.svg | 2 +- .../resources/MR/images/ic_call_500.svg | 2 +- .../resources/MR/images/ic_call_end.svg | 2 +- .../MR/images/ic_call_end_filled.svg | 2 +- .../resources/MR/images/ic_call_filled.svg | 2 +- .../resources/MR/images/ic_camera_enhance.svg | 2 +- .../resources/MR/images/ic_cancel_filled.svg | 2 +- .../resources/MR/images/ic_chat.svg | 2 +- .../resources/MR/images/ic_chat_bubble.svg | 2 +- .../resources/MR/images/ic_check.svg | 2 +- .../resources/MR/images/ic_check_circle.svg | 2 +- .../MR/images/ic_check_circle_filled.svg | 2 +- .../resources/MR/images/ic_check_filled.svg | 2 +- .../resources/MR/images/ic_chevron_right.svg | 2 +- .../resources/MR/images/ic_circle.svg | 2 +- .../resources/MR/images/ic_circle_filled.svg | 2 +- .../resources/MR/images/ic_close.svg | 2 +- .../resources/MR/images/ic_code.svg | 2 +- .../resources/MR/images/ic_collapse_all.svg | 2 +- .../resources/MR/images/ic_content_copy.svg | 2 +- .../resources/MR/images/ic_content_paste.svg | 2 +- .../resources/MR/images/ic_database.svg | 2 +- .../resources/MR/images/ic_delete.svg | 2 +- .../resources/MR/images/ic_delete_forever.svg | 2 +- .../MR/images/ic_delete_forever_filled.svg | 2 +- .../resources/MR/images/ic_desktop.svg | 2 +- .../commonMain/resources/MR/images/ic_dns.svg | 2 +- .../MR/images/ic_do_not_disturb_on.svg | 2 +- .../resources/MR/images/ic_do_not_touch.svg | 2 +- .../resources/MR/images/ic_done_filled.svg | 2 +- .../resources/MR/images/ic_double_check.svg | 2 +- .../resources/MR/images/ic_download.svg | 2 +- .../resources/MR/images/ic_draft.svg | 2 +- .../resources/MR/images/ic_draft_filled.svg | 2 +- .../MR/images/ic_drive_folder_upload.svg | 2 +- .../resources/MR/images/ic_edit.svg | 2 +- .../resources/MR/images/ic_edit_filled.svg | 2 +- .../resources/MR/images/ic_edit_note.svg | 2 +- .../MR/images/ic_electrical_services.svg | 2 +- .../resources/MR/images/ic_engineering.svg | 2 +- .../resources/MR/images/ic_error.svg | 2 +- .../resources/MR/images/ic_error_filled.svg | 2 +- .../resources/MR/images/ic_expand_all.svg | 2 +- .../resources/MR/images/ic_expand_less.svg | 1 - .../resources/MR/images/ic_expand_more.svg | 1 - .../resources/MR/images/ic_filter_list.svg | 2 +- .../resources/MR/images/ic_flag.svg | 2 +- .../images/ic_flip_camera_android_filled.svg | 2 +- .../resources/MR/images/ic_folder_filled.svg | 4 +- .../resources/MR/images/ic_folder_open.svg | 2 +- .../resources/MR/images/ic_folder_pen.svg | 2 +- .../resources/MR/images/ic_forum.svg | 2 +- .../resources/MR/images/ic_forward.svg | 2 +- .../resources/MR/images/ic_github.svg | 2 +- .../resources/MR/images/ic_group.svg | 2 +- .../resources/MR/images/ic_headphones.svg | 2 +- .../resources/MR/images/ic_help.svg | 2 +- .../resources/MR/images/ic_history.svg | 2 +- .../resources/MR/images/ic_id_card.svg | 2 +- .../resources/MR/images/ic_image.svg | 2 +- .../resources/MR/images/ic_info.svg | 2 +- .../resources/MR/images/ic_inventory_2.svg | 2 +- .../resources/MR/images/ic_ios_share.svg | 2 +- .../resources/MR/images/ic_keyboard.svg | 2 +- .../MR/images/ic_keyboard_arrow_down.svg | 2 +- .../resources/MR/images/ic_keyboard_voice.svg | 2 +- .../MR/images/ic_keyboard_voice_filled.svg | 2 +- .../resources/MR/images/ic_light_mode.svg | 2 +- .../resources/MR/images/ic_lightbulb.svg | 2 +- .../resources/MR/images/ic_link.svg | 2 +- .../resources/MR/images/ic_lock.svg | 2 +- .../resources/MR/images/ic_lock_filled.svg | 2 +- .../MR/images/ic_lock_open_right.svg | 2 +- .../resources/MR/images/ic_login.svg | 2 +- .../resources/MR/images/ic_logout.svg | 2 +- .../resources/MR/images/ic_mail.svg | 2 +- .../resources/MR/images/ic_mail_filled.svg | 2 +- .../MR/images/ic_manage_accounts.svg | 2 +- .../resources/MR/images/ic_maps_ugc.svg | 2 +- .../MR/images/ic_mark_chat_unread.svg | 2 +- .../resources/MR/images/ic_match_case.svg | 2 +- .../resources/MR/images/ic_menu.svg | 2 +- .../commonMain/resources/MR/images/ic_mic.svg | 2 +- .../resources/MR/images/ic_mic_filled.svg | 2 +- .../resources/MR/images/ic_mic_off.svg | 2 +- .../resources/MR/images/ic_more_horiz.svg | 2 +- .../resources/MR/images/ic_more_vert.svg | 2 +- .../resources/MR/images/ic_music_note.svg | 2 +- .../resources/MR/images/ic_note_add.svg | 2 +- .../resources/MR/images/ic_notifications.svg | 2 +- .../MR/images/ic_notifications_off.svg | 2 +- .../MR/images/ic_notifications_off_filled.svg | 2 +- .../resources/MR/images/ic_open_in_new.svg | 2 +- .../MR/images/ic_outline_terminal.svg | 2 +- .../resources/MR/images/ic_palette.svg | 2 +- .../resources/MR/images/ic_pause_filled.svg | 2 +- .../resources/MR/images/ic_pending.svg | 2 +- .../resources/MR/images/ic_pending_filled.svg | 2 +- .../resources/MR/images/ic_person.svg | 2 +- .../resources/MR/images/ic_person_add.svg | 2 +- .../resources/MR/images/ic_person_add_500.svg | 2 +- .../resources/MR/images/ic_person_off.svg | 2 +- .../MR/images/ic_phone_bluetooth_speaker.svg | 2 +- .../MR/images/ic_phone_in_talk_filled.svg | 2 +- .../resources/MR/images/ic_photo_camera.svg | 2 +- .../MR/images/ic_play_arrow_filled.svg | 2 +- .../MR/images/ic_power_settings_new.svg | 2 +- .../resources/MR/images/ic_priority_high.svg | 2 +- .../resources/MR/images/ic_qr_code.svg | 2 +- .../resources/MR/images/ic_question_mark.svg | 2 +- .../MR/images/ic_radio_button_unchecked.svg | 2 +- .../images/ic_radiowaves_up_forward_1_bar.svg | 2 +- .../images/ic_radiowaves_up_forward_2_bar.svg | 2 +- .../images/ic_radiowaves_up_forward_3_bar.svg | 2 +- .../images/ic_radiowaves_up_forward_4_bar.svg | 2 +- .../resources/MR/images/ic_redeem.svg | 2 +- .../resources/MR/images/ic_refresh.svg | 2 +- .../resources/MR/images/ic_repeat_one.svg | 2 +- .../resources/MR/images/ic_replay.svg | 2 +- .../resources/MR/images/ic_reply.svg | 2 +- .../resources/MR/images/ic_report.svg | 2 +- .../resources/MR/images/ic_report_filled.svg | 2 +- .../resources/MR/images/ic_restart_alt.svg | 2 +- .../resources/MR/images/ic_ring_volume.svg | 2 +- .../resources/MR/images/ic_safety_divider.svg | 2 +- .../resources/MR/images/ic_schedule.svg | 2 +- .../MR/images/ic_schedule_filled.svg | 2 +- .../resources/MR/images/ic_search.svg | 2 +- .../resources/MR/images/ic_search_500.svg | 2 +- .../resources/MR/images/ic_security.svg | 2 +- .../resources/MR/images/ic_settings.svg | 2 +- .../MR/images/ic_settings_backup_restore.svg | 2 +- .../MR/images/ic_settings_ethernet.svg | 2 +- .../resources/MR/images/ic_settings_phone.svg | 2 +- .../resources/MR/images/ic_share.svg | 2 +- .../resources/MR/images/ic_share_filled.svg | 2 +- .../resources/MR/images/ic_shield.svg | 2 +- .../resources/MR/images/ic_smart_display.svg | 2 +- .../resources/MR/images/ic_smartphone.svg | 2 +- .../resources/MR/images/ic_smartphone_300.svg | 2 +- .../resources/MR/images/ic_star.svg | 2 +- .../resources/MR/images/ic_star_filled.svg | 2 +- .../resources/MR/images/ic_star_off.svg | 4 +- .../resources/MR/images/ic_start.svg | 2 +- .../resources/MR/images/ic_stop_filled.svg | 2 +- .../ic_supervised_user_circle_filled.svg | 2 +- .../MR/images/ic_swap_horizontal_circle.svg | 2 +- .../ic_swap_horizontal_circle_filled.svg | 2 +- .../resources/MR/images/ic_sync_problem.svg | 2 +- .../commonMain/resources/MR/images/ic_tag.svg | 2 +- .../resources/MR/images/ic_task.svg | 2 +- .../resources/MR/images/ic_theater_comedy.svg | 2 +- .../MR/images/ic_theater_comedy_filled.svg | 2 +- .../resources/MR/images/ic_timer.svg | 2 +- .../resources/MR/images/ic_timer_filled.svg | 2 +- .../resources/MR/images/ic_toast.svg | 2 +- .../resources/MR/images/ic_toggle_on.svg | 2 +- .../resources/MR/images/ic_translate.svg | 2 +- .../resources/MR/images/ic_travel_explore.svg | 2 +- .../resources/MR/images/ic_upgrade.svg | 2 +- .../resources/MR/images/ic_upload_file.svg | 2 +- .../commonMain/resources/MR/images/ic_usb.svg | 2 +- .../resources/MR/images/ic_verified_user.svg | 2 +- .../resources/MR/images/ic_videocam.svg | 2 +- .../MR/images/ic_videocam_filled.svg | 2 +- .../resources/MR/images/ic_videocam_off.svg | 2 +- .../resources/MR/images/ic_visibility.svg | 2 +- .../MR/images/ic_visibility_filled.svg | 2 +- .../resources/MR/images/ic_visibility_off.svg | 2 +- .../MR/images/ic_visibility_off_filled.svg | 2 +- .../resources/MR/images/ic_volume_down.svg | 2 +- .../resources/MR/images/ic_volume_up.svg | 2 +- .../resources/MR/images/ic_vpn_key_filled.svg | 2 +- .../MR/images/ic_vpn_key_off_filled.svg | 2 +- .../resources/MR/images/ic_warning.svg | 2 +- .../resources/MR/images/ic_warning_filled.svg | 2 +- .../resources/MR/images/ic_wifi.svg | 2 +- .../resources/MR/images/ic_wifi_off.svg | 2 +- .../resources/MR/images/ic_wifi_tethering.svg | 2 +- .../views/usersettings/Appearance.desktop.kt | 8 +-- .../usersettings/SettingsView.desktop.kt | 10 ++- 245 files changed, 401 insertions(+), 394 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_drop_down.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_drop_up.svg delete mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_less.svg delete mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_more.svg diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 8d4ab3206a..d05172a7a1 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -457,7 +457,7 @@ private fun DisabledBackgroundCallsButton() { ) { Text(stringResource(MR.strings.system_restricted_background_in_call_title), color = WarningOrange) Spacer(Modifier.width(8.dp)) - IconButton(onClick = { show = false }, Modifier.size(24.dp)) { + IconButton(onClick = { show = false }, Modifier.size(22.dp)) { Icon(painterResource(MR.images.ic_close), null, tint = WarningOrange) } } @@ -539,7 +539,7 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni Icon( painterResource(MR.images.ic_call_500), stringResource(MR.strings.permissions_record_audio), - Modifier.size(24.dp), + Modifier.size(22.dp), tint = Color(0xFFFFFFD8) ) } @@ -547,7 +547,7 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni Icon( painterResource(MR.images.ic_videocam), stringResource(MR.strings.permissions_camera), - Modifier.size(24.dp), + Modifier.size(22.dp), tint = Color(0xFFFFFFD8) ) } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index 0c105a1da1..b985601962 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -13,12 +13,15 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.stringResource @@ -112,13 +115,13 @@ fun AppearanceScope.AppearanceLayout( } } - SectionDividerSpaced(maxTopPadding = true) + SectionDividerSpaced() ThemesSection(systemDarkTheme) - SectionDividerSpaced(maxTopPadding = true) + SectionDividerSpaced() ProfileImageSection() - SectionDividerSpaced() + SectionDividerSpaced(maxTopPadding = true) SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { LazyRow { @@ -129,7 +132,8 @@ fun AppearanceScope.AppearanceLayout( contentDescription = "", contentScale = ContentScale.Fit, modifier = Modifier - .shadow(if (item == icon.value) 1.dp else 0.dp, ambientColor = colors.secondaryVariant) + .border(1.dp, color = if (item == icon.value) colors.secondaryVariant else Color.Transparent, RoundedCornerShape(percent = 22)) + .clip(RoundedCornerShape(percent = 22)) .size(70.dp) .clickable { changeIcon(item) } .padding(10.dp) @@ -143,7 +147,7 @@ fun AppearanceScope.AppearanceLayout( } } - SectionDividerSpaced(maxBottomPadding = true) + SectionDividerSpaced(maxTopPadding = true) FontScaleSection() SectionBottomSpacer() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt index f2c3d86ab5..38818a123d 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt @@ -19,9 +19,9 @@ actual fun SettingsSectionApp( withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { SectionView(stringResource(MR.strings.settings_section_title_app)) { - SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) }, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp) + SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) }) + SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }) AppVersionItem(showVersion) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index fb922fe52b..595c22e3e2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -607,6 +607,8 @@ val DEFAULT_SPACE_AFTER_ICON = 4.dp val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2 val DEFAULT_BOTTOM_PADDING = 48.dp val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp +val DEFAULT_MIN_SECTION_ITEM_HEIGHT = 50.dp +val DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL = 15.dp val DEFAULT_START_MODAL_WIDTH = 388.dp val DEFAULT_MIN_CENTER_MODAL_WIDTH = 590.dp diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 5d9b33d416..24416ff49e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -599,7 +599,7 @@ fun ChatInfoLayout( } } } - SectionDividerSpaced() + SectionDividerSpaced(maxBottomPadding = false) val conn = contact.activeConn if (conn != null) { @@ -616,7 +616,7 @@ fun ChatInfoLayout( ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) } SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName)) } - SectionDividerSpaced() + SectionDividerSpaced(maxTopPadding = true) } if (contact.ready && contact.active) { @@ -650,7 +650,7 @@ fun ChatInfoLayout( } } } - SectionDividerSpaced() + SectionDividerSpaced(maxBottomPadding = false) } SectionView { @@ -970,7 +970,7 @@ fun InfoViewActionButton( Icon( icon, contentDescription = null, - Modifier.size(24.dp * fontSizeSqrtMultiplier), + Modifier.size(22.dp * fontSizeSqrtMultiplier), tint = if (disabledLook) MaterialTheme.colors.secondary else MaterialTheme.colors.onPrimary ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index a5593693c0..d0e972965a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -224,7 +224,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools Row( Modifier .fillMaxWidth() - .sizeIn(minHeight = 46.dp) + .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) .padding(PaddingValues(horizontal = DEFAULT_PADDING)) .clickable { expanded.value = !expanded.value }, horizontalArrangement = Arrangement.spacedBy(12.dp), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt index f7f0a55372..725367e150 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt @@ -93,22 +93,22 @@ private fun ContactPreferencesLayout( TimedMessagesFeatureSection(featuresAllowed, contact.mergedPreferences.timedMessages, timedMessages, onTTLUpdated) { allowed, ttl -> applyPrefs(featuresAllowed.copy(timedMessagesAllowed = allowed, timedMessagesTTL = ttl ?: currentFeaturesAllowed.timedMessagesTTL)) } - SectionDividerSpaced(true, maxBottomPadding = false) + SectionDividerSpaced(true) val allowFullDeletion: MutableState = remember(featuresAllowed) { mutableStateOf(featuresAllowed.fullDelete) } FeatureSection(ChatFeature.FullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, allowFullDeletion) { applyPrefs(featuresAllowed.copy(fullDelete = it)) } - SectionDividerSpaced(true, maxBottomPadding = false) + SectionDividerSpaced(true) val allowReactions: MutableState = remember(featuresAllowed) { mutableStateOf(featuresAllowed.reactions) } FeatureSection(ChatFeature.Reactions, user.fullPreferences.reactions.allow, contact.mergedPreferences.reactions, allowReactions) { applyPrefs(featuresAllowed.copy(reactions = it)) } - SectionDividerSpaced(true, maxBottomPadding = false) + SectionDividerSpaced(true) val allowVoice: MutableState = remember(featuresAllowed) { mutableStateOf(featuresAllowed.voice) } FeatureSection(ChatFeature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) { applyPrefs(featuresAllowed.copy(voice = it)) } - SectionDividerSpaced(true, maxBottomPadding = false) + SectionDividerSpaced(true) val allowCalls: MutableState = remember(featuresAllowed) { mutableStateOf(featuresAllowed.calls) } FeatureSection(ChatFeature.Calls, user.fullPreferences.calls.allow, contact.mergedPreferences.calls, allowCalls) { applyPrefs(featuresAllowed.copy(calls = it)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt index 9934d853f3..2aebe07306 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt @@ -66,7 +66,7 @@ fun SelectedItemsBottomToolbar( Icon( painterResource(MR.images.ic_delete), null, - Modifier.size(24.dp), + Modifier.size(22.dp), tint = if (!deleteEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error ) } @@ -75,7 +75,7 @@ fun SelectedItemsBottomToolbar( Icon( painterResource(MR.images.ic_flag), null, - Modifier.size(24.dp), + Modifier.size(22.dp), tint = if (!moderateEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error ) } @@ -84,7 +84,7 @@ fun SelectedItemsBottomToolbar( Icon( painterResource(MR.images.ic_share), null, - Modifier.size(24.dp), + Modifier.size(22.dp), tint = if (allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index a7562ff3b5..18a3a0d14d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -4,6 +4,7 @@ import SectionBottomSpacer import SectionCustomFooter import SectionDividerSpaced import SectionItemView +import SectionItemViewWithoutMinPadding import SectionSpacer import SectionView import androidx.compose.foundation.* @@ -177,7 +178,7 @@ fun AddGroupMembersLayout( InviteSectionFooter(selectedContactsCount = selectedContacts.size, allowModifyMembers, clearSelection) } SectionDividerSpaced(maxTopPadding = true) - SectionView(stringResource(MR.strings.select_contacts)) { + SectionView(stringResource(MR.strings.select_contacts).uppercase()) { SectionItemView(padding = PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF)) { SearchRowView(searchText) } @@ -255,7 +256,8 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec Text( String.format(generalGetString(MR.strings.num_contacts_selected), selectedContactsCount), color = MaterialTheme.colors.secondary, - fontSize = 12.sp + lineHeight = 18.sp, + fontSize = 14.sp ) Box( Modifier.clickable { if (enabled) clearSelection() } @@ -263,14 +265,16 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec Text( stringResource(MR.strings.clear_contacts_selection_button), color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - fontSize = 12.sp + lineHeight = 18.sp, + fontSize = 14.sp, ) } } else { Text( stringResource(MR.strings.no_contacts_selected), color = MaterialTheme.colors.secondary, - fontSize = 12.sp + lineHeight = 18.sp, + fontSize = 14.sp, ) } } @@ -318,7 +322,7 @@ fun ContactCheckRow( icon = painterResource(MR.images.ic_circle) iconColor = MaterialTheme.colors.secondary } - SectionItemView( + SectionItemViewWithoutMinPadding( click = if (enabled) { { if (prohibitedToInviteIncognito) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index ea07231eb0..9b1bb45d8f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -365,7 +365,7 @@ fun GroupChatInfoLayout( SearchRowView(searchText) } } - SectionItemView(minHeight = 54.dp) { + SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) { MemberRow(groupInfo.membership, user = true) } } @@ -373,7 +373,7 @@ fun GroupChatInfoLayout( items(filteredMembers.value) { member -> Divider() val showMenu = remember { mutableStateOf(false) } - SectionItemViewLongClickable({ showMemberInfo(member) }, { showMenu.value = true }, minHeight = 54.dp) { + SectionItemViewLongClickable({ showMemberInfo(member) }, { showMenu.value = true }, minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) { DropDownMenuForMember(chat.remoteHostId, member, groupInfo, showMenu) MemberRow(member, onClick = { showMemberInfo(member) }) } @@ -513,7 +513,7 @@ private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() - verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - MemberProfileImage(size = 46.dp, member) + MemberProfileImage(size = DEFAULT_MIN_SECTION_ITEM_HEIGHT, member) Spacer(Modifier.width(DEFAULT_PADDING_HALF)) Column { Row(verticalAlignment = Alignment.CenterVertically) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index 794d6d4a20..b6312e4d82 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -133,13 +133,7 @@ private fun GroupWelcomeLayout( val clipboard = LocalClipboardManager.current CopyTextButton { clipboard.setText(AnnotatedString(wt.value)) } - Divider( - Modifier.padding( - start = DEFAULT_PADDING_HALF, - top = 8.dp, - end = DEFAULT_PADDING_HALF, - bottom = 8.dp) - ) + SectionDividerSpaced(maxBottomPadding = false) SaveButton( save = save, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 6f595e2ccf..11b1006f41 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -219,7 +219,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, contentColor = Color.White ) { - Icon(painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(24.dp * fontSizeSqrtMultiplier)) + Icon(painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(22.dp * fontSizeSqrtMultiplier)) } } } @@ -520,7 +520,7 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState Icon( painterResource(MR.images.ic_search), contentDescription = null, - Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(24.dp * fontSizeSqrtMultiplier), + Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(22.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.secondary ) SearchTextField( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt index f5343efc18..6219252b54 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -546,15 +546,19 @@ fun XFTPServerSummaryLayout(summary: XFTPServerSummary, statsStartedAt: Instant, }) { Text(generalGetString(MR.strings.open_server_settings_button)) } + if (summary.stats != null || summary.sessions != null) { + SectionDividerSpaced() + } } if (summary.stats != null) { - SectionDividerSpaced() XFTPStatsView(stats = summary.stats, rh = rh, statsStartedAt = statsStartedAt) + if (summary.sessions != null) { + SectionDividerSpaced(maxTopPadding = true) + } } if (summary.sessions != null) { - SectionDividerSpaced() ServerSessionsView(summary.sessions) } } @@ -581,20 +585,24 @@ fun SMPServerSummaryLayout(summary: SMPServerSummary, statsStartedAt: Instant, r }) { Text(generalGetString(MR.strings.open_server_settings_button)) } + SectionDividerSpaced() } if (summary.stats != null) { - SectionDividerSpaced() SMPStatsView(stats = summary.stats, remoteHostInfo = rh, statsStartedAt = statsStartedAt) + if (summary.subs != null || summary.sessions != null) { + SectionDividerSpaced(maxTopPadding = true) + } } if (summary.subs != null) { - SectionDividerSpaced() SMPSubscriptionsSection(subs = summary.subs, summary = summary, rh = rh) + if (summary.sessions != null) { + SectionDividerSpaced() + } } if (summary.sessions != null) { - SectionDividerSpaced() ServerSessionsView(summary.sessions) } } @@ -854,7 +862,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta val statsStartedAt = it.statsStartedAt SMPStatsView(totals.stats, statsStartedAt, rh) - SectionDividerSpaced() + SectionDividerSpaced(maxTopPadding = true) SMPSubscriptionsSection(totals) SectionDividerSpaced() @@ -886,7 +894,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta footer = generalGetString(MR.strings.servers_info_proxied_servers_section_footer), rh = rh ) - SectionDividerSpaced() + SectionDividerSpaced(maxTopPadding = true) } ServerSessionsView(totals.sessions) @@ -903,7 +911,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta val previouslyUsedXFTPServers = xftpSummary.previouslyUsedXFTPServers XFTPStatsView(totals.stats, statsStartedAt, rh) - SectionDividerSpaced() + SectionDividerSpaced(maxTopPadding = true) if (currentlyUsedXFTPServers.isNotEmpty()) { XFTPServersListView( @@ -930,7 +938,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta } } - SectionDividerSpaced() + SectionDividerSpaced(maxBottomPadding = false) SectionView { ReconnectAllServersButton(rh) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index dad3192e45..e15bc3863e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -262,7 +262,7 @@ fun UserProfilePickerItem( Row( Modifier .fillMaxWidth() - .sizeIn(minHeight = 46.dp) + .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) .combinedClickable( enabled = enabled, onClick = if (u.activeUser) openSettings else onClick, @@ -330,7 +330,7 @@ fun RemoteHostPickerItem(h: RemoteHostInfo, onLongClick: () -> Unit = {}, action Modifier .fillMaxWidth() .background(color = if (h.activeHost) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) - .sizeIn(minHeight = 46.dp) + .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) .combinedClickable( onClick = onClick, onLongClick = onLongClick @@ -373,7 +373,7 @@ fun LocalDevicePickerItem(active: Boolean, onLongClick: () -> Unit = {}, onClick Modifier .fillMaxWidth() .background(color = if (active) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) - .sizeIn(minHeight = 46.dp) + .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) .combinedClickable( onClick = if (active) {{}} else onClick, onLongClick = onLongClick, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 87ee0723f8..333fda307a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -195,7 +195,7 @@ fun DatabaseLayout( stringResource(MR.strings.stop_chat_to_enable_database_actions) } ) - SectionDividerSpaced() + SectionDividerSpaced(maxTopPadding = true) } SectionView(stringResource(MR.strings.chat_database_section)) { @@ -264,7 +264,7 @@ fun DatabaseLayout( disabled = operationsDisabled ) } - SectionDividerSpaced(maxTopPadding = true) + SectionDividerSpaced() SectionView(stringResource(MR.strings.files_and_media_section).uppercase()) { val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0 diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultSwitch.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultSwitch.kt index 75abc67b46..79255fb0bf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultSwitch.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultSwitch.kt @@ -25,9 +25,9 @@ fun DefaultSwitch( ) ) { val color = if (checked) MaterialTheme.colors.primary.copy(alpha = 0.3f) else MaterialTheme.colors.secondary.copy(alpha = 0.3f) - val size = with(LocalDensity.current) { Size(46.dp.toPx(), 28.dp.toPx()) } - val offset = with(LocalDensity.current) { Offset(1.dp.toPx(), 10.dp.toPx()) } - val radius = with(LocalDensity.current) { 28.dp.toPx() } + val size = with(LocalDensity.current) { Size(40.dp.toPx(), 26.dp.toPx()) } + val offset = with(LocalDensity.current) { Offset(4.dp.toPx(), 11.dp.toPx()) } + val radius = with(LocalDensity.current) { 13.dp.toPx() } Switch( checked = checked, onCheckedChange = onCheckedChange, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt index 7e57bda928..4141fd2ead 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt @@ -50,7 +50,7 @@ fun ExposedDropDownSetting( ) Spacer(Modifier.size(12.dp)) Icon( - if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less), + if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up), generalGetString(MR.strings.icon_descr_more_button), tint = MaterialTheme.colors.secondary ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index d3a3a0ff9c..facecd2398 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* -import chat.simplex.common.model.NotificationsMode import chat.simplex.common.platform.onRightClick import chat.simplex.common.platform.windowWidth import chat.simplex.common.ui.theme.* @@ -20,7 +19,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.SelectableCard import chat.simplex.common.views.usersettings.SettingsActionItemWithContent import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.stringResource @Composable fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit)) { @@ -28,7 +26,7 @@ fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), if (title != null) { Text( title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, - modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), fontSize = 12.sp + modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = DEFAULT_PADDING), fontSize = 12.sp ) } Column(Modifier.padding(padding).fillMaxWidth()) { content() } @@ -101,13 +99,13 @@ fun SectionViewSelectableCards( @Composable fun SectionItemView( click: (() -> Unit)? = null, - minHeight: Dp = 46.dp, + minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT, disabled: Boolean = false, extraPadding: Boolean = false, padding: PaddingValues = if (extraPadding) - PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING) + PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL) else - PaddingValues(horizontal = DEFAULT_PADDING), + PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), content: (@Composable RowScope.() -> Unit) ) { val modifier = Modifier @@ -122,10 +120,9 @@ fun SectionItemView( } @Composable -fun SectionItemViewLongClickable( - click: () -> Unit, - longClick: () -> Unit, - minHeight: Dp = 46.dp, +fun SectionItemViewWithoutMinPadding( + click: (() -> Unit)? = null, + minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT, disabled: Boolean = false, extraPadding: Boolean = false, padding: PaddingValues = if (extraPadding) @@ -133,6 +130,22 @@ fun SectionItemViewLongClickable( else PaddingValues(horizontal = DEFAULT_PADDING), content: (@Composable RowScope.() -> Unit) +) { + SectionItemView(click, minHeight, disabled, extraPadding, padding, content) +} + +@Composable +fun SectionItemViewLongClickable( + click: () -> Unit, + longClick: () -> Unit, + minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT, + disabled: Boolean = false, + extraPadding: Boolean = false, + padding: PaddingValues = if (extraPadding) + PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL) + else + PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), + content: (@Composable RowScope.() -> Unit) ) { val modifier = Modifier .fillMaxWidth() @@ -149,30 +162,11 @@ fun SectionItemViewLongClickable( } } -@Composable -fun SectionItemViewWithIcon( - click: (() -> Unit)? = null, - minHeight: Dp = 46.dp, - disabled: Boolean = false, - padding: PaddingValues = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING), - content: (@Composable RowScope.() -> Unit) -) { - val modifier = Modifier - .fillMaxWidth() - .sizeIn(minHeight = minHeight) - Row( - if (click == null || disabled) modifier.padding(padding) else modifier.clickable(onClick = click).padding(padding), - verticalAlignment = Alignment.CenterVertically - ) { - content() - } -} - @Composable fun SectionItemViewSpaceBetween( click: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, - minHeight: Dp = 46.dp, + minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT, padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING), disabled: Boolean = false, content: (@Composable RowScope.() -> Unit) @@ -181,7 +175,7 @@ fun SectionItemViewSpaceBetween( .fillMaxWidth() .sizeIn(minHeight = minHeight) Row( - if (click == null || disabled) modifier.padding(padding) else modifier + if (click == null || disabled) modifier.padding(padding).padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL) else modifier .combinedClickable(onClick = click, onLongClick = onLongClick).padding(padding) .onRightClick { onLongClick?.invoke() }, horizontalArrangement = Arrangement.SpaceBetween, @@ -254,9 +248,9 @@ fun SectionDividerSpaced(maxTopPadding: Boolean = false, maxBottomPadding: Boole Divider( Modifier.padding( start = DEFAULT_PADDING_HALF, - top = if (maxTopPadding) 37.dp else 27.dp, + top = if (maxTopPadding) DEFAULT_PADDING + 18.dp else DEFAULT_PADDING + 2.dp, end = DEFAULT_PADDING_HALF, - bottom = if (maxBottomPadding) 37.dp else 27.dp) + bottom = if (maxBottomPadding) DEFAULT_PADDING + 18.dp else DEFAULT_PADDING + 2.dp) ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt index 24f9bf539b..a77290d90f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt @@ -1,6 +1,7 @@ package chat.simplex.common.views.helpers import SectionBottomSpacer +import SectionDividerSpaced import SectionItemView import SectionSpacer import SectionView @@ -203,11 +204,12 @@ fun ModalData.UserWallpaperEditor( } ) - SectionSpacer() + SectionDividerSpaced() AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor) - SectionSpacer() + SectionDividerSpaced(maxBottomPadding = false) + ImportExportThemeSection(null, remember { chatModel.currentUser }.value?.uiThemes) { withBGApi { themeModeOverride.value = it @@ -440,11 +442,11 @@ fun ModalData.ChatWallpaperEditor( } ) - SectionSpacer() + SectionDividerSpaced() AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor) - SectionSpacer() + SectionDividerSpaced(maxBottomPadding = false) ImportExportThemeSection(themeModeOverride.value, remember { chatModel.currentUser }.value?.uiThemes) { withBGApi { themeModeOverride.value = it diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 09a6c447d5..88e483e92d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -142,7 +142,7 @@ private fun ContactConnectionInfoLayout( } SectionTextFooter(sharedProfileInfo(chatModel, contactConnection.incognito)) - SectionDividerSpaced(maxBottomPadding = false) + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) DeleteButton(deleteConnection) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 3146c14589..0621c5509f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -262,13 +262,13 @@ private fun NewChatSheetLayout( } } } - SectionDividerSpaced(maxBottomPadding = false) val deletedContactTypes = listOf(ContactType.CHAT_DELETED) val deletedChats by remember(chatModel.chats.value, deletedContactTypes) { derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) } } if (deletedChats.isNotEmpty()) { + SectionDividerSpaced(maxBottomPadding = false) Row(modifier = sectionModifier) { SectionView { SectionItemView( @@ -295,16 +295,20 @@ private fun NewChatSheetLayout( } } } - SectionDividerSpaced(maxBottomPadding = false) } } } item { if (filteredContactChats.isNotEmpty() && !oneHandUI.value) { + if (searchText.value.text.isNotEmpty()) { + Spacer(Modifier.height(DEFAULT_PADDING)) + } else { + SectionDividerSpaced() + } Text( stringResource(MR.strings.contact_list_header_title).uppercase(), color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, - modifier = sectionModifier.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF, bottom = DEFAULT_PADDING_HALF), fontSize = 12.sp + modifier = sectionModifier.padding(start = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF), fontSize = 12.sp ) } } @@ -364,7 +368,7 @@ private fun ContactsSearchBar( Icon( painterResource(MR.images.ic_search), contentDescription = null, - Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(24.dp * fontSizeSqrtMultiplier), + Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(22.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.secondary ) SearchTextField( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index a872f2548d..419d3b6ed7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -293,7 +293,7 @@ private fun PasteLinkView(rhId: Long?, pastedLink: MutableState, showQRC @Composable fun LinkTextView(link: String, share: Boolean) { val clipboard = LocalClipboardManager.current - Row(Modifier.fillMaxWidth().heightIn(min = 46.dp).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { + Row(Modifier.fillMaxWidth().heightIn(min = DEFAULT_MIN_SECTION_ITEM_HEIGHT).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { Box(Modifier.weight(1f).clickable { chatModel.markShowingInvitationUsed() clipboard.shareText(link) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index ed68c7b013..3c8ab2b70a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -446,7 +446,7 @@ fun IntSettingRow(title: String, selection: MutableState, values: List ) Spacer(Modifier.size(4.dp)) Icon( - if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less), + if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up), generalGetString(MR.strings.invite_to_group_button), modifier = Modifier.padding(start = 8.dp), tint = MaterialTheme.colors.secondary @@ -506,7 +506,7 @@ fun TimeoutSettingRow(title: String, selection: MutableState, values: List ) Spacer(Modifier.size(4.dp)) Icon( - if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less), + if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up), generalGetString(MR.strings.invite_to_group_button), modifier = Modifier.padding(start = 8.dp), tint = MaterialTheme.colors.secondary diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 5fda4b7d1d..d8993307d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -549,13 +549,13 @@ object AppearanceScope { }, ) } - SectionDividerSpaced(maxTopPadding = true) + SectionDividerSpaced() CustomizeThemeColorsSection(currentTheme) { name -> editColor(name) } - SectionSpacer() + SectionDividerSpaced(maxBottomPadding = false) val currentOverrides = remember(currentTheme) { ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get()) } val canResetColors = currentTheme.base.hasChangedAnyColor(currentOverrides) @@ -889,7 +889,7 @@ object AppearanceScope { val hexTrimmed = currentColor.toReadableHex().replaceFirst("#ff", "#") val savedColor by remember(wallpaperType) { mutableStateOf(initialColor) } - Row(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).height(46.dp)) { + Row(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).height(DEFAULT_MIN_SECTION_ITEM_HEIGHT)) { Box(Modifier.weight(1f).fillMaxHeight().background(savedColor).clickable { currentColor = savedColor onColorChange(currentColor) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt index 8a6e4efe32..9dfdf23085 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt @@ -1,6 +1,7 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer +import SectionDividerSpaced import SectionSpacer import SectionTextFooter import SectionView @@ -43,7 +44,7 @@ fun DeveloperView( ) } if (devTools.value) { - SectionSpacer() + SectionDividerSpaced(maxTopPadding = true) SectionView(stringResource(MR.strings.developer_options_section).uppercase()) { SettingsPreferenceItem(painterResource(MR.images.ic_drive_folder_upload), stringResource(MR.strings.confirm_database_upgrades), m.controller.appPrefs.confirmDBUpgrades) if (appPlatform.isDesktop) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt index c60e8e6bf4..b97a686e22 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt @@ -3,6 +3,7 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionItemView import SectionItemViewSpaceBetween +import SectionItemViewWithoutMinPadding import SectionSpacer import SectionTextFooter import SectionView @@ -74,10 +75,10 @@ private fun HiddenProfileLayout( val confirmValid by remember { derivedStateOf { confirmHidePassword.value == "" || hidePassword.value == confirmHidePassword.value } } val saveDisabled by remember { derivedStateOf { hidePassword.value == "" || !passwordValid || confirmHidePassword.value == "" || !confirmValid } } SectionView(stringResource(MR.strings.hidden_profile_password).uppercase()) { - SectionItemView { + SectionItemViewWithoutMinPadding { PassphraseField(hidePassword, generalGetString(MR.strings.password_to_show), isValid = { passwordValid }, showStrength = true) } - SectionItemView { + SectionItemViewWithoutMinPadding { PassphraseField(confirmHidePassword, stringResource(MR.strings.confirm_password), isValid = { confirmValid }, dependsOn = hidePassword) } SectionItemViewSpaceBetween({ saveProfilePassword(hidePassword.value) }, disabled = saveDisabled, minHeight = TextFieldDefaults.MinHeight) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 62ba287879..74e6bf5910 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -264,30 +264,26 @@ fun SocksProxySettings( .fillMaxWidth() ) { AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) - SectionView { - SectionItemView { - DefaultConfigurableTextField( - hostUnsaved, - stringResource(MR.strings.host_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validHost, - keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), - keyboardType = KeyboardType.Text, - ) - } - SectionItemView { - DefaultConfigurableTextField( - portUnsaved, - stringResource(MR.strings.port_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validPort, - keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }), - keyboardType = KeyboardType.Number, - ) - } + SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + DefaultConfigurableTextField( + hostUnsaved, + stringResource(MR.strings.host_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validHost, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Text, + ) + DefaultConfigurableTextField( + portUnsaved, + stringResource(MR.strings.port_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validPort, + keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }), + keyboardType = KeyboardType.Number, + ) } - Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 27.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp)) + SectionDividerSpaced(maxBottomPadding = false) SectionView { SectionItemView({ diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 578a505e0c..084dfc20d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -104,13 +104,13 @@ fun PrivacySettingsView( } SettingsPreferenceItem(painterResource(MR.images.ic_security), stringResource(MR.strings.protect_ip_address), chatModel.controller.appPrefs.privacyAskToApproveRelays) } - SectionCustomFooter { + SectionTextFooter( if (chatModel.controller.appPrefs.privacyAskToApproveRelays.state.value) { - Text(stringResource(MR.strings.app_will_ask_to_confirm_unknown_file_servers)) + stringResource(MR.strings.app_will_ask_to_confirm_unknown_file_servers) } else { - Text(stringResource(MR.strings.without_tor_or_vpn_ip_address_will_be_visible_to_file_servers)) + stringResource(MR.strings.without_tor_or_vpn_ip_address_will_be_visible_to_file_servers) } - } + ) val currentUser = chatModel.currentUser.value if (currentUser != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index 88018dd1c9..3a1a1cb8f3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -110,7 +110,7 @@ private fun PresetServer( ) } } - SectionDividerSpaced(maxTopPadding = true) + SectionDividerSpaced() UseServerSection(true, testing, server, testServer, onUpdate, onDelete) } @@ -150,7 +150,7 @@ private fun CustomServer( } } } - SectionDividerSpaced() + SectionDividerSpaced(maxTopPadding = true) UseServerSection(valid.value, testing, server, testServer, onUpdate, onDelete) if (valid.value) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 6c07a9ceeb..b50e905f39 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -3,7 +3,6 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced import SectionItemView -import SectionItemViewWithIcon import SectionView import TextIconSpaced import androidx.compose.desktop.ui.tooling.preview.Preview @@ -124,8 +123,8 @@ fun SettingsLayout( SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { ProfilePreview(profile, stopped = stopped) } - SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden, drawerState) } } }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden, drawerState) } } }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped) ChatPreferencesItem(showCustomModal, stopped = stopped) } else if (chatModel.localUserCreated.value == false) { SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.create_chat_profile), { @@ -137,35 +136,35 @@ fun SettingsLayout( CreateProfile(chatModel, close) } } - }, disabled = stopped, extraPadding = true) + }, disabled = stopped) } if (appPlatform.isDesktop) { - SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped) } else { - SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal { it, close -> ConnectDesktopView(close) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal { it, close -> ConnectDesktopView(close) }, disabled = stopped) } - SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped) } SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_settings)) { - SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }, extraPadding = true) + SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }) DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) } SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_help)) { - SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }) if (!chatModel.desktopNoUserNoRemote) { - SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped) } - SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary) } SectionDividerSpaced() @@ -190,18 +189,19 @@ expect fun SettingsSectionApp( ) @Composable private fun DatabaseItem(encrypted: Boolean, saved: Boolean, openDatabaseView: () -> Unit, stopped: Boolean) { - SectionItemViewWithIcon(openDatabaseView) { + SectionItemView(openDatabaseView) { Row( Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - Row(Modifier.weight(1f)) { + Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) { Icon( painterResource(MR.images.ic_database), contentDescription = stringResource(MR.strings.database_passphrase_and_export), tint = if (encrypted && (appPlatform.isAndroid || !saved)) MaterialTheme.colors.secondary else WarningOrange, ) - TextIconSpaced(true) + TextIconSpaced(false) Text(stringResource(MR.strings.database_passphrase_and_export)) } if (stopped) { @@ -225,8 +225,7 @@ expect fun SettingsSectionApp( PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close) }() }), - disabled = stopped, - extraPadding = true + disabled = stopped ) } @@ -241,27 +240,26 @@ fun ChatLockItem( click = showSettingsModal { SimplexLockView(ChatModel, currentLAMode, setPerformLA) }, icon = if (performLA.value) painterResource(MR.images.ic_lock_filled) else painterResource(MR.images.ic_lock), text = stringResource(MR.strings.chat_lock), - iconColor = if (performLA.value) SimplexGreen else MaterialTheme.colors.secondary, - extraPadding = false, + iconColor = if (performLA.value) SimplexGreen else MaterialTheme.colors.secondary ) { Text(if (performLA.value) remember { currentLAMode.state }.value.text else generalGetString(MR.strings.la_mode_off), color = MaterialTheme.colors.secondary) } } @Composable private fun ContributeItem(uriHandler: UriHandler) { - SectionItemViewWithIcon({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) { + SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) { Icon( painterResource(MR.images.ic_keyboard), contentDescription = "GitHub", tint = MaterialTheme.colors.secondary, ) - TextIconSpaced(extraPadding = true) + TextIconSpaced() Text(generalGetString(MR.strings.contribute), color = MaterialTheme.colors.primary) } } @Composable private fun RateAppItem(uriHandler: UriHandler) { - SectionItemViewWithIcon({ + SectionItemView({ runCatching { uriHandler.openUriCatching("market://details?id=chat.simplex.app") } .onFailure { uriHandler.openUriCatching("https://play.google.com/store/apps/details?id=chat.simplex.app") } } @@ -271,19 +269,19 @@ fun ChatLockItem( contentDescription = "Google Play", tint = MaterialTheme.colors.secondary, ) - TextIconSpaced(extraPadding = true) + TextIconSpaced() Text(generalGetString(MR.strings.rate_the_app), color = MaterialTheme.colors.primary) } } @Composable private fun StarOnGithubItem(uriHandler: UriHandler) { - SectionItemViewWithIcon({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { + SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { Icon( painter = painterResource(MR.images.ic_github), contentDescription = "GitHub", tint = MaterialTheme.colors.secondary, ) - TextIconSpaced(extraPadding = true) + TextIconSpaced() Text(generalGetString(MR.strings.star_on_github), color = MaterialTheme.colors.primary) } } @@ -301,7 +299,7 @@ fun ChatLockItem( } @Composable fun TerminalAlwaysVisibleItem(pref: SharedPreference, onChange: (Boolean) -> Unit) { - SettingsActionItemWithContent(painterResource(MR.images.ic_engineering), stringResource(MR.strings.terminal_always_visible), extraPadding = false) { + SettingsActionItemWithContent(painterResource(MR.images.ic_engineering), stringResource(MR.strings.terminal_always_visible)) { DefaultSwitch( checked = remember { pref.state }.value, onCheckedChange = onChange, @@ -348,7 +346,7 @@ fun unchangedHintPreferences(): Boolean = appPreferences.hintPreferences.all { ( @Composable fun AppVersionItem(showVersion: () -> Unit) { - SectionItemViewWithIcon(showVersion) { AppVersionText() } + SectionItemView(showVersion) { AppVersionText() } } @Composable fun AppVersionText() { @@ -439,7 +437,7 @@ fun PreferenceToggle( checked: Boolean, onChange: (Boolean) -> Unit = {}, ) { - SettingsActionItemWithContent(null, text, disabled = disabled, extraPadding = true,) { + SettingsActionItemWithContent(null, text, disabled = disabled) { DefaultSwitch( checked = checked, onCheckedChange = onChange, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index e6d3202cd0..b357272e16 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -185,7 +185,7 @@ private fun UserAddressLayout( CreateAddressButton(createAddress) SectionTextFooter(stringResource(MR.strings.create_address_and_let_people_connect)) } - SectionDividerSpaced(maxBottomPadding = false) + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) SectionView { LearnMoreButton(learnMore) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index 78a7637a14..4a9af6e822 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -4,6 +4,7 @@ import SectionBottomSpacer import SectionDivider import SectionItemView import SectionItemViewSpaceBetween +import SectionItemViewWithoutMinPadding import SectionSpacer import SectionTextFooter import SectionView @@ -277,7 +278,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: ( @Composable fun PasswordAndAction(label: StringResource, color: Color = MaterialTheme.colors.primary) { SectionView() { - SectionItemView { + SectionItemViewWithoutMinPadding { PassphraseField(actionPassword, generalGetString(MR.strings.profile_password), isValid = { passwordValid }, showStrength = true) } SectionItemViewSpaceBetween({ doAction(actionPassword.value) }, disabled = !actionEnabled, minHeight = TextFieldDefaults.MinHeight) { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_account_box.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_account_box.svg index 9680161c4f..07f5645f79 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_account_box.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_account_box.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_account_circle_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_account_circle_filled.svg index f63966fbc8..db9ba45fab 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_account_circle_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_account_circle_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add.svg index 7da1daade8..da3f68daa4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_link.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_link.svg index 6e3ec1e453..1530035ead 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_link.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_link.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_photo.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_photo.svg index 3197ae8bb6..be7fcc8f54 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_photo.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_photo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_reaction.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_reaction.svg index 62ee67166c..ecca4f4872 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_reaction.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_reaction.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_reaction_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_reaction_filled.svg index 0475962f63..47b4790bc5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_reaction_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_reaction_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_all_inclusive.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_all_inclusive.svg index 4675bb6642..7628baecda 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_all_inclusive.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_all_inclusive.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_back_ios_new.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_back_ios_new.svg index 6940e4d004..f4f2bc772f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_back_ios_new.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_back_ios_new.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_downward.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_downward.svg index c09c1c4d66..e9bd252628 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_downward.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_downward.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_drop_down.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_drop_down.svg new file mode 100644 index 0000000000..f2cfb3cbdd --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_drop_down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_drop_up.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_drop_up.svg new file mode 100644 index 0000000000..90c085ae7b --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_drop_up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_forward.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_forward.svg index d393921c05..0993b22658 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_forward.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_forward.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_forward_ios.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_forward_ios.svg index dd07f2bad9..58b93e79a1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_forward_ios.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_forward_ios.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_upward.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_upward.svg index 88b20124f3..0a54ed27db 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_upward.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_upward.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrows_left_right.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrows_left_right.svg index de3a39b826..4b5a982e41 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrows_left_right.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrows_left_right.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_article.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_article.svg index 05d4d6fe6e..4cd46fc7cb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_article.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_article.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_attach_file_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_attach_file_filled.svg index 5ff6754f22..dcebed008e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_attach_file_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_attach_file_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_attach_file_filled_500.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_attach_file_filled_500.svg index 04ef1d379f..d0897cd912 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_attach_file_filled_500.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_attach_file_filled_500.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_avg_pace.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_avg_pace.svg index d3052a6c84..78d1b86471 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_avg_pace.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_avg_pace.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_back_hand.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_back_hand.svg index 41013ff66c..b9a3887c8f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_back_hand.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_back_hand.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_backspace.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_backspace.svg index 5bb12b42ca..a82b16c3ce 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_backspace.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_backspace.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_backup.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_backup.svg index 5d4b0c0dc6..e076c55993 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_backup.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_backup.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_battery_2_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_battery_2_bar.svg index fcdb3dfd50..3278d1d2dc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_battery_2_bar.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_battery_2_bar.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_battery_3_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_battery_3_bar.svg index 819fe590c9..9f73bc20f9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_battery_3_bar.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_battery_3_bar.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bluetooth.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bluetooth.svg index 53bc5becaa..d39581566e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bluetooth.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bluetooth.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_blur_on.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_blur_on.svg index e8767ccc86..d729371159 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_blur_on.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_blur_on.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt.svg index 4c686c282a..aa276fa8c1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_filled.svg index 012a8e2a45..2fd1fdcb38 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_off.svg index 7b003c8e7c..4278ea764a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_off.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_off.svg @@ -1,8 +1,8 @@ \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cable.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cable.svg index 1ca40515b7..f786d0c2e9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cable.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cable.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call.svg index 19c840a5cf..05577c3d0b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_500.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_500.svg index f2e1e9af6b..c18f78b148 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_500.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_500.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_end.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_end.svg index 0f21f36e83..2bcb5949f9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_end.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_end.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_end_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_end_filled.svg index ceb32f98f5..2d1e207e0e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_end_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_end_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_filled.svg index c0277fd5b6..100c933b7b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_call_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_camera_enhance.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_camera_enhance.svg index b07fbd7a87..baea2e28e9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_camera_enhance.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_camera_enhance.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cancel_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cancel_filled.svg index 44e901ff3c..41c39e11ef 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cancel_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cancel_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat.svg index c263166229..82e58ead79 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat_bubble.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat_bubble.svg index 132c514e92..78e3bf42dc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat_bubble.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat_bubble.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check.svg index e3d6eab069..a5445b0d5f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_circle.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_circle.svg index d44f9a2d1b..813adcea2d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_circle.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_circle.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_circle_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_circle_filled.svg index 42f1a37725..ac2f200c6f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_circle_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_circle_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_filled.svg index e3d6eab069..a5445b0d5f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_check_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_right.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_right.svg index ac6025580b..3c33462417 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_right.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_right.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_circle.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_circle.svg index d0da456188..dd6d17343d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_circle.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_circle.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_circle_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_circle_filled.svg index e8eebbdc7c..65d942e3ad 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_circle_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_circle_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_close.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_close.svg index f3803e3cac..b24eaf6cb9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_close.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_close.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_code.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_code.svg index 8554e557b0..918bf1a005 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_code.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_code.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_collapse_all.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_collapse_all.svg index a380594e79..673cb66084 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_collapse_all.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_collapse_all.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_content_copy.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_content_copy.svg index 2f4b964368..f8dde3e39f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_content_copy.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_content_copy.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_content_paste.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_content_paste.svg index 84a0be536b..b162257525 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_content_paste.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_content_paste.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_database.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_database.svg index 8b47047b60..1195169889 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_database.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_database.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete.svg index 1709888116..ea3f90edaa 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete_forever.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete_forever.svg index 519fbc87fb..af3f3c5fb3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete_forever.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete_forever.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete_forever_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete_forever_filled.svg index c8c1d4cea5..8b46878258 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete_forever_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_delete_forever_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_desktop.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_desktop.svg index e9c30f5199..aa60447d7e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_desktop.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_desktop.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_dns.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_dns.svg index c1056cefc6..932f45e6b9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_dns.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_dns.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_do_not_disturb_on.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_do_not_disturb_on.svg index 9a0d038ba5..14df478255 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_do_not_disturb_on.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_do_not_disturb_on.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_do_not_touch.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_do_not_touch.svg index 5ea1a5f2e3..698df39e9c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_do_not_touch.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_do_not_touch.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_done_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_done_filled.svg index e3d6eab069..a5445b0d5f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_done_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_done_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_double_check.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_double_check.svg index 2c651607df..a5c0797f3e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_double_check.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_double_check.svg @@ -1,6 +1,6 @@ - + Created with Fabric.js 5.3.0 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_download.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_download.svg index 8d62bebda6..65e1af1fb6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_download.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_download.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_draft.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_draft.svg index bbe311364f..edced6eb83 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_draft.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_draft.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_draft_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_draft_filled.svg index 06140a055d..f40173226c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_draft_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_draft_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_drive_folder_upload.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_drive_folder_upload.svg index ec3d03510e..94a21b1a9d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_drive_folder_upload.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_drive_folder_upload.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit.svg index 400707272e..143f7e8e0b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit_filled.svg index e87e2eccaf..7550e08df5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit_note.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit_note.svg index 0c66d1bc4d..cb0488d834 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit_note.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_edit_note.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_electrical_services.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_electrical_services.svg index e6ab5bb608..03e014417d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_electrical_services.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_electrical_services.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_engineering.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_engineering.svg index 0848537041..ea2c5c3843 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_engineering.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_engineering.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_error.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_error.svg index 60296ca034..7a91448c9d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_error.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_error.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_error_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_error_filled.svg index 6aaa01f0d3..7940dc756a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_error_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_error_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_all.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_all.svg index 75b2874d5b..ec76dfb115 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_all.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_all.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_less.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_less.svg deleted file mode 100644 index 23a6402630..0000000000 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_less.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_more.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_more.svg deleted file mode 100644 index 77becd0856..0000000000 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_expand_more.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_filter_list.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_filter_list.svg index a54ee0dc55..4a9c6e8420 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_filter_list.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_filter_list.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_flag.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_flag.svg index 35dd4ac780..d2f84f3b3a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_flag.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_flag.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_flip_camera_android_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_flip_camera_android_filled.svg index da9d686ce9..0244be139f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_flip_camera_android_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_flip_camera_android_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_folder_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_folder_filled.svg index f4c284a5f2..ab47ff4136 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_folder_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_folder_filled.svg @@ -1,8 +1,8 @@ \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_folder_pen.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_folder_pen.svg index f1aba5bee6..7f2fb6e38e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_folder_pen.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_folder_pen.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_forum.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_forum.svg index 0f556da867..5271a02284 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_forum.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_forum.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_forward.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_forward.svg index 7ad0b14b70..174b80bcc7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_forward.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_forward.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_github.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_github.svg index 70baacb727..996fbd9743 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_github.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_github.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_group.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_group.svg index 74d6cdcc02..88c24ca67a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_group.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_group.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_headphones.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_headphones.svg index 2bda1e9d74..8487893d8d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_headphones.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_headphones.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_help.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_help.svg index 3bf2406ab8..b654c8eee5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_help.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_help.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_history.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_history.svg index f98f4db386..6edaf1512c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_history.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_history.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_id_card.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_id_card.svg index ff3b77c1d6..e2539c3608 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_id_card.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_id_card.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_image.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_image.svg index 408668d204..aa42e1a978 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_image.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_image.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_info.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_info.svg index aa3e5a2f4e..b854b30359 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_info.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_info.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_inventory_2.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_inventory_2.svg index c7e8aa21c1..20c39b2ed2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_inventory_2.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_inventory_2.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_ios_share.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_ios_share.svg index 099b5149bf..33f52db54b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_ios_share.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_ios_share.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard.svg index 35689927b1..a39e9c7e0b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_arrow_down.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_arrow_down.svg index c92d244c17..aaa188bed7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_arrow_down.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_arrow_down.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_voice.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_voice.svg index a938a4250f..769198e13c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_voice.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_voice.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_voice_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_voice_filled.svg index bd28745400..44c0f89fa0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_voice_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_keyboard_voice_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_light_mode.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_light_mode.svg index 858ee788cf..44bad28fc9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_light_mode.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_light_mode.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lightbulb.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lightbulb.svg index 689e5c2d5c..e84b1bd476 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lightbulb.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lightbulb.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_link.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_link.svg index b8063c1c8c..7de4497d5d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_link.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_link.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock.svg index 2bc91dde71..cec12d0d4a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock_filled.svg index 396e11fac6..798a92145a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock_open_right.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock_open_right.svg index 3188cf798e..0805267813 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock_open_right.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_lock_open_right.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_login.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_login.svg index be56f0f451..d675b77073 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_login.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_login.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_logout.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_logout.svg index 5808094655..734242b294 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_logout.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_logout.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mail.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mail.svg index 0a4b8adbd8..f45ed237f3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mail.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mail.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mail_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mail_filled.svg index 5b43f32f04..c51057b8cb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mail_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mail_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_manage_accounts.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_manage_accounts.svg index e1cc6816b1..6a5b60424c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_manage_accounts.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_manage_accounts.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_maps_ugc.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_maps_ugc.svg index 469d44a2de..282b36ba25 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_maps_ugc.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_maps_ugc.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mark_chat_unread.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mark_chat_unread.svg index 5e1e746ee9..a89a956b29 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mark_chat_unread.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mark_chat_unread.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_match_case.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_match_case.svg index b56c8cb590..20adb34c10 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_match_case.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_match_case.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_menu.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_menu.svg index a192e32f34..28070d177d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_menu.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_menu.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic.svg index a938a4250f..769198e13c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic_filled.svg index bd28745400..44c0f89fa0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic_off.svg index 1ea755eb4e..f7f2c074f4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic_off.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mic_off.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_more_horiz.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_more_horiz.svg index cc4f97431d..8d36d622c0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_more_horiz.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_more_horiz.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_more_vert.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_more_vert.svg index 9a9c7d193b..dc55c23649 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_more_vert.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_more_vert.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_music_note.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_music_note.svg index 766dc76be8..9e127f18f8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_music_note.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_music_note.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_note_add.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_note_add.svg index 8459292f6e..76ba771f33 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_note_add.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_note_add.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications.svg index bcba2ae944..afe98fc544 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications_off.svg index 556013c364..ca1de72e82 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications_off.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications_off.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications_off_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications_off_filled.svg index 647ad7dd3d..98a24503d7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications_off_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_notifications_off_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_open_in_new.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_open_in_new.svg index 9669ee635e..0edc51abef 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_open_in_new.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_open_in_new.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_outline_terminal.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_outline_terminal.svg index 07f8078e0c..f384ef36bd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_outline_terminal.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_outline_terminal.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_palette.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_palette.svg index f36ef054e0..a0da2dcca5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_palette.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_palette.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pause_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pause_filled.svg index eaa4573780..dc742eb7e8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pause_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pause_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pending.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pending.svg index 90b9dc7204..7ee6422616 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pending.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pending.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pending_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pending_filled.svg index 00fe0d41a7..eee4c2c323 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pending_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_pending_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person.svg index 0830a9963b..0a87c6589e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add.svg index 4b2a249f23..4e22be2b55 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add_500.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add_500.svg index 179dce95fb..1c67f0813a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add_500.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add_500.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_off.svg index a583950023..54cfd3e1c0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_off.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_off.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_phone_bluetooth_speaker.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_phone_bluetooth_speaker.svg index 513bb38c40..48fe86d1ec 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_phone_bluetooth_speaker.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_phone_bluetooth_speaker.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_phone_in_talk_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_phone_in_talk_filled.svg index b43c7d9c15..45cbd61bbd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_phone_in_talk_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_phone_in_talk_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_camera.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_camera.svg index aa99ac888a..ddb231b4bb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_camera.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_camera.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_play_arrow_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_play_arrow_filled.svg index 6b3ed2a581..9b5f303e35 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_play_arrow_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_play_arrow_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_power_settings_new.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_power_settings_new.svg index 0af51ad2ab..c125143e41 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_power_settings_new.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_power_settings_new.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_priority_high.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_priority_high.svg index 42b93fba83..408b4b4376 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_priority_high.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_priority_high.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_qr_code.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_qr_code.svg index d7f8763c87..621fe3c413 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_qr_code.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_qr_code.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_question_mark.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_question_mark.svg index 9c2e7e110b..4346924f9d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_question_mark.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_question_mark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radio_button_unchecked.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radio_button_unchecked.svg index 7433962953..7a3a36dc89 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radio_button_unchecked.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radio_button_unchecked.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_1_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_1_bar.svg index db1cb48d53..a7dd37c4e6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_1_bar.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_1_bar.svg @@ -1,5 +1,5 @@ - + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_2_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_2_bar.svg index 6f9a3211dd..cb6e344be9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_2_bar.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_2_bar.svg @@ -1,5 +1,5 @@ - + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_3_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_3_bar.svg index 1962851519..4bde8511f6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_3_bar.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_3_bar.svg @@ -1,5 +1,5 @@ - + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_4_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_4_bar.svg index ff7a146284..131569e9bc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_4_bar.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_4_bar.svg @@ -1,5 +1,5 @@ - + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_redeem.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_redeem.svg index a0093692cc..3178938a86 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_redeem.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_redeem.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_refresh.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_refresh.svg index a1f27d5798..bcbe9c4f34 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_refresh.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_refresh.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_repeat_one.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_repeat_one.svg index d37aa28022..014453c240 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_repeat_one.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_repeat_one.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_replay.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_replay.svg index 9f8c033f89..3e1b60666c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_replay.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_replay.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_reply.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_reply.svg index 7298dc984e..bffa9f33a2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_reply.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_reply.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report.svg index 8695857b97..c4c54f0e02 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report_filled.svg index 914c74a40a..8148ca77f2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_restart_alt.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_restart_alt.svg index a28c95673e..d2f91c991c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_restart_alt.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_restart_alt.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_ring_volume.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_ring_volume.svg index 92bc814040..4b01530b52 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_ring_volume.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_ring_volume.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_safety_divider.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_safety_divider.svg index 5517bcd740..9b532e6420 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_safety_divider.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_safety_divider.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule.svg index 4bd8e90cb6..2e806eb315 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule_filled.svg index 368e2fcc1f..b7afaf420b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_search.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_search.svg index 8a74c6d43f..7a6e668cf5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_search.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_search.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_search_500.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_search_500.svg index 18e56def19..2e36a5dec4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_search_500.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_search_500.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_security.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_security.svg index 13b5c1914e..2801bacad7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_security.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_security.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings.svg index 04b250537d..edc27762a0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_backup_restore.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_backup_restore.svg index a3cf4db7e1..22e8bcbbfb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_backup_restore.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_backup_restore.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_ethernet.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_ethernet.svg index 828c7b8a8b..8fd3d489c9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_ethernet.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_ethernet.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_phone.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_phone.svg index 7280127ceb..6231a35d21 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_phone.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_settings_phone.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_share.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_share.svg index f24d145a50..32d2875894 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_share.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_share.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_share_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_share_filled.svg index 16b4c0ecf2..86424776cb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_share_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_share_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_shield.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_shield.svg index ca1ed6828c..e99e34a8d3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_shield.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_shield.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smart_display.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smart_display.svg index 195c3b2bea..4027f2f1f9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smart_display.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smart_display.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smartphone.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smartphone.svg index 93094d1445..bb13e7f818 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smartphone.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smartphone.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smartphone_300.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smartphone_300.svg index 7d8553db11..6d9d70650c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smartphone_300.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_smartphone_300.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star.svg index 2a2733830a..72d7962366 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star_filled.svg index bf9bd2b4b5..22afbba02c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star_filled.svg @@ -1,2 +1,2 @@ - \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star_off.svg index d17a1e37d9..f353793d2a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star_off.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_star_off.svg @@ -1,8 +1,8 @@ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_start.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_start.svg index 42983718ac..f60449580b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_start.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_start.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_stop_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_stop_filled.svg index 0e8823dab5..bda4d1e10c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_stop_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_stop_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_supervised_user_circle_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_supervised_user_circle_filled.svg index ecfb7002a1..162ef06670 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_supervised_user_circle_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_supervised_user_circle_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_swap_horizontal_circle.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_swap_horizontal_circle.svg index 0b842e8716..05de300ed2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_swap_horizontal_circle.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_swap_horizontal_circle.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_swap_horizontal_circle_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_swap_horizontal_circle_filled.svg index 3509f870de..a71845eac7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_swap_horizontal_circle_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_swap_horizontal_circle_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_sync_problem.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_sync_problem.svg index 79a0441792..4f85d90659 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_sync_problem.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_sync_problem.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_tag.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_tag.svg index 4b7e39b8ec..535d97a168 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_tag.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_tag.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_task.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_task.svg index eab8e8f09d..e8120529a3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_task.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_task.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_theater_comedy.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_theater_comedy.svg index 24267d0fa8..e6a78e6923 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_theater_comedy.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_theater_comedy.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_theater_comedy_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_theater_comedy_filled.svg index 401e9affac..c1a3753674 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_theater_comedy_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_theater_comedy_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_timer.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_timer.svg index e530ef1e83..8f50769f03 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_timer.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_timer.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_timer_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_timer_filled.svg index 4e0a99775a..a7576c6ee2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_timer_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_timer_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_toast.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_toast.svg index 64f03f2fa7..dccf6dff3d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_toast.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_toast.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_toggle_on.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_toggle_on.svg index de240dce5f..02e068f30a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_toggle_on.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_toggle_on.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_translate.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_translate.svg index 8ad343c088..6b05619e01 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_translate.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_translate.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_travel_explore.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_travel_explore.svg index 023ce42a26..1cacbed166 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_travel_explore.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_travel_explore.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_upgrade.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_upgrade.svg index c9ecd4ad87..ab8fb7d951 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_upgrade.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_upgrade.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_upload_file.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_upload_file.svg index 1840509fb1..d84e8bc513 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_upload_file.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_upload_file.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_usb.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_usb.svg index 068bfc1a82..0663015ae4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_usb.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_usb.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_verified_user.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_verified_user.svg index cd6e89db86..7506004c1d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_verified_user.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_verified_user.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam.svg index 9a9edf61ce..d0ebb2fd41 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam_filled.svg index 0606c6a06f..9f3b877e0d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam_off.svg index 77c85ed987..992f052ed0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam_off.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_videocam_off.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility.svg index 50095bc6e5..24c3544621 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_filled.svg index 2bc6fbd9a5..1f07b63790 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_off.svg index 459ea40242..7cacfafdbf 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_off.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_off.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_off_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_off_filled.svg index 7269dffdd3..9a52f493a7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_off_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_visibility_off_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_down.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_down.svg index 44b8f89e91..51f7d1a35d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_down.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_down.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_up.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_up.svg index 8d9f2ebecf..292a1fe27e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_up.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_up.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_vpn_key_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_vpn_key_filled.svg index adb8814e12..f40ab9c6e1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_vpn_key_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_vpn_key_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_vpn_key_off_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_vpn_key_off_filled.svg index 6ae367024e..0fe5d9a21e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_vpn_key_off_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_vpn_key_off_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_warning.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_warning.svg index 7148c5740b..065e2f883c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_warning.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_warning.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_warning_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_warning_filled.svg index 673b7453f6..7806a17d7f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_warning_filled.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_warning_filled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi.svg index 2fb4750af5..313748293b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi_off.svg index 814077e485..31354b7ed2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi_off.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi_off.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi_tethering.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi_tethering.svg index f6007d3f02..b6b6eedd9c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi_tethering.svg +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_wifi_tethering.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 9f7f613835..38ffb137ed 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -60,16 +60,16 @@ fun AppearanceScope.AppearanceLayout( } } } - SectionDividerSpaced(maxTopPadding = true) + SectionDividerSpaced() ThemesSection(systemDarkTheme) - SectionDividerSpaced(maxTopPadding = true) + SectionDividerSpaced() ProfileImageSection() - SectionDividerSpaced(maxBottomPadding = true) + SectionDividerSpaced(maxTopPadding = true) FontScaleSection() - SectionDividerSpaced(maxBottomPadding = true) + SectionDividerSpaced(maxTopPadding = true) DensityScaleSection() SectionBottomSpacer() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt index ed1d820259..ee8ae93de5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt @@ -23,14 +23,12 @@ actual fun SettingsSectionApp( withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { SectionView(stringResource(MR.strings.settings_section_title_app)) { - SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }) val selectedChannel = remember { appPrefs.appUpdateChannel.state } val values = AppUpdatesChannel.entries.map { it to it.text } - Box(Modifier.padding(start = DEFAULT_PADDING_HALF * 1.4f)) { - ExposedDropDownSettingRow(stringResource(MR.strings.app_check_for_updates), values, selectedChannel) { - appPrefs.appUpdateChannel.set(it) - setupUpdateChecker() - } + ExposedDropDownSettingRow(stringResource(MR.strings.app_check_for_updates), values, selectedChannel) { + appPrefs.appUpdateChannel.set(it) + setupUpdateChecker() } AppVersionItem(showVersion) } From fd9c080103f6850e9d23bf7b47f578b0ad17ed29 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Wed, 21 Aug 2024 19:04:12 +0300 Subject: [PATCH 028/440] ios: fix last message being hidden on load (#4733) --- apps/ios/Shared/Views/Chat/ReverseList.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 4ce6a1a065..94d160e1b4 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -180,6 +180,13 @@ struct ReverseList: UIV snapshot, animatingDifferences: itemCount != 0 && abs(items.count - itemCount) == 1 ) + // Sets content offset on initial load + if itemCount == 0 { + tableView.setContentOffset( + CGPoint(x: 0, y: -InvertedTableView.inset), + animated: false + ) + } itemCount = items.count } } From 996c6efddd441994e8fe55ef3b77d4b2c3d251d5 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Wed, 21 Aug 2024 21:16:57 +0300 Subject: [PATCH 029/440] ios: prevent hangs when opening app from background with async api calls (#4730) * ios: async api calls on entering foreground * rename --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 43 ++++++++++++------- apps/ios/Shared/SimpleXApp.swift | 20 +++++---- .../Shared/Views/Call/CallController.swift | 2 +- .../Shared/Views/ChatList/UserPicker.swift | 21 +++++---- 4 files changed, 52 insertions(+), 34 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 3abd1e92d1..ebc58c6a05 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1218,12 +1218,18 @@ func apiEndCall(_ contact: Contact) async throws { try await sendCommandOkResp(.apiEndCall(contact: contact)) } -func apiGetCallInvitations() throws -> [RcvCallInvitation] { +func apiGetCallInvitationsSync() throws -> [RcvCallInvitation] { let r = chatSendCmdSync(.apiGetCallInvitations) if case let .callInvitations(invs) = r { return invs } throw r } +func apiGetCallInvitations() async throws -> [RcvCallInvitation] { + let r = await chatSendCmd(.apiGetCallInvitations) + if case let .callInvitations(invs) = r { return invs } + throw r +} + func apiCallStatus(_ contact: Contact, _ status: String) async throws { if let callStatus = WebRTCCallStatus.init(rawValue: status) { try await sendCommandOkResp(.apiCallStatus(contact: contact, callStatus: callStatus)) @@ -1517,7 +1523,7 @@ func startChat(refreshInvitations: Bool = true) throws { try getUserChatData() NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers()) if (refreshInvitations) { - try refreshCallInvitations() + Task { try await refreshCallInvitations() } } (m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken() _ = try apiStartChat() @@ -2161,23 +2167,30 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async { } } -func refreshCallInvitations() throws { +func refreshCallInvitations() async throws { let m = ChatModel.shared - let callInvitations = try justRefreshCallInvitations() - if let (chatId, ntfAction) = m.ntfCallInvitationAction, - let invitation = m.callInvitations.removeValue(forKey: chatId) { - m.ntfCallInvitationAction = nil - CallController.shared.callAction(invitation: invitation, action: ntfAction) - } else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) { - activateCall(invitation) + let callInvitations = try await apiGetCallInvitations() + await MainActor.run { + m.callInvitations = callsByChat(callInvitations) + if let (chatId, ntfAction) = m.ntfCallInvitationAction, + let invitation = m.callInvitations.removeValue(forKey: chatId) { + m.ntfCallInvitationAction = nil + CallController.shared.callAction(invitation: invitation, action: ntfAction) + } else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) { + activateCall(invitation) + } } } -func justRefreshCallInvitations() throws -> [RcvCallInvitation] { - let m = ChatModel.shared - let callInvitations = try apiGetCallInvitations() - m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv } - return callInvitations +func justRefreshCallInvitations() throws { + let callInvitations = try apiGetCallInvitationsSync() + ChatModel.shared.callInvitations = callsByChat(callInvitations) +} + +private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: RcvCallInvitation] { + callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { + result, inv in result[inv.contact.id] = inv + } } func activateCall(_ callInvitation: RcvCallInvitation) { diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 621f58dc0c..7f2c3b5866 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -83,9 +83,11 @@ struct SimpleXApp: App { if appState != .stopped { startChatAndActivate { if appState.inactive && chatModel.chatRunning == true { - updateChats() - if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { - updateCallInvitations() + Task { + await updateChats() + if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { + await updateCallInvitations() + } } } } @@ -130,16 +132,16 @@ struct SimpleXApp: App { } } - private func updateChats() { + private func updateChats() async { do { - let chats = try apiGetChats() - chatModel.updateChats(chats) + let chats = try await apiGetChatsAsync() + await MainActor.run { chatModel.updateChats(chats) } if let id = chatModel.chatId, let chat = chatModel.getChat(id) { Task { await loadChat(chat: chat, clearItems: false) } } if let ncr = chatModel.ntfContactRequest { - chatModel.ntfContactRequest = nil + await MainActor.run { chatModel.ntfContactRequest = nil } if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo { Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) } } @@ -149,9 +151,9 @@ struct SimpleXApp: App { } } - private func updateCallInvitations() { + private func updateCallInvitations() async { do { - try refreshCallInvitations() + try await refreshCallInvitations() } catch let error { logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))") } diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index 64b565e8e6..a8a91057fa 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -186,7 +186,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse logger.debug("CallController: started chat") self.shouldSuspendChat = true // There are no invitations in the model, as it was processed by NSE - _ = try? justRefreshCallInvitations() + try? justRefreshCallInvitations() logger.debug("CallController: updated call invitations chat") // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") // Extract the call information from the push notification payload diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index eeb7bf14f4..5041e093db 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -85,15 +85,18 @@ struct UserPicker: View { .padding(8) .opacity(userPickerVisible ? 1.0 : 0.0) .onAppear { - do { - // This check prevents the call of listUsers after the app is suspended, and the database is closed. - if case .active = scenePhase { - m.users = try listUsers() - } - } catch let error { - logger.error("Error loading users \(responseError(error))") - } - } + // This check prevents the call of listUsers after the app is suspended, and the database is closed. + if case .active = scenePhase { + Task { + do { + let users = try await listUsersAsync() + await MainActor.run { m.users = users } + } catch { + logger.error("Error loading users \(responseError(error))") + } + } + } + } } private func userView(_ u: UserInfo) -> some View { From 94218a1a7ef9d8101b6263de70869323ba4ba76d Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 21 Aug 2024 19:20:20 +0100 Subject: [PATCH 030/440] core: 6.0.2.0 (simplexmq 6.0.1.0) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 08973f9be8..9976edb2af 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 1cbf8c0015a8014bcc9d1894055d734947176684 + tag: 1d22608f860636f21a5557f1b3fab4a7da09c5cc source-repository-package type: git diff --git a/package.yaml b/package.yaml index 2455d4284e..df23a563a2 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.0.8 +version: 6.0.2.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index a77e8ee167..6352837e25 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."1cbf8c0015a8014bcc9d1894055d734947176684" = "01x8fvwzahz32fmxc2mjvxl1z6agwr3952hl4qjswym76r1dpc1s"; + "https://github.com/simplex-chat/simplexmq.git"."1d22608f860636f21a5557f1b3fab4a7da09c5cc" = "16kmc05avzdyd6kpj83nyqkyjks5kim5j351397f6p3yvm7iydwz"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 0f1564f618..7acf07c497 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.0.0.8 +version: 6.0.2.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 519dd9e219e545e74cf19e993c2db9461a7d3dac Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:03:49 +0000 Subject: [PATCH 031/440] android, desktop: fix onboarding layout (#4734) --- .../chat/simplex/common/views/WelcomeView.kt | 96 +++++------ .../views/onboarding/CreateSimpleXAddress.kt | 98 ++++++------ .../views/onboarding/SetNotificationsMode.kt | 70 ++++---- .../onboarding/SetupDatabasePassphrase.kt | 150 +++++++++--------- 4 files changed, 220 insertions(+), 194 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index e90a581416..f5dcc6b54a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -111,53 +111,59 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { val scrollState = rememberScrollState() val keyboardState by getKeyboardState() var savedKeyboardState by remember { mutableStateOf(keyboardState) } - - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { - Column( - modifier = Modifier - .fillMaxSize() - .themedBackground(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - CloseSheetBar(close = { - if (chatModel.users.none { !it.user.hidden }) { - appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - } else { - close() - } - }) - BackHandler(onBack = { - appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - }) - - ColumnWithScrollBar( - modifier = if (appPlatform.isAndroid) Modifier.fillMaxSize() else Modifier.widthIn(max = 600.dp).fillMaxHeight(), + val handler = remember { AppBarHandler() } + CompositionLocalProvider( + LocalAppBarHandler provides handler + ) { + ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { + Column( + modifier = Modifier + .fillMaxSize() + .themedBackground(), + horizontalAlignment = Alignment.CenterHorizontally ) { - val displayName = rememberSaveable { mutableStateOf("") } - val focusRequester = remember { FocusRequester() } - Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING) - ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) - Spacer(Modifier.height(DEFAULT_PADDING)) - ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - } - Spacer(Modifier.fillMaxHeight().weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - OnboardingActionButton( - if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.create_profile_button, - onboarding = null, - enabled = canCreateProfile(displayName.value), - onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) } - ) - // Reserve space - TextButtonBelowOnboardingButton("", null) - } + CloseSheetBar(close = { + if (chatModel.users.none { !it.user.hidden }) { + appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + } else { + close() + } + }) + BackHandler(onBack = { + appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + }) - LaunchedEffect(Unit) { - delay(300) - focusRequester.requestFocus() + ColumnWithScrollBar( + modifier = Modifier.fillMaxSize() + ) { + val displayName = rememberSaveable { mutableStateOf("") } + val focusRequester = remember { FocusRequester() } + Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING, withPadding = false) + } + ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) + Spacer(Modifier.height(DEFAULT_PADDING)) + ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) + ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + labelId = MR.strings.create_profile_button, + onboarding = null, + enabled = canCreateProfile(displayName.value), + onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) } + ) + // Reserve space + TextButtonBelowOnboardingButton("", null) + } + + LaunchedEffect(Unit) { + delay(300) + focusRequester.requestFocus() + } } } LaunchedEffect(Unit) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index fddf964f28..20a7ada3aa 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -76,56 +76,62 @@ private fun CreateSimpleXAddressLayout( createAddress: () -> Unit, nextStep: () -> Unit, ) { - ColumnWithScrollBar( - Modifier - .fillMaxSize() - .themedBackground(), - horizontalAlignment = Alignment.CenterHorizontally, + val handler = remember { AppBarHandler() } + CompositionLocalProvider( + LocalAppBarHandler provides handler ) { - CloseSheetBar(showClose = false, close = {}) - AppBarTitle(stringResource(MR.strings.simplex_address)) + ModalView({}, showClose = false) { + ColumnWithScrollBar( + Modifier + .fillMaxSize() + .themedBackground(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AppBarTitle(stringResource(MR.strings.simplex_address)) - Spacer(Modifier.weight(1f)) + Spacer(Modifier.weight(1f)) - if (userAddress != null) { - SimpleXLinkQRCode(userAddress.connReqContact) - Spacer(Modifier.height(DEFAULT_PADDING_HALF)) - Row { - ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } - Spacer(Modifier.width(DEFAULT_PADDING * 2)) - ShareViaEmailButton { sendEmail(userAddress) } - } - Spacer(Modifier.height(DEFAULT_PADDING)) - Spacer(Modifier.weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.continue_to_next_step, - onboarding = null, - onclick = nextStep - ) - // Reserve space - TextButtonBelowOnboardingButton("", null) - } - } else { - Button(createAddress, Modifier, shape = CircleShape, contentPadding = PaddingValues()) { - Icon(painterResource(MR.images.ic_mail_filled), null, Modifier.size(100.dp).background(MaterialTheme.colors.primary, CircleShape).padding(25.dp), tint = Color.White) - } - Spacer(Modifier.height(DEFAULT_PADDING)) - Spacer(Modifier.weight(1f)) - Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold) - TextBelowButton(stringResource(MR.strings.you_can_make_address_visible_via_settings)) - Spacer(Modifier.height(DEFAULT_PADDING)) - Spacer(Modifier.weight(1f)) + if (userAddress != null) { + SimpleXLinkQRCode(userAddress.connReqContact) + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + Row { + ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } + Spacer(Modifier.width(DEFAULT_PADDING * 2)) + ShareViaEmailButton { sendEmail(userAddress) } + } + Spacer(Modifier.height(DEFAULT_PADDING)) + Spacer(Modifier.weight(1f)) + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + labelId = MR.strings.continue_to_next_step, + onboarding = null, + onclick = nextStep + ) + // Reserve space + TextButtonBelowOnboardingButton("", null) + } + } else { + Button(createAddress, Modifier, shape = CircleShape, contentPadding = PaddingValues()) { + Icon(painterResource(MR.images.ic_mail_filled), null, Modifier.size(100.dp).background(MaterialTheme.colors.primary, CircleShape).padding(25.dp), tint = Color.White) + } + Spacer(Modifier.height(DEFAULT_PADDING)) + Spacer(Modifier.weight(1f)) + Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold) + TextBelowButton(stringResource(MR.strings.you_can_make_address_visible_via_settings)) + Spacer(Modifier.height(DEFAULT_PADDING)) + Spacer(Modifier.weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.create_address_button, - onboarding = null, - onclick = createAddress - ) - TextButtonBelowOnboardingButton(stringResource(MR.strings.dont_create_address), nextStep) + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + labelId = MR.strings.create_address_button, + onboarding = null, + onclick = createAddress + ) + TextButtonBelowOnboardingButton(stringResource(MR.strings.dont_create_address), nextStep) + } + } } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index 96e1ce8e34..1903b3cf81 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -25,39 +25,47 @@ import chat.simplex.res.MR @Composable fun SetNotificationsMode(m: ChatModel) { - ColumnWithScrollBar( - modifier = Modifier - .fillMaxSize() - .themedBackground() + val handler = remember { AppBarHandler() } + CompositionLocalProvider( + LocalAppBarHandler provides handler ) { - CloseSheetBar(showClose = false, close = {}) - AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title)) - val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } - Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { - Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) - Spacer(Modifier.height(DEFAULT_PADDING * 2f)) - SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) { - currentMode.value = NotificationsMode.OFF - } - SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)){ - currentMode.value = NotificationsMode.PERIODIC - } - SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)){ - currentMode.value = NotificationsMode.SERVICE - } - } - Spacer(Modifier.fillMaxHeight().weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, - labelId = MR.strings.use_chat, - onboarding = OnboardingStage.OnboardingComplete, - onclick = { - changeNotificationsMode(currentMode.value, m) + ModalView({}, showClose = false) { + ColumnWithScrollBar( + modifier = Modifier + .fillMaxSize() + .themedBackground() + ) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title)) } - ) - // Reserve space - TextButtonBelowOnboardingButton("", null) + val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } + Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { + Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING * 2f)) + SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) { + currentMode.value = NotificationsMode.OFF + } + SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)) { + currentMode.value = NotificationsMode.PERIODIC + } + SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)) { + currentMode.value = NotificationsMode.SERVICE + } + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + labelId = MR.strings.use_chat, + onboarding = OnboardingStage.OnboardingComplete, + onclick = { + changeNotificationsMode(currentMode.value, m) + } + ) + // Reserve space + TextButtonBelowOnboardingButton("", null) + } + } } } SetNotificationsModeAdditions() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index 7a1b7cccb4..858ca68af3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -104,84 +104,90 @@ private fun SetupDatabasePassphraseLayout( onConfirmEncrypt: () -> Unit, nextStep: () -> Unit, ) { - ColumnWithScrollBar( - Modifier.fillMaxSize().themedBackground().padding(bottom = DEFAULT_PADDING * 2), - horizontalAlignment = Alignment.CenterHorizontally, + val handler = remember { AppBarHandler() } + CompositionLocalProvider( + LocalAppBarHandler provides handler ) { - CloseSheetBar(showClose = false, close = {}) - AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) + ModalView({}, showClose = false) { + ColumnWithScrollBar( + Modifier.fillMaxSize().themedBackground().padding(bottom = DEFAULT_PADDING * 2), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) - Spacer(Modifier.weight(1f)) + Spacer(Modifier.weight(1f)) - Column(Modifier.width(600.dp)) { - val focusRequester = remember { FocusRequester() } - val focusManager = LocalFocusManager.current - LaunchedEffect(Unit) { - delay(100L) - focusRequester.requestFocus() - } - PassphraseField( - newKey, - generalGetString(MR.strings.new_passphrase), - modifier = Modifier - .padding(horizontal = DEFAULT_PADDING) - .focusRequester(focusRequester) - .onPreviewKeyEvent { - if ((it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) { - focusManager.moveFocus(FocusDirection.Down) - true - } else { - false + Column(Modifier.width(600.dp)) { + val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current + LaunchedEffect(Unit) { + delay(100L) + focusRequester.requestFocus() + } + PassphraseField( + newKey, + generalGetString(MR.strings.new_passphrase), + modifier = Modifier + .padding(horizontal = DEFAULT_PADDING) + .focusRequester(focusRequester) + .onPreviewKeyEvent { + if ((it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) { + focusManager.moveFocus(FocusDirection.Down) + true + } else { + false + } + }, + showStrength = true, + isValid = ::validKey, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + ) + val onClickUpdate = { + // Don't do things concurrently. Shouldn't be here concurrently, just in case + if (!progressIndicator.value) { + encryptDatabaseAlert(onConfirmEncrypt) } - }, - showStrength = true, - isValid = ::validKey, - keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), - ) - val onClickUpdate = { - // Don't do things concurrently. Shouldn't be here concurrently, just in case - if (!progressIndicator.value) { - encryptDatabaseAlert(onConfirmEncrypt) + } + val disabled = currentKey.value == newKey.value || + newKey.value != confirmNewKey.value || + newKey.value.isEmpty() || + !validKey(currentKey.value) || + !validKey(newKey.value) || + progressIndicator.value + + PassphraseField( + confirmNewKey, + generalGetString(MR.strings.confirm_new_passphrase), + modifier = Modifier + .padding(horizontal = DEFAULT_PADDING) + .onPreviewKeyEvent { + if (!disabled && (it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) { + onClickUpdate() + true + } else { + false + } + }, + isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value }, + keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }), + ) + + Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) { + SetPassphraseButton(disabled, onClickUpdate) + } + + Column { + SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time)) + SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) + } + } + + Spacer(Modifier.weight(1f)) + SkipButton(progressIndicator.value) { + chatModel.desktopOnboardingRandomPassword.value = true + nextStep() } } - val disabled = currentKey.value == newKey.value || - newKey.value != confirmNewKey.value || - newKey.value.isEmpty() || - !validKey(currentKey.value) || - !validKey(newKey.value) || - progressIndicator.value - - PassphraseField( - confirmNewKey, - generalGetString(MR.strings.confirm_new_passphrase), - modifier = Modifier - .padding(horizontal = DEFAULT_PADDING) - .onPreviewKeyEvent { - if (!disabled && (it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) { - onClickUpdate() - true - } else { - false - } - }, - isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value }, - keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }), - ) - - Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) { - SetPassphraseButton(disabled, onClickUpdate) - } - - Column { - SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time)) - SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) - } - } - - Spacer(Modifier.weight(1f)) - SkipButton(progressIndicator.value) { - chatModel.desktopOnboardingRandomPassword.value = true - nextStep() } } } From 073818db5562d0bf1dbbb38526c6f04cf4f6cc66 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:05:50 +0000 Subject: [PATCH 032/440] android, desktop: fix applying theme when no global theme edited yet (#4735) --- .../kotlin/chat/simplex/common/ui/theme/ThemeManager.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt index a86e92efba..7f19f58949 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt @@ -49,7 +49,9 @@ object ThemeManager { return perUserTheme } val defaultTheme = defaultActiveTheme(appSettingsTheme) - return ThemeModeOverride(colors = defaultTheme?.colors ?: ThemeColors(), wallpaper = defaultTheme?.wallpaper) + return ThemeModeOverride(colors = defaultTheme?.colors ?: ThemeColors(), wallpaper = defaultTheme?.wallpaper + // Situation when user didn't change global theme at all (it is not saved yet). Using defaults + ?: ThemeWallpaper.from(PresetWallpaper.SCHOOL.toType(CurrentColors.value.base), null, null)) } fun currentColors(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: List): ActiveTheme { From 7e7d93c5963c277b59ef2478bd73bee7e245709f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 21 Aug 2024 22:43:24 +0100 Subject: [PATCH 033/440] ios: update library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5d525004b9..b2d8c8e64b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E58C91472C72458500EADB92 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91422C72458500EADB92 /* libffi.a */; }; - E58C91482C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */; }; - E58C91492C72458500EADB92 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91442C72458500EADB92 /* libgmpxx.a */; }; - E58C914A2C72458500EADB92 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91452C72458500EADB92 /* libgmp.a */; }; - E58C914B2C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */; }; + E51ED5762C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */; }; + E51ED5772C7691A2009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5722C7691A2009F2C7C /* libgmp.a */; }; + E51ED5782C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */; }; + E51ED5792C7691A2009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5742C7691A2009F2C7C /* libgmpxx.a */; }; + E51ED57A2C7691A2009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5752C7691A2009F2C7C /* libffi.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -550,11 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E58C91422C72458500EADB92 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a"; sourceTree = ""; }; - E58C91442C72458500EADB92 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E58C91452C72458500EADB92 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a"; sourceTree = ""; }; + E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a"; sourceTree = ""; }; + E51ED5722C7691A2009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a"; sourceTree = ""; }; + E51ED5742C7691A2009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E51ED5752C7691A2009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E58C91492C72458500EADB92 /* libgmpxx.a in Frameworks */, - E58C914B2C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a in Frameworks */, - E58C914A2C72458500EADB92 /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + E51ED5772C7691A2009F2C7C /* libgmp.a in Frameworks */, + E51ED5762C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a in Frameworks */, + E51ED57A2C7691A2009F2C7C /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E58C91482C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E58C91472C72458500EADB92 /* libffi.a in Frameworks */, + E51ED5782C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a in Frameworks */, + E51ED5792C7691A2009F2C7C /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E58C91422C72458500EADB92 /* libffi.a */, - E58C91452C72458500EADB92 /* libgmp.a */, - E58C91442C72458500EADB92 /* libgmpxx.a */, - E58C91432C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo-ghc9.6.3.a */, - E58C91462C72458500EADB92 /* libHSsimplex-chat-6.0.0.8-GILzHAOMg84gReBIsoPFo.a */, + E51ED5752C7691A2009F2C7C /* libffi.a */, + E51ED5722C7691A2009F2C7C /* libgmp.a */, + E51ED5742C7691A2009F2C7C /* libgmpxx.a */, + E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */, + E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */, ); path = Libraries; sourceTree = ""; From 9f44242e4cff970fa9b0a4e2121e9e676c67f455 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 22 Aug 2024 07:34:17 +0000 Subject: [PATCH 034/440] ios: fix applying theme when no global theme edited yet (#4738) * ios: fix applying theme when no global theme editer yet * base color * added check for base --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Theme/ThemeManager.swift | 2 +- apps/ios/Shared/Views/Helpers/ChatWallpaper.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Theme/ThemeManager.swift b/apps/ios/Shared/Theme/ThemeManager.swift index 55f9a08878..9d648750d1 100644 --- a/apps/ios/Shared/Theme/ThemeManager.swift +++ b/apps/ios/Shared/Theme/ThemeManager.swift @@ -53,7 +53,7 @@ class ThemeManager { return perUserTheme } let defaultTheme = defaultActiveTheme(appSettingsTheme) - return ThemeModeOverride(mode: CurrentColors.base.mode, colors: defaultTheme?.colors ?? ThemeColors(), wallpaper: defaultTheme?.wallpaper) + return ThemeModeOverride(mode: CurrentColors.base.mode, colors: defaultTheme?.colors ?? ThemeColors(), wallpaper: defaultTheme?.wallpaper ?? ThemeWallpaper.from(PresetWallpaper.school.toType(CurrentColors.base), nil, nil)) } static func currentColors(_ themeOverridesForType: WallpaperType?, _ perChatTheme: ThemeModeOverride?, _ perUserTheme: ThemeModeOverrides?, _ appSettingsTheme: [ThemeOverrides]) -> ActiveTheme { diff --git a/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift b/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift index 9eea278d4e..cc5be9e7bb 100644 --- a/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift +++ b/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift @@ -100,7 +100,7 @@ extension PresetWallpaper { scale } else if let type = ChatModel.shared.currentUser?.uiThemes?.preferredMode(base.mode == DefaultThemeMode.dark)?.wallpaper?.toAppWallpaper().type, type.sameType(WallpaperType.preset(filename, nil)) { type.scale - } else if let scale = themeOverridesDefault.get().first(where: { $0.wallpaper != nil && $0.wallpaper!.preset == filename })?.wallpaper?.scale { + } else if let scale = themeOverridesDefault.get().first(where: { $0.wallpaper != nil && $0.wallpaper!.preset == filename && $0.base == base })?.wallpaper?.scale { scale } else { Float(1.0) From 8d48c4b14c51e69184879c1541141b9c05352068 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 22 Aug 2024 11:40:54 +0100 Subject: [PATCH 035/440] ui: translations (#4740) * Translated using Weblate (Italian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 64.6% (1314 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 62.8% (1121 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Vietnamese) Currently translated at 33.5% (682 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 81.6% (1659 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 81.6% (1659 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 62.8% (1121 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 49.6% (887 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/ * Translated using Weblate (Polish) Currently translated at 99.9% (2031 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Polish) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/ * Translated using Weblate (Indonesian) Currently translated at 5.3% (108 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Vietnamese) Currently translated at 34.2% (696 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 85.4% (1736 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 86.8% (1764 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Czech) Currently translated at 88.6% (1801 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Indonesian) Currently translated at 5.8% (118 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ * Translated using Weblate (Vietnamese) Currently translated at 34.4% (701 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 34.9% (710 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Vietnamese) Currently translated at 35.6% (725 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * process localizations * ru * ru export * hu --------- Co-authored-by: Random Co-authored-by: kumo Co-authored-by: No name Co-authored-by: M1K4 Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: Jiaa Co-authored-by: axmfs Co-authored-by: B.O.S.S Co-authored-by: billy appetie Co-authored-by: Max Co-authored-by: Reza Almanda Co-authored-by: Hosted Weblate Co-authored-by: summoner001 Co-authored-by: jonnysemon --- .../es.xcloc/Localized Contents/es.xliff | 10 +- .../hu.xcloc/Localized Contents/hu.xliff | 20 +- .../it.xcloc/Localized Contents/it.xliff | 2 +- .../nl.xcloc/Localized Contents/nl.xliff | 2 +- .../pl.xcloc/Localized Contents/pl.xliff | 118 ++++ .../Localized Contents/pt-BR.xliff | 268 ++++++++ .../ru.xcloc/Localized Contents/ru.xliff | 6 +- .../uk.xcloc/Localized Contents/uk.xliff | 256 +++++++ .../Localized Contents/zh-Hant.xliff | 228 ++++++- .../ios/SimpleX SE/pl.lproj/InfoPlist.strings | 14 +- .../SimpleX SE/pl.lproj/Localizable.strings | 116 +++- .../ios/SimpleX SE/uk.lproj/InfoPlist.strings | 14 +- .../SimpleX SE/uk.lproj/Localizable.strings | 116 +++- apps/ios/es.lproj/Localizable.strings | 10 +- apps/ios/hu.lproj/Localizable.strings | 20 +- apps/ios/it.lproj/Localizable.strings | 2 +- apps/ios/nl.lproj/Localizable.strings | 2 +- apps/ios/pl.lproj/Localizable.strings | 234 +++++++ apps/ios/uk.lproj/Localizable.strings | 645 ++++++++++++++++++ .../commonMain/resources/MR/ar/strings.xml | 2 +- .../commonMain/resources/MR/es/strings.xml | 12 +- .../commonMain/resources/MR/hu/strings.xml | 14 +- .../commonMain/resources/MR/in/strings.xml | 12 + .../commonMain/resources/MR/it/strings.xml | 2 +- .../commonMain/resources/MR/nl/strings.xml | 4 +- .../commonMain/resources/MR/pl/strings.xml | 101 ++- .../commonMain/resources/MR/vi/strings.xml | 88 +++ .../resources/MR/zh-rTW/strings.xml | 614 ++++++++++++++++- 28 files changed, 2816 insertions(+), 116 deletions(-) diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 8c8416857f..6e5ec0e85a 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -802,7 +802,7 @@ Allow to send SimpleX links. - Permitir enviar enlaces SimpleX. + Se permite enviar enlaces SimpleX. No comment provided by engineer. @@ -2406,7 +2406,7 @@ This is your own one-time link! Do not send history to new members. - No enviar historial a miembros nuevos. + No se envía el historial a los miembros nuevos. No comment provided by engineer. @@ -5068,7 +5068,7 @@ Error: %@ Prohibit sending SimpleX links. - No permitir el envío de enlaces SimpleX. + No se permite enviar enlaces SimpleX. No comment provided by engineer. @@ -5841,7 +5841,7 @@ Actívalo en ajustes de *Servidores y Redes*. Send up to 100 last messages to new members. - Enviar hasta 100 últimos mensajes a los miembros nuevos. + Se envían hasta 100 mensajes más recientes a los miembros nuevos. No comment provided by engineer. @@ -7449,7 +7449,7 @@ Repeat join request? You can change it in Appearance settings. - Puede cambiarlo desde el menú Apariencia. + Puedes cambiar la posición de la barra desde el menú Apariencia. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 771c2f9405..f7328eed91 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1042,7 +1042,7 @@ Bad message hash - Téves üzenet hash + Hibás az üzenet ellenőrzőösszege No comment provided by engineer. @@ -2131,7 +2131,7 @@ Ez az egyszer használatos hivatkozása! Delete member message? - Csoporttag üzenet törlése? + Csoporttag üzenetének törlése? No comment provided by engineer. @@ -2221,7 +2221,7 @@ Ez az egyszer használatos hivatkozása! Delivery receipts! - Kézbesítési igazolások! + Üzenet kézbesítési jelentések! No comment provided by engineer. @@ -2681,12 +2681,12 @@ Ez az egyszer használatos hivatkozása! Enter welcome message… - Üdvözlő üzenetet megadása… + Üdvözlő üzenet megadása… placeholder Enter welcome message… (optional) - Üdvözlő üzenetet megadása… (opcionális) + Üdvözlő üzenet megadása… (opcionális) placeholder @@ -4131,7 +4131,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message delivery receipts! - Üzenetkézbesítési bizonylatok! + Üzenet kézbesítési jelentések! No comment provided by engineer. @@ -4176,7 +4176,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message reception - Üzenetjelentés + Üzenet kézbesítési jelentés No comment provided by engineer. @@ -6573,7 +6573,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The hash of the previous message is different. - Az előző üzenet hash-e más. + Az előző üzenet ellenőrzőösszege különbözik. No comment provided by engineer. @@ -7888,7 +7888,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. bad message hash - téves üzenet hash + hibás az üzenet ellenőrzőösszege integrity error chat item @@ -8490,7 +8490,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. removed %@ - %@ eltávolítva + eltávolította őt: %@ rcv group event chat item diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index c7f9ad6e58..72eb3561e3 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1617,7 +1617,7 @@ Questo è il tuo link una tantum! Connection and servers status. - Stato di connessione e server. + Stato della connessione e dei server. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index dfec7b0b05..15a8c01a64 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -3906,7 +3906,7 @@ Dit is jouw link voor groep %@! Keep conversation - Blijf in gesprek + Behoud het gesprek No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index f0f4670dd6..525d30daa6 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -752,6 +752,7 @@ Allow calls? + Zezwolić na połączenia? No comment provided by engineer. @@ -791,6 +792,7 @@ Allow sharing + Zezwól na udostępnianie No comment provided by engineer. @@ -945,10 +947,12 @@ Archive contacts to chat later. + Archiwizuj kontakty aby porozmawiać później. No comment provided by engineer. Archived contacts + Zarchiwizowane kontakty No comment provided by engineer. @@ -1053,6 +1057,7 @@ Better networking + Lepsze sieciowanie No comment provided by engineer. @@ -1097,10 +1102,12 @@ Blur for better privacy. + Rozmycie dla lepszej prywatności. No comment provided by engineer. Blur media + Rozmycie mediów No comment provided by engineer. @@ -1150,6 +1157,7 @@ Calls prohibited! + Połączenia zakazane! No comment provided by engineer. @@ -1159,10 +1167,12 @@ Can't call contact + Nie można zadzwonić do kontaktu No comment provided by engineer. Can't call member + Nie można zadzwonić do członka No comment provided by engineer. @@ -1177,6 +1187,7 @@ Can't message member + Nie można wysłać wiadomości do członka No comment provided by engineer. @@ -1292,6 +1303,7 @@ Chat database exported + Wyeksportowano bazę danych czatu No comment provided by engineer. @@ -1316,6 +1328,7 @@ Chat list + Lista czatów No comment provided by engineer. @@ -1405,6 +1418,7 @@ Color chats with the new themes. + Koloruj czaty z nowymi motywami. No comment provided by engineer. @@ -1449,6 +1463,7 @@ Confirm contact deletion? + Potwierdzić usunięcie kontaktu? No comment provided by engineer. @@ -1508,6 +1523,7 @@ Connect to your friends faster. + Szybciej łącz się ze znajomymi. No comment provided by engineer. @@ -1586,6 +1602,7 @@ To jest twój jednorazowy link! Connecting to contact, please wait or check later! + Łączenie z kontaktem, poczekaj lub sprawdź później! No comment provided by engineer. @@ -1600,6 +1617,7 @@ To jest twój jednorazowy link! Connection and servers status. + Stan połączenia i serwerów. No comment provided by engineer. @@ -1614,6 +1632,7 @@ To jest twój jednorazowy link! Connection notifications + Powiadomienia o połączeniu No comment provided by engineer. @@ -1653,6 +1672,7 @@ To jest twój jednorazowy link! Contact deleted! + Kontakt usunięty! No comment provided by engineer. @@ -1667,6 +1687,7 @@ To jest twój jednorazowy link! Contact is deleted. + Kontakt jest usunięty. No comment provided by engineer. @@ -1681,6 +1702,7 @@ To jest twój jednorazowy link! Contact will be deleted - this cannot be undone! + Kontakt zostanie usunięty – nie można tego cofnąć! No comment provided by engineer. @@ -1700,6 +1722,7 @@ To jest twój jednorazowy link! Conversation deleted! + Rozmowa usunięta! No comment provided by engineer. @@ -1978,6 +2001,7 @@ To jest twój jednorazowy link! Delete %lld messages of members? + Usunąć %lld wiadomości członków? No comment provided by engineer. @@ -2042,6 +2066,7 @@ To jest twój jednorazowy link! Delete contact? + Usunąć kontakt? No comment provided by engineer. @@ -2151,6 +2176,7 @@ To jest twój jednorazowy link! Delete up to 20 messages at once. + Usuń do 20 wiadomości na raz. No comment provided by engineer. @@ -2160,6 +2186,7 @@ To jest twój jednorazowy link! Delete without notification + Usuń bez powiadomienia No comment provided by engineer. @@ -2219,6 +2246,7 @@ To jest twój jednorazowy link! Destination server address of %@ is incompatible with forwarding server %@ settings. + Adres serwera docelowego %@ jest niekompatybilny z ustawieniami serwera przekazującego %@. No comment provided by engineer. @@ -2228,6 +2256,7 @@ To jest twój jednorazowy link! Destination server version of %@ is incompatible with forwarding server %@. + Wersja serwera docelowego %@ jest niekompatybilna z serwerem przekierowującym %@. No comment provided by engineer. @@ -2247,6 +2276,7 @@ To jest twój jednorazowy link! Developer options + Opcje deweloperskie No comment provided by engineer. @@ -2301,6 +2331,7 @@ To jest twój jednorazowy link! Disabled + Wyłączony No comment provided by engineer. @@ -2530,6 +2561,7 @@ To jest twój jednorazowy link! Enabled + Włączony No comment provided by engineer. @@ -2704,6 +2736,7 @@ To jest twój jednorazowy link! Error connecting to forwarding server %@. Please try later. + Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później. No comment provided by engineer. @@ -3209,14 +3242,17 @@ To jest twój jednorazowy link! Forwarding server %@ failed to connect to destination server %@. Please try later. + Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później. No comment provided by engineer. Forwarding server address is incompatible with network settings: %@. + Adres serwera przekierowującego jest niekompatybilny z ustawieniami sieciowymi: %@. No comment provided by engineer. Forwarding server version is incompatible with network settings: %@. + Wersja serwera przekierowującego jest niekompatybilna z ustawieniami sieciowymi: %@. No comment provided by engineer. @@ -3803,6 +3839,7 @@ Błąd: %2$@ It protects your IP address and connections. + Chroni Twój adres IP i połączenia. No comment provided by engineer. @@ -3869,6 +3906,7 @@ To jest twój link do grupy %@! Keep conversation + Zachowaj rozmowę No comment provided by engineer. @@ -4048,10 +4086,12 @@ To jest twój link do grupy %@! Media & file servers + Serwery mediów i plików No comment provided by engineer. Medium + Średni blur media @@ -4141,6 +4181,7 @@ To jest twój link do grupy %@! Message servers + Serwery wiadomości No comment provided by engineer. @@ -4355,6 +4396,7 @@ To jest twój link do grupy %@! New chat experience 🎉 + Nowe możliwości czatu 🎉 No comment provided by engineer. @@ -4389,6 +4431,7 @@ To jest twój link do grupy %@! New media options + Nowe opcje mediów No comment provided by engineer. @@ -4483,6 +4526,7 @@ To jest twój link do grupy %@! Nothing selected + Nic nie jest zaznaczone No comment provided by engineer. @@ -4560,6 +4604,7 @@ Wymaga włączenia VPN. Only delete conversation + Usuń tylko rozmowę No comment provided by engineer. @@ -4799,10 +4844,12 @@ Wymaga włączenia VPN. Play from the chat list. + Odtwórz z listy czatów. No comment provided by engineer. Please ask your contact to enable calls. + Poproś kontakt o włącznie połączeń. No comment provided by engineer. @@ -5108,6 +5155,7 @@ Włącz w ustawianiach *Sieć i serwery* . Reachable chat toolbar + Osiągalny pasek narzędzi czatu No comment provided by engineer. @@ -5383,6 +5431,7 @@ Włącz w ustawianiach *Sieć i serwery* . Reset all hints + Zresetuj wszystkie wskazówki No comment provided by engineer. @@ -5517,6 +5566,7 @@ Włącz w ustawianiach *Sieć i serwery* . Save and reconnect + Zapisz i połącz ponownie No comment provided by engineer. @@ -5681,6 +5731,7 @@ Włącz w ustawianiach *Sieć i serwery* . Selected %lld + Zaznaczono %lld No comment provided by engineer. @@ -5750,6 +5801,7 @@ Włącz w ustawianiach *Sieć i serwery* . Send message to enable calls. + Wyślij wiadomość aby włączyć połączenia. No comment provided by engineer. @@ -6039,6 +6091,7 @@ Włącz w ustawianiach *Sieć i serwery* . Share from other apps. + Udostępnij z innych aplikacji. No comment provided by engineer. @@ -6053,6 +6106,7 @@ Włącz w ustawianiach *Sieć i serwery* . Share to SimpleX + Udostępnij do SimpleX No comment provided by engineer. @@ -6207,10 +6261,12 @@ Włącz w ustawianiach *Sieć i serwery* . Soft + Łagodny blur media Some file(s) were not exported: + Niektóre plik(i) nie zostały wyeksportowane: No comment provided by engineer. @@ -6220,6 +6276,7 @@ Włącz w ustawianiach *Sieć i serwery* . Some non-fatal errors occurred during import: + Podczas importu wystąpiły niekrytyczne błędy: No comment provided by engineer. @@ -6319,6 +6376,7 @@ Włącz w ustawianiach *Sieć i serwery* . Strong + Silne blur media @@ -6358,6 +6416,7 @@ Włącz w ustawianiach *Sieć i serwery* . TCP connection + Połączenie TCP No comment provided by engineer. @@ -6529,10 +6588,12 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom The messages will be deleted for all members. + Wiadomości zostaną usunięte dla wszystkich członków. No comment provided by engineer. The messages will be marked as moderated for all members. + Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków. No comment provided by engineer. @@ -6719,6 +6780,7 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania. Toggle chat list: + Przełącz listę czatów: No comment provided by engineer. @@ -6728,6 +6790,7 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania. Toolbar opacity + Nieprzezroczystość paska narzędzi No comment provided by engineer. @@ -6914,6 +6977,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Update settings? + Zaktualizować ustawienia? No comment provided by engineer. @@ -7023,6 +7087,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Use the app with one hand. + Korzystaj z aplikacji jedną ręką. No comment provided by engineer. @@ -7384,6 +7449,7 @@ Powtórzyć prośbę dołączenia? You can change it in Appearance settings. + Możesz to zmienić w ustawieniach wyglądu. No comment provided by engineer. @@ -7423,6 +7489,7 @@ Powtórzyć prośbę dołączenia? You can send messages to %@ from Archived contacts. + Możesz wysyłać wiadomości do %@ ze zarchiwizowanych kontaktów. No comment provided by engineer. @@ -7452,6 +7519,7 @@ Powtórzyć prośbę dołączenia? You can still view conversation with %@ in the list of chats. + Nadal możesz przeglądać rozmowę z %@ na liście czatów. No comment provided by engineer. @@ -7518,10 +7586,12 @@ Powtórzyć prośbę połączenia? You may migrate the exported database. + Możesz zmigrować wyeksportowaną bazy danych. No comment provided by engineer. You may save the exported archive. + Możesz zapisać wyeksportowane archiwum. No comment provided by engineer. @@ -7531,6 +7601,7 @@ Powtórzyć prośbę połączenia? You need to allow your contact to call to be able to call them. + Aby móc dzwonić, musisz zezwolić kontaktowi na połączenia. No comment provided by engineer. @@ -7842,6 +7913,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. call + zadzwoń No comment provided by engineer. @@ -8216,6 +8288,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. invite + zaproś No comment provided by engineer. @@ -8275,6 +8348,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. message + wiadomość No comment provided by engineer. @@ -8309,6 +8383,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. mute + wycisz No comment provided by engineer. @@ -8445,6 +8520,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. search + szukaj No comment provided by engineer. @@ -8533,6 +8609,7 @@ ostatnia otrzymana wiadomość: %2$@ unmute + wyłącz wyciszenie No comment provided by engineer. @@ -8582,6 +8659,7 @@ ostatnia otrzymana wiadomość: %2$@ video + wideo No comment provided by engineer. @@ -8762,14 +8840,17 @@ ostatnia otrzymana wiadomość: %2$@ SimpleX SE + SimpleX SE Bundle display name SimpleX SE + SimpleX SE Bundle name Copyright © 2024 SimpleX Chat. All rights reserved. + Copyright © 2024 SimpleX Chat. Wszelkie prawa zastrzeżone. Copyright (human-readable) @@ -8781,150 +8862,187 @@ ostatnia otrzymana wiadomość: %2$@ %@ + %@ No comment provided by engineer. App is locked! + Aplikacja zablokowana! No comment provided by engineer. Cancel + Anuluj No comment provided by engineer. Cannot access keychain to save database password + Nie można uzyskać dostępu do pęku kluczy aby zapisać hasło do bazy danych No comment provided by engineer. Cannot forward message + Nie można przekazać wiadomości No comment provided by engineer. Comment + Komentarz No comment provided by engineer. Currently maximum supported file size is %@. + Obecnie maksymalny obsługiwany rozmiar pliku to %@. No comment provided by engineer. Database downgrade required + Wymagane obniżenie wersji bazy danych No comment provided by engineer. Database encrypted! + Baza danych zaszyfrowana! No comment provided by engineer. Database error + Błąd bazy danych No comment provided by engineer. Database passphrase is different from saved in the keychain. + Hasło bazy danych jest inne niż zapisane w pęku kluczy. No comment provided by engineer. Database passphrase is required to open chat. + Hasło do bazy danych jest wymagane do otwarcia czatu. No comment provided by engineer. Database upgrade required + Wymagana aktualizacja bazy danych No comment provided by engineer. Error preparing file + Błąd przygotowania pliku No comment provided by engineer. Error preparing message + Błąd przygotowania wiadomości No comment provided by engineer. Error: %@ + Błąd: %@ No comment provided by engineer. File error + Błąd pliku No comment provided by engineer. Incompatible database version + Niekompatybilna wersja bazy danych No comment provided by engineer. Invalid migration confirmation + Nieprawidłowe potwierdzenie migracji No comment provided by engineer. Keychain error + Błąd pęku kluczy No comment provided by engineer. Large file! + Duży plik! No comment provided by engineer. No active profile + Brak aktywnego profilu No comment provided by engineer. Ok + Ok No comment provided by engineer. Open the app to downgrade the database. + Otwórz aplikację aby obniżyć wersję bazy danych. No comment provided by engineer. Open the app to upgrade the database. + Otwórz aplikację aby zaktualizować bazę danych. No comment provided by engineer. Passphrase + Hasło No comment provided by engineer. Please create a profile in the SimpleX app + Proszę utworzyć profil w aplikacji SimpleX No comment provided by engineer. Selected chat preferences prohibit this message. + Wybrane preferencje czatu zabraniają tej wiadomości. No comment provided by engineer. Sending a message takes longer than expected. + Wysłanie wiadomości trwa dłużej niż oczekiwano. No comment provided by engineer. Sending message… + Wysyłanie wiadomości… No comment provided by engineer. Share + Udostępnij No comment provided by engineer. Slow network? + Wolna sieć? No comment provided by engineer. Unknown database error: %@ + Nieznany błąd bazy danych: %@ No comment provided by engineer. Unsupported format + Niewspierany format No comment provided by engineer. Wait + Czekaj No comment provided by engineer. Wrong database passphrase + Nieprawidłowe hasło bazy danych No comment provided by engineer. You can allow sharing in Privacy & Security / SimpleX Lock settings. + Możesz zezwolić na udostępnianie w ustawieniach Prywatność i bezpieczeństwo / Blokada SimpleX. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index 0abab468fd..5f6cbc3b8f 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -5234,6 +5234,274 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi %1$@ em %2$@: copied message info, <sender> at <time> + + Allow your contacts to irreversibly delete sent messages. (24 hours) + Permitir que seus contatos deletem mensagens enviadas de maneira irreversível. (24 horas) + + + %@ downloaded + baixado + + + %@ uploaded + transferido + + + A new random profile will be shared. + Um novo perfil aleatório será compartilhado. + + + Camera not available + Câmera indisponível + + + Admins can block a member for all. + Administradores podem bloquear um membro para todos. + + + Allow to irreversibly delete sent messages. (24 hours) + Permitir que mensagens enviadas sejam deletadas de maneira irreversível. (24 horas) + + + Apply + Aplicar + + + Accent + Esquema + + + Accept connection request? + Aceitar solicitação de conexão? + + + Active connections + Conexões ativas + + + Add contact + Adicionar contato + + + Additional accent + Esquema adicional + + + All new messages from %@ will be hidden! + Todas as novas mensagens de %@ serão ocultas! + + + All profiles + Todos perfis + + + Allow calls? + Permitir chamadas? + + + Archive contacts to chat later. + Arquivar contatos para conversar depois. + + + Blur media + Censurar mídia + + + Calls prohibited! + Chamadas proibidas! + + + Can't call contact + Não foi possível ligar para o contato + + + %lld messages marked deleted + mensagens deletadas + + + 0 sec + 0 seg + + + %lld messages blocked + mensagens bloqueadas + + + %lld messages blocked by admin + mensagens bloqueadas pelo administrador + + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Nota**: usar o mesmo banco de dados em dois dispositivos irá quebrar a desencriptação das mensagens de suas conexões como uma medida de segurança. + + + - more stable message delivery. +- a bit better groups. +- and more! + - entrega de mensagens mais estável. +- grupos melhorados. +- e muito mais! + + + All messages will be deleted - this cannot be undone! + Todas as mensagens serão deletadas - isto não pode ser desfeito! + + + Allow to send files and media. + Permitir o envio de arquivos e mídia. + + + Allow to send SimpleX links. + Permitir envio de links SimpleX. + + + Block for all + Bloquear para todos + + + Block member + Bloquear membro + + + Blocked by admin + Bloqueado por um administrador + + + Block group members + Bloquear membros de grupo + + + Block member for all? + Bloquear membro para todos? + + + Block member? + Bloquear membro? + + + Both you and your contact can irreversibly delete sent messages. (24 hours) + Você e seu contato podem apagar mensagens enviadas de maneira irreversível. (24 horas) + + + Can't call member + Não foi possível ligar para este membro + + + Can't message member + Não foi possível enviar mensagem para este membro + + + Cancel migration + Cancelar migração + + + Abort + Abortar + + + Abort changing address + Abortar troca de endereço + + + Abort changing address? + Abortar troca de endereço? + + + - optionally notify deleted contacts. +- profile names with spaces. +- and more! + - notificar contatos apagados de maneira opcional. +- nome de perfil com espaços. +- e muito mais! + + + Allow sharing + Permitir compartilhamento + + + Block + Bloquear + + + Additional accent 2 + Esquema adicional 2 + + + Address change will be aborted. Old receiving address will be used. + Alteração de endereço será abortada. O endereço antigo será utilizado. + + + Advanced settings + Configurações avançadas + + + All data is private to your device. + Toda informação é privada em seu dispositivo. + + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Todos os seus contatos, conversas e arquivos serão encriptados e enviados em pedaços para nós XFTP. + + + Allow irreversible message deletion only if your contact allows it to you. (24 hours) + Permitir deletar mensagens de maneira irreversível apenas se seu contato permitir para você. (24 horas) + + + Already connecting! + Já está conectando! + + + Already joining the group! + Já está entrando no grupo! + + + Always use private routing. + Sempre use rotas privadas. + + + Apply to + Aplicar em + + + Archiving database + Arquivando banco de dados + + + Black + Preto + + + Cannot forward message + Não é possível encaminhar mensagem + + + (new) + (novo) + + + (this device v%@) + este dispositivo + + + **Add contact**: to create a new invitation link, or connect via a link you received. + **Adicionar contato**: criar um novo link de convite ou conectar via um link que você recebeu. + + + **Create group**: to create a new group. + **Criar grupo**: criar um novo grupo. + + + **Warning**: the archive will be removed. + **Aviso**: o arquivo será removido. + + + A few more things + E mais algumas coisas + + + Archived contacts + Contatos arquivados + diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 8bbf054868..969a7d68e0 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1418,7 +1418,7 @@ Color chats with the new themes. - Добавьте цвета к чатам в настройках тем. + Добавьте цвета к чатам в настройках. No comment provided by engineer. @@ -2176,7 +2176,7 @@ This is your own one-time link! Delete up to 20 messages at once. - Удаляйте до 20 сообщений одновременно. + Удаляйте до 20 сообщений за раз. No comment provided by engineer. @@ -4988,7 +4988,7 @@ Error: %@ Private message routing 🚀 - Конфиденциальная доставка сообщений 🚀 + Конфиденциальная доставка 🚀 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index ee06975768..7bcb30c1db 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -554,6 +554,7 @@ Accent + Акцент No comment provided by engineer. @@ -581,14 +582,17 @@ Acknowledged + Визнано No comment provided by engineer. Acknowledgement errors + Помилки підтвердження No comment provided by engineer. Active connections + Активні з'єднання No comment provided by engineer. @@ -633,14 +637,17 @@ Additional accent + Додатковий акцент No comment provided by engineer. Additional accent 2 + Додатковий акцент 2 No comment provided by engineer. Additional secondary + Додаткова вторинна No comment provided by engineer. @@ -670,6 +677,7 @@ Advanced settings + Додаткові налаштування No comment provided by engineer. @@ -689,6 +697,7 @@ All data is private to your device. + Всі дані є приватними для вашого пристрою. No comment provided by engineer. @@ -713,6 +722,7 @@ All profiles + Всі профілі No comment provided by engineer. @@ -742,6 +752,7 @@ Allow calls? + Дозволити дзвінки? No comment provided by engineer. @@ -781,6 +792,7 @@ Allow sharing + Дозволити спільний доступ No comment provided by engineer. @@ -925,6 +937,7 @@ Apply to + Звертатися до No comment provided by engineer. @@ -934,10 +947,12 @@ Archive contacts to chat later. + Архівуйте контакти, щоб поспілкуватися пізніше. No comment provided by engineer. Archived contacts + Архівні контакти No comment provided by engineer. @@ -1012,6 +1027,7 @@ Background + Фон No comment provided by engineer. @@ -1041,10 +1057,12 @@ Better networking + Краща мережа No comment provided by engineer. Black + Чорний No comment provided by engineer. @@ -1084,10 +1102,12 @@ Blur for better privacy. + Розмиття для кращої приватності. No comment provided by engineer. Blur media + Розмиття медіа No comment provided by engineer. @@ -1137,6 +1157,7 @@ Calls prohibited! + Дзвінки заборонені! No comment provided by engineer. @@ -1146,10 +1167,12 @@ Can't call contact + Не вдається додзвонитися до контакту No comment provided by engineer. Can't call member + Не вдається зателефонувати користувачеві No comment provided by engineer. @@ -1164,6 +1187,7 @@ Can't message member + Не можу надіслати повідомлення користувачеві No comment provided by engineer. @@ -1183,6 +1207,7 @@ Cannot forward message + Неможливо переслати повідомлення No comment provided by engineer. @@ -1258,6 +1283,7 @@ Chat colors + Кольори чату No comment provided by engineer. @@ -1277,6 +1303,7 @@ Chat database exported + Експортовано базу даних чату No comment provided by engineer. @@ -1301,6 +1328,7 @@ Chat list + Список чатів No comment provided by engineer. @@ -1315,6 +1343,7 @@ Chat theme + Тема чату No comment provided by engineer. @@ -1349,14 +1378,17 @@ Chunks deleted + Фрагменти видалено No comment provided by engineer. Chunks downloaded + Завантажено фрагменти No comment provided by engineer. Chunks uploaded + Завантажено фрагменти No comment provided by engineer. @@ -1386,10 +1418,12 @@ Color chats with the new themes. + Кольорові чати з новими темами. No comment provided by engineer. Color mode + Колірний режим No comment provided by engineer. @@ -1404,6 +1438,7 @@ Completed + Завершено No comment provided by engineer. @@ -1413,6 +1448,7 @@ Configured %@ servers + Налаштовані сервери %@ No comment provided by engineer. @@ -1427,6 +1463,7 @@ Confirm contact deletion? + Підтвердити видалення контакту? No comment provided by engineer. @@ -1486,6 +1523,7 @@ Connect to your friends faster. + Швидше спілкуйтеся з друзями. No comment provided by engineer. @@ -1529,6 +1567,7 @@ This is your own one-time link! Connected + Підключено No comment provided by engineer. @@ -1538,6 +1577,7 @@ This is your own one-time link! Connected servers + Підключені сервери No comment provided by engineer. @@ -1547,6 +1587,7 @@ This is your own one-time link! Connecting + Підключення No comment provided by engineer. @@ -1561,6 +1602,7 @@ This is your own one-time link! Connecting to contact, please wait or check later! + З'єднання з контактом, будь ласка, зачекайте або перевірте пізніше! No comment provided by engineer. @@ -1575,6 +1617,7 @@ This is your own one-time link! Connection and servers status. + Стан з'єднання та серверів. No comment provided by engineer. @@ -1589,6 +1632,7 @@ This is your own one-time link! Connection notifications + Сповіщення про підключення No comment provided by engineer. @@ -1608,10 +1652,12 @@ This is your own one-time link! Connection with desktop stopped + Припинено зв'язок з робочим столом No comment provided by engineer. Connections + З'єднання No comment provided by engineer. @@ -1626,6 +1672,7 @@ This is your own one-time link! Contact deleted! + Контакт видалено! No comment provided by engineer. @@ -1640,6 +1687,7 @@ This is your own one-time link! Contact is deleted. + Контакт видалено. No comment provided by engineer. @@ -1654,6 +1702,7 @@ This is your own one-time link! Contact will be deleted - this cannot be undone! + Контакт буде видалено - це неможливо скасувати! No comment provided by engineer. @@ -1673,6 +1722,7 @@ This is your own one-time link! Conversation deleted! + Розмова видалена! No comment provided by engineer. @@ -1682,6 +1732,7 @@ This is your own one-time link! Copy error + Помилка копіювання No comment provided by engineer. @@ -1761,6 +1812,7 @@ This is your own one-time link! Created + Створено No comment provided by engineer. @@ -1800,6 +1852,7 @@ This is your own one-time link! Current profile + Поточний профіль No comment provided by engineer. @@ -1814,6 +1867,7 @@ This is your own one-time link! Customize theme + Налаштувати тему No comment provided by engineer. @@ -1823,6 +1877,7 @@ This is your own one-time link! Dark mode colors + Кольори темного режиму No comment provided by engineer. @@ -1946,6 +2001,7 @@ This is your own one-time link! Delete %lld messages of members? + Видалити %lld повідомлень користувачів? No comment provided by engineer. @@ -2010,6 +2066,7 @@ This is your own one-time link! Delete contact? + Видалити контакт? No comment provided by engineer. @@ -2119,6 +2176,7 @@ This is your own one-time link! Delete up to 20 messages at once. + Видаляйте до 20 повідомлень одночасно. No comment provided by engineer. @@ -2128,10 +2186,12 @@ This is your own one-time link! Delete without notification + Видалення без попередження No comment provided by engineer. Deleted + Видалено No comment provided by engineer. @@ -2146,6 +2206,7 @@ This is your own one-time link! Deletion errors + Помилки видалення No comment provided by engineer. @@ -2185,6 +2246,7 @@ This is your own one-time link! Destination server address of %@ is incompatible with forwarding server %@ settings. + Адреса сервера призначення %@ несумісна з налаштуваннями сервера пересилання %@. No comment provided by engineer. @@ -2194,14 +2256,17 @@ This is your own one-time link! Destination server version of %@ is incompatible with forwarding server %@. + Версія сервера призначення %@ несумісна з версією сервера переадресації %@. No comment provided by engineer. Detailed statistics + Детальна статистика No comment provided by engineer. Details + Деталі No comment provided by engineer. @@ -2211,6 +2276,7 @@ This is your own one-time link! Developer options + Можливості для розробників No comment provided by engineer. @@ -2265,6 +2331,7 @@ This is your own one-time link! Disabled + Вимкнено No comment provided by engineer. @@ -2369,6 +2436,7 @@ This is your own one-time link! Download errors + Помилки завантаження No comment provided by engineer. @@ -2383,10 +2451,12 @@ This is your own one-time link! Downloaded + Завантажено No comment provided by engineer. Downloaded files + Завантажені файли No comment provided by engineer. @@ -2491,6 +2561,7 @@ This is your own one-time link! Enabled + Увімкнено No comment provided by engineer. @@ -2665,6 +2736,7 @@ This is your own one-time link! Error connecting to forwarding server %@. Please try later. + Помилка підключення до сервера переадресації %@. Спробуйте пізніше. No comment provided by engineer. @@ -2764,6 +2836,7 @@ This is your own one-time link! Error exporting theme: %@ + Помилка експорту теми: %@ No comment provided by engineer. @@ -2793,10 +2866,12 @@ This is your own one-time link! Error reconnecting server + Помилка перепідключення сервера No comment provided by engineer. Error reconnecting servers + Помилка перепідключення серверів No comment provided by engineer. @@ -2806,6 +2881,7 @@ This is your own one-time link! Error resetting statistics + Статистика скидання помилок No comment provided by engineer. @@ -2941,6 +3017,7 @@ This is your own one-time link! Errors + Помилки No comment provided by engineer. @@ -2970,6 +3047,7 @@ This is your own one-time link! Export theme + Тема експорту No comment provided by engineer. @@ -3009,22 +3087,27 @@ This is your own one-time link! File error + Помилка файлу No comment provided by engineer. File not found - most likely file was deleted or cancelled. + Файл не знайдено - найімовірніше, файл було видалено або скасовано. file error text File server error: %@ + Помилка файлового сервера: %@ file error text File status + Статус файлу No comment provided by engineer. File status: %@ + Статус файлу: %@ copied message info @@ -3159,14 +3242,17 @@ This is your own one-time link! Forwarding server %@ failed to connect to destination server %@. Please try later. + Серверу переадресації %@ не вдалося з'єднатися з сервером призначення %@. Спробуйте пізніше. No comment provided by engineer. Forwarding server address is incompatible with network settings: %@. + Адреса сервера переадресації несумісна з налаштуваннями мережі: %@. No comment provided by engineer. Forwarding server version is incompatible with network settings: %@. + Версія сервера переадресації несумісна з мережевими налаштуваннями: %@. No comment provided by engineer. @@ -3230,10 +3316,12 @@ Error: %2$@ Good afternoon! + Доброго дня! message preview Good morning! + Доброго ранку! message preview @@ -3518,6 +3606,7 @@ Error: %2$@ Import theme + Імпорт теми No comment provided by engineer. @@ -3644,6 +3733,7 @@ Error: %2$@ Interface colors + Кольори інтерфейсу No comment provided by engineer. @@ -3749,6 +3839,7 @@ Error: %2$@ It protects your IP address and connections. + Він захищає вашу IP-адресу та з'єднання. No comment provided by engineer. @@ -3815,6 +3906,7 @@ This is your link for group %@! Keep conversation + Підтримуйте розмову No comment provided by engineer. @@ -3994,10 +4086,12 @@ This is your link for group %@! Media & file servers + Медіа та файлові сервери No comment provided by engineer. Medium + Середній blur media @@ -4007,6 +4101,7 @@ This is your link for group %@! Member inactive + Користувач неактивний item status text @@ -4026,6 +4121,7 @@ This is your link for group %@! Menus + Меню No comment provided by engineer. @@ -4050,10 +4146,12 @@ This is your link for group %@! Message forwarded + Повідомлення переслано item status text Message may be delivered later if member becomes active. + Повідомлення може бути доставлене пізніше, якщо користувач стане активним. item status description @@ -4078,10 +4176,12 @@ This is your link for group %@! Message reception + Прийом повідомлень No comment provided by engineer. Message servers + Сервери повідомлень No comment provided by engineer. @@ -4091,10 +4191,12 @@ This is your link for group %@! Message status + Статус повідомлення No comment provided by engineer. Message status: %@ + Статус повідомлення: %@ copied message info @@ -4124,10 +4226,12 @@ This is your link for group %@! Messages received + Отримані повідомлення No comment provided by engineer. Messages sent + Надіслані повідомлення No comment provided by engineer. @@ -4292,6 +4396,7 @@ This is your link for group %@! New chat experience 🎉 + Новий досвід спілкування в чаті 🎉 No comment provided by engineer. @@ -4326,6 +4431,7 @@ This is your link for group %@! New media options + Нові медіа-опції No comment provided by engineer. @@ -4375,6 +4481,7 @@ This is your link for group %@! No direct connection yet, message is forwarded by admin. + Прямого зв'язку ще немає, повідомлення пересилається адміністратором. item status description @@ -4394,6 +4501,7 @@ This is your link for group %@! No info, try to reload + Немає інформації, спробуйте перезавантажити No comment provided by engineer. @@ -4418,6 +4526,7 @@ This is your link for group %@! Nothing selected + Нічого не вибрано No comment provided by engineer. @@ -4495,6 +4604,7 @@ Requires compatible VPN. Only delete conversation + Видаляйте тільки розмови No comment provided by engineer. @@ -4594,6 +4704,7 @@ Requires compatible VPN. Open server settings + Відкрити налаштування сервера No comment provided by engineer. @@ -4638,6 +4749,7 @@ Requires compatible VPN. Other %@ servers + Інші сервери %@ No comment provided by engineer. @@ -4707,6 +4819,7 @@ Requires compatible VPN. Pending + В очікуванні No comment provided by engineer. @@ -4731,10 +4844,12 @@ Requires compatible VPN. Play from the chat list. + Грати зі списку чату. No comment provided by engineer. Please ask your contact to enable calls. + Будь ласка, попросіть свого контакту ввімкнути дзвінки. No comment provided by engineer. @@ -4745,6 +4860,8 @@ Requires compatible VPN. Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + Переконайтеся, що мобільний і настільний комп'ютери підключені до однієї локальної мережі, і що брандмауер настільного комп'ютера дозволяє з'єднання. +Будь ласка, повідомте про будь-які інші проблеми розробникам. No comment provided by engineer. @@ -4846,6 +4963,7 @@ Error: %@ Previously connected servers + Раніше підключені сервери No comment provided by engineer. @@ -4885,6 +5003,7 @@ Error: %@ Private routing error + Помилка приватної маршрутизації No comment provided by engineer. @@ -4919,6 +5038,7 @@ Error: %@ Profile theme + Тема профілю No comment provided by engineer. @@ -5005,10 +5125,12 @@ Enable in *Network & servers* settings. Proxied + Проксі-сервер No comment provided by engineer. Proxied servers + Проксі-сервери No comment provided by engineer. @@ -5033,6 +5155,7 @@ Enable in *Network & servers* settings. Reachable chat toolbar + Доступна панель інструментів чату No comment provided by engineer. @@ -5082,6 +5205,7 @@ Enable in *Network & servers* settings. Receive errors + Отримання помилок No comment provided by engineer. @@ -5106,14 +5230,17 @@ Enable in *Network & servers* settings. Received messages + Отримані повідомлення No comment provided by engineer. Received reply + Отримано відповідь No comment provided by engineer. Received total + Отримано всього No comment provided by engineer. @@ -5148,6 +5275,7 @@ Enable in *Network & servers* settings. Reconnect + Повторне підключення No comment provided by engineer. @@ -5157,18 +5285,22 @@ Enable in *Network & servers* settings. Reconnect all servers + Перепідключіть усі сервери No comment provided by engineer. Reconnect all servers? + Перепідключити всі сервери? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + Перепідключити сервер для примусової доставки повідомлень. Використовує додатковий трафік. No comment provided by engineer. Reconnect server? + Перепідключити сервер? No comment provided by engineer. @@ -5224,6 +5356,7 @@ Enable in *Network & servers* settings. Remove image + Видалити зображення No comment provided by engineer. @@ -5298,14 +5431,17 @@ Enable in *Network & servers* settings. Reset all hints + Скинути всі підказки No comment provided by engineer. Reset all statistics + Скинути всю статистику No comment provided by engineer. Reset all statistics? + Скинути всю статистику? No comment provided by engineer. @@ -5315,6 +5451,7 @@ Enable in *Network & servers* settings. Reset to app theme + Повернутися до теми програми No comment provided by engineer. @@ -5324,6 +5461,7 @@ Enable in *Network & servers* settings. Reset to user theme + Повернутися до теми користувача No comment provided by engineer. @@ -5393,6 +5531,7 @@ Enable in *Network & servers* settings. SMP server + Сервер SMP No comment provided by engineer. @@ -5427,6 +5566,7 @@ Enable in *Network & servers* settings. Save and reconnect + Збережіть і підключіться знову No comment provided by engineer. @@ -5511,10 +5651,12 @@ Enable in *Network & servers* settings. Scale + Масштаб No comment provided by engineer. Scan / Paste link + Відсканувати / Вставити посилання No comment provided by engineer. @@ -5559,6 +5701,7 @@ Enable in *Network & servers* settings. Secondary + Вторинний No comment provided by engineer. @@ -5568,6 +5711,7 @@ Enable in *Network & servers* settings. Secured + Забезпечено No comment provided by engineer. @@ -5587,10 +5731,12 @@ Enable in *Network & servers* settings. Selected %lld + Вибрано %lld No comment provided by engineer. Selected chat preferences prohibit this message. + Вибрані налаштування чату забороняють це повідомлення. No comment provided by engineer. @@ -5640,6 +5786,7 @@ Enable in *Network & servers* settings. Send errors + Помилки надсилання No comment provided by engineer. @@ -5654,6 +5801,7 @@ Enable in *Network & servers* settings. Send message to enable calls. + Надішліть повідомлення, щоб увімкнути дзвінки. No comment provided by engineer. @@ -5758,6 +5906,7 @@ Enable in *Network & servers* settings. Sent directly + Відправлено напряму No comment provided by engineer. @@ -5772,6 +5921,7 @@ Enable in *Network & servers* settings. Sent messages + Надіслані повідомлення No comment provided by engineer. @@ -5781,18 +5931,22 @@ Enable in *Network & servers* settings. Sent reply + Надіслано відповідь No comment provided by engineer. Sent total + Відправлено всього No comment provided by engineer. Sent via proxy + Відправлено через проксі No comment provided by engineer. Server address + Адреса сервера No comment provided by engineer. @@ -5802,6 +5956,7 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. + Адреса сервера несумісна з налаштуваннями мережі: %@. No comment provided by engineer. @@ -5821,6 +5976,7 @@ Enable in *Network & servers* settings. Server type + Тип сервера No comment provided by engineer. @@ -5830,6 +5986,7 @@ Enable in *Network & servers* settings. Server version is incompatible with your app: %@. + Версія сервера несумісна з вашим додатком: %@. No comment provided by engineer. @@ -5839,10 +5996,12 @@ Enable in *Network & servers* settings. Servers info + Інформація про сервери No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + Статистика серверів буде скинута - це неможливо скасувати! No comment provided by engineer. @@ -5862,6 +6021,7 @@ Enable in *Network & servers* settings. Set default theme + Встановлення теми за замовчуванням No comment provided by engineer. @@ -5931,6 +6091,7 @@ Enable in *Network & servers* settings. Share from other apps. + Діліться з інших програм. No comment provided by engineer. @@ -5945,6 +6106,7 @@ Enable in *Network & servers* settings. Share to SimpleX + Поділіться з SimpleX No comment provided by engineer. @@ -5979,6 +6141,7 @@ Enable in *Network & servers* settings. Show percentage + Показати відсоток No comment provided by engineer. @@ -5998,6 +6161,7 @@ Enable in *Network & servers* settings. SimpleX + SimpleX No comment provided by engineer. @@ -6077,6 +6241,7 @@ Enable in *Network & servers* settings. Size + Розмір No comment provided by engineer. @@ -6096,10 +6261,12 @@ Enable in *Network & servers* settings. Soft + М'який blur media Some file(s) were not exported: + Деякі файли не було експортовано: No comment provided by engineer. @@ -6109,6 +6276,7 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: + Під час імпорту виникли деякі несмертельні помилки: No comment provided by engineer. @@ -6138,10 +6306,12 @@ Enable in *Network & servers* settings. Starting from %@. + Починаючи з %@. No comment provided by engineer. Statistics + Статистика No comment provided by engineer. @@ -6206,6 +6376,7 @@ Enable in *Network & servers* settings. Strong + Сильний blur media @@ -6215,14 +6386,17 @@ Enable in *Network & servers* settings. Subscribed + Підписано No comment provided by engineer. Subscription errors + Помилки підписки No comment provided by engineer. Subscriptions ignored + Підписки ігноруються No comment provided by engineer. @@ -6242,6 +6416,7 @@ Enable in *Network & servers* settings. TCP connection + TCP-з'єднання No comment provided by engineer. @@ -6306,6 +6481,7 @@ Enable in *Network & servers* settings. Temporary file error + Тимчасова помилка файлу No comment provided by engineer. @@ -6412,10 +6588,12 @@ It can happen because of some bug or when the connection is compromised. The messages will be deleted for all members. + Повідомлення будуть видалені для всіх учасників. No comment provided by engineer. The messages will be marked as moderated for all members. + Повідомлення будуть позначені як модеровані для всіх учасників. No comment provided by engineer. @@ -6455,6 +6633,7 @@ It can happen because of some bug or when the connection is compromised. Themes + Теми No comment provided by engineer. @@ -6524,6 +6703,7 @@ It can happen because of some bug or when the connection is compromised. This link was used with another mobile device, please create a new link on the desktop. + Це посилання було використано з іншого мобільного пристрою, будь ласка, створіть нове посилання на робочому столі. No comment provided by engineer. @@ -6533,6 +6713,7 @@ It can happen because of some bug or when the connection is compromised. Title + Заголовок No comment provided by engineer. @@ -6599,6 +6780,7 @@ You will be prompted to complete authentication before this feature is enabled.< Toggle chat list: + Перемикання списку чату: No comment provided by engineer. @@ -6608,10 +6790,12 @@ You will be prompted to complete authentication before this feature is enabled.< Toolbar opacity + Непрозорість панелі інструментів No comment provided by engineer. Total + Всього No comment provided by engineer. @@ -6621,6 +6805,7 @@ You will be prompted to complete authentication before this feature is enabled.< Transport sessions + Транспортні сесії No comment provided by engineer. @@ -6792,6 +6977,7 @@ To connect, please ask your contact to create another connection link and check Update settings? + Оновити налаштування? No comment provided by engineer. @@ -6806,6 +6992,7 @@ To connect, please ask your contact to create another connection link and check Upload errors + Помилки завантаження No comment provided by engineer. @@ -6820,10 +7007,12 @@ To connect, please ask your contact to create another connection link and check Uploaded + Завантажено No comment provided by engineer. Uploaded files + Завантажені файли No comment provided by engineer. @@ -6898,6 +7087,7 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. + Використовуйте додаток однією рукою. No comment provided by engineer. @@ -6907,6 +7097,7 @@ To connect, please ask your contact to create another connection link and check User selection + Вибір користувача No comment provided by engineer. @@ -7041,10 +7232,12 @@ To connect, please ask your contact to create another connection link and check Wallpaper accent + Акцент на шпалерах No comment provided by engineer. Wallpaper background + Фон шпалер No comment provided by engineer. @@ -7154,6 +7347,7 @@ To connect, please ask your contact to create another connection link and check Wrong key or unknown file chunk address - most likely file is deleted. + Неправильний ключ або невідома адреса фрагмента файлу - найімовірніше, файл видалено. file error text @@ -7163,6 +7357,7 @@ To connect, please ask your contact to create another connection link and check XFTP server + XFTP-сервер No comment provided by engineer. @@ -7244,6 +7439,7 @@ Repeat join request? You are not connected to these servers. Private routing is used to deliver messages to them. + Не підключені до цих серверів. Для доставлення повідомлень до них використовується приватна маршрутизація. No comment provided by engineer. @@ -7253,6 +7449,7 @@ Repeat join request? You can change it in Appearance settings. + Ви можете змінити його в налаштуваннях зовнішнього вигляду. No comment provided by engineer. @@ -7292,6 +7489,7 @@ Repeat join request? You can send messages to %@ from Archived contacts. + Ви можете надсилати повідомлення на %@ з архівних контактів. No comment provided by engineer. @@ -7321,6 +7519,7 @@ Repeat join request? You can still view conversation with %@ in the list of chats. + Ви все ще можете переглянути розмову з %@ у списку чатів. No comment provided by engineer. @@ -7387,10 +7586,12 @@ Repeat connection request? You may migrate the exported database. + Ви можете мігрувати експортовану базу даних. No comment provided by engineer. You may save the exported archive. + Ви можете зберегти експортований архів. No comment provided by engineer. @@ -7400,6 +7601,7 @@ Repeat connection request? You need to allow your contact to call to be able to call them. + Щоб мати змогу зателефонувати контакту, вам потрібно дозволити йому зателефонувати. No comment provided by engineer. @@ -7666,6 +7868,7 @@ SimpleX servers cannot see your profile. attempts + спроби No comment provided by engineer. @@ -7710,6 +7913,7 @@ SimpleX servers cannot see your profile. call + дзвонити No comment provided by engineer. @@ -7864,6 +8068,7 @@ SimpleX servers cannot see your profile. decryption errors + помилки розшифровки No comment provided by engineer. @@ -7918,6 +8123,7 @@ SimpleX servers cannot see your profile. duplicates + дублікати No comment provided by engineer. @@ -8002,6 +8208,7 @@ SimpleX servers cannot see your profile. expired + закінчився No comment provided by engineer. @@ -8036,6 +8243,7 @@ SimpleX servers cannot see your profile. inactive + неактивний No comment provided by engineer. @@ -8080,6 +8288,7 @@ SimpleX servers cannot see your profile. invite + запросити No comment provided by engineer. @@ -8139,6 +8348,7 @@ SimpleX servers cannot see your profile. message + повідомлення No comment provided by engineer. @@ -8173,6 +8383,7 @@ SimpleX servers cannot see your profile. mute + приглушити No comment provided by engineer. @@ -8229,10 +8440,12 @@ SimpleX servers cannot see your profile. other + інший No comment provided by engineer. other errors + інші помилки No comment provided by engineer. @@ -8307,6 +8520,7 @@ SimpleX servers cannot see your profile. search + пошук No comment provided by engineer. @@ -8395,6 +8609,7 @@ last received msg: %2$@ unmute + увімкнути звук No comment provided by engineer. @@ -8444,6 +8659,7 @@ last received msg: %2$@ video + відео No comment provided by engineer. @@ -8624,14 +8840,17 @@ last received msg: %2$@ SimpleX SE + SimpleX SE Bundle display name SimpleX SE + SimpleX SE Bundle name Copyright © 2024 SimpleX Chat. All rights reserved. + Copyright © 2024 SimpleX Chat. Всі права захищені. Copyright (human-readable) @@ -8643,150 +8862,187 @@ last received msg: %2$@ %@ + %@ No comment provided by engineer. App is locked! + Додаток заблоковано! No comment provided by engineer. Cancel + Скасувати No comment provided by engineer. Cannot access keychain to save database password + Не вдається отримати доступ до зв'язки ключів для збереження пароля до бази даних No comment provided by engineer. Cannot forward message + Неможливо переслати повідомлення No comment provided by engineer. Comment + Коментар No comment provided by engineer. Currently maximum supported file size is %@. + Наразі максимальний підтримуваний розмір файлу - %@. No comment provided by engineer. Database downgrade required + Потрібне оновлення бази даних No comment provided by engineer. Database encrypted! + База даних зашифрована! No comment provided by engineer. Database error + Помилка в базі даних No comment provided by engineer. Database passphrase is different from saved in the keychain. + Парольна фраза бази даних відрізняється від збереженої у в’язці ключів. No comment provided by engineer. Database passphrase is required to open chat. + Для відкриття чату потрібно ввести пароль до бази даних. No comment provided by engineer. Database upgrade required + Потрібне оновлення бази даних No comment provided by engineer. Error preparing file + Помилка підготовки файлу No comment provided by engineer. Error preparing message + Повідомлення про підготовку до помилки No comment provided by engineer. Error: %@ + Помилка: %@ No comment provided by engineer. File error + Помилка файлу No comment provided by engineer. Incompatible database version + Несумісна версія бази даних No comment provided by engineer. Invalid migration confirmation + Недійсне підтвердження міграції No comment provided by engineer. Keychain error + Помилка зв'язки ключів No comment provided by engineer. Large file! + Великий файл! No comment provided by engineer. No active profile + Немає активного профілю No comment provided by engineer. Ok + Гаразд No comment provided by engineer. Open the app to downgrade the database. + Відкрийте програму, щоб знизити версію бази даних. No comment provided by engineer. Open the app to upgrade the database. + Відкрийте програму, щоб оновити базу даних. No comment provided by engineer. Passphrase + Парольна фраза No comment provided by engineer. Please create a profile in the SimpleX app + Будь ласка, створіть профіль у додатку SimpleX No comment provided by engineer. Selected chat preferences prohibit this message. + Вибрані налаштування чату забороняють це повідомлення. No comment provided by engineer. Sending a message takes longer than expected. + Надсилання повідомлення займає більше часу, ніж очікувалося. No comment provided by engineer. Sending message… + Надсилаю повідомлення… No comment provided by engineer. Share + Поділіться No comment provided by engineer. Slow network? + Повільна мережа? No comment provided by engineer. Unknown database error: %@ + Невідома помилка бази даних: %@ No comment provided by engineer. Unsupported format + Непідтримуваний формат No comment provided by engineer. Wait + Зачекай No comment provided by engineer. Wrong database passphrase + Неправильна ключова фраза до бази даних No comment provided by engineer. You can allow sharing in Privacy & Security / SimpleX Lock settings. + Ви можете дозволити спільний доступ у налаштуваннях Конфіденційність і безпека / SimpleX Lock. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 8bc5a3b25d..2b8649935c 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -314,7 +314,7 @@ About SimpleX Chat - 關於 SimpleX 對話 + 關於 SimpleX Chat No comment provided by engineer. @@ -445,7 +445,7 @@ Allow your contacts to send disappearing messages. - 允許你的聯絡人傳送自動銷毀的訊息。 + 允許您的聯絡人傳送限時訊息。 No comment provided by engineer. @@ -5898,6 +5898,230 @@ It can happen because of some bug or when the connection is compromised.%@ 和 %@ 已連接 No comment provided by engineer. + + %@ downloaded + %@ 下載 + + + %@ uploaded + %@ 上傳 + + + Abort + 中止 + + + **Create group**: to create a new group. + **創建群組**: 創建一個新的群組。 + + + Abort changing address + 中止更改地址 + + + Accept connection request? + 接受連線請求? + + + Camera not available + 相機不可用 + + + All messages will be deleted - this cannot be undone! + 所有訊息都將被刪除 - 這不能還原! + + + Allow irreversible message deletion only if your contact allows it to you. (24 hours) + 只有你的聯絡人允許的情況下,才允許不可逆地將訊息刪除。(24小時) + + + Allow to irreversibly delete sent messages. (24 hours) + 允許將不可撤銷的訊息刪除。(24小時) + + + Allow your contacts to irreversibly delete sent messages. (24 hours) + 允許您的聯絡人不可復原地刪除已傳送的訊息。(24小時) + + + Bad desktop address + 無效的桌面地址 + + + Error decrypting file + 解密檔案時出錯 + + + Add contact + 新增聯絡人 + + + Advanced settings + 進階設定 + + + Allow calls? + 允許通話? + + + Allow to send files and media. + 允許傳送檔案和媒體。 + + + Already joining the group! + 已加入群組! + + + App data migration + 應用資料轉移 + + + Apply + 應用 + + + Apply to + 應用到 + + + Archive and upload + 儲存並上傳 + + + Block + 封鎖 + + + Block group members + 封鎖群組成員 + + + Block member + 封鎖成員 + + + Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + 保加利亞語、芬蘭語、泰語和烏克蘭語——感謝使用者們和[Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + + + Can't call member + 無法與成員通話 + + + Can't message member + 無法傳送訊息給成員 + + + Cancel migration + 取消遷移 + + + Chat database exported + 導出聊天數據庫 + + + 0 sec + 0 秒 + + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + 你的所有聯絡人、對話和文件將被安全加密並切塊上傳到設置的 XFTP 中繼。 + + + Address change will be aborted. Old receiving address will be used. + 將取消地址更改。將使用舊聯絡地址。 + + + Archiving database + 正在儲存資料庫 + + + Cellular + 行動網路 + + + %@, %@ and %lld members + %@, %@ 和 %lld 成員 + + + %lld messages marked deleted + %lld 條訊息已刪除 + + + Already connecting! + 已連接! + + + Block member? + 封鎖成員? + + + (new) + (新) + + + %@, %@ and %lld other members connected + %@, %@ 和 %lld 個成員已連接 + + + A few more things + 其他 + + + Show last messages + 顯示最新的訊息 + + + App encrypts new local files (except videos). + 應用程式將為新的本機文件(影片除外)加密。 + + + Better groups + 更加的群組 + + + %lld new interface languages + %lld 種新的介面語言 + + + Blocked by admin + 由管理員封鎖 + + + Both you and your contact can irreversibly delete sent messages. (24 hours) + 您與您的聯絡人都可以不可復原地删除已傳送的訊息。(24小時) + + + Encrypt local files + 加密本機檔案 + + + - more stable message delivery. +- a bit better groups. +- and more! + - 更穩定的傳送! +- 更好的社群! +- 以及更多! + + + - optionally notify deleted contacts. +- profile names with spaces. +- and more! + - 可選擇通知已刪除的聯絡人 +- 帶空格的共人資料名稱。 +-以及更多! + + + Abort changing address? + 中止更改地址? + + + Allow to send SimpleX links. + 允許傳送 SimpleX 連結。 + + + Background + 後台 + diff --git a/apps/ios/SimpleX SE/pl.lproj/InfoPlist.strings b/apps/ios/SimpleX SE/pl.lproj/InfoPlist.strings index 388ac01f7f..81283a3f02 100644 --- a/apps/ios/SimpleX SE/pl.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX SE/pl.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ -/* - InfoPlist.strings - SimpleX +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX SE"; + +/* Bundle name */ +"CFBundleName" = "SimpleX SE"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Wszelkie prawa zastrzeżone."; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/pl.lproj/Localizable.strings b/apps/ios/SimpleX SE/pl.lproj/Localizable.strings index 5ef592ec70..c563431c28 100644 --- a/apps/ios/SimpleX SE/pl.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/pl.lproj/Localizable.strings @@ -1,7 +1,111 @@ -/* - Localizable.strings - SimpleX +/* No comment provided by engineer. */ +"%@" = "%@"; + +/* No comment provided by engineer. */ +"App is locked!" = "Aplikacja zablokowana!"; + +/* No comment provided by engineer. */ +"Cancel" = "Anuluj"; + +/* No comment provided by engineer. */ +"Cannot access keychain to save database password" = "Nie można uzyskać dostępu do pęku kluczy aby zapisać hasło do bazy danych"; + +/* No comment provided by engineer. */ +"Cannot forward message" = "Nie można przekazać wiadomości"; + +/* No comment provided by engineer. */ +"Comment" = "Komentarz"; + +/* No comment provided by engineer. */ +"Currently maximum supported file size is %@." = "Obecnie maksymalny obsługiwany rozmiar pliku to %@."; + +/* No comment provided by engineer. */ +"Database downgrade required" = "Wymagane obniżenie wersji bazy danych"; + +/* No comment provided by engineer. */ +"Database encrypted!" = "Baza danych zaszyfrowana!"; + +/* No comment provided by engineer. */ +"Database error" = "Błąd bazy danych"; + +/* No comment provided by engineer. */ +"Database passphrase is different from saved in the keychain." = "Hasło bazy danych jest inne niż zapisane w pęku kluczy."; + +/* No comment provided by engineer. */ +"Database passphrase is required to open chat." = "Hasło do bazy danych jest wymagane do otwarcia czatu."; + +/* No comment provided by engineer. */ +"Database upgrade required" = "Wymagana aktualizacja bazy danych"; + +/* No comment provided by engineer. */ +"Error preparing file" = "Błąd przygotowania pliku"; + +/* No comment provided by engineer. */ +"Error preparing message" = "Błąd przygotowania wiadomości"; + +/* No comment provided by engineer. */ +"Error: %@" = "Błąd: %@"; + +/* No comment provided by engineer. */ +"File error" = "Błąd pliku"; + +/* No comment provided by engineer. */ +"Incompatible database version" = "Niekompatybilna wersja bazy danych"; + +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Nieprawidłowe potwierdzenie migracji"; + +/* No comment provided by engineer. */ +"Keychain error" = "Błąd pęku kluczy"; + +/* No comment provided by engineer. */ +"Large file!" = "Duży plik!"; + +/* No comment provided by engineer. */ +"No active profile" = "Brak aktywnego profilu"; + +/* No comment provided by engineer. */ +"Ok" = "Ok"; + +/* No comment provided by engineer. */ +"Open the app to downgrade the database." = "Otwórz aplikację aby obniżyć wersję bazy danych."; + +/* No comment provided by engineer. */ +"Open the app to upgrade the database." = "Otwórz aplikację aby zaktualizować bazę danych."; + +/* No comment provided by engineer. */ +"Passphrase" = "Hasło"; + +/* No comment provided by engineer. */ +"Please create a profile in the SimpleX app" = "Proszę utworzyć profil w aplikacji SimpleX"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "Wybrane preferencje czatu zabraniają tej wiadomości."; + +/* No comment provided by engineer. */ +"Sending a message takes longer than expected." = "Wysłanie wiadomości trwa dłużej niż oczekiwano."; + +/* No comment provided by engineer. */ +"Sending message…" = "Wysyłanie wiadomości…"; + +/* No comment provided by engineer. */ +"Share" = "Udostępnij"; + +/* No comment provided by engineer. */ +"Slow network?" = "Wolna sieć?"; + +/* No comment provided by engineer. */ +"Unknown database error: %@" = "Nieznany błąd bazy danych: %@"; + +/* No comment provided by engineer. */ +"Unsupported format" = "Niewspierany format"; + +/* No comment provided by engineer. */ +"Wait" = "Czekaj"; + +/* No comment provided by engineer. */ +"Wrong database passphrase" = "Nieprawidłowe hasło bazy danych"; + +/* No comment provided by engineer. */ +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Możesz zezwolić na udostępnianie w ustawieniach Prywatność i bezpieczeństwo / Blokada SimpleX."; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/uk.lproj/InfoPlist.strings b/apps/ios/SimpleX SE/uk.lproj/InfoPlist.strings index 388ac01f7f..18c4d5e8a5 100644 --- a/apps/ios/SimpleX SE/uk.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX SE/uk.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ -/* - InfoPlist.strings - SimpleX +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX SE"; + +/* Bundle name */ +"CFBundleName" = "SimpleX SE"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Всі права захищені."; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/uk.lproj/Localizable.strings b/apps/ios/SimpleX SE/uk.lproj/Localizable.strings index 5ef592ec70..a6da81185e 100644 --- a/apps/ios/SimpleX SE/uk.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/uk.lproj/Localizable.strings @@ -1,7 +1,111 @@ -/* - Localizable.strings - SimpleX +/* No comment provided by engineer. */ +"%@" = "%@"; + +/* No comment provided by engineer. */ +"App is locked!" = "Додаток заблоковано!"; + +/* No comment provided by engineer. */ +"Cancel" = "Скасувати"; + +/* No comment provided by engineer. */ +"Cannot access keychain to save database password" = "Не вдається отримати доступ до зв'язки ключів для збереження пароля до бази даних"; + +/* No comment provided by engineer. */ +"Cannot forward message" = "Неможливо переслати повідомлення"; + +/* No comment provided by engineer. */ +"Comment" = "Коментар"; + +/* No comment provided by engineer. */ +"Currently maximum supported file size is %@." = "Наразі максимальний підтримуваний розмір файлу - %@."; + +/* No comment provided by engineer. */ +"Database downgrade required" = "Потрібне оновлення бази даних"; + +/* No comment provided by engineer. */ +"Database encrypted!" = "База даних зашифрована!"; + +/* No comment provided by engineer. */ +"Database error" = "Помилка в базі даних"; + +/* No comment provided by engineer. */ +"Database passphrase is different from saved in the keychain." = "Парольна фраза бази даних відрізняється від збереженої у в’язці ключів."; + +/* No comment provided by engineer. */ +"Database passphrase is required to open chat." = "Для відкриття чату потрібно ввести пароль до бази даних."; + +/* No comment provided by engineer. */ +"Database upgrade required" = "Потрібне оновлення бази даних"; + +/* No comment provided by engineer. */ +"Error preparing file" = "Помилка підготовки файлу"; + +/* No comment provided by engineer. */ +"Error preparing message" = "Повідомлення про підготовку до помилки"; + +/* No comment provided by engineer. */ +"Error: %@" = "Помилка: %@"; + +/* No comment provided by engineer. */ +"File error" = "Помилка файлу"; + +/* No comment provided by engineer. */ +"Incompatible database version" = "Несумісна версія бази даних"; + +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Недійсне підтвердження міграції"; + +/* No comment provided by engineer. */ +"Keychain error" = "Помилка зв'язки ключів"; + +/* No comment provided by engineer. */ +"Large file!" = "Великий файл!"; + +/* No comment provided by engineer. */ +"No active profile" = "Немає активного профілю"; + +/* No comment provided by engineer. */ +"Ok" = "Гаразд"; + +/* No comment provided by engineer. */ +"Open the app to downgrade the database." = "Відкрийте програму, щоб знизити версію бази даних."; + +/* No comment provided by engineer. */ +"Open the app to upgrade the database." = "Відкрийте програму, щоб оновити базу даних."; + +/* No comment provided by engineer. */ +"Passphrase" = "Парольна фраза"; + +/* No comment provided by engineer. */ +"Please create a profile in the SimpleX app" = "Будь ласка, створіть профіль у додатку SimpleX"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "Вибрані налаштування чату забороняють це повідомлення."; + +/* No comment provided by engineer. */ +"Sending a message takes longer than expected." = "Надсилання повідомлення займає більше часу, ніж очікувалося."; + +/* No comment provided by engineer. */ +"Sending message…" = "Надсилаю повідомлення…"; + +/* No comment provided by engineer. */ +"Share" = "Поділіться"; + +/* No comment provided by engineer. */ +"Slow network?" = "Повільна мережа?"; + +/* No comment provided by engineer. */ +"Unknown database error: %@" = "Невідома помилка бази даних: %@"; + +/* No comment provided by engineer. */ +"Unsupported format" = "Непідтримуваний формат"; + +/* No comment provided by engineer. */ +"Wait" = "Зачекай"; + +/* No comment provided by engineer. */ +"Wrong database passphrase" = "Неправильна ключова фраза до бази даних"; + +/* No comment provided by engineer. */ +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Ви можете дозволити спільний доступ у налаштуваннях Конфіденційність і безпека / SimpleX Lock."; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 1caf7246b8..3dce4e4474 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -506,7 +506,7 @@ "Allow to send files and media." = "Se permite enviar archivos y multimedia."; /* No comment provided by engineer. */ -"Allow to send SimpleX links." = "Permitir enviar enlaces SimpleX."; +"Allow to send SimpleX links." = "Se permite enviar enlaces SimpleX."; /* No comment provided by engineer. */ "Allow to send voice messages." = "Permites enviar mensajes de voz."; @@ -1606,7 +1606,7 @@ "Do it later" = "Hacer más tarde"; /* No comment provided by engineer. */ -"Do not send history to new members." = "No enviar historial a miembros nuevos."; +"Do not send history to new members." = "No se envía el historial a los miembros nuevos."; /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado."; @@ -3415,7 +3415,7 @@ "Prohibit sending files and media." = "No permitir el envío de archivos y multimedia."; /* No comment provided by engineer. */ -"Prohibit sending SimpleX links." = "No permitir el envío de enlaces SimpleX."; +"Prohibit sending SimpleX links." = "No se permite enviar enlaces SimpleX."; /* No comment provided by engineer. */ "Prohibit sending voice messages." = "No se permiten mensajes de voz."; @@ -3917,7 +3917,7 @@ "Send them from gallery or custom keyboards." = "Envíalos desde la galería o desde teclados personalizados."; /* No comment provided by engineer. */ -"Send up to 100 last messages to new members." = "Enviar hasta 100 últimos mensajes a los miembros nuevos."; +"Send up to 100 last messages to new members." = "Se envían hasta 100 mensajes más recientes a los miembros nuevos."; /* No comment provided by engineer. */ "Sender cancelled file transfer." = "El remitente ha cancelado la transferencia de archivos."; @@ -4979,7 +4979,7 @@ "You can accept calls from lock screen, without device and app authentication." = "Puede aceptar llamadas desde la pantalla de bloqueo, sin autenticación de dispositivos y aplicaciones."; /* No comment provided by engineer. */ -"You can change it in Appearance settings." = "Puede cambiarlo desde el menú Apariencia."; +"You can change it in Appearance settings." = "Puedes cambiar la posición de la barra desde el menú Apariencia."; /* No comment provided by engineer. */ "You can create it later" = "Puedes crearla más tarde"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 449f21a805..8c70bcd626 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -659,10 +659,10 @@ "Bad desktop address" = "Hibás számítógép cím"; /* integrity error chat item */ -"bad message hash" = "téves üzenet hash"; +"bad message hash" = "hibás az üzenet ellenőrzőösszege"; /* No comment provided by engineer. */ -"Bad message hash" = "Téves üzenet hash"; +"Bad message hash" = "Hibás az üzenet ellenőrzőösszege"; /* integrity error chat item */ "bad message ID" = "téves üzenet ID"; @@ -1432,7 +1432,7 @@ "Delete link?" = "Hivatkozás törlése?"; /* No comment provided by engineer. */ -"Delete member message?" = "Csoporttag üzenet törlése?"; +"Delete member message?" = "Csoporttag üzenetének törlése?"; /* No comment provided by engineer. */ "Delete message?" = "Üzenet törlése?"; @@ -1495,7 +1495,7 @@ "Delivery receipts are disabled!" = "A kézbesítési jelentések ki vannak kapcsolva!"; /* No comment provided by engineer. */ -"Delivery receipts!" = "Kézbesítési igazolások!"; +"Delivery receipts!" = "Üzenet kézbesítési jelentések!"; /* No comment provided by engineer. */ "Description" = "Leírás"; @@ -1828,10 +1828,10 @@ "Enter this device name…" = "Eszköznév megadása…"; /* placeholder */ -"Enter welcome message…" = "Üdvözlő üzenetet megadása…"; +"Enter welcome message…" = "Üdvözlő üzenet megadása…"; /* placeholder */ -"Enter welcome message… (optional)" = "Üdvözlő üzenetet megadása… (opcionális)"; +"Enter welcome message… (optional)" = "Üdvözlő üzenet megadása… (opcionális)"; /* No comment provided by engineer. */ "Enter your name…" = "Adjon meg egy nevet…"; @@ -2783,7 +2783,7 @@ "Message delivery error" = "Üzenetkézbesítési hiba"; /* No comment provided by engineer. */ -"Message delivery receipts!" = "Üzenetkézbesítési bizonylatok!"; +"Message delivery receipts!" = "Üzenet kézbesítési jelentések!"; /* item status text */ "Message delivery warning" = "Üzenet kézbesítési figyelmeztetés"; @@ -2813,7 +2813,7 @@ "message received" = "üzenet érkezett"; /* No comment provided by engineer. */ -"Message reception" = "Üzenetjelentés"; +"Message reception" = "Üzenet kézbesítési jelentés"; /* No comment provided by engineer. */ "Message servers" = "Üzenetkiszolgálók"; @@ -3605,7 +3605,7 @@ "removed" = "eltávolítva"; /* rcv group event chat item */ -"removed %@" = "%@ eltávolítva"; +"removed %@" = "eltávolította őt: %@"; /* profile update event chat item */ "removed contact address" = "törölt kapcsolattartási cím"; @@ -4373,7 +4373,7 @@ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!"; /* No comment provided by engineer. */ -"The hash of the previous message is different." = "Az előző üzenet hash-e más."; +"The hash of the previous message is different." = "Az előző üzenet ellenőrzőösszege különbözik."; /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index bb8d97a13d..f3fa0424cc 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -1089,7 +1089,7 @@ "Connection" = "Connessione"; /* No comment provided by engineer. */ -"Connection and servers status." = "Stato di connessione e server."; +"Connection and servers status." = "Stato della connessione e dei server."; /* No comment provided by engineer. */ "Connection error" = "Errore di connessione"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 289fe34a83..7d452743c6 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -2630,7 +2630,7 @@ "Keep" = "Bewaar"; /* No comment provided by engineer. */ -"Keep conversation" = "Blijf in gesprek"; +"Keep conversation" = "Behoud het gesprek"; /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Houd de app geopend om deze vanaf de desktop te gebruiken"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index fa8c276a0a..179b2d3848 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -472,6 +472,9 @@ /* No comment provided by engineer. */ "Allow calls only if your contact allows them." = "Zezwalaj na połączenia tylko wtedy, gdy Twój kontakt na to pozwala."; +/* No comment provided by engineer. */ +"Allow calls?" = "Zezwolić na połączenia?"; + /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Zezwól na znikające wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli."; @@ -493,6 +496,9 @@ /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Zezwól na wysyłanie znikających wiadomości."; +/* No comment provided by engineer. */ +"Allow sharing" = "Zezwól na udostępnianie"; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Zezwól na nieodwracalne usunięcie wysłanych wiadomości. (24 godziny)"; @@ -589,6 +595,12 @@ /* No comment provided by engineer. */ "Archive and upload" = "Archiwizuj i prześlij"; +/* No comment provided by engineer. */ +"Archive contacts to chat later." = "Archiwizuj kontakty aby porozmawiać później."; + +/* No comment provided by engineer. */ +"Archived contacts" = "Zarchiwizowane kontakty"; + /* No comment provided by engineer. */ "Archiving database" = "Archiwizowanie bazy danych"; @@ -664,6 +676,9 @@ /* No comment provided by engineer. */ "Better messages" = "Lepsze wiadomości"; +/* No comment provided by engineer. */ +"Better networking" = "Lepsze sieciowanie"; + /* No comment provided by engineer. */ "Black" = "Czarny"; @@ -697,6 +712,12 @@ /* No comment provided by engineer. */ "Blocked by admin" = "Zablokowany przez admina"; +/* No comment provided by engineer. */ +"Blur for better privacy." = "Rozmycie dla lepszej prywatności."; + +/* No comment provided by engineer. */ +"Blur media" = "Rozmycie mediów"; + /* No comment provided by engineer. */ "bold" = "pogrubiona"; @@ -721,6 +742,9 @@ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"call" = "zadzwoń"; + /* No comment provided by engineer. */ "Call already ended!" = "Połączenie już zakończone!"; @@ -736,15 +760,27 @@ /* No comment provided by engineer. */ "Calls" = "Połączenia"; +/* No comment provided by engineer. */ +"Calls prohibited!" = "Połączenia zakazane!"; + /* No comment provided by engineer. */ "Camera not available" = "Kamera nie dostępna"; +/* No comment provided by engineer. */ +"Can't call contact" = "Nie można zadzwonić do kontaktu"; + +/* No comment provided by engineer. */ +"Can't call member" = "Nie można zadzwonić do członka"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Nie można zaprosić kontaktu!"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Nie można zaprosić kontaktów!"; +/* No comment provided by engineer. */ +"Can't message member" = "Nie można wysłać wiadomości do członka"; + /* No comment provided by engineer. */ "Cancel" = "Anuluj"; @@ -830,6 +866,9 @@ /* No comment provided by engineer. */ "Chat database deleted" = "Baza danych czatu usunięta"; +/* No comment provided by engineer. */ +"Chat database exported" = "Wyeksportowano bazę danych czatu"; + /* No comment provided by engineer. */ "Chat database imported" = "Zaimportowano bazę danych czatu"; @@ -842,6 +881,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Czat został zatrzymany. Jeśli korzystałeś już z tej bazy danych na innym urządzeniu, powinieneś przenieść ją z powrotem przed rozpoczęciem czatu."; +/* No comment provided by engineer. */ +"Chat list" = "Lista czatów"; + /* No comment provided by engineer. */ "Chat migrated!" = "Czat zmigrowany!"; @@ -893,6 +935,9 @@ /* No comment provided by engineer. */ "Clear verification" = "Wyczyść weryfikację"; +/* No comment provided by engineer. */ +"Color chats with the new themes." = "Koloruj czaty z nowymi motywami."; + /* No comment provided by engineer. */ "Color mode" = "Tryb koloru"; @@ -920,6 +965,9 @@ /* No comment provided by engineer. */ "Confirm" = "Potwierdź"; +/* No comment provided by engineer. */ +"Confirm contact deletion?" = "Potwierdzić usunięcie kontaktu?"; + /* No comment provided by engineer. */ "Confirm database upgrades" = "Potwierdź aktualizacje bazy danych"; @@ -959,6 +1007,9 @@ /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "połącz się z deweloperami SimpleX Chat."; +/* No comment provided by engineer. */ +"Connect to your friends faster." = "Szybciej łącz się ze znajomymi."; + /* No comment provided by engineer. */ "Connect to yourself?" = "Połączyć się ze sobą?"; @@ -1025,6 +1076,9 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Łączenie z serwerem... (błąd: %@)"; +/* No comment provided by engineer. */ +"Connecting to contact, please wait or check later!" = "Łączenie z kontaktem, poczekaj lub sprawdź później!"; + /* No comment provided by engineer. */ "Connecting to desktop" = "Łączenie z komputerem"; @@ -1034,6 +1088,9 @@ /* No comment provided by engineer. */ "Connection" = "Połączenie"; +/* No comment provided by engineer. */ +"Connection and servers status." = "Stan połączenia i serwerów."; + /* No comment provided by engineer. */ "Connection error" = "Błąd połączenia"; @@ -1043,6 +1100,9 @@ /* chat list item title (it should not be shown */ "connection established" = "połączenie ustanowione"; +/* No comment provided by engineer. */ +"Connection notifications" = "Powiadomienia o połączeniu"; + /* No comment provided by engineer. */ "Connection request sent!" = "Prośba o połączenie wysłana!"; @@ -1070,6 +1130,9 @@ /* No comment provided by engineer. */ "Contact already exists" = "Kontakt już istnieje"; +/* No comment provided by engineer. */ +"Contact deleted!" = "Kontakt usunięty!"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "kontakt posiada szyfrowanie e2e"; @@ -1082,12 +1145,18 @@ /* notification */ "Contact is connected" = "Kontakt jest połączony"; +/* No comment provided by engineer. */ +"Contact is deleted." = "Kontakt jest usunięty."; + /* No comment provided by engineer. */ "Contact name" = "Nazwa kontaktu"; /* No comment provided by engineer. */ "Contact preferences" = "Preferencje kontaktu"; +/* No comment provided by engineer. */ +"Contact will be deleted - this cannot be undone!" = "Kontakt zostanie usunięty – nie można tego cofnąć!"; + /* No comment provided by engineer. */ "Contacts" = "Kontakty"; @@ -1097,6 +1166,9 @@ /* No comment provided by engineer. */ "Continue" = "Kontynuuj"; +/* No comment provided by engineer. */ +"Conversation deleted!" = "Rozmowa usunięta!"; + /* No comment provided by engineer. */ "Copy" = "Kopiuj"; @@ -1281,6 +1353,9 @@ swipe action */ "Delete" = "Usuń"; +/* No comment provided by engineer. */ +"Delete %lld messages of members?" = "Usunąć %lld wiadomości członków?"; + /* No comment provided by engineer. */ "Delete %lld messages?" = "Usunąć %lld wiadomości?"; @@ -1317,6 +1392,9 @@ /* No comment provided by engineer. */ "Delete contact" = "Usuń kontakt"; +/* No comment provided by engineer. */ +"Delete contact?" = "Usunąć kontakt?"; + /* No comment provided by engineer. */ "Delete database" = "Usuń bazę danych"; @@ -1380,9 +1458,15 @@ /* server test step */ "Delete queue" = "Usuń kolejkę"; +/* No comment provided by engineer. */ +"Delete up to 20 messages at once." = "Usuń do 20 wiadomości na raz."; + /* No comment provided by engineer. */ "Delete user profile?" = "Usunąć profil użytkownika?"; +/* No comment provided by engineer. */ +"Delete without notification" = "Usuń bez powiadomienia"; + /* deleted chat item */ "deleted" = "usunięty"; @@ -1425,9 +1509,15 @@ /* No comment provided by engineer. */ "Desktop devices" = "Urządzenia komputerowe"; +/* No comment provided by engineer. */ +"Destination server address of %@ is incompatible with forwarding server %@ settings." = "Adres serwera docelowego %@ jest niekompatybilny z ustawieniami serwera przekazującego %@."; + /* snd error text */ "Destination server error: %@" = "Błąd docelowego serwera: %@"; +/* No comment provided by engineer. */ +"Destination server version of %@ is incompatible with forwarding server %@." = "Wersja serwera docelowego %@ jest niekompatybilna z serwerem przekierowującym %@."; + /* No comment provided by engineer. */ "Detailed statistics" = "Szczegółowe statystyki"; @@ -1437,6 +1527,9 @@ /* No comment provided by engineer. */ "Develop" = "Deweloperskie"; +/* No comment provided by engineer. */ +"Developer options" = "Opcje deweloperskie"; + /* No comment provided by engineer. */ "Developer tools" = "Narzędzia deweloperskie"; @@ -1476,6 +1569,9 @@ /* No comment provided by engineer. */ "disabled" = "wyłączony"; +/* No comment provided by engineer. */ +"Disabled" = "Wyłączony"; + /* No comment provided by engineer. */ "Disappearing message" = "Znikająca wiadomość"; @@ -1623,6 +1719,9 @@ /* enabled status */ "enabled" = "włączone"; +/* No comment provided by engineer. */ +"Enabled" = "Włączony"; + /* No comment provided by engineer. */ "Enabled for" = "Włączony dla"; @@ -1764,6 +1863,9 @@ /* No comment provided by engineer. */ "Error changing setting" = "Błąd zmiany ustawienia"; +/* No comment provided by engineer. */ +"Error connecting to forwarding server %@. Please try later." = "Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później."; + /* No comment provided by engineer. */ "Error creating address" = "Błąd tworzenia adresu"; @@ -2074,6 +2176,15 @@ /* No comment provided by engineer. */ "Forwarded from" = "Przekazane dalej od"; +/* No comment provided by engineer. */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później."; + +/* No comment provided by engineer. */ +"Forwarding server address is incompatible with network settings: %@." = "Adres serwera przekierowującego jest niekompatybilny z ustawieniami sieciowymi: %@."; + +/* No comment provided by engineer. */ +"Forwarding server version is incompatible with network settings: %@." = "Wersja serwera przekierowującego jest niekompatybilna z ustawieniami sieciowymi: %@."; + /* snd error text */ "Forwarding server: %@\nDestination server error: %@" = "Serwer przekazujący: %1$@\nBłąd serwera docelowego: %2$@"; @@ -2425,6 +2536,9 @@ /* group name */ "invitation to group %@" = "zaproszenie do grupy %@"; +/* No comment provided by engineer. */ +"invite" = "zaproś"; + /* No comment provided by engineer. */ "Invite friends" = "Zaproś znajomych"; @@ -2470,6 +2584,9 @@ /* No comment provided by engineer. */ "It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Może to nastąpić, gdy:\n1. Wiadomości wygasły w wysyłającym kliencie po 2 dniach lub na serwerze po 30 dniach.\n2. Odszyfrowanie wiadomości nie powiodło się, ponieważ Ty lub Twój kontakt użyliście starej kopii zapasowej bazy danych.\n3. Połączenie zostało skompromitowane."; +/* No comment provided by engineer. */ +"It protects your IP address and connections." = "Chroni Twój adres IP i połączenia."; + /* No comment provided by engineer. */ "It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Wygląda na to, że jesteś już połączony przez ten link. Jeśli tak nie jest, wystąpił błąd (%@)."; @@ -2512,6 +2629,9 @@ /* No comment provided by engineer. */ "Keep" = "Zachowaj"; +/* No comment provided by engineer. */ +"Keep conversation" = "Zachowaj rozmowę"; + /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Zostaw aplikację otwartą i używaj ją z komputera"; @@ -2623,6 +2743,12 @@ /* No comment provided by engineer. */ "Max 30 seconds, received instantly." = "Maksymalnie 30 sekund, odbierane natychmiast."; +/* No comment provided by engineer. */ +"Media & file servers" = "Serwery mediów i plików"; + +/* blur media */ +"Medium" = "Średni"; + /* member role */ "member" = "członek"; @@ -2650,6 +2776,9 @@ /* No comment provided by engineer. */ "Menus" = "Menu"; +/* No comment provided by engineer. */ +"message" = "wiadomość"; + /* item status text */ "Message delivery error" = "Błąd dostarczenia wiadomości"; @@ -2686,6 +2815,9 @@ /* No comment provided by engineer. */ "Message reception" = "Odebranie wiadomości"; +/* No comment provided by engineer. */ +"Message servers" = "Serwery wiadomości"; + /* No comment provided by engineer. */ "Message source remains private." = "Źródło wiadomości pozostaje prywatne."; @@ -2794,6 +2926,9 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "Wiele profili czatu"; +/* No comment provided by engineer. */ +"mute" = "wycisz"; + /* swipe action */ "Mute" = "Wycisz"; @@ -2827,6 +2962,9 @@ /* No comment provided by engineer. */ "New chat" = "Nowy czat"; +/* No comment provided by engineer. */ +"New chat experience 🎉" = "Nowe możliwości czatu 🎉"; + /* notification */ "New contact request" = "Nowa prośba o kontakt"; @@ -2845,6 +2983,9 @@ /* No comment provided by engineer. */ "New in %@" = "Nowość w %@"; +/* No comment provided by engineer. */ +"New media options" = "Nowe opcje mediów"; + /* No comment provided by engineer. */ "New member role" = "Nowa rola członka"; @@ -2914,6 +3055,9 @@ /* No comment provided by engineer. */ "Not compatible!" = "Nie kompatybilny!"; +/* No comment provided by engineer. */ +"Nothing selected" = "Nic nie jest zaznaczone"; + /* No comment provided by engineer. */ "Notifications" = "Powiadomienia"; @@ -2970,6 +3114,9 @@ /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**."; +/* No comment provided by engineer. */ +"Only delete conversation" = "Usuń tylko rozmowę"; + /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Tylko właściciele grup mogą zmieniać preferencje grupy."; @@ -3126,6 +3273,12 @@ /* No comment provided by engineer. */ "PING interval" = "Interwał PINGU"; +/* No comment provided by engineer. */ +"Play from the chat list." = "Odtwórz z listy czatów."; + +/* No comment provided by engineer. */ +"Please ask your contact to enable calls." = "Poproś kontakt o włącznie połączeń."; + /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "Poproś Twój kontakt o włączenie wysyłania wiadomości głosowych."; @@ -3306,6 +3459,9 @@ /* No comment provided by engineer. */ "Rate the app" = "Oceń aplikację"; +/* No comment provided by engineer. */ +"Reachable chat toolbar" = "Osiągalny pasek narzędzi czatu"; + /* chat item menu */ "React…" = "Reaguj…"; @@ -3493,6 +3649,9 @@ /* No comment provided by engineer. */ "Reset" = "Resetuj"; +/* No comment provided by engineer. */ +"Reset all hints" = "Zresetuj wszystkie wskazówki"; + /* No comment provided by engineer. */ "Reset all statistics" = "Resetuj wszystkie statystyki"; @@ -3568,6 +3727,9 @@ /* No comment provided by engineer. */ "Save and notify group members" = "Zapisz i powiadom członków grupy"; +/* No comment provided by engineer. */ +"Save and reconnect" = "Zapisz i połącz ponownie"; + /* No comment provided by engineer. */ "Save and update group profile" = "Zapisz i zaktualizuj profil grupowy"; @@ -3643,6 +3805,9 @@ /* No comment provided by engineer. */ "Scan server QR code" = "Zeskanuj kod QR serwera"; +/* No comment provided by engineer. */ +"search" = "szukaj"; + /* No comment provided by engineer. */ "Search" = "Szukaj"; @@ -3682,6 +3847,9 @@ /* chat item action */ "Select" = "Wybierz"; +/* No comment provided by engineer. */ +"Selected %lld" = "Zaznaczono %lld"; + /* No comment provided by engineer. */ "Selected chat preferences prohibit this message." = "Wybrane preferencje czatu zabraniają tej wiadomości."; @@ -3724,6 +3892,9 @@ /* No comment provided by engineer. */ "Send live message" = "Wyślij wiadomość na żywo"; +/* No comment provided by engineer. */ +"Send message to enable calls." = "Wyślij wiadomość aby włączyć połączenia."; + /* No comment provided by engineer. */ "Send messages directly when IP address is protected and your or destination server does not support private routing." = "Wysyłaj wiadomości bezpośrednio, gdy adres IP jest chroniony i Twój lub docelowy serwer nie obsługuje prywatnego trasowania."; @@ -3904,12 +4075,18 @@ /* No comment provided by engineer. */ "Share address with contacts?" = "Udostępnić adres kontaktom?"; +/* No comment provided by engineer. */ +"Share from other apps." = "Udostępnij z innych aplikacji."; + /* No comment provided by engineer. */ "Share link" = "Udostępnij link"; /* No comment provided by engineer. */ "Share this 1-time invite link" = "Udostępnij ten jednorazowy link"; +/* No comment provided by engineer. */ +"Share to SimpleX" = "Udostępnij do SimpleX"; + /* No comment provided by engineer. */ "Share with contacts" = "Udostępnij kontaktom"; @@ -4003,9 +4180,18 @@ /* No comment provided by engineer. */ "SMP server" = "Serwer SMP"; +/* blur media */ +"Soft" = "Łagodny"; + +/* No comment provided by engineer. */ +"Some file(s) were not exported:" = "Niektóre plik(i) nie zostały wyeksportowane:"; + /* No comment provided by engineer. */ "Some non-fatal errors occurred during import - you may see Chat console for more details." = "Podczas importu wystąpiły niekrytyczne błędy - więcej szczegółów można znaleźć w konsoli czatu."; +/* No comment provided by engineer. */ +"Some non-fatal errors occurred during import:" = "Podczas importu wystąpiły niekrytyczne błędy:"; + /* notification title */ "Somebody" = "Ktoś"; @@ -4072,6 +4258,9 @@ /* No comment provided by engineer. */ "strike" = "strajk"; +/* blur media */ +"Strong" = "Silne"; + /* No comment provided by engineer. */ "Submit" = "Zatwierdź"; @@ -4117,6 +4306,9 @@ /* No comment provided by engineer. */ "Tap to scan" = "Dotknij, aby zeskanować"; +/* No comment provided by engineer. */ +"TCP connection" = "Połączenie TCP"; + /* No comment provided by engineer. */ "TCP connection timeout" = "Limit czasu połączenia TCP"; @@ -4192,6 +4384,12 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Wiadomość zostanie oznaczona jako moderowana dla wszystkich członków."; +/* No comment provided by engineer. */ +"The messages will be deleted for all members." = "Wiadomości zostaną usunięte dla wszystkich członków."; + +/* No comment provided by engineer. */ +"The messages will be marked as moderated for all members." = "Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków."; + /* No comment provided by engineer. */ "The next generation of private messaging" = "Następna generacja prywatnych wiadomości"; @@ -4303,9 +4501,15 @@ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach."; +/* No comment provided by engineer. */ +"Toggle chat list:" = "Przełącz listę czatów:"; + /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Przełącz incognito przy połączeniu."; +/* No comment provided by engineer. */ +"Toolbar opacity" = "Nieprzezroczystość paska narzędzi"; + /* No comment provided by engineer. */ "Total" = "Łącznie"; @@ -4408,6 +4612,9 @@ /* authentication reason */ "Unlock app" = "Odblokuj aplikację"; +/* No comment provided by engineer. */ +"unmute" = "wyłącz wyciszenie"; + /* swipe action */ "Unmute" = "Wyłącz wyciszenie"; @@ -4429,6 +4636,9 @@ /* No comment provided by engineer. */ "Update network settings?" = "Zaktualizować ustawienia sieci?"; +/* No comment provided by engineer. */ +"Update settings?" = "Zaktualizować ustawienia?"; + /* rcv group event chat item */ "updated group profile" = "zaktualizowano profil grupy"; @@ -4498,6 +4708,9 @@ /* No comment provided by engineer. */ "Use the app while in the call." = "Używaj aplikacji podczas połączenia."; +/* No comment provided by engineer. */ +"Use the app with one hand." = "Korzystaj z aplikacji jedną ręką."; + /* No comment provided by engineer. */ "User profile" = "Profil użytkownika"; @@ -4552,6 +4765,9 @@ /* No comment provided by engineer. */ "Via secure quantum resistant protocol." = "Dzięki bezpiecznemu protokołowi odpornego kwantowo."; +/* No comment provided by engineer. */ +"video" = "wideo"; + /* No comment provided by engineer. */ "Video call" = "Połączenie wideo"; @@ -4762,6 +4978,9 @@ /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Możesz przyjmować połączenia z ekranu blokady, bez uwierzytelniania urządzenia i aplikacji."; +/* No comment provided by engineer. */ +"You can change it in Appearance settings." = "Możesz to zmienić w ustawieniach wyglądu."; + /* No comment provided by engineer. */ "You can create it later" = "Możesz go utworzyć później"; @@ -4783,6 +5002,9 @@ /* notification body */ "You can now chat with %@" = "Możesz teraz wysyłać wiadomości do %@"; +/* No comment provided by engineer. */ +"You can send messages to %@ from Archived contacts." = "Możesz wysyłać wiadomości do %@ ze zarchiwizowanych kontaktów."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Podgląd powiadomień na ekranie blokady można ustawić w ustawieniach."; @@ -4798,6 +5020,9 @@ /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Możesz rozpocząć czat poprzez Ustawienia aplikacji / Baza danych lub poprzez ponowne uruchomienie aplikacji"; +/* No comment provided by engineer. */ +"You can still view conversation with %@ in the list of chats." = "Nadal możesz przeglądać rozmowę z %@ na liście czatów."; + /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "Możesz włączyć blokadę SimpleX poprzez Ustawienia."; @@ -4849,9 +5074,18 @@ /* snd group event chat item */ "you left" = "wyszedłeś"; +/* No comment provided by engineer. */ +"You may migrate the exported database." = "Możesz zmigrować wyeksportowaną bazy danych."; + +/* No comment provided by engineer. */ +"You may save the exported archive." = "Możesz zapisać wyeksportowane archiwum."; + /* No comment provided by engineer. */ "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Musisz używać najnowszej wersji bazy danych czatu TYLKO na jednym urządzeniu, w przeciwnym razie możesz przestać otrzymywać wiadomości od niektórych kontaktów."; +/* No comment provided by engineer. */ +"You need to allow your contact to call to be able to call them." = "Aby móc dzwonić, musisz zezwolić kontaktowi na połączenia."; + /* No comment provided by engineer. */ "You need to allow your contact to send voice messages to be able to send them." = "Musisz zezwolić Twojemu kontaktowi na wysyłanie wiadomości głosowych, aby móc je wysyłać."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index f436ad561b..1908d386a3 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -334,6 +334,9 @@ /* No comment provided by engineer. */ "above, then choose:" = "вище, а потім обирайте:"; +/* No comment provided by engineer. */ +"Accent" = "Акцент"; + /* accept contact request via notification accept incoming call via notification swipe action */ @@ -352,6 +355,15 @@ /* call status */ "accepted call" = "прийнято виклик"; +/* No comment provided by engineer. */ +"Acknowledged" = "Визнано"; + +/* No comment provided by engineer. */ +"Acknowledgement errors" = "Помилки підтвердження"; + +/* No comment provided by engineer. */ +"Active connections" = "Активні з'єднання"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам."; @@ -376,6 +388,15 @@ /* No comment provided by engineer. */ "Add welcome message" = "Додати вітальне повідомлення"; +/* No comment provided by engineer. */ +"Additional accent" = "Додатковий акцент"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "Додатковий акцент 2"; + +/* No comment provided by engineer. */ +"Additional secondary" = "Додаткова вторинна"; + /* No comment provided by engineer. */ "Address" = "Адреса"; @@ -397,6 +418,9 @@ /* No comment provided by engineer. */ "Advanced network settings" = "Розширені налаштування мережі"; +/* No comment provided by engineer. */ +"Advanced settings" = "Додаткові налаштування"; + /* chat item text */ "agreeing encryption for %@…" = "узгодження шифрування для %@…"; @@ -412,6 +436,9 @@ /* No comment provided by engineer. */ "All data is erased when it is entered." = "Всі дані стираються при введенні."; +/* No comment provided by engineer. */ +"All data is private to your device." = "Всі дані є приватними для вашого пристрою."; + /* No comment provided by engineer. */ "All group members will remain connected." = "Всі учасники групи залишаться на зв'язку."; @@ -427,6 +454,9 @@ /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "Всі нові повідомлення від %@ будуть приховані!"; +/* No comment provided by engineer. */ +"All profiles" = "Всі профілі"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Всі ваші контакти залишаться на зв'язку."; @@ -442,6 +472,9 @@ /* No comment provided by engineer. */ "Allow calls only if your contact allows them." = "Дозволяйте дзвінки, тільки якщо ваш контакт дозволяє їх."; +/* No comment provided by engineer. */ +"Allow calls?" = "Дозволити дзвінки?"; + /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Дозволяйте зникати повідомленням, тільки якщо контакт дозволяє вам це робити."; @@ -463,6 +496,9 @@ /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Дозволити надсилання зникаючих повідомлень."; +/* No comment provided by engineer. */ +"Allow sharing" = "Дозволити спільний доступ"; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Дозволяє безповоротно видаляти надіслані повідомлення. (24 години)"; @@ -553,15 +589,27 @@ /* No comment provided by engineer. */ "Apply" = "Подати заявку"; +/* No comment provided by engineer. */ +"Apply to" = "Звертатися до"; + /* No comment provided by engineer. */ "Archive and upload" = "Архівування та завантаження"; +/* No comment provided by engineer. */ +"Archive contacts to chat later." = "Архівуйте контакти, щоб поспілкуватися пізніше."; + +/* No comment provided by engineer. */ +"Archived contacts" = "Архівні контакти"; + /* No comment provided by engineer. */ "Archiving database" = "Архівування бази даних"; /* No comment provided by engineer. */ "Attach" = "Прикріпити"; +/* No comment provided by engineer. */ +"attempts" = "спроби"; + /* No comment provided by engineer. */ "Audio & video calls" = "Аудіо та відео дзвінки"; @@ -604,6 +652,9 @@ /* No comment provided by engineer. */ "Back" = "Назад"; +/* No comment provided by engineer. */ +"Background" = "Фон"; + /* No comment provided by engineer. */ "Bad desktop address" = "Неправильна адреса робочого столу"; @@ -625,6 +676,12 @@ /* No comment provided by engineer. */ "Better messages" = "Кращі повідомлення"; +/* No comment provided by engineer. */ +"Better networking" = "Краща мережа"; + +/* No comment provided by engineer. */ +"Black" = "Чорний"; + /* No comment provided by engineer. */ "Block" = "Блокувати"; @@ -655,6 +712,12 @@ /* No comment provided by engineer. */ "Blocked by admin" = "Заблокований адміністратором"; +/* No comment provided by engineer. */ +"Blur for better privacy." = "Розмиття для кращої приватності."; + +/* No comment provided by engineer. */ +"Blur media" = "Розмиття медіа"; + /* No comment provided by engineer. */ "bold" = "жирний"; @@ -679,6 +742,9 @@ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"call" = "дзвонити"; + /* No comment provided by engineer. */ "Call already ended!" = "Дзвінок вже закінчився!"; @@ -694,15 +760,27 @@ /* No comment provided by engineer. */ "Calls" = "Дзвінки"; +/* No comment provided by engineer. */ +"Calls prohibited!" = "Дзвінки заборонені!"; + /* No comment provided by engineer. */ "Camera not available" = "Камера недоступна"; +/* No comment provided by engineer. */ +"Can't call contact" = "Не вдається додзвонитися до контакту"; + +/* No comment provided by engineer. */ +"Can't call member" = "Не вдається зателефонувати користувачеві"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Не вдається запросити контакт!"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Неможливо запросити контакти!"; +/* No comment provided by engineer. */ +"Can't message member" = "Не можу надіслати повідомлення користувачеві"; + /* No comment provided by engineer. */ "Cancel" = "Скасувати"; @@ -715,6 +793,9 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Не вдається отримати доступ до зв'язки ключів для збереження пароля до бази даних"; +/* No comment provided by engineer. */ +"Cannot forward message" = "Неможливо переслати повідомлення"; + /* No comment provided by engineer. */ "Cannot receive file" = "Не вдається отримати файл"; @@ -773,6 +854,9 @@ /* No comment provided by engineer. */ "Chat archive" = "Архів чату"; +/* No comment provided by engineer. */ +"Chat colors" = "Кольори чату"; + /* No comment provided by engineer. */ "Chat console" = "Консоль чату"; @@ -782,6 +866,9 @@ /* No comment provided by engineer. */ "Chat database deleted" = "Видалено базу даних чату"; +/* No comment provided by engineer. */ +"Chat database exported" = "Експортовано базу даних чату"; + /* No comment provided by engineer. */ "Chat database imported" = "Імпорт бази даних чату"; @@ -794,12 +881,18 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чат зупинено. Якщо ви вже використовували цю базу даних на іншому пристрої, перенесіть її назад перед запуском чату."; +/* No comment provided by engineer. */ +"Chat list" = "Список чатів"; + /* No comment provided by engineer. */ "Chat migrated!" = "Чат перемістився!"; /* No comment provided by engineer. */ "Chat preferences" = "Налаштування чату"; +/* No comment provided by engineer. */ +"Chat theme" = "Тема чату"; + /* No comment provided by engineer. */ "Chats" = "Чати"; @@ -818,6 +911,15 @@ /* No comment provided by engineer. */ "Choose from library" = "Виберіть з бібліотеки"; +/* No comment provided by engineer. */ +"Chunks deleted" = "Фрагменти видалено"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "Завантажено фрагменти"; + +/* No comment provided by engineer. */ +"Chunks uploaded" = "Завантажено фрагменти"; + /* swipe action */ "Clear" = "Чисто"; @@ -833,6 +935,12 @@ /* No comment provided by engineer. */ "Clear verification" = "Очистити перевірку"; +/* No comment provided by engineer. */ +"Color chats with the new themes." = "Кольорові чати з новими темами."; + +/* No comment provided by engineer. */ +"Color mode" = "Колірний режим"; + /* No comment provided by engineer. */ "colored" = "кольоровий"; @@ -845,12 +953,21 @@ /* No comment provided by engineer. */ "complete" = "завершено"; +/* No comment provided by engineer. */ +"Completed" = "Завершено"; + /* No comment provided by engineer. */ "Configure ICE servers" = "Налаштування серверів ICE"; +/* No comment provided by engineer. */ +"Configured %@ servers" = "Налаштовані сервери %@"; + /* No comment provided by engineer. */ "Confirm" = "Підтвердити"; +/* No comment provided by engineer. */ +"Confirm contact deletion?" = "Підтвердити видалення контакту?"; + /* No comment provided by engineer. */ "Confirm database upgrades" = "Підтвердити оновлення бази даних"; @@ -890,6 +1007,9 @@ /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "зв'язатися з розробниками SimpleX Chat."; +/* No comment provided by engineer. */ +"Connect to your friends faster." = "Швидше спілкуйтеся з друзями."; + /* No comment provided by engineer. */ "Connect to yourself?" = "З'єднатися з самим собою?"; @@ -914,18 +1034,27 @@ /* No comment provided by engineer. */ "connected" = "з'єднаний"; +/* No comment provided by engineer. */ +"Connected" = "Підключено"; + /* No comment provided by engineer. */ "Connected desktop" = "Підключений робочий стіл"; /* rcv group event chat item */ "connected directly" = "з'єднані безпосередньо"; +/* No comment provided by engineer. */ +"Connected servers" = "Підключені сервери"; + /* No comment provided by engineer. */ "Connected to desktop" = "Підключено до настільного комп'ютера"; /* No comment provided by engineer. */ "connecting" = "з'єднання"; +/* No comment provided by engineer. */ +"Connecting" = "Підключення"; + /* No comment provided by engineer. */ "connecting (accepted)" = "з'єднання (прийнято)"; @@ -947,6 +1076,9 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Підключення до сервера... (помилка: %@)"; +/* No comment provided by engineer. */ +"Connecting to contact, please wait or check later!" = "З'єднання з контактом, будь ласка, зачекайте або перевірте пізніше!"; + /* No comment provided by engineer. */ "Connecting to desktop" = "Підключення до ПК"; @@ -956,6 +1088,9 @@ /* No comment provided by engineer. */ "Connection" = "Підключення"; +/* No comment provided by engineer. */ +"Connection and servers status." = "Стан з'єднання та серверів."; + /* No comment provided by engineer. */ "Connection error" = "Помилка підключення"; @@ -965,6 +1100,9 @@ /* chat list item title (it should not be shown */ "connection established" = "з'єднання встановлене"; +/* No comment provided by engineer. */ +"Connection notifications" = "Сповіщення про підключення"; + /* No comment provided by engineer. */ "Connection request sent!" = "Запит на підключення відправлено!"; @@ -974,9 +1112,15 @@ /* No comment provided by engineer. */ "Connection timeout" = "Тайм-аут з'єднання"; +/* No comment provided by engineer. */ +"Connection with desktop stopped" = "Припинено зв'язок з робочим столом"; + /* connection information */ "connection:%@" = "з'єднання:%@"; +/* No comment provided by engineer. */ +"Connections" = "З'єднання"; + /* profile update event chat item */ "contact %@ changed to %@" = "контакт %1$@ змінено на %2$@"; @@ -986,6 +1130,9 @@ /* No comment provided by engineer. */ "Contact already exists" = "Контакт вже існує"; +/* No comment provided by engineer. */ +"Contact deleted!" = "Контакт видалено!"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "контакт має шифрування e2e"; @@ -998,12 +1145,18 @@ /* notification */ "Contact is connected" = "Контакт підключений"; +/* No comment provided by engineer. */ +"Contact is deleted." = "Контакт видалено."; + /* No comment provided by engineer. */ "Contact name" = "Ім'я контактної особи"; /* No comment provided by engineer. */ "Contact preferences" = "Налаштування контактів"; +/* No comment provided by engineer. */ +"Contact will be deleted - this cannot be undone!" = "Контакт буде видалено - це неможливо скасувати!"; + /* No comment provided by engineer. */ "Contacts" = "Контакти"; @@ -1013,9 +1166,15 @@ /* No comment provided by engineer. */ "Continue" = "Продовжуйте"; +/* No comment provided by engineer. */ +"Conversation deleted!" = "Розмова видалена!"; + /* No comment provided by engineer. */ "Copy" = "Копіювати"; +/* No comment provided by engineer. */ +"Copy error" = "Помилка копіювання"; + /* No comment provided by engineer. */ "Core version: v%@" = "Основна версія: v%@"; @@ -1061,6 +1220,9 @@ /* No comment provided by engineer. */ "Create your profile" = "Створіть свій профіль"; +/* No comment provided by engineer. */ +"Created" = "Створено"; + /* No comment provided by engineer. */ "Created at" = "Створено за адресою"; @@ -1085,6 +1247,9 @@ /* No comment provided by engineer. */ "Current passphrase…" = "Поточна парольна фраза…"; +/* No comment provided by engineer. */ +"Current profile" = "Поточний профіль"; + /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "Наразі максимальний підтримуваний розмір файлу - %@."; @@ -1094,9 +1259,15 @@ /* No comment provided by engineer. */ "Custom time" = "Індивідуальний час"; +/* No comment provided by engineer. */ +"Customize theme" = "Налаштувати тему"; + /* No comment provided by engineer. */ "Dark" = "Темний"; +/* No comment provided by engineer. */ +"Dark mode colors" = "Кольори темного режиму"; + /* No comment provided by engineer. */ "Database downgrade" = "Пониження версії бази даних"; @@ -1166,6 +1337,9 @@ /* message decrypt error item */ "Decryption error" = "Помилка розшифровки"; +/* No comment provided by engineer. */ +"decryption errors" = "помилки розшифровки"; + /* pref value */ "default (%@)" = "за замовчуванням (%@)"; @@ -1179,6 +1353,9 @@ swipe action */ "Delete" = "Видалити"; +/* No comment provided by engineer. */ +"Delete %lld messages of members?" = "Видалити %lld повідомлень користувачів?"; + /* No comment provided by engineer. */ "Delete %lld messages?" = "Видалити %lld повідомлень?"; @@ -1215,6 +1392,9 @@ /* No comment provided by engineer. */ "Delete contact" = "Видалити контакт"; +/* No comment provided by engineer. */ +"Delete contact?" = "Видалити контакт?"; + /* No comment provided by engineer. */ "Delete database" = "Видалити базу даних"; @@ -1278,12 +1458,21 @@ /* server test step */ "Delete queue" = "Видалити чергу"; +/* No comment provided by engineer. */ +"Delete up to 20 messages at once." = "Видаляйте до 20 повідомлень одночасно."; + /* No comment provided by engineer. */ "Delete user profile?" = "Видалити профіль користувача?"; +/* No comment provided by engineer. */ +"Delete without notification" = "Видалення без попередження"; + /* deleted chat item */ "deleted" = "видалено"; +/* No comment provided by engineer. */ +"Deleted" = "Видалено"; + /* No comment provided by engineer. */ "Deleted at" = "Видалено за"; @@ -1296,6 +1485,9 @@ /* rcv group event chat item */ "deleted group" = "видалено групу"; +/* No comment provided by engineer. */ +"Deletion errors" = "Помилки видалення"; + /* No comment provided by engineer. */ "Delivery" = "Доставка"; @@ -1317,12 +1509,27 @@ /* No comment provided by engineer. */ "Desktop devices" = "Настільні пристрої"; +/* No comment provided by engineer. */ +"Destination server address of %@ is incompatible with forwarding server %@ settings." = "Адреса сервера призначення %@ несумісна з налаштуваннями сервера пересилання %@."; + /* snd error text */ "Destination server error: %@" = "Помилка сервера призначення: %@"; +/* No comment provided by engineer. */ +"Destination server version of %@ is incompatible with forwarding server %@." = "Версія сервера призначення %@ несумісна з версією сервера переадресації %@."; + +/* No comment provided by engineer. */ +"Detailed statistics" = "Детальна статистика"; + +/* No comment provided by engineer. */ +"Details" = "Деталі"; + /* No comment provided by engineer. */ "Develop" = "Розробник"; +/* No comment provided by engineer. */ +"Developer options" = "Можливості для розробників"; + /* No comment provided by engineer. */ "Developer tools" = "Інструменти для розробників"; @@ -1362,6 +1569,9 @@ /* No comment provided by engineer. */ "disabled" = "вимкнено"; +/* No comment provided by engineer. */ +"Disabled" = "Вимкнено"; + /* No comment provided by engineer. */ "Disappearing message" = "Зникаюче повідомлення"; @@ -1422,12 +1632,21 @@ /* chat item action */ "Download" = "Завантажити"; +/* No comment provided by engineer. */ +"Download errors" = "Помилки завантаження"; + /* No comment provided by engineer. */ "Download failed" = "Не вдалося завантажити"; /* server test step */ "Download file" = "Завантажити файл"; +/* No comment provided by engineer. */ +"Downloaded" = "Завантажено"; + +/* No comment provided by engineer. */ +"Downloaded files" = "Завантажені файли"; + /* No comment provided by engineer. */ "Downloading archive" = "Завантажити архів"; @@ -1440,6 +1659,9 @@ /* integrity error chat item */ "duplicate message" = "дублююче повідомлення"; +/* No comment provided by engineer. */ +"duplicates" = "дублікати"; + /* No comment provided by engineer. */ "Duration" = "Тривалість"; @@ -1497,6 +1719,9 @@ /* enabled status */ "enabled" = "увімкнено"; +/* No comment provided by engineer. */ +"Enabled" = "Увімкнено"; + /* No comment provided by engineer. */ "Enabled for" = "Увімкнено для"; @@ -1638,6 +1863,9 @@ /* No comment provided by engineer. */ "Error changing setting" = "Помилка зміни налаштування"; +/* No comment provided by engineer. */ +"Error connecting to forwarding server %@. Please try later." = "Помилка підключення до сервера переадресації %@. Спробуйте пізніше."; + /* No comment provided by engineer. */ "Error creating address" = "Помилка створення адреси"; @@ -1695,6 +1923,9 @@ /* No comment provided by engineer. */ "Error exporting chat database" = "Помилка експорту бази даних чату"; +/* No comment provided by engineer. */ +"Error exporting theme: %@" = "Помилка експорту теми: %@"; + /* No comment provided by engineer. */ "Error importing chat database" = "Помилка імпорту бази даних чату"; @@ -1710,9 +1941,18 @@ /* No comment provided by engineer. */ "Error receiving file" = "Помилка отримання файлу"; +/* No comment provided by engineer. */ +"Error reconnecting server" = "Помилка перепідключення сервера"; + +/* No comment provided by engineer. */ +"Error reconnecting servers" = "Помилка перепідключення серверів"; + /* No comment provided by engineer. */ "Error removing member" = "Помилка видалення учасника"; +/* No comment provided by engineer. */ +"Error resetting statistics" = "Статистика скидання помилок"; + /* No comment provided by engineer. */ "Error saving %@ servers" = "Помилка збереження %@ серверів"; @@ -1792,6 +2032,9 @@ /* No comment provided by engineer. */ "Error: URL is invalid" = "Помилка: URL-адреса невірна"; +/* No comment provided by engineer. */ +"Errors" = "Помилки"; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Навіть коли вимкнений у розмові."; @@ -1804,12 +2047,18 @@ /* chat item action */ "Expand" = "Розгорнути"; +/* No comment provided by engineer. */ +"expired" = "закінчився"; + /* No comment provided by engineer. */ "Export database" = "Експорт бази даних"; /* No comment provided by engineer. */ "Export error:" = "Помилка експорту:"; +/* No comment provided by engineer. */ +"Export theme" = "Тема експорту"; + /* No comment provided by engineer. */ "Exported database archive." = "Експортований архів бази даних."; @@ -1831,6 +2080,21 @@ /* swipe action */ "Favorite" = "Улюблений"; +/* No comment provided by engineer. */ +"File error" = "Помилка файлу"; + +/* file error text */ +"File not found - most likely file was deleted or cancelled." = "Файл не знайдено - найімовірніше, файл було видалено або скасовано."; + +/* file error text */ +"File server error: %@" = "Помилка файлового сервера: %@"; + +/* No comment provided by engineer. */ +"File status" = "Статус файлу"; + +/* copied message info */ +"File status: %@" = "Статус файлу: %@"; + /* No comment provided by engineer. */ "File will be deleted from servers." = "Файл буде видалено з серверів."; @@ -1912,6 +2176,15 @@ /* No comment provided by engineer. */ "Forwarded from" = "Переслано з"; +/* No comment provided by engineer. */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Серверу переадресації %@ не вдалося з'єднатися з сервером призначення %@. Спробуйте пізніше."; + +/* No comment provided by engineer. */ +"Forwarding server address is incompatible with network settings: %@." = "Адреса сервера переадресації несумісна з налаштуваннями мережі: %@."; + +/* No comment provided by engineer. */ +"Forwarding server version is incompatible with network settings: %@." = "Версія сервера переадресації несумісна з мережевими налаштуваннями: %@."; + /* snd error text */ "Forwarding server: %@\nDestination server error: %@" = "Сервер переадресації: %1$@\nПомилка сервера призначення: %2$@"; @@ -1945,6 +2218,12 @@ /* No comment provided by engineer. */ "GIFs and stickers" = "GIF-файли та наклейки"; +/* message preview */ +"Good afternoon!" = "Доброго дня!"; + +/* message preview */ +"Good morning!" = "Доброго ранку!"; + /* No comment provided by engineer. */ "Group" = "Група"; @@ -2122,6 +2401,9 @@ /* No comment provided by engineer. */ "Import failed" = "Не вдалося імпортувати"; +/* No comment provided by engineer. */ +"Import theme" = "Імпорт теми"; + /* No comment provided by engineer. */ "Importing archive" = "Імпорт архіву"; @@ -2143,6 +2425,9 @@ /* No comment provided by engineer. */ "In-call sounds" = "Звуки вхідного дзвінка"; +/* No comment provided by engineer. */ +"inactive" = "неактивний"; + /* No comment provided by engineer. */ "Incognito" = "Інкогніто"; @@ -2206,6 +2491,9 @@ /* No comment provided by engineer. */ "Interface" = "Інтерфейс"; +/* No comment provided by engineer. */ +"Interface colors" = "Кольори інтерфейсу"; + /* invalid chat data */ "invalid chat" = "недійсний чат"; @@ -2248,6 +2536,9 @@ /* group name */ "invitation to group %@" = "запрошення до групи %@"; +/* No comment provided by engineer. */ +"invite" = "запросити"; + /* No comment provided by engineer. */ "Invite friends" = "Запросити друзів"; @@ -2293,6 +2584,9 @@ /* No comment provided by engineer. */ "It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Це може статися, коли:\n1. Термін дії повідомлень закінчився в клієнті-відправнику через 2 дні або на сервері через 30 днів.\n2. Не вдалося розшифрувати повідомлення, тому що ви або ваш контакт використовували стару резервну копію бази даних.\n3. З'єднання було скомпрометовано."; +/* No comment provided by engineer. */ +"It protects your IP address and connections." = "Він захищає вашу IP-адресу та з'єднання."; + /* No comment provided by engineer. */ "It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Схоже, що ви вже підключені за цим посиланням. Якщо це не так, сталася помилка (%@)."; @@ -2335,6 +2629,9 @@ /* No comment provided by engineer. */ "Keep" = "Тримай"; +/* No comment provided by engineer. */ +"Keep conversation" = "Підтримуйте розмову"; + /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Тримайте додаток відкритим, щоб використовувати його з робочого столу"; @@ -2446,6 +2743,12 @@ /* No comment provided by engineer. */ "Max 30 seconds, received instantly." = "Максимум 30 секунд, отримується миттєво."; +/* No comment provided by engineer. */ +"Media & file servers" = "Медіа та файлові сервери"; + +/* blur media */ +"Medium" = "Середній"; + /* member role */ "member" = "учасник"; @@ -2458,6 +2761,9 @@ /* rcv group event chat item */ "member connected" = "з'єднаний"; +/* item status text */ +"Member inactive" = "Користувач неактивний"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Роль учасника буде змінено на \"%@\". Всі учасники групи будуть повідомлені про це."; @@ -2467,6 +2773,12 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Учасник буде видалений з групи - це неможливо скасувати!"; +/* No comment provided by engineer. */ +"Menus" = "Меню"; + +/* No comment provided by engineer. */ +"message" = "повідомлення"; + /* item status text */ "Message delivery error" = "Помилка доставки повідомлення"; @@ -2479,6 +2791,12 @@ /* No comment provided by engineer. */ "Message draft" = "Чернетка повідомлення"; +/* item status text */ +"Message forwarded" = "Повідомлення переслано"; + +/* item status description */ +"Message may be delivered later if member becomes active." = "Повідомлення може бути доставлене пізніше, якщо користувач стане активним."; + /* No comment provided by engineer. */ "Message queue info" = "Інформація про чергу повідомлень"; @@ -2494,9 +2812,21 @@ /* notification */ "message received" = "повідомлення отримано"; +/* No comment provided by engineer. */ +"Message reception" = "Прийом повідомлень"; + +/* No comment provided by engineer. */ +"Message servers" = "Сервери повідомлень"; + /* No comment provided by engineer. */ "Message source remains private." = "Джерело повідомлення залишається приватним."; +/* No comment provided by engineer. */ +"Message status" = "Статус повідомлення"; + +/* copied message info */ +"Message status: %@" = "Статус повідомлення: %@"; + /* No comment provided by engineer. */ "Message text" = "Текст повідомлення"; @@ -2512,6 +2842,12 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Повідомлення від %@ будуть показані!"; +/* No comment provided by engineer. */ +"Messages received" = "Отримані повідомлення"; + +/* No comment provided by engineer. */ +"Messages sent" = "Надіслані повідомлення"; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Повідомлення, файли та дзвінки захищені **наскрізним шифруванням** з ідеальною секретністю переадресації, відмовою та відновленням після злому."; @@ -2590,6 +2926,9 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "Кілька профілів чату"; +/* No comment provided by engineer. */ +"mute" = "приглушити"; + /* swipe action */ "Mute" = "Вимкнути звук"; @@ -2623,6 +2962,9 @@ /* No comment provided by engineer. */ "New chat" = "Новий чат"; +/* No comment provided by engineer. */ +"New chat experience 🎉" = "Новий досвід спілкування в чаті 🎉"; + /* notification */ "New contact request" = "Новий запит на контакт"; @@ -2641,6 +2983,9 @@ /* No comment provided by engineer. */ "New in %@" = "Нове в %@"; +/* No comment provided by engineer. */ +"New media options" = "Нові медіа-опції"; + /* No comment provided by engineer. */ "New member role" = "Нова роль учасника"; @@ -2677,6 +3022,9 @@ /* No comment provided by engineer. */ "No device token!" = "Токен пристрою відсутній!"; +/* item status description */ +"No direct connection yet, message is forwarded by admin." = "Прямого зв'язку ще немає, повідомлення пересилається адміністратором."; + /* No comment provided by engineer. */ "no e2e encryption" = "без шифрування e2e"; @@ -2689,6 +3037,9 @@ /* No comment provided by engineer. */ "No history" = "Немає історії"; +/* No comment provided by engineer. */ +"No info, try to reload" = "Немає інформації, спробуйте перезавантажити"; + /* No comment provided by engineer. */ "No network connection" = "Немає підключення до мережі"; @@ -2704,6 +3055,9 @@ /* No comment provided by engineer. */ "Not compatible!" = "Не сумісні!"; +/* No comment provided by engineer. */ +"Nothing selected" = "Нічого не вибрано"; + /* No comment provided by engineer. */ "Notifications" = "Сповіщення"; @@ -2760,6 +3114,9 @@ /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Тільки клієнтські пристрої зберігають профілі користувачів, контакти, групи та повідомлення, надіслані за допомогою **2-шарового наскрізного шифрування**."; +/* No comment provided by engineer. */ +"Only delete conversation" = "Видаляйте тільки розмови"; + /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Тільки власники груп можуть змінювати налаштування групи."; @@ -2814,6 +3171,9 @@ /* authentication reason */ "Open migration to another device" = "Відкрита міграція на інший пристрій"; +/* No comment provided by engineer. */ +"Open server settings" = "Відкрити налаштування сервера"; + /* No comment provided by engineer. */ "Open Settings" = "Відкрийте Налаштування"; @@ -2838,9 +3198,18 @@ /* No comment provided by engineer. */ "Or show this code" = "Або покажіть цей код"; +/* No comment provided by engineer. */ +"other" = "інший"; + /* No comment provided by engineer. */ "Other" = "Інше"; +/* No comment provided by engineer. */ +"Other %@ servers" = "Інші сервери %@"; + +/* No comment provided by engineer. */ +"other errors" = "інші помилки"; + /* member role */ "owner" = "власник"; @@ -2883,6 +3252,9 @@ /* No comment provided by engineer. */ "peer-to-peer" = "одноранговий"; +/* No comment provided by engineer. */ +"Pending" = "В очікуванні"; + /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "Люди можуть зв'язатися з вами лише за посиланнями, якими ви ділитеся."; @@ -2901,9 +3273,18 @@ /* No comment provided by engineer. */ "PING interval" = "Інтервал PING"; +/* No comment provided by engineer. */ +"Play from the chat list." = "Грати зі списку чату."; + +/* No comment provided by engineer. */ +"Please ask your contact to enable calls." = "Будь ласка, попросіть свого контакту ввімкнути дзвінки."; + /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "Будь ласка, попросіть вашого контакту увімкнути відправку голосових повідомлень."; +/* No comment provided by engineer. */ +"Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "Переконайтеся, що мобільний і настільний комп'ютери підключені до однієї локальної мережі, і що брандмауер настільного комп'ютера дозволяє з'єднання.\nБудь ласка, повідомте про будь-які інші проблеми розробникам."; + /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Будь ласка, перевірте, чи ви скористалися правильним посиланням, або попросіть контактну особу надіслати вам інше."; @@ -2961,6 +3342,9 @@ /* No comment provided by engineer. */ "Preview" = "Попередній перегляд"; +/* No comment provided by engineer. */ +"Previously connected servers" = "Раніше підключені сервери"; + /* No comment provided by engineer. */ "Privacy & security" = "Конфіденційність і безпека"; @@ -2982,6 +3366,9 @@ /* No comment provided by engineer. */ "Private routing" = "Приватна маршрутизація"; +/* No comment provided by engineer. */ +"Private routing error" = "Помилка приватної маршрутизації"; + /* No comment provided by engineer. */ "Profile and server connections" = "З'єднання профілю та сервера"; @@ -3000,6 +3387,9 @@ /* No comment provided by engineer. */ "Profile password" = "Пароль до профілю"; +/* No comment provided by engineer. */ +"Profile theme" = "Тема профілю"; + /* No comment provided by engineer. */ "Profile update will be sent to your contacts." = "Оновлення профілю буде надіслано вашим контактам."; @@ -3048,6 +3438,12 @@ /* No comment provided by engineer. */ "Protocol timeout per KB" = "Тайм-аут протоколу на КБ"; +/* No comment provided by engineer. */ +"Proxied" = "Проксі-сервер"; + +/* No comment provided by engineer. */ +"Proxied servers" = "Проксі-сервери"; + /* No comment provided by engineer. */ "Push notifications" = "Push-повідомлення"; @@ -3063,6 +3459,9 @@ /* No comment provided by engineer. */ "Rate the app" = "Оцініть додаток"; +/* No comment provided by engineer. */ +"Reachable chat toolbar" = "Доступна панель інструментів чату"; + /* chat item menu */ "React…" = "Реагуй…"; @@ -3090,6 +3489,9 @@ /* No comment provided by engineer. */ "Receipts are disabled" = "Підтвердження виключені"; +/* No comment provided by engineer. */ +"Receive errors" = "Отримання помилок"; + /* No comment provided by engineer. */ "received answer…" = "отримали відповідь…"; @@ -3108,6 +3510,15 @@ /* message info title */ "Received message" = "Отримано повідомлення"; +/* No comment provided by engineer. */ +"Received messages" = "Отримані повідомлення"; + +/* No comment provided by engineer. */ +"Received reply" = "Отримано відповідь"; + +/* No comment provided by engineer. */ +"Received total" = "Отримано всього"; + /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Адреса отримувача буде змінена на інший сервер. Зміна адреси завершиться після того, як відправник з'явиться в мережі."; @@ -3126,9 +3537,24 @@ /* No comment provided by engineer. */ "Recipients see updates as you type them." = "Одержувачі бачать оновлення, коли ви їх вводите."; +/* No comment provided by engineer. */ +"Reconnect" = "Повторне підключення"; + /* No comment provided by engineer. */ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "Перепідключіть всі підключені сервери, щоб примусово доставити повідомлення. Це використовує додатковий трафік."; +/* No comment provided by engineer. */ +"Reconnect all servers" = "Перепідключіть усі сервери"; + +/* No comment provided by engineer. */ +"Reconnect all servers?" = "Перепідключити всі сервери?"; + +/* No comment provided by engineer. */ +"Reconnect server to force message delivery. It uses additional traffic." = "Перепідключити сервер для примусової доставки повідомлень. Використовує додатковий трафік."; + +/* No comment provided by engineer. */ +"Reconnect server?" = "Перепідключити сервер?"; + /* No comment provided by engineer. */ "Reconnect servers?" = "Перепідключити сервери?"; @@ -3163,6 +3589,9 @@ /* No comment provided by engineer. */ "Remove" = "Видалити"; +/* No comment provided by engineer. */ +"Remove image" = "Видалити зображення"; + /* No comment provided by engineer. */ "Remove member" = "Видалити учасника"; @@ -3220,12 +3649,27 @@ /* No comment provided by engineer. */ "Reset" = "Перезавантаження"; +/* No comment provided by engineer. */ +"Reset all hints" = "Скинути всі підказки"; + +/* No comment provided by engineer. */ +"Reset all statistics" = "Скинути всю статистику"; + +/* No comment provided by engineer. */ +"Reset all statistics?" = "Скинути всю статистику?"; + /* No comment provided by engineer. */ "Reset colors" = "Скинути кольори"; +/* No comment provided by engineer. */ +"Reset to app theme" = "Повернутися до теми програми"; + /* No comment provided by engineer. */ "Reset to defaults" = "Відновити налаштування за замовчуванням"; +/* No comment provided by engineer. */ +"Reset to user theme" = "Повернутися до теми користувача"; + /* No comment provided by engineer. */ "Restart the app to create a new chat profile" = "Перезапустіть програму, щоб створити новий профіль чату"; @@ -3283,6 +3727,9 @@ /* No comment provided by engineer. */ "Save and notify group members" = "Зберегти та повідомити учасників групи"; +/* No comment provided by engineer. */ +"Save and reconnect" = "Збережіть і підключіться знову"; + /* No comment provided by engineer. */ "Save and update group profile" = "Збереження та оновлення профілю групи"; @@ -3337,6 +3784,12 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Збережені сервери WebRTC ICE буде видалено"; +/* No comment provided by engineer. */ +"Scale" = "Масштаб"; + +/* No comment provided by engineer. */ +"Scan / Paste link" = "Відсканувати / Вставити посилання"; + /* No comment provided by engineer. */ "Scan code" = "Сканувати код"; @@ -3352,6 +3805,9 @@ /* No comment provided by engineer. */ "Scan server QR code" = "Відскануйте QR-код сервера"; +/* No comment provided by engineer. */ +"search" = "пошук"; + /* No comment provided by engineer. */ "Search" = "Пошук"; @@ -3364,6 +3820,9 @@ /* network option */ "sec" = "сек"; +/* No comment provided by engineer. */ +"Secondary" = "Вторинний"; + /* time unit */ "seconds" = "секунди"; @@ -3373,6 +3832,9 @@ /* server test step */ "Secure queue" = "Безпечна черга"; +/* No comment provided by engineer. */ +"Secured" = "Забезпечено"; + /* No comment provided by engineer. */ "Security assessment" = "Оцінка безпеки"; @@ -3385,6 +3847,12 @@ /* chat item action */ "Select" = "Виберіть"; +/* No comment provided by engineer. */ +"Selected %lld" = "Вибрано %lld"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "Вибрані налаштування чату забороняють це повідомлення."; + /* No comment provided by engineer. */ "Self-destruct" = "Самознищення"; @@ -3415,12 +3883,18 @@ /* No comment provided by engineer. */ "Send disappearing message" = "Надіслати зникаюче повідомлення"; +/* No comment provided by engineer. */ +"Send errors" = "Помилки надсилання"; + /* No comment provided by engineer. */ "Send link previews" = "Надіслати попередній перегляд за посиланням"; /* No comment provided by engineer. */ "Send live message" = "Надіслати живе повідомлення"; +/* No comment provided by engineer. */ +"Send message to enable calls." = "Надішліть повідомлення, щоб увімкнути дзвінки."; + /* No comment provided by engineer. */ "Send messages directly when IP address is protected and your or destination server does not support private routing." = "Надсилайте повідомлення напряму, якщо IP-адреса захищена, а ваш сервер або сервер призначення не підтримує приватну маршрутизацію."; @@ -3481,15 +3955,36 @@ /* copied message info */ "Sent at: %@" = "Надіслано за: %@"; +/* No comment provided by engineer. */ +"Sent directly" = "Відправлено напряму"; + /* notification */ "Sent file event" = "Подія надісланого файлу"; /* message info title */ "Sent message" = "Надіслано повідомлення"; +/* No comment provided by engineer. */ +"Sent messages" = "Надіслані повідомлення"; + /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Надіслані повідомлення будуть видалені через встановлений час."; +/* No comment provided by engineer. */ +"Sent reply" = "Надіслано відповідь"; + +/* No comment provided by engineer. */ +"Sent total" = "Відправлено всього"; + +/* No comment provided by engineer. */ +"Sent via proxy" = "Відправлено через проксі"; + +/* No comment provided by engineer. */ +"Server address" = "Адреса сервера"; + +/* No comment provided by engineer. */ +"Server address is incompatible with network settings: %@." = "Адреса сервера несумісна з налаштуваннями мережі: %@."; + /* srv error text. */ "Server address is incompatible with network settings." = "Адреса сервера несумісна з налаштуваннями мережі."; @@ -3505,12 +4000,24 @@ /* No comment provided by engineer. */ "Server test failed!" = "Тест сервера завершився невдало!"; +/* No comment provided by engineer. */ +"Server type" = "Тип сервера"; + /* srv error text */ "Server version is incompatible with network settings." = "Серверна версія несумісна з мережевими налаштуваннями."; +/* No comment provided by engineer. */ +"Server version is incompatible with your app: %@." = "Версія сервера несумісна з вашим додатком: %@."; + /* No comment provided by engineer. */ "Servers" = "Сервери"; +/* No comment provided by engineer. */ +"Servers info" = "Інформація про сервери"; + +/* No comment provided by engineer. */ +"Servers statistics will be reset - this cannot be undone!" = "Статистика серверів буде скинута - це неможливо скасувати!"; + /* No comment provided by engineer. */ "Session code" = "Код сесії"; @@ -3520,6 +4027,9 @@ /* No comment provided by engineer. */ "Set contact name…" = "Встановити ім'я контакту…"; +/* No comment provided by engineer. */ +"Set default theme" = "Встановлення теми за замовчуванням"; + /* No comment provided by engineer. */ "Set group preferences" = "Встановіть налаштування групи"; @@ -3565,12 +4075,18 @@ /* No comment provided by engineer. */ "Share address with contacts?" = "Поділіться адресою з контактами?"; +/* No comment provided by engineer. */ +"Share from other apps." = "Діліться з інших програм."; + /* No comment provided by engineer. */ "Share link" = "Поділіться посиланням"; /* No comment provided by engineer. */ "Share this 1-time invite link" = "Поділіться цим одноразовим посиланням-запрошенням"; +/* No comment provided by engineer. */ +"Share to SimpleX" = "Поділіться з SimpleX"; + /* No comment provided by engineer. */ "Share with contacts" = "Поділіться з контактами"; @@ -3589,6 +4105,9 @@ /* No comment provided by engineer. */ "Show message status" = "Показати статус повідомлення"; +/* No comment provided by engineer. */ +"Show percentage" = "Показати відсоток"; + /* No comment provided by engineer. */ "Show preview" = "Показати попередній перегляд"; @@ -3598,6 +4117,9 @@ /* No comment provided by engineer. */ "Show:" = "Показати:"; +/* No comment provided by engineer. */ +"SimpleX" = "SimpleX"; + /* No comment provided by engineer. */ "SimpleX address" = "Адреса SimpleX"; @@ -3643,6 +4165,9 @@ /* No comment provided by engineer. */ "Simplified incognito mode" = "Спрощений режим інкогніто"; +/* No comment provided by engineer. */ +"Size" = "Розмір"; + /* No comment provided by engineer. */ "Skip" = "Пропустити"; @@ -3652,9 +4177,21 @@ /* No comment provided by engineer. */ "Small groups (max 20)" = "Невеликі групи (максимум 20 осіб)"; +/* No comment provided by engineer. */ +"SMP server" = "Сервер SMP"; + +/* blur media */ +"Soft" = "М'який"; + +/* No comment provided by engineer. */ +"Some file(s) were not exported:" = "Деякі файли не було експортовано:"; + /* No comment provided by engineer. */ "Some non-fatal errors occurred during import - you may see Chat console for more details." = "Під час імпорту виникли деякі нефатальні помилки – ви можете переглянути консоль чату, щоб дізнатися більше."; +/* No comment provided by engineer. */ +"Some non-fatal errors occurred during import:" = "Під час імпорту виникли деякі несмертельні помилки:"; + /* notification title */ "Somebody" = "Хтось"; @@ -3673,9 +4210,15 @@ /* No comment provided by engineer. */ "Start migration" = "Почати міграцію"; +/* No comment provided by engineer. */ +"Starting from %@." = "Починаючи з %@."; + /* No comment provided by engineer. */ "starting…" = "починаючи…"; +/* No comment provided by engineer. */ +"Statistics" = "Статистика"; + /* No comment provided by engineer. */ "Stop" = "Зупинити"; @@ -3715,9 +4258,21 @@ /* No comment provided by engineer. */ "strike" = "закреслено"; +/* blur media */ +"Strong" = "Сильний"; + /* No comment provided by engineer. */ "Submit" = "Надіслати"; +/* No comment provided by engineer. */ +"Subscribed" = "Підписано"; + +/* No comment provided by engineer. */ +"Subscription errors" = "Помилки підписки"; + +/* No comment provided by engineer. */ +"Subscriptions ignored" = "Підписки ігноруються"; + /* No comment provided by engineer. */ "Support SimpleX Chat" = "Підтримка чату SimpleX"; @@ -3751,6 +4306,9 @@ /* No comment provided by engineer. */ "Tap to scan" = "Натисніть, щоб сканувати"; +/* No comment provided by engineer. */ +"TCP connection" = "TCP-з'єднання"; + /* No comment provided by engineer. */ "TCP connection timeout" = "Тайм-аут TCP-з'єднання"; @@ -3763,6 +4321,9 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; +/* No comment provided by engineer. */ +"Temporary file error" = "Тимчасова помилка файлу"; + /* server test failure */ "Test failed at step %@." = "Тест завершився невдало на кроці %@."; @@ -3823,6 +4384,12 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Повідомлення буде позначено як модероване для всіх учасників."; +/* No comment provided by engineer. */ +"The messages will be deleted for all members." = "Повідомлення будуть видалені для всіх учасників."; + +/* No comment provided by engineer. */ +"The messages will be marked as moderated for all members." = "Повідомлення будуть позначені як модеровані для всіх учасників."; + /* No comment provided by engineer. */ "The next generation of private messaging" = "Наступне покоління приватних повідомлень"; @@ -3844,6 +4411,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Текст, який ви вставили, не є посиланням SimpleX."; +/* No comment provided by engineer. */ +"Themes" = "Теми"; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Ці налаштування стосуються вашого поточного профілю **%@**."; @@ -3886,9 +4456,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Це ваша власна SimpleX-адреса!"; +/* No comment provided by engineer. */ +"This link was used with another mobile device, please create a new link on the desktop." = "Це посилання було використано з іншого мобільного пристрою, будь ласка, створіть нове посилання на робочому столі."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Це налаштування застосовується до повідомлень у вашому поточному профілі чату **%@**."; +/* No comment provided by engineer. */ +"Title" = "Заголовок"; + /* No comment provided by engineer. */ "To ask any questions and to receive updates:" = "Задати будь-які питання та отримувати новини:"; @@ -3925,12 +4501,24 @@ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Щоб перевірити наскрізне шифрування з вашим контактом, порівняйте (або відскануйте) код на ваших пристроях."; +/* No comment provided by engineer. */ +"Toggle chat list:" = "Перемикання списку чату:"; + /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Увімкніть інкогніто при підключенні."; +/* No comment provided by engineer. */ +"Toolbar opacity" = "Непрозорість панелі інструментів"; + +/* No comment provided by engineer. */ +"Total" = "Всього"; + /* No comment provided by engineer. */ "Transport isolation" = "Транспортна ізоляція"; +/* No comment provided by engineer. */ +"Transport sessions" = "Транспортні сесії"; + /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact (error: %@)." = "Спроба з'єднатися з сервером, який використовується для отримання повідомлень від цього контакту (помилка: %@)."; @@ -4024,6 +4612,9 @@ /* authentication reason */ "Unlock app" = "Розблокувати додаток"; +/* No comment provided by engineer. */ +"unmute" = "увімкнути звук"; + /* swipe action */ "Unmute" = "Увімкнути звук"; @@ -4045,6 +4636,9 @@ /* No comment provided by engineer. */ "Update network settings?" = "Оновити налаштування мережі?"; +/* No comment provided by engineer. */ +"Update settings?" = "Оновити налаштування?"; + /* rcv group event chat item */ "updated group profile" = "оновлений профіль групи"; @@ -4057,12 +4651,21 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Оновлення та відкритий чат"; +/* No comment provided by engineer. */ +"Upload errors" = "Помилки завантаження"; + /* No comment provided by engineer. */ "Upload failed" = "Не вдалося завантфжити"; /* server test step */ "Upload file" = "Завантажити файл"; +/* No comment provided by engineer. */ +"Uploaded" = "Завантажено"; + +/* No comment provided by engineer. */ +"Uploaded files" = "Завантажені файли"; + /* No comment provided by engineer. */ "Uploading archive" = "Завантаження архіву"; @@ -4105,9 +4708,15 @@ /* No comment provided by engineer. */ "Use the app while in the call." = "Використовуйте додаток під час розмови."; +/* No comment provided by engineer. */ +"Use the app with one hand." = "Використовуйте додаток однією рукою."; + /* No comment provided by engineer. */ "User profile" = "Профіль користувача"; +/* No comment provided by engineer. */ +"User selection" = "Вибір користувача"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Використання серверів SimpleX Chat."; @@ -4156,6 +4765,9 @@ /* No comment provided by engineer. */ "Via secure quantum resistant protocol." = "Через безпечний квантово-стійкий протокол."; +/* No comment provided by engineer. */ +"video" = "відео"; + /* No comment provided by engineer. */ "Video call" = "Відеодзвінок"; @@ -4213,6 +4825,12 @@ /* No comment provided by engineer. */ "Waiting for video" = "Чекаємо на відео"; +/* No comment provided by engineer. */ +"Wallpaper accent" = "Акцент на шпалерах"; + +/* No comment provided by engineer. */ +"Wallpaper background" = "Фон шпалер"; + /* No comment provided by engineer. */ "wants to connect to you!" = "хоче зв'язатися з вами!"; @@ -4285,9 +4903,15 @@ /* snd error text */ "Wrong key or unknown connection - most likely this connection is deleted." = "Неправильний ключ або невідоме з'єднання - швидше за все, це з'єднання видалено."; +/* file error text */ +"Wrong key or unknown file chunk address - most likely file is deleted." = "Неправильний ключ або невідома адреса фрагмента файлу - найімовірніше, файл видалено."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Неправильний пароль!"; +/* No comment provided by engineer. */ +"XFTP server" = "XFTP-сервер"; + /* pref value */ "yes" = "так"; @@ -4342,6 +4966,9 @@ /* No comment provided by engineer. */ "You are invited to group" = "Запрошуємо вас до групи"; +/* No comment provided by engineer. */ +"You are not connected to these servers. Private routing is used to deliver messages to them." = "Не підключені до цих серверів. Для доставлення повідомлень до них використовується приватна маршрутизація."; + /* No comment provided by engineer. */ "you are observer" = "ви спостерігач"; @@ -4351,6 +4978,9 @@ /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Ви можете приймати дзвінки з екрана блокування без автентифікації пристрою та програми."; +/* No comment provided by engineer. */ +"You can change it in Appearance settings." = "Ви можете змінити його в налаштуваннях зовнішнього вигляду."; + /* No comment provided by engineer. */ "You can create it later" = "Ви можете створити його пізніше"; @@ -4372,6 +5002,9 @@ /* notification body */ "You can now chat with %@" = "Тепер ви можете надсилати повідомлення на адресу %@"; +/* No comment provided by engineer. */ +"You can send messages to %@ from Archived contacts." = "Ви можете надсилати повідомлення на %@ з архівних контактів."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Ви можете налаштувати попередній перегляд сповіщень на екрані блокування за допомогою налаштувань."; @@ -4387,6 +5020,9 @@ /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Запустити чат можна через Налаштування програми / База даних або перезапустивши програму"; +/* No comment provided by engineer. */ +"You can still view conversation with %@ in the list of chats." = "Ви все ще можете переглянути розмову з %@ у списку чатів."; + /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "Увімкнути SimpleX Lock можна в Налаштуваннях."; @@ -4438,9 +5074,18 @@ /* snd group event chat item */ "you left" = "ти пішов"; +/* No comment provided by engineer. */ +"You may migrate the exported database." = "Ви можете мігрувати експортовану базу даних."; + +/* No comment provided by engineer. */ +"You may save the exported archive." = "Ви можете зберегти експортований архів."; + /* No comment provided by engineer. */ "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Ви повинні використовувати найновішу версію бази даних чату ТІЛЬКИ на одному пристрої, інакше ви можете перестати отримувати повідомлення від деяких контактів."; +/* No comment provided by engineer. */ +"You need to allow your contact to call to be able to call them." = "Щоб мати змогу зателефонувати контакту, вам потрібно дозволити йому зателефонувати."; + /* No comment provided by engineer. */ "You need to allow your contact to send voice messages to be able to send them." = "Щоб мати змогу надсилати голосові повідомлення, вам потрібно дозволити контакту надсилати їх."; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index b027be6e82..7fb812ade9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -1950,7 +1950,7 @@ متصل الخوادم المتصلة جارِ الاتصال - الاتصالات النسطة + الاتصالات النشطة ملف التعريف الحالي أخطاء الحذف إحصائيات مفصلة diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index d5bceef30b..bc27b0a29f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -1526,9 +1526,9 @@ El historial no se envía a miembros nuevos. Reintentar Cámara no disponible - Enviar hasta 100 últimos mensajes a los miembros nuevos. + Se envían hasta 100 mensajes más recientes a los miembros nuevos. Añadir contacto: crea un enlace de invitación nuevo o usa un enlace recibido.]]> - No enviar historial a miembros nuevos. + No se envía el historial a los miembros nuevos. O muestra este código QR Hasta 100 últimos mensajes son enviados a los miembros nuevos. El código QR escaneado no es un enlace SimpleX. @@ -1736,9 +1736,9 @@ Ethernet por cable administradores Activado para - No permitir el envío de enlaces SimpleX + No se permite enviar enlaces SimpleX todos los miembros - Permitir enviar enlaces SimpleX. + Se permite enviar enlaces SimpleX. guardado guardado desde %s Guardado @@ -2065,7 +2065,9 @@ Invitar Nueva experiencia de chat 🎉 Nuevas opciones multimedia - Puede cambiarlo desde el menú Apariencia. + Puedes cambiar la posición de la barra desde el menú Apariencia. Descarga nuevas versiones desde GitHub. Nuevo mensaje + Enlace no válido + Por favor, comprueba que el enlace SimpleX es correcto. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 39dc6cfb82..db06d74378 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -44,7 +44,7 @@ az alkalmazásban minden csevegési profiljához .]]> Mindkét fél küldhet eltűnő üzeneteket. Az Android Keystore-t a jelmondat biztonságos tárolására használják - lehetővé teszi az értesítési szolgáltatás működését. - Téves üzenet hash + Hibás az üzenet ellenőrzőösszege Háttér Tudnivaló: az üzenet- és fájl átjátszók SOCKS proxy által vannak kapcsolatban. A hívások és URL hivatkozás előnézetek közvetlen kapcsolatot használnak.]]> Alkalmazásadatok biztonsági mentése @@ -124,7 +124,7 @@ Élő csevegési üzenet visszavonása Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) Hang- és videóhívások - téves üzenet hash + hibás az üzenet ellenőrzőösszege Mindig fut Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat megváltoztatás után - lehetővé téve az értesítések fogadását. Minden alkalmazásadat törölve. @@ -242,7 +242,7 @@ kapcsolódás (bemutatkozó meghívó) SimpleX cím létrehozása törölt ismerős - Csoporttag üzenet törlése? + Csoporttag üzenetének törlése? A csevegés fut Egyszer használatos meghívó hivatkozás létrehozása Törlés @@ -487,7 +487,7 @@ \nCsatlakozzon hozzám SimpleX Chat-en keresztül: %s A megjelenített név nem tartalmazhat szóközöket. Csoport - Üdvözlő üzenetet megadása… (opcionális) + Üdvözlő üzenet megadása… (opcionális) Hiba a csevegési adatbázis exportálásakor Hiba a fájl mentésekor Helyi fájlok titkosítása @@ -544,7 +544,7 @@ Sikertelen titkosítás-újraegyeztetés. Hiba a felhasználói profil törlésekor Csoporttag általi javítás nem támogatott - Üdvözlő üzenetet megadása… + Üdvözlő üzenet megadása… Titkosított adatbázis Jelszó megadása a keresőben A fájl akkor érkezik meg, amikor a küldője befejezte annak feltöltését. @@ -1269,7 +1269,7 @@ SimpleX zár Beállítások Csevegési adatbázis - %1$s eltávolítva + eltávolította őt: %1$s Sikertelen kiszolgáló teszt! Kapcsolat ellenőrzése Tudjon meg többet @@ -1510,7 +1510,7 @@ Használati útmutatóban olvasható.]]> A jelmondat a beállításokban egyszerű szövegként van tárolva. Konzol megjelenítése új ablakban - Az előző üzenet hash-e más. + Az előző üzenet ellenőrzőösszege különbözik. Ezek a beállítások a jelenlegi profiljára vonatkoznak Várjon, amíg a fájl betöltődik a csatolt mobilról GitHub tárolónkban.]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 59d1e68afc..b6d6770250 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -100,4 +100,16 @@ Ponsel yang terhubung Hubungkan secara otomatis menyambungkan… + Menghubungkan + Tautan tidak valid + Periksa apakah tautan SimpleX sudah benar. + Anda terhubung ke server yang digunakan untuk menerima pesan dari kontak ini. + Mencoba menyambung ke server yang digunakan untuk menerima pesan dari kontak ini (error: %1$s). + Migrasi basis data sedang berlangsung, +\nmemerlukan waktu beberapa menit. + menghubungkan + Lokasi file tidak valid + Tampilan macet + Anda membagikan lokasi file yang tidak valid. Laporkan masalah ini ke pengembang aplikasi. + error \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index abf9345a8c..34ee1721f0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2051,7 +2051,7 @@ Barra degli strumenti di chat accessibile Archivia contatti per chattare più tardi. Usa l\'app con una mano sola. - Stato di connessione e server. + Stato della connessione e dei server. Protegge il tuo indirizzo IP e le connessioni. Salva e riconnetti Ripristina tutti i suggerimenti diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index ff06893e4a..97b6586844 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -2013,7 +2013,7 @@ verbinden Contact verwijderd! Het contact wordt verwijderd. Dit kan niet ongedaan worden gemaakt! - Blijf in gesprek + Behoud het gesprek bericht Alleen conversatie verwijderen open @@ -2065,4 +2065,6 @@ Nieuwe media-opties Uitnodigen Nieuw bericht + Ongeldige link + Controleer of de SimpleX-link correct is. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index dc97062155..d0b0ca0d1c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -181,7 +181,7 @@ jesteś obserwatorem Nie możesz wysyłać wiadomości! Połączony - Obecnie maksymalny obsługiwany rozmiar pliku to %1$s . + Obecnie maksymalny obsługiwany rozmiar pliku to %1$s. Usuń kontakt Usunąć kontakt\? Rozłączony @@ -344,7 +344,7 @@ Serwery ICE (po jednym na linię) Jeśli potwierdzisz, serwery wiadomości będą mogły zobaczyć Twój adres IP, a Twój dostawca - z jakimi serwerami się łączysz. Sieć i serwery - Ustawienia sieci + Zaawansowane ustawienia Nie Hosty onion będą wymagane do połączenia. \nUwaga: nie będziesz mógł połączyć się z serwerami bez adresu .onion. @@ -408,11 +408,11 @@ Zdecentralizowane zakończona Jak korzystać z markdown - Odporność na spam i nadużycia + Odporność na spam kursywa nieodebrane połączenie - Otwarto źródłowy protokół i kod - każdy może uruchomić serwery. - Ludzie mogą się z Tobą połączyć tylko poprzez linki, które udostępniasz. + Każdy może hostować serwery. + Ty decydujesz, kto może się połączyć. Redefinicja prywatności otrzymano odpowiedź… otrzymano potwierdzenie… @@ -420,8 +420,9 @@ sekret uruchamianie… strajk - Pierwsza platforma bez żadnych identyfikatorów użytkowników – z założenia prywatna. - Następna generacja prywatnych wiadomości + Brak identyfikatorów użytkownika. + Następna generacja +\nprywatnych wiadomości oczekiwanie na odpowiedź… oczekiwanie na potwierdzenie… Możesz używać markdown do formatowania wiadomości: @@ -440,7 +441,7 @@ Użyj czatu Gdy aplikacja jest uruchomiona odbierać wiadomości, Twoje kontakty - serwery, których używasz do wysyłania im wiadomości.]]> - Zużywa więcej baterii! Usługa zawsze działa w tle - powiadomienia są wyświetlane, gdy tylko wiadomości są dostępne.]]> + Zużywa więcej baterii! Aplikacja zawsze działa w tle - powiadomienia są wyświetlane natychmiastowo.]]> Przychodzące połączenie audio Przychodzące połączenie wideo Wklej link, który otrzymałeś @@ -913,7 +914,7 @@ Optymalizacja baterii jest aktywna, wyłącza usługi w tle i okresowe żądania nowych wiadomości. Możesz je ponownie włączyć za pośrednictwem ustawień. Można je wyłączyć poprzez ustawienia - powiadomienia nadal będą pokazywane podczas działania aplikacji.]]> Najlepsze dla baterii. Będziesz otrzymywać powiadomienia tylko wtedy, gdy aplikacja jest uruchomiona (NIE w tle).]]> - Dobry dla baterii. Usługa w tle sprawdza wiadomości co 10 minut. Możesz przegapić połączenia lub pilne wiadomości.]]> + Dobry dla baterii. Aplikacja sprawdza wiadomości co 10 minut. Możesz przegapić połączenia lub pilne wiadomości.]]> Zarówno Ty, jak i Twój kontakt możecie wysyłać wiadomości głosowe. Według profilu czatu (domyślnie) lub połączenia (BETA). Nie można zaprosić kontaktów! @@ -1239,7 +1240,7 @@ \n- niestandardowy czas zniknięcia. \n- historia edycji. brak tekstu - Podczas importu wystąpiły niekrytyczne błędy - więcej szczegółów można znaleźć w konsoli czatu. + Podczas importu wystąpiły niekrytyczne błędy: Restart APLIKACJA Powiadomienia przestaną działać do momentu ponownego uruchomienia aplikacji. @@ -1789,7 +1790,7 @@ TRASOWANIE PRYWATNYCH WIADOMOŚCI NIE wysyłaj wiadomości bezpośrednio, nawet jeśli serwer docelowy nie obsługuje prywatnego trasowania. Aby chronić Twój adres IP, prywatne trasowanie używa Twoich serwerów SMP, aby dostarczyć wiadomości. - Nieznane przekaźniki + Nieznane serwery Używaj prywatnego trasowania z nieznanymi serwerami. Rezerwowe trasowania wiadomości Prywatne trasowanie @@ -1990,4 +1991,82 @@ Wyślij błędy Rozmiar Błędy przesłania + Zwiększ rozmiar czcionki. + wiadomość + Wiadomość + otwórz + szukaj + Zaznaczono %d + wideo + Brak filtrowanych kontaktów + Zaproś + Pobieraj nowe wersje z GitHub. + Nowe opcje mediów + Twoje kontakty + Nowa wiadomość + Aktualizuj aplikację automatycznie + Rozmycie dla lepszej prywatności. + Możesz to zmienić w ustawieniach wyglądu. + Zaproś + Nie można wysłać wiadomości do członka grupy + Wyślij wiadomość aby włączyć połączenia. + Nowe możliwości czatu 🎉 + Korzystaj z aplikacji jedną ręką. + Odtwórz z listy czatów. + Zarchiwizowane kontakty + zadzwoń + połącz + Stan połączenia i serwerów. + Utwórz + Nieprawidłowy link + Zapisz i połącz ponownie + Ustawienia + Połączenie TCP + Nic nie jest zaznaczone + Sprawdź czy link SimpleX jest poprawny. + Przełącz listę czatów: + Archiwizuj kontakty aby porozmawiać później. + Chroni Twój adres IP i połączenia. + Osiągalny pasek narzędzi czatu + Silne + Rozmycie mediów + Średni + Wyłącz + Łagodny + Zachowaj rozmowę + Usuń tylko rozmowę + Możesz wysyłać wiadomości do %1$s ze zarchiwizowanych kontaktów. + Nadal możesz przeglądać rozmowę z %1$s na liście czatów. + Potwierdzić usunięcie kontaktu? + Kontakt usunięty! + Kontakt zostanie usunięty – nie można tego cofnąć! + Rozmowa usunięta! + Usuń bez powiadomienia + Wklej link + Zezwolić na połączenia? + Nie można zadzwonić do kontaktu + Łączenie z kontaktem, poczekaj lub sprawdź później! + Kontakt jest usunięty. + Aby móc dzwonić, musisz zezwolić kontaktowi na połączenia. + Połączenia zakazane! + Nie można zadzwonić do członka grupy + Poproś kontakt o włącznie połączeń. + Usunąć %d wiadomości członków? + Wiadomości zostaną oznaczone do usunięcia. Odbiorca(y) będą mogli ujawnić te wiadomości. + Zaznacz + Wiadomości zostaną usunięte dla wszystkich członków. + Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków. + Osiągalny pasek narzędzi czatu + Wyeksportowano bazę danych czatu + Kontynuuj + Serwery mediów i plików + Serwery wiadomości + Proxy SOCKS + Niektóre plik(i) nie zostały wyeksportowane + Możesz zmigrować wyeksportowaną bazy danych. + Możesz zapisać wyeksportowane archiwum. + Zresetuj wszystkie wskazówki + Szybciej łącz się ze znajomymi. + Kontroluj swoją sieć + Usuń do 20 wiadomości na raz. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index 7747722d5f..cd34f2efff 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -636,4 +636,92 @@ Cho phép xóa tin nhắn tự động? Chỉnh sửa Thư điện tử + Lỗi: %1$s + Lỗi + Tái đàm phán mã hóa thất bại. + mã hóa ok + cho phép tái đàm phán mã hóa với %s + cần tái đàm phán mã hóa cho %s + Bật chỉ báo đã nhận? + Mã hóa các tệp và phương tiện được lưu trữ + Lỗi + Kết thúc cuộc gọi + Bật trong cuộc trò chuyện trực tiếp (BETA)! + Bật mã truy cập tự hủy + Mã hóa cơ sở dữ liệu? + Cơ sở dữ liệu được mã hóa + Lỗi tái đàm phán mã hóa + Nhập mật khẩu + Bật (giữ thông tin ghi đè) + Mã hóa + mã hóa đã đồng nhất + cho phép tái đàm phán mã hóa + cần tái đàm phán mã hóa + Bật TCP keep-alive + Nhập mật khẩu trong tìm kiếm + Nhập tên của thiết bị này… + Bật SimpleX Lock + Nhập lời chào… + Nhập mã truy cập + Lỗi + Nhập lời chào... (không bắt buộc) + đã kết thúc + mã hóa đã đồng nhất cho %s + mã hóa ok cho %s + lỗi + Nhập máy chủ thủ công + Bật (giữ thông tin ghi đè nhóm) + Nhập đúng mật khẩu. + Nhập mật khẩu… + Mã hóa các tệp cục bộ + Bật tự hủy + Bật chỉ báo đã nhận cho các nhóm? + Nhập tên nhóm: + Nhập tên của bạn: + Bật khóa + Lỗi + Lỗi tạo hồ sơ! + Lỗi thay đổi cài đặt + Lỗi chặn thành viên cho tất cả + Lỗi thêm thành viên + Lỗi tạo tin nhắn + Lỗi tạo địa chỉ + Lỗi chấp nhận yêu cầu liên hệ + Lỗi thay đổi địa chỉ + Lỗi xóa cơ sở dữ liệu SimpleX Chat + Lỗi tạo liên hệ thành viên + Lỗi hủy bỏ thay đổi địa chỉ + Lỗi tạo liên kết nhóm + Lỗi thay đổi quyền hạn + Lỗi kết nối đến máy chủ chuyển tiếp %1$s. Vui lòng thử lại sau. + Lỗi xóa liên hệ + Lỗi xóa nhóm + Lỗi xóa liên kết nhóm + Lỗi xóa yêu cầu liên hệ + Lỗi xóa cơ sở dữ liệu + Lỗi xóa kết nối liên hệ đang chờ xử lý + Lỗi xuất cơ sở dữ liệu SimpleX Chat + Lỗi mã hóa cơ sở dữ liệu + Lỗi bật chỉ báo đã nhận! + Lỗi xóa ghi chú riêng tư + Lỗi xóa hồ sơ người dùng + Lỗi nhập cơ sở dữ liệu SimpleX Chat + Lỗi xuất cơ sở dữ liệu SimpleX Chat + Lỗi tải xuống kho lưu trữ + Lỗi khởi tạo WebView. Cập nhật hệ điều hành của bạn lên phiên bản mới. Vui lòng liên hệ với nhà phát triển. +\nLỗi: %s + Lỗi xóa thành viên + Lỗi: %s + Lỗi mở trình duyệt + Lỗi tham gia nhóm + Lỗi tải thông tin chi tiết + Lỗi nhận tệp + Lỗi lưu hồ sơ nhóm + Lỗi lưu tệp + Lỗi tải máy chủ SMP + Lỗi tải máy chủ XFTP + Lỗi kết nối lại máy chủ + Lỗi kết nối lại máy chủ + Lỗi + Lỗi khôi phục thống kê \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 5935ea303c..48efa52563 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -19,10 +19,10 @@ 接受 認證無效 允許 - 顯示名稱: + 個人資料名稱: 全名: - 使用更多電量!通知服務長期在背景中運行 – 有效的訊息就會即時顯示在通知內。]]> - 對電量友善。通知服務於每十分鐘檢查一次訊息。你可能會錯過通話和迫切的訊息。]]> + 使用更多電量!程式始終在背景中運行 – 通知會立即顯示。]]> + 對電量友善。程式每10分鐘檢查一次訊息。你可能會錯過電話或警急訊息。]]> 回應通話請求 清除 允許向群組內的成員傳送私訊。 @@ -69,7 +69,7 @@ 你目前的個人檔案 顯示的名稱中不能有空白。 儲存設定? - 顯示名稱 + 輸入你的名稱: 通話出錯 正在撥打… 通話中 @@ -130,7 +130,7 @@ 建立私密群組 退出群組 已連接 - 群組顯示名稱: + 輸入群組名稱: 群組全名: 對話設定 關閉 @@ -153,7 +153,7 @@ 群組設定 聯絡人設定 分享媒體… - 你和你的聯絡人都可以不可逆地刪除已經傳送的訊息。 + 你和你的聯絡人都可以不可逆地刪除已傳送的訊息。 已連接 簡介 完整連結 @@ -179,13 +179,13 @@ 檢查輸入的伺服器地址,然後再試一次。 終端機對話 於 Github 給個星星 - 匿名聊天模式會保護你的真實個人檔案名稱和頭像 — 當有新聯絡人的時候會自動建立一個隨機性的個人檔案。 + 隱身模式透過為每個聯絡人使用新的隨機設定檔來保護您的隱私。 這樣是允許每一個對話中擁有不同的顯示名稱,並且沒有任何的個人資料可用於分享或有機會外洩。 只有你的聯絡人允許的情況下,才允許自動銷毀訊息。 允許你的聯絡人傳送自動銷毀的訊息。 - 只有你的聯絡人允許的情況下,才允許不可逆地將訊息刪除。 - 允許你的聯絡人可以不可逆地刪除已發送的訊息。 - 允許將不可撤銷的訊息刪除。 + 只有你的聯絡人允許的情況下,才允許不可逆地將訊息刪除。(24小時) + 允許你的聯絡人不可逆地刪除已發送的訊息。(24小時) + 允許不可逆地將已傳送的訊息刪除。(24小時) 允許傳送語音訊息。 多久後刪除 群組內所有成員會保持連接。 @@ -231,9 +231,9 @@ 感謝用戶 - 使用 Weblate 的翻譯貢獻! SimpleX k - 透過連結連接聯絡人? - 透過邀請連結連接? - 透過邀請連結連接群組? + 透過聯絡人地址連接? + 透過一次性連結連接? + 加入群組? 你的個人檔案將傳送給你接收此連結的聯絡人。 你將加入此連結內的群組並且連接到此群組成為群組內的成員。 連接 @@ -509,7 +509,7 @@ 通話完結 連接 網路 & 伺服器 - 網路設定 + 進階設定 使用 SOCKS 代理伺服器 儲存並通知群組內的聯絡人 退出並且不儲存記錄 @@ -547,7 +547,8 @@ 有一些伺服器測試失敗: 掃描伺服器的二維碼 你的伺服器 - 連接時將需要使用 Onion 主機。 + 連接時需要使用 Onion 主機。 +\n請注意:如果沒有 .onion 地址,您將無法連接到伺服器。 對話檔案 透過群組連結 透過群組連結使用匿名聊天模式 @@ -621,7 +622,7 @@ 沒有聯絡人可以選擇 群組連結 刪除連結 - 群組是完全去中心化的 - 只有群組內的成員能看到。 + 完全去中心化 - 只有成員能看到。 禁止傳送自動銷毀的訊息。 %d 個小時 更新內容 @@ -643,7 +644,7 @@ 儲存群組檔案 語音訊息於這個聊天室是禁用的。 允許你的聯絡人可以完全刪除訊息。 - 第一個沒有任何用戶識別符的通訊平台 – 以私隱為設計。 + 沒有用戶識別符。 新一代的私密訊息平台 去中心化的 人們只能在你分享了連結後,才能和你連接。 @@ -652,10 +653,10 @@ 這是如何運作 你可以之後透過設定修改。 私下連接 - 開放源碼協議和程式碼 – 任何人也可以運行伺服器。 + 任何人都可以託管伺服器。 無視 語音通話來電 - 貼上你接收到的連結 + 貼上你收到的連結 端對端加密 沒有端對端加密 關閉喇叭 @@ -757,7 +758,7 @@ 不可逆地刪除訊息於這個聊天室內是禁用的。 只有你可以傳送語音訊息。 私訊群組內的成員於這個群組內是禁用的。 - 群組內的成員可以不可逆地刪除訊息。 + 群組內的成員可以不可逆地刪除訊息。(24小時) 語音訊息 改善伺服器配置 當你切換至最近應用程式版面時,無法預覽程式畫面。 @@ -828,8 +829,8 @@ 已啟用 已為你啟用 已為聯絡人啟用 - 只有你能不可逆地刪除訊息(你的聯絡人可以將它標記為刪除)。 - 只有你的聊絡人可以不可逆的刪除訊息(你可以將它標記為刪除)。 + 只有你能不可逆地刪除訊息(你的聯絡人可以將它標記為刪除)。(24小時) + 只有你的聊絡人可以不可逆的刪除訊息(你可以將它標記為刪除)。(24小時) 只有你的聯絡人可以傳送語音訊息。 禁止私訊群組內的成員。 不可逆地刪除訊息於這個群組內是禁用的。 @@ -899,7 +900,7 @@ 添加更多身份選項 聯絡人頭像 個人檔案頭像占位符 - 不受垃圾郵件和濫用行為影響 + 不受垃圾和騷擾訊息影響 %1$s 希望透過以下方式聯絡你 開啟視訊 翻轉相機 @@ -908,7 +909,7 @@ 你的通話 經由分程傳遞連接 在上鎖畫面顯示來電通知: - %1$d 你錯過了多個訊息 + %1$d 條訊息已跳過 錯誤的訊息雜湊值 你錯過了多個訊息 @@ -1103,7 +1104,7 @@ %1$d 訊息解密失敗。 使用SOCKS 代理伺服器 你的 XFTP 伺服器 - %1$d 錯過了多個訊息。 + %1$d 條訊息已跳過。 影片和檔案和最大上限為1gb 影片 呈交 @@ -1111,7 +1112,7 @@ 查看更多 SimpleX 聯絡地址 一次性連結 - 主題顏色 + 介面顏色 建立 SimpleX 的聯絡地址 更新了的個人檔案將傳送給你的聯絡人。 與你的聯絡人分享聯絡地址? @@ -1140,7 +1141,7 @@ 已傳送訊息 標題 關於 SimpleX 的聯絡地址 - 外加的顏色 + 額外的強調色 外加的輔助 聯絡地址 後台 @@ -1241,4 +1242,563 @@ \n- 編輯紀錄。 搜尋 已關閉 + 確認來自未知伺服器的檔案。 + 超出額度 - 收件人未收到先前傳送的訊息 + 應用程式資料轉移 + 應用 + 請在轉移之前確認你還記得數據庫密碼 + 被管理員封鎖 + 進階設定 + 封鎖群組成員 + 活躍連接 + 中止 + 和其他 %d 事件 + 封鎖成員? + 6種全新的介面語言 + 藍芽 + %2$s 審核了 %1$d 條訊息 + 已封鎖 + 將停止地址更改。將使用舊聯絡地址。 + 測試 + 檢查更新 + 相機和麥克風 + 封鎖 + 應用程式主題 + 管理員 + 模糊以增強隱私 + 所有成員 + 管理員可以為所有人封鎖一名成員 + 無法傳送訊息 + 為所有成員封鎖此成員? + + 中止更改地址? + 無法傳送訊息給群組成員 + 所有顏色模式 + 應用到 + 應用程式密碼 + 應用程式 + 聊天顏色 + 聊天已停止。如果你已經在另一台設備使用過此資料庫,你應該在啟動聊天前將數據庫傳輸回來。 + 即將推出! + 軟體更新以下載 + 儲存聯絡人以便稍後聊天 + 相機 + 選擇一個檔案 + 存檔並上傳 + 你的所有聯絡人、對話和檔案將被安全加密並切塊上傳到你設定的 XFTP 中繼 + 正在儲存資料庫 + 取消遷移 + 與 %s 協調加密中… + 允許 + 語音通話 + 相機不可用 + 所有訊息都將被刪除 - 這無法復原 + 請注意:訊息和檔案中繼通過 SOCKS 代理連接。通話和傳送連預覽使用直接連接。]]> + 封鎖全部 + 改進群組功能 + 行動網路 + 封鎖成員 + 警告:此存檔將被刪除。]]> + 清除私密筆記? + 添加聯絡人 + 總是 + 協調加密中… + 允許傳送檔案和媒體 + 允許傳送 SimpleX 連結 + 其他 + 已連接! + 中止更改地址 + 將分享新的隨機個人檔案 + 所有來自 %s 的新訊息都將被隱藏! + 已封鎖 %s + 作者 + 已封鎖 + 被管理員封鎖 + 額外的強調色2 + 阿拉伯語、保加利亞語、芬蘭語、希伯來語、泰國語和烏克蘭語——感謝使用者們與Weblate + 已加入群組! + 確認網路設定 + 嘗試 + 已確認 + 確認錯誤 + 完成 + 區塊已刪除 + 區塊已上傳 + 區塊已下載 + 添加聯絡人: 來創建新的邀請連結,或通過你收到的連結進行連接。]]> + 建立群組: 建立新的群組。]]> + 錯誤的桌面地址 + 已轉移聊天 + 從另一部設備轉移 並掃描QR code。]]> + 請注意: 作為安全保護措施,在兩部設備上使用同一數據庫會破壞解密來自你聯絡人的訊息。]]> + 確定刪除聯絡人? + 檢查更新 + 無法與聯絡人通話 + 通話被禁止! + 無法與群組成員通話 + 應用程式將為新的本機檔案(影片除外)加密。 + 檢查你的網路連接並重試 + 所有配置文件 + 已設定的 SMP 伺服器 + 聊天主題 + 通話 + 允許降級 + 始終使用私密路由。 + 以導出聊天資料庫 + 已設定的 XFTP 伺服器 + 色彩模式 + 已儲存的聯絡人 + 模糊媒體 + 允許通話? + 建立於:%s + Webview 初始化失敗。更新你的系統到新版本。請聯繫開發者。 +\n錯誤:%s + 已刪除聯絡人 + %d 個群事件 + 訊息太大 + 訊息傳送警告 + 錯誤:%1$s + 開發者選項 + 與 %s 的加密需要重協商 + %s 不活躍]]> + 最喜歡 + 訊息成功送達! + 檔案和媒體 + 連線停止 + %s的連接不穩定]]> + 聯絡人 + 適合 + 群組成員可以傳送檔案和媒體。 + 連結行動裝置 + 此群組禁止檔案和媒體 + 結束通話 + 刪除 %d 條訊息嗎? + 訊息草稿 + 允許重新協商加密 + 建立於 + 為所有人封鎖時出錯 + 桌面應用版本 %s 與此應用不相容 + 未找到檔案 - 檔案可能被刪除或被取消了 + 檔案伺服器錯誤:%1$s + 展開 + 啟用(保留組覆蓋) + 淺色 + 淺色模式 + 群組成員可傳送 SimpleX 連結。 + 深色 + 詳情 + 訊息接收 + 無效連結 + %d 條訊息被標記為刪除 + %d 條訊息已攔截 + 轉發伺服器地址不相容網路設定:%1$s。 + 轉發伺服器地址不相容網路設定:%1$s。 + %1$s 的目標伺服器地址不相容轉送伺服器 %2$s 的設定 + %1$s 的目地伺服器版本不相容於轉送伺服器 %2$s. + 關閉通知 + 網路問題 - 多次嘗試傳送訊息後,訊息已過期。 + 轉發伺服器:%1$s +\n目標伺服器錯誤:%2$s + 如果成員變得活躍,可能會在之後傳送訊息。 + 刪除了聯絡人! + 聯絡人將被刪除 - 無法復原此操作 + + 已停用 + 安裝成功 + 建立 + 打開瀏覽器出錯 + 啟用(保留覆蓋) + 禁用(保留組覆蓋) + 為群組啟用回執? + 網路連接 + 繼續 + 檔案狀態 + 深色模式顏色 + 字體大小 + 啟用於 + 立陶宛語使用者介面 + 新的聊天主題 + 連線和伺服器狀態 + 控制你的網路 + 從GitHub下載最新版本。 + 啟用 + 新的聊天體驗 🎉 + 新的媒體選項 + 以連接的行動裝置 + 連接桌面 + 連接到桌面 + 連線終止 + 連接到桌面 + 上傳存檔出錯 + 文件被刪除或鏈接無效 + 導入失敗 + 頭戴式耳機 + 耳機 + 對所有聯絡人關閉 + 深色模式 + 啟用已讀回條時出錯! + (此裝置 v%s)]]> + 不相容的版本 + PC版已斷線 + 轉移裝置 + 通話鈴聲 + 轉發並保存訊息 + 訊息來源保持私密 + 沒有已連接的行動裝置 + 禁止檔案和媒體! + 檔案載入中 + 檔案錯誤 + 備用訊息路由 + 如果你或你的目標伺服器不支持私密路由,將不直接傳送訊息。 + 建立個人資料 + 從另一台裝置轉移 + 成員姓名從 %1$s 改為了 %2$s + 同意加密 + 訊息狀態 + 連接請求將傳送給該組成員。 + - 更穩定的消息傳送。 +\n- 更好的群組。 +\n- 還有更多! + 匈牙利語和土耳其語用戶界面 + 轉移完成 + 從此裝置刪除數據庫 + 傳送 + 下載 + 轉發 + 已轉發 + 轉發自 + 不允許檔案和媒體 + 轉發訊息… + 建立連結中… + 保留 + 聯絡人姓名從 %1$s 改為了 %2$s + 新訊息 + 邀請 + 停用(保留覆蓋) + 訊息狀態:%s + 訊息隊列資訊 + 收到的訊息 + 加密本機檔案 + 檔案 + 對所有群組關閉 + 填充 + 讓你的聊天看上去不相同! + 增大字體大小。 + 輸入密碼短語 + 傳送的訊息 + PC版處理中 + 隱身模式連接 + 已刪除對話! + 目標伺服器錯誤:%1$s + 聊天載入中… + 已轉發的訊息 + 管理員封鎖了 %d 條訊息 + 連結轉發伺服器 %1$s 出錯。請稍候嘗試。 + 刪除資料庫出錯 + 轉發伺服器:%1$s +\n錯誤:%2$s + 轉發伺服器 %1$s 連結目標伺服器 %2$s 失敗。請稍後嘗試。 + 導入存檔中 + 改進訊息傳送 + 安裝更新 + 它保護你的 IP 位址和連線。 + 加入群組對話 + %s 的版本。請檢察兩台裝置安裝的是否版本相同]]> + 更可靠的網路連接 + 發現和加入群組 + 裝置 + 新行動裝置 + 保存設定出錯 + 導出的檔案不存在 + 導出資料庫時出錯 + 確認上傳 + 正在建立存檔連結 + 加密OK + 將顯示來自 %s 的訊息! + 送達回執! + 已轉發 + 端對端加密的保護,並具有完全的前向加密、不可否認性和入侵恢復。]]> + 加密協商錯誤 + 抗量子端對端加密保護。]]> + 加密重協商失敗 + 將更新資料庫密碼並儲存在設定中。 + 使用隨機身分建立群組 + 加入速度更快、訊息更可靠。 + 匿名群組 + 連接行動裝置 + 回復 + 和 %1$s 連接? + 在桌面應用裡建立新的帳號。💻 + 輸入此裝置名稱… + 已連結到行動裝置 + 可通過局域網發現 + (新)]]> + 嚴重錯誤 + 內部錯誤 + 驗證密碼短語出錯: + 顯示名稱無效! + 無效的檔案路徑 + 過濾未讀和收藏的聊天記錄。 + 斷開連結 + 斷開桌面連結? + 中止地址更改時出錯 + 顯示通知出錯,請聯繫開發者。 + 刪除並通知聯絡人 + 訊息路由模式 + 不使用私密路由。 + 要進行通話請授予一項或多項權限 + 在 Android 系統設定中找到此權限並手動授予權限。 + 在系統設定中授予 + 傳送邀請出錯 + 建立聯絡人時出錯 + 移除一條訊息 + 保持連接 + - 連接到目錄服務(BETA)! +\n- 發送回執(最多20名成員)。 +\n- 更快,更穩定。 + 連接行動端和桌面端應用程式! 🔗 + 改進訊息傳送 + 已關閉送達回執! + %s 斷開連接]]> + %s 未找到]]> + 存檔下載中 + 轉移中 + 下載失敗 + 為所有組啟用 + 需要重協商加密 + 關閉 + 修復連結 + 修復聯絡人不支援的問題 + 修復 + 修復連結? + 正在進行資料庫轉移。 +\n可能需要幾分鐘時間。 + 建立訊息出錯 + 刪除私密筆記錯誤 + 啟用相機訪問 + 新聊天 + 為所有人啟用 + 同步連接時出錯 + 顯示內容出錯 + 顯示訊息出錯 + 錯誤 + 無後台通話 + 建立聊天資料 + 已同意 %s 的加密 + 允許重新協商與 %s 的加密 + 與 %s 的加密OK + 桌面設備 + 連接桌面選項 + 連接桌面 + 直接連線中 + 邀請 + 建立群組 + 修復群組成員不支援的問題 + 更快地連接到你的好友 + 最多同時刪除20條訊息 + PC版非活躍 + 已連接的伺服器 + 詳細統計數據 + 已停用 + 不活躍 + 直接連接? + 午安! + 早安! + 不向新成員傳送歷史訊息 + 未發送歷史訊息給新成員。 + 在私聊中開啟(測試版)! + 透過QR code轉移到另一部裝置。 + 不啟用 + 錯誤 + 連線停止 + %s的連結斷開,原因是:%s]]> + 斷線原因:%s + 斷開行動裝置連接 + 桌面地址 + 通過局域網發現 + %s 處理中]]> + %s 斷開連接]]> + 下載連結詳情中 + 群組已存在! + 錯誤 + 已下載 + 重製統計數據出錯 + 已過期 + 連接數 + 已建立 + 刪除錯誤 + 解密出錯 + 已刪除 + 已下載的檔案 + 下載出錯 + 功能執行所花費的時間過長:%1$d 秒:%2$s + 無效名稱 + 正確名字為 %s? + 複製錯誤 + 正在連接到桌面 + 找到桌面 + 自動連接 + 與PC版的連接不穩定 + 桌面 + 已安裝的PC版本不支援。請確認兩台裝置所安裝的版本相同 + PC版邀請碼錯誤 + 通過連結連接? + 加入你的群組嗎? + 轉移到此處 + 無效連結 + 在另一部設備上完成轉移 + 下載存檔錯誤 + 轉移到另一部裝置 + 必須停止聊天才能繼續。 + 保留對話 + 不通知刪除 + 無效的QR code + 保留未使用的邀請嗎? + 停用回執? + 啟用回執? + 正在連接聯絡人,請等待或稍後檢查! + 聯絡人被刪除了。 + 傳送調試 + 為儲存的檔案和媒體加密 + 連接到你自己? + 完成轉移 + 目前配置文件 + 檔案 + 重連伺服器出錯 + 重連伺服器出錯 + 重複 + 檔案狀態:%s + 授予權限 + 麥克風 + 資料庫將被加密,密碼將儲存在設定中 + 刪除成員的 %d 條訊息嗎? + 成員非活躍 + 輸入訊息 + 訊息將被標記為刪除。收信人可以揭示這些訊息。 + 連接 + 訊息 + 停用 + 下載更新中,請不要關閉應用 + 下載 %s(%s) + 從不 + 中等 + 媒體和檔案伺服器 + 訊息伺服器 + 即使在對話中禁用。 + 更快的發起聊天 + 修復還原備份後的加密問題 + 網路管理 + 新的桌面應用! + 已連接 + 連接中 + 錯誤 + 為群組停用回執? + 過往的成員 %1$s + 私密訊息路由 🚀 + 貼上存檔連結 + 從桌面使用並掃描QR code。]]> + 無傳送資訊 + 或者顯示此碼 + 待連接 + 請檢查 Simple X 鏈接是否正確。 + 私密筆記 + 請稍後再試。 + 沒有過濾的聯絡人 + 打開檔案位置 + 打開 + 私密筆記 + 波斯語用戶界面 + 從聊天列表播放。 + 正在準備上傳 + 擁有者 + 不相容! + 請將它報告給開發者: +\n%s +\n +\n建議重啟應用。 + 從已連接行動裝置加載檔案時請稍候片刻 + 或者掃描QR code + 禁止傳送 SimpleX 連結 + 沒有選擇聊天 + 打開設定 + 這人資料主題 + 個人資料圖片 + 同一時刻只有一台裝置可工作 + 保護 IP 地址 + 其他 + 無網路連接 + 無歷史記錄 + 無過濾聊天 + 請將它報告給開發者: +\n%s + 打開應用程式設定 + 開啟轉移畫面 + 通知將停止,直到您重啟應用程式 + 禁止傳送檔案和媒體。 + 打開群組 + 只有群組所有者才能啟用檔案和媒體。 + 貼上你收到的連結以與你的聯絡人聯絡… + 先前連接的伺服器 + 其他 + + - 可選擇通知已刪除的聯絡人。 +\n- 帶空格的個人資料名稱。 +\n- 以及更多! + 貼上連結以連接! + 貼上桌面地址 + 請確認此裝置的網路設定是否正確。 + 其他錯誤 + 請檢查行動裝置和桌面設備連接到的是同一個本地網絡,且桌面防火牆允許連接。 +\n請和開發者分享任何其他問題。 + 在防火牆中打開端口 + 或貼上存檔連結 + 正在準備下載 + 只刪除對話 + 貼上連結 + 其他 SMP 伺服器 + 其他 XFTP 伺服器 + 請讓你的聯絡人啟用通話。 + 畫中畫通話 + 或安全分享此文件連結 + 無資訊,試試重新加載 + 打開伺服器設定 + 私密路由出錯 + 尚無直接連接,訊息由管理員轉發。 + 什麼也沒選中 + 打開 + 私密路由 + 打開資料庫文件夾 + 私密訊息路由 + 請重啟應用程式。 + 關閉 + 抗量子加密 + 刪除了聯繫地址 + 重連伺服器? + 接收到的訊息 + 接收錯誤 + 重新連接所有已連接的伺服器來強制傳送訊息。這會使用額外流量。 + 重連伺服器強制傳送訊息。這會使用額外流量。 + 並行接收 + 收件人看不到這條訊息來自誰。 + 刪除了資料圖片 + 可使用的聊天工具箱 + 可存取的聊天工具欄 + 最近歷史和改進的目錄機器人。 + 每 KB 協議超時 + 保護您的真實 IP 地址。不讓你聯絡人選擇的訊息中繼看到它。 +\n在*網絡&伺服器*設定中開啓。 + 隨機密碼以明文形式儲存在設定中。 +\n您可以稍後更改。 + 傳送回條已禁用 + 代理伺服器 + 重連伺服器? + 抗量子端到端加密 + 收到的回覆 + 重連所有伺服器 + 重連 + 代理 + 隨機 + 更新 + 接收總計 + 稍後提醒 \ No newline at end of file From a95415fa1a3495f96c6728b8fd0f8b54dddb2a63 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 22 Aug 2024 12:22:28 +0100 Subject: [PATCH 036/440] 6.0.2: ios 234, android 234, desktop 63 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b2d8c8e64b..b59b1aeab3 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1879,7 +1879,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1904,7 +1904,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1928,7 +1928,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1953,7 +1953,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1969,11 +1969,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1989,11 +1989,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2014,7 +2014,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2029,7 +2029,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2051,7 +2051,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2066,7 +2066,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2088,7 +2088,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2114,7 +2114,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2139,7 +2139,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2165,7 +2165,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2190,7 +2190,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2205,7 +2205,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2224,7 +2224,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 233; + CURRENT_PROJECT_VERSION = 234; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2239,7 +2239,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.1; + MARKETING_VERSION = 6.0.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 35b9d90fc4..51b6d4ce3b 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.0.1 -android.version_code=232 +android.version_name=6.0.2 +android.version_code=234 -desktop.version_name=6.0.1 -desktop.version_code=62 +desktop.version_name=6.0.2 +desktop.version_code=63 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From c48583791060de352b1b1cbf6282cfdaed870e5b Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 22 Aug 2024 15:02:32 +0100 Subject: [PATCH 037/440] ios: allow for chat profile selection on new chat screen (#4729) * ios: allow for chat profile selection on new chat screen * add api and types * initial api connection with error handling * improve incognito handling * adjustments to different server connections * loading state * simpler handling of race * smaller delay * improve error handling and messages * fix header * remove tap section footer * incognito adjustments * set UI driving vars in main thread * remove result * incognito in profile picker and footer * put incognito mask inside a circle * fix click on incognito when already selected * fix avoid users swapping position when picker is active * fix pending contact cleanup logic * icons * restore incognito help * fix updating qr code * remove info from footer * layout --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- apps/ios/Shared/Model/SimpleXAPI.swift | 7 + .../Views/NewChat/AddContactLearnMore.swift | 2 + .../Views/NewChat/NewChatMenuButton.swift | 31 +- .../Shared/Views/NewChat/NewChatView.swift | 317 +++++++++++++++--- apps/ios/SimpleXChat/APITypes.swift | 7 + 5 files changed, 306 insertions(+), 58 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ebc58c6a05..e4b5410392 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -673,6 +673,13 @@ func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> P throw r } +func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection? { + let r = await chatSendCmd(.apiChangeConnectionUser(connId: connId, userId: userId)) + + if case let .connectionUserChanged(_, _, toConnection, _) = r {return toConnection} + throw r +} + func apiConnectPlan(connReq: String) async throws -> ConnectionPlan { let userId = try currentUserId("apiConnectPlan") let r = await chatSendCmd(.apiConnectPlan(userId: userId, connReq: connReq)) diff --git a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift index 6001dff790..3a64a955c5 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift @@ -28,7 +28,9 @@ struct AddContactLearnMore: View { Text("If you can't meet in person, show QR code in a video call, or share the link.") Text("Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).") } + .frame(maxWidth: .infinity, alignment: .leading) .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } .modifier(ThemedBackground(grouped: true)) } diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index bcca763a75..78dc2be7b8 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -14,9 +14,10 @@ enum ContactType: Int { } struct NewChatMenuButton: View { + @EnvironmentObject var chatModel: ChatModel @State private var showNewChatSheet = false @State private var alert: SomeAlert? = nil - @State private var globalAlert: SomeAlert? = nil + @State private var pendingConnection: PendingContactConnection? = nil var body: some View { Button { @@ -28,22 +29,14 @@ struct NewChatMenuButton: View { .frame(width: 24, height: 24) } .appSheet(isPresented: $showNewChatSheet) { - NewChatSheet(alert: $alert) + NewChatSheet(pendingConnection: $pendingConnection) .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) - .alert(item: $alert) { a in - return a.alert + .onDisappear { + alert = cleanupPendingConnection(chatModel: chatModel, contactConnection: pendingConnection) + pendingConnection = nil } } - // 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 + .alert(item: $alert) { a in return a.alert } } @@ -60,7 +53,8 @@ struct NewChatSheet: View { @State private var searchText = "" @State private var searchShowingSimplexLink = false @State private var searchChatFilteredBySimplexLink: String? = nil - @Binding var alert: SomeAlert? + @State private var alert: SomeAlert? + @Binding var pendingConnection: PendingContactConnection? // Sheet height management @State private var isAddContactActive = false @@ -78,6 +72,9 @@ struct NewChatSheet: View { .navigationBarTitleDisplayMode(.large) .navigationBarHidden(searchMode) .modifier(ThemedBackground(grouped: true)) + .alert(item: $alert) { a in + return a.alert + } } if #available(iOS 16.0, *), oneHandUI { let sheetHeight: CGFloat = showArchive ? 575 : 500 @@ -112,7 +109,7 @@ struct NewChatSheet: View { if (searchText.isEmpty) { Section { NavigationLink(isActive: $isAddContactActive) { - NewChatView(selection: .invite, parentAlert: $alert) + NewChatView(selection: .invite, parentAlert: $alert, contactConnection: $pendingConnection) .navigationTitle("New chat") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) @@ -122,7 +119,7 @@ struct NewChatSheet: View { } } NavigationLink(isActive: $isScanPasteLinkActive) { - NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert) + NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert, contactConnection: $pendingConnection) .navigationTitle("New chat") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 6cbc65e7c9..9a52bfaa20 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -45,18 +45,47 @@ enum NewChatOption: Identifiable { var id: Self { self } } +func cleanupPendingConnection(chatModel: ChatModel, contactConnection: PendingContactConnection?) -> SomeAlert? { + var alert: SomeAlert? = nil + + if !(chatModel.showingInvitation?.connChatUsed ?? true), + let conn = contactConnection { + alert = 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" + ) + } + + chatModel.showingInvitation = nil + + return alert +} + struct NewChatView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @State var selection: NewChatOption @State var showQRCodeScanner = false @State private var invitationUsed: Bool = false - @State private var contactConnection: PendingContactConnection? = nil @State private var connReqInvitation: String = "" @State private var creatingConnReq = false + @State var choosingProfile = false @State private var pastedLink: String = "" @State private var alert: NewChatViewAlert? @Binding var parentAlert: SomeAlert? + @Binding var contactConnection: PendingContactConnection? var body: some View { VStack(alignment: .leading) { @@ -122,26 +151,10 @@ struct NewChatView: View { } } .onDisappear { - if !(m.showingInvitation?.connChatUsed ?? true), - let conn = contactConnection { - 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" - ) + if !choosingProfile { + parentAlert = cleanupPendingConnection(chatModel: m, contactConnection: contactConnection) + contactConnection = nil } - m.showingInvitation = nil } .alert(item: $alert) { a in switch(a) { @@ -159,7 +172,8 @@ struct NewChatView: View { InviteView( invitationUsed: $invitationUsed, contactConnection: $contactConnection, - connReqInvitation: connReqInvitation + connReqInvitation: $connReqInvitation, + choosingProfile: $choosingProfile ) } else if creatingConnReq { creatingLinkProgressView() @@ -210,13 +224,25 @@ struct NewChatView: View { } } +private func incognitoProfileImage() -> some View { + Image(systemName: "theatermasks.fill") + .resizable() + .scaledToFit() + .frame(width: 30) + .foregroundColor(.indigo) +} + private struct InviteView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Binding var invitationUsed: Bool @Binding var contactConnection: PendingContactConnection? - var connReqInvitation: String + @Binding var connReqInvitation: String + @Binding var choosingProfile: Bool + @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false + @State private var showSettings: Bool = false + @State private var showIncognitoSheet = false var body: some View { List { @@ -226,28 +252,43 @@ private struct InviteView: View { .listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 10)) qrCodeView() - - Section { - IncognitoToggle(incognitoEnabled: $incognitoDefault) - } footer: { - sharedProfileInfo(incognitoDefault) - .foregroundColor(theme.colors.secondary) - } - } - .onChange(of: incognitoDefault) { incognito in - Task { - do { - if let contactConn = contactConnection, - let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) { - await MainActor.run { - contactConnection = conn - chatModel.updateContactConnection(conn) + if let selectedProfile = chatModel.currentUser { + Section { + NavigationLink { + ActiveProfilePicker( + contactConnection: $contactConnection, + connReqInvitation: $connReqInvitation, + incognitoEnabled: $incognitoDefault, + choosingProfile: $choosingProfile, + selectedProfile: selectedProfile + ) + } label: { + HStack { + if incognitoDefault { + incognitoProfileImage() + Text("Incognito") + } else { + ProfileImage(imageStr: chatModel.currentUser?.image, size: 30) + Text(chatModel.currentUser?.chatViewName ?? "") + } } } - } catch { - logger.error("apiSetConnectionIncognito error: \(responseError(error))") + } header: { + Text("Share profile").foregroundColor(theme.colors.secondary) + } footer: { + if incognitoDefault { + Text("A new random profile will be shared.") + } } } + } + .sheet(isPresented: $showIncognitoSheet) { + IncognitoHelp() + } + .onChange(of: incognitoDefault) { incognito in + setInvitationUsed() + } + .onChange(of: chatModel.currentUser) { u in setInvitationUsed() } } @@ -270,6 +311,7 @@ private struct InviteView: View { private func qrCodeView() -> some View { Section(header: Text("Or show this code").foregroundColor(theme.colors.secondary)) { SimpleXLinkQRCode(uri: connReqInvitation, onShare: setInvitationUsed) + .id("simplex-qrcode-view-for-\(connReqInvitation)") .padding() .background( RoundedRectangle(cornerRadius: 12, style: .continuous) @@ -289,6 +331,197 @@ private struct InviteView: View { } } +private struct ActiveProfilePicker: View { + @Environment(\.dismiss) var dismiss + @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @Binding var contactConnection: PendingContactConnection? + @Binding var connReqInvitation: String + @Binding var incognitoEnabled: Bool + @Binding var choosingProfile: Bool + @State private var alert: SomeAlert? + @State private var switchingProfile = false + @State private var switchingProfileByTimeout = false + @State private var lastSwitchingProfileByTimeoutCall: Double? + @State private var profiles: [User] = [] + @State var selectedProfile: User + + var body: some View { + viewBody() + .navigationTitle("Select chat profile") + .navigationBarTitleDisplayMode(.large) + .onAppear { + profiles = chatModel.users + .map { $0.user } + .filter({ u in u.activeUser || !u.hidden }) + .sorted { u, _ in u.activeUser } + } + .onChange(of: incognitoEnabled) { incognito in + if !switchingProfile { + return + } + + Task { + do { + if let contactConn = contactConnection, + let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) { + await MainActor.run { + contactConnection = conn + chatModel.updateContactConnection(conn) + switchingProfile = false + dismiss() + } + } + } catch { + switchingProfile = false + incognitoEnabled = !incognito + logger.error("apiSetConnectionIncognito error: \(responseError(error))") + let err = getErrorAlert(error, "Error changing to incognito!") + + alert = SomeAlert( + alert: Alert( + title: Text(err.title), + message: Text(err.message ?? "Error: \(responseError(error))") + ), + id: "setConnectionIncognitoError" + ) + } + } + } + .onChange(of: switchingProfile) { sp in + if sp { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + switchingProfileByTimeout = switchingProfile + } + } else { + switchingProfileByTimeout = false + } + } + .onChange(of: selectedProfile) { profile in + if (profile == chatModel.currentUser) { + return + } + Task { + do { + switchingProfile = true + if let contactConn = contactConnection, + let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) { + + await MainActor.run { + contactConnection = conn + connReqInvitation = conn.connReqInv ?? "" + chatModel.updateContactConnection(conn) + } + do { + try await changeActiveUserAsync_(profile.userId, viewPwd: nil) + await MainActor.run { + switchingProfile = false + dismiss() + } + } catch { + await MainActor.run { + switchingProfile = false + alert = SomeAlert( + alert: Alert( + title: Text("Error switching profile"), + message: Text("Your connection was moved to \(profile.chatViewName) but and unexpected error ocurred while redirecting you to the profile.") + ), + id: "switchingProfileError" + ) + } + } + } + } catch { + await MainActor.run { + // TODO: discuss error handling + switchingProfile = false + if let currentUser = chatModel.currentUser { + selectedProfile = currentUser + } + let err = getErrorAlert(error, "Error changing connection profile") + alert = SomeAlert( + alert: Alert( + title: Text(err.title), + message: Text(err.message ?? "Error: \(responseError(error))") + ), + id: "changeConnectionUserError" + ) + } + } + } + } + .alert(item: $alert) { a in + a.alert + } + .onAppear { + choosingProfile = true + } + .onDisappear { + choosingProfile = false + } + } + + + @ViewBuilder private func viewBody() -> some View { + NavigationView { + if switchingProfileByTimeout { + ProgressView("Switching profile…") + .frame(maxWidth: .infinity, maxHeight: .infinity) + .modifier(ThemedBackground(grouped: true)) + } else { + profilePicker() + .modifier(ThemedBackground(grouped: true)) + } + } + } + + @ViewBuilder private func profilePicker() -> some View { + List { + Button { + if !incognitoEnabled { + incognitoEnabled = true + switchingProfile = true + } + } label : { + HStack { + incognitoProfileImage() + Text("Incognito") + .foregroundColor(theme.colors.onBackground) + Spacer() + if incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) + } + } + } + ForEach(profiles) { item in + Button { + if selectedProfile != item || incognitoEnabled { + switchingProfile = true + incognitoEnabled = false + selectedProfile = item + } + } label: { + HStack { + ProfileImage(imageStr: item.image, size: 30) + .padding(.trailing, 2) + Text(item.chatViewName) + .foregroundColor(theme.colors.onBackground) + .lineLimit(1) + Spacer() + if selectedProfile == item, !incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) + } + } + } + } + } + } +} + private struct ConnectView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme @@ -975,10 +1208,12 @@ func connReqSentAlert(_ type: ConnReqType) -> Alert { struct NewChatView_Previews: PreviewProvider { static var previews: some View { @State var parentAlert: SomeAlert? + @State var contactConnection: PendingContactConnection? = nil NewChatView( selection: .invite, - parentAlert: $parentAlert + parentAlert: $parentAlert, + contactConnection: $contactConnection ) } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a6409dec2f..e5de52828d 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -97,6 +97,7 @@ public enum ChatCommand { case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) case apiAddContact(userId: Int64, incognito: Bool) case apiSetConnectionIncognito(connId: Int64, incognito: Bool) + case apiChangeConnectionUser(connId: Int64, userId: Int64) case apiConnectPlan(userId: Int64, connReq: String) case apiConnect(userId: Int64, incognito: Bool, connReq: String) case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) @@ -262,6 +263,7 @@ public enum ChatCommand { case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)" case let .apiAddContact(userId, incognito): return "/_connect \(userId) incognito=\(onOff(incognito))" case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" + case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)" case let .apiConnectPlan(userId, connReq): return "/_connect plan \(userId) \(connReq)" case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)" case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" @@ -403,6 +405,7 @@ public enum ChatCommand { case .apiVerifyGroupMember: return "apiVerifyGroupMember" case .apiAddContact: return "apiAddContact" case .apiSetConnectionIncognito: return "apiSetConnectionIncognito" + case .apiChangeConnectionUser: return "apiChangeConnectionUser" case .apiConnectPlan: return "apiConnectPlan" case .apiConnect: return "apiConnect" case .apiDeleteChat: return "apiDeleteChat" @@ -555,6 +558,7 @@ public enum ChatResponse: Decodable, Error { case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) case invitation(user: UserRef, connReqInvitation: String, connection: PendingContactConnection) case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) + case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef) case connectionPlan(user: UserRef, connectionPlan: ConnectionPlan) case sentConfirmation(user: UserRef, connection: PendingContactConnection) case sentInvitation(user: UserRef, connection: PendingContactConnection) @@ -725,6 +729,7 @@ public enum ChatResponse: Decodable, Error { case .connectionVerified: return "connectionVerified" case .invitation: return "invitation" case .connectionIncognitoUpdated: return "connectionIncognitoUpdated" + case .connectionUserChanged: return "connectionUserChanged" case .connectionPlan: return "connectionPlan" case .sentConfirmation: return "sentConfirmation" case .sentInvitation: return "sentInvitation" @@ -893,6 +898,7 @@ public enum ChatResponse: Decodable, Error { case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") case let .invitation(u, connReqInvitation, connection): return withUser(u, "connReqInvitation: \(connReqInvitation)\nconnection: \(connection)") case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) + case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\newUserId: \(String(describing: newUser.userId))") case let .connectionPlan(u, connectionPlan): return withUser(u, String(describing: connectionPlan)) case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) @@ -1857,6 +1863,7 @@ public enum ChatErrorType: Decodable, Hashable { case agentCommandError(message: String) case invalidFileDescription(message: String) case connectionIncognitoChangeProhibited + case connectionUserChangeProhibited case peerChatVRangeIncompatible case internalError(message: String) case exception(message: String) From 791489e9430fdce4a1eceef71d6f1e70512fd866 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 22 Aug 2024 21:36:35 +0400 Subject: [PATCH 038/440] core: multi forward api (#4704) --- apps/ios/SimpleXChat/APITypes.swift | 1 - apps/simplex-bot-advanced/Main.hs | 2 +- .../src/Broadcast/Bot.hs | 2 +- .../src/Directory/Events.hs | 2 +- src/Simplex/Chat.hs | 752 +++++++++++------- src/Simplex/Chat/Bot.hs | 7 +- src/Simplex/Chat/Controller.hs | 9 +- src/Simplex/Chat/Messages/Batch.hs | 10 +- src/Simplex/Chat/Store/Files.hs | 10 +- src/Simplex/Chat/Terminal/Input.hs | 4 +- src/Simplex/Chat/Terminal/Main.hs | 2 +- src/Simplex/Chat/Terminal/Output.hs | 5 +- src/Simplex/Chat/View.hs | 6 +- tests/ChatTests/Direct.hs | 17 +- tests/ChatTests/Files.hs | 24 +- tests/ChatTests/Forward.hs | 38 +- tests/ChatTests/Groups.hs | 26 +- tests/ChatTests/Local.hs | 17 +- tests/ChatTests/Profiles.hs | 4 +- tests/MessageBatching.hs | 2 +- tests/RemoteTests.hs | 4 +- 21 files changed, 601 insertions(+), 343 deletions(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index e5de52828d..3dc9138774 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1848,7 +1848,6 @@ public enum ChatErrorType: Decodable, Hashable { case inlineFileProhibited(fileId: Int64) case invalidQuote case invalidForward - case forwardNoFile case invalidChatItemUpdate case invalidChatItemDelete case hasCurrentCall diff --git a/apps/simplex-bot-advanced/Main.hs b/apps/simplex-bot-advanced/Main.hs index 50d7005e34..4733dafb79 100644 --- a/apps/simplex-bot-advanced/Main.hs +++ b/apps/simplex-bot-advanced/Main.hs @@ -46,7 +46,7 @@ mySquaringBot _user cc = do CRContactConnected _ contact _ -> do contactConnected contact sendMessage cc contact welcomeMessage - CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) -> do + CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = T.unpack $ ciContentToText mc number_ = readMaybe msg :: Maybe Integer sendMessage cc contact $ case number_ of diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs index 5fa3fff0a7..da021ee0b5 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs @@ -40,7 +40,7 @@ broadcastBot BroadcastBotOpts {publishers, welcomeMessage, prohibitedMessage} _u CRContactConnected _ ct _ -> do contactConnected ct sendMessage cc ct welcomeMessage - CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) + CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) : _} | publisher `elem` publishers -> if allowContent mc then do diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 33b43a239b..64e6acf1d8 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -73,7 +73,7 @@ crDirectoryEvent = \case CRGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo CRChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct CRChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct - CRNewChatItem {chatItem = AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}} -> + CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}) : _} -> Just $ case (mc, itemLive) of (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t _ -> DEUnsupportedMessage ct ciId diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 5899be6445..54a7f1ef0b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -38,10 +38,11 @@ import Data.Char import Data.Constraint (Dict (..)) import Data.Either (fromRight, lefts, partitionEithers, rights) import Data.Fixed (div') +import Data.Foldable (foldr') import Data.Functor (($>)) import Data.Functor.Identity import Data.Int (Int64) -import Data.List (find, foldl', isSuffixOf, mapAccumL, partition, sortOn) +import Data.List (find, foldl', isSuffixOf, mapAccumL, partition, sortOn, zipWith4) import Data.List.NonEmpty (NonEmpty (..), nonEmpty, toList, (<|)) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) @@ -763,18 +764,18 @@ processChatCommand' vr = \case Just (CIFFGroup _ _ (Just gId) (Just fwdItemId)) -> Just <$> withFastStore (\db -> getAChatItem db vr user (ChatRef CTGroup gId) fwdItemId) _ -> pure Nothing - APISendMessage (ChatRef cType chatId) live itemTTL cm -> withUser $ \user -> case cType of + APISendMessages (ChatRef cType chatId) live itemTTL cms -> withUser $ \user -> case cType of CTDirect -> withContactLock "sendMessage" chatId $ - sendContactContentMessage user chatId live itemTTL cm Nothing + sendContactContentMessages user chatId live itemTTL (L.map (,Nothing) cms) CTGroup -> withGroupLock "sendMessage" chatId $ - sendGroupContentMessage user chatId live itemTTL cm Nothing + sendGroupContentMessages user chatId live itemTTL (L.map (,Nothing) cms) CTLocal -> pure $ chatCmdError (Just user) "not supported" CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" - APICreateChatItem folderId cm -> withUser $ \user -> - createNoteFolderContentItem user folderId cm Nothing + APICreateChatItems folderId cms -> withUser $ \user -> + createNoteFolderContentItems user folderId (L.map (,Nothing) cms) APIUpdateChatItem (ChatRef cType chatId) itemId live mc -> withUser $ \user -> case cType of CTDirect -> withContactLock "updateChatItem" chatId $ do ct@Contact {contactId} <- withFastStore $ \db -> getContact db vr user chatId @@ -813,7 +814,7 @@ processChatCommand' vr = \case let changed = mc /= oldMC if changed || fromMaybe False itemLive then do - (SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) + SndMessage {msgId} <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) ci' <- withFastStore' $ \db -> do currentTs <- liftIO getCurrentTime when changed $ @@ -959,7 +960,7 @@ processChatCommand' vr = \case let GroupMember {memberId = itemMemberId} = chatItemMember g ci rs <- withFastStore' $ \db -> getGroupReactions db g membership itemMemberId itemSharedMId True checkReactionAllowed rs - (SndMessage {msgId}, _) <- sendGroupMessage user g ms (XMsgReact itemSharedMId (Just itemMemberId) reaction add) + SndMessage {msgId} <- sendGroupMessage user g ms (XMsgReact itemSharedMId (Just itemMemberId) reaction add) createdAt <- liftIO getCurrentTime reactions <- withFastStore' $ \db -> do setGroupReaction db g membership itemMemberId itemSharedMId True reaction add msgId createdAt @@ -977,55 +978,83 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") - APIForwardChatItem (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemId itemTTL -> withUser $ \user -> case toCType of + APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of CTDirect -> do - (cm, ciff) <- prepareForward user - withContactLock "forwardChatItem, to contact" toChatId $ - sendContactContentMessage user toChatId False itemTTL cm ciff + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + withContactLock "forwardChatItem, to contact" toChatId $ + sendContactContentMessages user toChatId False itemTTL cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTGroup -> do - (cm, ciff) <- prepareForward user - withGroupLock "forwardChatItem, to group" toChatId $ - sendGroupContentMessage user toChatId False itemTTL cm ciff + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + withGroupLock "forwardChatItem, to group" toChatId $ + sendGroupContentMessages user toChatId False itemTTL cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTLocal -> do - (cm, ciff) <- prepareForward user - createNoteFolderContentItem user toChatId cm ciff + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + createNoteFolderContentItems user toChatId cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where - prepareForward :: User -> CM (ComposedMessage, Maybe CIForwardedFrom) + prepareForward :: User -> CM [ComposeMessageReq] prepareForward user = case fromCType of CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do - (ct, CChatItem _ ci) <- withFastStore $ \db -> do - ct <- getContact db vr user fromChatId - cci <- getDirectChatItem db user fromChatId itemId - pure (ct, cci) - (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci - let ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + ct <- withFastStore $ \db -> getContact db vr user fromChatId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + mapM (ciComposeMsgReq ct) items where - forwardName :: Contact -> ContactName - forwardName Contact {profile = LocalProfile {displayName, localAlias}} - | localAlias /= "" = localAlias - | otherwise = displayName + getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) + getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId + ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq + ciComposeMsgReq ct (CChatItem _ ci) = do + (mc, mDir) <- forwardMC ci + file <- forwardCryptoFile ci + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) + pure (ComposedMessage file Nothing mc, ciff) + where + forwardName :: Contact -> ContactName + forwardName Contact {profile = LocalProfile {displayName, localAlias}} + | localAlias /= "" = localAlias + | otherwise = displayName CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do - (gInfo, CChatItem _ ci) <- withFastStore $ \db -> do - gInfo <- getGroupInfo db vr user fromChatId - cci <- getGroupChatItem db user fromChatId itemId - pure (gInfo, cci) - (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci - let ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + mapM (ciComposeMsgReq gInfo) items where - forwardName :: GroupInfo -> ContactName - forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName + getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) + getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId + ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq + ciComposeMsgReq gInfo (CChatItem _ ci) = do + (mc, mDir) <- forwardMC ci + file <- forwardCryptoFile ci + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) + pure (ComposedMessage file Nothing mc, ciff) + where + forwardName :: GroupInfo -> ContactName + forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName CTLocal -> do - (CChatItem _ ci) <- withFastStore $ \db -> getLocalChatItem db user fromChatId itemId - (mc, _) <- forwardMC ci - file <- forwardCryptoFile ci - let ciff = forwardCIFF ci Nothing - pure (ComposedMessage file Nothing mc, ciff) + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + mapM ciComposeMsgReq items + where + getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) + getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId + ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq + ciComposeMsgReq (CChatItem _ ci) = do + (mc, _) <- forwardMC ci + file <- forwardCryptoFile ci + let ciff = forwardCIFF ci Nothing + pure (ComposedMessage file Nothing mc, ciff) CTContactRequest -> throwChatError $ CECommandError "not supported" CTContactConnection -> throwChatError $ CECommandError "not supported" where @@ -1042,27 +1071,26 @@ processChatCommand' vr = \case forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile) forwardCryptoFile ChatItem {file = Nothing} = pure Nothing forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of - CIFile {fileName, fileStatus, fileSource = Just fromCF@CryptoFile {filePath}} - | ciFileLoaded fileStatus -> - chatReadVar filesFolder >>= \case - Nothing -> - ifM (doesFileExist filePath) (pure $ Just fromCF) (throwChatError CEForwardNoFile) - Just filesFolder -> do - let fsFromPath = filesFolder filePath - ifM - (doesFileExist fsFromPath) - ( do - fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName - liftIO $ B.writeFile fsNewPath "" -- create empty file - encrypt <- chatReadVar encryptLocalFiles - cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing - let toCF = CryptoFile fsNewPath cfArgs - -- to keep forwarded file in case original is deleted - liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF - pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) - ) - (throwChatError CEForwardNoFile) - _ -> throwChatError CEForwardNoFile + CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> + chatReadVar filesFolder >>= \case + Nothing -> + ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing) + Just filesFolder -> do + let fsFromPath = filesFolder filePath + ifM + (doesFileExist fsFromPath) + ( do + fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName + liftIO $ B.writeFile fsNewPath "" -- create empty file + encrypt <- chatReadVar encryptLocalFiles + cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing + let toCF = CryptoFile fsNewPath cfArgs + -- to keep forwarded file in case original is deleted + liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF + pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) + ) + (pure Nothing) + _ -> pure Nothing copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do fromSizeFull <- getFileSize fsFromPath @@ -1271,7 +1299,7 @@ processChatCommand' vr = \case let call' = Call {contactId, callId, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} call_ <- atomically $ TM.lookupInsert contactId call' calls forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] ok user else pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (chatFeatureNameText CFCalls)) SendCallInvitation cName callType -> withUser $ \user -> do @@ -1784,17 +1812,17 @@ processChatCommand' vr = \case contactId <- withFastStore $ \db -> getContactIdByName db user fromContactName forwardedItemId <- withFastStore $ \db -> getDirectChatItemIdByText' db user contactId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItem toChatRef (ChatRef CTDirect contactId) forwardedItemId Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing ForwardGroupMessage toChatName fromGroupName fromMemberName_ forwardedMsg -> withUser $ \user -> do groupId <- withFastStore $ \db -> getGroupIdByName db user fromGroupName forwardedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId fromMemberName_ forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItem toChatRef (ChatRef CTGroup groupId) forwardedItemId Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing ForwardLocalMessage toChatName forwardedMsg -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) forwardedItemId <- withFastStore $ \db -> getLocalChatItemIdByText' db user folderId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItem toChatRef (ChatRef CTLocal folderId) forwardedItemId Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing SendMessage (ChatName cType name) msg -> withUser $ \user -> do let mc = MCText msg case cType of @@ -1802,7 +1830,7 @@ processChatCommand' vr = \case withFastStore' (\db -> runExceptT $ getContactIdByName db user name) >>= \case Right ctId -> do let chatRef = ChatRef CTDirect ctId - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage Nothing Nothing mc + processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage Nothing Nothing mc :| []) Left _ -> withFastStore' (\db -> runExceptT $ getActiveMembersByName db vr user name) >>= \case Right [(gInfo, member)] -> do @@ -1816,11 +1844,11 @@ processChatCommand' vr = \case CTGroup -> do gId <- withFastStore $ \db -> getGroupIdByName db user name let chatRef = ChatRef CTGroup gId - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage Nothing Nothing mc + processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage Nothing Nothing mc :| []) CTLocal | name == "" -> do folderId <- withFastStore (`getUserNoteFolderId` user) - processChatCommand . APICreateChatItem folderId $ ComposedMessage Nothing Nothing mc + processChatCommand $ APICreateChatItems folderId (ComposedMessage Nothing Nothing mc :| []) | otherwise -> throwChatError $ CECommandError "not supported" _ -> throwChatError $ CECommandError "not supported" SendMemberContactMessage gName mName msg -> withUser $ \user -> do @@ -1839,11 +1867,11 @@ processChatCommand' vr = \case cr -> pure cr Just ctId -> do let chatRef = ChatRef CTDirect ctId - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage Nothing Nothing mc + processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage Nothing Nothing mc :| []) SendLiveMessage chatName msg -> withUser $ \user -> do chatRef <- getChatRef user chatName let mc = MCText msg - processChatCommand . APISendMessage chatRef True Nothing $ ComposedMessage Nothing Nothing mc + processChatCommand $ APISendMessages chatRef True Nothing (ComposedMessage Nothing Nothing mc :| []) SendMessageBroadcast msg -> withUser $ \user -> do contacts <- withFastStore' $ \db -> getUserContacts db vr user withChatLock "sendMessageBroadcast" . procCmd $ do @@ -1884,7 +1912,7 @@ processChatCommand' vr = \case contactId <- withFastStore $ \db -> getContactIdByName db user cName quotedItemId <- withFastStore $ \db -> getDirectChatItemIdByText db userId contactId msgDir quotedMsg let mc = MCText msg - processChatCommand . APISendMessage (ChatRef CTDirect contactId) False Nothing $ ComposedMessage Nothing (Just quotedItemId) mc + processChatCommand $ APISendMessages (ChatRef CTDirect contactId) False Nothing (ComposedMessage Nothing (Just quotedItemId) mc :| []) DeleteMessage chatName deletedMsg -> withUser $ \user -> do chatRef <- getChatRef user chatName deletedItemId <- getSentChatItemIdByText user chatRef deletedMsg @@ -1995,9 +2023,9 @@ processChatCommand' vr = \case (Just ct, Just cReq) -> sendGrpInvitation user ct gInfo (m :: GroupMember) {memberRole = memRole} cReq _ -> throwChatError $ CEGroupCantResendInvitation gInfo cName _ -> do - (msg, _) <- sendGroupMessage user gInfo members $ XGrpMemRole mId memRole + msg <- sendGroupMessage user gInfo members $ XGrpMemRole mId memRole ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent gEvent) - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] pure CRMemberRoleUser {user, groupInfo = gInfo, member = m {memberRole = memRole}, fromRole = mRole, toRole = memRole} APIBlockMemberForAll groupId memberId blocked -> withUser $ \user -> do Group gInfo@GroupInfo {membership} members <- withFastStore $ \db -> getGroup db vr user groupId @@ -2014,7 +2042,7 @@ processChatCommand' vr = \case msg <- sendGroupMessage' user gInfo remainingMembers event let ciContent = CISndGroupEvent $ SGEMemberBlocked memberId (fromLocalProfile bmp) blocked ci <- saveSndChatItem user (CDGroupSnd gInfo) msg ciContent - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] bm' <- withFastStore $ \db -> do liftIO $ updateGroupMemberBlocked db user groupId memberId mrs getGroupMember db vr user groupId memberId @@ -2036,9 +2064,9 @@ processChatCommand' vr = \case deleteMemberConnection user m withFastStore' $ \db -> deleteGroupMember db user m _ -> do - (msg, _) <- sendGroupMessage user gInfo members $ XGrpMemDel mId + msg <- sendGroupMessage user gInfo members $ XGrpMemDel mId ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent $ SGEMemberDeleted memberId (fromLocalProfile memberProfile)) - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] deleteMemberConnection' user m True -- undeleted "member connected" chat item will prevent deletion of member record deleteOrUpdateMemberRecord user m @@ -2050,7 +2078,7 @@ processChatCommand' vr = \case cancelFilesInProgress user filesInfo msg <- sendGroupMessage' user gInfo members XGrpLeave ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent SGEUserLeft) - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] -- TODO delete direct connections that were unused deleteGroupLinkIfExists user gInfo -- member records are not deleted to keep history @@ -2149,7 +2177,7 @@ processChatCommand' vr = \case let ct' = ct {contactGrpInvSent = True} forM_ msgContent_ $ \mc -> do ci <- saveSndChatItem user (CDDirectSnd ct') sndMsg (CISndMsgContent mc) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct') ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci] pure $ CRNewMemberContactSentInv user ct' g m _ -> throwChatError CEGroupMemberNotActive CreateGroupLink gName mRole -> withUser $ \user -> do @@ -2168,7 +2196,7 @@ processChatCommand' vr = \case groupId <- withFastStore $ \db -> getGroupIdByName db user gName quotedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId cName quotedMsg let mc = MCText msg - processChatCommand . APISendMessage (ChatRef CTGroup groupId) False Nothing $ ComposedMessage Nothing (Just quotedItemId) mc + processChatCommand $ APISendMessages (ChatRef CTGroup groupId) False Nothing (ComposedMessage Nothing (Just quotedItemId) mc :| []) ClearNoteFolder -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) processChatCommand $ APIClearChat (ChatRef CTLocal folderId) @@ -2208,8 +2236,8 @@ processChatCommand' vr = \case SendFile chatName f -> withUser $ \user -> do chatRef <- getChatRef user chatName case chatRef of - ChatRef CTLocal folderId -> processChatCommand . APICreateChatItem folderId $ ComposedMessage (Just f) Nothing (MCFile "") - _ -> processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just f) Nothing (MCFile "") + ChatRef CTLocal folderId -> processChatCommand $ APICreateChatItems folderId (ComposedMessage (Just f) Nothing (MCFile "") :| []) + _ -> processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage (Just f) Nothing (MCFile "") :| []) SendImage chatName f@(CryptoFile fPath _) -> withUser $ \user -> do chatRef <- getChatRef user chatName filePath <- lift $ toFSFilePath fPath @@ -2217,7 +2245,7 @@ processChatCommand' vr = \case fileSize <- getFileSize filePath unless (fileSize <= maxImageSize) $ throwChatError CEFileImageSize {filePath} -- TODO include file description for preview - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just f) Nothing (MCImage "" fixedImagePreview) + processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage (Just f) Nothing (MCImage "" fixedImagePreview) :| []) ForwardFile chatName fileId -> forwardFile chatName fileId SendFile ForwardImage chatName fileId -> forwardFile chatName fileId SendImage SendFileDescription _chatName _f -> pure $ chatCmdError Nothing "TODO" @@ -2626,11 +2654,11 @@ processChatCommand' vr = \case assertUserGroupRole g GROwner when (n /= n') $ checkValidName n' g' <- withStore $ \db -> updateGroupProfile db user g p' - (msg, _) <- sendGroupMessage user g' ms (XGrpInfo p') + msg <- sendGroupMessage user g' ms (XGrpInfo p') let cd = CDGroupSnd g' unless (sameGroupProfileInfo p p') $ do ci <- saveSndChatItem user cd msg (CISndGroupEvent $ SGEGroupUpdated p') - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat g') ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat g') ci] createGroupFeatureChangedItems user cd CISndGroupFeature g g' pure $ CRGroupUpdated user g g' Nothing checkValidName :: GroupName -> CM () @@ -2712,7 +2740,7 @@ processChatCommand' vr = \case let content = CISndGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole timed_ <- contactCITimed ct ci <- saveSndChatItem' user (CDDirectSnd ct) msg content Nothing Nothing Nothing timed_ False - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) drgRandomBytes :: Int -> CM ByteString @@ -2858,77 +2886,154 @@ processChatCommand' vr = \case forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTDirect contactId, itemId) _ -> pure () -- prohibited - sendContactContentMessage :: User -> ContactId -> Bool -> Maybe Int -> ComposedMessage -> Maybe CIForwardedFrom -> CM ChatResponse - sendContactContentMessage user contactId live itemTTL (ComposedMessage file_ quotedItemId_ mc) itemForwarded = do + sendContactContentMessages :: User -> ContactId -> Bool -> Maybe Int -> NonEmpty ComposeMessageReq -> CM ChatResponse + sendContactContentMessages user contactId live itemTTL cmrs = do + assertMultiSendable live cmrs ct@Contact {contactUsed} <- withFastStore $ \db -> getContact db vr user contactId assertDirectAllowed user MDSnd ct XMsgNew_ + assertVoiceAllowed ct unless contactUsed $ withFastStore' $ \db -> updateContactUsed db user ct - if isVoice mc && not (featureAllowed SCFVoice forUser ct) - then pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (chatFeatureNameText CFVoice)) - else do - (fInv_, ciFile_) <- L.unzip <$> setupSndFileTransfer ct - timed_ <- sndContactCITimed live ct itemTTL - (msgContainer, quotedItem_) <- prepareMsg fInv_ timed_ - (msg, _) <- sendDirectContactMessage user ct (XMsgNew msgContainer) - ci <- saveSndChatItem' user (CDDirectSnd ct) msg (CISndMsgContent mc) ciFile_ quotedItem_ itemForwarded timed_ live - forM_ (timed_ >>= timedDeleteAt') $ - startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) - pure $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + processComposedMessages ct where - setupSndFileTransfer :: Contact -> CM (Maybe (FileInvitation, CIFile 'MDSnd)) - setupSndFileTransfer ct = forM file_ $ \file -> do - fileSize <- checkSndFile file - xftpSndFileTransfer user file fileSize 1 $ CGContact ct - prepareMsg :: Maybe FileInvitation -> Maybe CITimed -> CM (MsgContainer, Maybe (CIQuote 'CTDirect)) - prepareMsg fInv_ timed_ = case (quotedItemId_, itemForwarded) of - (Nothing, Nothing) -> pure (MCSimple (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) - (Nothing, Just _) -> pure (MCForward (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) - (Just quotedItemId, Nothing) -> do - CChatItem _ qci@ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, formattedText, file} <- - withFastStore $ \db -> getDirectChatItem db user contactId quotedItemId - (origQmc, qd, sent) <- quoteData qci - let msgRef = MsgRef {msgId = itemSharedMsgId, sentAt = itemTs, sent, memberId = Nothing} - qmc = quoteContent mc origQmc file - quotedItem = CIQuote {chatDir = qd, itemId = Just quotedItemId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc, formattedText} - pure (MCQuote QuotedMsg {msgRef, content = qmc} (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Just quotedItem) - (Just _, Just _) -> throwChatError CEInvalidQuote + assertVoiceAllowed :: Contact -> CM () + assertVoiceAllowed ct = + when (not (featureAllowed SCFVoice forUser ct) && any (\(ComposedMessage {msgContent}, _) -> isVoice msgContent) cmrs) $ + throwChatError (CECommandError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFVoice)) + processComposedMessages :: Contact -> CM ChatResponse + processComposedMessages ct = do + (fInvs_, ciFiles_) <- L.unzip <$> setupSndFileTransfers + timed_ <- sndContactCITimed live ct itemTTL + (msgContainers, quotedItems_) <- L.unzip <$> prepareMsgs (L.zip cmrs fInvs_) timed_ + msgs_ <- sendDirectContactMessages user ct $ L.map XMsgNew msgContainers + let itemsData = prepareSndItemsData msgs_ cmrs ciFiles_ quotedItems_ + (errs, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live + unless (null errs) $ toView $ CRChatErrors (Just user) errs + forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> + forM_ cis $ \ci -> + startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) deleteAt + pure $ CRNewChatItems user (map (AChatItem SCTDirect SMDSnd (DirectChat ct)) cis) where - quoteData :: ChatItem c d -> CM (MsgContent, CIQDirection 'CTDirect, Bool) - quoteData ChatItem {meta = CIMeta {itemDeleted = Just _}} = throwChatError CEInvalidQuote - quoteData ChatItem {content = CISndMsgContent qmc} = pure (qmc, CIQDirectSnd, True) - quoteData ChatItem {content = CIRcvMsgContent qmc} = pure (qmc, CIQDirectRcv, False) - quoteData _ = throwChatError CEInvalidQuote - sendGroupContentMessage :: User -> GroupId -> Bool -> Maybe Int -> ComposedMessage -> Maybe CIForwardedFrom -> CM ChatResponse - sendGroupContentMessage user groupId live itemTTL (ComposedMessage file_ quotedItemId_ mc) itemForwarded = do + setupSndFileTransfers :: CM (NonEmpty (Maybe FileInvitation, Maybe (CIFile 'MDSnd))) + setupSndFileTransfers = + forM cmrs $ \(ComposedMessage {fileSource = file_}, _) -> case file_ of + Just file -> do + fileSize <- checkSndFile file + (fInv, ciFile) <- xftpSndFileTransfer user file fileSize 1 $ CGContact ct + pure (Just fInv, Just ciFile) + Nothing -> pure (Nothing, Nothing) + prepareMsgs :: NonEmpty (ComposeMessageReq, Maybe FileInvitation) -> Maybe CITimed -> CM (NonEmpty (MsgContainer, Maybe (CIQuote 'CTDirect))) + prepareMsgs cmsFileInvs timed_ = + forM cmsFileInvs $ \((ComposedMessage {quotedItemId, msgContent = mc}, itemForwarded), fInv_) -> + case (quotedItemId, itemForwarded) of + (Nothing, Nothing) -> pure (MCSimple (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) + (Nothing, Just _) -> pure (MCForward (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) + (Just qiId, Nothing) -> do + CChatItem _ qci@ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, formattedText, file} <- + withFastStore $ \db -> getDirectChatItem db user contactId qiId + (origQmc, qd, sent) <- quoteData qci + let msgRef = MsgRef {msgId = itemSharedMsgId, sentAt = itemTs, sent, memberId = Nothing} + qmc = quoteContent mc origQmc file + quotedItem = CIQuote {chatDir = qd, itemId = Just qiId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc, formattedText} + pure (MCQuote QuotedMsg {msgRef, content = qmc} (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Just quotedItem) + (Just _, Just _) -> throwChatError CEInvalidQuote + where + quoteData :: ChatItem c d -> CM (MsgContent, CIQDirection 'CTDirect, Bool) + quoteData ChatItem {meta = CIMeta {itemDeleted = Just _}} = throwChatError CEInvalidQuote + quoteData ChatItem {content = CISndMsgContent qmc} = pure (qmc, CIQDirectSnd, True) + quoteData ChatItem {content = CIRcvMsgContent qmc} = pure (qmc, CIQDirectRcv, False) + quoteData _ = throwChatError CEInvalidQuote + sendGroupContentMessages :: User -> GroupId -> Bool -> Maybe Int -> NonEmpty ComposeMessageReq -> CM ChatResponse + sendGroupContentMessages user groupId live itemTTL cmrs = do + assertMultiSendable live cmrs g@(Group gInfo _) <- withFastStore $ \db -> getGroup db vr user groupId assertUserGroupRole gInfo GRAuthor - send g + assertGroupContentAllowed gInfo + processComposedMessages g where - send g@(Group gInfo@GroupInfo {membership} ms) = - case prohibitedGroupContent gInfo membership mc file_ of - Just f -> notAllowedError f - Nothing -> do - (fInv_, ciFile_) <- L.unzip <$> setupSndFileTransfer g (length $ filter memberCurrent ms) - timed_ <- sndGroupCITimed live gInfo itemTTL - (msgContainer, quotedItem_) <- prepareGroupMsg user gInfo mc quotedItemId_ itemForwarded fInv_ timed_ live - (msg, r) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer) - ci <- saveSndChatItem' user (CDGroupSnd gInfo) msg (CISndMsgContent mc) ciFile_ quotedItem_ itemForwarded timed_ live + assertGroupContentAllowed :: GroupInfo -> CM () + assertGroupContentAllowed gInfo@GroupInfo {membership} = + case findProhibited (L.toList cmrs) of + Just f -> throwChatError (CECommandError $ "feature not allowed " <> T.unpack (groupFeatureNameText f)) + Nothing -> pure () + where + findProhibited :: [ComposeMessageReq] -> Maybe GroupFeature + findProhibited = + foldr' + (\(ComposedMessage {fileSource, msgContent = mc}, _) acc -> prohibitedGroupContent gInfo membership mc fileSource <|> acc) + Nothing + processComposedMessages :: Group -> CM ChatResponse + processComposedMessages g@(Group gInfo ms) = do + (fInvs_, ciFiles_) <- L.unzip <$> setupSndFileTransfers (length $ filter memberCurrent ms) + timed_ <- sndGroupCITimed live gInfo itemTTL + (msgContainers, quotedItems_) <- L.unzip <$> prepareMsgs (L.zip cmrs fInvs_) timed_ + (msgs_, gsr) <- sendGroupMessages user gInfo ms $ L.map XMsgNew msgContainers + let itemsData = prepareSndItemsData (L.toList msgs_) cmrs ciFiles_ quotedItems_ + cis_ <- saveSndChatItems user (CDGroupSnd gInfo) itemsData timed_ live + createMemberSndStatuses cis_ msgs_ gsr + let (errs, cis) = partitionEithers cis_ + unless (null errs) $ toView $ CRChatErrors (Just user) errs + forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> + forM_ cis $ \ci -> + startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) deleteAt + pure $ CRNewChatItems user (map (AChatItem SCTGroup SMDSnd (GroupChat gInfo)) cis) + where + setupSndFileTransfers :: Int -> CM (NonEmpty (Maybe FileInvitation, Maybe (CIFile 'MDSnd))) + setupSndFileTransfers n = + forM cmrs $ \(ComposedMessage {fileSource = file_}, _) -> case file_ of + Just file -> do + fileSize <- checkSndFile file + (fInv, ciFile) <- xftpSndFileTransfer user file fileSize n $ CGGroup g + pure (Just fInv, Just ciFile) + Nothing -> pure (Nothing, Nothing) + prepareMsgs :: NonEmpty (ComposeMessageReq, Maybe FileInvitation) -> Maybe CITimed -> CM (NonEmpty (MsgContainer, Maybe (CIQuote 'CTGroup))) + prepareMsgs cmsFileInvs timed_ = + forM cmsFileInvs $ \((ComposedMessage {quotedItemId, msgContent = mc}, itemForwarded), fInv_) -> + prepareGroupMsg user gInfo mc quotedItemId itemForwarded fInv_ timed_ live + createMemberSndStatuses :: + [Either ChatError (ChatItem 'CTGroup 'MDSnd)] -> + NonEmpty (Either ChatError SndMessage) -> + GroupSndResult -> + CM () + createMemberSndStatuses cis_ msgs_ GroupSndResult {sentTo, pending, forwarded} = do + let msgToItem = mapMsgToItem withFastStore' $ \db -> do - let GroupSndResult {sentTo, pending, forwarded} = mkGroupSndResult r - createMemberSndStatuses db ci sentTo GSSNew - createMemberSndStatuses db ci forwarded GSSForwarded - createMemberSndStatuses db ci pending GSSInactive - forM_ (timed_ >>= timedDeleteAt') $ - startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) - pure $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + forM_ sentTo (processSentTo db msgToItem) + forM_ forwarded (processForwarded db) + forM_ pending (processPending db msgToItem) where - createMemberSndStatuses db ci ms' gss = - forM_ ms' $ \GroupMember {groupMemberId} -> createGroupSndStatus db (chatItemId' ci) groupMemberId gss - notAllowedError f = pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (groupFeatureNameText f)) - setupSndFileTransfer :: Group -> Int -> CM (Maybe (FileInvitation, CIFile 'MDSnd)) - setupSndFileTransfer g n = forM file_ $ \file -> do - fileSize <- checkSndFile file - xftpSndFileTransfer user file fileSize n $ CGGroup g + mapMsgToItem :: Map MessageId ChatItemId + mapMsgToItem = foldr' addItem M.empty (zip (L.toList msgs_) cis_) + where + addItem (Right SndMessage {msgId}, Right ci) m = M.insert msgId (chatItemId' ci) m + addItem _ m = m + processSentTo :: DB.Connection -> Map MessageId ChatItemId -> (GroupMemberId, Either ChatError [MessageId], Either ChatError ([Int64], PQEncryption)) -> IO () + processSentTo db msgToItem (mId, msgIds_, deliveryResult) = forM_ msgIds_ $ \msgIds -> do + let ciIds = mapMaybe (`M.lookup` msgToItem) msgIds + status = case deliveryResult of + Right _ -> GSSNew + Left e -> GSSError $ SndErrOther $ tshow e + forM_ ciIds $ \ciId -> createGroupSndStatus db ciId mId status + processForwarded :: DB.Connection -> GroupMember -> IO () + processForwarded db GroupMember {groupMemberId} = + forM_ cis_ $ \ci_ -> + forM_ ci_ $ \ci -> createGroupSndStatus db (chatItemId' ci) groupMemberId GSSForwarded + processPending :: DB.Connection -> Map MessageId ChatItemId -> (GroupMemberId, Either ChatError MessageId, Either ChatError ()) -> IO () + processPending db msgToItem (mId, msgId_, pendingResult) = forM_ msgId_ $ \msgId -> do + let ciId_ = M.lookup msgId msgToItem + status = case pendingResult of + Right _ -> GSSInactive + Left e -> GSSError $ SndErrOther $ tshow e + forM_ ciId_ $ \ciId -> createGroupSndStatus db ciId mId status + assertMultiSendable :: Bool -> NonEmpty ComposeMessageReq -> CM () + assertMultiSendable live cmrs + | length cmrs == 1 = pure () + | otherwise = + -- When sending multiple messages only single quote is allowed. + -- This is to support case of sending multiple attachments while also quoting another message. + -- UI doesn't allow composing with multiple quotes, so api prohibits it as well, and doesn't bother + -- batching retrieval of quoted messages (prepareMsgs). + when (live || length (L.filter (\(ComposedMessage {quotedItemId}, _) -> isJust quotedItemId) cmrs) > 1) $ + throwChatError (CECommandError "invalid multi send: live and more than one quote not supported") xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> CM (FileInvitation, CIFile 'MDSnd) xftpSndFileTransfer user file fileSize n contactOrGroup = do (fInv, ciFile, ft) <- xftpSndFileTransfer_ user file fileSize n $ Just contactOrGroup @@ -2944,27 +3049,58 @@ processChatCommand' vr = \case \db -> createSndFTDescrXFTP db user (Just m) conn ft dummyFileDescr saveMemberFD _ = pure () pure (fInv, ciFile) - createNoteFolderContentItem :: User -> NoteFolderId -> ComposedMessage -> Maybe CIForwardedFrom -> CM ChatResponse - createNoteFolderContentItem user folderId (ComposedMessage file_ quotedItemId_ mc) itemForwarded = do - forM_ quotedItemId_ $ \_ -> throwError $ ChatError $ CECommandError "not supported" + prepareSndItemsData :: + [Either ChatError SndMessage] -> + NonEmpty ComposeMessageReq -> + NonEmpty (Maybe (CIFile 'MDSnd)) -> + NonEmpty (Maybe (CIQuote c)) -> + [Either ChatError (NewSndChatItemData c)] + prepareSndItemsData msgs_ cmrs' ciFiles_ quotedItems_ = + [ ( case msg_ of + Right msg -> Right $ NewSndChatItemData msg (CISndMsgContent msgContent) f q itemForwarded + Left e -> Left e -- step over original error + ) + | (msg_, (ComposedMessage {msgContent}, itemForwarded), f, q) <- + zipWith4 (,,,) msgs_ (L.toList cmrs') (L.toList ciFiles_) (L.toList quotedItems_) + ] + createNoteFolderContentItems :: User -> NoteFolderId -> NonEmpty ComposeMessageReq -> CM ChatResponse + createNoteFolderContentItems user folderId cmrs = do + assertNoQuotes nf <- withFastStore $ \db -> getNoteFolder db user folderId createdAt <- liftIO getCurrentTime - let content = CISndMsgContent mc - let cd = CDLocalSnd nf - ciId <- createLocalChatItem user cd content itemForwarded createdAt - ciFile_ <- forM file_ $ \cf@CryptoFile {filePath, cryptoArgs} -> do - fsFilePath <- lift $ toFSFilePath filePath - fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cryptoArgs - chunkSize <- asks $ fileChunkSize . config - withFastStore' $ \db -> do - fileId <- createLocalFile CIFSSndStored db user nf ciId createdAt cf fileSize chunkSize - pure CIFile {fileId, fileName = takeFileName filePath, fileSize, fileSource = Just cf, fileStatus = CIFSSndStored, fileProtocol = FPLocal} - let ci = mkChatItem cd ciId content ciFile_ Nothing Nothing itemForwarded Nothing False createdAt Nothing createdAt - pure . CRNewChatItem user $ AChatItem SCTLocal SMDSnd (LocalChat nf) ci + ciFiles_ <- createLocalFiles nf createdAt + let itemsData = prepareLocalItemsData cmrs ciFiles_ + cis <- createLocalChatItems user (CDLocalSnd nf) itemsData createdAt + pure $ CRNewChatItems user (map (AChatItem SCTLocal SMDSnd (LocalChat nf)) cis) + where + assertNoQuotes :: CM () + assertNoQuotes = + when (any (\(ComposedMessage {quotedItemId}, _) -> isJust quotedItemId) cmrs) $ + throwChatError (CECommandError "createNoteFolderContentItems: quotes not supported") + createLocalFiles :: NoteFolder -> UTCTime -> CM (NonEmpty (Maybe (CIFile 'MDSnd))) + createLocalFiles nf createdAt = + forM cmrs $ \(ComposedMessage {fileSource = file_}, _) -> + forM file_ $ \cf@CryptoFile {filePath, cryptoArgs} -> do + fsFilePath <- lift $ toFSFilePath filePath + fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cryptoArgs + chunkSize <- asks $ fileChunkSize . config + withFastStore' $ \db -> do + fileId <- createLocalFile CIFSSndStored db user nf createdAt cf fileSize chunkSize + pure CIFile {fileId, fileName = takeFileName filePath, fileSize, fileSource = Just cf, fileStatus = CIFSSndStored, fileProtocol = FPLocal} + prepareLocalItemsData :: + NonEmpty ComposeMessageReq -> + NonEmpty (Maybe (CIFile 'MDSnd)) -> + [(CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom)] + prepareLocalItemsData cmrs' ciFiles_ = + [ (CISndMsgContent mc, f, itemForwarded) + | ((ComposedMessage {msgContent = mc}, itemForwarded), f) <- zip (L.toList cmrs') (L.toList ciFiles_) + ] getConnQueueInfo user Connection {connId, agentConnId = AgentConnId acId} = do msgInfo <- withFastStore' (`getLastRcvMsgInfo` connId) CRQueueInfo user msgInfo <$> withAgent (`getConnectionQueueInfo` acId) +type ComposeMessageReq = (ComposedMessage, Maybe CIForwardedFrom) + contactCITimed :: Contact -> CM (Maybe CITimed) contactCITimed ct = sndContactCITimed False ct Nothing @@ -4398,7 +4534,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Just AutoAccept {autoReply = Just mc} -> do (msg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] _ -> pure () processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM () @@ -4732,7 +4868,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let GroupMember {memberId} = m ms = forwardedToGroupMembers (introducedMembers <> invitedMembers) forwardedMsgs' events = L.map (\cm -> XGrpMsgForward memberId cm brokerTs) forwardedMsgs' - unless (null ms) $ sendGroupMessages user gInfo ms events + unless (null ms) $ void $ sendGroupMessages user gInfo ms events RCVD msgMeta msgRcpt -> withAckMessage' "group rcvd" agentConnId msgMeta $ groupMsgReceived gInfo m conn msgMeta msgRcpt @@ -5240,7 +5376,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = newChatItem ciContent ciFile_ timed_ live = do ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs ciContent ciFile_ timed_ live reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getDirectCIReactions db ct sharedMsgId) sharedMsgId_ - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}] autoAcceptFile :: Maybe (RcvFileTransfer, CIFile 'MDRcv) -> CM () autoAcceptFile = mapM_ $ \(ft, CIFile {fileSize}) -> do @@ -5544,7 +5680,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol} ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs (CIRcvMsgContent $ MCFile "") ciFile Nothing False - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] where brokerTs = metaBrokerTs msgMeta @@ -5711,7 +5847,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupMsgToView :: forall d. MsgDirectionI d => GroupInfo -> ChatItem 'CTGroup d -> CM () groupMsgToView gInfo ci = - toView $ CRNewChatItem user (AChatItem SCTGroup (msgDirection @d) (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup (msgDirection @d) (GroupChat gInfo) ci] processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> CM () processGroupInvitation ct inv msg msgMeta = do @@ -5738,7 +5874,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let content = CIRcvGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole ci <- saveRcvChatItem user (CDDirectRcv ct) msg brokerTs content withStore' $ \db -> setGroupInvitationChatItemId db user groupId (chatItemId' ci) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] toView $ CRReceivedGroupInvitation {user, groupInfo = gInfo, contact = ct, fromMemberRole = fromRole, memberRole = memRole} where brokerTs = metaBrokerTs msgMeta @@ -5765,7 +5901,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted} let ct'' = ct' {activeConn = activeConn'} :: Contact ci <- saveRcvChatItem user (CDDirectRcv ct'') msg brokerTs (CIRcvDirectEvent RDEContactDeleted) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct'') ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct'') ci] toView $ CRContactDeletedByContact user ct'' else do contactConns <- withStore' $ \db -> getContactConnections db vr userId c @@ -5966,14 +6102,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = call_ <- atomically (TM.lookupInsert contactId call' calls) forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing toView $ CRCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callTs = chatItemTs' ci} - toView $ CRNewChatItem user $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] else featureRejected CFCalls where brokerTs = metaBrokerTs msgMeta saveCallItem status = saveRcvChatItem user (CDDirectRcv ct) msg brokerTs (CIRcvCall status 0) featureRejected f = do ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs (CIRcvChatFeatureRejected f) Nothing Nothing False - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] -- to party initiating call xCallOffer :: Contact -> CallId -> CallOffer -> RcvMessage -> CM () @@ -6426,7 +6562,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRNewMemberContactReceivedInv user mCt' g m' forM_ mContent_ $ \mc -> do ci <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat mCt') ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat mCt') ci] securityCodeChanged :: Contact -> CM () securityCodeChanged ct = do @@ -6813,21 +6949,23 @@ deleteOrUpdateMemberRecord user@User {userId} member = Just _ -> updateGroupMemberStatus db userId member GSMemRemoved Nothing -> deleteGroupMember db user member -sendDirectContactMessages :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM () +sendDirectContactMessages :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM [Either ChatError SndMessage] sendDirectContactMessages user ct events = do Connection {connChatVersion = v} <- liftEither $ contactSendConn_ ct if v >= batchSend2Version then sendDirectContactMessages' user ct events - else mapM_ (void . sendDirectContactMessage user ct) events + else forM (L.toList events) $ \evt -> + (Right . fst <$> sendDirectContactMessage user ct evt) `catchChatError` \e -> pure (Left e) -sendDirectContactMessages' :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM () +sendDirectContactMessages' :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM [Either ChatError SndMessage] sendDirectContactMessages' user ct events = do conn@Connection {connId} <- liftEither $ contactSendConn_ ct let idsEvts = L.map (ConnectionId connId,) events msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events} - (errs, msgs) <- lift $ partitionEithers . L.toList <$> createSndMessages idsEvts - unless (null errs) $ toView $ CRChatErrors (Just user) errs - mapM_ (batchSendConnMessages user conn msgFlags) (L.nonEmpty msgs) + sndMsgs_ <- lift $ createSndMessages idsEvts + (sndMsgs', pqEnc_) <- batchSendConnMessagesB user conn msgFlags sndMsgs_ + forM_ pqEnc_ $ \pqEnc' -> void $ createContactPQSndItem user ct conn pqEnc' + pure sndMsgs' sendDirectContactMessage :: MsgEncodingI e => User -> Contact -> ChatMsgEvent e -> CM (SndMessage, Int64) sendDirectContactMessage user ct chatMsgEvent = do @@ -6887,17 +7025,30 @@ sendGroupMemberMessages user conn events groupId = do forM_ (L.nonEmpty msgs) $ \msgs' -> batchSendConnMessages user conn MsgFlags {notification = True} msgs' -batchSendConnMessages :: User -> Connection -> MsgFlags -> NonEmpty SndMessage -> CM () -batchSendConnMessages user conn msgFlags msgs = do - let batched = batchSndMessagesJSON msgs - let (errs', msgBatches) = partitionEithers batched - -- shouldn't happen, as large messages would have caused createNewSndMessage to throw SELargeMsg - unless (null errs') $ toView $ CRChatErrors (Just user) errs' - forM_ (L.nonEmpty msgBatches) $ \msgBatches' -> do - let msgReq = L.map (msgBatchReq conn msgFlags) msgBatches' - void $ deliverMessages msgReq +batchSendConnMessages :: User -> Connection -> MsgFlags -> NonEmpty SndMessage -> CM ([Either ChatError SndMessage], Maybe PQEncryption) +batchSendConnMessages user conn msgFlags msgs = + batchSendConnMessagesB user conn msgFlags $ L.map Right msgs -batchSndMessagesJSON :: NonEmpty SndMessage -> [Either ChatError MsgBatch] +batchSendConnMessagesB :: User -> Connection -> MsgFlags -> NonEmpty (Either ChatError SndMessage) -> CM ([Either ChatError SndMessage], Maybe PQEncryption) +batchSendConnMessagesB _user conn msgFlags msgs_ = do + let batched_ = batchSndMessagesJSON msgs_ + case L.nonEmpty batched_ of + Just batched' -> do + let msgReqs = L.map (fmap (msgBatchReq conn msgFlags)) batched' + delivered <- deliverMessagesB msgReqs + let msgs' = concat $ L.zipWith flattenMsgs batched' delivered + pqEnc = findLastPQEnc delivered + pure (msgs', pqEnc) + Nothing -> pure ([], Nothing) + where + flattenMsgs :: Either ChatError MsgBatch -> Either ChatError ([Int64], PQEncryption) -> [Either ChatError SndMessage] + flattenMsgs (Right (MsgBatch _ sndMsgs)) (Right _) = map Right sndMsgs + flattenMsgs (Right (MsgBatch _ sndMsgs)) (Left ce) = replicate (length sndMsgs) (Left ce) + flattenMsgs (Left ce) _ = [Left ce] -- restore original ChatError + findLastPQEnc :: NonEmpty (Either ChatError ([Int64], PQEncryption)) -> Maybe PQEncryption + findLastPQEnc = foldr' (\x acc -> case x of Right (_, pqEnc) -> Just pqEnc; Left _ -> acc) Nothing + +batchSndMessagesJSON :: NonEmpty (Either ChatError SndMessage) -> [Either ChatError MsgBatch] batchSndMessagesJSON = batchMessages maxEncodedMsgLength . L.toList msgBatchReq :: Connection -> MsgFlags -> MsgBatch -> ChatMsgReq @@ -6949,7 +7100,7 @@ deliverMessagesB msgReqs = do lift . withStoreBatch $ \db -> L.map (bindRight $ createDelivery db) sent where compressBodies = - forME msgReqs $ \mr@(conn@Connection {pqSupport, connChatVersion = v}, msgFlags, msgBody, msgId) -> + forME msgReqs $ \mr@(conn@Connection {pqSupport, connChatVersion = v}, msgFlags, msgBody, msgIds) -> runExceptT $ case pqSupport of -- we only compress messages when: -- 1) PQ support is enabled @@ -6958,7 +7109,7 @@ deliverMessagesB msgReqs = do PQSupportOn | v >= pqEncryptionCompressionVersion && B.length msgBody > maxCompressedMsgLength -> do let msgBody' = compressedBatchMsgBody_ msgBody when (B.length msgBody' > maxCompressedMsgLength) $ throwError $ ChatError $ CEException "large compressed message" - pure (conn, msgFlags, msgBody', msgId) + pure (conn, msgFlags, msgBody', msgIds) _ -> pure mr toAgent prev = \case Right (conn@Connection {connId, pqEncryption}, msgFlags, msgBody, _msgIds) -> @@ -6982,13 +7133,23 @@ deliverMessagesB msgReqs = do where updatePQ = updateConnPQSndEnabled db connId pqSndEnabled' --- TODO combine profile update and message into one batch --- Take into account that it may not fit, and that we currently don't support sending multiple messages to the same connection in one call. -sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, GroupSndResultData) +sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM SndMessage sendGroupMessage user gInfo members chatMsgEvent = do + sendGroupMessages user gInfo members (chatMsgEvent :| []) >>= \case + ((Right msg) :| [], _) -> pure msg + _ -> throwChatError $ CEInternalError "sendGroupMessage: expected 1 message" + +sendGroupMessage' :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM SndMessage +sendGroupMessage' user gInfo members chatMsgEvent = + sendGroupMessages_ user gInfo members (chatMsgEvent :| []) >>= \case + ((Right msg) :| [], _) -> pure msg + _ -> throwChatError $ CEInternalError "sendGroupMessage': expected 1 message" + +sendGroupMessages :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty (Either ChatError SndMessage), GroupSndResult) +sendGroupMessages user gInfo members events = do when shouldSendProfileUpdate $ sendProfileUpdate `catchChatError` (toView . CRChatError (Just user)) - sendGroupMessage_ user gInfo members chatMsgEvent + sendGroupMessages_ user gInfo members events where User {profile = p, userMemberProfileUpdatedAt} = user GroupInfo {userMemberProfileSentAt} = gInfo @@ -7006,59 +7167,33 @@ sendGroupMessage user gInfo members chatMsgEvent = do currentTs <- liftIO getCurrentTime withStore' $ \db -> updateUserMemberProfileSentAt db user gInfo currentTs -type GroupSndResultData = (([Either ChatError ([Int64], PQEncryption)], [(GroupMember, Connection)]), ([Either ChatError ()], [GroupMember]), [GroupMember]) - data GroupSndResult = GroupSndResult - { sentTo :: [GroupMember], - pending :: [GroupMember], + { sentTo :: [(GroupMemberId, Either ChatError [MessageId], Either ChatError ([Int64], PQEncryption))], + pending :: [(GroupMemberId, Either ChatError MessageId, Either ChatError ())], forwarded :: [GroupMember] } -sendGroupMessage' :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM SndMessage -sendGroupMessage' user gInfo members chatMsgEvent = fst <$> sendGroupMessage_ user gInfo members chatMsgEvent - -sendGroupMessage_ :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, GroupSndResultData) -sendGroupMessage_ user gInfo members chatMsgEvent = - sendGroupMessages_ user gInfo members (chatMsgEvent :| []) >>= \case - (msg :| [], r) -> pure (msg, r) - _ -> throwChatError $ CEInternalError "sendGroupMessage': expected 1 message" - -sendGroupMessages :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM () -sendGroupMessages user gInfo members events = void $ sendGroupMessages_ user gInfo members events - -mkGroupSndResult :: GroupSndResultData -> GroupSndResult -mkGroupSndResult ((delivered, sentTo), (stored, pending), forwarded) = - GroupSndResult - { sentTo = filterSent' delivered sentTo fst, - pending = filterSent' stored pending id, - forwarded - } - where - -- TODO in theory this could deduplicate members and keep results only when ... some sent? or all sent? - -- This is not important, as it is not used in batch calls - filterSent' :: [Either ChatError a] -> [mem] -> (mem -> GroupMember) -> [GroupMember] - filterSent' rs ms mem = [mem m | (Right _, m) <- zip rs ms] - -sendGroupMessages_ :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty SndMessage, GroupSndResultData) -sendGroupMessages_ user gInfo@GroupInfo {groupId} members events = do +sendGroupMessages_ :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty (Either ChatError SndMessage), GroupSndResult) +sendGroupMessages_ _user gInfo@GroupInfo {groupId} members events = do let idsEvts = L.map (GroupId groupId,) events - (errs, msgs) <- lift $ partitionEithers . L.toList <$> createSndMessages idsEvts - unless (null errs) $ toView $ CRChatErrors (Just user) errs - case L.nonEmpty msgs of - Nothing -> throwChatError $ CEInternalError "sendGroupMessages: no messages created" - Just msgs' -> do - recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) - let msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events} - (toSendSeparate, toSendBatched, pending, forwarded, _, dups) = - foldr addMember ([], [], [], [], S.empty, 0 :: Int) recipientMembers - when (dups /= 0) $ logError $ "sendGroupMessage: " <> tshow dups <> " duplicate members" - -- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here - let msgReqs = prepareMsgReqs msgFlags msgs' toSendSeparate toSendBatched - delivered <- maybe (pure []) (fmap L.toList . deliverMessages) $ L.nonEmpty msgReqs - let errors = lefts delivered - unless (null errors) $ toView $ CRChatErrors (Just user) errors - stored <- lift . withStoreBatch' $ \db -> map (\m -> createPendingMsgs db m msgs') pending - pure (msgs', ((delivered, toSendSeparate <> toSendBatched), (stored, pending), forwarded)) + sndMsgs_ <- lift $ createSndMessages idsEvts + recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) + let msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events} + (toSendSeparate, toSendBatched, toPending, forwarded, _, dups) = + foldr' addMember ([], [], [], [], S.empty, 0 :: Int) recipientMembers + when (dups /= 0) $ logError $ "sendGroupMessages_: " <> tshow dups <> " duplicate members" + -- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here + -- Deliver to toSend members + let (sendToMemIds, msgReqs) = prepareMsgReqs msgFlags sndMsgs_ toSendSeparate toSendBatched + delivered <- maybe (pure []) (fmap L.toList . deliverMessagesB) $ L.nonEmpty msgReqs + when (length delivered /= length sendToMemIds) $ logError "sendGroupMessages_: sendToMemIds and delivered length mismatch" + -- Save as pending for toPending members + let (pendingMemIds, pendingReqs) = preparePending sndMsgs_ toPending + stored <- lift $ withStoreBatch (\db -> map (bindRight $ createPendingMsg db) pendingReqs) + -- Zip for easier access to results + let sentTo = zipWith3 (\mId mReq r -> (mId, fmap (\(_, _, _, msgIds) -> msgIds) mReq, r)) sendToMemIds msgReqs delivered + pending = zipWith3 (\mId pReq r -> (mId, fmap snd pReq, r)) pendingMemIds pendingReqs stored + pure (sndMsgs_, GroupSndResult {sentTo, pending, forwarded}) where shuffleMembers :: [GroupMember] -> IO [GroupMember] shuffleMembers ms = do @@ -7079,22 +7214,38 @@ sendGroupMessages_ user gInfo@GroupInfo {groupId} members events = do where mId = groupMemberId' m mIds' = S.insert mId mIds - prepareMsgReqs :: MsgFlags -> NonEmpty SndMessage -> [(GroupMember, Connection)] -> [(GroupMember, Connection)] -> [ChatMsgReq] - prepareMsgReqs msgFlags msgs toSendSeparate toSendBatched = do - let msgReqsSeparate = foldr (\(_, conn) reqs -> foldr (\msg -> (sndMessageReq conn msg :)) reqs msgs) [] toSendSeparate - batched = batchSndMessagesJSON msgs - -- _errs shouldn't happen, as large messages would have caused createNewSndMessage to throw SELargeMsg - (_errs, msgBatches) = partitionEithers batched - case L.nonEmpty msgBatches of - Just msgBatches' -> do - let msgReqsBatched = foldr (\(_, conn) reqs -> foldr (\batch -> (msgBatchReq conn msgFlags batch :)) reqs msgBatches') [] toSendBatched - msgReqsSeparate <> msgReqsBatched - Nothing -> msgReqsSeparate + prepareMsgReqs :: MsgFlags -> NonEmpty (Either ChatError SndMessage) -> [(GroupMember, Connection)] -> [(GroupMember, Connection)] -> ([GroupMemberId], [Either ChatError ChatMsgReq]) + prepareMsgReqs msgFlags msgs_ toSendSeparate toSendBatched = do + let batched_ = batchSndMessagesJSON msgs_ + case L.nonEmpty batched_ of + Just batched' -> do + let (memsSep, mreqsSep) = foldr' foldMsgs ([], []) toSendSeparate + (memsBtch, mreqsBtch) = foldr' (foldBatches batched') ([], []) toSendBatched + (memsSep <> memsBtch, mreqsSep <> mreqsBtch) + Nothing -> ([], []) where - sndMessageReq :: Connection -> SndMessage -> ChatMsgReq - sndMessageReq conn SndMessage {msgId, msgBody} = (conn, msgFlags, msgBody, [msgId]) - createPendingMsgs :: DB.Connection -> GroupMember -> NonEmpty SndMessage -> IO () - createPendingMsgs db m = mapM_ (\SndMessage {msgId} -> createPendingGroupMessage db (groupMemberId' m) msgId Nothing) + foldMsgs :: (GroupMember, Connection) -> ([GroupMemberId], [Either ChatError ChatMsgReq]) -> ([GroupMemberId], [Either ChatError ChatMsgReq]) + foldMsgs (GroupMember {groupMemberId}, conn) memIdsReqs = + foldr' (\msg_ (memIds, reqs) -> (groupMemberId : memIds, fmap sndMessageReq msg_ : reqs)) memIdsReqs msgs_ + where + sndMessageReq :: SndMessage -> ChatMsgReq + sndMessageReq SndMessage {msgId, msgBody} = (conn, msgFlags, msgBody, [msgId]) + foldBatches :: NonEmpty (Either ChatError MsgBatch) -> (GroupMember, Connection) -> ([GroupMemberId], [Either ChatError ChatMsgReq]) -> ([GroupMemberId], [Either ChatError ChatMsgReq]) + foldBatches batched' (GroupMember {groupMemberId}, conn) memIdsReqs = + foldr' (\batch_ (memIds, reqs) -> (groupMemberId : memIds, fmap (msgBatchReq conn msgFlags) batch_ : reqs)) memIdsReqs batched' + preparePending :: NonEmpty (Either ChatError SndMessage) -> [GroupMember] -> ([GroupMemberId], [Either ChatError (GroupMemberId, MessageId)]) + preparePending msgs_ = + foldr' foldMsgs ([], []) + where + foldMsgs :: GroupMember -> ([GroupMemberId], [Either ChatError (GroupMemberId, MessageId)]) -> ([GroupMemberId], [Either ChatError (GroupMemberId, MessageId)]) + foldMsgs GroupMember {groupMemberId} memIdsReqs = + foldr' (\msg_ (memIds, reqs) -> (groupMemberId : memIds, fmap pendingReq msg_ : reqs)) memIdsReqs msgs_ + where + pendingReq :: SndMessage -> (GroupMemberId, MessageId) + pendingReq SndMessage {msgId} = (groupMemberId, msgId) + createPendingMsg :: DB.Connection -> (GroupMemberId, MessageId) -> IO (Either ChatError ()) + createPendingMsg db (groupMemberId, msgId) = + createPendingGroupMessage db groupMemberId msgId Nothing $> Right () data MemberSendAction = MSASend Connection | MSASendBatched Connection | MSAPending | MSAForwarded @@ -7155,7 +7306,7 @@ sendPendingGroupMessages user GroupMember {groupMemberId} conn = do pgms <- withStore' $ \db -> getPendingGroupMessages db groupMemberId forM_ (L.nonEmpty pgms) $ \pgms' -> do let msgs = L.map (\(sndMsg, _, _) -> sndMsg) pgms' - batchSendConnMessages user conn MsgFlags {notification = True} msgs + void $ batchSendConnMessages user conn MsgFlags {notification = True} msgs lift . void . withStoreBatch' $ \db -> L.map (\SndMessage {msgId} -> deletePendingGroupMessage db groupMemberId msgId) msgs lift . void . withStoreBatch' $ \db -> L.map (\(_, tag, introId_) -> updateIntro_ db tag introId_) pgms' where @@ -7212,14 +7363,39 @@ saveSndChatItem :: ChatTypeI c => User -> ChatDirection c 'MDSnd -> SndMessage - saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothing Nothing Nothing Nothing False saveSndChatItem' :: ChatTypeI c => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIFile 'MDSnd) -> Maybe (CIQuote c) -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> CM (ChatItem c 'MDSnd) -saveSndChatItem' user cd msg@SndMessage {sharedMsgId} content ciFile quotedItem itemForwarded itemTimed live = do +saveSndChatItem' user cd msg content ciFile quotedItem itemForwarded itemTimed live = + saveSndChatItems user cd [Right NewSndChatItemData {msg, content, ciFile, quotedItem, itemForwarded}] itemTimed live >>= \case + [Right ci] -> pure ci + _ -> throwChatError $ CEInternalError "saveSndChatItem': expected 1 item" + +data NewSndChatItemData c = NewSndChatItemData + { msg :: SndMessage, + content :: CIContent 'MDSnd, + ciFile :: Maybe (CIFile 'MDSnd), + quotedItem :: Maybe (CIQuote c), + itemForwarded :: Maybe CIForwardedFrom + } + +saveSndChatItems :: + forall c. + ChatTypeI c => + User -> + ChatDirection c 'MDSnd -> + [Either ChatError (NewSndChatItemData c)] -> + Maybe CITimed -> + Bool -> + CM [Either ChatError (ChatItem c 'MDSnd)] +saveSndChatItems user cd itemsData itemTimed live = do createdAt <- liftIO getCurrentTime - ciId <- withStore' $ \db -> do - when (ciRequiresAttention content || contactChatDeleted cd) $ updateChatTs db user cd createdAt - ciId <- createNewSndChatItem db user cd msg content quotedItem itemForwarded itemTimed live createdAt - forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt - pure ciId - pure $ mkChatItem cd ciId content ciFile quotedItem (Just sharedMsgId) itemForwarded itemTimed live createdAt Nothing createdAt + when (contactChatDeleted cd || any (\NewSndChatItemData {content} -> ciRequiresAttention content) (rights itemsData)) $ + withStore' (\db -> updateChatTs db user cd createdAt) + lift $ withStoreBatch (\db -> map (bindRight $ createItem db createdAt) itemsData) + where + createItem :: DB.Connection -> UTCTime -> NewSndChatItemData c -> IO (Either ChatError (ChatItem c 'MDSnd)) + createItem db createdAt NewSndChatItemData {msg = msg@SndMessage {sharedMsgId}, content, ciFile, quotedItem, itemForwarded} = do + ciId <- createNewSndChatItem db user cd msg content quotedItem itemForwarded itemTimed live createdAt + forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt + pure $ Right $ mkChatItem cd ciId content ciFile quotedItem (Just sharedMsgId) itemForwarded itemTimed live createdAt Nothing createdAt saveRcvChatItem :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> CM (ChatItem c 'MDRcv) saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} brokerTs content = @@ -7472,7 +7648,7 @@ createContactsFeatureItems user cts chatDir ciFeature ciOffer getPref = do let dirsCIContents = map contactChangedFeatures cts (errs, acis) <- partitionEithers <$> createInternalItemsForChats user Nothing dirsCIContents unless (null errs) $ toView' $ CRChatErrors (Just user) errs - forM_ acis $ \aci -> toView' $ CRNewChatItem user aci + toView' $ CRNewChatItems user acis where contactChangedFeatures :: (Contact, Contact) -> (ChatDirection 'CTDirect d, [CIContent d]) contactChangedFeatures (Contact {mergedPreferences = cups}, ct'@Contact {mergedPreferences = cups'}) = do @@ -7510,7 +7686,7 @@ sameGroupProfileInfo p p' = p {groupPreferences = Nothing} == p' {groupPreferenc createInternalChatItem :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM () createInternalChatItem user cd content itemTs_ = lift (createInternalItemsForChats user itemTs_ [(cd, [content])]) >>= \case - [Right aci] -> toView $ CRNewChatItem user aci + [Right aci] -> toView $ CRNewChatItems user [aci] [Left e] -> throwError e rs -> throwChatError $ CEInternalError $ "createInternalChatItem: expected 1 result, got " <> show (length rs) @@ -7537,14 +7713,23 @@ createInternalItemsForChats user itemTs_ dirsCIContents = do let ci = mkChatItem cd ciId content Nothing Nothing Nothing Nothing Nothing False itemTs Nothing createdAt pure $ AChatItem (chatTypeI @c) (msgDirection @d) (toChatInfo cd) ci -createLocalChatItem :: MsgDirectionI d => User -> ChatDirection 'CTLocal d -> CIContent d -> Maybe CIForwardedFrom -> UTCTime -> CM ChatItemId -createLocalChatItem user cd content itemForwarded createdAt = do - gVar <- asks random - withStore $ \db -> do - liftIO $ updateChatTs db user cd createdAt - createWithRandomId gVar $ \sharedMsgId -> - let smi_ = Just (SharedMsgId sharedMsgId) - in createNewChatItem_ db user cd Nothing smi_ content (Nothing, Nothing, Nothing, Nothing, Nothing) itemForwarded Nothing False createdAt Nothing createdAt +createLocalChatItems :: + User -> + ChatDirection 'CTLocal 'MDSnd -> + [(CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom)] -> + UTCTime -> + CM [ChatItem 'CTLocal 'MDSnd] +createLocalChatItems user cd itemsData createdAt = do + withStore' $ \db -> updateChatTs db user cd createdAt + (errs, items) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (createItem db) itemsData) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure items + where + createItem :: DB.Connection -> (CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom) -> IO (ChatItem 'CTLocal 'MDSnd) + createItem db (content, ciFile, itemForwarded) = do + ciId <- createNewChatItem_ db user cd Nothing Nothing content (Nothing, Nothing, Nothing, Nothing, Nothing) itemForwarded Nothing False createdAt Nothing createdAt + forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt + pure $ mkChatItem cd ciId content ciFile Nothing Nothing itemForwarded Nothing False createdAt Nothing createdAt withUser' :: (User -> CM ChatResponse) -> CM ChatResponse withUser' action = @@ -7670,13 +7855,13 @@ chatCommandP = "/_get chat " *> (APIGetChat <$> chatRefP <* A.space <*> chatPaginationP <*> optional (" search=" *> stringP)), "/_get items " *> (APIGetChatItems <$> chatPaginationP <*> optional (" search=" *> stringP)), "/_get item info " *> (APIGetChatItemInfo <$> chatRefP <* A.space <*> A.decimal), - "/_send " *> (APISendMessage <$> chatRefP <*> liveMessageP <*> sendMessageTTLP <*> (" json " *> jsonP <|> " text " *> (ComposedMessage Nothing Nothing <$> mcTextP))), - "/_create *" *> (APICreateChatItem <$> A.decimal <*> (" json " *> jsonP <|> " text " *> (ComposedMessage Nothing Nothing <$> mcTextP))), + "/_send " *> (APISendMessages <$> chatRefP <*> liveMessageP <*> sendMessageTTLP <*> (" json " *> jsonP <|> " text " *> composedMessagesTextP)), + "/_create *" *> (APICreateChatItems <$> A.decimal <*> (" json " *> jsonP <|> " text " *> composedMessagesTextP)), "/_update item " *> (APIUpdateChatItem <$> chatRefP <* A.space <*> A.decimal <*> liveMessageP <* A.space <*> msgContentP), "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), - "/_forward " *> (APIForwardChatItem <$> chatRefP <* A.space <*> chatRefP <* A.space <*> A.decimal <*> sendMessageTTLP), + "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), @@ -7974,6 +8159,9 @@ chatCommandP = '*' -> head "❤️" '^' -> '🚀' c -> c + composedMessagesTextP = do + text <- mcTextP + pure $ (ComposedMessage Nothing Nothing text) :| [] liveMessageP = " live=" *> onOffP <|> pure False sendMessageTTLP = " ttl=" *> ((Just <$> A.decimal) <|> ("default" $> Nothing)) <|> pure Nothing receiptSettings = do diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index f3de92e1f2..66479c0ee6 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -11,6 +11,7 @@ import Control.Concurrent.Async import Control.Concurrent.STM import Control.Monad import qualified Data.ByteString.Char8 as B +import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Text as T import Simplex.Chat.Controller import Simplex.Chat.Core @@ -31,7 +32,7 @@ chatBotRepl welcome answer _user cc = do CRContactConnected _ contact _ -> do contactConnected contact void $ sendMessage cc contact welcome - CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) -> do + CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = T.unpack $ ciContentToText mc void $ sendMessage cc contact =<< answer contact msg _ -> pure () @@ -68,8 +69,8 @@ sendComposedMessage cc = sendComposedMessage' cc . contactId' sendComposedMessage' :: ChatController -> ContactId -> Maybe ChatItemId -> MsgContent -> IO () sendComposedMessage' cc ctId quotedItemId msgContent = do let cm = ComposedMessage {fileSource = Nothing, quotedItemId, msgContent} - sendChatCmd cc (APISendMessage (ChatRef CTDirect ctId) False Nothing cm) >>= \case - CRNewChatItem {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId + sendChatCmd cc (APISendMessages (ChatRef CTDirect ctId) False Nothing (cm :| [])) >>= \case + CRNewChatItems {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId r -> putStrLn $ "unexpected send message response: " <> show r deleteMessage :: ChatController -> Contact -> ChatItemId -> IO () diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index c4f056c778..9d92ee8193 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -292,13 +292,13 @@ data ChatCommand | APIGetChat ChatRef ChatPagination (Maybe String) | APIGetChatItems ChatPagination (Maybe String) | APIGetChatItemInfo ChatRef ChatItemId - | APISendMessage {chatRef :: ChatRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessage :: ComposedMessage} - | APICreateChatItem {noteFolderId :: NoteFolderId, composedMessage :: ComposedMessage} + | APISendMessages {chatRef :: ChatRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessages :: NonEmpty ComposedMessage} + | APICreateChatItems {noteFolderId :: NoteFolderId, composedMessages :: NonEmpty ComposedMessage} | APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, msgContent :: MsgContent} | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} - | APIForwardChatItem {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemId :: ChatItemId, ttl :: Maybe Int} + | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} | APIUserRead UserId | UserRead | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) @@ -597,7 +597,7 @@ data ChatResponse | CRContactCode {user :: User, contact :: Contact, connectionCode :: Text} | CRGroupMemberCode {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionCode :: Text} | CRConnectionVerified {user :: User, verified :: Bool, expectedCode :: Text} - | CRNewChatItem {user :: User, chatItem :: AChatItem} + | CRNewChatItems {user :: User, chatItems :: [AChatItem]} | CRChatItemStatusUpdated {user :: User, chatItem :: AChatItem} | CRChatItemUpdated {user :: User, chatItem :: AChatItem} | CRChatItemNotChanged {user :: User, chatItem :: AChatItem} @@ -1178,7 +1178,6 @@ data ChatErrorType | CEInlineFileProhibited {fileId :: FileTransferId} | CEInvalidQuote | CEInvalidForward - | CEForwardNoFile | CEInvalidChatItemUpdate | CEInvalidChatItemDelete | CEHasCurrentCall diff --git a/src/Simplex/Chat/Messages/Batch.hs b/src/Simplex/Chat/Messages/Batch.hs index 690ae5828f..c1c45d7b0a 100644 --- a/src/Simplex/Chat/Messages/Batch.hs +++ b/src/Simplex/Chat/Messages/Batch.hs @@ -17,16 +17,18 @@ import Simplex.Chat.Messages data MsgBatch = MsgBatch ByteString [SndMessage] --- | Batches [SndMessage] into batches of ByteStrings in form of JSON arrays. +-- | Batches SndMessages in [Either ChatError SndMessage] into batches of ByteStrings in form of JSON arrays. +-- Preserves original errors in the list. -- Does not check if the resulting batch is a valid JSON. -- If a single element is passed, it is returned as is (a JSON string). -- If an element exceeds maxLen, it is returned as ChatError. -batchMessages :: Int -> [SndMessage] -> [Either ChatError MsgBatch] +batchMessages :: Int -> [Either ChatError SndMessage] -> [Either ChatError MsgBatch] batchMessages maxLen = addBatch . foldr addToBatch ([], [], 0, 0) where msgBatch batch = Right (MsgBatch (encodeMessages batch) batch) - addToBatch :: SndMessage -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) - addToBatch msg@SndMessage {msgBody} acc@(batches, batch, len, n) + addToBatch :: Either ChatError SndMessage -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) + addToBatch (Left err) acc = (Left err : addBatch acc, [], 0, 0) -- step over original error + addToBatch (Right msg@SndMessage {msgBody}) acc@(batches, batch, len, n) | batchLen <= maxLen = (batches, msg : batch, len', n + 1) | msgLen <= maxLen = (addBatch acc, [msg], msgLen, 1) | otherwise = (errLarge msg : addBatch acc, [], 0, 0) diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index d1da081cee..2c02d872b1 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -966,20 +966,20 @@ lookupFileTransferRedirectMeta db User {userId} fileId = do redirects <- DB.query db "SELECT file_id FROM files WHERE user_id = ? AND redirect_file_id = ?" (userId, fileId) rights <$> mapM (runExceptT . getFileTransferMeta_ db userId . fromOnly) redirects -createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> ChatItemId -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 -createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} chatItemId itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do +createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 +createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do DB.execute db [sql| INSERT INTO files - ( user_id, note_folder_id, chat_item_id, + ( user_id, note_folder_id, file_name, file_path, file_size, file_crypto_key, file_crypto_nonce, chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at ) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, noteFolderId, chatItemId) + ( (userId, noteFolderId) :. (takeFileName filePath, filePath, fileSize) :. maybe (Nothing, Nothing) (\(CFArgs key nonce) -> (Just key, Just nonce)) cryptoArgs :. (fileChunkSize, Nothing :: Maybe InlineFileMode, fileStatus, FPLocal, itemTs, itemTs) diff --git a/src/Simplex/Chat/Terminal/Input.hs b/src/Simplex/Chat/Terminal/Input.hs index 2d1039e585..4f6d66d2c1 100644 --- a/src/Simplex/Chat/Terminal/Input.hs +++ b/src/Simplex/Chat/Terminal/Input.hs @@ -69,7 +69,7 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do Nothing -> setActive ct "" Just rhId -> updateRemoteUser ct u rhId CRChatItems u chatName_ _ -> whenCurrUser cc u $ mapM_ (setActive ct . chatActiveTo) chatName_ - CRNewChatItem u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo + CRNewChatItems u ((AChatItem _ SMDSnd cInfo _) : _) -> whenCurrUser cc u $ setActiveChat ct cInfo CRChatItemUpdated u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo CRChatItemsDeleted u ((ChatItemDeletion (AChatItem _ _ cInfo _) _) : _) _ _ -> whenCurrUser cc u $ setActiveChat ct cInfo CRContactDeleted u c -> whenCurrUser cc u $ unsetActiveContact ct c @@ -93,7 +93,7 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do Right SendMessageBroadcast {} -> True _ -> False startLiveMessage :: Either a ChatCommand -> ChatResponse -> IO () - startLiveMessage (Right (SendLiveMessage chatName msg)) (CRNewChatItem _ (AChatItem cType SMDSnd _ ChatItem {meta = CIMeta {itemId}})) = do + startLiveMessage (Right (SendLiveMessage chatName msg)) (CRNewChatItems {chatItems = [AChatItem cType SMDSnd _ ChatItem {meta = CIMeta {itemId}}]}) = do whenM (isNothing <$> readTVarIO liveMessageState) $ do let s = T.unpack msg int = case cType of SCTGroup -> 5000000; _ -> 3000000 :: Int diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs index a946ba3483..64703a3a92 100644 --- a/src/Simplex/Chat/Terminal/Main.hs +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -44,7 +44,7 @@ simplexChatCLI' cfg opts@ChatOpts {chatCmd, chatCmdLog, chatCmdDelay, chatServer when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do (_, _, r') <- atomically . readTBQueue $ outputQ cc case r' of - CRNewChatItem {} -> printResponse r' + CRNewChatItems {} -> printResponse r' _ -> when (chatCmdLog == CCLAll) $ printResponse r' sendChatCmdStr cc chatCmd >>= printResponse threadDelay $ chatCmdDelay * 1000000 diff --git a/src/Simplex/Chat/Terminal/Output.hs b/src/Simplex/Chat/Terminal/Output.hs index 40f14a10de..0ead850b86 100644 --- a/src/Simplex/Chat/Terminal/Output.hs +++ b/src/Simplex/Chat/Terminal/Output.hs @@ -147,7 +147,7 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha forever $ do (_, outputRH, r) <- atomically $ readTBQueue outputQ case r of - CRNewChatItem u ci -> when markRead $ markChatItemRead u ci + CRNewChatItems u (ci : _) -> when markRead $ markChatItemRead u ci -- At the moment of writing received items are created one at a time CRChatItemUpdated u ci -> when markRead $ markChatItemRead u ci CRRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId CRRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_ @@ -175,7 +175,8 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha responseNotification :: ChatTerminal -> ChatController -> ChatResponse -> IO () responseNotification t@ChatTerminal {sendNotification} cc = \case - CRNewChatItem u (AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent mc, formattedText}) -> + -- At the moment of writing received items are created one at a time + CRNewChatItems u ((AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent mc, formattedText}) : _) -> when (chatDirNtf u cInfo chatDir $ isMention ci) $ do whenCurrUser cc u $ setActiveChat t cInfo case (cInfo, chatDir) of diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index e154b5b902..dd2bf94467 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -120,7 +120,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRConnectionVerified u verified code -> ttyUser u [plain $ if verified then "connection verified" else "connection not verified, current code is " <> code] CRContactCode u ct code -> ttyUser u $ viewContactCode ct code testView CRGroupMemberCode u g m code -> ttyUser u $ viewGroupMemberCode g m code testView - CRNewChatItem u (AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item + CRNewChatItems u chatItems -> + concatMap + (\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item) + chatItems CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId] @@ -2025,7 +2028,6 @@ viewChatError isCmd logLevel testView = \case CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInvalidQuote -> ["cannot reply to this message"] CEInvalidForward -> ["cannot forward this message"] - CEForwardNoFile -> ["cannot forward this message, file not found"] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] CEHasCurrentCall -> ["call already in progress"] diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 09b2d7d51c..86c51f6aaa 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -52,6 +52,7 @@ chatDirectTests = do it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact it "should send multiline message" testMultilineMessage it "send large message" testLargeMessage + it "send multiple messages api" testSendMulti describe "duplicate contacts" $ do it "duplicate contacts are separate (contacts don't merge)" testDuplicateContactsSeparate it "new contact is separate with multiple duplicate contacts (contacts don't merge)" testDuplicateContactsMultipleSeparate @@ -839,6 +840,18 @@ testLargeMessage = bob <## "contact alice changed to alice2" bob <## "use @alice2 to send messages" +testSendMulti :: HasCallStack => FilePath -> IO () +testSendMulti = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + alice ##> "/_send @2 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "@bob test 1" + alice <# "@bob test 2" + bob <# "alice> test 1" + bob <# "alice> test 2" + testGetSetSMPServers :: HasCallStack => FilePath -> IO () testGetSetSMPServers = testChat2 aliceProfile bobProfile $ @@ -2162,7 +2175,7 @@ testSetChatItemTTL = -- chat item with file alice #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") copyFile "./tests/fixtures/test.jpg" "./tests/tmp/app_files/test.jpg" - alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob test.jpg" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -2410,7 +2423,7 @@ setupDesynchronizedRatchet tmp alice = do (bob "/tail @alice 1" bob <# "alice> decryption error, possibly due to the device change (header, 3 messages)" - bob ##> "@alice 1" + bob `send` "@alice 1" bob <## "error: command is prohibited, sendMessagesB: send prohibited" (alice FilePath -> IO () runTestMessageWithFile = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do connectUsers alice bob - alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}]" alice <# "@bob hi, sending a file" alice <# "/f @bob ./tests/fixtures/test.jpg" alice <## "use /fc 1 to cancel sending" @@ -91,7 +91,7 @@ testSendImage = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do connectUsers alice bob - alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob ./tests/fixtures/test.jpg" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -122,7 +122,7 @@ testSenderMarkItemDeleted = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do connectUsers alice bob - alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test_1MB.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test_1MB.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}]" alice <# "@bob hi, sending a file" alice <# "/f @bob ./tests/fixtures/test_1MB.pdf" alice <## "use /fc 1 to cancel sending" @@ -147,7 +147,7 @@ testFilesFoldersSendImage = connectUsers alice bob alice #$> ("/_files_folder ./tests/fixtures", id, "ok") bob #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") - alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob test.jpg" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -180,7 +180,7 @@ testFilesFoldersImageSndDelete = alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf" bob #$> ("/_files_folder ./tests/tmp/bob_app_files", id, "ok") - alice ##> "/_send @2 json {\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob test_1MB.pdf" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)" @@ -212,7 +212,7 @@ testFilesFoldersImageRcvDelete = connectUsers alice bob alice #$> ("/_files_folder ./tests/fixtures", id, "ok") bob #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") - alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob test.jpg" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -239,7 +239,7 @@ testSendImageWithTextAndQuote = connectUsers alice bob bob #> "@alice hi alice" alice <# "bob> hi alice" - alice ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 1 <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") + alice ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 1 <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]") alice <# "@bob > hi alice" alice <## " hey bob" alice <# "/f @bob ./tests/fixtures/test.jpg" @@ -265,7 +265,7 @@ testSendImageWithTextAndQuote = bob @@@ [("@alice", "hey bob")] -- quoting (file + text) with file uses quoted text - bob ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.pdf\", \"quotedItemId\": " <> itemId 2 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}") + bob ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.pdf\", \"quotedItemId\": " <> itemId 2 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}]") bob <# "@alice > hey bob" bob <## " test.pdf" bob <# "/f @alice ./tests/fixtures/test.pdf" @@ -287,7 +287,7 @@ testSendImageWithTextAndQuote = B.readFile "./tests/tmp/test.pdf" `shouldReturn` txtSrc -- quoting (file without text) with file uses file name - alice ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 3 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") + alice ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 3 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]") alice <# "@bob > test.pdf" alice <## " test.jpg" alice <# "/f @bob ./tests/fixtures/test.jpg" @@ -313,7 +313,7 @@ testGroupSendImage = \alice bob cath -> withXFTPServer $ do createGroup3 "team" alice bob cath threadDelay 1000000 - alice ##> "/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send #1 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f #team ./tests/fixtures/test.jpg" alice <## "use /fc 1 to cancel sending" concurrentlyN_ @@ -361,7 +361,7 @@ testGroupSendImageWithTextAndQuote = (cath <# "#team bob> hi team") threadDelay 1000000 msgItemId <- lastItemId alice - alice ##> ("/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> msgItemId <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") + alice ##> ("/_send #1 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> msgItemId <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]") alice <# "#team > bob hi team" alice <## " hey bob" alice <# "/f #team ./tests/fixtures/test.jpg" @@ -460,7 +460,7 @@ testXFTPFileTransferEncrypted = let fileJSON = LB.unpack $ J.encode $ CryptoFile srcPath $ Just cfArgs withXFTPServer $ do connectUsers alice bob - alice ##> ("/_send @2 json {\"msgContent\":{\"type\":\"file\", \"text\":\"\"}, \"fileSource\": " <> fileJSON <> "}") + alice ##> ("/_send @2 json [{\"msgContent\":{\"type\":\"file\", \"text\":\"\"}, \"fileSource\": " <> fileJSON <> "}]") alice <# "/f @bob ./tests/tmp/alice/test.pdf" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index d49c6df955..221a2426b1 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -33,6 +33,8 @@ chatForwardTests = do it "with relative paths: from contact to contact" testForwardFileContactToContact it "with relative paths: from group to notes" testForwardFileGroupToNotes it "with relative paths: from notes to group" testForwardFileNotesToGroup + describe "multi forward api" $ do + it "from contact to contact" testForwardContactToContactMulti testForwardContactToContact :: HasCallStack => FilePath -> IO () testForwardContactToContact = @@ -384,7 +386,7 @@ testForwardFileNoFilesFolder = connectUsers bob cath -- send original file - alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]" alice <# "@bob hi" alice <# "/f @bob ./tests/fixtures/test.pdf" alice <## "use /fc 1 to cancel sending" @@ -441,7 +443,7 @@ testForwardFileContactToContact = connectUsers bob cath -- send original file - alice ##> "/_send @2 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]" alice <# "@bob hi" alice <# "/f @bob test.pdf" alice <## "use /fc 1 to cancel sending" @@ -506,7 +508,7 @@ testForwardFileGroupToNotes = createCCNoteFolder cath -- send original file - alice ##> "/_send #1 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" + alice ##> "/_send #1 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]" alice <# "#team hi" alice <# "/f #team test.pdf" alice <## "use /fc 1 to cancel sending" @@ -555,7 +557,7 @@ testForwardFileNotesToGroup = createGroup2 "team" alice cath -- create original file - alice ##> "/_create *1 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" + alice ##> "/_create *1 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]" alice <# "* hi" alice <# "* file 1 (test.pdf)" @@ -590,3 +592,31 @@ testForwardFileNotesToGroup = alice <## "notes: all messages are removed" fwdFileExists <- doesFileExist "./tests/tmp/alice_files/test_1.pdf" fwdFileExists `shouldBe` True + +testForwardContactToContactMulti :: HasCallStack => FilePath -> IO () +testForwardContactToContactMulti = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + connectUsers alice bob + connectUsers alice cath + connectUsers bob cath + + alice #> "@bob hi" + bob <# "alice> hi" + msgId1 <- lastItemId alice + + threadDelay 1000000 + + bob #> "@alice hey" + alice <# "bob> hey" + msgId2 <- lastItemId alice + + alice ##> ("/_forward @3 @2 " <> msgId1 <> "," <> msgId2) + alice <# "@cath <- you @bob" + alice <## " hi" + alice <# "@cath <- @bob" + alice <## " hey" + cath <# "alice> -> forwarded" + cath <## " hi" + cath <# "alice> -> forwarded" + cath <## " hey" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index d6849d3074..1ff01a911c 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -64,6 +64,7 @@ chatGroupTests = do it "moderate message of another group member (full delete)" testGroupModerateFullDelete it "moderate message that arrives after the event of moderation" testGroupDelayedModeration it "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete + it "send multiple messages api" testSendMulti describe "async group connections" $ do xit "create and join group when clients go offline" testGroupAsync describe "group links" $ do @@ -1818,6 +1819,18 @@ testGroupDelayedModerationFullDelete tmp = do where cfg = testCfgCreateGroupDirect +testSendMulti :: HasCallStack => FilePath -> IO () +testSendMulti = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + createGroup2 "team" alice bob + + alice ##> "/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "#team test 1" + alice <# "#team test 2" + bob <# "#team alice> test 1" + bob <# "#team alice> test 2" + testGroupAsync :: HasCallStack => FilePath -> IO () testGroupAsync tmp = do withNewTestChat tmp "alice" aliceProfile $ \alice -> do @@ -3468,7 +3481,8 @@ testGroupSyncRatchet tmp = bob <## "1 contacts connected (use /cs for the list)" bob <## "#team: connected to server(s)" bob `send` "#team 1" - bob <## "error: command is prohibited, sendMessagesB: send prohibited" -- silence? + -- "send prohibited" error is not printed in group as SndMessage is created, + -- but it should be displayed in per member snd statuses bob <# "#team 1" (alice "/_send #1 json {\"filePath\": \"./tests/tmp/testfile\", \"msgContent\": {\"text\":\"hello\",\"type\":\"file\"}}" + bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile\", \"msgContent\": {\"text\":\"hello\",\"type\":\"file\"}}]" bob <# "#team hello" bob <# "/f #team ./tests/tmp/testfile" bob <## "use /fc 1 to cancel sending" @@ -4969,7 +4983,7 @@ testGroupHistoryMultipleFiles = threadDelay 1000000 - bob ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}" + bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}]" bob <# "#team hi alice" bob <# "/f #team ./tests/tmp/testfile_bob" bob <## "use /fc 1 to cancel sending" @@ -4981,7 +4995,7 @@ testGroupHistoryMultipleFiles = threadDelay 1000000 - alice ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}" + alice ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}]" alice <# "#team hey bob" alice <# "/f #team ./tests/tmp/testfile_alice" alice <## "use /fc 2 to cancel sending" @@ -5047,7 +5061,7 @@ testGroupHistoryFileCancel = createGroup2 "team" alice bob - bob ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}" + bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}]" bob <# "#team hi alice" bob <# "/f #team ./tests/tmp/testfile_bob" bob <## "use /fc 1 to cancel sending" @@ -5063,7 +5077,7 @@ testGroupHistoryFileCancel = threadDelay 1000000 - alice ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}" + alice ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}]" alice <# "#team hey bob" alice <# "/f #team ./tests/tmp/testfile_alice" alice <## "use /fc 2 to cancel sending" diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index 5562d517ac..f097edbc0c 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -17,6 +17,7 @@ chatLocalChatsTests :: SpecWith FilePath chatLocalChatsTests = do describe "note folders" $ do it "create folders, add notes, read, search" testNotes + it "create multiple messages api" testCreateMulti it "switch users" testUserNotes it "preview pagination for notes" testPreviewsPagination it "chat pagination" testChatPagination @@ -52,6 +53,14 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/tail *" alice <# "* Greetings." +testCreateMulti :: FilePath -> IO () +testCreateMulti tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do + createCCNoteFolder alice + + alice ##> "/_create *1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "* test 1" + alice <# "* test 2" + testUserNotes :: FilePath -> IO () testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do createCCNoteFolder alice @@ -120,7 +129,7 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do let source = "./tests/fixtures/test.jpg" let stored = files "test.jpg" copyFile source stored - alice ##> "/_create *1 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"hi myself\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_create *1 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"hi myself\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "* hi myself" alice <# "* file 1 (test.jpg)" @@ -141,7 +150,7 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do -- one more file let stored2 = files "another_test.jpg" copyFile source stored2 - alice ##> "/_create *1 json {\"filePath\": \"another_test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_create *1 json [{\"filePath\": \"another_test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "* file 2 (another_test.jpg)" alice ##> "/_delete item *1 2 internal" @@ -173,8 +182,8 @@ testOtherFiles = bob ##> "/fr 1" bob <### [ "saving file 1 from alice to test.jpg", - "started receiving file 1 (test.jpg) from alice" - ] + "started receiving file 1 (test.jpg) from alice" + ] bob <## "completed receiving file 1 (test.jpg) from alice" bob /* "test" diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 43ad5ba841..878546ba21 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1721,7 +1721,7 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $ let startFeatures = [(0, e2eeInfoPQStr), (0, "Disappearing messages: allowed"), (0, "Full deletion: off"), (0, "Message reactions: enabled"), (0, "Voice messages: off"), (0, "Audio/video calls: enabled")] alice #$> ("/_get chat @2 count=100", chat, startFeatures) bob #$> ("/_get chat @2 count=100", chat, startFeatures) - let sendVoice = "/_send @2 json {\"filePath\": \"test.txt\", \"msgContent\": {\"type\": \"voice\", \"text\": \"\", \"duration\": 10}}" + let sendVoice = "/_send @2 json [{\"filePath\": \"test.txt\", \"msgContent\": {\"type\": \"voice\", \"text\": \"\", \"duration\": 10}}]" voiceNotAllowed = "bad chat command: feature not allowed Voice messages" alice ##> sendVoice alice <## voiceNotAllowed @@ -2227,7 +2227,7 @@ testGroupPrefsSimplexLinksForRole = testChat3 aliceProfile bobProfile cathProfil inv <- getInvitation bob bob ##> ("#team \"" <> inv <> "\\ntest\"") bob <## "bad chat command: feature not allowed SimpleX links" - bob ##> ("/_send #1 json {\"msgContent\": {\"type\": \"text\", \"text\": \"" <> inv <> "\\ntest\"}}") + bob ##> ("/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"" <> inv <> "\\ntest\"}}]") bob <## "bad chat command: feature not allowed SimpleX links" (alice [SndMessage] -> [ChatError] -> [ByteString] -> IO () runBatcherTest' maxLen msgs expectedErrors expectedBatches = do - let (errors, batches) = partitionEithers $ batchMessages maxLen msgs + let (errors, batches) = partitionEithers $ batchMessages maxLen (map Right msgs) batchedStrs = map (\(MsgBatch batchBody _) -> batchBody) batches testErrors errors `shouldBe` testErrors expectedErrors batchedStrs `shouldBe` expectedBatches diff --git a/tests/RemoteTests.hs b/tests/RemoteTests.hs index 3f1bad613a..e51a938252 100644 --- a/tests/RemoteTests.hs +++ b/tests/RemoteTests.hs @@ -238,7 +238,7 @@ remoteStoreFileTest = desktop ##> "/get remote file 1 {\"userId\": 1, \"fileId\": 1, \"sent\": true, \"fileSource\": {\"filePath\": \"test_1.pdf\"}}" hostError desktop "SEFileNotFound" -- send file not encrypted locally on mobile host - desktop ##> "/_send @2 json {\"filePath\": \"test_1.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"sending a file\"}}" + desktop ##> "/_send @2 json [{\"filePath\": \"test_1.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"sending a file\"}}]" desktop <# "@bob sending a file" desktop <# "/f @bob test_1.pdf" desktop <## "use /fc 1 to cancel sending" @@ -268,7 +268,7 @@ remoteStoreFileTest = B.readFile (desktopHostStore "test_1.pdf") `shouldReturn` src -- send file encrypted locally on mobile host - desktop ##> ("/_send @2 json {\"fileSource\": {\"filePath\":\"test_2.pdf\", \"cryptoArgs\": " <> LB.unpack (J.encode cfArgs) <> "}, \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}") + desktop ##> ("/_send @2 json [{\"fileSource\": {\"filePath\":\"test_2.pdf\", \"cryptoArgs\": " <> LB.unpack (J.encode cfArgs) <> "}, \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}]") desktop <# "/f @bob test_2.pdf" desktop <## "use /fc 2 to cancel sending" bob <# "alice> sends file test_2.pdf (266.0 KiB / 272376 bytes)" From f587179045bbead74b87e10eaadfe116bef7a466 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 22 Aug 2024 21:38:22 +0400 Subject: [PATCH 039/440] ios: multi send & forward api (#4739) --- apps/ios/Shared/Model/SimpleXAPI.swift | 76 +++++++++++-------- .../Chat/ComposeMessage/ComposeView.swift | 34 +++++---- apps/ios/Shared/Views/TerminalView.swift | 2 +- .../ios/SimpleX NSE/NotificationService.swift | 25 +++--- apps/ios/SimpleX SE/ShareAPI.swift | 28 ++++--- apps/ios/SimpleX SE/ShareModel.swift | 26 ++++--- apps/ios/SimpleXChat/APITypes.swift | 46 ++++++----- 7 files changed, 134 insertions(+), 103 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index e4b5410392..a292f3d7a2 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -357,17 +357,17 @@ func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws - throw r } -func apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64, ttl: Int?) async -> ChatItem? { - let cmd: ChatCommand = .apiForwardChatItem(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemId: itemId, ttl: ttl) +func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? { + let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemIds: itemIds, ttl: ttl) return await processSendMessageCmd(toChatType: toChatType, cmd: cmd) } -func apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool = false, ttl: Int? = nil) async -> ChatItem? { - let cmd: ChatCommand = .apiSendMessage(type: type, id: id, file: file, quotedItemId: quotedItemId, msg: msg, live: live, ttl: ttl) +func apiSendMessages(type: ChatType, id: Int64, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? { + let cmd: ChatCommand = .apiSendMessages(type: type, id: id, live: live, ttl: ttl, composedMessages: composedMessages) return await processSendMessageCmd(toChatType: type, cmd: cmd) } -private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async -> ChatItem? { +private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async -> [ChatItem]? { let chatModel = ChatModel.shared let r: ChatResponse if toChatType == .direct { @@ -380,10 +380,13 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async } }) r = await chatSendCmd(cmd, bgTask: false) - if case let .newChatItem(_, aChatItem) = r { - cItem = aChatItem.chatItem - chatModel.messageDelivery[aChatItem.chatItem.id] = endTask - return cItem + if case let .newChatItems(_, aChatItems) = r { + let cItems = aChatItems.map { $0.chatItem } + if let cItemLast = cItems.last { + cItem = cItemLast + chatModel.messageDelivery[cItemLast.id] = endTask + } + return cItems } if let networkErrorAlert = networkErrorAlert(r) { AlertManager.shared.showAlert(networkErrorAlert) @@ -394,18 +397,18 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async return nil } else { r = await chatSendCmd(cmd, bgDelay: msgDelay) - if case let .newChatItem(_, aChatItem) = r { - return aChatItem.chatItem + if case let .newChatItems(_, aChatItems) = r { + return aChatItems.map { $0.chatItem } } sendMessageErrorAlert(r) return nil } } -func apiCreateChatItem(noteFolderId: Int64, file: CryptoFile?, msg: MsgContent) async -> ChatItem? { - let r = await chatSendCmd(.apiCreateChatItem(noteFolderId: noteFolderId, file: file, msg: msg)) - if case let .newChatItem(_, aChatItem) = r { return aChatItem.chatItem } - createChatItemErrorAlert(r) +func apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) async -> [ChatItem]? { + let r = await chatSendCmd(.apiCreateChatItems(noteFolderId: noteFolderId, composedMessages: composedMessages)) + if case let .newChatItems(_, aChatItems) = r { return aChatItems.map { $0.chatItem } } + createChatItemsErrorAlert(r) return nil } @@ -417,8 +420,8 @@ private func sendMessageErrorAlert(_ r: ChatResponse) { ) } -private func createChatItemErrorAlert(_ r: ChatResponse) { - logger.error("apiCreateChatItem error: \(String(describing: r))") +private func createChatItemsErrorAlert(_ r: ChatResponse) { + logger.error("apiCreateChatItems error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error creating message", message: "Error: \(responseError(r))" @@ -1782,23 +1785,25 @@ func processReceivedMsg(_ res: ChatResponse) async { n.networkStatuses = ns } } - case let .newChatItem(user, aChatItem): - let cInfo = aChatItem.chatInfo - let cItem = aChatItem.chatItem - await MainActor.run { - if active(user) { - m.addChatItem(cInfo, cItem) - } else if cItem.isRcvNew && cInfo.ntfsEnabled { - m.increaseUnreadCounter(user: user) + case let .newChatItems(user, chatItems): + for chatItem in chatItems { + let cInfo = chatItem.chatInfo + let cItem = chatItem.chatItem + await MainActor.run { + if active(user) { + m.addChatItem(cInfo, cItem) + } else if cItem.isRcvNew && cInfo.ntfsEnabled { + m.increaseUnreadCounter(user: user) + } } - } - if let file = cItem.autoReceiveFile() { - Task { - await receiveFile(user: user, fileId: file.fileId, auto: true) + if let file = cItem.autoReceiveFile() { + Task { + await receiveFile(user: user, fileId: file.fileId, auto: true) + } + } + if cItem.showNotification { + NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) } - } - if cItem.showNotification { - NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) } case let .chatItemStatusUpdated(user, aChatItem): let cInfo = aChatItem.chatInfo @@ -1808,10 +1813,15 @@ func processReceivedMsg(_ res: ChatResponse) async { } if let endTask = m.messageDelivery[cItem.id] { switch cItem.meta.itemStatus { + case .sndNew: () case .sndSent: endTask() + case .sndRcvd: endTask() case .sndErrorAuth: endTask() case .sndError: endTask() - default: () + case .sndWarning: endTask() + case .rcvNew: () + case .rcvRead: () + case .invalid: () } } case let .chatItemUpdated(user, aChatItem): diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 78cae78cf5..99ab778a0e 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -751,6 +751,7 @@ struct ComposeView: View { case .linkPreview: sent = await send(checkLinkPreview(), quoted: quoted, live: live, ttl: ttl) case let .mediaPreviews(mediaPreviews: media): + // TODO batch send: batch media previews let last = media.count - 1 if last >= 0 { for i in 0.. ChatItem? { - if let chatItem = chat.chatInfo.chatType == .local - ? await apiCreateChatItem(noteFolderId: chat.chatInfo.apiId, file: file, msg: mc) - : await apiSendMessage( + if let chatItems = chat.chatInfo.chatType == .local + ? await apiCreateChatItems( + noteFolderId: chat.chatInfo.apiId, + composedMessages: [ComposedMessage(fileSource: file, msgContent: mc)] + ) + : await apiSendMessages( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, - file: file, - quotedItemId: quoted, - msg: mc, live: live, - ttl: ttl + ttl: ttl, + composedMessages: [ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc)] ) { await MainActor.run { chatModel.removeLiveDummy(animated: false) - chatModel.addChatItem(chat.chatInfo, chatItem) + for chatItem in chatItems { + chatModel.addChatItem(chat.chatInfo, chatItem) + } } - return chatItem + // UI only supports sending one item at a time + return chatItems.first } if let file = file { removeFile(file.filePath) @@ -911,18 +916,21 @@ struct ComposeView: View { } func forwardItem(_ forwardedItem: ChatItem, _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> ChatItem? { - if let chatItem = await apiForwardChatItem( + if let chatItems = await apiForwardChatItems( toChatType: chat.chatInfo.chatType, toChatId: chat.chatInfo.apiId, fromChatType: fromChatInfo.chatType, fromChatId: fromChatInfo.apiId, - itemId: forwardedItem.id, + itemIds: [forwardedItem.id], ttl: ttl ) { await MainActor.run { - chatModel.addChatItem(chat.chatInfo, chatItem) + for chatItem in chatItems { + chatModel.addChatItem(chat.chatInfo, chatItem) + } } - return chatItem + // TODO batch send: forward multiple messages + return chatItems.first } return nil } diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index d209ced128..36c05ed43d 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -160,7 +160,7 @@ struct TerminalView_Previews: PreviewProvider { let chatModel = ChatModel() chatModel.terminalItems = [ .resp(.now, ChatResponse.response(type: "contactSubscribed", json: "{}")), - .resp(.now, ChatResponse.response(type: "newChatItem", json: "{}")) + .resp(.now, ChatResponse.response(type: "newChatItems", json: "{}")) ] return NavigationView { TerminalView() diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 1a2a27ba9b..7f1ad18ec2 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -571,17 +571,22 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { // TODO profile update case let .receivedContactRequest(user, contactRequest): return (UserContact(contactRequest: contactRequest).id, .nse(createContactRequestNtf(user, contactRequest))) - case let .newChatItem(user, aChatItem): - let cInfo = aChatItem.chatInfo - var cItem = aChatItem.chatItem - if !cInfo.ntfsEnabled { - ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1)) + case let .newChatItems(user, chatItems): + // Received items are created one at a time + if let chatItem = chatItems.first { + let cInfo = chatItem.chatInfo + var cItem = chatItem.chatItem + if !cInfo.ntfsEnabled { + ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1)) + } + if let file = cItem.autoReceiveFile() { + cItem = autoReceiveFile(file) ?? cItem + } + let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty + return cItem.showNotification ? (chatItem.chatId, ntf) : nil + } else { + return nil } - if let file = cItem.autoReceiveFile() { - cItem = autoReceiveFile(file) ?? cItem - } - let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty - return cItem.showNotification ? (aChatItem.chatId, ntf) : nil case let .rcvFileSndCancelled(_, aChatItem, _): cleanupFile(aChatItem) return nil diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index 47e072ae78..fcb78c64b1 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -54,32 +54,30 @@ func apiGetChats(userId: User.ID) throws -> Array { throw r } -func apiSendMessage( +func apiSendMessages( chatInfo: ChatInfo, - cryptoFile: CryptoFile?, - msgContent: MsgContent -) throws -> AChatItem { + composedMessages: [ComposedMessage] +) throws -> [AChatItem] { let r = sendSimpleXCmd( chatInfo.chatType == .local - ? .apiCreateChatItem( + ? .apiCreateChatItems( noteFolderId: chatInfo.apiId, - file: cryptoFile, - msg: msgContent + composedMessages: composedMessages ) - : .apiSendMessage( + : .apiSendMessages( type: chatInfo.chatType, id: chatInfo.apiId, - file: cryptoFile, - quotedItemId: nil, - msg: msgContent, live: false, - ttl: nil + ttl: nil, + composedMessages: composedMessages ) ) - if case let .newChatItem(_, chatItem) = r { - return chatItem + if case let .newChatItems(_, chatItems) = r { + return chatItems } else { - if let filePath = cryptoFile?.filePath { removeFile(filePath) } + for composedMessage in composedMessages { + if let filePath = composedMessage.fileSource?.filePath { removeFile(filePath) } + } throw r } } diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index 5bda361126..f43548f676 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -141,23 +141,25 @@ class ShareModel: ObservableObject { do { SEChatState.shared.set(.sendingMessage) await waitForOtherProcessesToSuspend() - let ci = try apiSendMessage( + let chatItems = try apiSendMessages( chatInfo: selected.chatInfo, - cryptoFile: sharedContent.cryptoFile, - msgContent: sharedContent.msgContent(comment: self.comment) + composedMessages: [ComposedMessage(fileSource: sharedContent.cryptoFile, msgContent: sharedContent.msgContent(comment: self.comment))] ) if selected.chatInfo.chatType == .local { completion() } else { - await MainActor.run { self.bottomBar = .loadingBar(progress: 0) } - if let e = await handleEvents( - isGroupChat: ci.chatInfo.chatType == .group, - isWithoutFile: sharedContent.cryptoFile == nil, - chatItemId: ci.chatItem.id - ) { - await MainActor.run { errorAlert = e } - } else { - completion() + // TODO batch send: share multiple items + if let ci = chatItems.first { + await MainActor.run { self.bottomBar = .loadingBar(progress: 0) } + if let e = await handleEvents( + isGroupChat: ci.chatInfo.chatType == .group, + isWithoutFile: sharedContent.cryptoFile == nil, + chatItemId: ci.chatItem.id + ) { + await MainActor.run { errorAlert = e } + } else { + completion() + } } } } catch { diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3dc9138774..68f569053e 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -42,13 +42,13 @@ public enum ChatCommand { case apiGetChats(userId: Int64) case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String) case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) - case apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool, ttl: Int?) - case apiCreateChatItem(noteFolderId: Int64, file: CryptoFile?, msg: MsgContent) + case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) + case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool) case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) - case apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64, ttl: Int?) + case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) case apiGetNtfToken case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) case apiVerifyToken(token: DeviceToken, nonce: String, code: String) @@ -191,20 +191,20 @@ public enum ChatCommand { case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)") case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)" - case let .apiSendMessage(type, id, file, quotedItemId, mc, live, ttl): - let msg = encodeJSON(ComposedMessage(fileSource: file, quotedItemId: quotedItemId, msgContent: mc)) + case let .apiSendMessages(type, id, live, ttl, composedMessages): + let msgs = encodeJSON(composedMessages) let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msg)" - case let .apiCreateChatItem(noteFolderId, file, mc): - let msg = encodeJSON(ComposedMessage(fileSource: file, msgContent: mc)) - return "/_create *\(noteFolderId) json \(msg)" + return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" + case let .apiCreateChatItems(noteFolderId, composedMessages): + let msgs = encodeJSON(composedMessages) + return "/_create *\(noteFolderId) json \(msgs)" case let .apiUpdateChatItem(type, id, itemId, mc, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(mc.cmdString)" case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" - case let .apiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId, ttl): + case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemId) ttl=\(ttlStr)" + return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" case .apiGetNtfToken: return "/_ntf get " case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" @@ -349,14 +349,14 @@ public enum ChatCommand { case .apiGetChats: return "apiGetChats" case .apiGetChat: return "apiGetChat" case .apiGetChatItemInfo: return "apiGetChatItemInfo" - case .apiSendMessage: return "apiSendMessage" - case .apiCreateChatItem: return "apiCreateChatItem" + case .apiSendMessages: return "apiSendMessages" + case .apiCreateChatItems: return "apiCreateChatItems" case .apiUpdateChatItem: return "apiUpdateChatItem" case .apiDeleteChatItem: return "apiDeleteChatItem" case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" case .apiChatItemReaction: return "apiChatItemReaction" - case .apiForwardChatItem: return "apiForwardChatItem" + case .apiForwardChatItems: return "apiForwardChatItems" case .apiGetNtfToken: return "apiGetNtfToken" case .apiRegisterToken: return "apiRegisterToken" case .apiVerifyToken: return "apiVerifyToken" @@ -592,7 +592,7 @@ public enum ChatResponse: Decodable, Error { case memberSubErrors(user: UserRef, memberSubErrors: [MemberSubError]) case groupEmpty(user: UserRef, groupInfo: GroupInfo) case userContactLinkSubscribed - case newChatItem(user: UserRef, chatItem: AChatItem) + case newChatItems(user: UserRef, chatItems: [AChatItem]) case chatItemStatusUpdated(user: UserRef, chatItem: AChatItem) case chatItemUpdated(user: UserRef, chatItem: AChatItem) case chatItemNotChanged(user: UserRef, chatItem: AChatItem) @@ -763,7 +763,7 @@ public enum ChatResponse: Decodable, Error { case .memberSubErrors: return "memberSubErrors" case .groupEmpty: return "groupEmpty" case .userContactLinkSubscribed: return "userContactLinkSubscribed" - case .newChatItem: return "newChatItem" + case .newChatItems: return "newChatItems" case .chatItemStatusUpdated: return "chatItemStatusUpdated" case .chatItemUpdated: return "chatItemUpdated" case .chatItemNotChanged: return "chatItemNotChanged" @@ -932,7 +932,9 @@ public enum ChatResponse: Decodable, Error { case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors)) case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo)) case .userContactLinkSubscribed: return noDetails - case let .newChatItem(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) @@ -1119,10 +1121,16 @@ public enum ChatPagination: Hashable { } } -struct ComposedMessage: Encodable { - var fileSource: CryptoFile? +public struct ComposedMessage: Encodable { + public var fileSource: CryptoFile? var quotedItemId: Int64? var msgContent: MsgContent + + public init(fileSource: CryptoFile? = nil, quotedItemId: Int64? = nil, msgContent: MsgContent) { + self.fileSource = fileSource + self.quotedItemId = quotedItemId + self.msgContent = msgContent + } } public struct ArchiveConfig: Encodable { From ef1897f865bd006dfd05fb201b8c5b410f83a6ae Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 22 Aug 2024 21:39:13 +0400 Subject: [PATCH 040/440] multiplatform: multi send & forward api (#4745) --- .../chat/simplex/common/model/SimpleXAPI.kt | 91 ++++++++++--------- .../simplex/common/views/chat/ComposeView.kt | 46 ++++++---- 2 files changed, 74 insertions(+), 63 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index fe568b5144..8dc9b55dd1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -846,15 +846,15 @@ object ChatController { return null } - suspend fun apiSendMessage(rh: Long?, type: ChatType, id: Long, file: CryptoFile? = null, quotedItemId: Long? = null, mc: MsgContent, live: Boolean = false, ttl: Int? = null): AChatItem? { - val cmd = CC.ApiSendMessage(type, id, file, quotedItemId, mc, live, ttl) + suspend fun apiSendMessages(rh: Long?, type: ChatType, id: Long, live: Boolean = false, ttl: Int? = null, composedMessages: List): List? { + val cmd = CC.ApiSendMessages(type, id, live, ttl, composedMessages) return processSendMessageCmd(rh, cmd) } - private suspend fun processSendMessageCmd(rh: Long?, cmd: CC): AChatItem? { + private suspend fun processSendMessageCmd(rh: Long?, cmd: CC): List? { val r = sendCmd(rh, cmd) return when (r) { - is CR.NewChatItem -> r.chatItem + is CR.NewChatItems -> r.chatItems else -> { if (!(networkErrorAlert(r))) { apiErrorAlert("processSendMessageCmd", generalGetString(MR.strings.error_sending_message), r) @@ -863,13 +863,13 @@ object ChatController { } } } - suspend fun apiCreateChatItem(rh: Long?, noteFolderId: Long, file: CryptoFile? = null, mc: MsgContent): AChatItem? { - val cmd = CC.ApiCreateChatItem(noteFolderId, file, mc) + suspend fun apiCreateChatItems(rh: Long?, noteFolderId: Long, composedMessages: List): List? { + val cmd = CC.ApiCreateChatItems(noteFolderId, composedMessages) val r = sendCmd(rh, cmd) return when (r) { - is CR.NewChatItem -> r.chatItem + is CR.NewChatItems -> r.chatItems else -> { - apiErrorAlert("apiCreateChatItem", generalGetString(MR.strings.error_creating_message), r) + apiErrorAlert("apiCreateChatItems", generalGetString(MR.strings.error_creating_message), r) null } } @@ -885,9 +885,9 @@ object ChatController { } } - suspend fun apiForwardChatItem(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemId: Long, ttl: Int?): ChatItem? { - val cmd = CC.ApiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId, ttl) - return processSendMessageCmd(rh, cmd)?.chatItem + suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemIds: List, ttl: Int?): List? { + val cmd = CC.ApiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl) + return processSendMessageCmd(rh, cmd)?.map { it.chatItem } } @@ -2132,27 +2132,30 @@ object ChatController { chatModel.networkStatuses[s.agentConnId] = s.networkStatus } } - is CR.NewChatItem -> withBGApi { - val cInfo = r.chatItem.chatInfo - val cItem = r.chatItem.chatItem - if (active(r.user)) { - withChats { - addChatItem(rhId, cInfo, cItem) + is CR.NewChatItems -> withBGApi { + r.chatItems.forEach { chatItem -> + val cInfo = chatItem.chatInfo + val cItem = chatItem.chatItem + if (active(r.user)) { + withChats { + addChatItem(rhId, cInfo, cItem) + } + } else if (cItem.isRcvNew && cInfo.ntfsEnabled) { + chatModel.increaseUnreadCounter(rhId, r.user) } - } else if (cItem.isRcvNew && cInfo.ntfsEnabled) { - chatModel.increaseUnreadCounter(rhId, r.user) - } - val file = cItem.file - val mc = cItem.content.msgContent - if (file != null && + val file = cItem.file + val mc = cItem.content.msgContent + if (file != null && appPrefs.privacyAcceptImages.get() && ((mc is MsgContent.MCImage && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV) || (mc is MsgContent.MCVideo && file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV) - || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted))) { - receiveFile(rhId, r.user, file.fileId, auto = true) - } - if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId() != rhId)) { - ntfManager.notifyMessageReceived(r.user, cInfo, cItem) + || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted)) + ) { + receiveFile(rhId, r.user, file.fileId, auto = true) + } + if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId() != rhId)) { + ntfManager.notifyMessageReceived(r.user, cInfo, cItem) + } } } is CR.ChatItemStatusUpdated -> { @@ -2863,13 +2866,13 @@ sealed class CC { class ApiGetChats(val userId: Long): CC() class ApiGetChat(val type: ChatType, val id: Long, val pagination: ChatPagination, val search: String = ""): CC() class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC() - class ApiSendMessage(val type: ChatType, val id: Long, val file: CryptoFile?, val quotedItemId: Long?, val mc: MsgContent, val live: Boolean, val ttl: Int?): CC() - class ApiCreateChatItem(val noteFolderId: Long, val file: CryptoFile?, val mc: MsgContent): CC() + class ApiSendMessages(val type: ChatType, val id: Long, val live: Boolean, val ttl: Int?, val composedMessages: List): CC() + class ApiCreateChatItems(val noteFolderId: Long, val composedMessages: List): CC() class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val mc: MsgContent, val live: Boolean): CC() class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List, val mode: CIDeleteMode): CC() class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List): CC() class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC() - class ApiForwardChatItem(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemId: Long, val ttl: Int?): CC() + class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List, val ttl: Int?): CC() class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC() class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC() class ApiJoinGroup(val groupId: Long): CC() @@ -3008,20 +3011,22 @@ sealed class CC { is ApiGetChats -> "/_get chats $userId pcc=on" is ApiGetChat -> "/_get chat ${chatRef(type, id)} ${pagination.cmdString}" + (if (search == "") "" else " search=$search") is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id)} $itemId" - is ApiSendMessage -> { + is ApiSendMessages -> { + val msgs = json.encodeToString(composedMessages) val ttlStr = if (ttl != null) "$ttl" else "default" - "/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json ${json.encodeToString(ComposedMessage(file, quotedItemId, mc))}" + "/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json $msgs" } - is ApiCreateChatItem -> { - "/_create *$noteFolderId json ${json.encodeToString(ComposedMessage(file, null, mc))}" + is ApiCreateChatItems -> { + val msgs = json.encodeToString(composedMessages) + "/_create *$noteFolderId json $msgs" } is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${mc.cmdString}" is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} ${itemIds.joinToString(",")} ${mode.deleteMode}" is ApiDeleteMemberChatItem -> "/_delete member item #$groupId ${itemIds.joinToString(",")}" is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}" - is ApiForwardChatItem -> { + is ApiForwardChatItems -> { val ttlStr = if (ttl != null) "$ttl" else "default" - "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} $itemId ttl=${ttlStr}" + "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}" } is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}" is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}" @@ -3158,13 +3163,13 @@ sealed class CC { is ApiGetChats -> "apiGetChats" is ApiGetChat -> "apiGetChat" is ApiGetChatItemInfo -> "apiGetChatItemInfo" - is ApiSendMessage -> "apiSendMessage" - is ApiCreateChatItem -> "apiCreateChatItem" + is ApiSendMessages -> "apiSendMessages" + is ApiCreateChatItems -> "apiCreateChatItems" is ApiUpdateChatItem -> "apiUpdateChatItem" is ApiDeleteChatItem -> "apiDeleteChatItem" is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem" is ApiChatItemReaction -> "apiChatItemReaction" - is ApiForwardChatItem -> "apiForwardChatItem" + is ApiForwardChatItems -> "apiForwardChatItems" is ApiNewGroup -> "apiNewGroup" is ApiAddMember -> "apiAddMember" is ApiJoinGroup -> "apiJoinGroup" @@ -4790,7 +4795,7 @@ sealed class CR { @Serializable @SerialName("memberSubErrors") class MemberSubErrors(val user: UserRef, val memberSubErrors: List): CR() @Serializable @SerialName("groupEmpty") class GroupEmpty(val user: UserRef, val group: GroupInfo): CR() @Serializable @SerialName("userContactLinkSubscribed") class UserContactLinkSubscribed: CR() - @Serializable @SerialName("newChatItem") class NewChatItem(val user: UserRef, val chatItem: AChatItem): CR() + @Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemStatusUpdated") class ChatItemStatusUpdated(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR() @@ -4966,7 +4971,7 @@ sealed class CR { is MemberSubErrors -> "memberSubErrors" is GroupEmpty -> "groupEmpty" is UserContactLinkSubscribed -> "userContactLinkSubscribed" - is NewChatItem -> "newChatItem" + is NewChatItems -> "newChatItems" is ChatItemStatusUpdated -> "chatItemStatusUpdated" is ChatItemUpdated -> "chatItemUpdated" is ChatItemNotChanged -> "chatItemNotChanged" @@ -5134,7 +5139,7 @@ sealed class CR { is MemberSubErrors -> withUser(user, json.encodeToString(memberSubErrors)) is GroupEmpty -> withUser(user, json.encodeToString(group)) is UserContactLinkSubscribed -> noDetails() - is NewChatItem -> withUser(user, json.encodeToString(chatItem)) + is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemStatusUpdated -> withUser(user, json.encodeToString(chatItem)) is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 372de02b41..821a449509 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -380,24 +380,28 @@ fun ComposeView( suspend fun send(chat: Chat, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? { val cInfo = chat.chatInfo - val aChatItem = if (chat.chatInfo.chatType == ChatType.Local) - chatModel.controller.apiCreateChatItem(rh = chat.remoteHostId, noteFolderId = chat.chatInfo.apiId, file = file, mc = mc) + val chatItems = if (chat.chatInfo.chatType == ChatType.Local) + chatModel.controller.apiCreateChatItems( + rh = chat.remoteHostId, + noteFolderId = chat.chatInfo.apiId, + composedMessages = listOf(ComposedMessage(file, null, mc)) + ) else - chatModel.controller.apiSendMessage( - rh = chat.remoteHostId, - type = cInfo.chatType, - id = cInfo.apiId, - file = file, - quotedItemId = quoted, - mc = mc, - live = live, - ttl = ttl - ) - if (aChatItem != null) { - withChats { - addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem) + chatModel.controller.apiSendMessages( + rh = chat.remoteHostId, + type = cInfo.chatType, + id = cInfo.apiId, + live = live, + ttl = ttl, + composedMessages = listOf(ComposedMessage(file, quoted, mc)) + ) + if (!chatItems.isNullOrEmpty()) { + chatItems.forEach { aChatItem -> + withChats { + addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem) + } } - return aChatItem.chatItem + return chatItems.first().chatItem } if (file != null) removeFile(file.filePath) return null @@ -414,21 +418,22 @@ fun ComposeView( } suspend fun forwardItem(rhId: Long?, forwardedItem: ChatItem, fromChatInfo: ChatInfo, ttl: Int?): ChatItem? { - val chatItem = controller.apiForwardChatItem( + val chatItems = controller.apiForwardChatItems( rh = rhId, toChatType = chat.chatInfo.chatType, toChatId = chat.chatInfo.apiId, fromChatType = fromChatInfo.chatType, fromChatId = fromChatInfo.apiId, - itemId = forwardedItem.id, + itemIds = listOf(forwardedItem.id), ttl = ttl ) - if (chatItem != null) { + chatItems?.forEach { chatItem -> withChats { addChatItem(rhId, chat.chatInfo, chatItem) } } - return chatItem + // TODO batch send: forward multiple messages + return chatItems?.firstOrNull() } fun checkLinkPreview(): MsgContent { @@ -519,6 +524,7 @@ fun ComposeView( ComposePreview.NoPreview -> msgs.add(MsgContent.MCText(msgText)) is ComposePreview.CLinkPreview -> msgs.add(checkLinkPreview()) is ComposePreview.MediaPreview -> { + // TODO batch send: batch media previews preview.content.forEachIndexed { index, it -> val file = when (it) { is UploadContent.SimpleImage -> From 7b90e01b3a332b9ff6fb0f382ced69fe069198ac Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 23 Aug 2024 13:18:51 +0100 Subject: [PATCH 041/440] core: 6.1.0.0 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.yaml b/package.yaml index df23a563a2..3899ff4b68 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.2.0 +version: 6.1.0.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7acf07c497..72ece2f2d6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.0.2.0 +version: 6.1.0.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 276baf56e8..527b87c010 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -72,11 +72,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 0, 0, 4] +minRemoteCtrlVersion = AppVersion [6, 1, 0, 0] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 0, 0, 4] +minRemoteHostVersion = AppVersion [6, 1, 0, 0] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 04033fc0b5757d9da6618586f9c98dd378103909 Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 23 Aug 2024 13:20:07 +0100 Subject: [PATCH 042/440] ios: connection profile search, incognito info in selection list and improved loader (#4744) * remove comment * improve switching chat profile loader * add search on profile selection * disable auto correction * add incognito info in select chat profile * fix typos * layout * fix choosing hidden user * opacity back * Revert "layout" This reverts commit 10f1e5e9249237ac269d70dec5df79a97da98312. * remove padding * selected profile on top (profile or incognito) --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Evgeny Poberezkin --- .../Shared/Views/NewChat/NewChatView.swift | 154 ++++++++++++------ .../Views/UserSettings/IncognitoHelp.swift | 1 + .../Views/UserSettings/UserProfilesView.swift | 7 + 3 files changed, 109 insertions(+), 53 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 9a52bfaa20..ca300d64cc 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -242,7 +242,6 @@ private struct InviteView: View { @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false @State private var showSettings: Bool = false - @State private var showIncognitoSheet = false var body: some View { List { @@ -282,9 +281,6 @@ private struct InviteView: View { } } } - .sheet(isPresented: $showIncognitoSheet) { - IncognitoHelp() - } .onChange(of: incognitoDefault) { incognito in setInvitationUsed() } @@ -344,16 +340,21 @@ private struct ActiveProfilePicker: View { @State private var switchingProfileByTimeout = false @State private var lastSwitchingProfileByTimeoutCall: Double? @State private var profiles: [User] = [] + @State private var searchTextOrPassword = "" + @State private var showIncognitoSheet = false + @State private var incognitoFirst: Bool = false @State var selectedProfile: User + var trimmedSearchTextOrPassword: String { searchTextOrPassword.trimmingCharacters(in: .whitespaces)} var body: some View { viewBody() .navigationTitle("Select chat profile") + .searchable(text: $searchTextOrPassword, placement: .navigationBarDrawer(displayMode: .always)) + .autocorrectionDisabled(true) .navigationBarTitleDisplayMode(.large) .onAppear { profiles = chatModel.users .map { $0.user } - .filter({ u in u.activeUser || !u.hidden }) .sorted { u, _ in u.activeUser } } .onChange(of: incognitoEnabled) { incognito in @@ -413,7 +414,7 @@ private struct ActiveProfilePicker: View { chatModel.updateContactConnection(conn) } do { - try await changeActiveUserAsync_(profile.userId, viewPwd: nil) + try await changeActiveUserAsync_(profile.userId, viewPwd: profile.hidden ? trimmedSearchTextOrPassword : nil ) await MainActor.run { switchingProfile = false dismiss() @@ -424,7 +425,7 @@ private struct ActiveProfilePicker: View { alert = SomeAlert( alert: Alert( title: Text("Error switching profile"), - message: Text("Your connection was moved to \(profile.chatViewName) but and unexpected error ocurred while redirecting you to the profile.") + message: Text("Your connection was moved to \(profile.chatViewName) but an unexpected error occurred while redirecting you to the profile.") ), id: "switchingProfileError" ) @@ -433,7 +434,6 @@ private struct ActiveProfilePicker: View { } } catch { await MainActor.run { - // TODO: discuss error handling switchingProfile = false if let currentUser = chatModel.currentUser { selectedProfile = currentUser @@ -454,71 +454,119 @@ private struct ActiveProfilePicker: View { a.alert } .onAppear { + incognitoFirst = incognitoEnabled choosingProfile = true } .onDisappear { choosingProfile = false } + .sheet(isPresented: $showIncognitoSheet) { + IncognitoHelp() + } } @ViewBuilder private func viewBody() -> some View { - NavigationView { - if switchingProfileByTimeout { - ProgressView("Switching profile…") + profilePicker() + .allowsHitTesting(!switchingProfileByTimeout) + .modifier(ThemedBackground(grouped: true)) + .overlay { + if switchingProfileByTimeout { + ProgressView() + .scaleEffect(2) .frame(maxWidth: .infinity, maxHeight: .infinity) - .modifier(ThemedBackground(grouped: true)) - } else { - profilePicker() - .modifier(ThemedBackground(grouped: true)) + } + } + } + + private func filteredProfiles() -> [User] { + let s = trimmedSearchTextOrPassword + let lower = s.localizedLowercase + + return profiles.filter { u in + if (u.activeUser || !u.hidden) && (s == "" || u.chatViewName.localizedLowercase.contains(lower)) { + return true + } + return correctPassword(u, s) + } + } + + @ViewBuilder private func profilerPickerUserOption(_ user: User) -> some View { + Button { + if selectedProfile != user || incognitoEnabled { + switchingProfile = true + incognitoEnabled = false + selectedProfile = user + } + } label: { + HStack { + ProfileImage(imageStr: user.image, size: 30) + .padding(.trailing, 2) + Text(user.chatViewName) + .foregroundColor(theme.colors.onBackground) + .lineLimit(1) + Spacer() + if selectedProfile == user, !incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) + } } } } @ViewBuilder private func profilePicker() -> some View { - List { - Button { - if !incognitoEnabled { - incognitoEnabled = true - switchingProfile = true - } - } label : { - HStack { - incognitoProfileImage() - Text("Incognito") - .foregroundColor(theme.colors.onBackground) - Spacer() - if incognitoEnabled { - Image(systemName: "checkmark") - .resizable().scaledToFit().frame(width: 16) - .foregroundColor(theme.colors.primary) - } - } + let incognitoOption = Button { + if !incognitoEnabled { + incognitoEnabled = true + switchingProfile = true } - ForEach(profiles) { item in - Button { - if selectedProfile != item || incognitoEnabled { - switchingProfile = true - incognitoEnabled = false - selectedProfile = item - } - } label: { - HStack { - ProfileImage(imageStr: item.image, size: 30) - .padding(.trailing, 2) - Text(item.chatViewName) - .foregroundColor(theme.colors.onBackground) - .lineLimit(1) - Spacer() - if selectedProfile == item, !incognitoEnabled { - Image(systemName: "checkmark") - .resizable().scaledToFit().frame(width: 16) - .foregroundColor(theme.colors.primary) - } + } label : { + HStack { + incognitoProfileImage() + Text("Incognito") + .foregroundColor(theme.colors.onBackground) + Image(systemName: "info.circle") + .foregroundColor(theme.colors.primary) + .font(.system(size: 14)) + .onTapGesture { + showIncognitoSheet = true } + Spacer() + if incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) } } } + + List { + let filteredProfiles = filteredProfiles() + let activeProfile = filteredProfiles.first { u in u.activeUser } + + if let selectedProfile = activeProfile { + let otherProfiles = filteredProfiles.filter { u in u.userId != activeProfile?.userId } + + if incognitoFirst { + incognitoOption + profilerPickerUserOption(selectedProfile) + } else { + profilerPickerUserOption(selectedProfile) + incognitoOption + } + + ForEach(otherProfiles) { p in + profilerPickerUserOption(p) + } + } else { + incognitoOption + ForEach(filteredProfiles) { p in + profilerPickerUserOption(p) + } + } + } + .opacity(switchingProfileByTimeout ? 0.4 : 1) } } diff --git a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift index a0250afddf..d9862aaac8 100644 --- a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift @@ -26,6 +26,7 @@ struct IncognitoHelp: View { Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).") } .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 160130bccc..06342db529 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -406,6 +406,13 @@ public func chatPasswordHash(_ pwd: String, _ salt: String) -> String { return hash } +public func correctPassword(_ user: User, _ pwd: String) -> Bool { + if let ph = user.viewPwdHash { + return pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash + } + return false +} + struct UserProfilesView_Previews: PreviewProvider { static var previews: some View { UserProfilesView(showSettings: Binding.constant(true)) From 7b48c59f9f72637d32015e21cb1eeb53212da644 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 23 Aug 2024 14:32:16 +0100 Subject: [PATCH 043/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b59b1aeab3..4e46d80a5d 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E51ED5762C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */; }; - E51ED5772C7691A2009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5722C7691A2009F2C7C /* libgmp.a */; }; - E51ED5782C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */; }; - E51ED5792C7691A2009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5742C7691A2009F2C7C /* libgmpxx.a */; }; - E51ED57A2C7691A2009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5752C7691A2009F2C7C /* libffi.a */; }; + E51ED5802C78BF95009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57B2C78BF95009F2C7C /* libgmpxx.a */; }; + E51ED5812C78BF95009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57C2C78BF95009F2C7C /* libgmp.a */; }; + E51ED5822C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */; }; + E51ED5832C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */; }; + E51ED5842C78BF95009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57F2C78BF95009F2C7C /* libffi.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -550,11 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a"; sourceTree = ""; }; - E51ED5722C7691A2009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a"; sourceTree = ""; }; - E51ED5742C7691A2009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5752C7691A2009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51ED57B2C78BF95009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E51ED57C2C78BF95009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a"; sourceTree = ""; }; + E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a"; sourceTree = ""; }; + E51ED57F2C78BF95009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E51ED5802C78BF95009F2C7C /* libgmpxx.a in Frameworks */, + E51ED5812C78BF95009F2C7C /* libgmp.a in Frameworks */, + E51ED5822C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a in Frameworks */, + E51ED5842C78BF95009F2C7C /* libffi.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E51ED5772C7691A2009F2C7C /* libgmp.a in Frameworks */, - E51ED5762C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a in Frameworks */, - E51ED57A2C7691A2009F2C7C /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E51ED5782C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a in Frameworks */, - E51ED5792C7691A2009F2C7C /* libgmpxx.a in Frameworks */, + E51ED5832C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5752C7691A2009F2C7C /* libffi.a */, - E51ED5722C7691A2009F2C7C /* libgmp.a */, - E51ED5742C7691A2009F2C7C /* libgmpxx.a */, - E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */, - E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */, + E51ED57F2C78BF95009F2C7C /* libffi.a */, + E51ED57C2C78BF95009F2C7C /* libgmp.a */, + E51ED57B2C78BF95009F2C7C /* libgmpxx.a */, + E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */, + E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */, ); path = Libraries; sourceTree = ""; From bcd50019be034ac62824cbe2200302cf4b11a0e8 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:05:37 +0400 Subject: [PATCH 044/440] core: add more multi send api tests (#4750) --- src/Simplex/Chat.hs | 4 + src/Simplex/Chat/Messages.hs | 3 + src/Simplex/Chat/View.hs | 15 ++- tests/ChatTests/Direct.hs | 123 +++++++++++++++++++++-- tests/ChatTests/Files.hs | 163 ++++++++++++++++++++++++++++++ tests/ChatTests/Forward.hs | 187 +++++++++++++++++++++++++++++++++++ tests/ChatTests/Groups.hs | 105 +++++++++++++++++--- tests/ChatTests/Local.hs | 45 +++++++-- 8 files changed, 611 insertions(+), 34 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 54a7f1ef0b..dc3b4b2e54 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2906,6 +2906,7 @@ processChatCommand' vr = \case (msgContainers, quotedItems_) <- L.unzip <$> prepareMsgs (L.zip cmrs fInvs_) timed_ msgs_ <- sendDirectContactMessages user ct $ L.map XMsgNew msgContainers let itemsData = prepareSndItemsData msgs_ cmrs ciFiles_ quotedItems_ + when (length itemsData /= length cmrs) $ logError "sendContactContentMessages: cmrs and itemsData length mismatch" (errs, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live unless (null errs) $ toView $ CRChatErrors (Just user) errs forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> @@ -2969,6 +2970,7 @@ processChatCommand' vr = \case (msgs_, gsr) <- sendGroupMessages user gInfo ms $ L.map XMsgNew msgContainers let itemsData = prepareSndItemsData (L.toList msgs_) cmrs ciFiles_ quotedItems_ cis_ <- saveSndChatItems user (CDGroupSnd gInfo) itemsData timed_ live + when (length itemsData /= length cmrs) $ logError "sendGroupContentMessages: cmrs and cis_ length mismatch" createMemberSndStatuses cis_ msgs_ gsr let (errs, cis) = partitionEithers cis_ unless (null errs) $ toView $ CRChatErrors (Just user) errs @@ -7038,6 +7040,7 @@ batchSendConnMessagesB _user conn msgFlags msgs_ = do delivered <- deliverMessagesB msgReqs let msgs' = concat $ L.zipWith flattenMsgs batched' delivered pqEnc = findLastPQEnc delivered + when (length msgs' /= length msgs_) $ logError "batchSendConnMessagesB: msgs_ and msgs' length mismatch" pure (msgs', pqEnc) Nothing -> pure ([], Nothing) where @@ -7190,6 +7193,7 @@ sendGroupMessages_ _user gInfo@GroupInfo {groupId} members events = do -- Save as pending for toPending members let (pendingMemIds, pendingReqs) = preparePending sndMsgs_ toPending stored <- lift $ withStoreBatch (\db -> map (bindRight $ createPendingMsg db) pendingReqs) + when (length stored /= length pendingMemIds) $ logError "sendGroupMessages_: pendingMemIds and stored length mismatch" -- Zip for easier access to results let sentTo = zipWith3 (\mId mReq r -> (mId, fmap (\(_, _, _, msgIds) -> msgIds) mReq, r)) sendToMemIds msgReqs delivered pending = zipWith3 (\mId pReq r -> (mId, fmap snd pReq, r)) pendingMemIds pendingReqs stored diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 78e3b4c640..f6c59cbcb5 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -336,6 +336,9 @@ aChatItemId (AChatItem _ _ _ ci) = chatItemId' ci aChatItemTs :: AChatItem -> UTCTime aChatItemTs (AChatItem _ _ _ ci) = chatItemTs' ci +aChatItemDir :: AChatItem -> MsgDirection +aChatItemDir (AChatItem _ sMsgDir _ _) = toMsgDirection sMsgDir + updateFileStatus :: forall c d. ChatItem c d -> CIFileStatus d -> ChatItem c d updateFileStatus ci@ChatItem {file} status = case file of Just f -> ci {file = Just (f :: CIFile d) {fileStatus = status}} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index dd2bf94467..2158599c4b 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} @@ -120,10 +121,16 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRConnectionVerified u verified code -> ttyUser u [plain $ if verified then "connection verified" else "connection not verified, current code is " <> code] CRContactCode u ct code -> ttyUser u $ viewContactCode ct code testView CRGroupMemberCode u g m code -> ttyUser u $ viewGroupMemberCode g m code testView - CRNewChatItems u chatItems -> - concatMap - (\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item) - chatItems + CRNewChatItems u chatItems + | length chatItems > 20 -> + if + | all (\aci -> aChatItemDir aci == MDRcv) chatItems -> ttyUser u [sShow (length chatItems) <> " new messages"] + | all (\aci -> aChatItemDir aci == MDSnd) chatItems -> ttyUser u [sShow (length chatItems) <> " messages sent"] + | otherwise -> ttyUser u [sShow (length chatItems) <> " new messages created"] + | otherwise -> + concatMap + (\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item) + chatItems CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId] diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 86c51f6aaa..9980b3b723 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -17,6 +17,7 @@ import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB import Data.List (intercalate) import qualified Data.Text as T +import Database.SQLite.Simple (Only (..)) import Simplex.Chat.AppSettings (defaultAppSettings) import qualified Simplex.Chat.AppSettings as AS import Simplex.Chat.Call @@ -25,6 +26,7 @@ import Simplex.Chat.Options (ChatOpts (..)) import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, pattern VersionChat) +import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Version @@ -52,7 +54,11 @@ chatDirectTests = do it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact it "should send multiline message" testMultilineMessage it "send large message" testLargeMessage + describe "batch send messages" $ do it "send multiple messages api" testSendMulti + it "send multiple timed messages" testSendMultiTimed + it "send multiple messages, including quote" testSendMultiWithQuote + it "send multiple messages (many chat batches)" testSendMultiManyBatches describe "duplicate contacts" $ do it "duplicate contacts are separate (contacts don't merge)" testDuplicateContactsSeparate it "new contact is separate with multiple duplicate contacts (contacts don't merge)" testDuplicateContactsMultipleSeparate @@ -716,22 +722,27 @@ testDirectMessageDeleteMultipleManyBatches = \alice bob -> do connectUsers alice bob - alice #> "@bob message 0" - bob <# "alice> message 0" - msgIdFirst <- lastItemId alice + msgIdZero <- lastItemId alice - forM_ [(1 :: Int) .. 300] $ \i -> do - alice #> ("@bob message " <> show i) - bob <# ("alice> message " <> show i) + let cm i = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message " <> show i <> "\"}}" + cms = intercalate ", " (map cm [1 .. 300 :: Int]) + + alice `send` ("/_send @2 json [" <> cms <> "]") + _ <- getTermLine alice + + alice <## "300 messages sent" msgIdLast <- lastItemId alice - let mIdFirst = read msgIdFirst :: Int + forM_ [(1 :: Int) .. 300] $ \i -> do + bob <# ("alice> message " <> show i) + + let mIdFirst = (read msgIdZero :: Int) + 1 mIdLast = read msgIdLast :: Int deleteIds = intercalate "," (map show [mIdFirst .. mIdLast]) alice `send` ("/_delete item @2 " <> deleteIds <> " broadcast") _ <- getTermLine alice - alice <## "301 messages deleted" - forM_ [(0 :: Int) .. 300] $ \i -> do + alice <## "300 messages deleted" + forM_ [(1 :: Int) .. 300] $ \i -> do bob <# ("alice> [marked deleted] message " <> show i) testDirectLiveMessage :: HasCallStack => FilePath -> IO () @@ -852,6 +863,100 @@ testSendMulti = bob <# "alice> test 1" bob <# "alice> test 2" +testSendMultiTimed :: HasCallStack => FilePath -> IO () +testSendMultiTimed = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + alice ##> "/_send @2 ttl=1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "@bob test 1" + alice <# "@bob test 2" + bob <# "alice> test 1" + bob <# "alice> test 2" + + alice + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + bob + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + +testSendMultiWithQuote :: HasCallStack => FilePath -> IO () +testSendMultiWithQuote = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + alice #> "@bob hello" + bob <# "alice> hello" + msgId1 <- lastItemId alice + + threadDelay 1000000 + + bob #> "@alice hi" + alice <# "bob> hi" + msgId2 <- lastItemId alice + + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message 1\"}}" + cm2 = "{\"quotedItemId\": " <> msgId1 <> ", \"msgContent\": {\"type\": \"text\", \"text\": \"message 2\"}}" + cm3 = "{\"quotedItemId\": " <> msgId2 <> ", \"msgContent\": {\"type\": \"text\", \"text\": \"message 3\"}}" + + alice ##> ("/_send @2 json [" <> cm1 <> ", " <> cm2 <> ", " <> cm3 <> "]") + alice <## "bad chat command: invalid multi send: live and more than one quote not supported" + + alice ##> ("/_send @2 json [" <> cm1 <> ", " <> cm2 <> "]") + + alice <# "@bob message 1" + alice <# "@bob >> hello" + alice <## " message 2" + + bob <# "alice> message 1" + bob <# "alice> >> hello" + bob <## " message 2" + + alice ##> ("/_send @2 json [" <> cm3 <> ", " <> cm1 <> "]") + + alice <# "@bob > hi" + alice <## " message 3" + alice <# "@bob message 1" + + bob <# "alice> > hi" + bob <## " message 3" + bob <# "alice> message 1" + +testSendMultiManyBatches :: HasCallStack => FilePath -> IO () +testSendMultiManyBatches = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + threadDelay 1000000 + + msgIdAlice <- lastItemId alice + msgIdBob <- lastItemId bob + + let cm i = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message " <> show i <> "\"}}" + cms = intercalate ", " (map cm [1 .. 300 :: Int]) + + alice `send` ("/_send @2 json [" <> cms <> "]") + _ <- getTermLine alice + + alice <## "300 messages sent" + + forM_ [(1 :: Int) .. 300] $ \i -> + bob <# ("alice> message " <> show i) + + aliceItemsCount <- withCCTransaction alice $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdAlice) :: IO [[Int]] + aliceItemsCount `shouldBe` [[300]] + + bobItemsCount <- withCCTransaction bob $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdBob) :: IO [[Int]] + bobItemsCount `shouldBe` [[300]] + testGetSetSMPServers :: HasCallStack => FilePath -> IO () testGetSetSMPServers = testChat2 aliceProfile bobProfile $ diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 32f3c02c29..c97193186b 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -36,6 +36,9 @@ chatFileTests = do it "send and receive image with text and quote" testSendImageWithTextAndQuote it "send and receive image to group" testGroupSendImage it "send and receive image with text and quote to group" testGroupSendImageWithTextAndQuote + describe "batch send messages with files" $ do + it "with files folder: send multiple files to contact" testSendMultiFilesDirect + it "with files folder: send multiple files to group" testSendMultiFilesGroup describe "file transfer over XFTP" $ do it "round file description count" $ const testXFTPRoundFDCount it "send and receive file" testXFTPFileTransfer @@ -406,6 +409,166 @@ testGroupSendImageWithTextAndQuote = cath #$> ("/_get chat #1 count=2", chat'', [((0, "hi team"), Nothing, Nothing), ((0, "hey bob"), Just (0, "hi team"), Just "./tests/tmp/test_1.jpg")]) cath @@@ [("#team", "hey bob"), ("@alice", "received invitation to join group team as admin")] +testSendMultiFilesDirect :: HasCallStack => FilePath -> IO () +testSendMultiFilesDirect = + testChat2 aliceProfile bobProfile $ \alice bob -> do + withXFTPServer $ do + connectUsers alice bob + + alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + bob #$> ("/_files_folder ./tests/tmp/bob_app_files", id, "ok") + + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + + alice <# "@bob message without file" + + alice <# "@bob sending file 1" + alice <# "/f @bob test.jpg" + alice <## "use /fc 1 to cancel sending" + + alice <# "@bob sending file 2" + alice <# "/f @bob test.pdf" + alice <## "use /fc 2 to cancel sending" + + bob <# "alice> message without file" + + bob <# "alice> sending file 1" + bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 1 [
    / | ] to receive it" + + bob <# "alice> sending file 2" + bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + bob <## "use /fr 2 [/ | ] to receive it" + + alice <## "completed uploading file 1 (test.jpg) for bob" + alice <## "completed uploading file 2 (test.pdf) for bob" + + bob ##> "/fr 1" + bob + <### [ "saving file 1 from alice to test.jpg", + "started receiving file 1 (test.jpg) from alice" + ] + bob <## "completed receiving file 1 (test.jpg) from alice" + + bob ##> "/fr 2" + bob + <### [ "saving file 2 from alice to test.pdf", + "started receiving file 2 (test.pdf) from alice" + ] + bob <## "completed receiving file 2 (test.pdf) from alice" + + src1 <- B.readFile "./tests/tmp/alice_app_files/test.jpg" + dest1 <- B.readFile "./tests/tmp/bob_app_files/test.jpg" + dest1 `shouldBe` src1 + + src2 <- B.readFile "./tests/tmp/alice_app_files/test.pdf" + dest2 <- B.readFile "./tests/tmp/bob_app_files/test.pdf" + dest2 `shouldBe` src2 + + alice #$> ("/_get chat @2 count=3", chatF, [((1, "message without file"), Nothing), ((1, "sending file 1"), Just "test.jpg"), ((1, "sending file 2"), Just "test.pdf")]) + bob #$> ("/_get chat @2 count=3", chatF, [((0, "message without file"), Nothing), ((0, "sending file 1"), Just "test.jpg"), ((0, "sending file 2"), Just "test.pdf")]) + +testSendMultiFilesGroup :: HasCallStack => FilePath -> IO () +testSendMultiFilesGroup = + testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do + withXFTPServer $ do + createGroup3 "team" alice bob cath + + threadDelay 1000000 + + alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + bob #$> ("/_files_folder ./tests/tmp/bob_app_files", id, "ok") + cath #$> ("/_files_folder ./tests/tmp/cath_app_files", id, "ok") + + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" + alice ##> ("/_send #1 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + + alice <# "#team message without file" + + alice <# "#team sending file 1" + alice <# "/f #team test.jpg" + alice <## "use /fc 1 to cancel sending" + + alice <# "#team sending file 2" + alice <# "/f #team test.pdf" + alice <## "use /fc 2 to cancel sending" + + bob <# "#team alice> message without file" + + bob <# "#team alice> sending file 1" + bob <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 1 [/ | ] to receive it" + + bob <# "#team alice> sending file 2" + bob <# "#team alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + bob <## "use /fr 2 [/ | ] to receive it" + + cath <# "#team alice> message without file" + + cath <# "#team alice> sending file 1" + cath <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + cath <## "use /fr 1 [/ | ] to receive it" + + cath <# "#team alice> sending file 2" + cath <# "#team alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + cath <## "use /fr 2 [/ | ] to receive it" + + alice <## "completed uploading file 1 (test.jpg) for #team" + alice <## "completed uploading file 2 (test.pdf) for #team" + + bob ##> "/fr 1" + bob + <### [ "saving file 1 from alice to test.jpg", + "started receiving file 1 (test.jpg) from alice" + ] + bob <## "completed receiving file 1 (test.jpg) from alice" + + bob ##> "/fr 2" + bob + <### [ "saving file 2 from alice to test.pdf", + "started receiving file 2 (test.pdf) from alice" + ] + bob <## "completed receiving file 2 (test.pdf) from alice" + + cath ##> "/fr 1" + cath + <### [ "saving file 1 from alice to test.jpg", + "started receiving file 1 (test.jpg) from alice" + ] + cath <## "completed receiving file 1 (test.jpg) from alice" + + cath ##> "/fr 2" + cath + <### [ "saving file 2 from alice to test.pdf", + "started receiving file 2 (test.pdf) from alice" + ] + cath <## "completed receiving file 2 (test.pdf) from alice" + + src1 <- B.readFile "./tests/tmp/alice_app_files/test.jpg" + dest1_1 <- B.readFile "./tests/tmp/bob_app_files/test.jpg" + dest1_2 <- B.readFile "./tests/tmp/cath_app_files/test.jpg" + dest1_1 `shouldBe` src1 + dest1_2 `shouldBe` src1 + + src2 <- B.readFile "./tests/tmp/alice_app_files/test.pdf" + dest2_1 <- B.readFile "./tests/tmp/bob_app_files/test.pdf" + dest2_2 <- B.readFile "./tests/tmp/cath_app_files/test.pdf" + dest2_1 `shouldBe` src2 + dest2_2 `shouldBe` src2 + + alice #$> ("/_get chat #1 count=3", chatF, [((1, "message without file"), Nothing), ((1, "sending file 1"), Just "test.jpg"), ((1, "sending file 2"), Just "test.pdf")]) + bob #$> ("/_get chat #1 count=3", chatF, [((0, "message without file"), Nothing), ((0, "sending file 1"), Just "test.jpg"), ((0, "sending file 2"), Just "test.pdf")]) + cath #$> ("/_get chat #1 count=3", chatF, [((0, "message without file"), Nothing), ((0, "sending file 1"), Just "test.jpg"), ((0, "sending file 2"), Just "test.pdf")]) + testXFTPRoundFDCount :: Expectation testXFTPRoundFDCount = do roundedFDCount (-100) `shouldBe` 4 diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index 221a2426b1..b339053abf 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -35,6 +35,8 @@ chatForwardTests = do it "with relative paths: from notes to group" testForwardFileNotesToGroup describe "multi forward api" $ do it "from contact to contact" testForwardContactToContactMulti + it "from group to group" testForwardGroupToGroupMulti + it "with relative paths: multiple files from contact to contact" testMultiForwardFiles testForwardContactToContact :: HasCallStack => FilePath -> IO () testForwardContactToContact = @@ -620,3 +622,188 @@ testForwardContactToContactMulti = cath <## " hi" cath <# "alice> -> forwarded" cath <## " hey" + +testForwardGroupToGroupMulti :: HasCallStack => FilePath -> IO () +testForwardGroupToGroupMulti = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup2 "team" alice bob + createGroup2 "club" alice cath + + threadDelay 1000000 + + alice #> "#team hi" + bob <# "#team alice> hi" + msgId1 <- lastItemId alice + + threadDelay 1000000 + + bob #> "#team hey" + alice <# "#team bob> hey" + msgId2 <- lastItemId alice + + alice ##> ("/_forward #2 #1 " <> msgId1 <> "," <> msgId2) + alice <# "#club <- you #team" + alice <## " hi" + alice <# "#club <- #team" + alice <## " hey" + cath <# "#club alice> -> forwarded" + cath <## " hi" + cath <# "#club alice> -> forwarded" + cath <## " hey" + + -- read chat + alice ##> "/tail #club 2" + alice <# "#club <- you #team" + alice <## " hi" + alice <# "#club <- #team" + alice <## " hey" + + cath ##> "/tail #club 2" + cath <# "#club alice> -> forwarded" + cath <## " hi" + cath <# "#club alice> -> forwarded" + cath <## " hey" + +testMultiForwardFiles :: HasCallStack => FilePath -> IO () +testMultiForwardFiles = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> withXFTPServer $ do + setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp" + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" + setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" + connectUsers alice bob + connectUsers bob cath + + threadDelay 1000000 + + msgIdZero <- lastItemId bob + + bob #> "@alice hi" + alice <# "bob> hi" + + -- send original files + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + + alice <# "@bob message without file" + + alice <# "@bob sending file 1" + alice <# "/f @bob test.jpg" + alice <## "use /fc 1 to cancel sending" + + alice <# "@bob sending file 2" + alice <# "/f @bob test.pdf" + alice <## "use /fc 2 to cancel sending" + + bob <# "alice> message without file" + + bob <# "alice> sending file 1" + bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 1 [/ | ] to receive it" + + bob <# "alice> sending file 2" + bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + bob <## "use /fr 2 [/ | ] to receive it" + + alice <## "completed uploading file 1 (test.jpg) for bob" + alice <## "completed uploading file 2 (test.pdf) for bob" + + bob ##> "/fr 1" + bob + <### [ "saving file 1 from alice to test.jpg", + "started receiving file 1 (test.jpg) from alice" + ] + bob <## "completed receiving file 1 (test.jpg) from alice" + + bob ##> "/fr 2" + bob + <### [ "saving file 2 from alice to test.pdf", + "started receiving file 2 (test.pdf) from alice" + ] + bob <## "completed receiving file 2 (test.pdf) from alice" + + src1 <- B.readFile "./tests/tmp/alice_app_files/test.jpg" + dest1 <- B.readFile "./tests/tmp/bob_app_files/test.jpg" + dest1 `shouldBe` src1 + + src2 <- B.readFile "./tests/tmp/alice_app_files/test.pdf" + dest2 <- B.readFile "./tests/tmp/bob_app_files/test.pdf" + dest2 `shouldBe` src2 + + -- forward file + let msgId1 = (read msgIdZero :: Int) + 1 + bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3)) + + -- messages printed for bob + bob <# "@cath <- you @alice" + bob <## " hi" + + bob <# "@cath <- @alice" + bob <## " message without file" + + bob <# "@cath <- @alice" + bob <## " sending file 1" + bob <# "/f @cath test_1.jpg" + bob <## "use /fc 3 to cancel sending" + + bob <# "@cath <- @alice" + bob <## " sending file 2" + bob <# "/f @cath test_1.pdf" + bob <## "use /fc 4 to cancel sending" + + -- messages printed for cath + cath <# "bob> -> forwarded" + cath <## " hi" + + cath <# "bob> -> forwarded" + cath <## " message without file" + + cath <# "bob> -> forwarded" + cath <## " sending file 1" + cath <# "bob> sends file test_1.jpg (136.5 KiB / 139737 bytes)" + cath <## "use /fr 1 [/ | ] to receive it" + + cath <# "bob> -> forwarded" + cath <## " sending file 2" + cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)" + cath <## "use /fr 2 [/ | ] to receive it" + + -- file transfer + bob <## "completed uploading file 3 (test_1.jpg) for cath" + bob <## "completed uploading file 4 (test_1.pdf) for cath" + + cath ##> "/fr 1" + cath + <### [ "saving file 1 from bob to test_1.jpg", + "started receiving file 1 (test_1.jpg) from bob" + ] + cath <## "completed receiving file 1 (test_1.jpg) from bob" + + cath ##> "/fr 2" + cath + <### [ "saving file 2 from bob to test_1.pdf", + "started receiving file 2 (test_1.pdf) from bob" + ] + cath <## "completed receiving file 2 (test_1.pdf) from bob" + + src1B <- B.readFile "./tests/tmp/bob_app_files/test_1.jpg" + src1B `shouldBe` dest1 + dest1C <- B.readFile "./tests/tmp/cath_app_files/test_1.jpg" + dest1C `shouldBe` src1B + + src2B <- B.readFile "./tests/tmp/bob_app_files/test_1.pdf" + src2B `shouldBe` dest2 + dest2C <- B.readFile "./tests/tmp/cath_app_files/test_1.pdf" + dest2C `shouldBe` src2B + + -- deleting original file doesn't delete forwarded file + checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do + bob ##> "/clear alice" + bob <## "alice: all messages are removed locally ONLY" + fwdFileExists <- doesFileExist "./tests/tmp/bob_app_files/test_1.jpg" + fwdFileExists `shouldBe` True diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 1ff01a911c..d3e65ce5df 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -14,6 +14,7 @@ import Control.Monad (forM_, void, when) import qualified Data.ByteString.Char8 as B import Data.List (intercalate, isInfixOf) import qualified Data.Text as T +import Database.SQLite.Simple (Only (..)) import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Options import Simplex.Chat.Protocol (supportedChatVRange) @@ -64,7 +65,10 @@ chatGroupTests = do it "moderate message of another group member (full delete)" testGroupModerateFullDelete it "moderate message that arrives after the event of moderation" testGroupDelayedModeration it "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete + describe "batch send messages" $ do it "send multiple messages api" testSendMulti + it "send multiple timed messages" testSendMultiTimed + it "send multiple messages (many chat batches)" testSendMultiManyBatches describe "async group connections" $ do xit "create and join group when clients go offline" testGroupAsync describe "group links" $ do @@ -1305,26 +1309,29 @@ testGroupMessageDeleteMultipleManyBatches = cath ##> "/set receipts all off" cath <## "ok" - alice #> "#team message 0" - concurrently_ - (bob <# "#team alice> message 0") - (cath <# "#team alice> message 0") - msgIdFirst <- lastItemId alice + msgIdZero <- lastItemId alice + + let cm i = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message " <> show i <> "\"}}" + cms = intercalate ", " (map cm [1 .. 300 :: Int]) + + alice `send` ("/_send #1 json [" <> cms <> "]") + _ <- getTermLine alice + + alice <## "300 messages sent" forM_ [(1 :: Int) .. 300] $ \i -> do - alice #> ("#team message " <> show i) concurrently_ (bob <# ("#team alice> message " <> show i)) (cath <# ("#team alice> message " <> show i)) msgIdLast <- lastItemId alice - let mIdFirst = read msgIdFirst :: Int + let mIdFirst = (read msgIdZero :: Int) + 1 mIdLast = read msgIdLast :: Int deleteIds = intercalate "," (map show [mIdFirst .. mIdLast]) alice `send` ("/_delete item #1 " <> deleteIds <> " broadcast") _ <- getTermLine alice - alice <## "301 messages deleted" - forM_ [(0 :: Int) .. 300] $ \i -> + alice <## "300 messages deleted" + forM_ [(1 :: Int) .. 300] $ \i -> concurrently_ (bob <# ("#team alice> [marked deleted] message " <> show i)) (cath <# ("#team alice> [marked deleted] message " <> show i)) @@ -1821,15 +1828,89 @@ testGroupDelayedModerationFullDelete tmp = do testSendMulti :: HasCallStack => FilePath -> IO () testSendMulti = - testChat2 aliceProfile bobProfile $ - \alice bob -> do - createGroup2 "team" alice bob + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup3 "team" alice bob cath alice ##> "/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" alice <# "#team test 1" alice <# "#team test 2" bob <# "#team alice> test 1" bob <# "#team alice> test 2" + cath <# "#team alice> test 1" + cath <# "#team alice> test 2" + +testSendMultiTimed :: HasCallStack => FilePath -> IO () +testSendMultiTimed = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup3 "team" alice bob cath + + alice ##> "/set disappear #team on 1" + alice <## "updated group preferences:" + alice <## "Disappearing messages: on (1 sec)" + bob <## "alice updated group #team:" + bob <## "updated group preferences:" + bob <## "Disappearing messages: on (1 sec)" + cath <## "alice updated group #team:" + cath <## "updated group preferences:" + cath <## "Disappearing messages: on (1 sec)" + + alice ##> "/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "#team test 1" + alice <# "#team test 2" + bob <# "#team alice> test 1" + bob <# "#team alice> test 2" + cath <# "#team alice> test 1" + cath <# "#team alice> test 2" + + alice + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + bob + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + cath + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + +testSendMultiManyBatches :: HasCallStack => FilePath -> IO () +testSendMultiManyBatches = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup3 "team" alice bob cath + + msgIdAlice <- lastItemId alice + msgIdBob <- lastItemId bob + msgIdCath <- lastItemId cath + + let cm i = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message " <> show i <> "\"}}" + cms = intercalate ", " (map cm [1 .. 300 :: Int]) + + alice `send` ("/_send #1 json [" <> cms <> "]") + _ <- getTermLine alice + + alice <## "300 messages sent" + + forM_ [(1 :: Int) .. 300] $ \i -> do + concurrently_ + (bob <# ("#team alice> message " <> show i)) + (cath <# ("#team alice> message " <> show i)) + + aliceItemsCount <- withCCTransaction alice $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdAlice) :: IO [[Int]] + aliceItemsCount `shouldBe` [[300]] + + bobItemsCount <- withCCTransaction bob $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdBob) :: IO [[Int]] + bobItemsCount `shouldBe` [[300]] + + cathItemsCount <- withCCTransaction cath $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdCath) :: IO [[Int]] + cathItemsCount `shouldBe` [[300]] testGroupAsync :: HasCallStack => FilePath -> IO () testGroupAsync tmp = do diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index f097edbc0c..da9c043648 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -17,12 +17,14 @@ chatLocalChatsTests :: SpecWith FilePath chatLocalChatsTests = do describe "note folders" $ do it "create folders, add notes, read, search" testNotes - it "create multiple messages api" testCreateMulti it "switch users" testUserNotes it "preview pagination for notes" testPreviewsPagination it "chat pagination" testChatPagination it "stores files" testFiles it "deleting files does not interfere with other chat types" testOtherFiles + describe "batch create messages" $ do + it "create multiple messages api" testCreateMulti + it "create multiple messages with files" testCreateMultiFiles testNotes :: FilePath -> IO () testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do @@ -53,14 +55,6 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/tail *" alice <# "* Greetings." -testCreateMulti :: FilePath -> IO () -testCreateMulti tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do - createCCNoteFolder alice - - alice ##> "/_create *1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" - alice <# "* test 1" - alice <# "* test 2" - testUserNotes :: FilePath -> IO () testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do createCCNoteFolder alice @@ -197,3 +191,36 @@ testOtherFiles = doesFileExist "./tests/tmp/test.jpg" `shouldReturn` True where cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, sendChunks = 100, receiveChunks = 100}} + +testCreateMulti :: FilePath -> IO () +testCreateMulti tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do + createCCNoteFolder alice + + alice ##> "/_create *1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "* test 1" + alice <# "* test 2" + +testCreateMultiFiles :: FilePath -> IO () +testCreateMultiFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do + createCCNoteFolder alice + alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" + alice ##> ("/_create *1 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + + alice <# "* message without file" + alice <# "* sending file 1" + alice <# "* file 1 (test.jpg)" + alice <# "* sending file 2" + alice <# "* file 2 (test.pdf)" + + doesFileExist "./tests/tmp/alice_app_files/test.jpg" `shouldReturn` True + doesFileExist "./tests/tmp/alice_app_files/test.pdf" `shouldReturn` True + + alice ##> "/_get chat *1 count=3" + r <- chatF <$> getTermLine alice + r `shouldBe` [((1, "message without file"), Nothing), ((1, "sending file 1"), Just "test.jpg"), ((1, "sending file 2"), Just "test.pdf")] From efe8ed17393109bcb2e799068455d0cdaeb31892 Mon Sep 17 00:00:00 2001 From: Diogo Date: Sat, 24 Aug 2024 14:59:50 +0100 Subject: [PATCH 045/440] ios: fix possible race between incognito set and profile change in conn profile picker (#4752) * ios: fix possible race between incognito set and profile change in conn profile picker * typo * fix swithcing incognito on same profile --- .../Shared/Views/NewChat/NewChatView.swift | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index ca300d64cc..c8cfc84230 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -327,6 +327,12 @@ private struct InviteView: View { } } +private enum ProfileSwitchStatus { + case switchingUser + case switchingIncognito + case idle +} + private struct ActiveProfilePicker: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var chatModel: ChatModel @@ -336,7 +342,7 @@ private struct ActiveProfilePicker: View { @Binding var incognitoEnabled: Bool @Binding var choosingProfile: Bool @State private var alert: SomeAlert? - @State private var switchingProfile = false + @State private var profileSwitchStatus: ProfileSwitchStatus = .idle @State private var switchingProfileByTimeout = false @State private var lastSwitchingProfileByTimeoutCall: Double? @State private var profiles: [User] = [] @@ -358,7 +364,7 @@ private struct ActiveProfilePicker: View { .sorted { u, _ in u.activeUser } } .onChange(of: incognitoEnabled) { incognito in - if !switchingProfile { + if profileSwitchStatus != .switchingIncognito { return } @@ -369,12 +375,12 @@ private struct ActiveProfilePicker: View { await MainActor.run { contactConnection = conn chatModel.updateContactConnection(conn) - switchingProfile = false + profileSwitchStatus = .idle dismiss() } } } catch { - switchingProfile = false + profileSwitchStatus = .idle incognitoEnabled = !incognito logger.error("apiSetConnectionIncognito error: \(responseError(error))") let err = getErrorAlert(error, "Error changing to incognito!") @@ -389,39 +395,39 @@ private struct ActiveProfilePicker: View { } } } - .onChange(of: switchingProfile) { sp in - if sp { + .onChange(of: profileSwitchStatus) { sp in + if sp != .idle { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - switchingProfileByTimeout = switchingProfile + switchingProfileByTimeout = profileSwitchStatus != .idle } } else { switchingProfileByTimeout = false } } .onChange(of: selectedProfile) { profile in - if (profile == chatModel.currentUser) { + if (profileSwitchStatus != .switchingUser) { return } Task { do { - switchingProfile = true if let contactConn = contactConnection, let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) { await MainActor.run { contactConnection = conn connReqInvitation = conn.connReqInv ?? "" + incognitoEnabled = false chatModel.updateContactConnection(conn) } do { try await changeActiveUserAsync_(profile.userId, viewPwd: profile.hidden ? trimmedSearchTextOrPassword : nil ) await MainActor.run { - switchingProfile = false + profileSwitchStatus = .idle dismiss() } } catch { await MainActor.run { - switchingProfile = false + profileSwitchStatus = .idle alert = SomeAlert( alert: Alert( title: Text("Error switching profile"), @@ -434,7 +440,7 @@ private struct ActiveProfilePicker: View { } } catch { await MainActor.run { - switchingProfile = false + profileSwitchStatus = .idle if let currentUser = chatModel.currentUser { selectedProfile = currentUser } @@ -493,10 +499,12 @@ private struct ActiveProfilePicker: View { @ViewBuilder private func profilerPickerUserOption(_ user: User) -> some View { Button { - if selectedProfile != user || incognitoEnabled { - switchingProfile = true + if selectedProfile == user && incognitoEnabled { incognitoEnabled = false + profileSwitchStatus = .switchingIncognito + } else if selectedProfile != user { selectedProfile = user + profileSwitchStatus = .switchingUser } } label: { HStack { @@ -519,7 +527,7 @@ private struct ActiveProfilePicker: View { let incognitoOption = Button { if !incognitoEnabled { incognitoEnabled = true - switchingProfile = true + profileSwitchStatus = .switchingIncognito } } label : { HStack { From c07df9e05ff3214261c04834e746901e8513b9a2 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 24 Aug 2024 14:00:56 +0000 Subject: [PATCH 046/440] android: target API level 34 (Android 14) (#4697) --- apps/multiplatform/android/build.gradle.kts | 22 +++++----- .../android/src/main/AndroidManifest.xml | 14 +++++- .../main/java/chat/simplex/app/CallService.kt | 44 ++++++++++++++++--- .../java/chat/simplex/app/MainActivity.kt | 2 +- .../main/java/chat/simplex/app/SimplexApp.kt | 5 ++- .../java/chat/simplex/app/SimplexService.kt | 36 ++++++++++++--- .../simplex/app/model/NtfManager.android.kt | 4 +- .../simplex/app/views/call/CallActivity.kt | 10 ++--- .../android/src/main/res/values/colors.xml | 1 - apps/multiplatform/common/build.gradle.kts | 16 +++---- .../res/drawable/edit_text_cursor.xml | 2 +- 11 files changed, 112 insertions(+), 44 deletions(-) diff --git a/apps/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts index 5c2c786a21..250616ea5c 100644 --- a/apps/multiplatform/android/build.gradle.kts +++ b/apps/multiplatform/android/build.gradle.kts @@ -15,7 +15,7 @@ android { namespace = "chat.simplex.app" minSdk = 26 //noinspection OldTargetApi - targetSdk = 33 + targetSdk = 34 // !!! // skip version code after release to F-Droid, as it uses two version codes versionCode = (extra["android.version_code"] as String).toInt() @@ -126,29 +126,29 @@ android { dependencies { implementation(project(":common")) - implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.core:core-ktx:1.13.1") //implementation("androidx.compose.ui:ui:${rootProject.extra["compose.version"] as String}") //implementation("androidx.compose.material:material:$compose_version") //implementation("androidx.compose.ui:ui-tooling-preview:$compose_version") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") - implementation("androidx.lifecycle:lifecycle-process:2.7.0") - implementation("androidx.activity:activity-compose:1.8.2") - val workVersion = "2.9.0" + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4") + implementation("androidx.lifecycle:lifecycle-process:2.8.4") + implementation("androidx.activity:activity-compose:1.9.1") + val workVersion = "2.9.1" implementation("androidx.work:work-runtime-ktx:$workVersion") implementation("androidx.work:work-multiprocess:$workVersion") - implementation("com.jakewharton:process-phoenix:2.2.0") + implementation("com.jakewharton:process-phoenix:3.0.0") //Camera Permission - implementation("com.google.accompanist:accompanist-permissions:0.23.0") + implementation("com.google.accompanist:accompanist-permissions:0.34.0") //implementation("androidx.compose.material:material-icons-extended:$compose_version") //implementation("androidx.compose.ui:ui-util:$compose_version") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") //androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version") debugImplementation("androidx.compose.ui:ui-tooling:1.6.4") } diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index 073f1bf8c8..deb5d83e5f 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -21,6 +21,12 @@ + + + + + + + android:stopWithTask="false" + android:foregroundServiceType="remoteMessaging" + /> @@ -141,7 +149,9 @@ android:name=".CallService" android:enabled="true" android:exported="false" - android:stopWithTask="false"/> + android:stopWithTask="false" + android:foregroundServiceType="mediaPlayback|microphone|camera|remoteMessaging" + /> = 34) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING + } else { + 0 + } + } else if (Build.VERSION.SDK_INT >= 30) { + if (call.supportsVideo()) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA + } else { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE + } + } else if (Build.VERSION.SDK_INT >= 29) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + } else { + 0 + } } private fun createNotificationChannel(): NotificationManager? { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index 5a69d282b4..c63b6cb497 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -54,7 +54,7 @@ class MainActivity: FragmentActivity() { SimplexApp.context.schedulePeriodicWakeUp() } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) processIntent(intent) processExternalIntent(intent) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 9206b5be89..a8b91e261b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -120,7 +120,10 @@ class SimplexApp: Application(), LifecycleEventObserver { * */ if (chatModel.chatRunning.value != false && chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && - appPrefs.notificationsMode.get() == NotificationsMode.SERVICE + appPrefs.notificationsMode.get() == NotificationsMode.SERVICE && + // New installation passes all checks above and tries to start the service which is not needed at all + // because preferred notification type is not yet chosen. So, check that the user has initialized db already + appPrefs.newDatabaseInitialized.get() ) { SimplexService.start() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index a5f5d84ec2..004d2bc7f1 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.* import android.content.* import android.content.pm.PackageManager +import android.content.pm.ServiceInfo import android.net.Uri import android.os.* import android.os.SystemClock @@ -15,8 +16,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat import androidx.core.content.ContextCompat import androidx.work.* +import chat.simplex.app.model.NtfManager import chat.simplex.common.AppLock import chat.simplex.common.helpers.requiresIgnoringBattery import chat.simplex.common.model.ChatController @@ -52,18 +55,15 @@ class SimplexService: Service() { } else { Log.d(TAG, "null intent. Probably restarted by the system.") } - startForeground(SIMPLEX_SERVICE_ID, serviceNotification) + ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType()) return START_STICKY // to restart if killed } override fun onCreate() { super.onCreate() Log.d(TAG, "Simplex service created") - val title = generalGetString(MR.strings.simplex_service_notification_title) - val text = generalGetString(MR.strings.simplex_service_notification_text) - notificationManager = createNotificationChannel() - serviceNotification = createNotification(title, text) - startForeground(SIMPLEX_SERVICE_ID, serviceNotification) + createNotificationIfNeeded() + ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType()) /** * The reason [stopAfterStart] exists is because when the service is not called [startForeground] yet, and * we call [stopSelf] on the same service, [ForegroundServiceDidNotStartInTimeException] will be thrown. @@ -103,6 +103,26 @@ class SimplexService: Service() { super.onDestroy() } + private fun createNotificationIfNeeded(): Notification { + val ntf = serviceNotification + if (ntf != null) return ntf + + val title = generalGetString(MR.strings.simplex_service_notification_title) + val text = generalGetString(MR.strings.simplex_service_notification_text) + notificationManager = createNotificationChannel() + val newNtf = createNotification(title, text) + serviceNotification = newNtf + return newNtf + } + + private fun foregroundServiceType(): Int { + return if (Build.VERSION.SDK_INT >= 34) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING + } else { + 0 + } + } + private fun startService() { Log.d(TAG, "SimplexService startService") if (wakeLock != null || isCheckingNewMessages) return @@ -292,6 +312,10 @@ class SimplexService: Service() { } private suspend fun serviceAction(action: Action) { + if (!NtfManager.areNotificationsEnabledInSystem()) { + Log.d(TAG, "SimplexService serviceAction: ${action.name}. Notifications are not enabled in OS yet, not starting service") + return + } Log.d(TAG, "SimplexService serviceAction: ${action.name}") withContext(Dispatchers.IO) { Intent(androidAppContext, SimplexService::class.java).also { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt index 417a81a953..cf19589d4a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt @@ -53,7 +53,7 @@ object NtfManager { private val msgNtfTimeoutMs = 30000L init { - if (manager.areNotificationsEnabled()) createNtfChannelsMaybeShowAlert() + if (areNotificationsEnabledInSystem()) createNtfChannelsMaybeShowAlert() } private fun callNotificationChannel(channelId: String, channelName: String): NotificationChannel { @@ -287,6 +287,8 @@ object NtfManager { } } + fun areNotificationsEnabledInSystem() = manager.areNotificationsEnabled() + /** * This function creates notifications channels. On Android 13+ calling it for the first time will trigger system alert, * The alert asks a user to allow or disallow to show notifications for the app. That's why it should be called only when the user diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index b78f3ac518..3d29737128 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -120,6 +120,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { return grantedAudio && grantedCamera } + @Deprecated("Was deprecated in OS") override fun onBackPressed() { if (isOnLockScreenNow()) { super.onBackPressed() @@ -139,6 +140,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { } override fun onUserLeaveHint() { + super.onUserLeaveHint() // On Android 12+ PiP is enabled automatically when a user hides the app if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) { enterPictureInPictureMode() @@ -248,6 +250,9 @@ fun CallActivityView() { ) if (permissionsState.allPermissionsGranted) { ActiveCallView() + LaunchedEffect(Unit) { + activity.startServiceAndBind() + } } else { CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) { withBGApi { chatModel.callManager.endCall(call) } @@ -285,11 +290,6 @@ fun CallActivityView() { AlertManager.shared.showInView() } } - LaunchedEffect(call == null) { - if (call != null) { - activity.startServiceAndBind() - } - } LaunchedEffect(invitation, call, switchingCall, showCallView) { if (!switchingCall && invitation == null && (!showCallView || call == null)) { Log.d(TAG, "CallActivityView: finishing activity") diff --git a/apps/multiplatform/android/src/main/res/values/colors.xml b/apps/multiplatform/android/src/main/res/values/colors.xml index e1a994e57f..1833a6d9a3 100644 --- a/apps/multiplatform/android/src/main/res/values/colors.xml +++ b/apps/multiplatform/android/src/main/res/values/colors.xml @@ -2,6 +2,5 @@ #FF000000 #FFFFFFFF - #8b8786 #121212 \ No newline at end of file diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 7e97ea3414..a1afb655e2 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -61,8 +61,8 @@ kotlin { val androidMain by getting { kotlin.srcDir("build/generated/moko/androidMain/src") dependencies { - implementation("androidx.activity:activity-compose:1.8.2") - val workVersion = "2.9.0" + implementation("androidx.activity:activity-compose:1.9.1") + val workVersion = "2.9.1" implementation("androidx.work:work-runtime-ktx:$workVersion") implementation("com.google.accompanist:accompanist-insets:0.30.1") @@ -78,22 +78,22 @@ kotlin { //Camera Permission implementation("com.google.accompanist:accompanist-permissions:0.34.0") - implementation("androidx.webkit:webkit:1.10.0") + implementation("androidx.webkit:webkit:1.11.0") // GIFs support implementation("io.coil-kt:coil-compose:2.6.0") implementation("io.coil-kt:coil-gif:2.6.0") - implementation("com.jakewharton:process-phoenix:2.2.0") + implementation("com.jakewharton:process-phoenix:3.0.0") - val cameraXVersion = "1.3.2" + val cameraXVersion = "1.3.4" implementation("androidx.camera:camera-core:${cameraXVersion}") implementation("androidx.camera:camera-camera2:${cameraXVersion}") implementation("androidx.camera:camera-lifecycle:${cameraXVersion}") implementation("androidx.camera:camera-view:${cameraXVersion}") // Calls lifecycle listener - implementation("androidx.lifecycle:lifecycle-process:2.4.1") + implementation("androidx.lifecycle:lifecycle-process:2.8.4") } } val desktopMain by getting { @@ -119,8 +119,8 @@ android { defaultConfig { minSdk = 26 } - testOptions.targetSdk = 33 - lint.targetSdk = 33 + testOptions.targetSdk = 34 + lint.targetSdk = 34 val isAndroid = gradle.startParameter.taskNames.find { val lower = it.lowercase() lower.contains("release") || lower.startsWith("assemble") || lower.startsWith("install") diff --git a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml index 683c3a4dd4..948ae4d4bf 100644 --- a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml +++ b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml @@ -1,5 +1,5 @@ - + From f0b889ffcf760409faf7bf57c294bd7224a1cc58 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 24 Aug 2024 15:07:32 +0100 Subject: [PATCH 047/440] core: 6.0.3.0 (simplexmq 6.0.2.0) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 9976edb2af..9bf39c2841 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 1d22608f860636f21a5557f1b3fab4a7da09c5cc + tag: 56986f82c89b04beae84a61208db8b55eb0098e3 source-repository-package type: git diff --git a/package.yaml b/package.yaml index df23a563a2..b0732e17ee 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.2.0 +version: 6.0.3.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 6352837e25..0569199515 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."1d22608f860636f21a5557f1b3fab4a7da09c5cc" = "16kmc05avzdyd6kpj83nyqkyjks5kim5j351397f6p3yvm7iydwz"; + "https://github.com/simplex-chat/simplexmq.git"."56986f82c89b04beae84a61208db8b55eb0098e3" = "0vqvdnm560xrfq7kjsghdbpk67vn4hcdpp58dfqgh9l2c9f79bin"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7acf07c497..fed3a884ce 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.0.2.0 +version: 6.0.3.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From ed60f28e56b47d704b8cc16b9cbb47897fcd2bd3 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 24 Aug 2024 15:44:08 +0100 Subject: [PATCH 048/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b59b1aeab3..7ffb169fa4 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E51ED5762C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */; }; - E51ED5772C7691A2009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5722C7691A2009F2C7C /* libgmp.a */; }; - E51ED5782C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */; }; - E51ED5792C7691A2009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5742C7691A2009F2C7C /* libgmpxx.a */; }; - E51ED57A2C7691A2009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5752C7691A2009F2C7C /* libffi.a */; }; + E51ED58A2C7A26FE009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5852C7A26FE009F2C7C /* libffi.a */; }; + E51ED58B2C7A26FE009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5862C7A26FE009F2C7C /* libgmpxx.a */; }; + E51ED58C2C7A26FE009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5872C7A26FE009F2C7C /* libgmp.a */; }; + E51ED58D2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */; }; + E51ED58E2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -550,11 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a"; sourceTree = ""; }; - E51ED5722C7691A2009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a"; sourceTree = ""; }; - E51ED5742C7691A2009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5752C7691A2009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51ED5852C7A26FE009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51ED5862C7A26FE009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E51ED5872C7A26FE009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a"; sourceTree = ""; }; + E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a"; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E51ED58D2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a in Frameworks */, + E51ED58E2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E51ED5772C7691A2009F2C7C /* libgmp.a in Frameworks */, - E51ED5762C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a in Frameworks */, - E51ED57A2C7691A2009F2C7C /* libffi.a in Frameworks */, + E51ED58B2C7A26FE009F2C7C /* libgmpxx.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E51ED5782C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a in Frameworks */, - E51ED5792C7691A2009F2C7C /* libgmpxx.a in Frameworks */, + E51ED58C2C7A26FE009F2C7C /* libgmp.a in Frameworks */, + E51ED58A2C7A26FE009F2C7C /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5752C7691A2009F2C7C /* libffi.a */, - E51ED5722C7691A2009F2C7C /* libgmp.a */, - E51ED5742C7691A2009F2C7C /* libgmpxx.a */, - E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */, - E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */, + E51ED5852C7A26FE009F2C7C /* libffi.a */, + E51ED5872C7A26FE009F2C7C /* libgmp.a */, + E51ED5862C7A26FE009F2C7C /* libgmpxx.a */, + E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */, + E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */, ); path = Libraries; sourceTree = ""; From defd095a4fa5aab0b64f44c8bbc991e40d8c32b2 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 24 Aug 2024 16:32:32 +0100 Subject: [PATCH 049/440] 6.0.3: ios 235, android 235, desktop 64 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 7ffb169fa4..48689d1010 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1879,7 +1879,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1904,7 +1904,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1928,7 +1928,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1953,7 +1953,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1969,11 +1969,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1989,11 +1989,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2014,7 +2014,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2029,7 +2029,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2051,7 +2051,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2066,7 +2066,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2088,7 +2088,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2114,7 +2114,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2139,7 +2139,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2165,7 +2165,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2190,7 +2190,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2205,7 +2205,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2224,7 +2224,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 234; + CURRENT_PROJECT_VERSION = 235; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2239,7 +2239,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.2; + MARKETING_VERSION = 6.0.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 51b6d4ce3b..c2b93f3efe 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.0.2 -android.version_code=234 +android.version_name=6.0.3 +android.version_code=235 -desktop.version_name=6.0.2 -desktop.version_code=63 +desktop.version_name=6.0.3 +desktop.version_code=64 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 4d18174b11b5ff09aef43925c55a74be4b9a54af Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 24 Aug 2024 19:10:30 +0100 Subject: [PATCH 050/440] ui: fix Debug delivery (#4757) --- apps/ios/Shared/Model/SimpleXAPI.swift | 4 ++-- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 11 ++++++++++- .../chat/simplex/common/model/SimpleXAPI.kt | 16 +++++++++++++--- .../simplex/common/views/chat/ChatInfoView.kt | 2 +- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ebc58c6a05..239ef7916e 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -582,13 +582,13 @@ func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (Gro throw r } -func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { +func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } throw r } -func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { +func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } throw r diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index ea3b04c2ff..35adcd49c1 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -942,7 +942,7 @@ func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Al ) } -func queueInfoText(_ info: (RcvMsgInfo?, QueueInfo)) -> String { +func queueInfoText(_ info: (RcvMsgInfo?, ServerQueueInfo)) -> String { let (rcvMsgInfo, qInfo) = info var msgInfo: String if let rcvMsgInfo { msgInfo = encodeJSON(rcvMsgInfo) } else { msgInfo = "none" } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a6409dec2f..a5a475de11 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -537,7 +537,7 @@ public enum ChatResponse: Decodable, Error { case networkConfig(networkConfig: NetCfg) case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?) - case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: QueueInfo) + case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: ServerQueueInfo) case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) @@ -2294,6 +2294,15 @@ public struct RcvMsgInfo: Codable, Hashable { var agentMsgMeta: String } +public struct ServerQueueInfo: Codable, Hashable { + var server: String + var rcvId: String + var sndId: String + var ntfId: String? + var status: String + var info: QueueInfo +} + public struct QueueInfo: Codable, Hashable { var qiSnd: Bool var qiNtf: Bool diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index fe568b5144..983390b09e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1030,14 +1030,14 @@ object ChatController { return null } - suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair? { + suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair? { val r = sendCmd(rh, CC.APIContactQueueInfo(contactId)) if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r) return null } - suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId)) if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r) @@ -4734,7 +4734,7 @@ sealed class CR { @Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR() @Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR() @Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats? = null): CR() - @Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: QueueInfo): CR() + @Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: ServerQueueInfo): CR() @Serializable @SerialName("contactSwitchStarted") class ContactSwitchStarted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("groupMemberSwitchStarted") class GroupMemberSwitchStarted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("contactSwitchAborted") class ContactSwitchAborted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @@ -6409,6 +6409,16 @@ data class RcvMsgInfo ( val agentMsgMeta: String ) +@Serializable +data class ServerQueueInfo ( + val server: String, + val rcvId: String, + val sndId: String, + val ntfId: String? = null, + val status: String, + val info: QueueInfo +) + @Serializable data class QueueInfo ( val qiSnd: Boolean, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 24416ff49e..9149b039ef 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -1268,7 +1268,7 @@ fun showSyncConnectionForceAlert(syncConnectionForce: () -> Unit) { ) } -fun queueInfoText(info: Pair): String { +fun queueInfoText(info: Pair): String { val (rcvMsgInfo, qInfo) = info val msgInfo: String = if (rcvMsgInfo != null) json.encodeToString(rcvMsgInfo) else generalGetString(MR.strings.message_queue_info_none) return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo) From a1579810bb31f9b7bf8ae3a3a0920fcbb7fc75f1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 25 Aug 2024 14:31:26 +0100 Subject: [PATCH 051/440] ios: remove unnecessary protocols (#4763) --- apps/ios/SimpleXChat/APITypes.swift | 78 ++++++++++++++--------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 13661dc74a..d4998762d7 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1102,12 +1102,12 @@ public enum GroupLinkPlan: Decodable, Hashable { case known(groupInfo: GroupInfo) } -struct NewUser: Encodable, Hashable { +struct NewUser: Encodable { var profile: Profile? var pastTimestamp: Bool } -public enum ChatPagination: Hashable { +public enum ChatPagination { case last(count: Int) case after(chatItemId: Int64, count: Int) case before(chatItemId: Int64, count: Int) @@ -1329,7 +1329,7 @@ public struct ServerAddress: Decodable { ) } -public struct NetCfg: Codable, Equatable, Hashable { +public struct NetCfg: Codable, Equatable { public var socksProxy: String? = nil var socksMode: SocksMode = .always public var hostMode: HostMode = .publicHost @@ -1383,18 +1383,18 @@ public struct NetCfg: Codable, Equatable, Hashable { public var enableKeepAlive: Bool { tcpKeepAlive != nil } } -public enum HostMode: String, Codable, Hashable { +public enum HostMode: String, Codable { case onionViaSocks case onionHost = "onion" case publicHost = "public" } -public enum SocksMode: String, Codable, Hashable { +public enum SocksMode: String, Codable { case always = "always" case onion = "onion" } -public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { +public enum SMPProxyMode: String, Codable, SelectableItem { case always = "always" case unknown = "unknown" case unprotected = "unprotected" @@ -1414,7 +1414,7 @@ public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never] } -public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { +public enum SMPProxyFallback: String, Codable, SelectableItem { case allow = "allow" case allowProtected = "allowProtected" case prohibit = "prohibit" @@ -1432,7 +1432,7 @@ public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit] } -public enum OnionHosts: String, Identifiable, Hashable { +public enum OnionHosts: String, Identifiable { case no case prefer case require @@ -1466,7 +1466,7 @@ public enum OnionHosts: String, Identifiable, Hashable { public static let values: [OnionHosts] = [.no, .prefer, .require] } -public enum TransportSessionMode: String, Codable, Identifiable, Hashable { +public enum TransportSessionMode: String, Codable, Identifiable { case user case entity @@ -1482,7 +1482,7 @@ public enum TransportSessionMode: String, Codable, Identifiable, Hashable { public static let values: [TransportSessionMode] = [.user, .entity] } -public struct KeepAliveOpts: Codable, Equatable, Hashable { +public struct KeepAliveOpts: Codable, Equatable { public var keepIdle: Int // seconds public var keepIntvl: Int // seconds public var keepCnt: Int // times @@ -1490,7 +1490,7 @@ public struct KeepAliveOpts: Codable, Equatable, Hashable { public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) } -public enum NetworkStatus: Decodable, Equatable, Hashable { +public enum NetworkStatus: Decodable, Equatable { case unknown case connected case disconnected @@ -1528,7 +1528,7 @@ public enum NetworkStatus: Decodable, Equatable, Hashable { } } -public struct ConnNetworkStatus: Decodable, Hashable { +public struct ConnNetworkStatus: Decodable { public var agentConnId: String public var networkStatus: NetworkStatus } @@ -1553,7 +1553,7 @@ public enum MsgFilter: String, Codable, Hashable { case mentions } -public struct UserMsgReceiptSettings: Codable, Hashable { +public struct UserMsgReceiptSettings: Codable { public var enable: Bool public var clearOverrides: Bool @@ -1602,7 +1602,7 @@ public enum SndSwitchStatus: String, Codable, Hashable { case sendingQTEST = "sending_qtest" } -public enum QueueDirection: String, Decodable, Hashable { +public enum QueueDirection: String, Decodable { case rcv case snd } @@ -1657,12 +1657,12 @@ public struct AutoAccept: Codable, Hashable { } } -public protocol SelectableItem: Hashable, Identifiable { +public protocol SelectableItem: Identifiable, Equatable { var label: LocalizedStringKey { get } static var values: [Self] { get } } -public struct DeviceToken: Decodable, Hashable { +public struct DeviceToken: Decodable { var pushProvider: PushProvider var token: String @@ -1676,12 +1676,12 @@ public struct DeviceToken: Decodable, Hashable { } } -public enum PushEnvironment: String, Hashable { +public enum PushEnvironment: String { case development case production } -public enum PushProvider: String, Decodable, Hashable { +public enum PushProvider: String, Decodable { case apns_dev case apns_prod @@ -1695,7 +1695,7 @@ public enum PushProvider: String, Decodable, Hashable { // This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, // and .local for periodic background checks -public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { +public enum NotificationsMode: String, Decodable, SelectableItem { case off = "OFF" case periodic = "PERIODIC" case instant = "INSTANT" @@ -1713,7 +1713,7 @@ public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { public static var values: [NotificationsMode] = [.instant, .periodic, .off] } -public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { +public enum NotificationPreviewMode: String, SelectableItem, Codable { case hidden case contact case message @@ -1731,7 +1731,7 @@ public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden] } -public struct RemoteCtrlInfo: Decodable, Hashable { +public struct RemoteCtrlInfo: Decodable { public var remoteCtrlId: Int64 public var ctrlDeviceName: String public var sessionState: RemoteCtrlSessionState? @@ -1741,7 +1741,7 @@ public struct RemoteCtrlInfo: Decodable, Hashable { } } -public enum RemoteCtrlSessionState: Decodable, Hashable { +public enum RemoteCtrlSessionState: Decodable { case starting case searching case connecting @@ -1756,17 +1756,17 @@ public enum RemoteCtrlStopReason: Decodable { case disconnected } -public struct CtrlAppInfo: Decodable, Hashable { +public struct CtrlAppInfo: Decodable { public var appVersionRange: AppVersionRange public var deviceName: String } -public struct AppVersionRange: Decodable, Hashable { +public struct AppVersionRange: Decodable { public var minVersion: String public var maxVersion: String } -public struct CoreVersionInfo: Decodable, Hashable { +public struct CoreVersionInfo: Decodable { public var version: String public var simplexmqVersion: String public var simplexmqCommit: String @@ -2104,14 +2104,14 @@ public enum RemoteCtrlError: Decodable, Hashable { case protocolError } -public struct MigrationFileLinkData: Codable, Hashable { +public struct MigrationFileLinkData: Codable { let networkConfig: NetworkConfig? public init(networkConfig: NetworkConfig) { self.networkConfig = networkConfig } - public struct NetworkConfig: Codable, Hashable { + public struct NetworkConfig: Codable { let socksProxy: String? let hostMode: HostMode? let requiredHostMode: Bool? @@ -2143,7 +2143,7 @@ public struct MigrationFileLinkData: Codable, Hashable { } } -public struct AppSettings: Codable, Equatable, Hashable { +public struct AppSettings: Codable, Equatable { public var networkConfig: NetCfg? = nil public var privacyEncryptLocalFiles: Bool? = nil public var privacyAskToApproveRelays: Bool? = nil @@ -2238,7 +2238,7 @@ public struct AppSettings: Codable, Equatable, Hashable { } } -public enum AppSettingsNotificationMode: String, Codable, Hashable { +public enum AppSettingsNotificationMode: String, Codable { case off case periodic case instant @@ -2266,13 +2266,13 @@ public enum AppSettingsNotificationMode: String, Codable, Hashable { // case message //} -public enum AppSettingsLockScreenCalls: String, Codable, Hashable { +public enum AppSettingsLockScreenCalls: String, Codable { case disable case show case accept } -public struct UserNetworkInfo: Codable, Equatable, Hashable { +public struct UserNetworkInfo: Codable, Equatable { public let networkType: UserNetworkType public let online: Bool @@ -2282,7 +2282,7 @@ public struct UserNetworkInfo: Codable, Equatable, Hashable { } } -public enum UserNetworkType: String, Codable, Hashable { +public enum UserNetworkType: String, Codable { case none case cellular case wifi @@ -2300,7 +2300,7 @@ public enum UserNetworkType: String, Codable, Hashable { } } -public struct RcvMsgInfo: Codable, Hashable { +public struct RcvMsgInfo: Codable { var msgId: Int64 var msgDeliveryId: Int64 var msgDeliveryStatus: String @@ -2308,7 +2308,7 @@ public struct RcvMsgInfo: Codable, Hashable { var agentMsgMeta: String } -public struct ServerQueueInfo: Codable, Hashable { +public struct ServerQueueInfo: Codable { var server: String var rcvId: String var sndId: String @@ -2317,7 +2317,7 @@ public struct ServerQueueInfo: Codable, Hashable { var info: QueueInfo } -public struct QueueInfo: Codable, Hashable { +public struct QueueInfo: Codable { var qiSnd: Bool var qiNtf: Bool var qiSub: QSub? @@ -2325,25 +2325,25 @@ public struct QueueInfo: Codable, Hashable { var qiMsg: MsgInfo? } -public struct QSub: Codable, Hashable { +public struct QSub: Codable { var qSubThread: QSubThread var qDelivered: String? } -public enum QSubThread: String, Codable, Hashable { +public enum QSubThread: String, Codable { case noSub case subPending case subThread case prohibitSub } -public struct MsgInfo: Codable, Hashable { +public struct MsgInfo: Codable { var msgId: String var msgTs: Date var msgType: MsgType } -public enum MsgType: String, Codable, Hashable { +public enum MsgType: String, Codable { case message case quota } From 4552860345feef1dbdbde2c4e9fd072567872ed3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 25 Aug 2024 14:31:26 +0100 Subject: [PATCH 052/440] ios: remove unnecessary protocols (#4763) --- apps/ios/SimpleXChat/APITypes.swift | 78 ++++++++++++++--------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a5a475de11..d7fc533e91 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1094,12 +1094,12 @@ public enum GroupLinkPlan: Decodable, Hashable { case known(groupInfo: GroupInfo) } -struct NewUser: Encodable, Hashable { +struct NewUser: Encodable { var profile: Profile? var pastTimestamp: Bool } -public enum ChatPagination: Hashable { +public enum ChatPagination { case last(count: Int) case after(chatItemId: Int64, count: Int) case before(chatItemId: Int64, count: Int) @@ -1315,7 +1315,7 @@ public struct ServerAddress: Decodable { ) } -public struct NetCfg: Codable, Equatable, Hashable { +public struct NetCfg: Codable, Equatable { public var socksProxy: String? = nil var socksMode: SocksMode = .always public var hostMode: HostMode = .publicHost @@ -1369,18 +1369,18 @@ public struct NetCfg: Codable, Equatable, Hashable { public var enableKeepAlive: Bool { tcpKeepAlive != nil } } -public enum HostMode: String, Codable, Hashable { +public enum HostMode: String, Codable { case onionViaSocks case onionHost = "onion" case publicHost = "public" } -public enum SocksMode: String, Codable, Hashable { +public enum SocksMode: String, Codable { case always = "always" case onion = "onion" } -public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { +public enum SMPProxyMode: String, Codable, SelectableItem { case always = "always" case unknown = "unknown" case unprotected = "unprotected" @@ -1400,7 +1400,7 @@ public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never] } -public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { +public enum SMPProxyFallback: String, Codable, SelectableItem { case allow = "allow" case allowProtected = "allowProtected" case prohibit = "prohibit" @@ -1418,7 +1418,7 @@ public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit] } -public enum OnionHosts: String, Identifiable, Hashable { +public enum OnionHosts: String, Identifiable { case no case prefer case require @@ -1452,7 +1452,7 @@ public enum OnionHosts: String, Identifiable, Hashable { public static let values: [OnionHosts] = [.no, .prefer, .require] } -public enum TransportSessionMode: String, Codable, Identifiable, Hashable { +public enum TransportSessionMode: String, Codable, Identifiable { case user case entity @@ -1468,7 +1468,7 @@ public enum TransportSessionMode: String, Codable, Identifiable, Hashable { public static let values: [TransportSessionMode] = [.user, .entity] } -public struct KeepAliveOpts: Codable, Equatable, Hashable { +public struct KeepAliveOpts: Codable, Equatable { public var keepIdle: Int // seconds public var keepIntvl: Int // seconds public var keepCnt: Int // times @@ -1476,7 +1476,7 @@ public struct KeepAliveOpts: Codable, Equatable, Hashable { public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) } -public enum NetworkStatus: Decodable, Equatable, Hashable { +public enum NetworkStatus: Decodable, Equatable { case unknown case connected case disconnected @@ -1514,7 +1514,7 @@ public enum NetworkStatus: Decodable, Equatable, Hashable { } } -public struct ConnNetworkStatus: Decodable, Hashable { +public struct ConnNetworkStatus: Decodable { public var agentConnId: String public var networkStatus: NetworkStatus } @@ -1539,7 +1539,7 @@ public enum MsgFilter: String, Codable, Hashable { case mentions } -public struct UserMsgReceiptSettings: Codable, Hashable { +public struct UserMsgReceiptSettings: Codable { public var enable: Bool public var clearOverrides: Bool @@ -1588,7 +1588,7 @@ public enum SndSwitchStatus: String, Codable, Hashable { case sendingQTEST = "sending_qtest" } -public enum QueueDirection: String, Decodable, Hashable { +public enum QueueDirection: String, Decodable { case rcv case snd } @@ -1643,12 +1643,12 @@ public struct AutoAccept: Codable, Hashable { } } -public protocol SelectableItem: Hashable, Identifiable { +public protocol SelectableItem: Identifiable, Equatable { var label: LocalizedStringKey { get } static var values: [Self] { get } } -public struct DeviceToken: Decodable, Hashable { +public struct DeviceToken: Decodable { var pushProvider: PushProvider var token: String @@ -1662,12 +1662,12 @@ public struct DeviceToken: Decodable, Hashable { } } -public enum PushEnvironment: String, Hashable { +public enum PushEnvironment: String { case development case production } -public enum PushProvider: String, Decodable, Hashable { +public enum PushProvider: String, Decodable { case apns_dev case apns_prod @@ -1681,7 +1681,7 @@ public enum PushProvider: String, Decodable, Hashable { // This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, // and .local for periodic background checks -public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { +public enum NotificationsMode: String, Decodable, SelectableItem { case off = "OFF" case periodic = "PERIODIC" case instant = "INSTANT" @@ -1699,7 +1699,7 @@ public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { public static var values: [NotificationsMode] = [.instant, .periodic, .off] } -public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { +public enum NotificationPreviewMode: String, SelectableItem, Codable { case hidden case contact case message @@ -1717,7 +1717,7 @@ public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden] } -public struct RemoteCtrlInfo: Decodable, Hashable { +public struct RemoteCtrlInfo: Decodable { public var remoteCtrlId: Int64 public var ctrlDeviceName: String public var sessionState: RemoteCtrlSessionState? @@ -1727,7 +1727,7 @@ public struct RemoteCtrlInfo: Decodable, Hashable { } } -public enum RemoteCtrlSessionState: Decodable, Hashable { +public enum RemoteCtrlSessionState: Decodable { case starting case searching case connecting @@ -1742,17 +1742,17 @@ public enum RemoteCtrlStopReason: Decodable { case disconnected } -public struct CtrlAppInfo: Decodable, Hashable { +public struct CtrlAppInfo: Decodable { public var appVersionRange: AppVersionRange public var deviceName: String } -public struct AppVersionRange: Decodable, Hashable { +public struct AppVersionRange: Decodable { public var minVersion: String public var maxVersion: String } -public struct CoreVersionInfo: Decodable, Hashable { +public struct CoreVersionInfo: Decodable { public var version: String public var simplexmqVersion: String public var simplexmqCommit: String @@ -2090,14 +2090,14 @@ public enum RemoteCtrlError: Decodable, Hashable { case protocolError } -public struct MigrationFileLinkData: Codable, Hashable { +public struct MigrationFileLinkData: Codable { let networkConfig: NetworkConfig? public init(networkConfig: NetworkConfig) { self.networkConfig = networkConfig } - public struct NetworkConfig: Codable, Hashable { + public struct NetworkConfig: Codable { let socksProxy: String? let hostMode: HostMode? let requiredHostMode: Bool? @@ -2129,7 +2129,7 @@ public struct MigrationFileLinkData: Codable, Hashable { } } -public struct AppSettings: Codable, Equatable, Hashable { +public struct AppSettings: Codable, Equatable { public var networkConfig: NetCfg? = nil public var privacyEncryptLocalFiles: Bool? = nil public var privacyAskToApproveRelays: Bool? = nil @@ -2224,7 +2224,7 @@ public struct AppSettings: Codable, Equatable, Hashable { } } -public enum AppSettingsNotificationMode: String, Codable, Hashable { +public enum AppSettingsNotificationMode: String, Codable { case off case periodic case instant @@ -2252,13 +2252,13 @@ public enum AppSettingsNotificationMode: String, Codable, Hashable { // case message //} -public enum AppSettingsLockScreenCalls: String, Codable, Hashable { +public enum AppSettingsLockScreenCalls: String, Codable { case disable case show case accept } -public struct UserNetworkInfo: Codable, Equatable, Hashable { +public struct UserNetworkInfo: Codable, Equatable { public let networkType: UserNetworkType public let online: Bool @@ -2268,7 +2268,7 @@ public struct UserNetworkInfo: Codable, Equatable, Hashable { } } -public enum UserNetworkType: String, Codable, Hashable { +public enum UserNetworkType: String, Codable { case none case cellular case wifi @@ -2286,7 +2286,7 @@ public enum UserNetworkType: String, Codable, Hashable { } } -public struct RcvMsgInfo: Codable, Hashable { +public struct RcvMsgInfo: Codable { var msgId: Int64 var msgDeliveryId: Int64 var msgDeliveryStatus: String @@ -2294,7 +2294,7 @@ public struct RcvMsgInfo: Codable, Hashable { var agentMsgMeta: String } -public struct ServerQueueInfo: Codable, Hashable { +public struct ServerQueueInfo: Codable { var server: String var rcvId: String var sndId: String @@ -2303,7 +2303,7 @@ public struct ServerQueueInfo: Codable, Hashable { var info: QueueInfo } -public struct QueueInfo: Codable, Hashable { +public struct QueueInfo: Codable { var qiSnd: Bool var qiNtf: Bool var qiSub: QSub? @@ -2311,25 +2311,25 @@ public struct QueueInfo: Codable, Hashable { var qiMsg: MsgInfo? } -public struct QSub: Codable, Hashable { +public struct QSub: Codable { var qSubThread: QSubThread var qDelivered: String? } -public enum QSubThread: String, Codable, Hashable { +public enum QSubThread: String, Codable { case noSub case subPending case subThread case prohibitSub } -public struct MsgInfo: Codable, Hashable { +public struct MsgInfo: Codable { var msgId: String var msgTs: Date var msgType: MsgType } -public enum MsgType: String, Codable, Hashable { +public enum MsgType: String, Codable { case message case quota } From ae850c8ce8367eb967618b7b0a1953f64178dd83 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 25 Aug 2024 18:00:46 +0100 Subject: [PATCH 053/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 89b8817fd4..9a34227f46 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E51ED5802C78BF95009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57B2C78BF95009F2C7C /* libgmpxx.a */; }; - E51ED5812C78BF95009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57C2C78BF95009F2C7C /* libgmp.a */; }; - E51ED5822C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */; }; - E51ED5832C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */; }; - E51ED5842C78BF95009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57F2C78BF95009F2C7C /* libffi.a */; }; + E51ED5942C7B9983009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED58F2C7B9983009F2C7C /* libgmpxx.a */; }; + E51ED5952C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */; }; + E51ED5962C7B9983009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5912C7B9983009F2C7C /* libgmp.a */; }; + E51ED5972C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */; }; + E51ED5982C7B9983009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5932C7B9983009F2C7C /* libffi.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -550,11 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E51ED57B2C78BF95009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED57C2C78BF95009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a"; sourceTree = ""; }; - E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a"; sourceTree = ""; }; - E51ED57F2C78BF95009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51ED58F2C7B9983009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a"; sourceTree = ""; }; + E51ED5912C7B9983009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a"; sourceTree = ""; }; + E51ED5932C7B9983009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51ED5802C78BF95009F2C7C /* libgmpxx.a in Frameworks */, - E51ED5812C78BF95009F2C7C /* libgmp.a in Frameworks */, - E51ED5822C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a in Frameworks */, - E51ED5842C78BF95009F2C7C /* libffi.a in Frameworks */, + E51ED5942C7B9983009F2C7C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E51ED5982C7B9983009F2C7C /* libffi.a in Frameworks */, + E51ED5972C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a in Frameworks */, + E51ED5952C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a in Frameworks */, + E51ED5962C7B9983009F2C7C /* libgmp.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E51ED5832C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED57F2C78BF95009F2C7C /* libffi.a */, - E51ED57C2C78BF95009F2C7C /* libgmp.a */, - E51ED57B2C78BF95009F2C7C /* libgmpxx.a */, - E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */, - E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */, + E51ED5932C7B9983009F2C7C /* libffi.a */, + E51ED5912C7B9983009F2C7C /* libgmp.a */, + E51ED58F2C7B9983009F2C7C /* libgmpxx.a */, + E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */, + E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */, ); path = Libraries; sourceTree = ""; From 0477b1aad37b939be89e171a98cc94b6c58286d3 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Sun, 25 Aug 2024 21:21:24 +0300 Subject: [PATCH 054/440] ios: time based message grouping (#4743) * ios: time based message grouping * cleanup * hide timestamp * fix chat item not getting updated * round to minute * separate by minute * chat dir * time separation struct * add date logic * cleanup * fix groups * simplify timestamp logic; remove shape * cleanup * cleanup * refactor, add type --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 26 +++++--- .../Chat/ChatItem/CIGroupInvitationView.swift | 5 +- .../Views/Chat/ChatItem/CIMetaView.swift | 22 ++++--- .../Chat/ChatItem/CIRcvDecryptionError.swift | 5 +- .../Views/Chat/ChatItem/MsgContentView.swift | 3 +- apps/ios/Shared/Views/Chat/ChatItemView.swift | 12 ++++ apps/ios/Shared/Views/Chat/ChatView.swift | 64 +++++++++++++------ apps/ios/SimpleXChat/ChatTypes.swift | 9 ++- 8 files changed, 100 insertions(+), 46 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 0ac1a9cacb..07a0d19a55 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -123,6 +123,14 @@ class NetworkModel: ObservableObject { } } +/// ChatItemWithMenu can depend on previous or next item for it's appearance +/// This dummy model is used to force an update of all chat items, +/// when they might have changed appearance. +class ChatItemDummyModel: ObservableObject { + static let shared = ChatItemDummyModel() + func sendUpdate() { objectWillChange.send() } +} + final class ChatModel: ObservableObject { @Published var onboardingStage: OnboardingStage? @Published var setDeliveryReceipts = false @@ -428,19 +436,17 @@ final class ChatModel: ObservableObject { private func _upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool { if let i = getChatItemIndex(cItem) { - withConditionalAnimation { - _updateChatItem(at: i, with: cItem) - } + _updateChatItem(at: i, with: cItem) + ChatItemDummyModel.shared.sendUpdate() return false } else { - withConditionalAnimation(itemAnimation()) { - var ci = cItem - if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus { - ci.meta.itemStatus = status - } - im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) - im.itemAdded = true + var ci = cItem + if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus { + ci.meta.itemStatus = status } + im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) + im.itemAdded = true + ChatItemDummyModel.shared.sendUpdate() return true } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index ef0fec5dfe..3c6da34ae5 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct CIGroupInvitationView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool @ObservedObject var chat: Chat var chatItem: ChatItem var groupInvitation: CIGroupInvitation @@ -45,7 +46,7 @@ struct CIGroupInvitationView: View { .foregroundColor(inProgress ? theme.colors.secondary : chatIncognito ? .indigo : theme.colors.primary) .font(.callout) + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } @@ -53,7 +54,7 @@ struct CIGroupInvitationView: View { ( groupInvitationText() + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 66b810cf2f..3f2ec8f38e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct CIMetaView: View { @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool var chatItem: ChatItem var metaColor: Color var paleMetaColor = Color(UIColor.tertiaryLabel) @@ -30,24 +31,24 @@ struct CIMetaView: View { switch meta.itemStatus { case let .sndSent(sndProgress): switch sndProgress { - case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } case let .sndRcvd(_, sndProgress): switch sndProgress { case .complete: ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } case .partial: ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } default: - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } } @@ -69,7 +70,8 @@ func ciMetaText( sent: SentCheckmark? = nil, showStatus: Bool = true, showEdited: Bool = true, - showViaProxy: Bool + showViaProxy: Bool, + showTimesamp: Bool ) -> Text { var r = Text("") if showEdited, meta.itemEdited { @@ -105,7 +107,9 @@ func ciMetaText( if let enc = encrypted { r = r + statusIconText(enc ? "lock" : "lock.open", color) + Text(" ") } - r = r + meta.timestampText.foregroundColor(color) + if showTimesamp { + r = r + meta.timestampText.foregroundColor(color) + } return r.font(.caption) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 1f2e16448d..915af3f479 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -15,6 +15,7 @@ struct CIRcvDecryptionError: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat + @Environment(\.showTimestamp) var showTimestamp: Bool var msgDecryptError: MsgDecryptError var msgCount: UInt32 var chatItem: ChatItem @@ -122,7 +123,7 @@ struct CIRcvDecryptionError: View { .foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary) .font(.callout) + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) } .padding(.horizontal, 12) @@ -142,7 +143,7 @@ struct CIRcvDecryptionError: View { .foregroundColor(.red) .italic() + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } .padding(.horizontal, 12) CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index 999f99b294..9cc4179723 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -26,6 +26,7 @@ private func typing(_ w: Font.Weight = .light) -> Text { struct MsgContentView: View { @ObservedObject var chat: Chat + @Environment(\.showTimestamp) var showTimestamp: Bool @EnvironmentObject var theme: AppTheme var text: String var formattedText: [FormattedText]? = nil @@ -84,7 +85,7 @@ struct MsgContentView: View { } private func reserveSpaceForMeta(_ mt: CIMeta) -> Text { - (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 870fe30108..d444ce0735 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -9,9 +9,21 @@ import SwiftUI import SimpleXChat +extension EnvironmentValues { + struct ShowTimestamp: EnvironmentKey { + static let defaultValue: Bool = true + } + + var showTimestamp: Bool { + get { self[ShowTimestamp.self] } + set { self[ShowTimestamp.self] = newValue } + } +} + struct ChatItemView: View { @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool var chatItem: ChatItem var maxWidth: CGFloat = .infinity @Binding var revealed: Bool diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 655dd8aaed..e9e86c31d7 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -696,6 +696,7 @@ struct ChatView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @Binding @ObservedObject var chat: Chat + @ObservedObject var dummyModel: ChatItemDummyModel = .shared let chatItem: ChatItem let maxWidth: CGFloat @Binding var composeState: ComposeState @@ -716,31 +717,50 @@ struct ChatView: View { var revealed: Bool { chatItem == revealedChatItem } + typealias ItemSeparation = (timestamp: Bool, largeGap: Bool) + + func getItemSeparation(_ chatItem: ChatItem, at i: Int?) -> ItemSeparation { + let im = ItemsModel.shared + if let i, i > 0 && im.reversedChatItems.count >= i { + let nextItem = im.reversedChatItems[i - 1] + let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.createdAt.timeIntervalSince(chatItem.meta.createdAt) > 60 + return ( + timestamp: largeGap || formatTimestampText(chatItem.meta.createdAt) != formatTimestampText(nextItem.meta.createdAt), + largeGap: largeGap + ) + } else { + return (timestamp: true, largeGap: true) + } + } + var body: some View { - let (currIndex, _) = m.getNextChatItem(chatItem) + let currIndex = m.getChatItemIndex(chatItem) let ciCategory = chatItem.mergeCategory let (prevHidden, prevItem) = m.getPrevShownChatItem(currIndex, ciCategory) let range = itemsRange(currIndex, prevHidden) + let timeSeparation = getItemSeparation(chatItem, at: currIndex) let im = ItemsModel.shared Group { if revealed, let range = range { let items = Array(zip(Array(range), im.reversedChatItems[range])) - ForEach(items.reversed(), id: \.1.viewId) { (i, ci) in - 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) + VStack(spacing: 0) { + ForEach(items.reversed(), id: \.1.viewId) { (i: Int, ci: ChatItem) in + let prev = i == prevHidden ? prevItem : im.reversedChatItems[i + 1] + chatItemView(ci, nil, prev, getItemSeparation(ci, at: i)) + .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) + chatItemView(chatItem, range, prevItem, timeSeparation) .overlay { if let selected = selectedChatItems, chatItem.canBeDeletedForSelf { Color.clear @@ -791,7 +811,8 @@ struct ChatView: View { } } - @ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange?, _ prevItem: ChatItem?) -> some View { + @ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange?, _ prevItem: ChatItem?, _ itemSeparation: ItemSeparation) -> some View { + let bottomPadding: Double = itemSeparation.largeGap ? 10 : 2 if case let .groupRcv(member) = ci.chatDir, case let .group(groupInfo) = chat.chatInfo { let (prevMember, memCount): (GroupMember?, Int) = @@ -833,11 +854,11 @@ struct ChatView: View { } } } - chatItemWithMenu(ci, range, maxWidth) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) } } } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) .padding(.trailing) .padding(.leading, 12) } else { @@ -846,11 +867,11 @@ struct ChatView: View { SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems) .padding(.leading, 12) } - chatItemWithMenu(ci, range, maxWidth) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) .padding(.trailing) .padding(.leading, memberImageSize + 8 + 12) } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) } } else { HStack(alignment: .center, spacing: 0) { @@ -863,10 +884,10 @@ struct ChatView: View { .padding(.leading) } } - chatItemWithMenu(ci, range, maxWidth) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) .padding(.horizontal) } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) } } @@ -881,7 +902,7 @@ struct ChatView: View { } } - @ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat) -> some View { + @ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat, _ itemSeparation: ItemSeparation) -> some View { let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading VStack(alignment: alignment.horizontal, spacing: 3) { ChatItemView( @@ -891,6 +912,7 @@ struct ChatView: View { revealed: .constant(revealed), allowMenu: $allowMenu ) + .environment(\.showTimestamp, itemSeparation.timestamp) .modifier(ChatItemClipped(ci)) .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } .accessibilityLabel("") diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 1a9cf4a216..d51e5a7cc3 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2688,6 +2688,13 @@ public enum CIDirection: Decodable, Hashable { } } } + + public func sameDirection(_ dir: CIDirection) -> Bool { + switch (self, dir) { + case let (.groupRcv(m1), .groupRcv(m2)): m1.groupMemberId == m2.groupMemberId + default: sent == dir.sent + } + } } public struct CIMeta: Decodable, Hashable { @@ -2762,7 +2769,7 @@ let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) public func formatTimestampText(_ date: Date) -> Text { - return Text(date, format: recent(date) ? msgTimeFormat : msgDateFormat) + Text(verbatim: date.formatted(recent(date) ? msgTimeFormat : msgDateFormat)) } private func recent(_ date: Date) -> Bool { From 0118e64ab497b4874918f3cf494018f3a0d1acce Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:59:57 +0000 Subject: [PATCH 055/440] android, desktop: items padding and min height (#4767) --- .../views/usersettings/Appearance.android.kt | 7 ++----- .../kotlin/chat/simplex/common/model/ChatModel.kt | 7 +++++++ .../simplex/common/views/chat/ChatItemInfoView.kt | 10 +++++----- .../common/views/chat/group/GroupChatInfoView.kt | 4 ++-- .../common/views/chatlist/ShareListNavLinkView.kt | 8 +++++--- .../common/views/chatlist/ShareListView.kt | 15 +++++++++++---- .../common/views/database/DatabaseErrorView.kt | 4 +--- .../simplex/common/views/helpers/CloseSheetBar.kt | 3 ++- .../chat/simplex/common/views/helpers/Section.kt | 6 +++--- .../simplex/common/views/newchat/NewChatView.kt | 8 ++++---- .../common/views/remote/ConnectDesktopView.kt | 12 +++++------- .../common/views/usersettings/Appearance.kt | 4 ++-- .../views/usersettings/HiddenProfileView.kt | 5 +---- .../views/usersettings/NetworkAndServers.kt | 3 +-- .../common/views/usersettings/UserProfilesView.kt | 3 +-- .../views/usersettings/Appearance.desktop.kt | 5 ++--- 16 files changed, 54 insertions(+), 50 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index b985601962..6a76be0fe4 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -14,13 +14,11 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext @@ -33,7 +31,6 @@ import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.saveAppLocale -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.compose.painterResource @@ -82,7 +79,7 @@ fun AppearanceScope.AppearanceLayout( Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.appearance_settings)) - SectionView(stringResource(MR.strings.settings_section_title_interface), padding = PaddingValues()) { + SectionView(stringResource(MR.strings.settings_section_title_interface), contentPadding = PaddingValues()) { val context = LocalContext.current // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // SectionItemWithValue( @@ -123,7 +120,7 @@ fun AppearanceScope.AppearanceLayout( SectionDividerSpaced(maxTopPadding = true) - SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { + SectionView(stringResource(MR.strings.settings_section_title_icon), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { LazyRow { items(AppIcon.values().size, { index -> AppIcon.values()[index] }) { index -> val item = AppIcon.values()[index] diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 628481477d..e92b3d714a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -985,6 +985,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val fullName get() = contact.fullName override val image get() = contact.image override val localAlias: String get() = contact.localAlias + override fun anyNameContains(searchAnyCase: String): Boolean = contact.anyNameContains(searchAnyCase) companion object { val sampleData = Direct(Contact.sampleData) @@ -1219,6 +1220,12 @@ data class Contact( override val localAlias get() = profile.localAlias val verified get() = activeConn?.connectionCode != null + override fun anyNameContains(searchAnyCase: String): Boolean { + val s = searchAnyCase.trim().lowercase() + return profile.chatViewName.lowercase().contains(s) || profile.displayName.lowercase().contains(s) || profile.fullName.lowercase().contains(s) + } + + val directOrUsed: Boolean get() = if (activeConn != null) { (activeConn.connLevel == 0 && !activeConn.viaGroupLink) || contactUsed diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index d0e972965a..c403fe512b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -282,14 +282,14 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) val versions = ciInfo.itemVersions if (versions.isNotEmpty()) { - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Text(stringResource(MR.strings.edit_history), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) versions.forEachIndexed { i, ciVersion -> ItemVersionView(ciVersion, current = i == 0) } } } else { - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text(stringResource(MR.strings.no_history), color = MaterialTheme.colors.secondary) } @@ -304,7 +304,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Text(stringResource(MR.strings.in_reply_to), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) QuotedMsgView(qi) } @@ -381,14 +381,14 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) val mss = membersStatuses(chatModel, memberDeliveryStatuses) if (mss.isNotEmpty()) { - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Text(stringResource(MR.strings.delivery), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) mss.forEach { (member, status, sentViaProxy) -> MemberDeliveryStatusView(member, status, sentViaProxy) } } } else { - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text(stringResource(MR.strings.no_info_on_delivery), color = MaterialTheme.colors.secondary) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 9b1bb45d8f..81a0de7bb9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -509,11 +509,11 @@ private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() - verticalAlignment = Alignment.CenterVertically ) { Row( - Modifier.weight(1f).padding(end = DEFAULT_PADDING), + Modifier.weight(1f).padding(top = 8.dp, end = DEFAULT_PADDING, bottom = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - MemberProfileImage(size = DEFAULT_MIN_SECTION_ITEM_HEIGHT, member) + MemberProfileImage(size = 42.dp, member) Spacer(Modifier.width(DEFAULT_PADDING_HALF)) Column { Row(verticalAlignment = Alignment.CenterVertically) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt index 8b2e008ad3..47668c4fb3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt @@ -80,7 +80,7 @@ private fun ShareListNavLinkLayout( click: () -> Unit, stopped: Boolean, ) { - SectionItemView(minHeight = 50.dp, click = click, disabled = stopped) { + SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = 8.dp), click = click, disabled = stopped) { chatLinkPreview() } Divider(Modifier.padding(horizontal = 8.dp)) @@ -98,9 +98,11 @@ private fun SharePreviewView(chat: Chat, disabled: Boolean) { horizontalArrangement = Arrangement.spacedBy(4.dp) ) { if (chat.chatInfo is ChatInfo.Local) { - ProfileImage(size = 46.dp, null, icon = MR.images.ic_folder_filled, color = NoteFolderIconColor) + ProfileImage(size = 42.dp, null, icon = MR.images.ic_folder_filled, color = NoteFolderIconColor) + } else if (chat.chatInfo is ChatInfo.Group) { + ProfileImage(size = 42.dp, chat.chatInfo.image, icon = MR.images.ic_supervised_user_circle_filled) } else { - ProfileImage(size = 46.dp, chat.chatInfo.image) + ProfileImage(size = 42.dp, chat.chatInfo.image) } Text( chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index cdf1766a25..886b82de7d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -94,10 +94,17 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe } if (appPlatform.isAndroid) { tryOrShowError("UserPicker", error = {}) { - UserPicker(chatModel, userPickerState, showSettings = false, showCancel = true, cancelClicked = { - chatModel.sharedContent.value = null - userPickerState.value = AnimatedViewState.GONE - }) + UserPicker( + chatModel, + userPickerState, + showSettings = false, + showCancel = true, + contentAlignment = if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart, + cancelClicked = { + chatModel.sharedContent.value = null + userPickerState.value = AnimatedViewState.GONE + } + ) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt index 109e5bc737..333c73e195 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt @@ -5,9 +5,7 @@ import SectionSpacer import SectionView import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -66,7 +64,7 @@ fun DatabaseErrorView( Modifier.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING, bottom = DEFAULT_PADDING), style = MaterialTheme.typography.h1 ) - SectionView(null, padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content) + SectionView(null, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content = content) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index 080edd22b2..90f8299404 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -85,7 +85,8 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co Text( title.value, fontWeight = FontWeight.SemiBold, - maxLines = 1 + maxLines = 1, + overflow = TextOverflow.Ellipsis ) } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index facecd2398..37bf5b10b1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -21,15 +21,15 @@ import chat.simplex.common.views.usersettings.SettingsActionItemWithContent import chat.simplex.res.MR @Composable -fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit)) { +fun SectionView(title: String? = null, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = DEFAULT_PADDING, content: (@Composable ColumnScope.() -> Unit)) { Column { if (title != null) { Text( title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, - modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = DEFAULT_PADDING), fontSize = 12.sp + modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = headerBottomPadding), fontSize = 12.sp ) } - Column(Modifier.padding(padding).fillMaxWidth()) { content() } + Column(Modifier.padding(contentPadding).fillMaxWidth()) { content() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 419d3b6ed7..d2e8ac7a6c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -194,13 +194,13 @@ private fun RetryButton(onClick: () -> Unit) { @Composable private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState) { - SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase()) { + SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) { LinkTextView(connReqInvitation, true) } Spacer(Modifier.height(10.dp)) - SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase()) { + SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase(), headerBottomPadding = 5.dp) { SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() }) } @@ -242,14 +242,14 @@ fun AddContactLearnMoreButton() { @Composable private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState, pastedLink: MutableState, close: () -> Unit) { - SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase()) { + SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase(), headerBottomPadding = 5.dp) { PasteLinkView(rhId, pastedLink, showQRCodeScanner, close) } if (appPlatform.isAndroid) { Spacer(Modifier.height(10.dp)) - SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase()) { + SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase(), headerBottomPadding = 5.dp) { QRCodeScanner(showQRCodeScanner) { text -> withBGApi { val res = verify(rhId, text, close) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt index b5349e826d..eb7fd7b6b5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt @@ -8,9 +8,7 @@ import SectionSpacer import SectionView import TextIconSpaced import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -149,7 +147,7 @@ private fun ConnectDesktop(deviceName: String, remoteCtrls: SnapshotStateList) { AppBarTitle(stringResource(MR.strings.verify_connection)) - SectionView(stringResource(MR.strings.connected_to_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.connected_to_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { CtrlDeviceNameText(session, rc) Spacer(Modifier.height(DEFAULT_PADDING_HALF)) CtrlDeviceVersionText(session) @@ -313,7 +311,7 @@ private fun CtrlDeviceVersionText(session: RemoteCtrlSession) { @Composable private fun ActiveSession(session: RemoteCtrlSession, rc: RemoteCtrlInfo, close: () -> Unit) { AppBarTitle(stringResource(MR.strings.connected_to_desktop)) - SectionView(stringResource(MR.strings.connected_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.connected_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Text(rc.deviceViewName) Spacer(Modifier.height(DEFAULT_PADDING_HALF)) CtrlDeviceVersionText(session) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index d8993307d2..3747ae047a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -53,7 +53,7 @@ expect fun AppearanceView(m: ChatModel) object AppearanceScope { @Composable fun ProfileImageSection() { - SectionView(stringResource(MR.strings.settings_section_title_profile_images).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.settings_section_title_profile_images).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { val image = remember { chatModel.currentUser }.value?.image Row(Modifier.padding(top = 10.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { val size = 60 @@ -86,7 +86,7 @@ object AppearanceScope { @Composable fun FontScaleSection() { val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) } - SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { Box(Modifier.size(60.dp) .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt index b97a686e22..e5116f9149 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt @@ -1,15 +1,12 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionItemView import SectionItemViewSpaceBetween import SectionItemViewWithoutMinPadding import SectionSpacer import SectionTextFooter import SectionView import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -64,7 +61,7 @@ private fun HiddenProfileLayout( .fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.hide_profile)) - SectionView(padding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { UserProfileRow(user) } SectionSpacer() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 74e6bf5910..5bcb0a545d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.text.input.* import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller @@ -264,7 +263,7 @@ fun SocksProxySettings( .fillMaxWidth() ) { AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { DefaultConfigurableTextField( hostUnsaved, stringResource(MR.strings.host_verb), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index 4a9af6e822..a7bf5920e4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -8,7 +8,6 @@ import SectionItemViewWithoutMinPadding import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -270,7 +269,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: ( @Composable fun ActionHeader(title: StringResource) { AppBarTitle(stringResource(title)) - SectionView(padding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { UserProfileRow(user) } SectionSpacer() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 38ffb137ed..36c7d180b5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -25,7 +25,6 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.delay import java.util.Locale -import kotlin.math.roundToInt @Composable actual fun AppearanceView(m: ChatModel) { @@ -44,7 +43,7 @@ fun AppearanceScope.AppearanceLayout( Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.appearance_settings)) - SectionView(stringResource(MR.strings.settings_section_title_language), padding = PaddingValues()) { + SectionView(stringResource(MR.strings.settings_section_title_language), contentPadding = PaddingValues()) { val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") } LangSelector(state) { state.value = it @@ -79,7 +78,7 @@ fun AppearanceScope.AppearanceLayout( @Composable fun DensityScaleSection() { val localDensityScale = remember { mutableStateOf(appPrefs.densityScale.get()) } - SectionView(stringResource(MR.strings.appearance_zoom).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.appearance_zoom).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { Box(Modifier.size(60.dp) .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) From f1e8c65aa1442f32cb0fdbc20b22465ef6f3ebf8 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:06:21 +0000 Subject: [PATCH 056/440] android, desktop: using SemVer when checking for updates (#4768) * android, desktop: using SemVer when checking for updates * simplify * simplify * no comment * simplify * change --------- Co-authored-by: Evgeny Poberezkin --- .../common/views/helpers/AppUpdater.kt | 94 ++++++++++++++++--- .../kotlin/chat/simplex/app/SemVerTest.kt | 63 +++++++++++++ 2 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SemVerTest.kt diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt index faef957705..ac69c41832 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt @@ -19,12 +19,76 @@ import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import okhttp3.OkHttpClient import okhttp3.Request import java.io.Closeable import java.io.File import java.net.InetSocketAddress import java.net.Proxy +import kotlin.math.min + +data class SemVer( + val major: Int, + val minor: Int, + val patch: Int, + val preRelease: String? = null, + val buildNumber: Int? = null, +): Comparable { + + val isNotStable: Boolean = preRelease != null + + override fun compareTo(other: SemVer?): Int { + if (other == null) return 1 + return when { + major != other.major -> major.compareTo(other.major) + minor != other.minor -> minor.compareTo(other.minor) + patch != other.patch -> patch.compareTo(other.patch) + preRelease != null && other.preRelease != null -> { + val pr = preRelease.compareTo(other.preRelease, ignoreCase = true) + when { + pr != 0 -> pr + buildNumber != null && other.buildNumber != null -> buildNumber.compareTo(other.buildNumber) + buildNumber != null -> -1 + other.buildNumber != null -> 1 + else -> 0 + } + } + preRelease != null -> -1 + other.preRelease != null -> 1 + else -> 0 + } + } + + companion object { + private val regex = Regex("^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([A-Za-z]+)\\.(\\d+))?\$") + fun from(tagName: String): SemVer? { + val trimmed = tagName.trimStart { it == 'v' } + val redacted = when { + trimmed.contains('-') && trimmed.substringBefore('-').count { it == '.' } == 1 -> "${trimmed.substringBefore('-')}.0-${trimmed.substringAfter('-')}" + trimmed.substringBefore('-').count { it == '.' } == 1 -> "${trimmed}.0" + else -> trimmed + } + val group = regex.matchEntire(redacted)?.groups + return if (group != null) { + SemVer( + major = group[1]?.value?.toIntOrNull() ?: return null, + minor = group[2]?.value?.toIntOrNull() ?: return null, + patch = group[3]?.value?.toIntOrNull() ?: return null, + preRelease = group[4]?.value, + buildNumber = group[5]?.value?.toIntOrNull(), + ) + } else { + null + } + } + + fun fromCurrentVersionName(): SemVer? { + val currentVersionName = if (appPlatform.isAndroid) BuildConfigCommon.ANDROID_VERSION_NAME else BuildConfigCommon.DESKTOP_VERSION_NAME + return from(currentVersionName) + } + } +} @Serializable data class GitHubRelease( @@ -34,12 +98,18 @@ data class GitHubRelease( val htmlUrl: String, val name: String, val draft: Boolean, - val prerelease: Boolean, + @SerialName("prerelease") + private val preRelease: Boolean, val body: String, @SerialName("published_at") val publishedAt: String, val assets: List -) +) { + @Transient + val semVer: SemVer? = SemVer.from(tagName) + + val isConsideredBeta: Boolean = preRelease || semVer == null || semVer.isNotStable +} @Serializable data class GitHubAsset( @@ -105,25 +175,25 @@ private fun createUpdateJob() { fun checkForUpdate() { Log.d(TAG, "Checking for update") + val currentSemVer = SemVer.fromCurrentVersionName() + if (currentSemVer == null) { + Log.e(TAG, "Current SemVer cannot be parsed") + return + } val client = setupHttpClient() try { val request = Request.Builder().url("https://api.github.com/repos/simplex-chat/simplex-chat/releases").addHeader("User-agent", "curl").build() client.newCall(request).execute().use { response -> response.body?.use { val body = it.string() - val releases = json.decodeFromString>(body).filterNot { it.draft } + val releases = json.decodeFromString>(body) val release = when (appPrefs.appUpdateChannel.get()) { - AppUpdatesChannel.STABLE -> releases.firstOrNull { !it.prerelease } - AppUpdatesChannel.BETA -> releases.firstOrNull() + AppUpdatesChannel.STABLE -> releases.firstOrNull { r -> !r.draft && !r.isConsideredBeta && currentSemVer < r.semVer } + AppUpdatesChannel.BETA -> releases.firstOrNull { r -> !r.draft && currentSemVer < r.semVer } AppUpdatesChannel.DISABLED -> return - } ?: return - val currentVersionName = "v" + (if (appPlatform.isAndroid) BuildConfigCommon.ANDROID_VERSION_NAME else BuildConfigCommon.DESKTOP_VERSION_NAME) - val redactedCurrentVersionName = when { - currentVersionName.contains('-') && currentVersionName.substringBefore('-').count { it == '.' } == 1 -> "${currentVersionName.substringBefore('-')}.0-${currentVersionName.substringAfter('-')}" - currentVersionName.substringBefore('-').count { it == '.' } == 1 -> "${currentVersionName}.0" - else -> currentVersionName } - if (release.tagName == appPrefs.appSkippedUpdate.get() || release.tagName == currentVersionName || release.tagName == redactedCurrentVersionName) { + + if (release == null || release.tagName == appPrefs.appSkippedUpdate.get()) { Log.d(TAG, "Skipping update because of the same version or skipped version") return } diff --git a/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SemVerTest.kt b/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SemVerTest.kt new file mode 100644 index 0000000000..561911773f --- /dev/null +++ b/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SemVerTest.kt @@ -0,0 +1,63 @@ +package chat.simplex.app + +import chat.simplex.common.views.helpers.SemVer +import kotlin.test.Test +import kotlin.test.assertEquals + +// use this command for testing: +// ./gradlew desktopTest +class SemVerTest { + @Test + fun testValidSemVer() { + assertEquals(SemVer.from("1.0.0"), SemVer(1, 0, 0)) + assertEquals(SemVer.from("1.0"), SemVer(1, 0, 0)) + assertEquals(SemVer.from("v1.0"), SemVer(1, 0, 0)) + assertEquals(SemVer.from("v1.0-beta.1"), SemVer(1, 0, 0, "beta", 1)) + val r = listOf>( + "0.0.4" to SemVer(0, 0, 4), + "1.2.3" to SemVer(1, 2, 3), + "10.20.30" to SemVer(10, 20, 30), + "1.0.0-alpha.1" to SemVer(1, 0, 0, "alpha", buildNumber = 1), + "1.0.0" to SemVer(1, 0, 0), + "2.0.0" to SemVer(2, 0, 0), + "1.1.7" to SemVer(1, 1, 7), + "2.0.1-alpha.1227" to SemVer(2, 0, 1, "alpha", 1227), + ) + r.forEach { (value, correct) -> + assertEquals(SemVer.from(value), correct) + } + } + + @Test + fun testComparisonSemVer() { + assert(SemVer(0, 1, 0) == SemVer.from("0.1.0")) + assert(SemVer(1, 1, 0) == SemVer.from("v1.1.0")) + assert(SemVer(0, 1, 0) > SemVer(0, 0, 1)) + assert(SemVer(1, 0, 0) > SemVer(0, 100, 100)) + assert(SemVer(0, 200, 0) > SemVer(0, 100, 100)) + assert(SemVer(0, 1, 0, "beta") > SemVer(0, 1, 0, "alpha")) + assert(SemVer(0, 1, 0) > SemVer(0, 1, 0, "alpha")) + assert(SemVer(0, 1, 0) > SemVer(0, 1, 0, "beta")) + assert(SemVer(0, 1, 0) > SemVer(0, 1, 0, "beta.0")) + assert(SemVer(0, 1, 0, "beta", 1) > SemVer(0, 1, 0, "beta", 0)) + assert(SemVer(0, 1, 0, "beta", 11) > SemVer(0, 1, 0, "beta", 10)) + assert(SemVer(0, 1, 0, "beta", 11) > SemVer(0, 1, 0, "beta", 9)) + assert(SemVer(0, 1, 0, "beta.1") > SemVer(0, 1, 0, "alpha.2")) + assert(SemVer(1, 1, 0, "beta.1") > SemVer(0, 1, 0, "beta.1")) + assert(SemVer(1, 0, 0) > SemVer(1, 0, 0, "beta.1")) + assert(SemVer(1, 0, 0) > null) + assert(SemVer.from("v6.0.0")!! > SemVer.from("v6.0.0-beta.3")) + assert(SemVer.from("v6.0.0-beta.3")!! > SemVer.from("v6.0.0-beta.2")) + assert(SemVer.from("0.1.0") == SemVer.from("0.1.0")) + assert(SemVer.from("0.1.1")!! > SemVer.from("0.1.0")) + assert(SemVer.from("0.2.1")!! > SemVer.from("0.1.1")) + assert(SemVer.from("2.0.1")!! > SemVer.from("0.1.1")) + assert(SemVer.from("0.1.1-beta.0")!! > SemVer.from("0.1.0-beta.0")) + assert(SemVer.from("0.1.1-beta.0")!! == SemVer.from("0.1.1-beta.0")) + assert(SemVer.from("0.1.1-beta.1")!! > SemVer.from("0.1.1-beta.0")) + assert(SemVer.from("10.0.0-beta.12")!! > SemVer.from("1.1.1")) + assert(SemVer.from("1.1.1-beta.120")!! > SemVer.from("1.1.1-alpha.9")) + assert(SemVer.from("1.1.1-beta.120")!! > SemVer.from("1.1.1-alpha.120")) + assert(SemVer.from("2.0.1")!! > SemVer.from("0.1.1")) + } +} From 76cb9013f5d1fc3d6243d8c6f588f3778a0e16e1 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:21:00 +0000 Subject: [PATCH 057/440] desktop: show only AppImage download option for those who running AppImage (#4774) --- .../kotlin/chat/simplex/common/views/helpers/AppUpdater.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt index ac69c41832..974578882d 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt @@ -368,13 +368,15 @@ private suspend fun downloadAsset(asset: GitHubAsset) { } } +private fun isRunningFromAppImage(): Boolean = System.getenv("APPIMAGE") != null + private fun isRunningFromFlatpak(): Boolean = System.getenv("container") == "flatpak" private fun chooseGitHubReleaseAssets(release: GitHubRelease): List { val res = if (isRunningFromFlatpak()) { // No need to show download options for Flatpak users emptyList() - } else if (Runtime.getRuntime().exec("which dpkg").onExit().join().exitValue() == 0) { + } else if (!isRunningFromAppImage() && Runtime.getRuntime().exec("which dpkg").onExit().join().exitValue() == 0) { // Show all available .deb packages and user will choose the one that works on his system (for Debian derivatives) release.assets.filter { it.name.lowercase().endsWith(".deb") } } else { From 043a4ed9159a89f7e12026348d2bed8a8ccceb24 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 27 Aug 2024 16:30:07 +0300 Subject: [PATCH 058/440] ios: add chat message tail and roundness settings; date separators (#4764) * ios: add chat message tail and roundness settings * cleanup * minor * rename * date separator * revert max roundness to pills * increase default roundness to 1 * minor * out of bounds tails, style date separator * formatting * hardcode tail growth * revert * different shape (WIP) * tail * rename * square * only show tail for the last message * remove func * capture less * variable tail height * export localizations --------- Co-authored-by: Evgeny Poberezkin --- .../Chat/ChatItem/CIGroupInvitationView.swift | 2 +- .../Chat/ChatItem/CIRcvDecryptionError.swift | 55 +++--- .../Views/Chat/ChatItem/FramedItemView.swift | 4 +- .../ChatItem/IntegrityErrorItemView.swift | 2 +- .../Chat/ChatItem/MarkedDeletedItemView.swift | 2 +- apps/ios/Shared/Views/Chat/ChatView.swift | 32 +++- .../Views/Helpers/ChatItemClipShape.swift | 174 ++++++++++++++---- .../UserSettings/AppearanceSettings.swift | 17 +- .../Views/UserSettings/SettingsView.swift | 6 + .../bg.xcloc/Localized Contents/bg.xliff | 40 ++++ .../cs.xcloc/Localized Contents/cs.xliff | 40 ++++ .../de.xcloc/Localized Contents/de.xliff | 40 ++++ .../en.xcloc/Localized Contents/en.xliff | 50 +++++ .../es.xcloc/Localized Contents/es.xliff | 40 ++++ .../fi.xcloc/Localized Contents/fi.xliff | 40 ++++ .../fr.xcloc/Localized Contents/fr.xliff | 40 ++++ .../hu.xcloc/Localized Contents/hu.xliff | 40 ++++ .../it.xcloc/Localized Contents/it.xliff | 40 ++++ .../ja.xcloc/Localized Contents/ja.xliff | 40 ++++ .../nl.xcloc/Localized Contents/nl.xliff | 40 ++++ .../pl.xcloc/Localized Contents/pl.xliff | 40 ++++ .../ru.xcloc/Localized Contents/ru.xliff | 40 ++++ .../th.xcloc/Localized Contents/th.xliff | 40 ++++ .../tr.xcloc/Localized Contents/tr.xliff | 40 ++++ .../uk.xcloc/Localized Contents/uk.xliff | 40 ++++ .../Localized Contents/zh-Hans.xliff | 40 ++++ 26 files changed, 909 insertions(+), 75 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index 3c6da34ae5..da859c1606 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -70,7 +70,7 @@ struct CIGroupInvitationView: View { } .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(chatItem, theme)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } .onChange(of: inProgress) { inProgress in diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 915af3f479..9f721f83b7 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -69,37 +69,40 @@ struct CIRcvDecryptionError: View { } @ViewBuilder private func viewBody() -> some View { - if case let .direct(contact) = chat.chatInfo, - let contactStats = contact.activeConn?.connectionStats { - if contactStats.ratchetSyncAllowed { - decryptionErrorItemFixButton(syncSupported: true) { - alert = .syncAllowedAlert { syncContactConnection(contact) } + Group { + if case let .direct(contact) = chat.chatInfo, + let contactStats = contact.activeConn?.connectionStats { + if contactStats.ratchetSyncAllowed { + decryptionErrorItemFixButton(syncSupported: true) { + alert = .syncAllowedAlert { syncContactConnection(contact) } + } + } else if !contactStats.ratchetSyncSupported { + decryptionErrorItemFixButton(syncSupported: false) { + alert = .syncNotSupportedContactAlert + } + } else { + basicDecryptionErrorItem() } - } else if !contactStats.ratchetSyncSupported { - decryptionErrorItemFixButton(syncSupported: false) { - alert = .syncNotSupportedContactAlert + } else if case let .group(groupInfo) = chat.chatInfo, + case let .groupRcv(groupMember) = chatItem.chatDir, + let mem = m.getGroupMember(groupMember.groupMemberId), + let memberStats = mem.wrapped.activeConn?.connectionStats { + if memberStats.ratchetSyncAllowed { + decryptionErrorItemFixButton(syncSupported: true) { + alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) } + } + } else if !memberStats.ratchetSyncSupported { + decryptionErrorItemFixButton(syncSupported: false) { + alert = .syncNotSupportedMemberAlert + } + } else { + basicDecryptionErrorItem() } } else { basicDecryptionErrorItem() } - } else if case let .group(groupInfo) = chat.chatInfo, - case let .groupRcv(groupMember) = chatItem.chatDir, - let mem = m.getGroupMember(groupMember.groupMemberId), - let memberStats = mem.wrapped.activeConn?.connectionStats { - if memberStats.ratchetSyncAllowed { - decryptionErrorItemFixButton(syncSupported: true) { - alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) } - } - } else if !memberStats.ratchetSyncSupported { - decryptionErrorItemFixButton(syncSupported: false) { - alert = .syncNotSupportedMemberAlert - } - } else { - basicDecryptionErrorItem() - } - } else { - basicDecryptionErrorItem() } + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } } private func basicDecryptionErrorItem() -> some View { @@ -132,7 +135,6 @@ struct CIRcvDecryptionError: View { } .onTapGesture(perform: { onClick() }) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) .textSelection(.disabled) } @@ -151,7 +153,6 @@ struct CIRcvDecryptionError: View { } .onTapGesture(perform: { onClick() }) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) .textSelection(.disabled) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 313ec0d419..e70f891302 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -71,8 +71,8 @@ struct FramedItemView: View { .overlay(DetermineWidth()) .accessibilityLabel("") } - } - .background(chatItemFrameColorMaybeImageOrVideo(chatItem, theme)) + } + .background { chatItemFrameColorMaybeImageOrVideo(chatItem, theme).modifier(ChatTailPadding()) } .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } if let (title, text) = chatItem.meta.itemStatus.statusInfo { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift index 822dda4d06..afeb88b05d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift @@ -69,7 +69,7 @@ struct CIMsgError: View { } .padding(.leading, 12) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) .onTapGesture(perform: onTap) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift index 25e06b9ea4..afd817357c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift @@ -22,7 +22,7 @@ struct MarkedDeletedItemView: View { .foregroundColor(theme.colors.secondary) .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(chatItem, theme)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e9e86c31d7..d94be2bb81 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -717,8 +717,8 @@ struct ChatView: View { var revealed: Bool { chatItem == revealedChatItem } - typealias ItemSeparation = (timestamp: Bool, largeGap: Bool) - + typealias ItemSeparation = (timestamp: Bool, largeGap: Bool, date: Date?) + func getItemSeparation(_ chatItem: ChatItem, at i: Int?) -> ItemSeparation { let im = ItemsModel.shared if let i, i > 0 && im.reversedChatItems.count >= i { @@ -726,10 +726,11 @@ struct ChatView: View { let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.createdAt.timeIntervalSince(chatItem.meta.createdAt) > 60 return ( timestamp: largeGap || formatTimestampText(chatItem.meta.createdAt) != formatTimestampText(nextItem.meta.createdAt), - largeGap: largeGap + largeGap: largeGap, + date: Calendar.current.isDate(chatItem.meta.createdAt, inSameDayAs: nextItem.meta.createdAt) ? nil : nextItem.meta.createdAt ) } else { - return (timestamp: true, largeGap: true) + return (timestamp: true, largeGap: true, date: nil) } } @@ -760,7 +761,20 @@ struct ChatView: View { } } } else { - chatItemView(chatItem, range, prevItem, timeSeparation) + VStack(spacing: 0) { + chatItemView(chatItem, range, prevItem, timeSeparation) + if let date = timeSeparation.date { + Text(String.localizedStringWithFormat( + NSLocalizedString("%@, %@", comment: "format for date separator in chat"), + date.formatted(.dateTime.weekday(.abbreviated)), + date.formatted(.dateTime.day().month(.abbreviated)) + )) + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .padding(8) + } + } .overlay { if let selected = selectedChatItems, chatItem.canBeDeletedForSelf { Color.clear @@ -834,14 +848,14 @@ struct ChatView: View { .foregroundStyle(.secondary) .lineLimit(2) .padding(.leading, memberImageSize + 14 + (selectedChatItems != nil && ci.canBeDeletedForSelf ? 12 + 24 : 0)) - .padding(.top, 7) + .padding(.top, 3) // this is in addition to message sequence gap } HStack(alignment: .center, spacing: 0) { if selectedChatItems != nil && ci.canBeDeletedForSelf { SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems) .padding(.trailing, 12) } - HStack(alignment: .top, spacing: 8) { + HStack(alignment: .top, spacing: 10) { MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background) .onTapGesture { if let member = m.getGroupMember(member.groupMemberId) { @@ -869,7 +883,7 @@ struct ChatView: View { } chatItemWithMenu(ci, range, maxWidth, itemSeparation) .padding(.trailing) - .padding(.leading, memberImageSize + 8 + 12) + .padding(.leading, 10 + memberImageSize + 12) } .padding(.bottom, bottomPadding) } @@ -913,7 +927,7 @@ struct ChatView: View { allowMenu: $allowMenu ) .environment(\.showTimestamp, itemSeparation.timestamp) - .modifier(ChatItemClipped(ci)) + .modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap)) .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } .accessibilityLabel("") if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 { diff --git a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift index 477dc567eb..ddae6a5f6d 100644 --- a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift +++ b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift @@ -14,50 +14,60 @@ import SimpleXChat /// Supports [Dynamic Type](https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically) /// by retaining pill shape, even when ``ChatItem``'s height is less that twice its corner radius struct ChatItemClipped: ViewModifier { - struct ClipShape: Shape { - let maxCornerRadius: Double - - func path(in rect: CGRect) -> Path { - Path( - roundedRect: rect, - cornerRadius: min((rect.height / 2), maxCornerRadius), - style: .circular - ) - } - } - + @AppStorage(DEFAULT_CHAT_ITEM_ROUNDNESS) private var roundness = defaultChatItemRoundness + @AppStorage(DEFAULT_CHAT_ITEM_TAIL) private var tailEnabled = true + private let chatItem: (content: CIContent, chatDir: CIDirection)? + private let tailVisible: Bool + init() { - clipShape = ClipShape( - maxCornerRadius: 18 - ) + self.chatItem = nil + self.tailVisible = false + } + + init(_ ci: ChatItem, tailVisible: Bool) { + self.chatItem = (ci.content, ci.chatDir) + self.tailVisible = tailVisible } - init(_ chatItem: ChatItem) { - clipShape = ClipShape( - maxCornerRadius: { - switch chatItem.content { - case - .sndMsgContent, + private func shapeStyle() -> ChatItemShape.Style { + if let ci = chatItem { + switch ci.content { + case + .sndMsgContent, .rcvMsgContent, .rcvDecryptionError, .rcvGroupInvitation, .sndGroupInvitation, - .sndDeleted, + .sndDeleted, .rcvDeleted, .rcvIntegrityError, - .sndModerated, - .rcvModerated, + .sndModerated, + .rcvModerated, .rcvBlocked, - .invalidJSON: 18 - default: 8 + .invalidJSON: + let tail = if let mc = ci.content.msgContent, mc.isImageOrVideo && mc.text.isEmpty { + false + } else { + tailVisible } - }() - ) + return tailEnabled + ? .bubble( + padding: ci.chatDir.sent ? .trailing : .leading, + tailVisible: tail + ) + : .roundRect(radius: msgRectMaxRadius) + default: return .roundRect(radius: 8) + } + } else { + return.roundRect(radius: msgRectMaxRadius) + } } - - private let clipShape: ClipShape - + func body(content: Content) -> some View { + let clipShape = ChatItemShape( + roundness: roundness, + style: shapeStyle() + ) content .contentShape(.dragPreview, clipShape) .contentShape(.contextMenuPreview, clipShape) @@ -65,4 +75,106 @@ struct ChatItemClipped: ViewModifier { } } +struct ChatTailPadding: ViewModifier { + func body(content: Content) -> some View { + content.padding(.horizontal, -msgTailWidth) + } +} +private let msgRectMaxRadius: Double = 18 +private let msgBubbleMaxRadius: Double = msgRectMaxRadius * 1.2 +private let msgTailWidth: Double = 9 +private let msgTailMinHeight: Double = msgTailWidth * 1.254 // ~56deg +private let msgTailMaxHeight: Double = msgTailWidth * 1.732 // 60deg + +struct ChatItemShape: Shape { + fileprivate enum Style { + case bubble(padding: HorizontalEdge, tailVisible: Bool) + case roundRect(radius: Double) + } + + fileprivate let roundness: Double + fileprivate let style: Style + + func path(in rect: CGRect) -> Path { + switch style { + case let .bubble(padding, tailVisible): + let w = rect.width + let h = rect.height + let rxMax = min(msgBubbleMaxRadius, w / 2) + let ryMax = min(msgBubbleMaxRadius, h / 2) + let rx = roundness * rxMax + let ry = roundness * ryMax + let tailHeight = min(msgTailMinHeight + roundness * (msgTailMaxHeight - msgTailMinHeight), h / 2) + var path = Path() + // top side + path.move(to: CGPoint(x: rx, y: 0)) + path.addLine(to: CGPoint(x: w - rx, y: 0)) + if roundness > 0 { + // top-right corner + path.addQuadCurve(to: CGPoint(x: w, y: ry), control: CGPoint(x: w, y: 0)) + } + if rect.height > 2 * ry { + // right side + path.addLine(to: CGPoint(x: w, y: h - ry)) + } + if roundness > 0 { + // bottom-right corner + path.addQuadCurve(to: CGPoint(x: w - rx, y: h), control: CGPoint(x: w, y: h)) + } + // bottom side + if tailVisible { + path.addLine(to: CGPoint(x: -msgTailWidth, y: h)) + if roundness > 0 { + // bottom-left tail + // distance of control point from touch point, calculated via ratios + let d = tailHeight - msgTailWidth * msgTailWidth / tailHeight + // tail control point + let tc = CGPoint(x: 0, y: h - tailHeight + d * sqrt(roundness)) + // bottom-left tail curve + path.addQuadCurve(to: CGPoint(x: 0, y: h - tailHeight), control: tc) + } else { + path.addLine(to: CGPoint(x: 0, y: h - tailHeight)) + } + if rect.height > ry + tailHeight { + // left side + path.addLine(to: CGPoint(x: 0, y: ry)) + } + } else { + path.addLine(to: CGPoint(x: rx, y: h)) + path.addQuadCurve(to: CGPoint(x: 0, y: h - ry), control: CGPoint(x: 0 , y: h)) + if rect.height > 2 * ry { + // left side + path.addLine(to: CGPoint(x: 0, y: ry)) + } + } + if roundness > 0 { + // top-left corner + path.addQuadCurve(to: CGPoint(x: rx, y: 0), control: CGPoint(x: 0, y: 0)) + } + path.closeSubpath() + return switch padding { + case .leading: path + case .trailing: path + .scale(x: -1, y: 1, anchor: .center) + .path(in: rect) + } + case let .roundRect(radius): + return Path(roundedRect: rect, cornerRadius: radius * roundness) + } + } + + var offset: Double? { + switch style { + case let .bubble(padding, isTailVisible): + if isTailVisible { + switch padding { + case .leading: -msgTailWidth + case .trailing: msgTailWidth + } + } else { 0 } + case .roundRect: 0 + } + } + +} diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index 73a789f108..70c33329b1 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -33,6 +33,8 @@ 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(DEFAULT_CHAT_ITEM_ROUNDNESS) private var chatItemRoundness = defaultChatItemRoundness + @AppStorage(DEFAULT_CHAT_ITEM_TAIL) private var chatItemTail = true @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial @@ -179,6 +181,14 @@ struct AppearanceSettings: View { } } + Section(header: Text("Message shape").foregroundColor(theme.colors.secondary)) { + HStack { + Text("Corner") + Slider(value: $chatItemRoundness, in: 0...1, step: 0.05) + } + Toggle("Tail", isOn: $chatItemTail) + } + Section(header: Text("Profile images").foregroundColor(theme.colors.secondary)) { HStack(spacing: 16) { if let img = m.currentUser?.image, img != "" { @@ -358,20 +368,21 @@ struct ChatThemePreview: View { let bob = ChatItem.getSample(2, CIDirection.directSnd, Date.now, NSLocalizedString("Good morning!", comment: "message preview"), quotedItem: CIQuote.getSample(alice.id, alice.meta.itemTs, alice.content.text, chatDir: alice.chatDir)) HStack { ChatItemView(chat: Chat.sampleData, chatItem: alice, revealed: Binding.constant(false)) - .modifier(ChatItemClipped()) + .modifier(ChatItemClipped(alice, tailVisible: true)) Spacer() } HStack { Spacer() ChatItemView(chat: Chat.sampleData, chatItem: bob, revealed: Binding.constant(false)) - .modifier(ChatItemClipped()) + .modifier(ChatItemClipped(bob, tailVisible: true)) .frame(alignment: .trailing) } } else { Rectangle().fill(.clear) } } - .padding(10) + .padding(.vertical, 10) + .padding(.horizontal, 16) .frame(maxWidth: .infinity) if let wallpaperType, let wallpaperImage = wallpaperType.image, let backgroundColor, let tintColor { diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index a4908f628f..d9c83803dd 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -47,6 +47,8 @@ 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_CHAT_ITEM_ROUNDNESS = "chatItemRoundness" +let DEFAULT_CHAT_ITEM_TAIL = "chatItemTail" let DEFAULT_ONE_HAND_UI_CARD_SHOWN = "oneHandUICardShown" let DEFAULT_TOOLBAR_MATERIAL = "toolbarMaterial" let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab" @@ -75,6 +77,8 @@ let DEFAULT_THEME_OVERRIDES = "themeOverrides" let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen" +let defaultChatItemRoundness: Double = 0.75 + let appDefaults: [String: Any] = [ DEFAULT_SHOW_LA_NOTICE: false, DEFAULT_LA_NOTICE_SHOWN: false, @@ -98,6 +102,8 @@ let appDefaults: [String: Any] = [ DEFAULT_DEVELOPER_TOOLS: false, DEFAULT_ENCRYPTION_STARTED: false, DEFAULT_PROFILE_IMAGE_CORNER_RADIUS: defaultProfileImageCorner, + DEFAULT_CHAT_ITEM_ROUNDNESS: defaultChatItemRoundness, + DEFAULT_CHAT_ITEM_TAIL: true, DEFAULT_ONE_HAND_UI_CARD_SHOWN: false, DEFAULT_TOOLBAR_MATERIAL: ToolbarMaterial.defaultMaterial, DEFAULT_CONNECT_VIA_LINK_TAB: ConnectViaLinkTab.scan.rawValue, diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 419f0ae864..89e628e2b6 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -137,6 +137,10 @@ %@ иска да се свърже! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ и %lld членове @@ -1685,6 +1689,10 @@ This is your own one-time link! Версия на ядрото: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Поправи име на %@? @@ -2645,6 +2653,10 @@ This is your own one-time link! Грешка при промяна на адреса No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Грешка при промяна на ролята @@ -2655,6 +2667,10 @@ This is your own one-time link! Грешка при промяна на настройката No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2870,6 +2886,10 @@ This is your own one-time link! Грешка при спиране на чата No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Грешка при смяна на профил! @@ -4069,6 +4089,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Източникът на съобщението остава скрит. @@ -5562,6 +5586,10 @@ Enable in *Network & servers* settings. Избери chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5911,6 +5939,10 @@ Enable in *Network & servers* settings. Сподели линк No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Сподели този еднократен линк за връзка @@ -6235,6 +6267,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Направи снимка @@ -7480,6 +7516,10 @@ Repeat connection request? Вашите чат профили No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Вашият контакт изпрати файл, който е по-голям от поддържания в момента максимален размер (%@). diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index a02203e630..65bc83d096 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -135,6 +135,10 @@ %@ se chce připojit! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -1623,6 +1627,10 @@ This is your own one-time link! Verze jádra: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2552,6 +2560,10 @@ This is your own one-time link! Chuba změny adresy No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Chyba při změně role @@ -2562,6 +2574,10 @@ This is your own one-time link! Chyba změny nastavení No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2772,6 +2788,10 @@ This is your own one-time link! Chyba při zastavení chatu No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Chyba při přepínání profilu! @@ -3928,6 +3948,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -5363,6 +5387,10 @@ Enable in *Network & servers* settings. Vybrat chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5708,6 +5736,10 @@ Enable in *Network & servers* settings. Sdílet odkaz No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -6024,6 +6056,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Vyfotit @@ -7209,6 +7245,10 @@ Repeat connection request? Vaše chat profily No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Kontakt odeslal soubor, který je větší než aktuálně podporovaná maximální velikost (%@). diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 29adfbabf0..357b1c3c47 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -137,6 +137,10 @@ %@ will sich mit Ihnen verbinden! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ und %lld Mitglieder @@ -1740,6 +1744,10 @@ Das ist Ihr eigener Einmal-Link! Core Version: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Richtiger Name für %@? @@ -2724,6 +2732,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Wechseln der Empfängeradresse No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Fehler beim Ändern der Rolle @@ -2734,6 +2746,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Ändern der Einstellung No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut. @@ -2954,6 +2970,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Beenden des Chats No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Fehler beim Umschalten des Profils! @@ -4184,6 +4204,10 @@ Das ist Ihr Link für die Gruppe %@! Nachrichten-Server No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Die Nachrichtenquelle bleibt privat. @@ -5729,6 +5753,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Auswählen chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld ausgewählt @@ -6099,6 +6127,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Link teilen No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Teilen Sie diesen Einmal-Einladungslink @@ -6439,6 +6471,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Machen Sie ein Foto @@ -7719,6 +7755,10 @@ Verbindungsanfrage wiederholen? Ihre Chat-Profile No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@). diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index c217793f03..006996c8d9 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -137,6 +137,11 @@ %@ wants to connect! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ and %lld members @@ -1740,6 +1745,11 @@ This is your own one-time link! Core version: v%@ No comment provided by engineer. + + Corner + Corner + No comment provided by engineer. + Correct name to %@? Correct name to %@? @@ -2724,6 +2734,11 @@ This is your own one-time link! Error changing address No comment provided by engineer. + + Error changing connection profile + Error changing connection profile + No comment provided by engineer. + Error changing role Error changing role @@ -2734,6 +2749,11 @@ This is your own one-time link! Error changing setting No comment provided by engineer. + + Error changing to incognito! + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Error connecting to forwarding server %@. Please try later. @@ -2954,6 +2974,11 @@ This is your own one-time link! Error stopping chat No comment provided by engineer. + + Error switching profile + Error switching profile + No comment provided by engineer. + Error switching profile! Error switching profile! @@ -4184,6 +4209,11 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + Message shape + No comment provided by engineer. + Message source remains private. Message source remains private. @@ -5729,6 +5759,11 @@ Enable in *Network & servers* settings. Select chat item action + + Select chat profile + Select chat profile + No comment provided by engineer. + Selected %lld Selected %lld @@ -6099,6 +6134,11 @@ Enable in *Network & servers* settings. Share link No comment provided by engineer. + + Share profile + Share profile + No comment provided by engineer. + Share this 1-time invite link Share this 1-time invite link @@ -6439,6 +6479,11 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Tail + No comment provided by engineer. + Take picture Take picture @@ -7719,6 +7764,11 @@ Repeat connection request? Your chat profiles No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Your contact sent a file that is larger than currently supported maximum size (%@). diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 6e5ec0e85a..529b185e25 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -137,6 +137,10 @@ ¡ %@ quiere contactar! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ y %lld miembro(s) más @@ -1740,6 +1744,10 @@ This is your own one-time link! Versión Core: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? ¿Corregir el nombre a %@? @@ -2724,6 +2732,10 @@ This is your own one-time link! Error al cambiar servidor No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Error al cambiar rol @@ -2734,6 +2746,10 @@ This is your own one-time link! Error cambiando configuración No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Error al conectar con el servidor de reenvío %@. Por favor, inténtalo más tarde. @@ -2954,6 +2970,10 @@ This is your own one-time link! Error al parar SimpleX No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! ¡Error al cambiar perfil! @@ -4184,6 +4204,10 @@ This is your link for group %@! Servidores de mensajes No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. El autor del mensaje se mantiene privado. @@ -5729,6 +5753,10 @@ Actívalo en ajustes de *Servidores y Redes*. Seleccionar chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld Seleccionados %lld @@ -6099,6 +6127,10 @@ Actívalo en ajustes de *Servidores y Redes*. Compartir enlace No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Comparte este enlace de un solo uso @@ -6439,6 +6471,10 @@ Actívalo en ajustes de *Servidores y Redes*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Tomar foto @@ -7719,6 +7755,10 @@ Repeat connection request? Mis perfiles No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). El contacto ha enviado un archivo mayor al máximo admitido (%@). diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 211e512a1e..fc31e4f2df 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -133,6 +133,10 @@ %@ haluaa muodostaa yhteyden! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -1616,6 +1620,10 @@ This is your own one-time link! Ydinversio: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2544,6 +2552,10 @@ This is your own one-time link! Virhe osoitteenvaihdossa No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Virhe roolin vaihdossa @@ -2554,6 +2566,10 @@ This is your own one-time link! Virhe asetuksen muuttamisessa No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2762,6 +2778,10 @@ This is your own one-time link! Virhe keskustelun lopettamisessa No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Virhe profiilin vaihdossa! @@ -3918,6 +3938,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -5351,6 +5375,10 @@ Enable in *Network & servers* settings. Valitse chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5695,6 +5723,10 @@ Enable in *Network & servers* settings. Jaa linkki No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -6010,6 +6042,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Ota kuva @@ -7194,6 +7230,10 @@ Repeat connection request? Keskusteluprofiilisi No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Yhteyshenkilösi lähetti tiedoston, joka on suurempi kuin tällä hetkellä tuettu enimmäiskoko (%@). diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index c05098980e..6970039315 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -137,6 +137,10 @@ %@ veut se connecter ! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ et %lld membres @@ -1740,6 +1744,10 @@ Il s'agit de votre propre lien unique ! Version du cœur : v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Corriger le nom pour %@ ? @@ -2724,6 +2732,10 @@ Il s'agit de votre propre lien unique ! Erreur de changement d'adresse No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Erreur lors du changement de rôle @@ -2734,6 +2746,10 @@ Il s'agit de votre propre lien unique ! Erreur de changement de paramètre No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Erreur de connexion au serveur de redirection %@. Veuillez réessayer plus tard. @@ -2954,6 +2970,10 @@ Il s'agit de votre propre lien unique ! Erreur lors de l'arrêt du chat No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Erreur lors du changement de profil ! @@ -4184,6 +4204,10 @@ Voici votre lien pour le groupe %@ ! Serveurs de messages No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. La source du message reste privée. @@ -5729,6 +5753,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Choisir chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld sélectionné(s) @@ -6099,6 +6127,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Partager le lien No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Partager ce lien d'invitation unique @@ -6439,6 +6471,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Prendre une photo @@ -7719,6 +7755,10 @@ Répéter la demande de connexion ? Vos profils de chat No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@). diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index f7328eed91..de95de3421 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -137,6 +137,10 @@ %@ kapcsolódni szeretne! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ és további %lld tag @@ -1740,6 +1744,10 @@ Ez az egyszer használatos hivatkozása! Alapverziószám: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Név javítása erre: %@? @@ -2724,6 +2732,10 @@ Ez az egyszer használatos hivatkozása! Hiba a cím megváltoztatásakor No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Hiba a szerepkör megváltoztatásakor @@ -2734,6 +2746,10 @@ Ez az egyszer használatos hivatkozása! Hiba a beállítás megváltoztatásakor No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Hiba a(z) %@ továbbító kiszolgálóhoz való kapcsolódáskor. Próbálja meg később. @@ -2954,6 +2970,10 @@ Ez az egyszer használatos hivatkozása! Hiba a csevegés megállításakor No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Hiba a profil váltásakor! @@ -4184,6 +4204,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Üzenetkiszolgálók No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Az üzenet forrása titokban marad. @@ -5729,6 +5753,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Választás chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld kiválasztva @@ -6099,6 +6127,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Hivatkozás megosztása No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Egyszer használatos meghívó hivatkozás megosztása @@ -6439,6 +6471,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Kép készítése @@ -7719,6 +7755,10 @@ Kapcsolódási kérés megismétlése? Csevegési profilok No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ismerőse olyan fájlt küldött, amely meghaladja a jelenleg támogatott maximális méretet (%@). diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 72eb3561e3..700d181aab 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -137,6 +137,10 @@ %@ si vuole connettere! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ e %lld membri @@ -1740,6 +1744,10 @@ Questo è il tuo link una tantum! Versione core: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Correggere il nome a %@? @@ -2724,6 +2732,10 @@ Questo è il tuo link una tantum! Errore nella modifica dell'indirizzo No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Errore nel cambio di ruolo @@ -2734,6 +2746,10 @@ Questo è il tuo link una tantum! Errore nella modifica dell'impostazione No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Errore di connessione al server di inoltro %@. Riprova più tardi. @@ -2954,6 +2970,10 @@ Questo è il tuo link una tantum! Errore nell'interruzione della chat No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Errore nel cambio di profilo! @@ -4184,6 +4204,10 @@ Questo è il tuo link per il gruppo %@! Server dei messaggi No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. La fonte del messaggio resta privata. @@ -5729,6 +5753,10 @@ Attivalo nelle impostazioni *Rete e server*. Seleziona chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld selezionato @@ -6099,6 +6127,10 @@ Attivalo nelle impostazioni *Rete e server*. Condividi link No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Condividi questo link di invito una tantum @@ -6439,6 +6471,10 @@ Attivalo nelle impostazioni *Rete e server*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Scatta foto @@ -7719,6 +7755,10 @@ Ripetere la richiesta di connessione? I tuoi profili di chat No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Il tuo contatto ha inviato un file più grande della dimensione massima attualmente supportata (%@). diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index a545f3ba05..0b9b431c8b 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -137,6 +137,10 @@ %@ が接続を希望しています! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@や%@など%lld人のメンバー @@ -1640,6 +1644,10 @@ This is your own one-time link! コアのバージョン: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2569,6 +2577,10 @@ This is your own one-time link! アドレス変更にエラー発生 No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role 役割変更にエラー発生 @@ -2579,6 +2591,10 @@ This is your own one-time link! 設定変更にエラー発生 No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2787,6 +2803,10 @@ This is your own one-time link! チャット停止にエラー発生 No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! プロフィール切り替えにエラー発生! @@ -3942,6 +3962,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -5376,6 +5400,10 @@ Enable in *Network & servers* settings. 選択 chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5713,6 +5741,10 @@ Enable in *Network & servers* settings. リンクを送る No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -6029,6 +6061,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture 写真を撮影 @@ -7212,6 +7248,10 @@ Repeat connection request? あなたのチャットプロフィール No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). 連絡先が現在サポートされている最大サイズ (%@) より大きいファイルを送信しました。 diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 15a8c01a64..cd52825882 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -137,6 +137,10 @@ %@ wil verbinding maken! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ en %lld leden @@ -1740,6 +1744,10 @@ Dit is uw eigen eenmalige link! Core versie: v% @ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Juiste naam voor %@? @@ -2724,6 +2732,10 @@ Dit is uw eigen eenmalige link! Fout bij wijzigen van adres No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Fout bij wisselen van rol @@ -2734,6 +2746,10 @@ Dit is uw eigen eenmalige link! Fout bij wijzigen van instelling No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Fout bij het verbinden met doorstuurserver %@. Probeer het later opnieuw. @@ -2954,6 +2970,10 @@ Dit is uw eigen eenmalige link! Fout bij het stoppen van de chat No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Fout bij wisselen van profiel! @@ -4184,6 +4204,10 @@ Dit is jouw link voor groep %@! Berichtservers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Berichtbron blijft privé. @@ -5729,6 +5753,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Selecteer chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld geselecteerd @@ -6099,6 +6127,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Deel link No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Deel deze eenmalige uitnodigingslink @@ -6439,6 +6471,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Foto nemen @@ -7719,6 +7755,10 @@ Verbindingsverzoek herhalen? Uw chat profielen No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Uw contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%@). diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 525d30daa6..a474f30768 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -137,6 +137,10 @@ %@ chce się połączyć! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ i %lld członków @@ -1740,6 +1744,10 @@ To jest twój jednorazowy link! Wersja rdzenia: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Poprawić imię na %@? @@ -2724,6 +2732,10 @@ To jest twój jednorazowy link! Błąd zmiany adresu No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Błąd zmiany roli @@ -2734,6 +2746,10 @@ To jest twój jednorazowy link! Błąd zmiany ustawienia No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później. @@ -2954,6 +2970,10 @@ To jest twój jednorazowy link! Błąd zatrzymania czatu No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Błąd przełączania profilu! @@ -4184,6 +4204,10 @@ To jest twój link do grupy %@! Serwery wiadomości No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Źródło wiadomości pozostaje prywatne. @@ -5729,6 +5753,10 @@ Włącz w ustawianiach *Sieć i serwery* . Wybierz chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld Zaznaczono %lld @@ -6099,6 +6127,10 @@ Włącz w ustawianiach *Sieć i serwery* . Udostępnij link No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Udostępnij ten jednorazowy link @@ -6439,6 +6471,10 @@ Włącz w ustawianiach *Sieć i serwery* . TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Zrób zdjęcie @@ -7719,6 +7755,10 @@ Powtórzyć prośbę połączenia? Twoje profile czatu No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%@). diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 969a7d68e0..957e599e85 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -137,6 +137,10 @@ %@ хочет соединиться! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ и %lld членов группы @@ -1740,6 +1744,10 @@ This is your own one-time link! Версия ядра: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Исправить имя на %@? @@ -2724,6 +2732,10 @@ This is your own one-time link! Ошибка при изменении адреса No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Ошибка при изменении роли @@ -2734,6 +2746,10 @@ This is your own one-time link! Ошибка при изменении настройки No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Ошибка подключения к пересылающему серверу %@. Попробуйте позже. @@ -2954,6 +2970,10 @@ This is your own one-time link! Ошибка при остановке чата No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Ошибка выбора профиля! @@ -4184,6 +4204,10 @@ This is your link for group %@! Серверы сообщений No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Источник сообщения остаётся конфиденциальным. @@ -5729,6 +5753,10 @@ Enable in *Network & servers* settings. Выбрать chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld Выбрано %lld @@ -6099,6 +6127,10 @@ Enable in *Network & servers* settings. Поделиться ссылкой No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Поделиться одноразовой ссылкой-приглашением @@ -6439,6 +6471,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Сделать фото @@ -7719,6 +7755,10 @@ Repeat connection request? Ваши профили чата No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ваш контакт отправил файл, размер которого превышает максимальный размер (%@). diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 646a94a337..366f67d0cd 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -129,6 +129,10 @@ %@ อยากเชื่อมต่อ! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -1606,6 +1610,10 @@ This is your own one-time link! รุ่นหลัก: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2530,6 +2538,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการเปลี่ยนที่อยู่ No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role เกิดข้อผิดพลาดในการเปลี่ยนบทบาท @@ -2540,6 +2552,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการเปลี่ยนการตั้งค่า No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2747,6 +2763,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการหยุดแชท No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! เกิดข้อผิดพลาดในการเปลี่ยนโปรไฟล์! @@ -3901,6 +3921,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -5328,6 +5352,10 @@ Enable in *Network & servers* settings. เลือก chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5670,6 +5698,10 @@ Enable in *Network & servers* settings. แชร์ลิงก์ No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -5983,6 +6015,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture ถ่ายภาพ @@ -7163,6 +7199,10 @@ Repeat connection request? โปรไฟล์แชทของคุณ No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). ผู้ติดต่อของคุณส่งไฟล์ที่ใหญ่กว่าขนาดสูงสุดที่รองรับในปัจจุบัน (%@) diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 054f65110f..13fa3c8a84 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -137,6 +137,10 @@ %@ bağlanmak istiyor! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ ve %lld üyeleri @@ -1689,6 +1693,10 @@ Bu senin kendi tek kullanımlık bağlantın! Çekirdek sürümü: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? İsim %@ olarak düzeltilsin mi? @@ -2653,6 +2661,10 @@ Bu senin kendi tek kullanımlık bağlantın! Adres değiştirilirken hata oluştu No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Rol değiştirilirken hata oluştu @@ -2663,6 +2675,10 @@ Bu senin kendi tek kullanımlık bağlantın! Ayar değiştirilirken hata oluştu No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2878,6 +2894,10 @@ Bu senin kendi tek kullanımlık bağlantın! Sohbet durdurulurken hata oluştu No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Profil değiştirilirken hata oluştu! @@ -4084,6 +4104,10 @@ Bu senin grup için bağlantın %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Mesaj kaynağı gizli kalır. @@ -5585,6 +5609,10 @@ Enable in *Network & servers* settings. Seç chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5938,6 +5966,10 @@ Enable in *Network & servers* settings. Bağlantıyı paylaş No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Bu tek kullanımlık bağlantı davetini paylaş @@ -6264,6 +6296,10 @@ Enable in *Network & servers* settings. TCP_TVLDEKAL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Fotoğraf çek @@ -7517,6 +7553,10 @@ Bağlantı isteği tekrarlansın mı? Sohbet profillerin No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Kişiniz şu anda desteklenen maksimum boyuttan (%@) daha büyük bir dosya gönderdi. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 7bcb30c1db..69685620ba 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -137,6 +137,10 @@ %@ хоче підключитися! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ та %lld учасників @@ -1740,6 +1744,10 @@ This is your own one-time link! Основна версія: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Виправити ім'я на %@? @@ -2724,6 +2732,10 @@ This is your own one-time link! Помилка зміни адреси No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Помилка зміни ролі @@ -2734,6 +2746,10 @@ This is your own one-time link! Помилка зміни налаштування No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Помилка підключення до сервера переадресації %@. Спробуйте пізніше. @@ -2954,6 +2970,10 @@ This is your own one-time link! Помилка зупинки чату No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Помилка перемикання профілю! @@ -4184,6 +4204,10 @@ This is your link for group %@! Сервери повідомлень No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Джерело повідомлення залишається приватним. @@ -5729,6 +5753,10 @@ Enable in *Network & servers* settings. Виберіть chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld Вибрано %lld @@ -6099,6 +6127,10 @@ Enable in *Network & servers* settings. Поділіться посиланням No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Поділіться цим одноразовим посиланням-запрошенням @@ -6439,6 +6471,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Сфотографуйте @@ -7719,6 +7755,10 @@ Repeat connection request? Ваші профілі чату No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ваш контакт надіслав файл, розмір якого перевищує підтримуваний на цей момент максимальний розмір (%@). diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 8c3641549d..b524846ffd 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -135,6 +135,10 @@ %@ 要连接! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ 和 %lld 成员 @@ -1665,6 +1669,10 @@ This is your own one-time link! 核心版本: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2618,6 +2626,10 @@ This is your own one-time link! 更改地址错误 No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role 更改角色错误 @@ -2628,6 +2640,10 @@ This is your own one-time link! 更改设置错误 No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2841,6 +2857,10 @@ This is your own one-time link! 停止聊天错误 No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! 切换资料错误! @@ -4033,6 +4053,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. 消息来源保持私密。 @@ -5512,6 +5536,10 @@ Enable in *Network & servers* settings. 选择 chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5861,6 +5889,10 @@ Enable in *Network & servers* settings. 分享链接 No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link 分享此一次性邀请链接 @@ -6185,6 +6217,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture 拍照 @@ -7417,6 +7453,10 @@ Repeat connection request? 您的聊天资料 No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). 您的联系人发送的文件大于当前支持的最大大小 (%@)。 From e582d2d742120ccc208b208f70f9d95878f91551 Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 27 Aug 2024 14:32:54 +0100 Subject: [PATCH 059/440] android, desktop: allow for chat profile selection on new chat screen (#4741) * add api and types * basic ui * add search on profiles * profile images on select chat profile * incognito adjustments * basic api connection * handling errors * add loading state * header to scroll * selected profile on top (profile or incognito) * adjust share profile copy * avoid list moving around on selection commit * bigger profile pick * info icon interactive area * thumbs to match contacts list size * incognito sizes matching icons * title to section padding * add chevron * align borders and other chevron icon * prevent click on self * only prevent selection * update * selectable item area * no need for oninfo to be composable * simplify * wrap apis in try * remove redundant derivedStateOf * closure fns capital naming * simplify current user null check --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/ChatModel.kt | 3 +- .../chat/simplex/common/model/SimpleXAPI.kt | 35 +- .../newchat/ContactConnectionInfoView.kt | 2 +- .../common/views/newchat/NewChatView.kt | 338 +++++++++++++++++- .../views/usersettings/UserProfilesView.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 4 + 6 files changed, 363 insertions(+), 23 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index e92b3d714a..de13f05dbd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -784,7 +784,8 @@ object ChatModel { data class ShowingInvitation( val connId: String, val connReq: String, - val connChatUsed: Boolean + val connChatUsed: Boolean, + val conn: PendingContactConnection ) enum class ChatType(val type: String) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 060e75a9a1..c621b9eacf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1132,9 +1132,30 @@ object ChatController { suspend fun apiSetConnectionIncognito(rh: Long?, connId: Long, incognito: Boolean): PendingContactConnection? { val r = sendCmd(rh, CC.ApiSetConnectionIncognito(connId, incognito)) - if (r is CR.ConnectionIncognitoUpdated) return r.toConnection - Log.e(TAG, "apiSetConnectionIncognito bad response: ${r.responseType} ${r.details}") - return null + + return when (r) { + is CR.ConnectionIncognitoUpdated -> r.toConnection + else -> { + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiSetConnectionIncognito", generalGetString(MR.strings.error_sending_message), r) + } + null + } + } + } + + suspend fun apiChangeConnectionUser(rh: Long?, connId: Long, userId: Long): PendingContactConnection? { + val r = sendCmd(rh, CC.ApiChangeConnectionUser(connId, userId)) + + return when (r) { + is CR.ConnectionUserChanged -> r.toConnection + else -> { + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiChangeConnectionUser", generalGetString(MR.strings.error_sending_message), r) + } + null + } + } } suspend fun apiConnectPlan(rh: Long?, connReq: String): ConnectionPlan? { @@ -2916,6 +2937,7 @@ sealed class CC { class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC() class APIAddContact(val userId: Long, val incognito: Boolean): CC() class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC() + class ApiChangeConnectionUser(val connId: Long, val userId: Long): CC() class APIConnectPlan(val userId: Long, val connReq: String): CC() class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC() class ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC() @@ -3071,6 +3093,7 @@ sealed class CC { is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else "" is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}" is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}" + is ApiChangeConnectionUser -> "/_set conn user :$connId $userId" is APIConnectPlan -> "/_connect plan $userId $connReq" is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq" is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId" @@ -3213,6 +3236,7 @@ sealed class CC { is APIVerifyGroupMember -> "apiVerifyGroupMember" is APIAddContact -> "apiAddContact" is ApiSetConnectionIncognito -> "apiSetConnectionIncognito" + is ApiChangeConnectionUser -> "apiChangeConnectionUser" is APIConnectPlan -> "apiConnectPlan" is APIConnect -> "apiConnect" is ApiConnectContactViaAddress -> "apiConnectContactViaAddress" @@ -4757,6 +4781,7 @@ sealed class CR { @Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR() @Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connReqInvitation: String, val connection: PendingContactConnection): CR() @Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR() + @Serializable @SerialName("connectionUserChanged") class ConnectionUserChanged(val user: UserRef, val fromConnection: PendingContactConnection, val toConnection: PendingContactConnection, val newUser: UserRef): CR() @Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connectionPlan: ConnectionPlan): CR() @Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef, val connection: PendingContactConnection): CR() @Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef, val connection: PendingContactConnection): CR() @@ -4935,6 +4960,7 @@ sealed class CR { is ConnectionVerified -> "connectionVerified" is Invitation -> "invitation" is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated" + is ConnectionUserChanged -> "ConnectionUserChanged" is CRConnectionPlan -> "connectionPlan" is SentConfirmation -> "sentConfirmation" is SentInvitation -> "sentInvitation" @@ -5103,6 +5129,7 @@ sealed class CR { is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode") is Invitation -> withUser(user, "connReqInvitation: $connReqInvitation\nconnection: $connection") is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection)) + is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" ) is CRConnectionPlan -> withUser(user, json.encodeToString(connectionPlan)) is SentConfirmation -> withUser(user, json.encodeToString(connection)) is SentInvitation -> withUser(user, json.encodeToString(connection)) @@ -5553,6 +5580,7 @@ sealed class ChatErrorType { is AgentCommandError -> "agentCommandError" is InvalidFileDescription -> "invalidFileDescription" is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited" + is ConnectionUserChangeProhibited -> "connectionUserChangeProhibited" is PeerChatVRangeIncompatible -> "peerChatVRangeIncompatible" is InternalError -> "internalError" is CEException -> "exception $message" @@ -5630,6 +5658,7 @@ sealed class ChatErrorType { @Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType() @Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType() @Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType() + @Serializable @SerialName("connectionUserChangeProhibited") object ConnectionUserChangeProhibited: ChatErrorType() @Serializable @SerialName("peerChatVRangeIncompatible") object PeerChatVRangeIncompatible: ChatErrorType() @Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType() @Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 88e483e92d..64ff7e4f40 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -39,7 +39,7 @@ fun ContactConnectionInfoView( ) { LaunchedEffect(connReqInvitation) { if (connReqInvitation != null) { - chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false) + chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false, conn = contactConnection) } } /** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation]. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index d2e8ac7a6c..544f5f72bb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -2,20 +2,25 @@ package chat.simplex.common.views.newchat import SectionBottomSpacer import SectionItemView +import SectionSpacer import SectionTextFooter import SectionView +import TextIconSpaced import androidx.compose.foundation.* -import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.TextStyle @@ -32,6 +37,7 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.net.URI @@ -43,7 +49,7 @@ enum class NewChatOption { fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRCodeScanner: Boolean = false, close: () -> Unit) { val selection = remember { stateGetOrPut("selection") { selection } } val showQRCodeScanner = remember { stateGetOrPut("showQRCodeScanner") { showQRCodeScanner } } - val contactConnection: MutableState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(null) } + val contactConnection: MutableState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(chatModel.showingInvitation.value?.conn) } val connReqInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connReq ?: "" } } val creatingConnReq = rememberSaveable { mutableStateOf(false) } val pastedLink = rememberSaveable { mutableStateOf("") } @@ -177,6 +183,15 @@ private fun CreatingLinkProgressView() { DefaultProgressView(stringResource(MR.strings.creating_link)) } +private fun updateShownConnection(conn: PendingContactConnection) { + chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy( + conn = conn, + connId = conn.id, + connReq = conn.connReqInv ?: "", + connChatUsed = true + ) +} + @Composable private fun RetryButton(onClick: () -> Unit) { Column( @@ -192,6 +207,248 @@ private fun RetryButton(onClick: () -> Unit) { } } +@Composable +private fun ProfilePickerOption( + title: String, + selected: Boolean, + disabled: Boolean, + onSelected: () -> Unit, + image: @Composable () -> Unit, + onInfo: (() -> Unit)? = null +) { + Row( + Modifier + .fillMaxWidth() + .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp) + .clickable(enabled = !disabled, onClick = onSelected) + .padding(horizontal = DEFAULT_PADDING, vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + image() + TextIconSpaced(false) + Text(title, modifier = Modifier.align(Alignment.CenterVertically)) + if (onInfo != null) { + Spacer(Modifier.padding(6.dp)) + Column(Modifier + .size(48.dp) + .clip(CircleShape) + .clickable( + enabled = !disabled, + onClick = { ModalManager.start.showModal { IncognitoView() } } + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painterResource(MR.images.ic_info), + stringResource(MR.strings.incognito), + tint = MaterialTheme.colors.primary + ) + } + } + Spacer(Modifier.weight(1f)) + if (selected) { + Icon( + painterResource( + MR.images.ic_check + ), + title, + Modifier.size(20.dp), + tint = MaterialTheme.colors.primary, + ) + } + } + Divider( + Modifier.padding( + start = DEFAULT_PADDING_HALF, + end = DEFAULT_PADDING_HALF, + ) + ) +} + +private fun filteredProfiles(users: List, searchTextOrPassword: String): List { + val s = searchTextOrPassword.trim() + val lower = s.lowercase() + return users.filter { u -> + if ((u.activeUser || !u.hidden) && (s == "" || u.anyNameContains(lower))) { + true + } else { + correctPassword(u, s) + } + } +} + +@Composable +private fun ActiveProfilePicker( + search: MutableState, + contactConnection: PendingContactConnection?, + close: () -> Unit, + rhId: Long? +) { + val switchingProfile = remember { mutableStateOf(false) } + val incognito = remember { + chatModel.showingInvitation.value?.conn?.incognito ?: controller.appPrefs.incognito.get() + } + val selectedProfile by remember { chatModel.currentUser } + val searchTextOrPassword = rememberSaveable { search } + val profiles = remember { + chatModel.users.map { it.user }.sortedBy { !it.activeUser } + } + val filteredProfiles by remember { + derivedStateOf { filteredProfiles(profiles, searchTextOrPassword.value) } + } + + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(switchingProfile.value) { + progressByTimeout = if (switchingProfile.value) { + delay(500) + switchingProfile.value + } else { + false + } + } + + @Composable + fun ProfilePickerUserOption(user: User) { + val selected = selectedProfile?.userId == user.userId && !incognito + + ProfilePickerOption( + title = user.chatViewName, + disabled = switchingProfile.value || selected, + selected = selected, + onSelected = { + switchingProfile.value = true + withApi { + try { + if (contactConnection != null) { + val conn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId) + if (conn != null) { + withChats { + updateContactConnection(rhId, conn) + updateShownConnection(conn) + } + controller.changeActiveUser_( + rhId = user.remoteHostId, + toUserId = user.userId, + viewPwd = if (user.hidden) searchTextOrPassword.value else null + ) + + if (chatModel.currentUser.value?.userId != user.userId) { + AlertManager.shared.showAlertMsg(generalGetString( + MR.strings.switching_profile_error_title), + String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName) + ) + } + + withChats { + updateContactConnection(user.remoteHostId, conn) + } + close.invoke() + } + } + } finally { + switchingProfile.value = false + } + } + }, + image = { ProfileImage(size = 42.dp, image = user.image) } + ) + } + + @Composable + fun IncognitoUserOption() { + ProfilePickerOption( + disabled = switchingProfile.value, + title = stringResource(MR.strings.incognito), + selected = incognito, + onSelected = { + if (!incognito) { + switchingProfile.value = true + withApi { + try { + if (contactConnection != null) { + val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) + + if (conn != null) { + withChats { + updateContactConnection(rhId, conn) + updateShownConnection(conn) + } + close.invoke() + } + } + } finally { + switchingProfile.value = false + } + } + } + }, + image = { + Spacer(Modifier.width(8.dp)) + Icon( + painterResource(MR.images.ic_theater_comedy_filled), + contentDescription = stringResource(MR.strings.incognito), + Modifier.size(32.dp), + tint = Indigo, + ) + Spacer(Modifier.width(2.dp)) + }, + onInfo = { ModalManager.start.showModal { IncognitoView() } }, + ) + } + + BoxWithConstraints { + Column( + Modifier + .fillMaxSize() + .alpha(if (progressByTimeout) 0.6f else 1f) + ) { + LazyColumnWithScrollBar(userScrollEnabled = !switchingProfile.value) { + item { + AppBarTitle(stringResource(MR.strings.select_chat_profile), hostDevice(rhId), bottomPadding = DEFAULT_PADDING) + } + val activeProfile = filteredProfiles.firstOrNull { it.activeUser } + + if (activeProfile != null) { + val otherProfiles = filteredProfiles.filter { it.userId != activeProfile.userId } + + if (incognito) { + item { + IncognitoUserOption() + } + item { + ProfilePickerUserOption(activeProfile) + } + } else { + item { + ProfilePickerUserOption(activeProfile) + } + item { + IncognitoUserOption() + } + } + + itemsIndexed(otherProfiles) { _, p -> + ProfilePickerUserOption(p) + } + } else { + item { + IncognitoUserOption() + } + itemsIndexed(filteredProfiles) { _, p -> + ProfilePickerUserOption(p) + } + } + } + } + if (progressByTimeout) { + DefaultProgressView("") + } + } +} + @Composable private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState) { SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) { @@ -204,23 +461,72 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() }) } - Spacer(Modifier.height(10.dp)) - val incognito = remember { mutableStateOf(controller.appPrefs.incognito.get()) } - IncognitoToggle(controller.appPrefs.incognito, incognito) { - ModalManager.start.showModal { IncognitoView() } + Spacer(Modifier.height(DEFAULT_PADDING)) + val incognito by remember(chatModel.showingInvitation.value?.conn?.incognito, controller.appPrefs.incognito.get()) { + derivedStateOf { + chatModel.showingInvitation.value?.conn?.incognito ?: controller.appPrefs.incognito.get() + } } - KeyChangeEffect(incognito.value) { - withBGApi { - val contactConn = contactConnection.value ?: return@withBGApi - val conn = controller.apiSetConnectionIncognito(rhId, contactConn.pccConnId, incognito.value) ?: return@withBGApi - withChats { - contactConnection.value = conn - updateContactConnection(rhId, conn) + val currentUser = remember { chatModel.currentUser }.value + + if (currentUser != null) { + SectionView(stringResource(MR.strings.new_chat_share_profile).uppercase(), headerBottomPadding = 5.dp) { + SectionItemView( + padding = PaddingValues( + top = 0.dp, + bottom = 0.dp, + start = 16.dp, + end = 16.dp + ), + click = { + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { + ActiveProfilePicker( + search = search, + close = close, + rhId = rhId, + contactConnection = contactConnection.value + ) + }) + } + } + ) { + if (incognito) { + Spacer(Modifier.width(8.dp)) + Icon( + painterResource(MR.images.ic_theater_comedy_filled), + contentDescription = stringResource(MR.strings.incognito), + tint = Indigo, + modifier = Modifier.size(32.dp) + ) + Spacer(Modifier.width(2.dp)) + } else { + ProfileImage(size = 42.dp, image = currentUser.image) + } + TextIconSpaced(false) + Text( + text = if (incognito) stringResource(MR.strings.incognito) else currentUser.chatViewName, + color = MaterialTheme.colors.onBackground + ) + Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.End) { + Icon( + painter = painterResource(MR.images.ic_arrow_forward_ios), + contentDescription = stringResource(MR.strings.new_chat_share_profile), + tint = MaterialTheme.colors.secondary, + ) + } } } - chatModel.markShowingInvitationUsed() + if (incognito) { + SectionTextFooter(generalGetString(MR.strings.connect__a_new_random_profile_will_be_shared)) + } } - SectionTextFooter(sharedProfileInfo(chatModel, incognito.value)) } @Composable @@ -366,7 +672,7 @@ private fun createInvitation( if (r != null) { withChats { updateContactConnection(rhId, r.second) - chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false) + chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false, conn = r.second) contactConnection.value = r.second } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index a7bf5920e4..d4334dfed2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -303,7 +303,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: ( } } -private fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List { +fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List { val s = searchTextOrPassword.trim() val lower = s.lowercase() return m.users.filter { u -> @@ -317,7 +317,7 @@ private fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List !u.user.hidden }.size -private fun correctPassword(user: User, pwd: String): Boolean { +fun correctPassword(user: User, pwd: String): Boolean { val ph = user.viewPwdHash return ph != null && pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index f5272ce7fc..9e9beef86b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -665,6 +665,10 @@ 1-time link SimpleX address Or show this code + Share profile + Select chat profile + Error switching profile + Your connection was moved to %s but an unexpected error occurred while redirecting you to the profile. Or scan QR code Keep unused invitation? You can view invitation link again in connection details. From 05e7f350378b01c700b934adf9850f3d00291df3 Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 27 Aug 2024 22:12:55 +0100 Subject: [PATCH 060/440] core: fix associated agent user for recreated connections (#4771) * core: fix associated user for recreated connections * fix test for connection recreation --- src/Simplex/Chat.hs | 2 +- tests/ChatTests/Profiles.hs | 67 +++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index dc3b4b2e54..796a128abe 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1709,7 +1709,7 @@ processChatCommand' vr = \case pure conn' recreateConn user conn@PendingContactConnection {customUserProfileId} newUser = do subMode <- chatReadVar subscriptionMode - (agConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOn subMode + (agConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId newUser) True SCMInvitation Nothing IKPQOn subMode conn' <- withFastStore' $ \db -> do deleteConnectionRecord db user connId forM_ customUserProfileId $ \profileId -> diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 878546ba21..a36eef8ca9 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PostfixOperators #-} +{-# LANGUAGE TypeApplications #-} module ChatTests.Profiles where @@ -18,6 +19,8 @@ import Simplex.Chat.Types (ConnStatus (..), Profile (..)) import Simplex.Chat.Types.Shared (GroupMemberRole (..)) import Simplex.Chat.Types.UITheme import Simplex.Messaging.Encoding.String (StrEncoding (..)) +import Simplex.Messaging.Server.Env.STM hiding (subscriptions) +import Simplex.Messaging.Transport import Simplex.Messaging.Util (encodeJSON) import System.Directory (copyFile, createDirectoryIfMissing) import Test.Hspec hiding (it) @@ -1653,34 +1656,42 @@ testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ ] testChangePCCUserDiffSrv :: HasCallStack => FilePath -> IO () -testChangePCCUserDiffSrv = testChat2 aliceProfile bobProfile $ - \alice bob -> do - -- Create a new invite - alice ##> "/connect" - _ <- getInvitation alice - alice ##> "/_set incognito :1 on" - alice <## "connection 1 changed to incognito" - -- Create new user with different servers - alice ##> "/create user alisa" - showActiveUser alice "alisa" - alice #$> ("/smp smp://2345-w==@smp2.example.im smp://3456-w==@smp3.example.im:5224", id, "ok") - alice ##> "/user alice" - showActiveUser alice "alice (Alice)" - -- Change connection to newly created user and use the newly created connection - alice ##> "/_set conn user :1 2" - alice <## "connection 1 changed from user alice to user alisa, new link:" - alice <## "" - inv <- getTermLine alice - alice <## "" - alice `hasContactProfiles` ["alice"] - alice ##> "/user alisa" - showActiveUser alice "alisa" - -- Connect - bob ##> ("/connect " <> inv) - bob <## "confirmation sent!" - concurrently_ - (alice <## "bob (Bob): contact is connected") - (bob <## "alisa: contact is connected") +testChangePCCUserDiffSrv tmp = do + withSmpServer' serverCfg' $ do + withNewTestChatCfgOpts tmp testCfg testOpts "alice" aliceProfile $ \alice -> do + withNewTestChatCfgOpts tmp testCfg testOpts "bob" bobProfile $ \bob -> do + -- Create a new invite + alice ##> "/connect" + _ <- getInvitation alice + alice ##> "/_set incognito :1 on" + alice <## "connection 1 changed to incognito" + -- Create new user with different servers + alice ##> "/create user alisa" + showActiveUser alice "alisa" + alice #$> ("/smp smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003", id, "ok") + alice ##> "/user alice" + showActiveUser alice "alice (Alice)" + -- Change connection to newly created user and use the newly created connection + alice ##> "/_set conn user :1 2" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" + alice `hasContactProfiles` ["alice"] + alice ##> "/user alisa" + showActiveUser alice "alisa" + -- Connect + bob ##> ("/connect " <> inv) + bob <## "confirmation sent!" + concurrently_ + (alice <## "bob (Bob): contact is connected") + (bob <## "alisa: contact is connected") + where + serverCfg' = + smpServerCfg + { transports = [("7003", transport @TLS), ("7002", transport @TLS)], + msgQueueQuota = 2 + } testSetConnectionAlias :: HasCallStack => FilePath -> IO () testSetConnectionAlias = testChat2 aliceProfile bobProfile $ From 8cc075eda8a958ccfe1629a9d9f253c3083c6851 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 27 Aug 2024 22:13:20 +0100 Subject: [PATCH 061/440] ios: show correct message times (#4779) --- apps/ios/Shared/Views/Chat/ChatView.swift | 6 +++--- apps/ios/SimpleXChat/ChatTypes.swift | 20 +------------------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index d94be2bb81..acdcabd7e2 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -723,11 +723,11 @@ struct ChatView: View { let im = ItemsModel.shared if let i, i > 0 && im.reversedChatItems.count >= i { let nextItem = im.reversedChatItems[i - 1] - let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.createdAt.timeIntervalSince(chatItem.meta.createdAt) > 60 + let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.itemTs.timeIntervalSince(chatItem.meta.itemTs) > 60 return ( - timestamp: largeGap || formatTimestampText(chatItem.meta.createdAt) != formatTimestampText(nextItem.meta.createdAt), + timestamp: largeGap || formatTimestampText(chatItem.meta.itemTs) != formatTimestampText(nextItem.meta.itemTs), largeGap: largeGap, - date: Calendar.current.isDate(chatItem.meta.createdAt, inSameDayAs: nextItem.meta.createdAt) ? nil : nextItem.meta.createdAt + date: Calendar.current.isDate(chatItem.meta.itemTs, inSameDayAs: nextItem.meta.itemTs) ? nil : nextItem.meta.itemTs ) } else { return (timestamp: true, largeGap: true, date: nil) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index d51e5a7cc3..07340bb963 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2765,26 +2765,8 @@ public struct CITimed: Decodable, Hashable { public var deleteAt: Date? } -let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() -let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) - public func formatTimestampText(_ date: Date) -> Text { - Text(verbatim: date.formatted(recent(date) ? msgTimeFormat : msgDateFormat)) -} - -private func recent(_ date: Date) -> Bool { - let now = Date() - let calendar = Calendar.current - - guard let previousDay = calendar.date(byAdding: DateComponents(day: -1), to: now), - let previousDay18 = calendar.date(bySettingHour: 18, minute: 0, second: 0, of: previousDay), - let currentDay00 = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: now), - let currentDay12 = calendar.date(bySettingHour: 12, minute: 0, second: 0, of: now) else { - return false - } - - let isSameDay = calendar.isDate(date, inSameDayAs: now) - return isSameDay || (now < currentDay12 && date >= previousDay18 && date < currentDay00) + Text(verbatim: date.formatted(date: .omitted, time: .shortened)) } public enum CIStatus: Decodable, Hashable { From 121eaf60738b7b94d63b8b8548b04a4f183b0502 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:39:28 +0400 Subject: [PATCH 062/440] flatpak: update metainfo (#4784) * flatpak: update metainfo * flatpak: change release link and ol to ul --- .../flatpak/chat.simplex.simplex.metainfo.xml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index a74a7b86a0..4ac5f88e14 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,38 @@ + + https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html + +

    New in v6.0.1-3:

    +
      +
    • reduce app memory usage and start time.
    • +
    • faster sending files to groups.
    • +
    • fix rare delivery bug.
    • +
    +

    New in v6.0:

    +

    New chat experience:

    +
      +
    • connect to your friends faster.
    • +
    • archive contacts to chat later.
    • +
    • delete up to 20 messages at once.
    • +
    • increase font size.
    • +
    +

    New media options:

    +
      +
    • play from the chat list.
    • +
    • blur for better privacy.
    • +
    +

    Private routing:

    +
      +
    • it protects your IP address and connections and is now enabled by default.
    • +
    +

    Connection and servers information:

    +
      +
    • to control your network status and usage.
    • +
    +
    +
    https://github.com/simplex-chat/simplex-chat/releases/tag/v6.0.0 From acb372a4ce0074374458a0bae1f5ac863d64f3d0 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:31:02 +0000 Subject: [PATCH 063/440] core: call uuid (#4777) * core: call uuid * fix * text * android, desktop * ios --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 2 +- .../Shared/Views/Call/ActiveCallView.swift | 8 ++-- .../Shared/Views/Call/CallController.swift | 42 +++++++++++-------- apps/ios/Shared/Views/Call/CallManager.swift | 26 ++++++------ apps/ios/Shared/Views/Call/WebRTC.swift | 6 +-- apps/ios/Shared/Views/Chat/ChatView.swift | 4 +- apps/ios/SimpleXChat/CallTypes.swift | 5 +-- .../simplex/app/views/call/CallActivity.kt | 1 + .../simplex/common/views/call/CallManager.kt | 1 + .../views/call/IncomingCallAlertView.kt | 1 + .../chat/simplex/common/views/call/WebRTC.kt | 2 + .../simplex/common/views/chat/ChatView.kt | 2 +- package.yaml | 1 + simplex-chat.cabal | 8 ++++ src/Simplex/Chat.hs | 18 ++++---- src/Simplex/Chat/Call.hs | 2 + .../Chat/Migrations/M20240827_calls_uuid.hs | 18 ++++++++ src/Simplex/Chat/Migrations/chat_schema.sql | 2 + src/Simplex/Chat/Store/Migrations.hs | 4 +- src/Simplex/Chat/Store/Profiles.hs | 14 +++---- 20 files changed, 107 insertions(+), 60 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 239ef7916e..797e68db4f 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2199,7 +2199,7 @@ func activateCall(_ callInvitation: RcvCallInvitation) { CallController.shared.reportNewIncomingCall(invitation: callInvitation) { error in if let error = error { DispatchQueue.main.async { - m.callInvitations[callInvitation.contact.id]?.callkitUUID = nil + m.callInvitations[callInvitation.contact.id]?.callUUID = nil } logger.error("reportNewIncomingCall error: \(error.localizedDescription)") } else { diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index 97415018bf..d238c2dbae 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -185,7 +185,7 @@ struct ActiveCallView: View { case .ended: closeCallView(client) call.callState = .ended - if let uuid = call.callkitUUID { + if let uuid = call.callUUID { CallController.shared.endCall(callUUID: uuid) } case .ok: @@ -382,7 +382,7 @@ struct ActiveCallOverlay: View { private func endCallButton() -> some View { let cc = CallController.shared return callButton("phone.down.fill", width: 60, height: 60) { - if let uuid = call.callkitUUID { + if let uuid = call.callUUID { cc.endCall(callUUID: uuid) } else { cc.endCall(call: call) {} @@ -462,9 +462,9 @@ struct ActiveCallOverlay: View { struct ActiveCallOverlay_Previews: PreviewProvider { static var previews: some View { Group{ - ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callkitUUID: UUID(), callState: .offerSent, localMedia: .video), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) + ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callUUID: UUID().uuidString.lowercased(), callState: .offerSent, localMedia: .video), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) .background(.black) - ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callkitUUID: UUID(), callState: .offerSent, localMedia: .audio), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) + ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callUUID: UUID().uuidString.lowercased(), callState: .offerSent, localMedia: .audio), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) .background(.black) } } diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index a8a91057fa..bfa26700e5 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -51,7 +51,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func provider(_ provider: CXProvider, perform action: CXStartCallAction) { logger.debug("CallController.provider CXStartCallAction") - if callManager.startOutgoingCall(callUUID: action.callUUID) { + if callManager.startOutgoingCall(callUUID: action.callUUID.uuidString.lowercased()) { action.fulfill() provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil) } else { @@ -61,7 +61,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { logger.debug("CallController.provider CXAnswerCallAction") - if callManager.answerIncomingCall(callUUID: action.callUUID) { + if callManager.answerIncomingCall(callUUID: action.callUUID.uuidString.lowercased()) { // WebRTC call should be in connected state to fulfill. // Otherwise no audio and mic working on lockscreen fulfillOnConnect = action @@ -75,7 +75,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse // Should be nil here if connection was in connected state fulfillOnConnect?.fail() fulfillOnConnect = nil - callManager.endCall(callUUID: action.callUUID) { ok in + callManager.endCall(callUUID: action.callUUID.uuidString.lowercased()) { ok in if ok { action.fulfill() } else { @@ -86,7 +86,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { - if callManager.enableMedia(media: .audio, enable: !action.isMuted, callUUID: action.callUUID) { + if callManager.enableMedia(media: .audio, enable: !action.isMuted, callUUID: action.callUUID.uuidString.lowercased()) { action.fulfill() } else { action.fail() @@ -194,7 +194,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse if let contactId = payload.dictionaryPayload["contactId"] as? String, let invitation = m.callInvitations[contactId] { let update = self.cxCallUpdate(invitation: invitation) - if let uuid = invitation.callkitUUID { + if let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { logger.debug("CallController: report pushkit call via CallKit") let update = self.cxCallUpdate(invitation: invitation) self.provider.reportNewIncomingCall(with: uuid, update: update) { error in @@ -239,8 +239,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) { - logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID))") - if CallController.useCallKit(), let uuid = invitation.callkitUUID { + logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callUUID))") + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { if invitation.callTs.timeIntervalSinceNow >= -180 { let update = cxCallUpdate(invitation: invitation) provider.reportNewIncomingCall(with: uuid, update: update, completion: completion) @@ -272,14 +272,14 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) { logger.debug("CallController: reporting outgoing call connected") - if CallController.useCallKit(), let uuid = call.callkitUUID { + if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected) } } func reportCallRemoteEnded(invitation: RcvCallInvitation) { logger.debug("CallController: reporting remote ended") - if CallController.useCallKit(), let uuid = invitation.callkitUUID { + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) } else if invitation.contact.id == activeCallInvitation?.contact.id { activeCallInvitation = nil @@ -288,14 +288,17 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func reportCallRemoteEnded(call: Call) { logger.debug("CallController: reporting remote ended") - if CallController.useCallKit(), let uuid = call.callkitUUID { + if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) } } func startCall(_ contact: Contact, _ media: CallMediaType) { logger.debug("CallController.startCall") - let uuid = callManager.newOutgoingCall(contact, media) + let callUUID = callManager.newOutgoingCall(contact, media) + guard let uuid = UUID(uuidString: callUUID) else { + return + } if CallController.useCallKit() { let handle = CXHandle(type: .generic, value: contact.id) let action = CXStartCallAction(call: uuid, handle: handle) @@ -307,8 +310,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse update.localizedCallerName = contact.displayName self.provider.reportCall(with: uuid, updated: update) } - } else if callManager.startOutgoingCall(callUUID: uuid) { - if callManager.startOutgoingCall(callUUID: uuid) { + } else if callManager.startOutgoingCall(callUUID: callUUID) { + if callManager.startOutgoingCall(callUUID: callUUID) { logger.debug("CallController.startCall: call started") } else { logger.error("CallController.startCall: no active call") @@ -318,8 +321,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func answerCall(invitation: RcvCallInvitation) { logger.debug("CallController: answering a call") - if CallController.useCallKit(), let callUUID = invitation.callkitUUID { - requestTransaction(with: CXAnswerCallAction(call: callUUID)) + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { + requestTransaction(with: CXAnswerCallAction(call: uuid)) } else { callManager.answerIncomingCall(invitation: invitation) } @@ -328,10 +331,13 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } } - func endCall(callUUID: UUID) { - logger.debug("CallController: ending the call with UUID \(callUUID.uuidString)") + func endCall(callUUID: String) { + let uuid = UUID(uuidString: callUUID) + logger.debug("CallController: ending the call with UUID \(callUUID)") if CallController.useCallKit() { - requestTransaction(with: CXEndCallAction(call: callUUID)) + if let uuid { + requestTransaction(with: CXEndCallAction(call: uuid)) + } } else { callManager.endCall(callUUID: callUUID) { ok in if ok { diff --git a/apps/ios/Shared/Views/Call/CallManager.swift b/apps/ios/Shared/Views/Call/CallManager.swift index a6d5ea17c4..f3021815af 100644 --- a/apps/ios/Shared/Views/Call/CallManager.swift +++ b/apps/ios/Shared/Views/Call/CallManager.swift @@ -10,17 +10,17 @@ import Foundation import SimpleXChat class CallManager { - func newOutgoingCall(_ contact: Contact, _ media: CallMediaType) -> UUID { - let uuid = UUID() - let call = Call(direction: .outgoing, contact: contact, callkitUUID: uuid, callState: .waitCapabilities, localMedia: media) + func newOutgoingCall(_ contact: Contact, _ media: CallMediaType) -> String { + let uuid = UUID().uuidString.lowercased() + let call = Call(direction: .outgoing, contact: contact, callUUID: uuid, callState: .waitCapabilities, localMedia: media) call.speakerEnabled = media == .video ChatModel.shared.activeCall = call return uuid } - func startOutgoingCall(callUUID: UUID) -> Bool { + func startOutgoingCall(callUUID: String) -> Bool { let m = ChatModel.shared - if let call = m.activeCall, call.callkitUUID == callUUID { + if let call = m.activeCall, call.callUUID == callUUID { m.showCallView = true Task { await m.callCommand.processCommand(.capabilities(media: call.localMedia)) } return true @@ -28,7 +28,7 @@ class CallManager { return false } - func answerIncomingCall(callUUID: UUID) -> Bool { + func answerIncomingCall(callUUID: String) -> Bool { if let invitation = getCallInvitation(callUUID) { answerIncomingCall(invitation: invitation) return true @@ -42,7 +42,7 @@ class CallManager { let call = Call( direction: .incoming, contact: invitation.contact, - callkitUUID: invitation.callkitUUID, + callUUID: invitation.callUUID, callState: .invitationAccepted, localMedia: invitation.callType.media, sharedKey: invitation.sharedKey @@ -68,8 +68,8 @@ class CallManager { } } - func enableMedia(media: CallMediaType, enable: Bool, callUUID: UUID) -> Bool { - if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID { + func enableMedia(media: CallMediaType, enable: Bool, callUUID: String) -> Bool { + if let call = ChatModel.shared.activeCall, call.callUUID == callUUID { let m = ChatModel.shared Task { await m.callCommand.processCommand(.media(media: media, enable: enable)) } return true @@ -77,8 +77,8 @@ class CallManager { return false } - func endCall(callUUID: UUID, completed: @escaping (Bool) -> Void) { - if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID { + func endCall(callUUID: String, completed: @escaping (Bool) -> Void) { + if let call = ChatModel.shared.activeCall, call.callUUID == callUUID { endCall(call: call) { completed(true) } } else if let invitation = getCallInvitation(callUUID) { endCall(invitation: invitation) { completed(true) } @@ -126,8 +126,8 @@ class CallManager { } } - private func getCallInvitation(_ callUUID: UUID) -> RcvCallInvitation? { - if let (_, invitation) = ChatModel.shared.callInvitations.first(where: { (_, inv) in inv.callkitUUID == callUUID }) { + private func getCallInvitation(_ callUUID: String) -> RcvCallInvitation? { + if let (_, invitation) = ChatModel.shared.callInvitations.first(where: { (_, inv) in inv.callUUID == callUUID }) { return invitation } return nil diff --git a/apps/ios/Shared/Views/Call/WebRTC.swift b/apps/ios/Shared/Views/Call/WebRTC.swift index 333dc082d5..ba990981a1 100644 --- a/apps/ios/Shared/Views/Call/WebRTC.swift +++ b/apps/ios/Shared/Views/Call/WebRTC.swift @@ -18,7 +18,7 @@ class Call: ObservableObject, Equatable { var direction: CallDirection var contact: Contact - var callkitUUID: UUID? + var callUUID: String? var localMedia: CallMediaType @Published var callState: CallState @Published var localCapabilities: CallCapabilities? @@ -33,14 +33,14 @@ class Call: ObservableObject, Equatable { init( direction: CallDirection, contact: Contact, - callkitUUID: UUID?, + callUUID: String?, callState: CallState, localMedia: CallMediaType, sharedKey: String? = nil ) { self.direction = direction self.contact = contact - self.callkitUUID = callkitUUID + self.callUUID = callUUID self.callState = callState self.localMedia = localMedia self.sharedKey = sharedKey diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 655dd8aaed..d65fbc1ed6 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -568,8 +568,8 @@ struct ChatView: View { private func endCallButton(_ call: Call) -> some View { Button { - if let uuid = call.callkitUUID { - CallController.shared.endCall(callUUID: uuid) + if CallController.useCallKit(), let callUUID = call.callUUID { + CallController.shared.endCall(callUUID: callUUID) } else { CallController.shared.endCall(call: call) {} } diff --git a/apps/ios/SimpleXChat/CallTypes.swift b/apps/ios/SimpleXChat/CallTypes.swift index 227a1fbda5..9f6d98e518 100644 --- a/apps/ios/SimpleXChat/CallTypes.swift +++ b/apps/ios/SimpleXChat/CallTypes.swift @@ -42,6 +42,7 @@ public struct RcvCallInvitation: Decodable { public var contact: Contact public var callType: CallType public var sharedKey: String? + public var callUUID: String? public var callTs: Date public var callTypeText: LocalizedStringKey { get { @@ -52,10 +53,8 @@ public struct RcvCallInvitation: Decodable { } } - public var callkitUUID: UUID? = UUID() - private enum CodingKeys: String, CodingKey { - case user, contact, callType, sharedKey, callTs + case user, contact, callType, sharedKey, callUUID, callTs } public static let sampleData = RcvCallInvitation( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index b78f3ac518..323eb4417b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -424,6 +424,7 @@ fun PreviewIncomingCallLockScreenAlert() { ) { IncomingCallLockScreenAlertLayout( invitation = RcvCallInvitation( + callUUID = "", remoteHostId = null, user = User.sampleData, contact = Contact.sampleData, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index 285658ec1d..7704509148 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -47,6 +47,7 @@ class CallManager(val chatModel: ChatModel) { remoteHostId = invitation.remoteHostId, userProfile = userProfile, contact = invitation.contact, + callUUID = invitation.callUUID, callState = CallState.InvitationAccepted, localMedia = invitation.callType.media, sharedKey = invitation.sharedKey, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt index 829a849ddc..32681234fa 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt @@ -115,6 +115,7 @@ fun PreviewIncomingCallAlertLayout() { contact = Contact.sampleData, callType = CallType(media = CallMediaType.Audio, capabilities = CallCapabilities(encryption = false)), sharedKey = null, + callUUID = "", callTs = Clock.System.now() ), chatModel = ChatModel, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index 0a7231370b..5332bc650e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -13,6 +13,7 @@ data class Call( val remoteHostId: Long?, val userProfile: Profile, val contact: Contact, + val callUUID: String?, val callState: CallState, val localMedia: CallMediaType, val localCapabilities: CallCapabilities? = null, @@ -105,6 +106,7 @@ sealed class WCallResponse { val contact: Contact, val callType: CallType, val sharedKey: String? = null, + val callUUID: String, val callTs: Instant ) { val callTypeText: String get() = generalGetString(when(callType.media) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index e90eed547d..a4fe622a6f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -544,7 +544,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) if (chatInfo is ChatInfo.Direct) { val contactInfo = chatModel.controller.apiContactInfo(remoteHostId, chatInfo.contact.contactId) val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi - chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) + chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) chatModel.showCallView.value = true chatModel.callCommand.add(WCallCommand.Capabilities(media)) } diff --git a/package.yaml b/package.yaml index b0732e17ee..090933594d 100644 --- a/package.yaml +++ b/package.yaml @@ -48,6 +48,7 @@ dependencies: - tls >= 1.9.0 && < 1.10 - unliftio == 0.2.* - unliftio-core == 0.2.* + - uuid == 1.3.* - zip == 2.0.* flags: diff --git a/simplex-chat.cabal b/simplex-chat.cabal index fed3a884ce..877bec0af6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -146,6 +146,7 @@ library Simplex.Chat.Migrations.M20240510_chat_items_via_proxy Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays Simplex.Chat.Migrations.M20240528_quota_err_counter + Simplex.Chat.Migrations.M20240827_calls_uuid Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared @@ -229,6 +230,7 @@ library , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -292,6 +294,7 @@ executable simplex-bot , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -355,6 +358,7 @@ executable simplex-bot-advanced , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -421,6 +425,7 @@ executable simplex-broadcast-bot , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -485,6 +490,7 @@ executable simplex-chat , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , websockets ==0.12.* , zip ==2.0.* default-language: Haskell2010 @@ -555,6 +561,7 @@ executable simplex-directory-service , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -655,6 +662,7 @@ test-suite simplex-chat-test , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 5899be6445..ac1d0ac601 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -55,6 +55,8 @@ import Data.Time (NominalDiffTime, addUTCTime, defaultTimeLocale, formatTime) import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime, nominalDay, nominalDiffTimeToSeconds) import Data.Time.Clock.System (systemToUTCTime) import Data.Word (Word32) +import qualified Data.UUID as UUID +import qualified Data.UUID.V4 as V4 import qualified Database.SQLite.Simple as SQL import Simplex.Chat.Archive import Simplex.Chat.Call @@ -1263,12 +1265,13 @@ processChatCommand' vr = \case withContactLock "sendCallInvitation" contactId $ do g <- asks random callId <- atomically $ CallId <$> C.randomBytes 16 g + callUUID <- UUID.toText <$> liftIO V4.nextRandom dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing let invitation = CallInvitation {callType, callDhPubKey = fst <$> dhKeyPair} callState = CallInvitationSent {localCallType = callType, localDhPrivKey = snd <$> dhKeyPair} (msg, _) <- sendDirectContactMessage user ct (XCallInv callId invitation) ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndCall CISCallPending 0) - let call' = Call {contactId, callId, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} + let call' = Call {contactId, callId, callUUID, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} call_ <- atomically $ TM.lookupInsert contactId call' calls forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) @@ -1338,13 +1341,13 @@ processChatCommand' vr = \case rcvCallInvitations <- rights <$> mapM rcvCallInvitation invs pure $ CRCallInvitations rcvCallInvitations where - callInvitation Call {contactId, callState, callTs} = case callState of - CallInvitationReceived {peerCallType, sharedKey} -> Just (contactId, callTs, peerCallType, sharedKey) + callInvitation Call {contactId, callUUID, callState, callTs} = case callState of + CallInvitationReceived {peerCallType, sharedKey} -> Just (contactId, callUUID, callTs, peerCallType, sharedKey) _ -> Nothing - rcvCallInvitation (contactId, callTs, peerCallType, sharedKey) = runExceptT . withFastStore $ \db -> do + rcvCallInvitation (contactId, callUUID, callTs, peerCallType, sharedKey) = runExceptT . withFastStore $ \db -> do user <- getUserByContactId db contactId contact <- getContact db vr user contactId - pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callTs} + pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callUUID, callTs} APIGetNetworkStatuses -> withUser $ \_ -> CRNetworkStatuses Nothing . map (uncurry ConnNetworkStatus) . M.toList <$> chatReadVar connNetworkStatuses APICallStatus contactId receivedStatus -> @@ -5955,9 +5958,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = g <- asks random dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing ci <- saveCallItem CISCallPending + callUUID <- UUID.toText <$> liftIO V4.nextRandom let sharedKey = C.Key . C.dhBytes' <$> (C.dh' <$> callDhPubKey <*> (snd <$> dhKeyPair)) callState = CallInvitationReceived {peerCallType = callType, localDhPubKey = fst <$> dhKeyPair, sharedKey} - call' = Call {contactId, callId, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} + call' = Call {contactId, callId, callUUID, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} calls <- asks currentCalls -- theoretically, the new call invitation for the current contact can mark the in-progress call as ended -- (and replace it in ChatController) @@ -5965,7 +5969,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> createCall db user call' $ chatItemTs' ci call_ <- atomically (TM.lookupInsert contactId call' calls) forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing - toView $ CRCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callTs = chatItemTs' ci} + toView $ CRCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callUUID, callTs = chatItemTs' ci} toView $ CRNewChatItem user $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci else featureRejected CFCalls where diff --git a/src/Simplex/Chat/Call.hs b/src/Simplex/Chat/Call.hs index 9968d170aa..882ec8ccd0 100644 --- a/src/Simplex/Chat/Call.hs +++ b/src/Simplex/Chat/Call.hs @@ -29,6 +29,7 @@ import Simplex.Messaging.Util (decodeJSON, encodeJSON) data Call = Call { contactId :: ContactId, callId :: CallId, + callUUID :: Text, chatItemId :: Int64, callState :: CallState, callTs :: UTCTime @@ -111,6 +112,7 @@ data RcvCallInvitation = RcvCallInvitation contact :: Contact, callType :: CallType, sharedKey :: Maybe C.Key, + callUUID :: Text, callTs :: UTCTime } deriving (Show) diff --git a/src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs b/src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs new file mode 100644 index 0000000000..eb1e8db65a --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240827_calls_uuid where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240827_calls_uuid :: Query +m20240827_calls_uuid = + [sql| +ALTER TABLE calls ADD COLUMN call_uuid TEXT NOT NULL DEFAULT ""; +|] + +down_m20240827_calls_uuid :: Query +down_m20240827_calls_uuid = + [sql| +ALTER TABLE calls DROP COLUMN call_uuid; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index fdbc44a9c3..25cf886384 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -415,6 +415,8 @@ CREATE TABLE calls( user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) + , + call_uuid TEXT NOT NULL DEFAULT "" ); CREATE TABLE commands( command_id INTEGER PRIMARY KEY AUTOINCREMENT, -- used as ACorrId diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 5c9082b361..be3f4027ca 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -110,6 +110,7 @@ import Simplex.Chat.Migrations.M20240501_chat_deleted import Simplex.Chat.Migrations.M20240510_chat_items_via_proxy import Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays import Simplex.Chat.Migrations.M20240528_quota_err_counter +import Simplex.Chat.Migrations.M20240827_calls_uuid import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -219,7 +220,8 @@ schemaMigrations = ("20240501_chat_deleted", m20240501_chat_deleted, Just down_m20240501_chat_deleted), ("20240510_chat_items_via_proxy", m20240510_chat_items_via_proxy, Just down_m20240510_chat_items_via_proxy), ("20240515_rcv_files_user_approved_relays", m20240515_rcv_files_user_approved_relays, Just down_m20240515_rcv_files_user_approved_relays), - ("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter) + ("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter), + ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index fb87662c27..a29460d5b1 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -549,17 +549,17 @@ overwriteProtocolServers db User {userId} servers = protocol = decodeLatin1 $ strEncode $ protocolTypeI @p createCall :: DB.Connection -> User -> Call -> UTCTime -> IO () -createCall db user@User {userId} Call {contactId, callId, chatItemId, callState} callTs = do +createCall db user@User {userId} Call {contactId, callId, callUUID, chatItemId, callState} callTs = do currentTs <- getCurrentTime deleteCalls db user contactId DB.execute db [sql| INSERT INTO calls - (contact_id, shared_call_id, chat_item_id, call_state, call_ts, user_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?) + (contact_id, shared_call_id, call_uuid, chat_item_id, call_state, call_ts, user_id, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?) |] - (contactId, callId, chatItemId, callState, callTs, userId, currentTs, currentTs) + (contactId, callId, callUUID, chatItemId, callState, callTs, userId, currentTs, currentTs) deleteCalls :: DB.Connection -> User -> ContactId -> IO () deleteCalls db User {userId} contactId = do @@ -572,13 +572,13 @@ getCalls db = db [sql| SELECT - contact_id, shared_call_id, chat_item_id, call_state, call_ts + contact_id, shared_call_id, call_uuid, chat_item_id, call_state, call_ts FROM calls ORDER BY call_ts ASC |] where - toCall :: (ContactId, CallId, ChatItemId, CallState, UTCTime) -> Call - toCall (contactId, callId, chatItemId, callState, callTs) = Call {contactId, callId, chatItemId, callState, callTs} + toCall :: (ContactId, CallId, Text, ChatItemId, CallState, UTCTime) -> Call + toCall (contactId, callId, callUUID, chatItemId, callState, callTs) = Call {contactId, callId, callUUID, chatItemId, callState, callTs} createCommand :: DB.Connection -> User -> Maybe Int64 -> CommandFunction -> IO CommandId createCommand db User {userId} connId commandFunction = do From dfe16991d01eda56c670d1f220c3fbb719411fd0 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:49:11 +0000 Subject: [PATCH 064/440] ios: make CallKit calls fire in time after cold start (#4787) * ios: make CallKit calls fire in time after cold start * longer wait period * uncomment * change * change * removed commented code * ios: update core library --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 9 +- .../Shared/Views/Call/CallController.swift | 102 +++++++++++++----- .../ios/SimpleX NSE/NotificationService.swift | 4 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++---- 4 files changed, 103 insertions(+), 52 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 797e68db4f..6c5d5504e6 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2182,9 +2182,11 @@ func refreshCallInvitations() async throws { } } -func justRefreshCallInvitations() throws { +func justRefreshCallInvitations() async throws { let callInvitations = try apiGetCallInvitationsSync() - ChatModel.shared.callInvitations = callsByChat(callInvitations) + await MainActor.run { + ChatModel.shared.callInvitations = callsByChat(callInvitations) + } } private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: RcvCallInvitation] { @@ -2194,8 +2196,9 @@ private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: Rcv } func activateCall(_ callInvitation: RcvCallInvitation) { - if !callInvitation.user.showNotifications { return } let m = ChatModel.shared + logger.debug("reportNewIncomingCall activeCallUUID \(String(describing: m.activeCall?.callUUID)) invitationUUID \(String(describing: callInvitation.callUUID))") + if !callInvitation.user.showNotifications || m.activeCall?.callUUID == callInvitation.callUUID { return } CallController.shared.reportNewIncomingCall(invitation: callInvitation) { error in if let error = error { DispatchQueue.main.async { diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index bfa26700e5..36887d6184 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -61,12 +61,30 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { logger.debug("CallController.provider CXAnswerCallAction") - if callManager.answerIncomingCall(callUUID: action.callUUID.uuidString.lowercased()) { - // WebRTC call should be in connected state to fulfill. - // Otherwise no audio and mic working on lockscreen - fulfillOnConnect = action - } else { - action.fail() + Task { + let chatIsReady = await waitUntilChatStarted(timeoutMs: 30_000, stepMs: 500) + logger.debug("CallController chat started \(chatIsReady) \(ChatModel.shared.chatInitialized) \(ChatModel.shared.chatRunning == true) \(String(describing: AppChatState.shared.value))") + if !chatIsReady { + action.fail() + return + } + if !ChatModel.shared.callInvitations.values.contains(where: { inv in inv.callUUID == action.callUUID.uuidString.lowercased() }) { + try? await justRefreshCallInvitations() + logger.debug("CallController: updated call invitations chat") + } + await MainActor.run { + logger.debug("CallController.provider will answer on call") + + if callManager.answerIncomingCall(callUUID: action.callUUID.uuidString.lowercased()) { + logger.debug("CallController.provider answered on call") + // WebRTC call should be in connected state to fulfill. + // Otherwise no audio and mic working on lockscreen + fulfillOnConnect = action + } else { + logger.debug("CallController.provider will fail the call") + action.fail() + } + } } } @@ -156,6 +174,19 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } } + private func waitUntilChatStarted(timeoutMs: UInt64, stepMs: UInt64) async -> Bool { + logger.debug("CallController waiting until chat started") + var t: UInt64 = 0 + repeat { + if ChatModel.shared.chatInitialized, ChatModel.shared.chatRunning == true, case .active = AppChatState.shared.value { + return true + } + _ = try? await Task.sleep(nanoseconds: stepMs * 1000000) + t += stepMs + } while t < timeoutMs + return false + } + @objc(pushRegistry:didUpdatePushCredentials:forType:) func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)") @@ -171,32 +202,19 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse self.reportExpiredCall(payload: payload, completion) return } - if (!ChatModel.shared.chatInitialized) { - logger.debug("CallController: initializing chat") - do { - try initializeChat(start: true, refreshInvitations: false) - } catch let error { - logger.error("CallController: initializing chat error: \(error)") - self.reportExpiredCall(payload: payload, completion) - return - } - } - logger.debug("CallController: initialized chat") - startChatForCall() - logger.debug("CallController: started chat") - self.shouldSuspendChat = true - // There are no invitations in the model, as it was processed by NSE - try? justRefreshCallInvitations() - logger.debug("CallController: updated call invitations chat") - // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") // Extract the call information from the push notification payload let m = ChatModel.shared if let contactId = payload.dictionaryPayload["contactId"] as? String, - let invitation = m.callInvitations[contactId] { - let update = self.cxCallUpdate(invitation: invitation) - if let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { + let displayName = payload.dictionaryPayload["displayName"] as? String, + let callUUID = payload.dictionaryPayload["callUUID"] as? String, + let uuid = UUID(uuidString: callUUID), + let callTsInterval = payload.dictionaryPayload["callTs"] as? TimeInterval, + let mediaStr = payload.dictionaryPayload["media"] as? String, + let media = CallMediaType(rawValue: mediaStr) { + let update = self.cxCallUpdate(contactId, displayName, media) + let callTs = Date(timeIntervalSince1970: callTsInterval) + if callTs.timeIntervalSinceNow >= -180 { logger.debug("CallController: report pushkit call via CallKit") - let update = self.cxCallUpdate(invitation: invitation) self.provider.reportNewIncomingCall(with: uuid, update: update) { error in if error != nil { m.callInvitations.removeValue(forKey: contactId) @@ -205,11 +223,31 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse completion() } } else { + logger.debug("CallController will expire call 1") self.reportExpiredCall(update: update, completion) } } else { + logger.debug("CallController will expire call 2") self.reportExpiredCall(payload: payload, completion) } + + //DispatchQueue.main.asyncAfter(deadline: .now() + 10) { + if (!ChatModel.shared.chatInitialized) { + logger.debug("CallController: initializing chat") + do { + try initializeChat(start: true, refreshInvitations: false) + } catch let error { + logger.error("CallController: initializing chat error: \(error)") + if let call = ChatModel.shared.activeCall { + self.endCall(call: call, completed: completion) + } + return + } + } + logger.debug("CallController: initialized chat") + startChatForCall() + logger.debug("CallController: started chat") + self.shouldSuspendChat = true } // This function fulfils the requirement to always report a call when PushKit notification is received, @@ -261,6 +299,14 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse return update } + private func cxCallUpdate(_ contactId: String, _ displayName: String, _ media: CallMediaType) -> CXCallUpdate { + let update = CXCallUpdate() + update.remoteHandle = CXHandle(type: .generic, value: contactId) + update.hasVideo = media == .video + update.localizedCallerName = displayName + return update + } + func reportIncomingCall(call: Call, connectedAt dateConnected: Date?) { logger.debug("CallController: reporting incoming call connected") if CallController.useCallKit() { diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 1a2a27ba9b..81d0c9eac1 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -339,7 +339,9 @@ class NotificationService: UNNotificationServiceExtension { CXProvider.reportNewIncomingVoIPPushPayload([ "displayName": invitation.contact.displayName, "contactId": invitation.contact.id, - "media": invitation.callType.media.rawValue + "callUUID": invitation.callUUID ?? "", + "media": invitation.callType.media.rawValue, + "callTs": invitation.callTs.timeIntervalSince1970 ]) { error in logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") deliver(error == nil ? nil : createCallInvitationNtf(invitation)) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 48689d1010..1a348fc93e 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E51ED58A2C7A26FE009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5852C7A26FE009F2C7C /* libffi.a */; }; - E51ED58B2C7A26FE009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5862C7A26FE009F2C7C /* libgmpxx.a */; }; - E51ED58C2C7A26FE009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5872C7A26FE009F2C7C /* libgmp.a */; }; - E51ED58D2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */; }; - E51ED58E2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */; }; + E51ED5A82C7F5F4B009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */; }; + E51ED5A92C7F5F4B009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A42C7F5F4B009F2C7C /* libffi.a */; }; + E51ED5AA2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */; }; + E51ED5AB2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */; }; + E51ED5AC2C7F5F4B009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A72C7F5F4B009F2C7C /* libgmp.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -550,11 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E51ED5852C7A26FE009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E51ED5862C7A26FE009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5872C7A26FE009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a"; sourceTree = ""; }; - E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a"; sourceTree = ""; }; + E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E51ED5A42C7F5F4B009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a"; sourceTree = ""; }; + E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a"; sourceTree = ""; }; + E51ED5A72C7F5F4B009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51ED58D2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a in Frameworks */, - E51ED58E2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a in Frameworks */, + E51ED5A82C7F5F4B009F2C7C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E51ED58B2C7A26FE009F2C7C /* libgmpxx.a in Frameworks */, + E51ED5AC2C7F5F4B009F2C7C /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E51ED5AB2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a in Frameworks */, + E51ED5A92C7F5F4B009F2C7C /* libffi.a in Frameworks */, + E51ED5AA2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E51ED58C2C7A26FE009F2C7C /* libgmp.a in Frameworks */, - E51ED58A2C7A26FE009F2C7C /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5852C7A26FE009F2C7C /* libffi.a */, - E51ED5872C7A26FE009F2C7C /* libgmp.a */, - E51ED5862C7A26FE009F2C7C /* libgmpxx.a */, - E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */, - E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */, + E51ED5A42C7F5F4B009F2C7C /* libffi.a */, + E51ED5A72C7F5F4B009F2C7C /* libgmp.a */, + E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */, + E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */, + E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */, ); path = Libraries; sourceTree = ""; From 700918f0cabc4812474470deac3d3a6cd0a1eea3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 28 Aug 2024 20:55:54 +0100 Subject: [PATCH 065/440] ios: show member role on the right (#4783) * ios: show member role on the right * member layout --------- Co-authored-by: Levitating Pineapple --- apps/ios/Shared/Views/Chat/ChatView.swift | 86 +++++++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index d74b7b88e6..dde63b6511 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -710,7 +710,8 @@ struct ChatView: View { @State private var showChatItemInfoSheet: Bool = false @State private var chatItemInfo: ChatItemInfo? @State private var showForwardingSheet: Bool = false - + @State private var msgWidth: CGFloat = 0 + @Binding var selectedChatItems: Set? @State private var allowMenu: Bool = true @@ -824,6 +825,51 @@ struct ChatView: View { } } } + + + @available(iOS 16.0, *) + struct MemberLayout: Layout { + let spacing: Double + let msgWidth: Double + + private func sizes(subviews: Subviews, proposal: ProposedViewSize) -> (CGSize, CGSize) { + assert(subviews.count == 2, "member layout must contain exactly two subviews") + let roleSize = subviews[1].sizeThatFits(proposal) + let memberSize = subviews[0].sizeThatFits( + ProposedViewSize( + width: (proposal.width ?? msgWidth) - roleSize.width, + height: proposal.height + ) + ) + return (memberSize, roleSize) + } + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize { + let (memberSize, roleSize) = sizes(subviews: subviews, proposal: proposal) + return CGSize( + width: min( + proposal.width ?? msgWidth, + max(msgWidth, roleSize.width + spacing + memberSize.width) + ), + height: max(memberSize.height, roleSize.height) + ) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) { + let (memberSize, roleSize) = sizes(subviews: subviews, proposal: proposal) + subviews[0].place( + at: CGPoint(x: bounds.minX, y: bounds.midY - memberSize.height / 2), + proposal: ProposedViewSize(memberSize) + ) + subviews[1].place( + at: CGPoint( + x: bounds.minX + max(memberSize.width + spacing, msgWidth - roleSize.width), + y: bounds.midY - roleSize.height / 2 + ), + proposal: ProposedViewSize(roleSize) + ) + } + } @ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange?, _ prevItem: ChatItem?, _ itemSeparation: ItemSeparation) -> some View { let bottomPadding: Double = itemSeparation.largeGap ? 10 : 2 @@ -838,15 +884,40 @@ struct ChatView: View { if prevItem == nil || showMemberImage(member, prevItem) || prevMember != nil { VStack(alignment: .leading, spacing: 4) { if ci.content.showMemberName { - let t = if memCount == 1 && member.memberRole > .member { - Text(member.memberRole.text + " ").fontWeight(.semibold) + Text(member.displayName) - } else { - Text(memberNames(member, prevMember, memCount)) + Group { + if memCount == 1 && member.memberRole > .member { + Group { + if #available(iOS 16.0, *) { + MemberLayout(spacing: 16, msgWidth: msgWidth) { + Text(member.chatViewName) + .lineLimit(1) + Text(member.memberRole.text) + .fontWeight(.semibold) + .lineLimit(1) + .padding(.trailing, 8) + } + } else { + HStack(spacing: 16) { + Text(member.chatViewName) + .lineLimit(1) + Text(member.memberRole.text) + .fontWeight(.semibold) + .lineLimit(1) + .layoutPriority(1) + } + } + } + .frame( + maxWidth: maxWidth, + alignment: chatItem.chatDir.sent ? .trailing : .leading + ) + } else { + Text(memberNames(member, prevMember, memCount)) + .lineLimit(2) + } } - t .font(.caption) .foregroundStyle(.secondary) - .lineLimit(2) .padding(.leading, memberImageSize + 14 + (selectedChatItems != nil && ci.canBeDeletedForSelf ? 12 + 24 : 0)) .padding(.top, 3) // this is in addition to message sequence gap } @@ -869,6 +940,7 @@ struct ChatView: View { } } chatItemWithMenu(ci, range, maxWidth, itemSeparation) + .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } } } } From 1c64b17545a884764e734fc0fa5fb353696aba51 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 29 Aug 2024 13:19:41 +0300 Subject: [PATCH 066/440] ios: remove tails from group invitations (#4792) --- apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift index ddae6a5f6d..e1e0911e4d 100644 --- a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift +++ b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift @@ -36,8 +36,6 @@ struct ChatItemClipped: ViewModifier { .sndMsgContent, .rcvMsgContent, .rcvDecryptionError, - .rcvGroupInvitation, - .sndGroupInvitation, .sndDeleted, .rcvDeleted, .rcvIntegrityError, @@ -56,10 +54,12 @@ struct ChatItemClipped: ViewModifier { tailVisible: tail ) : .roundRect(radius: msgRectMaxRadius) + case .rcvGroupInvitation, .sndGroupInvitation: + return .roundRect(radius: msgRectMaxRadius) default: return .roundRect(radius: 8) } } else { - return.roundRect(radius: msgRectMaxRadius) + return .roundRect(radius: msgRectMaxRadius) } } From 2fe3acf4dfc75478f15eb4d6603bd10f8feca057 Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 29 Aug 2024 12:01:29 +0100 Subject: [PATCH 067/440] fix android simulator build (#4795) --- .../kotlin/chat/simplex/common/views/call/CallView.android.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index d05172a7a1..22f0c8d70b 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -770,6 +770,7 @@ fun PreviewActiveCallOverlayVideo() { callState = CallState.Negotiated, localMedia = CallMediaType.Video, peerMedia = CallMediaType.Video, + callUUID = "", connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "tcp"), RTCIceCandidate(RTCIceCandidateType.Host, "tcp") @@ -799,6 +800,7 @@ fun PreviewActiveCallOverlayAudio() { callState = CallState.Negotiated, localMedia = CallMediaType.Audio, peerMedia = CallMediaType.Audio, + callUUID = "", connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "udp"), RTCIceCandidate(RTCIceCandidateType.Host, "udp") From 6edea46dade4221e2233002722b7a88089cb292f Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 29 Aug 2024 12:15:11 +0000 Subject: [PATCH 068/440] android, desktop: improvement to a lock UI (#4769) * android, desktop: improvement to a lock UI * oneTime passcode screen which allows to pass verification while in call * change * unused line * don't ask to set up auth if already has --------- Co-authored-by: Evgeny Poberezkin --- .../helpers/LocalAuthentication.android.kt | 3 +- .../kotlin/chat/simplex/common/App.kt | 5 +-- .../kotlin/chat/simplex/common/AppLock.kt | 41 ++++++++++++------- .../chat/simplex/common/model/ChatModel.kt | 3 +- .../common/views/database/DatabaseView.kt | 1 + .../views/helpers/LocalAuthentication.kt | 4 +- .../simplex/common/views/helpers/ModalView.kt | 16 ++++++-- .../views/usersettings/PrivacySettings.kt | 36 ++++++++-------- .../common/views/usersettings/SettingsView.kt | 4 +- .../kotlin/chat/simplex/common/DesktopApp.kt | 4 +- .../helpers/LocalAuthentication.desktop.kt | 3 +- 11 files changed, 70 insertions(+), 50 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt index b238bdf7ca..07426c7fbf 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt @@ -14,6 +14,7 @@ actual fun authenticate( promptSubtitle: String, selfDestruct: Boolean, usingLAMode: LAMode, + oneTime: Boolean, completed: (LAResult) -> Unit ) { val activity = mainActivity.get() ?: return completed(LAResult.Error("")) @@ -27,7 +28,7 @@ actual fun authenticate( else -> completed(LAResult.Unavailable()) } LAMode.PASSCODE -> { - authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, completed) + authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, oneTime, completed) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 94ca307529..3cba89922d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -74,6 +74,7 @@ fun MainScreen() { LaunchedEffect(showAdvertiseLAAlert) { if ( !chatModel.controller.appPrefs.laNoticeShown.get() + && !appPrefs.performLA.get() && showAdvertiseLAAlert && chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && chatModel.chats.size > 3 @@ -211,10 +212,8 @@ fun MainScreen() { } else { ActiveCallView() } - } else { - // It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked - ModalManager.fullscreen.showPasscodeInView() } + ModalManager.fullscreen.showOneTimePasscodeInView() AlertManager.privacySensitive.showInView() if (onboarding == OnboardingStage.OnboardingComplete) { LaunchedEffect(chatModel.currentUser.value, chatModel.appOpenUrl.value) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt index d6214c252c..c93fabec8b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt @@ -5,6 +5,7 @@ import androidx.compose.material.* import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.localauth.SetAppPasscodeView @@ -31,7 +32,7 @@ object AppLock { fun showLANotice(laNoticeShown: SharedPreference) { Log.d(TAG, "showLANotice") - if (!laNoticeShown.get()) { + if (!laNoticeShown.get() && !appPrefs.performLA.get()) { laNoticeShown.set(true) AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.la_notice_title_simplex_lock), @@ -57,6 +58,8 @@ object AppLock { private fun showChooseLAMode() { Log.d(TAG, "showLANotice") + if (appPrefs.performLA.get()) return + AlertManager.shared.showAlertDialogStacked( title = generalGetString(MR.strings.la_lock_mode), text = null, @@ -80,21 +83,23 @@ object AppLock { authenticate( generalGetString(MR.strings.auth_enable_simplex_lock), generalGetString(MR.strings.auth_confirm_credential), + oneTime = true, completed = { laResult -> when (laResult) { LAResult.Success -> { - m.performLA.value = true + m.showAuthScreen.value = true appPrefs.performLA.set(true) laTurnedOnAlert() } is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Error -> { - m.performLA.value = false - appPrefs.performLA.set(false) + m.showAuthScreen.value = false + // Don't drop auth pref in case of state inconsistency (eg, you have set passcode but somehow bypassed toggle and turned it off and then on) + // appPrefs.performLA.set(false) laFailedAlert() } is LAResult.Unavailable -> { - m.performLA.value = false + m.showAuthScreen.value = false appPrefs.performLA.set(false) m.showAdvertiseLAUnavailableAlert.value = true } @@ -104,19 +109,22 @@ object AppLock { } private fun setPasscode() { + if (appPrefs.performLA.get()) return + val appPrefs = ChatController.appPrefs ModalManager.fullscreen.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) { SetAppPasscodeView( submit = { - ChatModel.performLA.value = true + ChatModel.showAuthScreen.value = true appPrefs.performLA.set(true) appPrefs.laMode.set(LAMode.PASSCODE) laTurnedOnAlert() }, cancel = { - ChatModel.performLA.value = false - appPrefs.performLA.set(false) + ChatModel.showAuthScreen.value = false + // Don't drop auth pref in case of state inconsistency (eg, you have set passcode but somehow bypassed toggle and turned it off and then on) + // appPrefs.performLA.set(false) laPasscodeNotSetAlert() }, close = close @@ -147,6 +155,7 @@ object AppLock { else generalGetString(MR.strings.auth_unlock), selfDestruct = true, + oneTime = false, completed = { laResult -> when (laResult) { LAResult.Success -> @@ -160,7 +169,7 @@ object AppLock { } is LAResult.Unavailable -> { userAuthorized.value = true - m.performLA.value = false + m.showAuthScreen.value = false m.controller.appPrefs.performLA.set(false) laUnavailableTurningOffAlert() } @@ -192,22 +201,23 @@ object AppLock { generalGetString(MR.strings.auth_confirm_credential) else "", + oneTime = true, completed = { laResult -> val prefPerformLA = m.controller.appPrefs.performLA when (laResult) { LAResult.Success -> { - m.performLA.value = true + m.showAuthScreen.value = true prefPerformLA.set(true) laTurnedOnAlert() } is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Error -> { - m.performLA.value = false + m.showAuthScreen.value = false prefPerformLA.set(false) laFailedAlert() } is LAResult.Unavailable -> { - m.performLA.value = false + m.showAuthScreen.value = false prefPerformLA.set(false) laUnavailableInstructionAlert() } @@ -227,12 +237,13 @@ object AppLock { generalGetString(MR.strings.auth_confirm_credential) else generalGetString(MR.strings.auth_disable_simplex_lock), + oneTime = true, completed = { laResult -> val prefPerformLA = m.controller.appPrefs.performLA val selfDestructPref = m.controller.appPrefs.selfDestruct when (laResult) { LAResult.Success -> { - m.performLA.value = false + m.showAuthScreen.value = false prefPerformLA.set(false) DatabaseUtils.ksAppPassword.remove() selfDestructPref.set(false) @@ -240,12 +251,12 @@ object AppLock { } is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Error -> { - m.performLA.value = true + m.showAuthScreen.value = true prefPerformLA.set(true) laFailedAlert() } is LAResult.Unavailable -> { - m.performLA.value = false + m.showAuthScreen.value = false prefPerformLA.set(false) laUnavailableTurningOffAlert() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index e92b3d714a..5a1c46666d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -20,7 +20,6 @@ import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.flow.internal.ChannelFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.datetime.* @@ -98,7 +97,7 @@ object ChatModel { } ) } - val performLA by lazy { mutableStateOf(ChatController.appPrefs.performLA.get()) } + val showAuthScreen by lazy { mutableStateOf(ChatController.appPrefs.performLA.get()) } val showAdvertiseLAUnavailableAlert = mutableStateOf(false) val showChatPreviews by lazy { mutableStateOf(ChatController.appPrefs.privacyShowChatPreviews.get()) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 333fda307a..b287847ace 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -423,6 +423,7 @@ fun authStopChat(m: ChatModel, progressIndicator: MutableState? = null, authenticate( generalGetString(MR.strings.auth_stop_chat), generalGetString(MR.strings.auth_log_in_using_credential), + oneTime = true, completed = { laResult -> when (laResult) { LAResult.Success, is LAResult.Unavailable -> { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt index 022ee37589..28f6320ee7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt @@ -34,6 +34,7 @@ expect fun authenticate( promptSubtitle: String, selfDestruct: Boolean = false, usingLAMode: LAMode = ChatModel.controller.appPrefs.laMode.get(), + oneTime: Boolean, completed: (LAResult) -> Unit ) @@ -41,10 +42,11 @@ fun authenticateWithPasscode( promptTitle: String, promptSubtitle: String, selfDestruct: Boolean, + oneTime: Boolean, completed: (LAResult) -> Unit ) { val password = DatabaseUtils.ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(MR.strings.la_no_app_password))) - ModalManager.fullscreen.showPasscodeCustomModal { close -> + ModalManager.fullscreen.showPasscodeCustomModal(oneTime) { close -> BackHandler { close() completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled))) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index bfd61a2add..8da73ab3ca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -69,6 +69,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { // Don't use mutableStateOf() here, because it produces this if showing from SimpleXAPI.startChat(): // java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied private var passcodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null) + private var onTimePasscodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null) fun showModal(settings: Boolean = false, showClose: Boolean = true, closeOnTop: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { val data = ModalData() @@ -105,9 +106,13 @@ class ModalManager(private val placement: ModalPlacement? = null) { } } - fun showPasscodeCustomModal(modal: @Composable (close: () -> Unit) -> Unit) { - Log.d(TAG, "ModalManager.showPasscodeCustomModal") - passcodeView.value = modal + fun showPasscodeCustomModal(oneTime: Boolean, modal: @Composable (close: () -> Unit) -> Unit) { + Log.d(TAG, "ModalManager.showPasscodeCustomModal, oneTime: $oneTime") + if (oneTime) { + onTimePasscodeView.value = modal + } else { + passcodeView.value = modal + } } fun hasModalsOpen() = modalCount.value > 0 @@ -179,6 +184,11 @@ class ModalManager(private val placement: ModalPlacement? = null) { passcodeView.collectAsState().value?.invoke { passcodeView.value = null } } + @Composable + fun showOneTimePasscodeInView() { + onTimePasscodeView.collectAsState().value?.invoke { onTimePasscodeView.value = null } + } + /** * Allows to modify a list without getting [ConcurrentModificationException] * */ diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 084dfc20d2..abf318390f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -1,12 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionTextFooter import SectionView -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -34,8 +32,6 @@ import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.common.model.ChatModel import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* -import kotlin.math.min -import kotlin.math.roundToInt enum class LAMode { SYSTEM, @@ -374,7 +370,8 @@ fun SimplexLockView( currentLAMode: SharedPreference, setPerformLA: (Boolean) -> Unit ) { - val performLA = remember { chatModel.performLA } + val showAuthScreen = remember { chatModel.showAuthScreen } + val performLA = remember { appPrefs.performLA.state } val laMode = remember { chatModel.controller.appPrefs.laMode.state } val laLockDelay = remember { chatModel.controller.appPrefs.laLockDelay } val showChangePasscode = remember { derivedStateOf { performLA.value && currentLAMode.state.value == LAMode.PASSCODE } } @@ -382,13 +379,9 @@ fun SimplexLockView( val selfDestructDisplayName = remember { mutableStateOf(chatModel.controller.appPrefs.selfDestructDisplayName.get() ?: "") } val selfDestructDisplayNamePref = remember { chatModel.controller.appPrefs.selfDestructDisplayName } - fun resetLAEnabled(onOff: Boolean) { - chatModel.controller.appPrefs.performLA.set(onOff) - chatModel.performLA.value = onOff - } - fun disableUnavailableLA() { - resetLAEnabled(false) + chatModel.controller.appPrefs.performLA.set(false) + chatModel.showAuthScreen.value = false currentLAMode.set(LAMode.default) laUnavailableInstructionAlert() } @@ -405,7 +398,8 @@ fun SimplexLockView( } else { generalGetString(MR.strings.chat_lock) }, - generalGetString(MR.strings.change_lock_mode) + generalGetString(MR.strings.change_lock_mode), + oneTime = true, ) { laResult -> when (laResult) { is LAResult.Error -> { @@ -415,7 +409,7 @@ fun SimplexLockView( LAResult.Success -> { when (toLAMode) { LAMode.SYSTEM -> { - authenticate(generalGetString(MR.strings.auth_enable_simplex_lock), promptSubtitle = "", usingLAMode = toLAMode) { laResult -> + authenticate(generalGetString(MR.strings.auth_enable_simplex_lock), promptSubtitle = "", usingLAMode = toLAMode, oneTime = true) { laResult -> when (laResult) { LAResult.Success -> { currentLAMode.set(toLAMode) @@ -451,7 +445,7 @@ fun SimplexLockView( } fun toggleSelfDestruct(selfDestruct: SharedPreference) { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_mode)) { laResult -> + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_mode), oneTime = true) { laResult -> when (laResult) { is LAResult.Error -> laFailedAlert() is LAResult.Failed -> { /* Can be called multiple times on every failure */ } @@ -470,7 +464,7 @@ fun SimplexLockView( } fun changeLAPassword() { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.la_change_app_passcode)) { laResult -> + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.la_change_app_passcode), oneTime = true) { laResult -> when (laResult) { LAResult.Success -> { ModalManager.fullscreen.showCustomModal { close -> @@ -494,7 +488,7 @@ fun SimplexLockView( } fun changeSelfDestructPassword() { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_passcode)) { laResult -> + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_passcode), oneTime = true) { laResult -> when (laResult) { LAResult.Success -> { ModalManager.fullscreen.showCustomModal { close -> @@ -525,8 +519,8 @@ fun SimplexLockView( ) { AppBarTitle(stringResource(MR.strings.chat_lock)) SectionView { - EnableLock(performLA) { performLAToggle -> - performLA.value = performLAToggle + EnableLock(remember { appPrefs.performLA.state }) { performLAToggle -> + showAuthScreen.value = performLAToggle chatModel.controller.appPrefs.laNoticeShown.set(true) if (performLAToggle) { when (currentLAMode.state.value) { @@ -543,7 +537,9 @@ fun SimplexLockView( passcodeAlert(generalGetString(MR.strings.passcode_set)) }, cancel = { - resetLAEnabled(false) + chatModel.showAuthScreen.value = false + // Don't drop auth pref in case of state inconsistency (eg, you have set passcode but somehow bypassed toggle and turned it off and then on) + // chatModel.controller.appPrefs.performLA.set(false) }, close = close ) @@ -660,7 +656,7 @@ private fun EnableSelfDestruct( } @Composable -private fun EnableLock(performLA: MutableState, onCheckedChange: (Boolean) -> Unit) { +private fun EnableLock(performLA: State, onCheckedChange: (Boolean) -> Unit) { SectionItemView { Row(verticalAlignment = Alignment.CenterVertically) { Text( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index b50e905f39..3e1522b288 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.CreateProfile @@ -234,7 +235,7 @@ fun ChatLockItem( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), setPerformLA: (Boolean) -> Unit ) { - val performLA = remember { ChatModel.performLA } + val performLA = remember { appPrefs.performLA.state } val currentLAMode = remember { ChatModel.controller.appPrefs.laMode } SettingsActionItemWithContent( click = showSettingsModal { SimplexLockView(ChatModel, currentLAMode, setPerformLA) }, @@ -505,6 +506,7 @@ private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) -> authenticate( title, desc, + oneTime = true, completed = { laResult -> onFinish(laResult == LAResult.Success || laResult is LAResult.Unavailable) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index df8887c4e1..fc0e97b417 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.window.* import chat.simplex.common.model.* -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.DEFAULT_START_MODAL_WIDTH import chat.simplex.common.ui.theme.SimpleXTheme @@ -27,7 +26,6 @@ import kotlinx.coroutines.* import java.awt.event.WindowEvent import java.awt.event.WindowFocusListener import java.io.File -import kotlin.math.sqrt import kotlin.system.exitProcess val simplexWindowState = SimplexWindowState() @@ -172,7 +170,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { var windowFocused by remember { simplexWindowState.windowFocused } LaunchedEffect(windowFocused) { val delay = ChatController.appPrefs.laLockDelay.get() - if (!windowFocused && ChatModel.performLA.value && delay > 0) { + if (!windowFocused && ChatModel.showAuthScreen.value && delay > 0) { delay(delay * 1000L) // Trigger auth state check when delay ends (and if it ends) AppLock.recheckAuthState() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt index a251b7dc20..e245efae03 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt @@ -7,10 +7,11 @@ actual fun authenticate( promptSubtitle: String, selfDestruct: Boolean, usingLAMode: LAMode, + oneTime: Boolean, completed: (LAResult) -> Unit ) { when (usingLAMode) { - LAMode.PASSCODE -> authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, completed) + LAMode.PASSCODE -> authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, oneTime, completed) else -> {} } } From eef1e97ecc9ce68f1b3b28fa85d3e107e4251a5c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 29 Aug 2024 13:40:55 +0100 Subject: [PATCH 069/440] ci: dont build when files in core do not change (#4797) --- .github/workflows/build.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c41fb4646a..6ad4f12ef9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,22 @@ on: branches: - master - stable - - users tags: - "v*" - "!*-fdroid" - "!*-armv7a" pull_request: + paths-ignore: + - "apps/ios" + - "apps/multiplatform" + - "blog" + - "docs" + - "fastlane" + - "images" + - "packages" + - "website" + - "README.md" + - "PRIVACY.md" jobs: prepare-release: From 0b0b78293f5cf2a6f196aee52f43e14d986c4e77 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 29 Aug 2024 19:25:08 +0300 Subject: [PATCH 070/440] ios: fix inaccurate floating unread counters in chat message view (#4781) * ios: fix inaccurate floating unread counters in chat message view * account for inset; remove old on appear/disappear blocks * revert id * first visible * remove UnreadChatItemCounts * cleanup * revert duplicates * add todo * throttle first * cleanup * lines --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 35 -------- .../Views/Chat/ChatItem/FramedItemView.swift | 2 +- .../Chat/ChatItem/FullScreenMediaView.swift | 2 +- apps/ios/Shared/Views/Chat/ChatView.swift | 88 +++++++------------ apps/ios/Shared/Views/Chat/ReverseList.swift | 58 +++++++++--- .../Views/ChatList/ChatPreviewView.swift | 4 +- 6 files changed, 83 insertions(+), 106 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 07a0d19a55..2be3191f4f 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -888,35 +888,6 @@ final class ChatModel: ObservableObject { _ = upsertGroupMember(groupInfo, updatedMember) } } - - func unreadChatItemCounts(itemsInView: Set) -> UnreadChatItemCounts { - var i = 0 - var totalBelow = 0 - var unreadBelow = 0 - while i < im.reversedChatItems.count - 1 && !itemsInView.contains(im.reversedChatItems[i].viewId) { - totalBelow += 1 - if im.reversedChatItems[i].isRcvNew { - unreadBelow += 1 - } - i += 1 - } - return UnreadChatItemCounts( - // TODO these thresholds account for the fact that items are still "visible" while - // covered by compose area, they should be replaced with the actual height in pixels below the screen. - isNearBottom: totalBelow < 15, - isReallyNearBottom: totalBelow < 2, - unreadBelow: unreadBelow - ) - } - - func topItemInView(itemsInView: Set) -> ChatItem? { - let maxIx = im.reversedChatItems.count - 1 - var i = 0 - let inView = { itemsInView.contains(self.im.reversedChatItems[$0].viewId) } - while i < maxIx && !inView(i) { i += 1 } - while i < maxIx && inView(i) { i += 1 } - return im.reversedChatItems[min(i - 1, maxIx)] - } } struct ShowingInvitation { @@ -929,12 +900,6 @@ struct NTFContactRequest { var chatId: String } -struct UnreadChatItemCounts: Equatable { - var isNearBottom: Bool - var isReallyNearBottom: Bool - var unreadBelow: Int -} - final class Chat: ObservableObject, Identifiable, ChatLike { @Published var chatInfo: ChatInfo @Published var chatItems: [ChatItem] diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index e70f891302..260ac64e43 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -12,7 +12,7 @@ import SimpleXChat struct FramedItemView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @EnvironmentObject var scrollModel: ReverseListScrollModel + @EnvironmentObject var scrollModel: ReverseListScrollModel @ObservedObject var chat: Chat var chatItem: ChatItem var preview: UIImage? diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift index a80c5412b6..044ee2a26d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift @@ -13,7 +13,7 @@ import AVKit struct FullScreenMediaView: View { @EnvironmentObject var m: ChatModel - @EnvironmentObject var scrollModel: ReverseListScrollModel + @EnvironmentObject var scrollModel: ReverseListScrollModel @State var chatItem: ChatItem @State var image: UIImage? @State var player: AVPlayer? = nil diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index dde63b6511..a5ad7ce456 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -22,8 +22,8 @@ struct ChatView: View { @Environment(\.presentationMode) var presentationMode @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat - @StateObject private var scrollModel = ReverseListScrollModel() - @StateObject private var floatingButtonModel = FloatingButtonModel() + @StateObject private var scrollModel = ReverseListScrollModel() + @StateObject private var floatingButtonModel: FloatingButtonModel = .shared @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @State private var composeState = ComposeState() @@ -76,7 +76,8 @@ struct ChatView: View { VStack(spacing: 0) { ZStack(alignment: .bottomTrailing) { chatItemsList() - floatingButtons(counts: floatingButtonModel.unreadChatItemCounts) + // TODO: Extract into a separate view, to reduce the scope of `FloatingButtonModel` updates + floatingButtons(unreadBelow: floatingButtonModel.unreadBelow, isNearBottom: floatingButtonModel.isNearBottom) } connectingText() if selectedChatItems == nil { @@ -413,12 +414,6 @@ struct ChatView: View { revealedChatItem: $revealedChatItem, selectedChatItems: $selectedChatItems ) - .onAppear { - floatingButtonModel.appeared(viewId: ci.viewId) - } - .onDisappear { - floatingButtonModel.disappeared(viewId: ci.viewId) - } .id(ci.id) // Required to trigger `onAppear` on iOS15 } loadPage: { loadChatItems(cInfo) @@ -429,13 +424,10 @@ struct ChatView: View { .onChange(of: searchText) { _ in Task { await loadChat(chat: chat, search: searchText) } } - .onChange(of: im.reversedChatItems) { _ in - floatingButtonModel.chatItemsChanged() - } .onChange(of: im.itemAdded) { added in if added { im.itemAdded = false - if floatingButtonModel.unreadChatItemCounts.isReallyNearBottom { + if floatingButtonModel.isReallyNearBottom { scrollModel.scrollToBottom() } } @@ -458,57 +450,43 @@ struct ChatView: View { } class FloatingButtonModel: ObservableObject { - private enum Event { - case appeared(String) - case disappeared(String) - case chatItemsChanged - } - - @Published var unreadChatItemCounts: UnreadChatItemCounts - - private let events = PassthroughSubject() + static let shared = FloatingButtonModel() + @Published var unreadBelow: Int = 0 + @Published var isNearBottom: Bool = true + var isReallyNearBottom: Bool { scrollOffset.value > 0 && scrollOffset.value < 500 } + let visibleItems = PassthroughSubject<[String], Never>() + let scrollOffset = CurrentValueSubject(0) private var bag = Set() init() { - unreadChatItemCounts = UnreadChatItemCounts( - isNearBottom: true, - isReallyNearBottom: true, - unreadBelow: 0 - ) - events + visibleItems .receive(on: DispatchQueue.global(qos: .background)) - .scan(Set()) { itemsInView, event in - var updated = itemsInView - switch event { - case let .appeared(viewId): updated.insert(viewId) - case let .disappeared(viewId): updated.remove(viewId) - case .chatItemsChanged: () - } - return updated + .map { itemIds in + if let viewId = itemIds.first, + let index = ItemsModel.shared.reversedChatItems.firstIndex(where: { $0.viewId == viewId }) { + ItemsModel.shared.reversedChatItems[.. some View { + private func floatingButtons(unreadBelow: Int, isNearBottom: Bool) -> some View { VStack { - let unreadAbove = chat.chatStats.unreadCount - counts.unreadBelow + let unreadAbove = chat.chatStats.unreadCount - unreadBelow if unreadAbove > 0 { circleButton { unreadCountText(unreadAbove) @@ -529,16 +507,16 @@ struct ChatView: View { } } Spacer() - if counts.unreadBelow > 0 { + if unreadBelow > 0 { circleButton { - unreadCountText(counts.unreadBelow) + unreadCountText(unreadBelow) .font(.callout) .foregroundColor(theme.colors.primary) } .onTapGesture { scrollModel.scrollToBottom() } - } else if !counts.isNearBottom { + } else if !isNearBottom { circleButton { Image(systemName: "chevron.down") .foregroundColor(theme.colors.primary) diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 94d160e1b4..bff0774926 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -8,15 +8,16 @@ import SwiftUI import Combine +import SimpleXChat /// A List, which displays it's items in reverse order - from bottom to top -struct ReverseList: UIViewControllerRepresentable { - let items: Array +struct ReverseList: UIViewControllerRepresentable { + let items: Array - @Binding var scrollState: ReverseListScrollModel.State + @Binding var scrollState: ReverseListScrollModel.State /// Closure, that returns user interface for a given item - let content: (Item) -> Content + let content: (ChatItem) -> Content let loadPage: () -> Void @@ -25,7 +26,9 @@ struct ReverseList: UIV } func updateUIViewController(_ controller: Controller, context: Context) { + controller.representer = self if case let .scrollingTo(destination) = scrollState, !items.isEmpty { + controller.view.layer.removeAllAnimations() switch destination { case .nextPage: controller.scrollToNextPage() @@ -42,9 +45,10 @@ struct ReverseList: UIV /// Controller, which hosts SwiftUI cells class Controller: UITableViewController { private enum Section { case main } - private let representer: ReverseList - private var dataSource: UITableViewDiffableDataSource! + var representer: ReverseList + private var dataSource: UITableViewDiffableDataSource! private var itemCount: Int = 0 + private let updateFloatingButtons = PassthroughSubject() private var bag = Set() init(representer: ReverseList) { @@ -71,7 +75,7 @@ struct ReverseList: UIV } // 3. Configure data source - self.dataSource = UITableViewDiffableDataSource( + self.dataSource = UITableViewDiffableDataSource( tableView: tableView ) { (tableView, indexPath, item) -> UITableViewCell? in if indexPath.item > self.itemCount - 8, self.itemCount > 8 { @@ -103,6 +107,10 @@ struct ReverseList: UIV name: notificationName, object: nil ) + updateFloatingButtons + .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) + .sink { self.updateVisibleItems() } + .store(in: &bag) } @available(*, unavailable) @@ -171,8 +179,8 @@ struct ReverseList: UIV Task { representer.scrollState = .atDestination } } - func update(items: Array) { - var snapshot = NSDiffableDataSourceSnapshot() + func update(items: [ChatItem]) { + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(items) dataSource.defaultRowAnimation = .none @@ -188,6 +196,32 @@ struct ReverseList: UIV ) } itemCount = items.count + updateFloatingButtons.send() + } + + override func scrollViewDidScroll(_ scrollView: UIScrollView) { + updateFloatingButtons.send() + } + + private func updateVisibleItems() { + let fbm = ChatView.FloatingButtonModel.shared + fbm.scrollOffset.send(tableView.contentOffset.y + InvertedTableView.inset) + fbm.visibleItems.send( + (tableView.indexPathsForVisibleRows ?? []) + .compactMap { indexPath -> String? in + let relativeFrame = tableView.superview!.convert( + tableView.rectForRow(at: indexPath), + from: tableView + ) + // Checks that the cell is visible accounting for the added insets + let isVisible = + relativeFrame.maxY > InvertedTableView.inset && + relativeFrame.minY < tableView.frame.height - InvertedTableView.inset + return indexPath.item < representer.items.count && isVisible + ? representer.items[indexPath.item].viewId + : nil + } + ) } } @@ -232,12 +266,12 @@ struct ReverseList: UIV } /// Manages ``ReverseList`` scrolling -class ReverseListScrollModel: ObservableObject { +class ReverseListScrollModel: ObservableObject { /// Represents Scroll State of ``ReverseList`` enum State: Equatable { enum Destination: Equatable { case nextPage - case item(Item.ID) + case item(ChatItem.ID) case bottom } @@ -255,7 +289,7 @@ class ReverseListScrollModel: ObservableObject { state = .scrollingTo(.bottom) } - func scrollToItem(id: Item.ID) { + func scrollToItem(id: ChatItem.ID) { state = .scrollingTo(.item(id)) } } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 9e6d3005b6..43892ec469 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -324,12 +324,12 @@ struct ChatPreviewView: View { case let .image(_, image): smallContentPreview(size: dynamicMediaSize) { CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) - .environmentObject(ReverseListScrollModel()) + .environmentObject(ReverseListScrollModel()) } case let .video(_,image, duration): smallContentPreview(size: dynamicMediaSize) { CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) - .environmentObject(ReverseListScrollModel()) + .environmentObject(ReverseListScrollModel()) } case let .voice(_, duration): smallContentPreviewVoice(size: dynamicMediaSize) { From 23f54c1022669af5fbe0f33ee2a0fb0a614bf8b6 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 29 Aug 2024 20:33:48 +0300 Subject: [PATCH 071/440] ios: fix crash regression (#4800) --- apps/ios/Shared/Views/Chat/ReverseList.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index bff0774926..f64189f95f 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -209,10 +209,10 @@ struct ReverseList: UIViewControllerRepresentable { fbm.visibleItems.send( (tableView.indexPathsForVisibleRows ?? []) .compactMap { indexPath -> String? in - let relativeFrame = tableView.superview!.convert( + guard let relativeFrame = tableView.superview?.convert( tableView.rectForRow(at: indexPath), from: tableView - ) + ) else { return nil } // Checks that the cell is visible accounting for the added insets let isVisible = relativeFrame.maxY > InvertedTableView.inset && From 122387d180322b06513393fc145b59e9e8f2038a Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:11:26 +0000 Subject: [PATCH 072/440] android, desktop: fix loading chat items when search was not empty (#4802) --- .../commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index a4fe622a6f..c8ad89609d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -86,6 +86,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - .collect { chatId -> markUnreadChatAsRead(chatId) showSearch.value = false + searchText.value = "" selectedChatItems.value = null } } From a9ec1f9ec1f1fbf8f196771d9ad88ec298464699 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 30 Aug 2024 13:39:35 +0100 Subject: [PATCH 073/440] core: 6.0.4.0 (simplexmq 6.0.3.0) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat.hs | 2 +- tests/ChatClient.hs | 1 + tests/ProtocolTests.hs | 4 ++-- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cabal.project b/cabal.project index 9bf39c2841..d7f6b67eb1 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 56986f82c89b04beae84a61208db8b55eb0098e3 + tag: d559a66145cf7b4cd367c09974ed1ce8393940b2 source-repository-package type: git diff --git a/package.yaml b/package.yaml index 090933594d..947589acd0 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.3.0 +version: 6.0.4.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 0569199515..0f6592086f 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."56986f82c89b04beae84a61208db8b55eb0098e3" = "0vqvdnm560xrfq7kjsghdbpk67vn4hcdpp58dfqgh9l2c9f79bin"; + "https://github.com/simplex-chat/simplexmq.git"."d559a66145cf7b4cd367c09974ed1ce8393940b2" = "1jav7jmriims6vlkxg8gmal03f9mbgrwc8v6g0rp95ivkx8gfjyw"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 877bec0af6..b3cde5ae9f 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.0.3.0 +version: 6.0.4.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index ac1d0ac601..cfc9f0dc97 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -3522,7 +3522,7 @@ agentSubscriber = do toView' $ CRChatError Nothing $ ChatErrorAgent (CRITICAL True $ "Message reception stopped: " <> show e) Nothing E.throwIO e where - process :: (ACorrId, EntityId, AEvt) -> CM' () + process :: (ACorrId, AEntityId, AEvt) -> CM' () process (corrId, entId, AEvt e msg) = run $ case e of SAENone -> processAgentMessageNoConn msg SAEConn -> processAgentMessage corrId entId msg diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index e3d557166b..42c12f1c6e 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -442,6 +442,7 @@ smpServerCfg = logStatsStartTime = 0, serverStatsLogFile = "tests/smp-server-stats.daily.log", serverStatsBackupFile = Nothing, + pendingENDInterval = 500000, smpServerVRange = supportedServerSMPRelayVRange, transportConfig = defaultTransportServerConfig {alpn = Just supportedSMPHandshakes}, smpHandshakeTimeout = 1000000, diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index d9552452cd..f64efe108f 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -16,7 +16,7 @@ import Simplex.Chat.Types.Shared import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet -import Simplex.Messaging.Protocol (supportedSMPClientVRange) +import Simplex.Messaging.Protocol (EntityId (..), supportedSMPClientVRange) import Simplex.Messaging.ServiceScheme import Simplex.Messaging.Version import Test.Hspec @@ -33,7 +33,7 @@ queue = supportedSMPClientVRange SMPQueueAddress { smpServer = srv, - senderId = "\223\142z\251", + senderId = EntityId "\223\142z\251", dhPublicKey = "MCowBQYDK2VuAyEAjiswwI3O/NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=", sndSecure = False } From 9432a5e5cdc56a787196d7f31c17b7efa26f340b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 30 Aug 2024 17:09:49 +0100 Subject: [PATCH 074/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 48 +++++++++++++--------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 1a348fc93e..b49e8ffd11 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E51ED5A82C7F5F4B009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */; }; - E51ED5A92C7F5F4B009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A42C7F5F4B009F2C7C /* libffi.a */; }; - E51ED5AA2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */; }; - E51ED5AB2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */; }; - E51ED5AC2C7F5F4B009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A72C7F5F4B009F2C7C /* libgmp.a */; }; + E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84482C8220D0008C24D1 /* libffi.a */; }; + E5BD844E2C8220D0008C24D1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84492C8220D0008C24D1 /* libgmpxx.a */; }; + E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */; }; + E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */; }; + E5BD84512C8220D0008C24D1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844C2C8220D0008C24D1 /* libgmp.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -550,11 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5A42C7F5F4B009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a"; sourceTree = ""; }; - E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a"; sourceTree = ""; }; - E51ED5A72C7F5F4B009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5BD84482C8220D0008C24D1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E5BD84492C8220D0008C24D1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a"; sourceTree = ""; }; + E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a"; sourceTree = ""; }; + E5BD844C2C8220D0008C24D1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51ED5A82C7F5F4B009F2C7C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E51ED5AC2C7F5F4B009F2C7C /* libgmp.a in Frameworks */, + E5BD844E2C8220D0008C24D1 /* libgmpxx.a in Frameworks */, + E5BD84512C8220D0008C24D1 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E51ED5AB2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a in Frameworks */, - E51ED5A92C7F5F4B009F2C7C /* libffi.a in Frameworks */, - E51ED5AA2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, + E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */, + E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */, + E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5A42C7F5F4B009F2C7C /* libffi.a */, - E51ED5A72C7F5F4B009F2C7C /* libgmp.a */, - E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */, - E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */, - E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */, + E5BD84482C8220D0008C24D1 /* libffi.a */, + E5BD844C2C8220D0008C24D1 /* libgmp.a */, + E5BD84492C8220D0008C24D1 /* libgmpxx.a */, + E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */, + E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */, ); path = Libraries; sourceTree = ""; @@ -2105,6 +2105,10 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Libraries", + ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", @@ -2156,6 +2160,10 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Libraries", + ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", From 4ca1b57e1b38e74fed8487c7331abe0a80c89c5c Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:34:53 +0000 Subject: [PATCH 075/440] android, desktop: small enhancements to new chat sheet (#4803) * android, desktop: small enhancements to new chat sheet * padding * normal view matching stable * fix one hand layout --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: Diogo --- .../common/views/newchat/NewChatSheet.kt | 225 +++++++++--------- 1 file changed, 112 insertions(+), 113 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 0621c5509f..99ba7be8d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -1,7 +1,9 @@ package chat.simplex.common.views.newchat +import SectionDivider import SectionDividerSpaced import SectionItemView +import SectionSpacer import SectionView import TextIconSpaced import androidx.compose.desktop.ui.tooling.preview.Preview @@ -164,6 +166,10 @@ private fun NewChatSheetLayout( ) val sectionModifier = Modifier.fillMaxWidth() + val deletedContactTypes = listOf(ContactType.CHAT_DELETED) + val deletedChats by remember(chatModel.chats.value, deletedContactTypes) { + derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) } + } LazyColumnWithScrollBar( Modifier.fillMaxSize(), @@ -224,7 +230,9 @@ private fun NewChatSheetLayout( } } item { - Spacer(Modifier.padding(bottom = 27.dp)) + if (searchText.value.text.isEmpty()) { + Spacer(Modifier.padding(bottom = 27.dp)) + } val actionButtonsOriginal = listOf( Triple( @@ -262,37 +270,30 @@ private fun NewChatSheetLayout( } } } - - val deletedContactTypes = listOf(ContactType.CHAT_DELETED) - val deletedChats by remember(chatModel.chats.value, deletedContactTypes) { - derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) } - } if (deletedChats.isNotEmpty()) { SectionDividerSpaced(maxBottomPadding = false) - Row(modifier = sectionModifier) { - SectionView { - SectionItemView( - click = { - ModalManager.start.showCustomModal { closeDeletedChats -> - ModalView( - close = closeDeletedChats, - closeOnTop = !oneHandUI.value, - ) { - DeletedContactsView(rh = rh, closeDeletedChats = closeDeletedChats, close = { - ModalManager.start.closeModals() - }) - } + SectionView { + SectionItemView( + click = { + ModalManager.start.showCustomModal { closeDeletedChats -> + ModalView( + close = closeDeletedChats, + closeOnTop = !oneHandUI.value, + ) { + DeletedContactsView(rh = rh, closeDeletedChats = closeDeletedChats, close = { + ModalManager.start.closeModals() + }) } } - ) { - Icon( - painterResource(MR.images.ic_inventory_2), - contentDescription = stringResource(MR.strings.deleted_chats), - tint = MaterialTheme.colors.secondary, - ) - TextIconSpaced(false) - Text(text = stringResource(MR.strings.deleted_chats), color = MaterialTheme.colors.onBackground) } + ) { + Icon( + painterResource(MR.images.ic_inventory_2), + contentDescription = stringResource(MR.strings.deleted_chats), + tint = MaterialTheme.colors.secondary, + ) + TextIconSpaced(false) + Text(text = stringResource(MR.strings.deleted_chats), color = MaterialTheme.colors.onBackground) } } } @@ -300,16 +301,28 @@ private fun NewChatSheetLayout( } item { - if (filteredContactChats.isNotEmpty() && !oneHandUI.value) { - if (searchText.value.text.isNotEmpty()) { - Spacer(Modifier.height(DEFAULT_PADDING)) - } else { + if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) { + if (!oneHandUI.value) { SectionDividerSpaced() + SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {} + } else { + SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) + SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {} + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + } + } + } + + item { + if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) { + Column(sectionModifier.fillMaxSize().padding(DEFAULT_PADDING)) { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + Text( + generalGetString(MR.strings.no_filtered_contacts), + color = MaterialTheme.colors.secondary + ) + } } - Text( - stringResource(MR.strings.contact_list_header_title).uppercase(), color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, - modifier = sectionModifier.padding(start = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF), fontSize = 12.sp - ) } } @@ -322,17 +335,6 @@ private fun NewChatSheetLayout( ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true) } } - - if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) { - Column(sectionModifier.fillMaxSize().padding(DEFAULT_PADDING)) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - Text( - generalGetString(MR.strings.no_filtered_contacts), - color = MaterialTheme.colors.secondary - ) - } - } - } } @Composable @@ -555,78 +557,75 @@ private fun DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Un } } } - ) { - Column( - Modifier - .fillMaxSize() - .padding(it) + ) { contentPadding -> + val listState = rememberLazyListState(lazyListState.first, lazyListState.second) + val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } + val searchShowingSimplexLink = remember { mutableStateOf(false) } + val searchChatFilteredBySimplexLink = remember { mutableStateOf(null) } + val showUnreadAndFavorites = remember { appPrefs.showUnreadAndFavorites.state }.value + val allChats by remember(chatModel.chats.value) { + derivedStateOf { filterContactTypes(chatModel.chats.value, listOf(ContactType.CHAT_DELETED)) } + } + val filteredContactChats = filteredContactChats( + showUnreadAndFavorites = showUnreadAndFavorites, + searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, + searchShowingSimplexLink = searchShowingSimplexLink, + searchText = searchText.value.text, + contactChats = allChats + ) + + LazyColumnWithScrollBar( + Modifier.fillMaxSize(), + contentPadding = contentPadding, + reverseLayout = oneHandUI.value, ) { - if (!oneHandUI.value) { - Box(contentAlignment = Alignment.Center) { - val bottomPadding = DEFAULT_PADDING - AppBarTitle( - stringResource(MR.strings.deleted_chats), - hostDevice(rh?.remoteHostId), - bottomPadding = bottomPadding - ) - } - } - - val listState = rememberLazyListState(lazyListState.first, lazyListState.second) - val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } - val searchShowingSimplexLink = remember { mutableStateOf(false) } - val searchChatFilteredBySimplexLink = remember { mutableStateOf(null) } - val showUnreadAndFavorites = remember { appPrefs.showUnreadAndFavorites.state }.value - val allChats by remember(chatModel.chats.value) { - derivedStateOf { filterContactTypes(chatModel.chats.value, listOf(ContactType.CHAT_DELETED)) } - } - val filteredContactChats = filteredContactChats( - showUnreadAndFavorites = showUnreadAndFavorites, - searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, - searchShowingSimplexLink = searchShowingSimplexLink, - searchText = searchText.value.text, - contactChats = allChats - ) - - LazyColumnWithScrollBar( - Modifier.fillMaxSize(), - reverseLayout = oneHandUI.value, - ) { - item { - if (!oneHandUI.value) { - Divider() - } - ContactsSearchBar( - listState = listState, - searchText = searchText, - searchShowingSimplexLink = searchShowingSimplexLink, - searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, - close = close, - ) - Divider() - - Spacer(Modifier.padding(bottom = DEFAULT_PADDING)) - } - - itemsIndexed(filteredContactChats) { index, chat -> - val nextChatSelected = remember(chat.id, filteredContactChats) { - derivedStateOf { - chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value - } - } - ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false) - } - } - if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) { - Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING)) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - Text( - generalGetString(MR.strings.no_filtered_contacts), - color = MaterialTheme.colors.secondary, + item { + if (!oneHandUI.value) { + Box(contentAlignment = Alignment.Center) { + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.deleted_chats), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding ) } } } + item { + if (!oneHandUI.value) { + Divider() + } + ContactsSearchBar( + listState = listState, + searchText = searchText, + searchShowingSimplexLink = searchShowingSimplexLink, + searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, + close = close, + ) + Divider() + } + + item { + if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) { + Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING)) { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + Text( + generalGetString(MR.strings.no_filtered_contacts), + color = MaterialTheme.colors.secondary, + ) + } + } + } + } + + itemsIndexed(filteredContactChats) { index, chat -> + val nextChatSelected = remember(chat.id, filteredContactChats) { + derivedStateOf { + chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value + } + } + ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false) + } } } } From 6adf8f29b087d16d2537393dbc656949f80ade11 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 30 Aug 2024 19:59:40 +0100 Subject: [PATCH 076/440] 6.0.4: ios 236, android 237, desktop 65 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 48 +++++++++------------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b49e8ffd11..4a537f14bf 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1879,7 +1879,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1904,7 +1904,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1928,7 +1928,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1953,7 +1953,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1969,11 +1969,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1989,11 +1989,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2014,7 +2014,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2029,7 +2029,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2051,7 +2051,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2066,7 +2066,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2088,7 +2088,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2105,10 +2105,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Libraries", - ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", @@ -2118,7 +2114,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2143,7 +2139,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2160,10 +2156,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Libraries", - ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", @@ -2173,7 +2165,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2198,7 +2190,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2213,7 +2205,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2232,7 +2224,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 236; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2247,7 +2239,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index c2b93f3efe..ac2fce0e12 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.0.3 -android.version_code=235 +android.version_name=6.0.4 +android.version_code=237 -desktop.version_name=6.0.3 -desktop.version_code=64 +desktop.version_name=6.0.4 +desktop.version_code=65 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 41cb734d5681e6bff70a59bb100edc9e566334b1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 30 Aug 2024 21:31:57 +0100 Subject: [PATCH 077/440] docs: FAQ on deletion of sent messages and read receipts (#4470) * docs: FAQ on deletion of sent messages and read receipts * update --- .../xcshareddata/swiftpm/Package.resolved | 3 +- docs/FAQ.md | 35 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c8623a95cb..22312bf5a1 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,7 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { - "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" + "branch" : "master", + "revision" : "7c50eb60ca4b90043c6ad719d595803488496212" } }, { diff --git a/docs/FAQ.md b/docs/FAQ.md index a9f94b49eb..774054e58c 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -15,7 +15,9 @@ revision: 23.04.2024 - [How to configure and delete groups?](#how-to-configure-and-delete-groups) - [Are there any reactions to messages? Can I answer specific messages directly?](#are-there-any-reactions-to-messages-can-i-answer-specific-messages-directly) - [What do checkmarks mean?](#what-do-checkmarks-mean) +- [I want to see when my contacts read my messages](#i-want-to-see-when-my-contacts-read-my-messages) - [Can I use the same profile on desktop? Do messages sync cross-platform?](#can-i-use-the-same-profile-on-desktop-do-messages-sync-cross-platform) +- [Why cannot I delete messages I sent from my contact's device?](#why-cannot-i-delete-messages-i-sent-from-my-contacts-device) [Troubleshooting](#troubleshooting) - [I do not receive messages or message notifications](#i-do-not-receive-messages-or-message-notifications) @@ -79,12 +81,43 @@ It's quite simple: - two checkmarks - message is delivered to the recipient's device. "sent" means accepted by the relay for delivery, "delivered" - stored on the recipient device. -Also see [ ](#i-do-not-see-the-second-tick-on-the-messages-i-sent) +Also see: [I do not see the second tick on the messages I sent](#i-do-not-see-the-second-tick-on-the-messages-i-sent) + +### I want to see when my contacts read my messages + +To know when your contact read your messages, your contact's app has to send you a confirmation message. And vice versa, for your contact to know when you read the message, your app has to send a confirmation message. + +The important questions for this feature: +- do you always want that your contacts can see when you read all their messages? Probably, even with your close friends, sometimes you would prefer to have time before you answer their message, and also have a plausible deniability that you have not seen the message. And this should be ok - in the end, this is your device, and it should be for you to decide whether this confirmation message is sent or not, and when it is sent. +- what practical problems an automatic notification sent to your contacts when you read the message solves for you compared with you simply adding a reaction to a message or sending a quick reply? + +Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addicitve patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. + +We think that delivery receipts are important and equally benefit both sides as the conversation, as they confirm that communication network functions properly. But we strongly believe that read receipts is an anti-feature that only benefits the app developers, and hurts the relations between the app users. So we are not planning to add it even as opt-in. In case you want your contact to know you've read the message put a reaction to it. And if you don't want them to know it - it is also ok, what your device sends should be under your control. ### Can I use the same profile on desktop? Do messages sync cross-platform? You can use your profile from mobile device on desktop. However, to do so you need to be on the same network, both on your mobile and desktop. More about it: [Release info](../blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md#link-mobile-and-desktop-apps-via-secure-quantum-resistant-protocol). +### Why cannot I delete messages I sent from my contact's device? + +In SimpleX Chat, you and your contacts can delete the messages you send from recipients' devices if you both agree to that within 24 hours of sending it. To be able to do that you both have to enable "Delete for everyone" option in Contact preferences - tap on the contact's name above the conversation to get there. + +You can also revoke the files you send. If the recipients did not yet receive the file, they will not be able to receive it after the file is revoked. + +This is different from most other messengers that allow deleting messages from the recipients' devices without any agreement with the recipients. + +We believe that allowing deleting information from your device to your contacts is a very wrong design decision for several reasons: +1) it violates your data sovereignty as the device owner - once your are in possession of any information, you have the rights to retain it, and any deletion should be agreed with you. And security and privacy is not possible if users don't have sovereignty over their devices. +2) it may be a business communication, and either your organisation policy or a compliance requirement is that every message you receive must be preserved for some time. +3) the message can contain a legally binding promise, effectively a contract between you and your contact, in which case you both need to keep it. +4) the messages may contain threat or abuse and you may want to keep them as a proof. +5) you may have paid for the the message (e.g., it can be a design project or consulting report), and you don't want it to suddenly disappear before you had a chance to store it outside of the conversation. + +It is also important to remember, that even if your contact enabled "Delete for everyone", you cannot really see it as a strong guarantee that the message will be deleted. Your contact's app can have a very simple modification (a one-line code change), that would prevent this deletion from happening when you request it. So you cannot see it as something that guarantees your security from your contacts. + +When "Delete for everyone" is not enabled, you can still mark the sent message as deleted within 24 hours of sending it. In this case the recipient will see it as "deleted message", and will be able to reveal the original message. + ## Troubleshooting ### I do not receive messages or message notifications From 7a5b04d523f3cede54919eb93be0df50a169bc8d Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 31 Aug 2024 11:39:43 +0100 Subject: [PATCH 078/440] faq: private message routing (#4807) * faq: private message routing * readme * corrections --- README.md | 34 ++++++----- ...5.8-private-message-routing-chat-themes.md | 3 +- docs/FAQ.md | 60 +++++++++++++++++-- 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 1a500187c0..e62fb8c8b3 100644 --- a/README.md +++ b/README.md @@ -300,25 +300,27 @@ What is already implemented: 1. Instead of user profile identifiers used by all other platforms, even the most private ones, SimpleX uses [pairwise per-queue identifiers](./docs/GLOSSARY.md#pairwise-pseudonymous-identifier) (2 addresses for each unidirectional message queue, with an optional 3rd address for push notifications on iOS, 2 queues in each connection between the users). It makes observing the network graph on the application level more difficult, as for `n` users there can be up to `n * (n-1)` message queues. 2. [End-to-end encryption](./docs/GLOSSARY.md#end-to-end-encryption) in each message queue using [NaCl cryptobox](https://nacl.cr.yp.to/box.html). This is added to allow redundancy in the future (passing each message via several servers), to avoid having the same ciphertext in different queues (that would only be visible to the attacker if TLS is compromised). The encryption keys used for this encryption are not rotated, instead we are planning to rotate the queues. Curve25519 keys are used for key negotiation. 3. [Double ratchet](./docs/GLOSSARY.md#double-ratchet-algorithm) end-to-end encryption in each conversation between two users (or group members). This is the same algorithm that is used in Signal and many other messaging apps; it provides OTR messaging with [forward secrecy](./docs/GLOSSARY.md#forward-secrecy) (each message is encrypted by its own ephemeral key) and [break-in recovery](./docs/GLOSSARY.md#post-compromise-security) (the keys are frequently re-negotiated as part of the message exchange). Two pairs of Curve448 keys are used for the initial [key agreement](./docs/GLOSSARY.md#key-agreement-protocol), initiating party passes these keys via the connection link, accepting side - in the header of the confirmation message. -4. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well). -5. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks. -6. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed. -7. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448. -8. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key. -9. To protect your IP address all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details. -10. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings. -11. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections. -12. Manual messaging queue rotations to move conversation to another SMP relay. -13. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). -14. Local files encryption. +4. [Post-quantum resistant key exchange](./docs/GLOSSARY.md#post-quantum-cryptography) in double ratchet protocol *on every ratchet step*. Read more in [this post](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) and also see this [publication by Apple]( https://security.apple.com/blog/imessage-pq3/) explaining the need for post-quantum key rotation. +5. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well). +6. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks. +7. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed. +8. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448. +9. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key. +10. To protect your IP address from unknown messaging relays, and for per-message transport anonymity (compared with Tor/VPN per-connection anonymity), from v6.0 all SimpleX Chat clients use private message routing by default. Read more in [this post](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md#private-message-routing). +11. To protect your IP address from unknown file relays, when SOCKS proxy is not enabled SimpleX Chat clients ask for a confirmation before downloading the files from unknown servers. +12. To protect your IP address from known servers all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details. +13. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings. +14. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections. +15. Manual messaging queue rotations to move conversation to another SMP relay. +16. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). +17. Local files encryption. We plan to add: -1. Senders' SMP relays and recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. -2. Post-quantum resistant key exchange in double ratchet protocol. -3. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). -4. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. -5. Reproducible builds – this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code. +1. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). +2. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. +3. Reproducible builds – this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code. +4. Recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. ## For developers diff --git a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md index 9e915bd3c4..0519e78e7b 100644 --- a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md +++ b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md @@ -96,8 +96,9 @@ The diagram below shows all the encryption layers used in private message routin For private routing to work, both the forwardig and the destination relays should support the updated messaging protocol - it is supported from v5.8 of the messaging relays. It is already released to all relays preset in the app, and available as a self-hosted server. We updated [the guide](../docs/SERVER.md) about how to host your own messaging relays. Because many self-hosted relays did not upgrade yet, private routing is not enabled by default. To enable it, you can open *Network & servers* settings in the app and change the settings in *Private message routing* section. We recommend setting *Private routing* option to *Unprotected* (to use it only with unknown relays and when not connecting via Tor) and *Allow downgrade* to *Yes* (so messages can still be delivered to the messaging relays that didn't upgrade yet) or to *When IP hidden* (in which case the messages will fail to deliver to unknown relays that didn't upgrade yet unless you connect to them via Tor). +See [F.A.Q. section](../docs/FAQ.md#does-simplex-protect-my-ip-address) for answers about private message routing. -Read more about the technical design of the private message routing in [this document](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/2023-09-12-second-relays.md). +Read more about the technical design of the private message routing in [this document](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/done/2023-09-12-second-relays.md) and in [the messaging protocol specification](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/simplex-messaging.md#proxying-sender-commands). ## Server transparency diff --git a/docs/FAQ.md b/docs/FAQ.md index 774054e58c..d8a8d5938f 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -32,6 +32,8 @@ revision: 23.04.2024 - [Does SimpleX support post quantum cryptography?](#does-simplex-support-post-quantum-cryptography) - [What user data can be provided on request?](#what-user-data-can-be-provided-on-request) - [Does SimpleX protect my IP address?](#does-simplex-protect-my-ip-address) +- [Doesn't private message routing reinvent Tor?](#doesnt-private-message-routing-reinvent-tor) +- [Why don't you embed Tor in SimpleX Chat app?](#why-dont-you-embed-tor-in-simplex-chat-app) - [Can I host my own relays?](#can-i-host-my-own-relays) [Funding and business model](#funding-and-business-model) @@ -91,7 +93,7 @@ The important questions for this feature: - do you always want that your contacts can see when you read all their messages? Probably, even with your close friends, sometimes you would prefer to have time before you answer their message, and also have a plausible deniability that you have not seen the message. And this should be ok - in the end, this is your device, and it should be for you to decide whether this confirmation message is sent or not, and when it is sent. - what practical problems an automatic notification sent to your contacts when you read the message solves for you compared with you simply adding a reaction to a message or sending a quick reply? -Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addicitve patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. +Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addictive patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. We think that delivery receipts are important and equally benefit both sides as the conversation, as they confirm that communication network functions properly. But we strongly believe that read receipts is an anti-feature that only benefits the app developers, and hurts the relations between the app users. So we are not planning to add it even as opt-in. In case you want your contact to know you've read the message put a reaction to it. And if you don't want them to know it - it is also ok, what your device sends should be under your control. @@ -109,7 +111,7 @@ This is different from most other messengers that allow deleting messages from t We believe that allowing deleting information from your device to your contacts is a very wrong design decision for several reasons: 1) it violates your data sovereignty as the device owner - once your are in possession of any information, you have the rights to retain it, and any deletion should be agreed with you. And security and privacy is not possible if users don't have sovereignty over their devices. -2) it may be a business communication, and either your organisation policy or a compliance requirement is that every message you receive must be preserved for some time. +2) it may be a business communication, and either your organization policy or a compliance requirement is that every message you receive must be preserved for some time. 3) the message can contain a legally binding promise, effectively a contract between you and your contact, in which case you both need to keep it. 4) the messages may contain threat or abuse and you may want to keep them as a proof. 5) you may have paid for the the message (e.g., it can be a design project or consulting report), and you don't want it to suddenly disappear before you had a chance to store it outside of the conversation. @@ -208,7 +210,7 @@ To determine whether it is the limitation of your, your contact's or both device - if it is shown on your screen as soon as you start the call, then your device does not support call encryption. - if in the beginning of the call your device shows "e2e encryption" but when your contact accepts the call it changes to "no e2e encryption", then it is only your contact's device that does not support it. -You need to upgrade webview (some Android systems allow it), Android system or the device to have support for e2e encryption in the calls - all modern webviews (and browsers) support it. +You need to upgrade webview (some Android systems allow it), Android system or the device to have support for e2e encryption in the calls - all modern WebViews (and browsers) support it. ### I clicked the link to connect, but could not connect @@ -232,9 +234,55 @@ Please see our [Privacy Policy](../PRIVACY.md) and [Transparency Reports](./TRAN ### Does SimpleX protect my IP address? -Not fully yet, it is a work in progress. While your device does not connect to your contacts' devices directly, as it happens in p2p networks, your contacts can self-host their relays, and you will connect to them when sending messages. A modified relay can record IP addresses connecting devices, as is the case with any other server, including Tor entry nodes, VPN providers, etc. - IP address is fundamental to Internet functioning, and there will always be some server that can observe your IP address. +Yes! -We are currently working on the next version of message routing protocol that will protect your IP address from the relays chosen by your contacts, so it will only be visible to the relays chosen by you. Read about technical details here: [RFC](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/2023-09-12-second-relays.md). +SimpleX Chat from version 6.0 uses *private message routing* whenever you send messages to unknown servers (all servers in app network settings, both enabled and not, are considered "known"). + +For private routing to work, the servers chosen by your contacts (and by the group members in your groups) must be upgraded to the recent versions. Messaging servers include support for private routing from v5.8, but we recommend using the latest versions. + +If the servers didn't upgrade, the messages would temporarily fail to deliver. You will see an orange warning icon on the message, and you can decide if you want to deliver them by connecting to these servers directly (it would require changing network settings). At the time of writing (August 2024), all preset servers and absolute majority of self-hosted servers we can see on the network support private message routing. + +With private routing enabled, instead of connecting to your contact's server directly, your client would "instruct" one of the known servers to forward the message, preventing the destination server from observing your IP address. + +Your messages are additionally end-to-end encrypted between your client and the destination server, so that the forwarding server cannot observe the destination addresses and server responses – similarly to how onion routing work. Private message routing is, effectively, a two-hop onion packet routing. + +Also, this connection is protected from man-in-the-middle attack by the forwarding server, as your client will validate destination server certificate using its fingerprint in the server address. + +You can optionally enable private message routing for all servers in Advanced network settings to complicate traffic correlation for known servers too. This will be default once the clients are improved to "know about" and to take into account network server operators. + +See [this post](../blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md#private-message-routing) for more details about how private message routing works. + +### Doesn't private message routing reinvent Tor? + +No, it does not! + +It provides better privacy for messaging than Tor, and it can be used with and without Tor or other means to additionally protect your traffic from known servers as well. + +Tor, VPN and other transport overlay networks route sockets, by creating long-lived TCP circuits between you and the destination server. While it protects your IP address, it does not protect your activity within this circuit. E.g., if you visit a website via Tor, it can still observe all pages you view within a session. Likewise, if you were connecting directly to a messaging server via Tor, this server would be able to list all message queues you send messages to. + +Private message routing routes packets (each message is one 16kb packet), not sockets. Unlike Tor and VPN, it does not create circuits between your client and destination servers. The forwarding server creates one shared session between itself and the destination, and forwards all messages from you and other clients to that destination server, mixing messages from many clients into a single TCP session. + +As each message uses its own random encryption key and random (non-sequential) identifier, the destination server cannot link multiple message queue addresses to the same client. At the same time, the forwarding server cannot observe which (and how many) addresses on the destination server your client sends messages to, thanks to e2e encryption between the client and destination server. In that regard, this design is similar to onion routing, but with per-packet anonymity, not per-circuit. + +This design is similar to mixnets (e.g. [Nym network](https://nymtech.net)), and it is tailored to the needs of message routing, providing better transport anonymity that general purpose networks, like Tor or VPN. You still can use Tor or VPN to connect to known servers, to protect your IP address from them. + +### Why don't you embed Tor in SimpleX Chat app? + +[Tor](https://www.torproject.org) is a fantastic transport overlay network - we believe it might be the best there is right now. If its [threat model](https://support.torproject.org/about/attacks-on-onion-routing/) works for you, you absolutely should use it - SimpleX Chat app supports Tor via SOCKS proxy [since v3.1](https://simplex.chat/blog/20220808-simplex-chat-v3.1-chat-groups.html#access-messaging-servers-via-tor), and SimpleX network servers can be available on both public and onion address at the same time [since v3.2](https://simplex.chat/blog/20220901-simplex-chat-v3.2-incognito-mode.html#using-onion-server-addresses-with-tor), improving anonymity of the users who use Tor. + +If you host your messaging server on the onion address only, the users who don't use Tor would still be able to message you via private message routing - all preset servers are configured to forward messages to onion-only servers. + +But there are many reasons not to embed Tor in the app: +- it increases response latency, error rate, and battery usage, and we believe that for most users enabling Tor by default would be a bad trade-off. +- it would require us regularly updating Tor library in the app, and your Tor integrity would depend on us – you would be "putting too many eggs in one basket". +- some networks restrict Tor traffic, so the app UI would have to support advanced Tor configuration, diverting our limited resources from the core app features that benefit all users. +- some countries have legislative restrictions on Tor usage, so we would have to support multiple app versions, also increasing our costs and slowing down the progress. + +The last, but not the least, it would create an unfair competitive advantage to Tor. We believe in competition, and we want our users to be able to choose which transport overlay network to use, based on what network threat model works best for them. + +If you want to use Tor or any other overlay network, such as i2p, [Nym network](https://nymtech.net), [Katzenpost](https://katzenpost.network), etc., you need to research their limitations, because none of them provides absolute anonymity against all possible attackers. + +And if after that research you decide to use Tor, it takes about 2 minutes to install and start [Orbot app](https://guardianproject.info/apps/org.torproject.android/). We believe that if it seems complex, then you *should not* be using Tor - it is an advanced technology that can only improve your privacy and anonymity if you understand its limitations and know how to configure it. ### Can I host my own relays? @@ -244,7 +292,7 @@ Of course! Please check these tutorials: [SMP server](./SERVER.md) and [XFTP ser ### How are you funded? -SimpleX Chat Ltd is funded by private investors and venture capital. As an open-source project, it is also being generously supported by donations as well. Read [more details](../blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md#how-is-it-funded-and-what-is-the-business-model). +SimpleX Chat Ltd is funded by private investors and venture capital. As an open-source project, it is also being generously supported by donations as well. Read the posts [from 2023](../blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md#how-is-it-funded-and-what-is-the-business-model) and [from 2024](../blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) for more details. ### Why VCs? From 8cc03b6c21dd407248ad2517281e7d2a51e988f2 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 30 Aug 2024 21:31:57 +0100 Subject: [PATCH 079/440] docs: FAQ on deletion of sent messages and read receipts (#4470) * docs: FAQ on deletion of sent messages and read receipts * update --- .../xcshareddata/swiftpm/Package.resolved | 3 +- docs/FAQ.md | 35 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c8623a95cb..22312bf5a1 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,7 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { - "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" + "branch" : "master", + "revision" : "7c50eb60ca4b90043c6ad719d595803488496212" } }, { diff --git a/docs/FAQ.md b/docs/FAQ.md index a9f94b49eb..774054e58c 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -15,7 +15,9 @@ revision: 23.04.2024 - [How to configure and delete groups?](#how-to-configure-and-delete-groups) - [Are there any reactions to messages? Can I answer specific messages directly?](#are-there-any-reactions-to-messages-can-i-answer-specific-messages-directly) - [What do checkmarks mean?](#what-do-checkmarks-mean) +- [I want to see when my contacts read my messages](#i-want-to-see-when-my-contacts-read-my-messages) - [Can I use the same profile on desktop? Do messages sync cross-platform?](#can-i-use-the-same-profile-on-desktop-do-messages-sync-cross-platform) +- [Why cannot I delete messages I sent from my contact's device?](#why-cannot-i-delete-messages-i-sent-from-my-contacts-device) [Troubleshooting](#troubleshooting) - [I do not receive messages or message notifications](#i-do-not-receive-messages-or-message-notifications) @@ -79,12 +81,43 @@ It's quite simple: - two checkmarks - message is delivered to the recipient's device. "sent" means accepted by the relay for delivery, "delivered" - stored on the recipient device. -Also see [ ](#i-do-not-see-the-second-tick-on-the-messages-i-sent) +Also see: [I do not see the second tick on the messages I sent](#i-do-not-see-the-second-tick-on-the-messages-i-sent) + +### I want to see when my contacts read my messages + +To know when your contact read your messages, your contact's app has to send you a confirmation message. And vice versa, for your contact to know when you read the message, your app has to send a confirmation message. + +The important questions for this feature: +- do you always want that your contacts can see when you read all their messages? Probably, even with your close friends, sometimes you would prefer to have time before you answer their message, and also have a plausible deniability that you have not seen the message. And this should be ok - in the end, this is your device, and it should be for you to decide whether this confirmation message is sent or not, and when it is sent. +- what practical problems an automatic notification sent to your contacts when you read the message solves for you compared with you simply adding a reaction to a message or sending a quick reply? + +Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addicitve patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. + +We think that delivery receipts are important and equally benefit both sides as the conversation, as they confirm that communication network functions properly. But we strongly believe that read receipts is an anti-feature that only benefits the app developers, and hurts the relations between the app users. So we are not planning to add it even as opt-in. In case you want your contact to know you've read the message put a reaction to it. And if you don't want them to know it - it is also ok, what your device sends should be under your control. ### Can I use the same profile on desktop? Do messages sync cross-platform? You can use your profile from mobile device on desktop. However, to do so you need to be on the same network, both on your mobile and desktop. More about it: [Release info](../blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md#link-mobile-and-desktop-apps-via-secure-quantum-resistant-protocol). +### Why cannot I delete messages I sent from my contact's device? + +In SimpleX Chat, you and your contacts can delete the messages you send from recipients' devices if you both agree to that within 24 hours of sending it. To be able to do that you both have to enable "Delete for everyone" option in Contact preferences - tap on the contact's name above the conversation to get there. + +You can also revoke the files you send. If the recipients did not yet receive the file, they will not be able to receive it after the file is revoked. + +This is different from most other messengers that allow deleting messages from the recipients' devices without any agreement with the recipients. + +We believe that allowing deleting information from your device to your contacts is a very wrong design decision for several reasons: +1) it violates your data sovereignty as the device owner - once your are in possession of any information, you have the rights to retain it, and any deletion should be agreed with you. And security and privacy is not possible if users don't have sovereignty over their devices. +2) it may be a business communication, and either your organisation policy or a compliance requirement is that every message you receive must be preserved for some time. +3) the message can contain a legally binding promise, effectively a contract between you and your contact, in which case you both need to keep it. +4) the messages may contain threat or abuse and you may want to keep them as a proof. +5) you may have paid for the the message (e.g., it can be a design project or consulting report), and you don't want it to suddenly disappear before you had a chance to store it outside of the conversation. + +It is also important to remember, that even if your contact enabled "Delete for everyone", you cannot really see it as a strong guarantee that the message will be deleted. Your contact's app can have a very simple modification (a one-line code change), that would prevent this deletion from happening when you request it. So you cannot see it as something that guarantees your security from your contacts. + +When "Delete for everyone" is not enabled, you can still mark the sent message as deleted within 24 hours of sending it. In this case the recipient will see it as "deleted message", and will be able to reveal the original message. + ## Troubleshooting ### I do not receive messages or message notifications From fff29f8548241461247f762307005114a64b6ccb Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 31 Aug 2024 11:39:43 +0100 Subject: [PATCH 080/440] faq: private message routing (#4807) * faq: private message routing * readme * corrections --- README.md | 34 ++++++----- ...5.8-private-message-routing-chat-themes.md | 3 +- docs/FAQ.md | 60 +++++++++++++++++-- 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 1a500187c0..e62fb8c8b3 100644 --- a/README.md +++ b/README.md @@ -300,25 +300,27 @@ What is already implemented: 1. Instead of user profile identifiers used by all other platforms, even the most private ones, SimpleX uses [pairwise per-queue identifiers](./docs/GLOSSARY.md#pairwise-pseudonymous-identifier) (2 addresses for each unidirectional message queue, with an optional 3rd address for push notifications on iOS, 2 queues in each connection between the users). It makes observing the network graph on the application level more difficult, as for `n` users there can be up to `n * (n-1)` message queues. 2. [End-to-end encryption](./docs/GLOSSARY.md#end-to-end-encryption) in each message queue using [NaCl cryptobox](https://nacl.cr.yp.to/box.html). This is added to allow redundancy in the future (passing each message via several servers), to avoid having the same ciphertext in different queues (that would only be visible to the attacker if TLS is compromised). The encryption keys used for this encryption are not rotated, instead we are planning to rotate the queues. Curve25519 keys are used for key negotiation. 3. [Double ratchet](./docs/GLOSSARY.md#double-ratchet-algorithm) end-to-end encryption in each conversation between two users (or group members). This is the same algorithm that is used in Signal and many other messaging apps; it provides OTR messaging with [forward secrecy](./docs/GLOSSARY.md#forward-secrecy) (each message is encrypted by its own ephemeral key) and [break-in recovery](./docs/GLOSSARY.md#post-compromise-security) (the keys are frequently re-negotiated as part of the message exchange). Two pairs of Curve448 keys are used for the initial [key agreement](./docs/GLOSSARY.md#key-agreement-protocol), initiating party passes these keys via the connection link, accepting side - in the header of the confirmation message. -4. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well). -5. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks. -6. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed. -7. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448. -8. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key. -9. To protect your IP address all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details. -10. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings. -11. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections. -12. Manual messaging queue rotations to move conversation to another SMP relay. -13. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). -14. Local files encryption. +4. [Post-quantum resistant key exchange](./docs/GLOSSARY.md#post-quantum-cryptography) in double ratchet protocol *on every ratchet step*. Read more in [this post](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) and also see this [publication by Apple]( https://security.apple.com/blog/imessage-pq3/) explaining the need for post-quantum key rotation. +5. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well). +6. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks. +7. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed. +8. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448. +9. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key. +10. To protect your IP address from unknown messaging relays, and for per-message transport anonymity (compared with Tor/VPN per-connection anonymity), from v6.0 all SimpleX Chat clients use private message routing by default. Read more in [this post](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md#private-message-routing). +11. To protect your IP address from unknown file relays, when SOCKS proxy is not enabled SimpleX Chat clients ask for a confirmation before downloading the files from unknown servers. +12. To protect your IP address from known servers all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details. +13. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings. +14. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections. +15. Manual messaging queue rotations to move conversation to another SMP relay. +16. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). +17. Local files encryption. We plan to add: -1. Senders' SMP relays and recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. -2. Post-quantum resistant key exchange in double ratchet protocol. -3. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). -4. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. -5. Reproducible builds – this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code. +1. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). +2. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. +3. Reproducible builds – this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code. +4. Recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. ## For developers diff --git a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md index 9e915bd3c4..0519e78e7b 100644 --- a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md +++ b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md @@ -96,8 +96,9 @@ The diagram below shows all the encryption layers used in private message routin For private routing to work, both the forwardig and the destination relays should support the updated messaging protocol - it is supported from v5.8 of the messaging relays. It is already released to all relays preset in the app, and available as a self-hosted server. We updated [the guide](../docs/SERVER.md) about how to host your own messaging relays. Because many self-hosted relays did not upgrade yet, private routing is not enabled by default. To enable it, you can open *Network & servers* settings in the app and change the settings in *Private message routing* section. We recommend setting *Private routing* option to *Unprotected* (to use it only with unknown relays and when not connecting via Tor) and *Allow downgrade* to *Yes* (so messages can still be delivered to the messaging relays that didn't upgrade yet) or to *When IP hidden* (in which case the messages will fail to deliver to unknown relays that didn't upgrade yet unless you connect to them via Tor). +See [F.A.Q. section](../docs/FAQ.md#does-simplex-protect-my-ip-address) for answers about private message routing. -Read more about the technical design of the private message routing in [this document](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/2023-09-12-second-relays.md). +Read more about the technical design of the private message routing in [this document](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/done/2023-09-12-second-relays.md) and in [the messaging protocol specification](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/simplex-messaging.md#proxying-sender-commands). ## Server transparency diff --git a/docs/FAQ.md b/docs/FAQ.md index 774054e58c..d8a8d5938f 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -32,6 +32,8 @@ revision: 23.04.2024 - [Does SimpleX support post quantum cryptography?](#does-simplex-support-post-quantum-cryptography) - [What user data can be provided on request?](#what-user-data-can-be-provided-on-request) - [Does SimpleX protect my IP address?](#does-simplex-protect-my-ip-address) +- [Doesn't private message routing reinvent Tor?](#doesnt-private-message-routing-reinvent-tor) +- [Why don't you embed Tor in SimpleX Chat app?](#why-dont-you-embed-tor-in-simplex-chat-app) - [Can I host my own relays?](#can-i-host-my-own-relays) [Funding and business model](#funding-and-business-model) @@ -91,7 +93,7 @@ The important questions for this feature: - do you always want that your contacts can see when you read all their messages? Probably, even with your close friends, sometimes you would prefer to have time before you answer their message, and also have a plausible deniability that you have not seen the message. And this should be ok - in the end, this is your device, and it should be for you to decide whether this confirmation message is sent or not, and when it is sent. - what practical problems an automatic notification sent to your contacts when you read the message solves for you compared with you simply adding a reaction to a message or sending a quick reply? -Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addicitve patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. +Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addictive patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. We think that delivery receipts are important and equally benefit both sides as the conversation, as they confirm that communication network functions properly. But we strongly believe that read receipts is an anti-feature that only benefits the app developers, and hurts the relations between the app users. So we are not planning to add it even as opt-in. In case you want your contact to know you've read the message put a reaction to it. And if you don't want them to know it - it is also ok, what your device sends should be under your control. @@ -109,7 +111,7 @@ This is different from most other messengers that allow deleting messages from t We believe that allowing deleting information from your device to your contacts is a very wrong design decision for several reasons: 1) it violates your data sovereignty as the device owner - once your are in possession of any information, you have the rights to retain it, and any deletion should be agreed with you. And security and privacy is not possible if users don't have sovereignty over their devices. -2) it may be a business communication, and either your organisation policy or a compliance requirement is that every message you receive must be preserved for some time. +2) it may be a business communication, and either your organization policy or a compliance requirement is that every message you receive must be preserved for some time. 3) the message can contain a legally binding promise, effectively a contract between you and your contact, in which case you both need to keep it. 4) the messages may contain threat or abuse and you may want to keep them as a proof. 5) you may have paid for the the message (e.g., it can be a design project or consulting report), and you don't want it to suddenly disappear before you had a chance to store it outside of the conversation. @@ -208,7 +210,7 @@ To determine whether it is the limitation of your, your contact's or both device - if it is shown on your screen as soon as you start the call, then your device does not support call encryption. - if in the beginning of the call your device shows "e2e encryption" but when your contact accepts the call it changes to "no e2e encryption", then it is only your contact's device that does not support it. -You need to upgrade webview (some Android systems allow it), Android system or the device to have support for e2e encryption in the calls - all modern webviews (and browsers) support it. +You need to upgrade webview (some Android systems allow it), Android system or the device to have support for e2e encryption in the calls - all modern WebViews (and browsers) support it. ### I clicked the link to connect, but could not connect @@ -232,9 +234,55 @@ Please see our [Privacy Policy](../PRIVACY.md) and [Transparency Reports](./TRAN ### Does SimpleX protect my IP address? -Not fully yet, it is a work in progress. While your device does not connect to your contacts' devices directly, as it happens in p2p networks, your contacts can self-host their relays, and you will connect to them when sending messages. A modified relay can record IP addresses connecting devices, as is the case with any other server, including Tor entry nodes, VPN providers, etc. - IP address is fundamental to Internet functioning, and there will always be some server that can observe your IP address. +Yes! -We are currently working on the next version of message routing protocol that will protect your IP address from the relays chosen by your contacts, so it will only be visible to the relays chosen by you. Read about technical details here: [RFC](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/2023-09-12-second-relays.md). +SimpleX Chat from version 6.0 uses *private message routing* whenever you send messages to unknown servers (all servers in app network settings, both enabled and not, are considered "known"). + +For private routing to work, the servers chosen by your contacts (and by the group members in your groups) must be upgraded to the recent versions. Messaging servers include support for private routing from v5.8, but we recommend using the latest versions. + +If the servers didn't upgrade, the messages would temporarily fail to deliver. You will see an orange warning icon on the message, and you can decide if you want to deliver them by connecting to these servers directly (it would require changing network settings). At the time of writing (August 2024), all preset servers and absolute majority of self-hosted servers we can see on the network support private message routing. + +With private routing enabled, instead of connecting to your contact's server directly, your client would "instruct" one of the known servers to forward the message, preventing the destination server from observing your IP address. + +Your messages are additionally end-to-end encrypted between your client and the destination server, so that the forwarding server cannot observe the destination addresses and server responses – similarly to how onion routing work. Private message routing is, effectively, a two-hop onion packet routing. + +Also, this connection is protected from man-in-the-middle attack by the forwarding server, as your client will validate destination server certificate using its fingerprint in the server address. + +You can optionally enable private message routing for all servers in Advanced network settings to complicate traffic correlation for known servers too. This will be default once the clients are improved to "know about" and to take into account network server operators. + +See [this post](../blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md#private-message-routing) for more details about how private message routing works. + +### Doesn't private message routing reinvent Tor? + +No, it does not! + +It provides better privacy for messaging than Tor, and it can be used with and without Tor or other means to additionally protect your traffic from known servers as well. + +Tor, VPN and other transport overlay networks route sockets, by creating long-lived TCP circuits between you and the destination server. While it protects your IP address, it does not protect your activity within this circuit. E.g., if you visit a website via Tor, it can still observe all pages you view within a session. Likewise, if you were connecting directly to a messaging server via Tor, this server would be able to list all message queues you send messages to. + +Private message routing routes packets (each message is one 16kb packet), not sockets. Unlike Tor and VPN, it does not create circuits between your client and destination servers. The forwarding server creates one shared session between itself and the destination, and forwards all messages from you and other clients to that destination server, mixing messages from many clients into a single TCP session. + +As each message uses its own random encryption key and random (non-sequential) identifier, the destination server cannot link multiple message queue addresses to the same client. At the same time, the forwarding server cannot observe which (and how many) addresses on the destination server your client sends messages to, thanks to e2e encryption between the client and destination server. In that regard, this design is similar to onion routing, but with per-packet anonymity, not per-circuit. + +This design is similar to mixnets (e.g. [Nym network](https://nymtech.net)), and it is tailored to the needs of message routing, providing better transport anonymity that general purpose networks, like Tor or VPN. You still can use Tor or VPN to connect to known servers, to protect your IP address from them. + +### Why don't you embed Tor in SimpleX Chat app? + +[Tor](https://www.torproject.org) is a fantastic transport overlay network - we believe it might be the best there is right now. If its [threat model](https://support.torproject.org/about/attacks-on-onion-routing/) works for you, you absolutely should use it - SimpleX Chat app supports Tor via SOCKS proxy [since v3.1](https://simplex.chat/blog/20220808-simplex-chat-v3.1-chat-groups.html#access-messaging-servers-via-tor), and SimpleX network servers can be available on both public and onion address at the same time [since v3.2](https://simplex.chat/blog/20220901-simplex-chat-v3.2-incognito-mode.html#using-onion-server-addresses-with-tor), improving anonymity of the users who use Tor. + +If you host your messaging server on the onion address only, the users who don't use Tor would still be able to message you via private message routing - all preset servers are configured to forward messages to onion-only servers. + +But there are many reasons not to embed Tor in the app: +- it increases response latency, error rate, and battery usage, and we believe that for most users enabling Tor by default would be a bad trade-off. +- it would require us regularly updating Tor library in the app, and your Tor integrity would depend on us – you would be "putting too many eggs in one basket". +- some networks restrict Tor traffic, so the app UI would have to support advanced Tor configuration, diverting our limited resources from the core app features that benefit all users. +- some countries have legislative restrictions on Tor usage, so we would have to support multiple app versions, also increasing our costs and slowing down the progress. + +The last, but not the least, it would create an unfair competitive advantage to Tor. We believe in competition, and we want our users to be able to choose which transport overlay network to use, based on what network threat model works best for them. + +If you want to use Tor or any other overlay network, such as i2p, [Nym network](https://nymtech.net), [Katzenpost](https://katzenpost.network), etc., you need to research their limitations, because none of them provides absolute anonymity against all possible attackers. + +And if after that research you decide to use Tor, it takes about 2 minutes to install and start [Orbot app](https://guardianproject.info/apps/org.torproject.android/). We believe that if it seems complex, then you *should not* be using Tor - it is an advanced technology that can only improve your privacy and anonymity if you understand its limitations and know how to configure it. ### Can I host my own relays? @@ -244,7 +292,7 @@ Of course! Please check these tutorials: [SMP server](./SERVER.md) and [XFTP ser ### How are you funded? -SimpleX Chat Ltd is funded by private investors and venture capital. As an open-source project, it is also being generously supported by donations as well. Read [more details](../blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md#how-is-it-funded-and-what-is-the-business-model). +SimpleX Chat Ltd is funded by private investors and venture capital. As an open-source project, it is also being generously supported by donations as well. Read the posts [from 2023](../blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md#how-is-it-funded-and-what-is-the-business-model) and [from 2024](../blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) for more details. ### Why VCs? From d68a3ba80d356815c53d1910be434c3d5f3a995c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 31 Aug 2024 11:58:49 +0100 Subject: [PATCH 081/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 43 +++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 3 +- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 03c1108cd0..3700fbbf16 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E51ED5942C7B9983009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED58F2C7B9983009F2C7C /* libgmpxx.a */; }; - E51ED5952C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */; }; - E51ED5962C7B9983009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5912C7B9983009F2C7C /* libgmp.a */; }; - E51ED5972C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */; }; - E51ED5982C7B9983009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5932C7B9983009F2C7C /* libffi.a */; }; + E5BD84572C832BF9008C24D1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84522C832BF9008C24D1 /* libgmpxx.a */; }; + E5BD84582C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */; }; + E5BD84592C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */; }; + E5BD845A2C832BF9008C24D1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84552C832BF9008C24D1 /* libgmp.a */; }; + E5BD845B2C832BF9008C24D1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84562C832BF9008C24D1 /* libffi.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -550,11 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E51ED58F2C7B9983009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a"; sourceTree = ""; }; - E51ED5912C7B9983009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a"; sourceTree = ""; }; - E51ED5932C7B9983009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E5BD84522C832BF9008C24D1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a"; sourceTree = ""; }; + E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a"; sourceTree = ""; }; + E5BD84552C832BF9008C24D1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5BD84562C832BF9008C24D1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -645,17 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51ED5942C7B9983009F2C7C /* libgmpxx.a in Frameworks */, + E5BD84572C832BF9008C24D1 /* libgmpxx.a in Frameworks */, + E5BD845A2C832BF9008C24D1 /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + E5BD845B2C832BF9008C24D1 /* libffi.a in Frameworks */, + E5BD84582C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a in Frameworks */, + E5BD84592C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E51ED5982C7B9983009F2C7C /* libffi.a in Frameworks */, - E51ED5972C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a in Frameworks */, - E51ED5952C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a in Frameworks */, - E51ED5962C7B9983009F2C7C /* libgmp.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */, - E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */, - E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -732,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5932C7B9983009F2C7C /* libffi.a */, - E51ED5912C7B9983009F2C7C /* libgmp.a */, - E51ED58F2C7B9983009F2C7C /* libgmpxx.a */, - E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */, - E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */, + E5BD84562C832BF9008C24D1 /* libffi.a */, + E5BD84552C832BF9008C24D1 /* libgmp.a */, + E5BD84522C832BF9008C24D1 /* libgmpxx.a */, + E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */, + E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 22312bf5a1..c8623a95cb 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { - "branch" : "master", - "revision" : "7c50eb60ca4b90043c6ad719d595803488496212" + "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" } }, { From f94f0dea082bea18a8c4152a38066e47c731bb81 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 31 Aug 2024 14:34:46 +0100 Subject: [PATCH 082/440] website: fix links --- docs/TRANSPARENCY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/TRANSPARENCY.md b/docs/TRANSPARENCY.md index 43fdd12ac5..bae7a4f781 100644 --- a/docs/TRANSPARENCY.md +++ b/docs/TRANSPARENCY.md @@ -17,8 +17,8 @@ This page will include any and all reports on requests for user data. Our objective is to consistently ensure that no user data and absolute minimum of the metadata required for the network to function is available for disclosure by any infrastructure operators, under any circumstances. **Helpful resources**: -- [Privacy policy](../PRIVACY.md) -- [Privacy and security: technical details and limitations](../README.md#privacy-and-security-technical-details-and-limitations) +- [Privacy policy](/PRIVACY.md) +- [Privacy and security: technical details and limitations](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#privacy-and-security-technical-details-and-limitations) - Whitepaper: - [Trust in servers](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#trust-in-servers) - [Encryption Primitives Used](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#encryption-primitives-used) From 3b49817f173a0de33638c4f6ed703545420f9f0a Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 31 Aug 2024 17:24:50 +0000 Subject: [PATCH 083/440] desktop: fix vlc dependency (#4809) --- apps/multiplatform/common/build.gradle.kts | 2 +- apps/multiplatform/desktop/build.gradle.kts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index a1afb655e2..1670672753 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -102,7 +102,7 @@ kotlin { implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT") implementation("org.slf4j:slf4j-simple:2.0.12") - implementation("uk.co.caprica:vlcj:4.8.2") + implementation("uk.co.caprica:vlcj:4.8.3") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a") implementation("com.squareup.okhttp3:okhttp:4.12.0") diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts index 401c2938d5..e39ba48a0b 100644 --- a/apps/multiplatform/desktop/build.gradle.kts +++ b/apps/multiplatform/desktop/build.gradle.kts @@ -18,7 +18,6 @@ kotlin { dependencies { implementation(project(":common")) implementation(compose.desktop.currentOs) - implementation("net.java.dev.jna:jna:5.14.0") } } val jvmTest by getting From 087519c39def7da152fb976f2aeb362bd8906a8a Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 31 Aug 2024 17:24:50 +0000 Subject: [PATCH 084/440] desktop: fix vlc dependency (#4809) --- apps/multiplatform/common/build.gradle.kts | 2 +- apps/multiplatform/desktop/build.gradle.kts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 7e97ea3414..dd9c7ab161 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -102,7 +102,7 @@ kotlin { implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT") implementation("org.slf4j:slf4j-simple:2.0.12") - implementation("uk.co.caprica:vlcj:4.8.2") + implementation("uk.co.caprica:vlcj:4.8.3") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a") implementation("com.squareup.okhttp3:okhttp:4.12.0") diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts index 401c2938d5..e39ba48a0b 100644 --- a/apps/multiplatform/desktop/build.gradle.kts +++ b/apps/multiplatform/desktop/build.gradle.kts @@ -18,7 +18,6 @@ kotlin { dependencies { implementation(project(":common")) implementation(compose.desktop.currentOs) - implementation("net.java.dev.jna:jna:5.14.0") } } val jvmTest by getting From fe20a43232cb083c3b5930afbfad3f6f0d70432b Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:15:16 +0400 Subject: [PATCH 085/440] flatpak: update metainfo (#4811) --- .../flatpak/chat.simplex.simplex.metainfo.xml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 4ac5f88e14..2d48dc4dc3 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,41 @@ + + https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html + +

    New in v6.0.1-4:

    +
      +
    • reduce traffic and battery usage on unstable networks.
    • +
    • only offer the next versions for update, based on SerVer ordering.
    • +
    • UI fixes.
    • +
    • reduce app memory usage and start time.
    • +
    • faster sending of files to groups.
    • +
    • fix rare delivery bug.
    • +
    +

    New in v6.0:

    +

    New chat experience:

    +
      +
    • connect to your friends faster.
    • +
    • archive contacts to chat later.
    • +
    • delete up to 20 messages at once.
    • +
    • increase font size.
    • +
    +

    New media options:

    +
      +
    • play from the chat list.
    • +
    • blur for better privacy.
    • +
    +

    Private routing:

    +
      +
    • it protects your IP address and connections and is now enabled by default.
    • +
    +

    Connection and servers information:

    +
      +
    • to control your network status and usage.
    • +
    +
    +
    https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html From 33895b0330fd9e32dc9bc1e69bc15d54a9207cb2 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 3 Sep 2024 09:59:40 +0300 Subject: [PATCH 086/440] ios: show received messages using checkmark with slash (#4816) * ios: show received messages using checkmark with slash * update message info view * cleanup * remove dead arguments * Revert "remove dead arguments" This reverts commit 1fc07669c785dde960c77ee54026004871706acf. * remove status icon * cleanup * update assets * tweak checkmark * fix space, rename --------- Co-authored-by: Evgeny Poberezkin --- .../checkmark.2.symbolset/Contents.json | 12 + .../checkmark.2.symbolset/checkmark.2.svg | 227 ++++++++++++++++++ .../checkmark.wide.symbolset/Contents.json | 12 + .../checkmark.wide.svg | 218 +++++++++++++++++ .../Views/Chat/ChatItem/CIMetaView.swift | 57 ++--- .../Shared/Views/Chat/ChatItemInfoView.swift | 16 +- apps/ios/SimpleXChat/ChatTypes.swift | 54 +++-- 7 files changed, 514 insertions(+), 82 deletions(-) create mode 100644 apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json create mode 100644 apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg create mode 100644 apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json create mode 100644 apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json new file mode 100644 index 0000000000..8e38b499dd --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info": { + "author": "xcode", + "version": 1 + }, + "symbols": [ + { + "filename": "checkmark.2.svg", + "idiom": "universal" + } + ] +} \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg new file mode 100644 index 0000000000..577fa1db76 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg @@ -0,0 +1,227 @@ + + + checkmark.2 + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + + can be adjusted by modifying the x-location of the margin guidelines. + + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.5.0 + + + Requires Xcode 15 or greater + + + Generated from double.checkmark + + + Typeset at 100.0 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json new file mode 100644 index 0000000000..11a91cb811 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info": { + "author": "xcode", + "version": 1 + }, + "symbols": [ + { + "filename": "checkmark.wide.svg", + "idiom": "universal" + } + ] +} \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg new file mode 100644 index 0000000000..b5dfc6b3de --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg @@ -0,0 +1,218 @@ + + + checkmark.wide + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + + can be adjusted by modifying the x-location of the margin guidelines. + + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.5.0 + + + Requires Xcode 15 or greater + + + Generated from double.checkmark + + + Typeset at 100.0 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 3f2ec8f38e..719c1cabd0 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -25,41 +25,22 @@ struct CIMetaView: View { if chatItem.isDeletedContent { chatItem.timestampText.font(.caption).foregroundColor(metaColor) } else { - let meta = chatItem.meta - let ttl = chat.chatInfo.timedMessagesTTL - let encrypted = chatItem.encryptedFile - switch meta.itemStatus { - case let .sndSent(sndProgress): - switch sndProgress { - case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - } - case let .sndRcvd(_, sndProgress): - switch sndProgress { - case .complete: - ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - } - case .partial: - ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - } - } - default: - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - } + ciMetaText( + chatItem.meta, + chatTTL: chat.chatInfo.timedMessagesTTL, + encrypted: chatItem.encryptedFile, + color: chatItem.meta.itemStatus.sndProgress == .partial + ? paleMetaColor + : metaColor, + showStatus: showStatus, + showEdited: showEdited, + showViaProxy: showSentViaProxy, + showTimesamp: showTimestamp + ) } } } -enum SentCheckmark { - case sent - case rcvd1 - case rcvd2 -} - func ciMetaText( _ meta: CIMeta, chatTTL: Int?, @@ -67,7 +48,6 @@ func ciMetaText( color: Color = .clear, primaryColor: Color = .accentColor, transparent: Bool = false, - sent: SentCheckmark? = nil, showStatus: Bool = true, showEdited: Bool = true, showViaProxy: Bool, @@ -89,17 +69,8 @@ func ciMetaText( r = r + statusIconText("arrow.forward", color.opacity(0.67)).font(.caption2) } if showStatus { - if let (icon, statusColor) = meta.statusIcon(color, primaryColor) { - let t = Text(Image(systemName: icon)).font(.caption2) - let gap = Text(" ").kerning(-1.25) - let t1 = t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67)) - switch sent { - case nil: r = r + t1 - case .sent: r = r + t1 + gap - case .rcvd1: r = r + t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67)) + gap - case .rcvd2: r = r + gap + t1 - } - r = r + Text(" ") + if let (image, statusColor) = meta.itemStatus.statusIcon(color, primaryColor) { + r = r + Text(image).foregroundColor(transparent ? .clear : statusColor) + Text(" ") } else if !meta.disappearing { r = r + statusIconText("circlebadge.fill", .clear) + Text(" ") } diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index f6a856dad1..62ea607d27 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -450,20 +450,8 @@ struct ChatItemInfoView: View { .foregroundColor(theme.colors.secondary).opacity(0.67) } let v = Group { - let (icon, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) - switch status { - case .rcvd: - ZStack(alignment: .trailing) { - Image(systemName: icon) - .foregroundColor(statusColor.opacity(0.67)) - .padding(.trailing, 6) - Image(systemName: icon) - .foregroundColor(statusColor.opacity(0.67)) - } - default: - Image(systemName: icon) - .foregroundColor(statusColor) - } + let (image, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) + image.foregroundColor(statusColor) } if let (title, text) = status.statusInfo { diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 07340bb963..105edd725f 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2723,10 +2723,6 @@ public struct CIMeta: Decodable, Hashable { return false } - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color)? { - itemStatus.statusIcon(metaColor, primaryColor) - } - public static func getSample(_ id: Int64, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew, itemDeleted: CIDeleted? = nil, itemEdited: Bool = false, itemLive: Bool = false, deletable: Bool = true, editable: Bool = true) -> CIMeta { CIMeta( itemId: id, @@ -2794,21 +2790,29 @@ public enum CIStatus: Decodable, Hashable { } } - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color)? { + public func statusIcon(_ metaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { switch self { - case .sndNew: return nil - case .sndSent: return ("checkmark", metaColor) + case .sndNew: nil + case .sndSent: (Image("checkmark.wide"), metaColor) case let .sndRcvd(msgRcptStatus, _): switch msgRcptStatus { - case .ok: return ("checkmark", metaColor) - case .badMsgHash: return ("checkmark", .red) + case .ok: (Image("checkmark.2"), metaColor) + case .badMsgHash: (Image("checkmark.2"), .red) } - case .sndErrorAuth: return ("multiply", .red) - case .sndError: return ("multiply", .red) - case .sndWarning: return ("exclamationmark.triangle.fill", .orange) - case .rcvNew: return ("circlebadge.fill", primaryColor) - case .rcvRead: return nil - case .invalid: return ("questionmark", metaColor) + case .sndErrorAuth: (Image(systemName: "multiply"), .red) + case .sndError: (Image(systemName: "multiply"), .red) + case .sndWarning: (Image(systemName: "exclamationmark.triangle.fill"), .orange) + case .rcvNew: (Image(systemName: "circlebadge.fill"), primaryColor) + case .rcvRead: nil + case .invalid: (Image(systemName: "questionmark"), metaColor) + } + } + + public var sndProgress: SndCIStatusProgress? { + switch self { + case let .sndSent(sndProgress): sndProgress + case let .sndRcvd(_ , sndProgress): sndProgress + default: nil } } @@ -2903,20 +2907,20 @@ public enum GroupSndStatus: Decodable, Hashable { case warning(agentError: SndError) case invalid(text: String) - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color) { + public func statusIcon(_ metaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color) { switch self { - case .new: return ("ellipsis", metaColor) - case .forwarded: return ("chevron.forward.2", metaColor) - case .inactive: return ("person.badge.minus", metaColor) - case .sent: return ("checkmark", metaColor) + case .new: (Image(systemName: "ellipsis"), metaColor) + case .forwarded: (Image(systemName: "chevron.forward.2"), metaColor) + case .inactive: (Image(systemName: "person.badge.minus"), metaColor) + case .sent: (Image("checkmark.wide"), metaColor) case let .rcvd(msgRcptStatus): switch msgRcptStatus { - case .ok: return ("checkmark", metaColor) - case .badMsgHash: return ("checkmark", .red) + case .ok: (Image("checkmark.2"), metaColor) + case .badMsgHash: (Image("checkmark.2"), .red) } - case .error: return ("multiply", .red) - case .warning: return ("exclamationmark.triangle.fill", .orange) - case .invalid: return ("questionmark", metaColor) + case .error: (Image(systemName: "multiply"), .red) + case .warning: (Image(systemName: "exclamationmark.triangle.fill"), .orange) + case .invalid: (Image(systemName: "questionmark"), metaColor) } } From 31c4ff270584bf679e9256ff02df0874c85d62a0 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:36:01 +0000 Subject: [PATCH 087/440] android, desktop: group info remembers scroll position and search (#4817) --- .../views/chat/group/GroupChatInfoView.kt | 11 +++---- .../common/views/helpers/SearchTextField.kt | 6 ---- .../common/views/newchat/NewChatSheet.kt | 32 ++++++++++++++----- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 81a0de7bb9..a14d227074 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -40,7 +40,7 @@ import kotlinx.coroutines.launch const val SMALL_GROUPS_RCPS_MEM_LIMIT: Int = 20 @Composable -fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLink: String?, groupLinkMemberRole: GroupMemberRole?, onGroupLinkUpdated: (Pair?) -> Unit, close: () -> Unit, onSearchClicked: () -> Unit) { +fun ModalData.GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLink: String?, groupLinkMemberRole: GroupMemberRole?, onGroupLinkUpdated: (Pair?) -> Unit, close: () -> Unit, onSearchClicked: () -> Unit) { BackHandler(onBack = close) // TODO derivedStateOf? val chat = chatModel.chats.value.firstOrNull { ch -> ch.id == chatId && ch.remoteHostId == rhId } @@ -249,9 +249,8 @@ fun AddGroupMembersButton( ) } - @Composable -fun GroupChatInfoLayout( +fun ModalData.GroupChatInfoLayout( chat: Chat, groupInfo: GroupInfo, currentUser: User, @@ -272,12 +271,12 @@ fun GroupChatInfoLayout( close: () -> Unit = { ModalManager.closeAllModalsEverywhere()}, onSearchClicked: () -> Unit ) { - val listState = rememberLazyListState() + val listState = remember { appBarHandler.listState } val scope = rememberCoroutineScope() KeyChangeEffect(chat.id) { scope.launch { listState.scrollToItem(0) } } - val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) } + val searchText = remember { stateGetOrPut("searchText") { TextFieldValue() } } val filteredMembers = remember(members) { derivedStateOf { val s = searchText.value.text.trim().lowercase() @@ -674,7 +673,7 @@ private fun SearchRowView( @Composable fun PreviewGroupChatInfoLayout() { SimpleXTheme { - GroupChatInfoLayout( + ModalData().GroupChatInfoLayout( chat = Chat( remoteHostId = null, chatInfo = ChatInfo.Direct.sampleData, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt index c6d5d6ad16..60dceab4ad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt @@ -71,12 +71,6 @@ fun SearchTextField( } } - DisposableEffect(Unit) { - onDispose { - if (searchText.value.text.isNotEmpty()) onValueChange("") - } - } - val colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Unspecified, textColor = MaterialTheme.colors.onBackground, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 99ba7be8d2..7a9fec100d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -36,11 +36,13 @@ import chat.simplex.common.views.chatlist.ScrollDirection import chat.simplex.common.views.contacts.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import java.net.URI @Composable -fun NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) { +fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) { val oneHandUI = remember { appPrefs.oneHandUI.state } val keyboardState by getKeyboardState() val showToolbarInOneHandUI = remember { derivedStateOf { keyboardState == KeyboardState.Closed && oneHandUI.value } } @@ -111,10 +113,8 @@ private fun filterContactTypes(c: List, contactTypes: List): return c.filter { chat -> contactTypes.contains(chatContactType(chat)) } } -private var lazyListState = 0 to 0 - @Composable -private fun NewChatSheetLayout( +private fun ModalData.NewChatSheetLayout( rh: RemoteHostInfo?, addContact: () -> Unit, scanPaste: () -> Unit, @@ -122,7 +122,23 @@ private fun NewChatSheetLayout( close: () -> Unit, ) { val oneHandUI = remember { appPrefs.oneHandUI.state } - val listState = rememberLazyListState(lazyListState.first, lazyListState.second) + val listState = remember { appBarHandler.listState } + // This is workaround of an issue when position of a list is not restored (when going back to that screen) when a header exists. + // Upon returning back, this code returns correct index and position if number of items is the same + LaunchedEffect(Unit) { + val prevIndex = listState.firstVisibleItemIndex + val prevOffset = listState.firstVisibleItemScrollOffset + val total = listState.layoutInfo.totalItemsCount + if (prevIndex == 0 && prevOffset == 0) return@LaunchedEffect + snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } + .filter { it == 0 to 0 } + .collect { + if (total <= listState.layoutInfo.totalItemsCount) { + listState.scrollToItem(prevIndex, prevOffset) + } + cancel() + } + } val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } val searchShowingSimplexLink = remember { mutableStateOf(false) } val searchChatFilteredBySimplexLink = remember { mutableStateOf(null) } @@ -536,7 +552,7 @@ private fun contactTypesSearchTargets(baseContactTypes: List, searc } @Composable -private fun DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Unit, close: () -> Unit) { +private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Unit, close: () -> Unit) { val oneHandUI = remember { appPrefs.oneHandUI.state } val keyboardState by getKeyboardState() val showToolbarInOneHandUI = remember { derivedStateOf { keyboardState == KeyboardState.Closed && oneHandUI.value } } @@ -558,7 +574,7 @@ private fun DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Un } } ) { contentPadding -> - val listState = rememberLazyListState(lazyListState.first, lazyListState.second) + val listState = remember { appBarHandler.listState } val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } val searchShowingSimplexLink = remember { mutableStateOf(false) } val searchChatFilteredBySimplexLink = remember { mutableStateOf(null) } @@ -723,6 +739,6 @@ fun ActionButton( @Composable private fun PreviewNewChatSheet() { SimpleXTheme { - NewChatSheet(rh = null, close = {}) + ModalData().NewChatSheet(rh = null, close = {}) } } From 2089fd853981b3910fbad943c495473343a8dca4 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:36:41 +0000 Subject: [PATCH 088/440] android, desktop: remove slash from strings (#4818) --- .../src/commonMain/resources/MR/ar/strings.xml | 10 +++++----- .../src/commonMain/resources/MR/base/strings.xml | 6 +++--- .../src/commonMain/resources/MR/bg/strings.xml | 12 ++++++------ .../src/commonMain/resources/MR/cs/strings.xml | 6 +++--- .../src/commonMain/resources/MR/de/strings.xml | 8 ++++---- .../src/commonMain/resources/MR/es/strings.xml | 6 +++--- .../src/commonMain/resources/MR/fi/strings.xml | 6 +++--- .../src/commonMain/resources/MR/fr/strings.xml | 2 +- .../src/commonMain/resources/MR/it/strings.xml | 8 ++++---- .../src/commonMain/resources/MR/iw/strings.xml | 10 +++++----- .../src/commonMain/resources/MR/ko/strings.xml | 8 ++++---- .../src/commonMain/resources/MR/lt/strings.xml | 4 ++-- .../src/commonMain/resources/MR/ml/strings.xml | 2 +- .../src/commonMain/resources/MR/nl/strings.xml | 6 +++--- .../src/commonMain/resources/MR/pl/strings.xml | 6 +++--- .../src/commonMain/resources/MR/pt-rBR/strings.xml | 6 +++--- .../src/commonMain/resources/MR/pt/strings.xml | 2 +- .../src/commonMain/resources/MR/ru/strings.xml | 8 ++++---- .../src/commonMain/resources/MR/th/strings.xml | 6 +++--- .../src/commonMain/resources/MR/tr/strings.xml | 6 +++--- .../src/commonMain/resources/MR/uk/strings.xml | 4 ++-- .../src/commonMain/resources/MR/zh-rCN/strings.xml | 2 +- .../src/commonMain/resources/MR/zh-rTW/strings.xml | 4 ++-- 23 files changed, 69 insertions(+), 69 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 7fb812ade9..8f4b0158a7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -22,8 +22,8 @@ ينطبق هذا الإعداد على الرسائل الموجودة في ملف تعريف الدردشة الحالي الخاص بك منصة الرسائل والتطبيقات تحمي خصوصيتك وأمنك. يتم مشاركة ملف التعريف مع جهات اتصالك فقط. - سيتم تغيير الدور إلى \"%s\". سيتم إبلاغ كل فرد في المجموعة. - سيتم تغيير الدور إلى \"%s\". سيستلم العضو دعوة جديدة. + سيتم تغيير الدور إلى "%s". سيتم إبلاغ كل فرد في المجموعة. + سيتم تغيير الدور إلى "%s". سيستلم العضو دعوة جديدة. خوادم الاتصالات الجديدة لملف تعريف الدردشة الحالي الخاص بك سيتم تغيير عنوان الاستلام إلى خادم مختلف. سيتم إكمال تغيير العنوان بعد اتصال المرسل بالإنترنت. هذا الرابط ليس رابط اتصال صالح! @@ -767,7 +767,7 @@ لم تٌحدد جهات اتصال يمكّن للمشرف الآن: \n- حذف رسائل الأعضاء. -\n- تعطيل الأعضاء (دور \"المراقب\") +\n- تعطيل الأعضاء (دور "المراقب")
    خدمة الإشعار غير مفعّل` مفعل @@ -1076,7 +1076,7 @@ إيقاف الملف التوقف عن إرسال الملف؟ عنوان SimpleX - استخدم مضيفي .onion إلى \"لا\" إذا كان وكيل SOCKS لا يدعمها.]]> + استخدم مضيفي .onion
    إلى "لا" إذا كان وكيل SOCKS لا يدعمها.]]> مشاركة مع جهات الاتصال إيقاف التشغيل؟ إعدادات وكيل SOCKS @@ -1406,7 +1406,7 @@ سطح المكتب متصل اتصل تلقائيًا عنوان سطح المكتب - وضّع علامة \"محذوفة\" على %d من الرسائل + وضّع علامة "محذوفة" على %d من الرسائل اكتشف عبر الشبكة المحلية اتصل عبر الرابط؟ اتصل بنفسك؟ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index f5272ce7fc..1a3eb2c72f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1556,8 +1556,8 @@ Change Switch Change group role? - The role will be changed to \"%s\". Everyone in the group will be notified. - The role will be changed to \"%s\". The member will receive a new invitation. + The role will be changed to "%s". Everyone in the group will be notified. + The role will be changed to "%s". The member will receive a new invitation. Connect directly? Сonnection request will be sent to this group member. Error removing member @@ -1893,7 +1893,7 @@ Audio and video calls Support bluetooth and other improvements. Group moderation - Now admins can:\n- delete members\' messages.\n- disable members (\"observer\" role) + Now admins can:\n- delete members\' messages.\n- disable members ("observer" role) Group welcome message Set the message shown to new members! Further reduced battery usage diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index d43aee7c8a..54eb0e9034 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -482,7 +482,7 @@ Изпращането на потвърждениe за доставка ще бъде активирано за всички контакти във всички видими чат профили. Изпращане на потвърждениe за доставка Можете да активирате по-късно през Настройки - Можете да ги активирате по-късно през настройките за \"Поверителност и сигурност\" на приложението. + Можете да ги активирате по-късно през настройките за "Поверителност и сигурност" на приложението. Предупреждение: Може да загубите някои данни! Въведи правилна парола. активирано за вас @@ -517,7 +517,7 @@ криптирането е съгласувано за %s Редактирай групов профил Изчезва в: %s - Ролята ще бъде променена на \"%s\". Членът ще получи нова покана. + Ролята ще бъде променена на "%s". Членът ще получи нова покана. директна Предоговори криптирането Въведи име на групата: @@ -918,7 +918,7 @@ Членът ще бъде премахнат от групата - това не може да бъде отменено! няма текст Острани член - Ролята ще бъде променена на \"%s\". Всички в групата ще бъдат уведомени. + Ролята ще бъде променена на "%s". Всички в групата ще бъдат уведомени. Само данни за локален профил Профилни и сървърни връзки Без звук @@ -936,7 +936,7 @@ Защитете чат профилите с парола! Сега администраторите могат: \n- да изтриват съобщения на членове. -\n- да деактивират членове (роля \"наблюдател\") +\n- да деактивират членове (роля "наблюдател") Очаквайте скоро още подобрения! Полски интерфейс Реакции на съобщения @@ -1155,7 +1155,7 @@ Този линк не е валиден линк за връзка! Вашите чат профили Сървърите за нови връзки на текущия ви чат профил - За да покажете скрития профил, въведете пълната парола в полето за търсене на страницата \"Вашите чат профили\". + За да покажете скрития профил, въведете пълната парола в полето за търсене на страницата "Вашите чат профили". Профилът се споделя само с вашите контакти. Това действие не може да бъде отменено - всички получени и изпратени файлове и медия ще бъдат изтрити. Снимките с ниска разделителна способност ще бъдат запазени. Докосни за активиране на профил. @@ -1646,7 +1646,7 @@ Квантово устойчиво криптиране Миграция на данните от приложението Мигрирайте към друго устройство чрез QR код. - Обаждания \"картина в картина\" + Обаждания "картина в картина" По-безопасни групи Администраторите могат да блокират член за всички. Подробности за линка се изтеглят diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 591643a4da..0c4e78a269 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -462,7 +462,7 @@ odešel Vyčistit Přepnout - Role bude změněna na \"%s\". Všichni ve skupině budou informováni. + Role bude změněna na "%s". Všichni ve skupině budou informováni. Chyba při odebrání člena Chyba při ukládání profilu skupiny vteřiny @@ -857,7 +857,7 @@ Role Změnit roli Změnit - Role bude změněna na \"%s\". Člen obdrží novou pozvánku. + Role bude změněna na "%s". Člen obdrží novou pozvánku. Chyba při změně role přímo Odesíláno přes @@ -972,7 +972,7 @@ Další vylepšení již brzy! Nyní mohou správci: \n- mazat zprávy členů. -\n- zakázat členy (role \"pozorovatel\") +\n- zakázat členy (role "pozorovatel") Uložit heslo profilu Ztlumit Chraňte své chat profily heslem! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 64b7f9d8f7..897ea64be8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -792,8 +792,8 @@ Ändern Wechseln Die Mitgliederrolle ändern? - Die Mitgliederrolle wird auf \"%s\" geändert. Alle Mitglieder der Gruppe werden benachrichtigt. - Die Mitgliederrolle wird auf \"%s\" geändert. Das Mitglied wird eine neue Einladung erhalten. + Die Mitgliederrolle wird auf "%s" geändert. Alle Mitglieder der Gruppe werden benachrichtigt. + Die Mitgliederrolle wird auf "%s" geändert. Das Mitglied wird eine neue Einladung erhalten. Fehler beim Entfernen des Mitglieds Fehler beim Ändern der Rolle Gruppe @@ -1074,7 +1074,7 @@ Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind. Begrüßungsmeldung Sie können ein Benutzerprofil verbergen oder stummschalten – für das Menü gedrückt halten. - Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite \"Ihre Chat-Profile\" ein, um Ihr verborgenes Profil zu sehen. + Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite "Ihre Chat-Profile" ein, um Ihr verborgenes Profil zu sehen. Migrations-Bestätigung ungültig Aktualisieren und den Chat öffnen Datenbank-Aktualisierungen bestätigen @@ -1151,7 +1151,7 @@ SOCKS-Proxy nutzen SimpleX-Sperrmodus SimpleX-Sperre ist nicht aktiviert! - Verwende .onion-Hosts auf \"Nein\", wenn der SOCKS-Proxy sie nicht unterstützt.]]> + Verwende .onion-Hosts auf "Nein", wenn der SOCKS-Proxy sie nicht unterstützt.]]> Bestätigen System Sie können nicht überprüft werden – bitte versuchen Sie es nochmal. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index bc27b0a29f..9ba8c60f87 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -726,7 +726,7 @@ envío no autorizado Escribe un nombre para el contacto Error desconocido - El rol del miembro cambiará a \"%s\" y se notificará al grupo. + El rol del miembro cambiará a "%s" y se notificará al grupo. La seguridad de SimpleX Chat ha sido auditada por Trail of Bits. Los mensajes enviados se eliminarán una vez transcurrido el tiempo establecido. Mensajes de chat SimpleX @@ -814,7 +814,7 @@ Actualizar contraseña base de datos Pulsa para unirte en modo incógnito Cambiar - El rol del miembro cambiará a \"%s\" y recibirá una invitación nueva. + El rol del miembro cambiará a "%s" y recibirá una invitación nueva. Actualizar ¿Actualizar la configuración de red\? Intentando conectar con el servidor para recibir mensajes de este contacto. @@ -992,7 +992,7 @@ Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos. Ahora los administradores pueden: \n- eliminar mensajes de los miembros. -\n- desactivar el rol miembro (a rol \"observador\") +\n- desactivar el rol miembro (a rol "observador") Para hacer visible tu perfil oculto, introduce la contraseña completa en el campo de búsqueda del menú Mis perfiles. Actualización de la base de datos Volviendo a versión anterior de la base de datos diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index 8200d8a0ff..c3ea7d89f7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -757,7 +757,7 @@ Lisää parannuksia on tulossa pian! Nyt järjestelmänvalvojat voivat: \n- poistaa jäsenten viestit. -\n- poista jäsenet käytöstä (\"tarkkailija\" rooli) +\n- poista jäsenet käytöstä ("tarkkailija" rooli) Tallenna ja ilmoita ryhmän jäsenille Lopeta Pysäytä keskustelut viedäksesi, tuodaksesi tai poistaaksesi keskustelujen tietokannan. Et voi vastaanottaa ja lähettää viestejä, kun keskustelut on pysäytetty. @@ -1108,8 +1108,8 @@ Viestintä- ja sovellusalusta, joka suojaa yksityisyyttäsi ja tietoturvaasi. videopuhelu %1$s JÄSENET - Rooli muuttuu muotoon \"%s\". Kaikille ryhmän jäsenille ilmoitetaan asiasta. - Rooli muuttuu muotoon \"%s\". Jäsen saa uuden kutsun. + Rooli muuttuu muotoon "%s". Kaikille ryhmän jäsenille ilmoitetaan asiasta. + Rooli muuttuu muotoon "%s". Jäsen saa uuden kutsun. Ääniviestit ovat kiellettyjä tässä keskustelussa. Ääniviestit Yritetään muodostaa yhteys palvelimeen, jota käytetään viestien vastaanottamiseen tältä kontaktilta. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 02487f8ba7..553f70e31a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -972,7 +972,7 @@ Profils de chat cachés Désormais, les administrateurs peuvent : \n- supprimer les messages des membres. -\n- désactiver des membres (rôle \"observateur\") +\n- désactiver des membres (rôle "observateur") Enregistrer le message d\'accueil ? Choisissez un message à l\'attention des nouveaux membres ! Masquer le profil diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 34ee1721f0..1bc9f6283f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -822,8 +822,8 @@ Sistema Scadenza connessione TCP Completamente decentralizzato: visibile solo ai membri. - Il ruolo verrà cambiato in \"%s\". Tutti i membri del gruppo riceveranno una notifica. - Il ruolo verrà cambiato in \"%s\". Il membro riceverà un nuovo invito. + Il ruolo verrà cambiato in "%s". Tutti i membri del gruppo riceveranno una notifica. + Il ruolo verrà cambiato in "%s". Il membro riceverà un nuovo invito. Aggiorna Aggiornare le impostazioni di rete\? L\'aggiornamento delle impostazioni riconnetterà il client a tutti i server. @@ -978,11 +978,11 @@ Grazie agli utenti – contribuite via Weblate! Password del profilo nascosta Salva la password del profilo - Per rivelare il tuo profilo nascosto, inserisci una password completa in un campo di ricerca nella pagina \"I tuoi profili di chat\". + Per rivelare il tuo profilo nascosto, inserisci una password completa in un campo di ricerca nella pagina "I tuoi profili di chat". Password per mostrare Ora gli amministratori possono: \n- eliminare i messaggi dei membri. -\n- disattivare i membri (ruolo \"osservatore\") +\n- disattivare i membri (ruolo "osservatore") Nascondi il profilo Conferma password Errore nell\'aggiornamento della privacy dell\'utente diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index e4901dd875..d207057d0c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -1015,7 +1015,7 @@ הקישו כדי להצטרף תום זמן חיבור TCP האפליקציה בודקת הודעות חדשות מעת לעת - היא משתמשת בכמה אחוזים מהסוללה ביום. האפליקציה לא משתמשת בהתראות דחיפה - נתונים מהמכשיר שלך לא נשלחים לשרתים. - התפקיד ישתנה ל־\"%s\". כל חברי הקבוצה יקבלו הודעה על כך. + התפקיד ישתנה ל־"%s". כל חברי הקבוצה יקבלו הודעה על כך. כדי להתחבר באמצעות קישור פלטפורמת ההודעות והיישומים המגנה על הפרטיות והאבטחה שלך. המזהה של ההודעה הבאה שגוי (קטן או שווה להודעה הקודמת). @@ -1027,7 +1027,7 @@ תודה שהתקנתם את SimpleX Chat! קישור זה אינו קישור חיבור תקין! צבעי ערכת נושא - התפקיד ישתנה ל־\"%s\". החבר יקבל הזמנה חדשה. + התפקיד ישתנה ל־"%s". החבר יקבל הזמנה חדשה. השרתים לחיבורים חדשים של פרופיל הצ׳אט הנוכחי שלך הפלטפורמה הראשונה ללא כל מזהי משתמש - פרטית בעיצובה. הדור הבא של תקשורת פרטית @@ -1836,7 +1836,7 @@ מערכת שגיאה בהצגת התראה, צור קשר עם המפתחים שגיאה בהתחברות לשרת %1$s, אנא נסה מאוחר יותר - אין עדיין חיבור ישיר, ההודעה תעובר ע\"י מנהל. + אין עדיין חיבור ישיר, ההודעה תעובר ע"י מנהל. חבר לא פעיל שרתי XFTP אחרים הראה אחוזים @@ -1920,11 +1920,11 @@ קבצים אין מידע, נסה לרענן מידע על השרתים - התקבל סה\"כ + התקבל סה"כ התקבלו שגיאות התחבר מחדש שלח הודעות - נשלח בסה\"כ + נשלח בסה"כ שרת SMP שרת XFTP חלש diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index ffd62b4755..639a888bc9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -634,7 +634,7 @@ 메시지 이 설정은 현재 내 프로필의 메시지에 적용되어요. 멤버 - 역할이 \"%s\"(으)로 변경되고, 회원은 새로운 초대를 받게 될 거예요. + 역할이 "%s"(으)로 변경되고, 회원은 새로운 초대를 받게 될 거예요. 이 채팅에서는 메시지 영구 삭제가 허용되지 않았어요. 나가기 큰 파일! @@ -675,11 +675,11 @@ 그룹에서 나갈까요\? 데이터베이스 버전이 앱보다 최신이지만, 다음에 대한 다운 마이그레이션 없음: %s 멤버가 그룹에서 제거되어요. 이 작업은 되돌릴 수 없어요! - 역할이 \"%s\"(으)로 변경되어요. 그룹의 모든 멤버에게 알림이 전송됩니다. + 역할이 "%s"(으)로 변경되어요. 그룹의 모든 멤버에게 알림이 전송됩니다. 기본값으로 재설정 메시지 내용 대화 상대 이름 및 메시지 표시 - 나만 메시지를 영구 삭제할 수 있어요(대화 상대는 \"삭제됨\" 표시만 할 수 있음). + 나만 메시지를 영구 삭제할 수 있어요(대화 상대는 "삭제됨" 표시만 할 수 있음). 이 링크를 보낸 상대에게 프로필이 전송될 거예요. 파일 수신은 아직 지원되지 않아요. 올바른 링크를 사용했는지 확인하거나 상대에게 다른 링크를 보내달라고 말해 주세요 @@ -714,7 +714,7 @@ 대화 상대만 음성 메시지를 보낼 수 있어요. 메시지 영구 삭제 허용되지 않음. 음성 메시지 허용되지 않음. - 상대만 메시지를 영구 삭제할 수 있어요(나는 \"삭제됨\"으로 표시만 할 수 있음). + 상대만 메시지를 영구 삭제할 수 있어요(나는 "삭제됨"으로 표시만 할 수 있음). 그룹 소유자만 음성 메시지를 사용 가능하도록 설정할 수 있어요. 일회성 초대 링크 붙여넣기 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index 6139eb5133..8d8d73b206 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -600,7 +600,7 @@ Nepavyko pakviesti kontakto! Pagal pokalbių profilį (numatytieji nustatymai) arba pagal ryšį (BETA). Naudoja daugiau baterijos! Fono paslauga veikia visada - pranešimai rodomi, kai tik atsiranda žinučių.]]> - Negalima pasiekti \"Keystore\", kad išsaugotumėte duomenų bazės slaptažodį + Negalima pasiekti "Keystore", kad išsaugotumėte duomenų bazės slaptažodį Atšaukti failo peržiūrą Atšaukti vaizdo peržiūrą Duomenų bazės ID: %d @@ -1484,7 +1484,7 @@ Nėra pristatymo informacijos Dabar administratoriai gali: \n- ištrinti narių žinutes. -\n- išjungti narius (\"stebėtojas\" rolė) +\n- išjungti narius ("stebėtojas" rolė) įj. Nebemėgti Nustatymų atnaujinimas perjungs klientą iš naujo prie visų serverių. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml index d97cf4b7ad..dc692c1968 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml @@ -280,7 +280,7 @@ ആഴ്ചകൾ അനധികൃത അയക്കുക സ്വീകരിക്കുന്ന വിലാസം മാറണോ\? - കര്‍ത്തവ്യം \"%s\" ആയി മാറ്റും. അംഗത്തിന് പുതിയ ക്ഷണം ലഭിക്കും. + കര്‍ത്തവ്യം "%s" ആയി മാറ്റും. അംഗത്തിന് പുതിയ ക്ഷണം ലഭിക്കും. സംവിധാനം പ്രാമാണീകരണം അംഗങ്ങളെ ക്ഷണിക്കുന്നത് ഒഴിവാക്കുക സ്വാഗത സന്ദേശം സംരക്ഷിക്കണോ\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 97b6586844..9a8810de84 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -767,7 +767,7 @@ %1$s LEDEN U kunt een link of een QR-code delen. Iedereen kan lid worden van de groep. U verliest geen leden van de groep als u deze later verwijdert. Wijzig - De rol wordt gewijzigd in \"%s\". Iedereen in de groep wordt op de hoogte gebracht. + De rol wordt gewijzigd in "%s". Iedereen in de groep wordt op de hoogte gebracht. Ontvang via Groep profiel opslaan Volledig gedecentraliseerd – alleen zichtbaar voor leden. @@ -800,7 +800,7 @@ Lid verwijderen Rol Direct bericht sturen - De rol wordt gewijzigd in \"%s\". De gebruiker ontvangt een nieuwe uitnodiging. + De rol wordt gewijzigd in "%s". De gebruiker ontvangt een nieuwe uitnodiging. Verzenden via SERVERS Resetten naar standaardwaarden @@ -973,7 +973,7 @@ Profiel privé maken! Nu kunnen beheerders: \n- berichten van leden verwijderen. -\n- schakel leden uit (\"waarnemer\" rol) +\n- schakel leden uit ("waarnemer" rol) Bescherm je chat profielen met een wachtwoord! Wachtwoord om weer te geven Groep profiel opslaan en bijwerken diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index d0b0ca0d1c..62ff44f6db 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -739,7 +739,7 @@ Przełącz Zmień adres odbioru W pełni zdecentralizowana – widoczna tylko dla członków. - Rola zostanie zmieniona na \"%s\". Członek otrzyma nowe zaproszenie. + Rola zostanie zmieniona na "%s". Członek otrzyma nowe zaproszenie. Wiadomość powitalna Usunąć profil czatu\? Usuń profil czatu dla @@ -944,7 +944,7 @@ Nie udało się załadować czatu Teraz administratorzy mogą: \n- usuwać wiadomości członków. -\n- wyłączyć członków (rola \"obserwatora\") +\n- wyłączyć członków (rola "obserwatora") Z Galerii Obraz Wideo @@ -993,7 +993,7 @@ Niektóre serwery nie przeszły testu: Dziękujemy za zainstalowanie SimpleX Chat! Wiadomość zostanie oznaczona jako zmoderowana dla wszystkich członków. - Rola zostanie zmieniona na \"%s\". Wszyscy w grupie zostaną powiadomieni. + Rola zostanie zmieniona na "%s". Wszyscy w grupie zostaną powiadomieni. Tego działania nie można cofnąć - wszystkie odebrane i wysłane pliki oraz media zostaną usunięte. Obrazy o niskiej rozdzielczości pozostaną. Adres odbiorczy zostanie zmieniony na inny serwer. Zmiana adresu zostanie zakończona gdy nadawca będzie online. Ten link nie jest prawidłowym linkiem połączenia! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 2fa9f85599..3c639cf777 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -695,14 +695,14 @@ não Agora administradores podem: \n- excluir mensagens de membros. -\n- desativar membros (cargo de \"observador\") +\n- desativar membros (cargo de "observador") Moderação do grupo Mensagem de boas-vindas do grupo Desatualizar banco de dados migração diferente no aplicativo/banco de dados: %s / %s Convidar para o grupo Sem contatos para adicionar - O cargo será alterada para \"%s\". Todos no grupo serão notificados. + O cargo será alterada para "%s". Todos no grupo serão notificados. Mutar Somente você pode enviar mensagens de voz. Somente seu contato pode enviar mensagens de voz. @@ -813,7 +813,7 @@ EXPERIMENTAL você alterou o endereço Atualização do banco de dados - O cargo será alterado para \"%s\". O membro receberá um novo convite. + O cargo será alterado para "%s". O membro receberá um novo convite. Somente os proprietários do grupo podem alterar as preferências do grupo. Adicionar mensagem de boas-vindas Mensagem de boas-vindas diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index e652ab27e5..b38348b30f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -631,7 +631,7 @@ ligado Agora os administradores podem: \n- eliminar mensagens de membros. -\n- desativar membros (função de \"observador\") +\n- desativar membros (função de "observador") Ligação do grupo SimpleX Apenas 10 imagens podem ser enviadas ao mesmo tempo Apenas 10 vídeos podem ser enviados ao mesmo tempo diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 8c5adae3e3..2e9725913a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -796,8 +796,8 @@ Поменять Переключить Поменять роль в группе? - Роль будет изменена на \"%s\". Все в группе получат сообщение. - Роль будет изменена на \"%s\". Будет отправлено новое приглашение. + Роль будет изменена на "%s". Все в группе получат сообщение. + Роль будет изменена на "%s". Будет отправлено новое приглашение. Ошибка при удалении члена группы Ошибка при изменении роли Группа @@ -964,7 +964,7 @@ Исчезающие сообщения Отправленные сообщения будут удалены через заданное время. Улучшенная конфигурация серверов - \"Живые\" сообщения + "Живые" сообщения Получатели видят их в то время как Вы их набираете. Проверить безопасность соединения Сравните код безопасности с Вашими контактами. @@ -1065,7 +1065,7 @@ Дополнительные улучшения скоро! Теперь админы могут: \n- удалять сообщения членов. -\n- приостанавливать членов (роль \"наблюдатель\") +\n- приостанавливать членов (роль "наблюдатель") Защитите Ваши профили чата паролем! Раскрыть Поддержка bluetooth и другие улучшения. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index 058f0305a8..03bf9f0f27 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -742,7 +742,7 @@ การปรับปรุงเพิ่มเติมกำลังจะมาเร็ว ๆ นี้! ขณะนี้ผู้ดูแลระบบสามารถ: \n- ลบข้อความของสมาชิก -\n- ปิดการใช้งานสมาชิก (บทบาท \"ผู้สังเกตการณ์\") +\n- ปิดการใช้งานสมาชิก (บทบาท "ผู้สังเกตการณ์") ปฏิกิริยาต่อข้อความ นาที เดือน @@ -1020,7 +1020,7 @@ ขอบคุณผู้ใช้ – มีส่วนร่วมผ่าน Weblate! แพลตฟอร์มการส่งข้อความและแอปพลิเคชันที่ปกป้องความเป็นส่วนตัวและความปลอดภัยของคุณ การส่งข้อความส่วนตัวรุ่นต่อไป - บทบาทจะถูกเปลี่ยนเป็น \"%s\" ทุกคนในกลุ่มจะได้รับแจ้ง + บทบาทจะถูกเปลี่ยนเป็น "%s" ทุกคนในกลุ่มจะได้รับแจ้ง การดำเนินการนี้ไม่สามารถยกเลิกได้ ไฟล์และสื่อที่ได้รับและส่งทั้งหมดจะถูกลบ รูปภาพความละเอียดต่ำจะยังคงอยู่ หากต้องการเปิดเผยโปรไฟล์ที่ซ่อนอยู่ของคุณ ให้ป้อนรหัสผ่านแบบเต็มในช่องค้นหาในหน้าโปรไฟล์แชทของคุณ การแยกการขนส่ง @@ -1198,7 +1198,7 @@ ข้อความต้อนรับ คุณสามารถแชร์ลิงก์หรือคิวอาร์โค้ดได้ ทุกคนจะสามารถเข้าร่วมกลุ่มได้ คุณจะไม่สูญเสียสมาชิกของกลุ่มหากคุณลบในภายหลัง คุณสามารถแบ่งปันที่อยู่นี้กับผู้ติดต่อของคุณเพื่อให้พวกเขาเชื่อมต่อกับ %s - บทบาทจะถูกเปลี่ยนเป็น \"%s\" สมาชิกจะได้รับคำเชิญใหม่ + บทบาทจะถูกเปลี่ยนเป็น "%s" สมาชิกจะได้รับคำเชิญใหม่ ข้อความต้อนรับ โปรไฟล์การแชทของคุณจะถูกส่งไปยังสมาชิกในกลุ่ม อัปเดต diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 5413afa82b..864b5ae3df 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -138,14 +138,14 @@ Kaydet ve kişiyi bilgilendir Parolayı, Keystore\'a kaydet. Ses açık - Yetki, \"%s\" olarak değiştirelecek. Gruptaki herkes bilgilendirilecek. + Yetki, "%s" olarak değiştirelecek. Gruptaki herkes bilgilendirilecek. Sesli/görüntülü aramalar yasaktır. Kimlik doğrulama başarısız SimpleX Adresi Mesaj, tüm üyeler için silinecek. Gizliliğinizi ve güvenliğinizi koruyan mesajlaşma ve uygulama platformu. Gizlilik kipi - Yetki, \"%s\" olarak değiştirilecek. Üye, yeni bir davet alacak. + Yetki, "%s" olarak değiştirilecek. Üye, yeni bir davet alacak. Yetki Sesli mesajlara izin verilsin mi? Profil ekle @@ -360,7 +360,7 @@ %d saat Yeni yöneticiler artık: \n- üyelerin mesajlarını silebilir. -\n- üyeleri etkisizleştirebilir (\"gözlemci\" yetkisi verir) +\n- üyeleri etkisizleştirebilir ("gözlemci" yetkisi verir) Konuşmalar Grup bağlantısı oluştur Konuşma isteğini onaylarken hata oluştu diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index ee77905611..f6b3b12431 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -726,8 +726,8 @@ Зникає о: %s (поточне) Вилучити учасника - Роль буде змінено на \"%s\". Всі учасники групи будуть сповіщені. - Роль буде змінено на \"%s\". Учасник отримає нове запрошення. + Роль буде змінено на "%s". Всі учасники групи будуть сповіщені. + Роль буде змінено на "%s". Учасник отримає нове запрошення. Група Ласкаво просимо Профіль групи зберігається на пристроях учасників, а не на серверах. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 9e038995b3..469ce9b5b1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -1327,7 +1327,7 @@ 启用 该群组成员超过 %1$d ,未发送送达回执。 修复连接? - 我们错过的第二个\"√\"!✅ + 我们错过的第二个"√"!✅ 设定数据库密码 为群组禁用回执吗? %s、%s 和 %s 已连接 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 48efa52563..17cc45334e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -599,8 +599,8 @@ 建立連結 刪除連結? 移除成員 - 成員的身份會修改為 \"%s\"。所有在群組內的成員都接收到通知。 - 成員的身份會修改為 \"%s\"。該成員將接收到新的邀請。 + 成員的身份會修改為 "%s"。所有在群組內的成員都接收到通知。 + 成員的身份會修改為 "%s"。該成員將接收到新的邀請。 網路狀態 重置為預設值 當你與某人分享已啟用匿名聊天模式的個人檔案時,此個人檔案將用於他們邀請你參加的群組。 From 9cba96082d346d31c6e57713df928c918db97b4c Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:37:04 +0000 Subject: [PATCH 089/440] android, desktop: fix crash when window is small in chat view (#4819) --- .../kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt index 8921685cd6..2941b748c7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt @@ -356,7 +356,8 @@ sealed class WallpaperType { private fun drawToBitmap(image: ImageBitmap, imageScale: Float, tint: Color, size: Size, density: Float, layoutDirection: LayoutDirection): ImageBitmap { val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low val drawScope = CanvasDrawScope() - val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt()) + // Don't allow to make zero size because it crashes the app when reducing a size of a window on desktop + val bitmap = ImageBitmap(size.width.toInt().coerceAtLeast(1), size.height.toInt().coerceAtLeast(1)) val canvas = Canvas(bitmap) drawScope.draw( density = Density(density), From 6407d5de638a25bff6beb121038c7dfdc2394dd3 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:38:42 +0000 Subject: [PATCH 090/440] android, desktop: making list items unique (#4812) --- .../commonMain/kotlin/chat/simplex/common/model/ChatModel.kt | 4 +++- .../commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt | 1 + .../kotlin/chat/simplex/common/views/TerminalView.kt | 2 +- .../kotlin/chat/simplex/common/views/chat/ChatView.kt | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 5a1c46666d..618ad8f4b8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -411,7 +411,9 @@ object ChatModel { // remove from current chat if (chatId.value == cInfo.id) { chatItems.removeAll { - val remove = it.id == cItem.id + // We delete taking into account meta.createdAt to make sure we will not be in situation when two items with the same id will be deleted + // (it can happen if already deleted chat item in backend still in the list and new one came with the same (re-used) chat item id) + val remove = it.id == cItem.id && it.meta.createdAt == cItem.meta.createdAt if (remove) { AudioPlayer.stop(it) } remove } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 983390b09e..4d5caea16a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5316,6 +5316,7 @@ abstract class TerminalItem { val date: Instant = Clock.System.now() abstract val label: String abstract val details: String + val createdAtNanos: Long = System.nanoTime() class Cmd(override val id: Long, override val remoteHostId: Long?, val cmd: CC): TerminalItem() { override val label get() = "> ${cmd.cmdString}" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 1afbb0bdcb..5cb97d7d80 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -132,7 +132,7 @@ fun TerminalLog() { } val clipboard = LocalClipboardManager.current LazyColumnWithScrollBar(reverseLayout = true) { - items(reversedTerminalItems) { item -> + items(reversedTerminalItems, key = { item -> item.id to item.createdAtNanos }) { item -> val rhId = item.remoteHostId val rhIdStr = if (rhId == null) "" else "$rhId " Text( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index c8ad89609d..92c023c518 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -998,7 +998,7 @@ fun BoxWithConstraintsScope.ChatItemsList( } ) LazyColumnWithScrollBar(Modifier.align(Alignment.BottomCenter), state = listState, reverseLayout = true) { - itemsIndexed(reversedChatItems, key = { _, item -> item.id }) { i, cItem -> + itemsIndexed(reversedChatItems, key = { _, item -> item.id to item.meta.createdAt.toEpochMilliseconds() }) { i, cItem -> CompositionLocalProvider( // Makes horizontal and vertical scrolling to coexist nicely. // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view From 71bea947a5ce2a194ceda050742901712ed26e61 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 4 Sep 2024 14:49:01 +0100 Subject: [PATCH 091/440] ios: cache base64 images (#4827) --- apps/ios/Shared/Theme/Theme.swift | 2 +- .../Views/Chat/ChatItem/CILinkView.swift | 2 +- .../Views/Chat/ChatItem/FramedItemView.swift | 4 ++-- apps/ios/Shared/Views/Chat/ChatItemView.swift | 2 +- .../ComposeMessage/ComposeImageView.swift | 2 +- .../Chat/ComposeMessage/ComposeLinkView.swift | 2 +- .../Views/ChatList/ChatPreviewView.swift | 6 +++--- .../Shared/Views/Helpers/ProfileImage.swift | 2 +- apps/ios/SimpleX SE/ShareModel.swift | 2 +- apps/ios/SimpleX SE/ShareView.swift | 6 +++--- apps/ios/SimpleXChat/ImageUtils.swift | 20 ++++++++++++++++--- 11 files changed, 32 insertions(+), 18 deletions(-) diff --git a/apps/ios/Shared/Theme/Theme.swift b/apps/ios/Shared/Theme/Theme.swift index e2641eb8dd..53f2931d16 100644 --- a/apps/ios/Shared/Theme/Theme.swift +++ b/apps/ios/Shared/Theme/Theme.swift @@ -102,7 +102,7 @@ extension ThemeWallpaper { public func importFromString() -> ThemeWallpaper { if preset == nil, let image { // Need to save image from string and to save its path - if let parsed = UIImage(base64Encoded: image), + if let parsed = imageFromBase64(image), let filename = saveWallpaperFile(image: parsed) { var copy = self copy.image = nil diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 3c864ab172..692e6bb8a6 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -16,7 +16,7 @@ struct CILinkView: View { var body: some View { VStack(alignment: .center, spacing: 6) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .scaledToFit() diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 260ac64e43..d657d79a4f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -185,7 +185,7 @@ struct FramedItemView: View { let v = ZStack(alignment: .topTrailing) { switch (qi.content) { case let .image(_, image): - if let uiImage = UIImage(base64Encoded: image) { + if let uiImage = imageFromBase64(image) { ciQuotedMsgView(qi) .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading) Image(uiImage: uiImage) @@ -197,7 +197,7 @@ struct FramedItemView: View { ciQuotedMsgView(qi) } case let .video(_, image, _): - if let uiImage = UIImage(base64Encoded: image) { + if let uiImage = imageFromBase64(image) { ciQuotedMsgView(qi) .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading) Image(uiImage: uiImage) diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index d444ce0735..bf09d15ff1 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -72,7 +72,7 @@ struct ChatItemView: View { default: nil } } - .flatMap { UIImage(base64Encoded: $0) } + .flatMap { imageFromBase64($0) } let adjustedMaxWidth = { if let preview, preview.size.width <= preview.size.height { maxWidth * 0.75 diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift index df3a8caf55..14026d79d1 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift @@ -18,7 +18,7 @@ struct ComposeImageView: View { var body: some View { HStack(alignment: .center, spacing: 8) { let imgs: [UIImage] = images.compactMap { image in - UIImage(base64Encoded: image) + imageFromBase64(image) } if imgs.count == 0 { ProgressView() diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift index f7f1a89299..6c44aeea83 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift @@ -40,7 +40,7 @@ struct ComposeLinkView: View { private func linkPreviewView(_ linkPreview: LinkPreview) -> some View { HStack(alignment: .center, spacing: 8) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fit) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 43892ec469..cf9977860d 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -302,7 +302,7 @@ struct ChatPreviewView: View { case let .link(_, preview): smallContentPreview(size: dynamicMediaSize) { ZStack(alignment: .topTrailing) { - Image(uiImage: UIImage(base64Encoded: preview.image) ?? UIImage(systemName: "arrow.up.right")!) + Image(uiImage: imageFromBase64(preview.image) ?? UIImage(systemName: "arrow.up.right")!) .resizable() .aspectRatio(contentMode: .fill) .frame(width: dynamicMediaSize, height: dynamicMediaSize) @@ -323,12 +323,12 @@ struct ChatPreviewView: View { } case let .image(_, image): smallContentPreview(size: dynamicMediaSize) { - CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) + CIImageView(chatItem: ci, preview: imageFromBase64(image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) .environmentObject(ReverseListScrollModel()) } case let .video(_,image, duration): smallContentPreview(size: dynamicMediaSize) { - CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) + CIVideoView(chatItem: ci, preview: imageFromBase64(image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) .environmentObject(ReverseListScrollModel()) } case let .voice(_, duration): diff --git a/apps/ios/Shared/Views/Helpers/ProfileImage.swift b/apps/ios/Shared/Views/Helpers/ProfileImage.swift index 248504c59b..3eedd56441 100644 --- a/apps/ios/Shared/Views/Helpers/ProfileImage.swift +++ b/apps/ios/Shared/Views/Helpers/ProfileImage.swift @@ -20,7 +20,7 @@ struct ProfileImage: View { @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner var body: some View { - if let uiImage = UIImage(base64Encoded: imageStr) { + if let uiImage = imageFromBase64(imageStr) { clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius, blurred: blurred) } else { let c = color.asAnotherColorFromSecondaryVariant(theme) diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index f43548f676..e73aeee13c 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -104,7 +104,7 @@ class ShareModel: ObservableObject { // Decode base64 images on background thread let profileImages = chats.reduce(into: Dictionary()) { dict, chatData in if let profileImage = chatData.chatInfo.image, - let uiImage = UIImage(base64Encoded: profileImage) { + let uiImage = imageFromBase64(profileImage) { dict[chatData.id] = uiImage } } diff --git a/apps/ios/SimpleX SE/ShareView.swift b/apps/ios/SimpleX SE/ShareView.swift index 1f502ffcff..f2b9de9f72 100644 --- a/apps/ios/SimpleX SE/ShareView.swift +++ b/apps/ios/SimpleX SE/ShareView.swift @@ -147,8 +147,8 @@ struct ShareView: View { } } - @ViewBuilder private func imagePreview(_ img: String) -> some View { - if let img = UIImage(base64Encoded: img) { + @ViewBuilder private func imagePreview(_ imgStr: String) -> some View { + if let img = imageFromBase64(imgStr) { previewArea { Image(uiImage: img) .resizable() @@ -163,7 +163,7 @@ struct ShareView: View { @ViewBuilder private func linkPreview(_ linkPreview: LinkPreview) -> some View { previewArea { HStack(alignment: .center, spacing: 8) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fit) diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift index 67218a781e..2eb747c6a5 100644 --- a/apps/ios/SimpleXChat/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -383,16 +383,30 @@ extension UIImage { } return self } +} - public convenience init?(base64Encoded: String?) { - if let base64Encoded, let data = Data(base64Encoded: dropImagePrefix(base64Encoded)) { - self.init(data: data) +public func imageFromBase64(_ base64Encoded: String?) -> UIImage? { + if let base64Encoded { + if let img = imageCache.object(forKey: base64Encoded as NSString) { + return img + } else if let data = Data(base64Encoded: dropImagePrefix(base64Encoded)), + let img = UIImage(data: data) { + imageCache.setObject(img, forKey: base64Encoded as NSString) + return img } else { return nil } + } else { + return nil } } +private var imageCache: NSCache = { + var cache = NSCache() + cache.countLimit = 1000 + return cache +}() + public func getLinkPreview(url: URL, cb: @escaping (LinkPreview?) -> Void) { logger.debug("getLinkMetadata: fetching URL preview") LPMetadataProvider().startFetchingMetadata(for: url){ metadata, error in From e002f33c53e2c99fc165aee8ecbd70397f7c918c Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 4 Sep 2024 18:12:41 +0100 Subject: [PATCH 092/440] multiplatform: fix deleted contacts with conversations being identified as contact cards (#4828) * multiplatform: fix deleted contacts with conversations being identified as contact cards * fix in ios * Revert "fix in ios" This reverts commit 9b8c6bc125fae22a5bf8b8729dcd4a5c0f3b1776. * fix for ios * same check as ios for android and desktop --- apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift | 2 +- .../chat/simplex/common/views/chatlist/ChatListNavLinkView.kt | 2 +- .../kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index bcca763a75..764c2b47b3 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -199,7 +199,7 @@ func chatContactType(chat: Chat) -> ContactType { case .contactRequest: return .request case let .direct(contact): - if contact.activeConn == nil && contact.profile.contactLink != nil { + if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { return .card } else if contact.chatDeleted { return .chatDeleted diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index e39adcca3b..3c7f1e781f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -186,7 +186,7 @@ fun ErrorChatListItem() { suspend fun directChatAction(rhId: Long?, contact: Contact, chatModel: ChatModel) { when { - contact.activeConn == null && contact.profile.contactLink != null -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true) + contact.activeConn == null && contact.profile.contactLink != null && contact.active -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true) else -> openChat(rhId, ChatInfo.Direct(contact), chatModel) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 7a9fec100d..2a6c8838e4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -99,7 +99,7 @@ fun chatContactType(chat: Chat): ContactType { val contact = cInfo.contact when { - contact.activeConn == null && contact.profile.contactLink != null -> ContactType.CARD + contact.activeConn == null && contact.profile.contactLink != null && contact.active -> ContactType.CARD contact.chatDeleted -> ContactType.CHAT_DELETED contact.contactStatus == ContactStatus.Active -> ContactType.RECENT else -> ContactType.UNLISTED From ebb5d629e70d7d91d2e1a0f7b86075a294a40d56 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 4 Sep 2024 23:12:05 +0100 Subject: [PATCH 093/440] ios: attach share sheet to the topmost view controller (to support sharing from nested sheets) (#4830) --- apps/ios/Shared/Views/Helpers/ShareSheet.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 936c6cb3ab..88e4bffe9f 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -11,12 +11,18 @@ import SwiftUI func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { let keyWindowScene = UIApplication.shared.connectedScenes.first { $0.activationState == .foregroundActive } as? UIWindowScene if let keyWindow = keyWindowScene?.windows.filter(\.isKeyWindow).first, - let presentedViewController = keyWindow.rootViewController?.presentedViewController ?? keyWindow.rootViewController { + let rootViewController = keyWindow.rootViewController { + // Find the top-most presented view controller + var topController = rootViewController + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil) if let completed = completed { - let handler: UIActivityViewController.CompletionWithItemsHandler = { _,_,_,_ in completed() } - activityViewController.completionWithItemsHandler = handler - } - presentedViewController.present(activityViewController, animated: true) + activityViewController.completionWithItemsHandler = { _, _, _, _ in + completed() + } + } + topController.present(activityViewController, animated: true) } } From 1f3355921c1c186b11fc0032b57faab1ec886bc7 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Fri, 6 Sep 2024 14:36:54 +0300 Subject: [PATCH 094/440] ios: make message information on media readable (#4820) * ios: ensure legibility of elements rendered over media * reduce diff * match meta padding * material play background * remove circlebadge * progress circle * meta color modes * refactor * conditional space * fix * fix2 * fix3 * revert video buttons --------- Co-authored-by: Evgeny Poberezkin --- .../Chat/ChatItem/CIGroupInvitationView.swift | 4 +- .../Views/Chat/ChatItem/CIImageView.swift | 2 +- .../Views/Chat/ChatItem/CIMetaView.swift | 124 ++++++++++++++---- .../Chat/ChatItem/CIRcvDecryptionError.swift | 4 +- .../Views/Chat/ChatItem/CIVideoView.swift | 42 +++--- .../Views/Chat/ChatItem/FramedItemView.swift | 17 ++- .../Views/Chat/ChatItem/MsgContentView.swift | 2 +- .../Helpers/InvertedForegroundStyle.swift | 21 +++ apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + apps/ios/SimpleXChat/ChatTypes.swift | 17 +-- 10 files changed, 161 insertions(+), 76 deletions(-) create mode 100644 apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index da859c1606..1a77b36d6f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -46,7 +46,7 @@ struct CIGroupInvitationView: View { .foregroundColor(inProgress ? theme.colors.secondary : chatIncognito ? .indigo : theme.colors.primary) .font(.callout) + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } @@ -54,7 +54,7 @@ struct CIGroupInvitationView: View { ( groupInvitationText() + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index 3966d7e258..b06c6df48c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -165,9 +165,9 @@ struct CIImageView: View { private func fileIcon(_ icon: String, _ size: CGFloat, _ padding: CGFloat) -> some View { Image(systemName: icon) .resizable() + .invertedForegroundStyle() .aspectRatio(contentMode: .fit) .frame(width: size, height: size) - .foregroundColor(.white) .padding(padding) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 719c1cabd0..9840b22fc8 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -18,6 +18,7 @@ struct CIMetaView: View { var paleMetaColor = Color(UIColor.tertiaryLabel) var showStatus = true var showEdited = true + var invertedMaterial = false @AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false @@ -25,18 +26,59 @@ struct CIMetaView: View { if chatItem.isDeletedContent { chatItem.timestampText.font(.caption).foregroundColor(metaColor) } else { - ciMetaText( - chatItem.meta, - chatTTL: chat.chatInfo.timedMessagesTTL, - encrypted: chatItem.encryptedFile, - color: chatItem.meta.itemStatus.sndProgress == .partial - ? paleMetaColor - : metaColor, - showStatus: showStatus, - showEdited: showEdited, - showViaProxy: showSentViaProxy, - showTimesamp: showTimestamp - ) + ZStack { + ciMetaText( + chatItem.meta, + chatTTL: chat.chatInfo.timedMessagesTTL, + encrypted: chatItem.encryptedFile, + color: metaColor, + paleColor: paleMetaColor, + colorMode: invertedMaterial + ? .invertedMaterial + : .normal, + showStatus: showStatus, + showEdited: showEdited, + showViaProxy: showSentViaProxy, + showTimesamp: showTimestamp + ).invertedForegroundStyle(enabled: invertedMaterial) + if invertedMaterial { + ciMetaText( + chatItem.meta, + chatTTL: chat.chatInfo.timedMessagesTTL, + encrypted: chatItem.encryptedFile, + colorMode: .normal, + onlyOverrides: true, + showStatus: showStatus, + showEdited: showEdited, + showViaProxy: showSentViaProxy, + showTimesamp: showTimestamp + ) + } + } + } + } +} + +enum MetaColorMode { + // Renders provided colours + case normal + // Fully transparent meta - used for reserving space + case transparent + // Renders white on dark backgrounds and black on light ones + case invertedMaterial + + func resolve(_ c: Color?) -> Color? { + switch self { + case .normal: c + case .transparent: .clear + case .invertedMaterial: nil + } + } + + var statusSpacer: Text { + switch self { + case .normal, .transparent: Text(Image(systemName: "circlebadge.fill")).foregroundColor(.clear) + case .invertedMaterial: Text(" ").kerning(13) } } } @@ -45,47 +87,77 @@ func ciMetaText( _ meta: CIMeta, chatTTL: Int?, encrypted: Bool?, - color: Color = .clear, + color: Color = .clear, // we use this function to reserve space without rendering meta + paleColor: Color? = nil, primaryColor: Color = .accentColor, - transparent: Bool = false, + colorMode: MetaColorMode = .normal, + onlyOverrides: Bool = false, // only render colors that differ from base showStatus: Bool = true, showEdited: Bool = true, showViaProxy: Bool, showTimesamp: Bool ) -> Text { var r = Text("") + var space: Text? = nil + let appendSpace = { + if let sp = space { + r = r + sp + space = nil + } + } + let resolved = colorMode.resolve(color) if showEdited, meta.itemEdited { - r = r + statusIconText("pencil", color) + r = r + statusIconText("pencil", resolved) } if meta.disappearing { - r = r + statusIconText("timer", color).font(.caption2) + r = r + statusIconText("timer", resolved).font(.caption2) let ttl = meta.itemTimed?.ttl if ttl != chatTTL { - r = r + Text(shortTimeText(ttl)).foregroundColor(color) + r = r + colored(Text(shortTimeText(ttl)), resolved) } - r = r + Text(" ") + space = Text(" ") } if showViaProxy, meta.sentViaProxy == true { - r = r + statusIconText("arrow.forward", color.opacity(0.67)).font(.caption2) + appendSpace() + r = r + statusIconText("arrow.forward", resolved?.opacity(0.67)).font(.caption2) } if showStatus { - if let (image, statusColor) = meta.itemStatus.statusIcon(color, primaryColor) { - r = r + Text(image).foregroundColor(transparent ? .clear : statusColor) + Text(" ") + appendSpace() + if let (image, statusColor) = meta.itemStatus.statusIcon(color, paleColor ?? color, primaryColor) { + let metaColor = if onlyOverrides && statusColor == color { + Color.clear + } else { + colorMode.resolve(statusColor) + } + r = r + colored(Text(image), metaColor) + space = Text(" ") } else if !meta.disappearing { - r = r + statusIconText("circlebadge.fill", .clear) + Text(" ") + space = colorMode.statusSpacer + Text(" ") } } if let enc = encrypted { - r = r + statusIconText(enc ? "lock" : "lock.open", color) + Text(" ") + appendSpace() + r = r + statusIconText(enc ? "lock" : "lock.open", resolved) + space = Text(" ") } if showTimesamp { - r = r + meta.timestampText.foregroundColor(color) + appendSpace() + r = r + colored(meta.timestampText, resolved) } return r.font(.caption) } -private func statusIconText(_ icon: String, _ color: Color) -> Text { - Text(Image(systemName: icon)).foregroundColor(color) +private func statusIconText(_ icon: String, _ color: Color?) -> Text { + colored(Text(Image(systemName: icon)), color) +} + +// Applying `foregroundColor(nil)` breaks `.invertedForegroundStyle` modifier +private func colored(_ t: Text, _ color: Color?) -> Text { + if let color { + t.foregroundColor(color) + } else { + t + } } struct CIMetaView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 9f721f83b7..c76ffe8c05 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -126,7 +126,7 @@ struct CIRcvDecryptionError: View { .foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary) .font(.callout) + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) } .padding(.horizontal, 12) @@ -145,7 +145,7 @@ struct CIRcvDecryptionError: View { .foregroundColor(.red) .italic() + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } .padding(.horizontal, 12) CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index 4670fc685f..851b90bc3d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -292,30 +292,22 @@ struct CIVideoView: View { .clipShape(Circle()) } - private func durationProgress() -> some View { - HStack { - Text("\(durationText(videoPlaying ? progress : duration))") - .foregroundColor(.white) - .font(.caption) - .padding(.vertical, 3) - .padding(.horizontal, 6) - .background(Color.black.opacity(0.35)) - .cornerRadius(10) - .padding([.top, .leading], 6) - - if let file = chatItem.file, !videoPlaying { - Text("\(ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary))") - .foregroundColor(.white) - .font(.caption) - .padding(.vertical, 3) - .padding(.horizontal, 6) - .background(Color.black.opacity(0.35)) - .cornerRadius(10) - .padding(.top, 6) - } + private var fileSizeString: String { + if let file = chatItem.file, !videoPlaying { + " " + ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) + } else { + "" } } + private func durationProgress() -> some View { + Text((durationText(videoPlaying ? progress : duration)) + fileSizeString) + .invertedForegroundStyle() + .font(.caption) + .padding(.vertical, 6) + .padding(.horizontal, 12) + } + private func imageView(_ img: UIImage) -> some View { let w = img.size.width <= img.size.height ? maxWidth * 0.75 : maxWidth return ZStack(alignment: .topTrailing) { @@ -411,9 +403,9 @@ struct CIVideoView: View { private func fileIcon(_ icon: String, _ size: CGFloat, _ padding: CGFloat) -> some View { Image(systemName: icon) .resizable() + .invertedForegroundStyle() .aspectRatio(contentMode: .fit) .frame(width: size, height: size) - .foregroundColor(.white) .padding(smallView ? 0 : padding) } @@ -428,10 +420,8 @@ struct CIVideoView: View { private func progressCircle(_ progress: Int64, _ total: Int64) -> some View { Circle() .trim(from: 0, to: Double(progress) / Double(total)) - .stroke( - Color(uiColor: .white), - style: StrokeStyle(lineWidth: 2) - ) + .stroke(style: StrokeStyle(lineWidth: 2)) + .invertedForegroundStyle() .rotationEffect(.degrees(-90)) .frame(width: 16, height: 16) .padding([.trailing, .top], smallView ? 0 : 11) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index d657d79a4f..5f2930951f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -64,12 +64,17 @@ struct FramedItemView: View { .overlay(DetermineWidth()) } - if chatItem.content.msgContent != nil { - CIMetaView(chat: chat, chatItem: chatItem, metaColor: useWhiteMetaColor ? Color.white : theme.colors.secondary) - .padding(.horizontal, 12) - .padding(.bottom, 6) - .overlay(DetermineWidth()) - .accessibilityLabel("") + if let content = chatItem.content.msgContent { + CIMetaView( + chat: chat, + chatItem: chatItem, + metaColor: theme.colors.secondary, + invertedMaterial: useWhiteMetaColor + ) + .padding(.horizontal, 12) + .padding(.bottom, 6) + .overlay(DetermineWidth()) + .accessibilityLabel("") } } .background { chatItemFrameColorMaybeImageOrVideo(chatItem, theme).modifier(ChatTailPadding()) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index 9cc4179723..63d5dc30dc 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -85,7 +85,7 @@ struct MsgContentView: View { } private func reserveSpaceForMeta(_ mt: CIMeta) -> Text { - (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } diff --git a/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift b/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift new file mode 100644 index 0000000000..dca413dafe --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift @@ -0,0 +1,21 @@ +// +// Test.swift +// SimpleX (iOS) +// +// Created by Levitating Pineapple on 31/08/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +extension View { + @ViewBuilder + func invertedForegroundStyle(enabled: Bool = true) -> some View { + if enabled { + foregroundStyle(Material.ultraThin) + .environment(\.colorScheme, .dark) + .grayscale(1) + .contrast(-20) + } else { self } + } +} diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3700fbbf16..b4226d567b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -194,6 +194,7 @@ 8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; }; 8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */; }; B76E6C312C5C41D900EC11AA /* ContactListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */; }; + CE176F202C87014C00145DBC /* InvertedForegroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */; }; CE1EB0E42C459A660099D896 /* ShareAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1EB0E32C459A660099D896 /* ShareAPI.swift */; }; CE2AD9CE2C452A4D00E844E3 /* ChatUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */; }; CE3097FB2C4C0C9F00180898 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */; }; @@ -532,6 +533,7 @@ 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = ""; }; 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableChatItemToolbars.swift; sourceTree = ""; }; B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListNavLink.swift; sourceTree = ""; }; + CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvertedForegroundStyle.swift; sourceTree = ""; }; CE1EB0E32C459A660099D896 /* ShareAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAPI.swift; sourceTree = ""; }; CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatUtils.swift; sourceTree = ""; }; CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; @@ -789,6 +791,7 @@ 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */, CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */, CE7548092C622630009579B7 /* SwipeLabel.swift */, + CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */, ); path = Helpers; sourceTree = ""; @@ -1489,6 +1492,7 @@ 5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */, 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */, 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */, + CE176F202C87014C00145DBC /* InvertedForegroundStyle.swift in Sources */, 5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */, 5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */, 5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */, diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 105edd725f..c60e6e8f64 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2790,13 +2790,14 @@ public enum CIStatus: Decodable, Hashable { } } - public func statusIcon(_ metaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { + public func statusIcon(_ metaColor: Color, _ paleMetaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { switch self { case .sndNew: nil - case .sndSent: (Image("checkmark.wide"), metaColor) - case let .sndRcvd(msgRcptStatus, _): + case let .sndSent(sndProgress): + (Image("checkmark.wide"), sndProgress == .partial ? paleMetaColor : metaColor) + case let .sndRcvd(msgRcptStatus, sndProgress): switch msgRcptStatus { - case .ok: (Image("checkmark.2"), metaColor) + case .ok: (Image("checkmark.2"), sndProgress == .partial ? paleMetaColor : metaColor) case .badMsgHash: (Image("checkmark.2"), .red) } case .sndErrorAuth: (Image(systemName: "multiply"), .red) @@ -2808,14 +2809,6 @@ public enum CIStatus: Decodable, Hashable { } } - public var sndProgress: SndCIStatusProgress? { - switch self { - case let .sndSent(sndProgress): sndProgress - case let .sndRcvd(_ , sndProgress): sndProgress - default: nil - } - } - public var statusInfo: (String, String)? { switch self { case .sndNew: return nil From bccbb9900ff01f67d353f47d4d060188d0d067ad Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:39:23 +0000 Subject: [PATCH 095/440] android: fix initializing WorkManager (#4833) --- .../chat/simplex/app/MessagesFetcherWorker.kt | 7 +++---- .../src/main/java/chat/simplex/app/SimplexApp.kt | 10 +++++++--- .../main/java/chat/simplex/app/SimplexService.kt | 2 +- .../simplex/common/platform/AppCommon.android.kt | 15 +++++++++++++++ .../views/usersettings/SettingsView.android.kt | 3 +-- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt index 64b6639a58..b18204d905 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt @@ -5,9 +5,8 @@ import android.util.Log import androidx.work.* import chat.simplex.app.SimplexService.Companion.showPassphraseNotification import chat.simplex.common.model.ChatController +import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.DBMigrationResult -import chat.simplex.common.platform.chatModel -import chat.simplex.common.platform.initChatControllerOnStart import chat.simplex.common.views.helpers.DatabaseUtils import kotlinx.coroutines.* import java.util.Date @@ -30,12 +29,12 @@ object MessagesFetcherWorker { .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) .build() - WorkManager.getInstance(SimplexApp.context).enqueueUniqueWork(UNIQUE_WORK_TAG, ExistingWorkPolicy.REPLACE, periodicWorkRequest) + SimplexApp.context.getWorkManagerInstance().enqueueUniqueWork(UNIQUE_WORK_TAG, ExistingWorkPolicy.REPLACE, periodicWorkRequest) } fun cancelAll() { Log.d(TAG, "Worker: canceled all tasks") - WorkManager.getInstance(SimplexApp.context).cancelUniqueWork(UNIQUE_WORK_TAG) + SimplexApp.context.getWorkManagerInstance().cancelUniqueWork(UNIQUE_WORK_TAG) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 9206b5be89..d10f16341a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -7,7 +7,6 @@ import chat.simplex.common.platform.Log import android.content.Intent import android.content.pm.ActivityInfo import android.os.* -import android.view.View import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.graphics.Color @@ -37,7 +36,7 @@ import java.util.concurrent.TimeUnit const val TAG = "SIMPLEX" -class SimplexApp: Application(), LifecycleEventObserver { +class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider { val chatModel: ChatModel get() = chatController.chatModel @@ -161,7 +160,7 @@ class SimplexApp: Application(), LifecycleEventObserver { .addTag(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC) .build() Log.d(TAG, "ServiceStartWorker: Scheduling period work every ${SimplexService.SERVICE_START_WORKER_INTERVAL_MINUTES} minutes") - WorkManager.getInstance(context)?.enqueueUniquePeriodicWork(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC, workPolicy, work) + getWorkManagerInstance().enqueueUniquePeriodicWork(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC, workPolicy, work) } fun schedulePeriodicWakeUp() = CoroutineScope(Dispatchers.Default).launch { @@ -364,4 +363,9 @@ class SimplexApp: Application(), LifecycleEventObserver { } } } + + // Fix for an exception: + // WorkManager is not initialized properly. You have explicitly disabled WorkManagerInitializer in your manifest, have not manually called WorkManager#initialize at this point, and your Application does not implement Configuration.Provider. + override val workManagerConfiguration: Configuration + get() = Configuration.Builder().build() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index a5f5d84ec2..7fc1bd151c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -272,7 +272,7 @@ class SimplexService: Service() { fun scheduleStart(context: Context) { Log.d(TAG, "Enqueuing work to start subscriber service") - val workManager = WorkManager.getInstance(context) + val workManager = context.getWorkManagerInstance() val startServiceRequest = OneTimeWorkRequest.Builder(ServiceStartWorker::class.java).build() workManager.enqueueUniqueWork(WORK_NAME_ONCE, ExistingWorkPolicy.KEEP, startServiceRequest) // Unique avoids races! } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index d739a033f9..8cd51e8298 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -6,6 +6,8 @@ import android.net.LocalServerSocket import android.util.Log import androidx.activity.ComponentActivity import androidx.fragment.app.FragmentActivity +import androidx.work.Configuration +import androidx.work.WorkManager import java.io.* import java.lang.ref.WeakReference import java.util.* @@ -72,3 +74,16 @@ fun initHaskell() { initHS() } + +fun Context.getWorkManagerInstance(): WorkManager { + // https://github.com/OneSignal/OneSignal-Android-SDK/pull/2052/files + // https://github.com/OneSignal/OneSignal-Android-SDK/issues/1672 + if (!WorkManager.isInitialized()) { + try { + WorkManager.initialize(this, Configuration.Builder().build()) + } catch (e: IllegalStateException) { + Log.e(TAG, "Error initializing WorkManager: ${e.stackTraceToString()}") + } + } + return WorkManager.getInstance(this) +} diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt index 38818a123d..96b4a43e1a 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt @@ -2,7 +2,6 @@ package chat.simplex.common.views.usersettings import SectionView import androidx.compose.runtime.Composable -import androidx.work.WorkManager import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.* @@ -33,7 +32,7 @@ fun restartApp() { } private fun shutdownApp() { - WorkManager.getInstance(androidAppContext).cancelAllWork() + androidAppContext.getWorkManagerInstance().cancelAllWork() platform.androidServiceSafeStop() Runtime.getRuntime().exit(0) } From 06939343a1a55c850fc992c9764584f83fff10fc Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Fri, 6 Sep 2024 15:32:41 +0300 Subject: [PATCH 096/440] ios: revert showing date in chat list timestamp (#4834) --- apps/ios/Shared/Views/Chat/ChatView.swift | 2 +- apps/ios/SimpleXChat/ChatTypes.swift | 26 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index a5ad7ce456..f3d1cc92a5 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -704,7 +704,7 @@ struct ChatView: View { let nextItem = im.reversedChatItems[i - 1] let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.itemTs.timeIntervalSince(chatItem.meta.itemTs) > 60 return ( - timestamp: largeGap || formatTimestampText(chatItem.meta.itemTs) != formatTimestampText(nextItem.meta.itemTs), + timestamp: largeGap || formatTimestampMeta(chatItem.meta.itemTs) != formatTimestampMeta(nextItem.meta.itemTs), largeGap: largeGap, date: Calendar.current.isDate(chatItem.meta.itemTs, inSameDayAs: nextItem.meta.itemTs) ? nil : nextItem.meta.itemTs ) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index c60e6e8f64..84bf445601 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2713,7 +2713,7 @@ public struct CIMeta: Decodable, Hashable { public var deletable: Bool public var editable: Bool - public var timestampText: Text { get { formatTimestampText(itemTs) } } + public var timestampText: Text { Text(formatTimestampMeta(itemTs)) } public var recent: Bool { updatedAt + 10 > .now } public var isLive: Bool { itemLive == true } public var disappearing: Bool { !isRcvNew && itemTimed?.deleteAt != nil } @@ -2761,8 +2761,30 @@ public struct CITimed: Decodable, Hashable { public var deleteAt: Date? } +let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() +let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) + public func formatTimestampText(_ date: Date) -> Text { - Text(verbatim: date.formatted(date: .omitted, time: .shortened)) + Text(verbatim: date.formatted(recent(date) ? msgTimeFormat : msgDateFormat)) +} + +public func formatTimestampMeta(_ date: Date) -> String { + date.formatted(date: .omitted, time: .shortened) +} + +private func recent(_ date: Date) -> Bool { + let now = Date() + let calendar = Calendar.current + + guard let previousDay = calendar.date(byAdding: DateComponents(day: -1), to: now), + let previousDay18 = calendar.date(bySettingHour: 18, minute: 0, second: 0, of: previousDay), + let currentDay00 = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: now), + let currentDay12 = calendar.date(bySettingHour: 12, minute: 0, second: 0, of: now) else { + return false + } + + let isSameDay = calendar.isDate(date, inSameDayAs: now) + return isSameDay || (now < currentDay12 && date >= previousDay18 && date < currentDay00) } public enum CIStatus: Decodable, Hashable { From 1839dab17bc389201c31735930e8d688f7a53bca Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 6 Sep 2024 22:09:55 +0100 Subject: [PATCH 097/440] ios: move caching images to background thread, dont use main thread scheduler for marking items read (#4840) --- apps/ios/Shared/Views/Chat/ChatView.swift | 5 +++-- apps/ios/SimpleXChat/ImageUtils.swift | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index f3d1cc92a5..e22b4aa328 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -797,9 +797,10 @@ struct ChatView: View { } private func waitToMarkRead(_ op: @Sendable @escaping () async -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { + Task { + _ = try? await Task.sleep(nanoseconds: 600_000000) if m.chatId == chat.chatInfo.id { - Task(operation: op) + await op() } } } diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift index 2eb747c6a5..36148d4324 100644 --- a/apps/ios/SimpleXChat/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -391,7 +391,9 @@ public func imageFromBase64(_ base64Encoded: String?) -> UIImage? { return img } else if let data = Data(base64Encoded: dropImagePrefix(base64Encoded)), let img = UIImage(data: data) { - imageCache.setObject(img, forKey: base64Encoded as NSString) + imageCacheQueue.async { + imageCache.setObject(img, forKey: base64Encoded as NSString) + } return img } else { return nil @@ -401,6 +403,8 @@ public func imageFromBase64(_ base64Encoded: String?) -> UIImage? { } } +private let imageCacheQueue = DispatchQueue.global(qos: .background) + private var imageCache: NSCache = { var cache = NSCache() cache.countLimit = 1000 From 5ed701402b82377a59c657fd8aac6f1ccc137e6d Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 7 Sep 2024 19:40:10 +0100 Subject: [PATCH 098/440] core: optimize marking chat items as read, batch API (#4843) * core: optimize marking chat items as read * tests, ui types * ios: fix api * refactor --- apps/ios/Shared/Model/SimpleXAPI.swift | 4 ++ apps/ios/SimpleXChat/APITypes.swift | 7 +++ .../chat/simplex/common/model/SimpleXAPI.kt | 10 ++++ src/Simplex/Chat.hs | 49 +++++++++++++------ src/Simplex/Chat/Controller.hs | 1 + src/Simplex/Chat/Store/Messages.hs | 46 +++++++++++++---- tests/ChatTests/Direct.hs | 19 +++++++ tests/ChatTests/Groups.hs | 18 +++++++ 8 files changed, 129 insertions(+), 25 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 020b40f73e..9e77316725 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1000,6 +1000,10 @@ func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async thr try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange)) } +func apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) async throws { + try await sendCommandOkResp(.apiChatItemsRead(type: type, id: id, itemIds: itemIds)) +} + func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { try await sendCommandOkResp(.apiChatUnread(type: type, id: id, unreadChat: unreadChat)) } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index d4998762d7..7f030cb838 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -129,6 +129,7 @@ public enum ChatCommand { // WebRTC calls / case apiGetNetworkStatuses case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) + case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?) @@ -293,6 +294,7 @@ public enum ChatCommand { case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" case .apiGetNetworkStatuses: return "/_network_statuses" case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)" + case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))" case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))" @@ -434,6 +436,7 @@ public enum ChatCommand { case .apiCallStatus: return "apiCallStatus" case .apiGetNetworkStatuses: return "apiGetNetworkStatuses" case .apiChatRead: return "apiChatRead" + case .apiChatItemsRead: return "apiChatItemsRead" case .apiChatUnread: return "apiChatUnread" case .receiveFile: return "receiveFile" case .setFileToReceive: return "setFileToReceive" @@ -462,6 +465,10 @@ public enum ChatCommand { "\(type.rawValue)\(id)" } + func joinedIds(_ ids: [Int64]) -> String { + ids.map { "\($0)" }.joined(separator: ",") + } + func protoServersStr(_ servers: [ServerCfg]) -> String { encodeJSON(ProtoServersConfig(servers: servers)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 1c30e706d8..452e8a704b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1480,6 +1480,13 @@ object ChatController { return false } + suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, itemIds: List): Boolean { + val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, itemIds)) + if (r is CR.CmdOk) return true + Log.e(TAG, "apiChatItemsRead bad response: ${r.responseType} ${r.details}") + return false + } + suspend fun apiChatUnread(rh: Long?, type: ChatType, id: Long, unreadChat: Boolean): Boolean { val r = sendCmd(rh, CC.ApiChatUnread(type, id, unreadChat)) if (r is CR.CmdOk) return true @@ -2967,6 +2974,7 @@ sealed class CC { class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC() class ApiRejectContact(val contactReqId: Long): CC() class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC() + class ApiChatItemsRead(val type: ChatType, val id: Long, val itemIds: List): CC() class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC() class ReceiveFile(val fileId: Long, val userApprovedRelays: Boolean, val encrypt: Boolean, val inline: Boolean?): CC() class CancelFile(val fileId: Long): CC() @@ -3123,6 +3131,7 @@ sealed class CC { is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}" is ApiGetNetworkStatuses -> "/_network_statuses" is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}" + is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id)} ${itemIds.joinToString(",")}" is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}" is ReceiveFile -> "/freceive $fileId" + @@ -3266,6 +3275,7 @@ sealed class CC { is ApiCallStatus -> "apiCallStatus" is ApiGetNetworkStatuses -> "apiGetNetworkStatuses" is ApiChatRead -> "apiChatRead" + is ApiChatItemsRead -> "apiChatItemsRead" is ApiChatUnread -> "apiChatUnread" is ReceiveFile -> "receiveFile" is CancelFile -> "cancelFile" diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d9f4ce0db3..d348a56389 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -55,9 +55,9 @@ import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Time (NominalDiffTime, addUTCTime, defaultTimeLocale, formatTime) import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime, nominalDay, nominalDiffTimeToSeconds) import Data.Time.Clock.System (systemToUTCTime) -import Data.Word (Word32) import qualified Data.UUID as UUID import qualified Data.UUID.V4 as V4 +import Data.Word (Word32) import qualified Database.SQLite.Simple as SQL import Simplex.Chat.Archive import Simplex.Chat.Call @@ -115,7 +115,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import qualified Simplex.Messaging.TMap as TM @@ -1114,26 +1114,24 @@ processChatCommand' vr = \case when (size' > 0) $ copyChunks r w size' APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId - APIChatRead (ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of + APIChatRead chatRef@(ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of CTDirect -> do user <- withFastStore $ \db -> getUserByContactId db chatId - timedItems <- withFastStore' $ \db -> getDirectUnreadTimedItems db user chatId fromToIds ts <- liftIO getCurrentTime - forM_ timedItems $ \(itemId, ttl) -> do - let deleteAt = addUTCTime (realToFrac ttl) ts - withFastStore' $ \db -> setDirectChatItemDeleteAt db user chatId itemId deleteAt - startProximateTimedItemThread user (ChatRef CTDirect chatId, itemId) deleteAt - withFastStore' $ \db -> updateDirectChatItemsRead db user chatId fromToIds + timedItems <- withFastStore' $ \db -> do + timedItems <- getDirectUnreadTimedItems db user chatId fromToIds + updateDirectChatItemsRead db user chatId fromToIds + setDirectChatItemsDeleteAt db user chatId timedItems ts + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt ok user CTGroup -> do - user@User {userId} <- withFastStore $ \db -> getUserByGroupId db chatId - timedItems <- withFastStore' $ \db -> getGroupUnreadTimedItems db user chatId fromToIds + user <- withFastStore $ \db -> getUserByGroupId db chatId ts <- liftIO getCurrentTime - forM_ timedItems $ \(itemId, ttl) -> do - let deleteAt = addUTCTime (realToFrac ttl) ts - withFastStore' $ \db -> setGroupChatItemDeleteAt db user chatId itemId deleteAt - startProximateTimedItemThread user (ChatRef CTGroup chatId, itemId) deleteAt - withFastStore' $ \db -> updateGroupChatItemsRead db userId chatId fromToIds + timedItems <- withFastStore' $ \db -> do + timedItems <- getGroupUnreadTimedItems db user chatId fromToIds + updateGroupChatItemsRead db user chatId fromToIds + setGroupChatItemsDeleteAt db user chatId timedItems ts + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt ok user CTLocal -> do user <- withFastStore $ \db -> getUserByNoteFolderId db chatId @@ -1141,6 +1139,24 @@ processChatCommand' vr = \case ok user CTContactRequest -> pure $ chatCmdError Nothing "not supported" CTContactConnection -> pure $ chatCmdError Nothing "not supported" + APIChatItemsRead chatRef@(ChatRef cType chatId) itemIds -> withUser $ \_ -> case cType of + CTDirect -> do + user <- withFastStore $ \db -> getUserByContactId db chatId + timedItems <- withFastStore' $ \db -> do + timedItems <- updateDirectChatItemsReadList db user chatId itemIds + setDirectChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt + ok user + CTGroup -> do + user <- withFastStore $ \db -> getUserByGroupId db chatId + timedItems <- withFastStore' $ \db -> do + timedItems <- updateGroupChatItemsReadList db user chatId itemIds + setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt + ok user + CTLocal -> pure $ chatCmdError Nothing "not supported" + CTContactRequest -> pure $ chatCmdError Nothing "not supported" + CTContactConnection -> pure $ chatCmdError Nothing "not supported" APIChatUnread (ChatRef cType chatId) unreadChat -> withUser $ \user -> case cType of CTDirect -> do withFastStore $ \db -> do @@ -7873,6 +7889,7 @@ chatCommandP = "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), + "/_read chat items " *> (APIChatItemsRead <$> chatRefP <*> _strP), "/_unread chat " *> (APIChatUnread <$> chatRefP <* A.space <*> onOffP), "/_delete " *> (APIDeleteChat <$> chatRefP <*> chatDeleteMode), "/_clear chat " *> (APIClearChat <$> chatRefP), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 9d92ee8193..7268d0734a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -302,6 +302,7 @@ data ChatCommand | APIUserRead UserId | UserRead | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) + | APIChatItemsRead ChatRef (NonEmpty ChatItemId) | APIChatUnread ChatRef Bool | APIDeleteChat ChatRef ChatDeleteMode -- currently delete mode settings are only applied to direct chats | APIClearChat ChatRef diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 6dbd9124c5..f6f9588f66 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -60,10 +60,12 @@ module Simplex.Chat.Store.Messages deleteLocalChatItem, updateDirectChatItemsRead, getDirectUnreadTimedItems, - setDirectChatItemDeleteAt, + updateDirectChatItemsReadList, + setDirectChatItemsDeleteAt, updateGroupChatItemsRead, getGroupUnreadTimedItems, - setGroupChatItemDeleteAt, + updateGroupChatItemsReadList, + setGroupChatItemsDeleteAt, updateLocalChatItemsRead, getChatRefViaItemId, getChatItemVersions, @@ -126,7 +128,9 @@ import Data.ByteString.Char8 (ByteString) import Data.Either (fromRight, rights) import Data.Int (Int64) import Data.List (sortBy) -import Data.Maybe (fromMaybe, isJust, mapMaybe) +import Data.List.NonEmpty (NonEmpty) +import qualified Data.List.NonEmpty as L +import Data.Maybe (catMaybes, fromMaybe, isJust, mapMaybe) import Data.Ord (Down (..), comparing) import Data.Text (Text) import qualified Data.Text as T @@ -1339,15 +1343,27 @@ getDirectUnreadTimedItems db User {userId} contactId itemsRange_ = case itemsRan |] (userId, contactId, CISRcvNew) -setDirectChatItemDeleteAt :: DB.Connection -> User -> ContactId -> ChatItemId -> UTCTime -> IO () -setDirectChatItemDeleteAt db User {userId} contactId chatItemId deleteAt = +updateDirectChatItemsReadList :: DB.Connection -> User -> ContactId -> NonEmpty ChatItemId -> IO [(ChatItemId, Int)] +updateDirectChatItemsReadList db user contactId itemIds = do + catMaybes . L.toList <$> mapM getUpdateDirectItem itemIds + where + getUpdateDirectItem chatItemId = do + let itemsRange = Just (chatItemId, chatItemId) + timedItem <- maybeFirstRow id $ getDirectUnreadTimedItems db user contactId itemsRange + updateDirectChatItemsRead db user contactId itemsRange + pure timedItem + +setDirectChatItemsDeleteAt :: DB.Connection -> User -> ContactId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)] +setDirectChatItemsDeleteAt db User {userId} contactId itemIds currentTs = forM itemIds $ \(chatItemId, ttl) -> do + let deleteAt = addUTCTime (realToFrac ttl) currentTs DB.execute db "UPDATE chat_items SET timed_delete_at = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?" (deleteAt, userId, contactId, chatItemId) + pure (chatItemId, deleteAt) -updateGroupChatItemsRead :: DB.Connection -> UserId -> GroupId -> Maybe (ChatItemId, ChatItemId) -> IO () -updateGroupChatItemsRead db userId groupId itemsRange_ = do +updateGroupChatItemsRead :: DB.Connection -> User -> GroupId -> Maybe (ChatItemId, ChatItemId) -> IO () +updateGroupChatItemsRead db User {userId} groupId itemsRange_ = do currentTs <- getCurrentTime case itemsRange_ of Just (fromItemId, toItemId) -> @@ -1392,12 +1408,24 @@ getGroupUnreadTimedItems db User {userId} groupId itemsRange_ = case itemsRange_ |] (userId, groupId, CISRcvNew) -setGroupChatItemDeleteAt :: DB.Connection -> User -> GroupId -> ChatItemId -> UTCTime -> IO () -setGroupChatItemDeleteAt db User {userId} groupId chatItemId deleteAt = +updateGroupChatItemsReadList :: DB.Connection -> User -> GroupId -> NonEmpty ChatItemId -> IO [(ChatItemId, Int)] +updateGroupChatItemsReadList db user groupId itemIds = do + catMaybes . L.toList <$> mapM getUpdateGroupItem itemIds + where + getUpdateGroupItem chatItemId = do + let itemsRange = Just (chatItemId, chatItemId) + timedItem <- maybeFirstRow id $ getGroupUnreadTimedItems db user groupId itemsRange + updateGroupChatItemsRead db user groupId itemsRange + pure timedItem + +setGroupChatItemsDeleteAt :: DB.Connection -> User -> GroupId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)] +setGroupChatItemsDeleteAt db User {userId} groupId itemIds currentTs = forM itemIds $ \(chatItemId, ttl) -> do + let deleteAt = addUTCTime (realToFrac ttl) currentTs DB.execute db "UPDATE chat_items SET timed_delete_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id = ?" (deleteAt, userId, groupId, chatItemId) + pure (chatItemId, deleteAt) updateLocalChatItemsRead :: DB.Connection -> User -> NoteFolderId -> Maybe (ChatItemId, ChatItemId) -> IO () updateLocalChatItemsRead db User {userId} noteFolderId itemsRange_ = do diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 9980b3b723..97a9d89200 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -3,6 +3,7 @@ {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PostfixOperators #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} module ChatTests.Direct where @@ -22,6 +23,7 @@ import Simplex.Chat.AppSettings (defaultAppSettings) import qualified Simplex.Chat.AppSettings as AS import Simplex.Chat.Call import Simplex.Chat.Controller (ChatConfig (..)) +import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Options (ChatOpts (..)) import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) @@ -38,6 +40,7 @@ chatDirectTests :: SpecWith FilePath chatDirectTests = do describe "direct messages" $ do describe "add contact and send/receive messages" testAddContact + it "mark multiple messages as read" testMarkReadDirect it "clear chat with contact" testContactClear it "deleting contact deletes profile" testDeleteContactDeletesProfile it "delete contact keeping conversation" testDeleteContactKeepConversation @@ -212,6 +215,22 @@ testAddContact = versionTestMatrix2 runTestAddContact then chatFeatures else (0, e2eeInfoNoPQStr) : tail chatFeatures +testMarkReadDirect :: HasCallStack => FilePath -> IO () +testMarkReadDirect = testChat2 aliceProfile bobProfile $ \alice bob -> do + connectUsers alice bob + alice #> "@bob 1" + alice #> "@bob 2" + alice #> "@bob 3" + alice #> "@bob 4" + bob <# "alice> 1" + bob <# "alice> 2" + bob <# "alice> 3" + bob <# "alice> 4" + bob ##> "/last_item_id" + i :: ChatItemId <- read <$> getTermLine bob + let itemIds = intercalate "," $ map show [i - 3 .. i] + bob #$> ("/_read chat items @2 " <> itemIds, id, "ok") + testDuplicateContactsSeparate :: HasCallStack => FilePath -> IO () testDuplicateContactsSeparate = testChat2 aliceProfile bobProfile $ diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index d3e65ce5df..c65c7b8085 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -16,6 +16,7 @@ import Data.List (intercalate, isInfixOf) import qualified Data.Text as T import Database.SQLite.Simple (Only (..)) import Simplex.Chat.Controller (ChatConfig (..)) +import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Options import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) @@ -34,6 +35,7 @@ chatGroupTests :: SpecWith FilePath chatGroupTests = do describe "chat groups" $ do describe "add contacts, create group and send/receive messages" testGroupMatrix + it "mark multiple messages as read" testMarkReadGroup it "v1: add contacts, create group and send/receive messages" testGroup it "v1: add contacts, create group and send/receive messages, check messages" testGroupCheckMessages it "send large message" testGroupLargeMessage @@ -355,6 +357,22 @@ testGroupShared alice bob cath checkMessages directConnections = do alice #$> ("/_unread chat #1 on", id, "ok") alice #$> ("/_unread chat #1 off", id, "ok") +testMarkReadGroup :: HasCallStack => FilePath -> IO () +testMarkReadGroup = testChat2 aliceProfile bobProfile $ \alice bob -> do + createGroup2 "team" alice bob + alice #> "#team 1" + alice #> "#team 2" + alice #> "#team 3" + alice #> "#team 4" + bob <# "#team alice> 1" + bob <# "#team alice> 2" + bob <# "#team alice> 3" + bob <# "#team alice> 4" + bob ##> "/last_item_id" + i :: ChatItemId <- read <$> getTermLine bob + let itemIds = intercalate "," $ map show [i - 3 .. i] + bob #$> ("/_read chat items #1 " <> itemIds, id, "ok") + testGroupLargeMessage :: HasCallStack => FilePath -> IO () testGroupLargeMessage = testChat2 aliceProfile bobProfile $ From 691cd489ea8b8e10ee4b1a7adf4ff2e7cf25d455 Mon Sep 17 00:00:00 2001 From: Henrique Albuquerque <18596542+henrialb@users.noreply.github.com> Date: Sun, 8 Sep 2024 12:56:15 +0100 Subject: [PATCH 099/440] blog: fix grammar error (#4846) --- blog/20220404-simplex-chat-instant-notifications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/20220404-simplex-chat-instant-notifications.md b/blog/20220404-simplex-chat-instant-notifications.md index 73bf68a10b..ce7dfd613c 100644 --- a/blog/20220404-simplex-chat-instant-notifications.md +++ b/blog/20220404-simplex-chat-instant-notifications.md @@ -66,7 +66,7 @@ This service continues running when the app is switched off, and it is restarted So, for Android we can now deliver instant message notifications without compromising users' privacy in any way. The app version 1.5 that includes private instant notifications is now available on [Play Store](https://play.google.com/store/apps/details?id=chat.simplex.app), in our [F-Droid repo](https://app.simplex.chat/) and via direct [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) downloads! -Please let us what needs to be improved - it's only the first version of instant notifications for Android! +Please let us know what needs to be improved - it's only the first version of instant notifications for Android! ## Our iOS approach has one trade-off From 74b837bf9a39f974347ffebc730c2d72d42a136b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 8 Sep 2024 20:02:38 +0100 Subject: [PATCH 100/440] core: allow deleting user when user record in agent database was deleted (#4851) --- apps/simplex-chat/Server.hs | 4 ++-- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 5 ++++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/simplex-chat/Server.hs b/apps/simplex-chat/Server.hs index 58a167e4f5..5351565946 100644 --- a/apps/simplex-chat/Server.hs +++ b/apps/simplex-chat/Server.hs @@ -23,7 +23,7 @@ import Simplex.Chat import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Options -import Simplex.Messaging.Transport.Server (runTCPServer) +import Simplex.Messaging.Transport.Server (runLocalTCPServer) import Simplex.Messaging.Util (raceAny_) import UnliftIO.Exception import UnliftIO.STM @@ -68,7 +68,7 @@ newChatServerClient qSize = do runChatServer :: ChatServerConfig -> ChatController -> IO () runChatServer ChatServerConfig {chatPort, clientQSize} cc = do started <- newEmptyTMVarIO - runTCPServer started chatPort $ \sock -> do + runLocalTCPServer started chatPort $ \sock -> do ws <- liftIO $ getConnection sock c <- atomically $ newChatServerClient clientQSize putStrLn "client connected" diff --git a/cabal.project b/cabal.project index d7f6b67eb1..26a25613d6 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: d559a66145cf7b4cd367c09974ed1ce8393940b2 + tag: 344a295845ceea6a8a926e3f4c10fe79bcf05abe source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 0f6592086f..5b16072588 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."d559a66145cf7b4cd367c09974ed1ce8393940b2" = "1jav7jmriims6vlkxg8gmal03f9mbgrwc8v6g0rp95ivkx8gfjyw"; + "https://github.com/simplex-chat/simplexmq.git"."344a295845ceea6a8a926e3f4c10fe79bcf05abe" = "13l8qmzx0bfvs089hb68x25nfh5v0ik0gq1iyv3y3qnffw8601cf"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d348a56389..c140025648 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2807,7 +2807,10 @@ processChatCommand' vr = \case filesInfo <- withFastStore' (`getUserFileInfo` user) cancelFilesInProgress user filesInfo deleteFilesLocally filesInfo - withAgent $ \a -> deleteUser a (aUserId user) delSMPQueues + withAgent (\a -> deleteUser a (aUserId user) delSMPQueues) + `catchChatError` \case + e@(ChatErrorAgent NO_USER _) -> toView $ CRChatError (Just user) e + e -> throwError e withFastStore' (`deleteUserRecord` user) when (activeUser user) $ chatWriteVar currentUser Nothing ok_ From 351cfcbcbc1485b5bd65b1b2235f0b0bc4a1f416 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 8 Sep 2024 20:02:38 +0100 Subject: [PATCH 101/440] core: allow deleting user when user record in agent database was deleted (#4851) --- apps/simplex-chat/Server.hs | 4 ++-- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 5 ++++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/simplex-chat/Server.hs b/apps/simplex-chat/Server.hs index 58a167e4f5..5351565946 100644 --- a/apps/simplex-chat/Server.hs +++ b/apps/simplex-chat/Server.hs @@ -23,7 +23,7 @@ import Simplex.Chat import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Options -import Simplex.Messaging.Transport.Server (runTCPServer) +import Simplex.Messaging.Transport.Server (runLocalTCPServer) import Simplex.Messaging.Util (raceAny_) import UnliftIO.Exception import UnliftIO.STM @@ -68,7 +68,7 @@ newChatServerClient qSize = do runChatServer :: ChatServerConfig -> ChatController -> IO () runChatServer ChatServerConfig {chatPort, clientQSize} cc = do started <- newEmptyTMVarIO - runTCPServer started chatPort $ \sock -> do + runLocalTCPServer started chatPort $ \sock -> do ws <- liftIO $ getConnection sock c <- atomically $ newChatServerClient clientQSize putStrLn "client connected" diff --git a/cabal.project b/cabal.project index d7f6b67eb1..26a25613d6 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: d559a66145cf7b4cd367c09974ed1ce8393940b2 + tag: 344a295845ceea6a8a926e3f4c10fe79bcf05abe source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 0f6592086f..5b16072588 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."d559a66145cf7b4cd367c09974ed1ce8393940b2" = "1jav7jmriims6vlkxg8gmal03f9mbgrwc8v6g0rp95ivkx8gfjyw"; + "https://github.com/simplex-chat/simplexmq.git"."344a295845ceea6a8a926e3f4c10fe79bcf05abe" = "13l8qmzx0bfvs089hb68x25nfh5v0ik0gq1iyv3y3qnffw8601cf"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index cfc9f0dc97..1b4227eb39 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2763,7 +2763,10 @@ processChatCommand' vr = \case filesInfo <- withFastStore' (`getUserFileInfo` user) cancelFilesInProgress user filesInfo deleteFilesLocally filesInfo - withAgent $ \a -> deleteUser a (aUserId user) delSMPQueues + withAgent (\a -> deleteUser a (aUserId user) delSMPQueues) + `catchChatError` \case + e@(ChatErrorAgent NO_USER _) -> toView $ CRChatError (Just user) e + e -> throwError e withFastStore' (`deleteUserRecord` user) when (activeUser user) $ chatWriteVar currentUser Nothing ok_ From 8f6e9741e79a049b55e917c2376b09ca7c67b83f Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Mon, 9 Sep 2024 16:58:22 +0300 Subject: [PATCH 102/440] ios: add floating date separator (#4801) * ios: add floating date separator * floating date separator * revert formatTimestampText * send tuple, reduce lookups * background date visibility * whitespace * streamline * visible date * move pipeline to ReverseList * space * remove ViewUpdate * cleanup * refactor * combine unread items model updates * split publisher * remove readItemPublisher * revert markChatItemRead_ change * use single item api * comment test buttons * style * update top floating button instantly * cleanup * cleanup * minor * remove task * prevent concurrent updates * fix mark chat read --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 31 ++- apps/ios/Shared/Model/SimpleXAPI.swift | 18 +- apps/ios/Shared/Views/Chat/ChatView.swift | 245 ++++++++++++------- apps/ios/Shared/Views/Chat/ReverseList.swift | 61 +++-- 4 files changed, 232 insertions(+), 123 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 2be3191f4f..af394d11aa 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -61,7 +61,7 @@ class ItemsModel: ObservableObject { init() { publisher - .throttle(for: 0.25, scheduler: DispatchQueue.main, latest: true) + .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) .sink { self.objectWillChange.send() } .store(in: &bag) } @@ -563,6 +563,7 @@ final class ChatModel: ObservableObject { // update preview _updateChat(cInfo.id) { chat in self.decreaseUnreadCounter(user: self.currentUser!, by: chat.chatStats.unreadCount) + self.updateFloatingButtons(unreadCount: 0) chat.chatStats = ChatStats() } // update current chat @@ -579,6 +580,12 @@ final class ChatModel: ObservableObject { } } + private func updateFloatingButtons(unreadCount: Int) { + let fbm = ChatView.FloatingButtonModel.shared + fbm.totalUnread = unreadCount + fbm.objectWillChange.send() + } + func markChatItemsRead(_ cInfo: ChatInfo, aboveItem: ChatItem? = nil) { if let cItem = aboveItem { if chatId == cInfo.id, let i = getChatItemIndex(cItem) { @@ -597,6 +604,7 @@ final class ChatModel: ObservableObject { if markedCount > 0 { chat.chatStats.unreadCount -= markedCount self.decreaseUnreadCounter(user: self.currentUser!, by: markedCount) + self.updateFloatingButtons(unreadCount: chat.chatStats.unreadCount) } } } @@ -626,19 +634,15 @@ final class ChatModel: ObservableObject { } } - func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { - if chatId == cInfo.id, - let itemIndex = getChatItemIndex(cItem), - im.reversedChatItems[itemIndex].isRcvNew { - await MainActor.run { - withTransaction(Transaction()) { - // update current chat - markChatItemRead_(itemIndex) - // update preview - unreadCollector.changeUnreadCounter(cInfo.id, by: -1) + func markChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID]) { + if self.chatId == cInfo.id { + for itemId in itemIds { + if let i = im.reversedChatItems.firstIndex(where: { $0.id == itemId }) { + markChatItemRead_(i) } } } + self.unreadCollector.changeUnreadCounter(cInfo.id, by: -itemIds.count) } private let unreadCollector = UnreadCollector() @@ -664,9 +668,10 @@ final class ChatModel: ObservableObject { } func changeUnreadCounter(_ chatId: ChatId, by count: Int) { - DispatchQueue.main.async { - self.unreadCounts[chatId] = (self.unreadCounts[chatId] ?? 0) + count + if chatId == ChatModel.shared.chatId { + ChatView.FloatingButtonModel.shared.totalUnread += count } + self.unreadCounts[chatId] = (self.unreadCounts[chatId] ?? 0) + count subject.send() } } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 9e77316725..7312b42b1b 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1291,11 +1291,23 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async { func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { do { - logger.debug("apiMarkChatItemRead: \(cItem.id)") try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id)) - await ChatModel.shared.markChatItemRead(cInfo, cItem) + DispatchQueue.main.async { + ChatModel.shared.markChatItemsRead(cInfo, [cItem.id]) + } } catch { - logger.error("apiMarkChatItemRead apiChatRead error: \(responseError(error))") + logger.error("apiChatRead error: \(responseError(error))") + } +} + +func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID]) async { + do { + try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, itemIds: itemIds) + DispatchQueue.main.async { + ChatModel.shared.markChatItemsRead(cInfo, itemIds) + } + } catch { + logger.error("apiChatItemsRead error: \(responseError(error))") } } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e22b4aa328..5c11cdc3df 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -23,7 +23,6 @@ struct ChatView: View { @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat @StateObject private var scrollModel = ReverseListScrollModel() - @StateObject private var floatingButtonModel: FloatingButtonModel = .shared @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @State private var composeState = ComposeState() @@ -76,8 +75,7 @@ struct ChatView: View { VStack(spacing: 0) { ZStack(alignment: .bottomTrailing) { chatItemsList() - // TODO: Extract into a separate view, to reduce the scope of `FloatingButtonModel` updates - floatingButtons(unreadBelow: floatingButtonModel.unreadBelow, isNearBottom: floatingButtonModel.isNearBottom) + FloatingButtons(theme: theme, scrollModel: scrollModel, chat: chat) } connectingText() if selectedChatItems == nil { @@ -341,6 +339,7 @@ struct ChatView: View { await markChatUnread(chat, unreadChat: false) } } + ChatView.FloatingButtonModel.shared.totalUnread = chat.chatStats.unreadCount } private func searchToolbar() -> some View { @@ -427,7 +426,7 @@ struct ChatView: View { .onChange(of: im.itemAdded) { added in if added { im.itemAdded = false - if floatingButtonModel.isReallyNearBottom { + if FloatingButtonModel.shared.isReallyNearBottom { scrollModel.scrollToBottom() } } @@ -453,86 +452,162 @@ struct ChatView: View { static let shared = FloatingButtonModel() @Published var unreadBelow: Int = 0 @Published var isNearBottom: Bool = true - var isReallyNearBottom: Bool { scrollOffset.value > 0 && scrollOffset.value < 500 } - let visibleItems = PassthroughSubject<[String], Never>() - let scrollOffset = CurrentValueSubject(0) - private var bag = Set() + @Published var date: Date? + @Published var isDateVisible: Bool = false + var totalUnread: Int = 0 + var isReallyNearBottom: Bool = true + var hideDateWorkItem: DispatchWorkItem? - init() { - visibleItems - .receive(on: DispatchQueue.global(qos: .background)) - .map { itemIds in - if let viewId = itemIds.first, - let index = ItemsModel.shared.reversedChatItems.firstIndex(where: { $0.viewId == viewId }) { - ItemsModel.shared.reversedChatItems[.. 0 && listState.scrollOffset < 500 + } + + // set floating button indication mode + let nearBottom = listState.scrollOffset < 800 + if nearBottom != self.isNearBottom { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in + self?.isNearBottom = nearBottom + } + } + + // hide Date indicator after 1 second of no scrolling + hideDateWorkItem?.cancel() + let workItem = DispatchWorkItem { [weak self] in + guard let it = self else { return } + it.setDate(visibility: false) + it.hideDateWorkItem = nil + } + DispatchQueue.main.async { [weak self] in + self?.hideDateWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem) + } } + + func resetDate() { + date = nil + isDateVisible = false + } + + private func setDate(visibility isVisible: Bool) { + if isVisible { + if !isNearBottom, + !isDateVisible, + let date, !Calendar.current.isDateInToday(date) { + withAnimation { self.isDateVisible = true } + } + } else if isDateVisible { + withAnimation { self.isDateVisible = false } + } + } + } - private func floatingButtons(unreadBelow: Int, isNearBottom: Bool) -> some View { - VStack { - let unreadAbove = chat.chatStats.unreadCount - unreadBelow - if unreadAbove > 0 { - circleButton { - unreadCountText(unreadAbove) - .font(.callout) - .foregroundColor(theme.colors.primary) + private struct FloatingButtons: View { + let theme: AppTheme + let scrollModel: ReverseListScrollModel + let chat: Chat + @ObservedObject var model = FloatingButtonModel.shared + + var body: some View { + ZStack(alignment: .top) { + if let date = model.date { + DateSeparator(date: date) + .padding(.vertical, 4).padding(.horizontal, 8) + .background(.thinMaterial) + .clipShape(Capsule()) + .opacity(model.isDateVisible ? 1 : 0) } - .onTapGesture { - scrollModel.scrollToNextPage() - } - .contextMenu { - Button { - Task { - await markChatRead(chat) + VStack { + let unreadAbove = model.totalUnread - model.unreadBelow + if unreadAbove > 0 { + circleButton { + unreadCountText(unreadAbove) + .font(.callout) + .foregroundColor(theme.colors.primary) } - } label: { - Label("Mark read", systemImage: "checkmark") + .onTapGesture { + scrollModel.scrollToNextPage() + } + .contextMenu { + Button { + Task { + await markChatRead(chat) + } + } label: { + Label("Mark read", systemImage: "checkmark") + } + } + } + Spacer() + if model.unreadBelow > 0 { + circleButton { + unreadCountText(model.unreadBelow) + .font(.callout) + .foregroundColor(theme.colors.primary) + } + .onTapGesture { + scrollModel.scrollToBottom() + } + } else if !model.isNearBottom { + circleButton { + Image(systemName: "chevron.down") + .foregroundColor(theme.colors.primary) + } + .onTapGesture { scrollModel.scrollToBottom() } } } + .padding() + .frame(maxWidth: .infinity, alignment: .trailing) } - Spacer() - if unreadBelow > 0 { - circleButton { - unreadCountText(unreadBelow) - .font(.callout) - .foregroundColor(theme.colors.primary) - } - .onTapGesture { - scrollModel.scrollToBottom() - } - } else if !isNearBottom { - circleButton { - Image(systemName: "chevron.down") - .foregroundColor(theme.colors.primary) - } - .onTapGesture { scrollModel.scrollToBottom() } + .onDisappear(perform: model.resetDate) + } + + private func circleButton(_ content: @escaping () -> Content) -> some View { + ZStack { + Circle() + .foregroundColor(Color(uiColor: .tertiarySystemGroupedBackground)) + .frame(width: 44, height: 44) + content() } } - .padding() } - private func circleButton(_ content: @escaping () -> Content) -> some View { - ZStack { - Circle() - .foregroundColor(Color(uiColor: .tertiarySystemGroupedBackground)) - .frame(width: 44, height: 44) - content() + private struct DateSeparator: View { + let date: Date + + var body: some View { + Text(String.localizedStringWithFormat( + NSLocalizedString("%@, %@", comment: "format for date separator in chat"), + date.formatted(.dateTime.weekday(.abbreviated)), + date.formatted(.dateTime.day().month(.abbreviated)) + )) + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) } } @@ -693,6 +768,7 @@ struct ChatView: View { @Binding var selectedChatItems: Set? @State private var allowMenu: Bool = true + @State private var markedRead = false var revealed: Bool { chatItem == revealedChatItem } @@ -743,15 +819,7 @@ struct ChatView: View { VStack(spacing: 0) { chatItemView(chatItem, range, prevItem, timeSeparation) if let date = timeSeparation.date { - Text(String.localizedStringWithFormat( - NSLocalizedString("%@, %@", comment: "format for date separator in chat"), - date.formatted(.dateTime.weekday(.abbreviated)), - date.formatted(.dateTime.day().month(.abbreviated)) - )) - .font(.callout) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .padding(8) + DateSeparator(date: date).padding(8) } } .overlay { @@ -767,12 +835,16 @@ struct ChatView: View { } } .onAppear { + if markedRead { + return + } else { + markedRead = true + } if let range { - if let items = unreadItems(range) { + let itemIds = unreadItemIds(range) + if !itemIds.isEmpty { waitToMarkRead { - for ci in items { - await apiMarkChatItemRead(chat.chatInfo, ci) - } + await apiMarkChatItemsRead(chat.chatInfo, itemIds) } } } else if chatItem.isRcvNew { @@ -782,18 +854,17 @@ struct ChatView: View { } } } - - private func unreadItems(_ range: ClosedRange) -> [ChatItem]? { + + private func unreadItemIds(_ range: ClosedRange) -> [ChatItem.ID] { let im = ItemsModel.shared - let items = range.compactMap { i in + return range.compactMap { i in if i >= 0 && i < im.reversedChatItems.count { let ci = im.reversedChatItems[i] - return if ci.isRcvNew { ci } else { nil } + return if ci.isRcvNew { ci.id } else { nil } } else { return nil } } - return if items.isEmpty { nil } else { items } } private func waitToMarkRead(_ op: @Sendable @escaping () async -> Void) { diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index f64189f95f..2e09909c5e 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -107,9 +107,14 @@ struct ReverseList: UIViewControllerRepresentable { name: notificationName, object: nil ) + updateFloatingButtons - .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) - .sink { self.updateVisibleItems() } + .throttle(for: 0.2, scheduler: DispatchQueue.global(qos: .background), latest: true) + .sink { + if let listState = DispatchQueue.main.sync(execute: { [weak self] in self?.getListState() }) { + ChatView.FloatingButtonModel.shared.updateOnListChange(listState) + } + } .store(in: &bag) } @@ -203,25 +208,35 @@ struct ReverseList: UIViewControllerRepresentable { updateFloatingButtons.send() } - private func updateVisibleItems() { - let fbm = ChatView.FloatingButtonModel.shared - fbm.scrollOffset.send(tableView.contentOffset.y + InvertedTableView.inset) - fbm.visibleItems.send( - (tableView.indexPathsForVisibleRows ?? []) - .compactMap { indexPath -> String? in - guard let relativeFrame = tableView.superview?.convert( - tableView.rectForRow(at: indexPath), - from: tableView - ) else { return nil } - // Checks that the cell is visible accounting for the added insets - let isVisible = - relativeFrame.maxY > InvertedTableView.inset && - relativeFrame.minY < tableView.frame.height - InvertedTableView.inset - return indexPath.item < representer.items.count && isVisible - ? representer.items[indexPath.item].viewId - : nil + func getListState() -> ListState? { + if let visibleRows = tableView.indexPathsForVisibleRows, + visibleRows.last?.item ?? 0 < representer.items.count { + let scrollOffset: Double = tableView.contentOffset.y + InvertedTableView.inset + let topItemDate: Date? = + if let lastVisible = visibleRows.last(where: { isVisible(indexPath: $0) }) { + representer.items[lastVisible.item].meta.itemTs + } else { + nil } - ) + let bottomItemId: ChatItem.ID? = + if let firstVisible = visibleRows.first(where: { isVisible(indexPath: $0) }) { + representer.items[firstVisible.item].id + } else { + nil + } + return (scrollOffset: scrollOffset, topItemDate: topItemDate, bottomItemId: bottomItemId) + } + return nil + } + + private func isVisible(indexPath: IndexPath) -> Bool { + if let relativeFrame = tableView.superview?.convert( + tableView.rectForRow(at: indexPath), + from: tableView + ) { + relativeFrame.maxY > InvertedTableView.inset && + relativeFrame.minY < tableView.frame.height - InvertedTableView.inset + } else { false } } } @@ -265,6 +280,12 @@ struct ReverseList: UIViewControllerRepresentable { } } +typealias ListState = ( + scrollOffset: Double, + topItemDate: Date?, + bottomItemId: ChatItem.ID? +) + /// Manages ``ReverseList`` scrolling class ReverseListScrollModel: ObservableObject { /// Represents Scroll State of ``ReverseList`` From 0cb568d2066bd3cd5ab452962c1015c847b1ea34 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 9 Sep 2024 15:01:18 +0100 Subject: [PATCH 103/440] fix incorrect error of migration to device (#4852) * fix incorrect error of migration to device * alert to finish migration, ios fix * simplexmq * catching exception and stopping chat * text --------- Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- apps/ios/Shared/AppDelegate.swift | 5 --- apps/ios/Shared/Model/ChatModel.swift | 1 - .../Views/Migration/MigrateFromDevice.swift | 13 +++++++- .../Views/Migration/MigrateToDevice.swift | 32 +++++++++++++------ .../Shared/Views/NewChat/NewChatView.swift | 3 +- .../Views/UserSettings/AppSettings.swift | 8 ++--- apps/ios/SimpleX.xcodeproj/project.pbxproj | 8 +++++ .../xcshareddata/swiftpm/Package.resolved | 3 +- apps/ios/SimpleXChat/SimpleX.h | 1 + apps/ios/SimpleXChat/objc.h | 20 ++++++++++++ apps/ios/SimpleXChat/objc.m | 25 +++++++++++++++ .../views/migration/MigrateFromDevice.kt | 10 +++++- .../common/views/migration/MigrateToDevice.kt | 8 ++--- .../commonMain/resources/MR/base/strings.xml | 2 ++ cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Store/Shared.hs | 1 - 17 files changed, 111 insertions(+), 33 deletions(-) create mode 100644 apps/ios/SimpleXChat/objc.h create mode 100644 apps/ios/SimpleXChat/objc.m diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index dd91d4af68..5845793aa7 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -15,16 +15,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { logger.debug("AppDelegate: didFinishLaunchingWithOptions") application.registerForRemoteNotifications() - NotificationCenter.default.addObserver(self, selector: #selector(pasteboardChanged), name: UIPasteboard.changedNotification, object: nil) removePasscodesIfReinstalled() prepareForLaunch() return true } - @objc func pasteboardChanged() { - ChatModel.shared.pasteboardHasStrings = UIPasteboard.general.hasStrings - } - func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let token = deviceToken.map { String(format: "%02hhx", $0) }.joined() logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)") diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 0ac1a9cacb..caa54887f7 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -183,7 +183,6 @@ final class ChatModel: ObservableObject { @Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source @Published var draft: ComposeState? @Published var draftChatId: String? - @Published var pasteboardHasStrings: Bool = UIPasteboard.general.hasStrings @Published var networkInfo = UserNetworkInfo(networkType: .other, online: true) var messageDelivery: Dictionary Void> = [:] diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 9cc229ba80..1303a1247f 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -24,6 +24,7 @@ private enum MigrationFromState: Equatable { } private enum MigrateFromDeviceViewAlert: Identifiable { + case finishMigration(_ fileId: Int64, _ ctrl: chat_ctrl) case deleteChat(_ title: LocalizedStringKey = "Delete chat profile?", _ text: LocalizedStringKey = "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.") case startChat(_ title: LocalizedStringKey = "Start chat?", _ text: LocalizedStringKey = "Warning: starting chat on multiple devices is not supported and will cause message delivery failures") @@ -38,6 +39,7 @@ private enum MigrateFromDeviceViewAlert: Identifiable { var id: String { switch self { + case .finishMigration: return "finishMigration" case let .deleteChat(title, text): return "\(title) \(text)" case let .startChat(title, text): return "\(title) \(text)" @@ -138,6 +140,15 @@ struct MigrateFromDevice: View { } .alert(item: $alert) { alert in switch alert { + case let .finishMigration(fileId, ctrl): + return Alert( + title: Text("Remove archive?"), + message: Text("The uploaded database archive will be permanently removed from the servers."), + primaryButton: .destructive(Text("Continue")) { + finishMigration(fileId, ctrl) + }, + secondaryButton: .cancel() + ) case let .startChat(title, text): return Alert( title: Text(title), @@ -318,7 +329,7 @@ struct MigrateFromDevice: View { Text("Cancel migration").foregroundColor(.red) } } - Button(action: { finishMigration(fileId, ctrl) }) { + Button(action: { alert = .finishMigration(fileId, ctrl) }) { settingsRow("checkmark", color: theme.colors.secondary) { Text("Finalize migration").foregroundColor(theme.colors.primary) } diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index 67ea1008cd..e2df68a0e4 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -93,7 +93,6 @@ struct MigrateToDevice: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss: DismissAction - @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @Binding var migrationState: MigrationToState? @State private var useKeychain = storeDBPassphraseGroupDefault.get() @State private var alert: MigrateToDeviceViewAlert? @@ -102,6 +101,7 @@ struct MigrateToDevice: View { // Prevent from hiding the view until migration is finished or app deleted @State private var backDisabled: Bool = false @State private var showQRCodeScanner: Bool = true + @State private var pasteboardHasStrings = UIPasteboard.general.hasStrings var body: some View { VStack { @@ -197,10 +197,8 @@ struct MigrateToDevice: View { } } } - if developerTools { - Section(header: Text("Or paste archive link").foregroundColor(theme.colors.secondary)) { - pasteLinkView() - } + Section(header: Text("Or paste archive link").foregroundColor(theme.colors.secondary)) { + pasteLinkView() } } } @@ -218,7 +216,7 @@ struct MigrateToDevice: View { } label: { Text("Tap to paste link") } - .disabled(!ChatModel.shared.pasteboardHasStrings) + .disabled(!pasteboardHasStrings) .frame(maxWidth: .infinity, alignment: .center) } @@ -487,6 +485,9 @@ struct MigrateToDevice: View { do { if !hasChatCtrl() { chatInitControllerRemovingDatabases() + } else if ChatModel.shared.chatRunning == true { + // cannot delete storage if chat is running + try await apiStopChat() } try await apiDeleteStorage() try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true) @@ -556,11 +557,22 @@ struct MigrateToDevice: View { do { try? FileManager.default.removeItem(at: getMigrationTempFilesDirectory()) MigrationToDeviceState.save(nil) - appSettings.importIntoApp() - try SimpleX.startChat(refreshInvitations: true) - AlertManager.shared.showAlertMsg(title: "Chat migrated!", message: "Finalize migration on another device.") + try ObjC.catchException { + appSettings.importIntoApp() + } + do { + try SimpleX.startChat(refreshInvitations: true) + AlertManager.shared.showAlertMsg(title: "Chat migrated!", message: "Finalize migration on another device.") + } catch let error { + AlertManager.shared.showAlert(Alert(title: Text("Error starting chat"), message: Text(responseError(error)))) + } } catch let error { - AlertManager.shared.showAlert(Alert(title: Text("Error starting chat"), message: Text(responseError(error)))) + logger.error("Error importing settings: \(error.localizedDescription)") + AlertManager.shared.showAlert( + Alert( + title: Text("Error migrating settings"), + message: Text ("Not all settings were migrated. Repeat migration if you need them.") + Text("\n\n") + Text(responseError(error))) + ) } hideView() } diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 6cbc65e7c9..c9670aa44a 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -296,6 +296,7 @@ private struct ConnectView: View { @Binding var pastedLink: String @Binding var alert: NewChatViewAlert? @State private var sheet: PlanAndConnectActionSheet? + @State private var pasteboardHasStrings = UIPasteboard.general.hasStrings var body: some View { List { @@ -332,7 +333,7 @@ private struct ConnectView: View { } label: { Text("Tap to paste link") } - .disabled(!ChatModel.shared.pasteboardHasStrings) + .disabled(!pasteboardHasStrings) .frame(maxWidth: .infinity, alignment: .center) } else { linkTextView(pastedLink) diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index 6247777bf2..bd829552f4 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -52,10 +52,10 @@ extension AppSettings { 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 = uiColorScheme { currentThemeDefault.set(val) } + if let val = uiDarkColorScheme { systemDarkThemeDefault.set(val) } + if let val = uiCurrentThemeIds { currentThemeIdsDefault.set(val) } + if let val = uiThemes { themeOverridesDefault.set(val.skipDuplicates()) } if let val = oneHandUI { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_ONE_HAND_UI) } } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 4a537f14bf..399d88b39f 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -177,6 +177,8 @@ 64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; }; 64EEB0F72C353F1C00972D62 /* ServersSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EEB0F62C353F1C00972D62 /* ServersSummaryView.swift */; }; 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; }; + 8C01E9C12C8EFC33008A4B0A /* objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C01E9C02C8EFC33008A4B0A /* objc.m */; }; + 8C01E9C22C8EFF8F008A4B0A /* objc.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C01E9BF2C8EFBB6008A4B0A /* objc.h */; }; 8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */; }; 8C74C3E52C1B900600039E77 /* ThemeTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7E3CE32C0DEAC400BFF63A /* ThemeTypes.swift */; }; 8C74C3E72C1B901900039E77 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C852B072C1086D100BA61E8 /* Color.swift */; }; @@ -516,6 +518,8 @@ 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = ""; }; 64EEB0F62C353F1C00972D62 /* ServersSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersSummaryView.swift; sourceTree = ""; }; 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = ""; }; + 8C01E9BF2C8EFBB6008A4B0A /* objc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = objc.h; sourceTree = ""; }; + 8C01E9C02C8EFC33008A4B0A /* objc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = objc.m; sourceTree = ""; }; 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; 8C74C3EB2C1B92A900039E77 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 8C74C3ED2C1B942300039E77 /* ChatWallpaper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatWallpaper.swift; sourceTree = ""; }; @@ -976,6 +980,8 @@ 5CE2BA96284537A800EC33A6 /* dummy.m */, 5CD67B8D2B0E858A00C510B1 /* hs_init.h */, 5CD67B8E2B0E858A00C510B1 /* hs_init.c */, + 8C01E9BF2C8EFBB6008A4B0A /* objc.h */, + 8C01E9C02C8EFC33008A4B0A /* objc.m */, ); path = SimpleXChat; sourceTree = ""; @@ -1113,6 +1119,7 @@ files = ( 5CE2BA77284530BF00EC33A6 /* SimpleXChat.h in Headers */, 5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */, + 8C01E9C22C8EFF8F008A4B0A /* objc.h in Headers */, 5CE2BA952845354B00EC33A6 /* SimpleX.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1539,6 +1546,7 @@ 8C74C3E72C1B901900039E77 /* Color.swift in Sources */, 5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */, 5CE2BA91284533A300EC33A6 /* Notifications.swift in Sources */, + 8C01E9C12C8EFC33008A4B0A /* objc.m in Sources */, 5CE2BA79284530CC00EC33A6 /* SimpleXChat.docc in Sources */, 5CE2BA90284533A300EC33A6 /* JSON.swift in Sources */, 5CE2BA8B284533A300EC33A6 /* ChatTypes.swift in Sources */, diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 22312bf5a1..c8623a95cb 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { - "branch" : "master", - "revision" : "7c50eb60ca4b90043c6ad719d595803488496212" + "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" } }, { diff --git a/apps/ios/SimpleXChat/SimpleX.h b/apps/ios/SimpleXChat/SimpleX.h index 153365424e..92dfafca21 100644 --- a/apps/ios/SimpleXChat/SimpleX.h +++ b/apps/ios/SimpleXChat/SimpleX.h @@ -10,6 +10,7 @@ #define SimpleX_h #include "hs_init.h" +#include "objc.h" extern void hs_init(int argc, char **argv[]); diff --git a/apps/ios/SimpleXChat/objc.h b/apps/ios/SimpleXChat/objc.h new file mode 100644 index 0000000000..a75a6dc5e4 --- /dev/null +++ b/apps/ios/SimpleXChat/objc.h @@ -0,0 +1,20 @@ +// +// objc.h +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 09.09.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +#ifndef objc_h +#define objc_h + +#import + +@interface ObjC : NSObject + ++ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error; + +@end + +#endif /* objc_h */ diff --git a/apps/ios/SimpleXChat/objc.m b/apps/ios/SimpleXChat/objc.m new file mode 100644 index 0000000000..c6952578ab --- /dev/null +++ b/apps/ios/SimpleXChat/objc.m @@ -0,0 +1,25 @@ +// +// objc.m +// SimpleXChat +// +// Created by Stanislav Dmitrenko on 09.09.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +#import "objc.h" + +@implementation ObjC + +// https://stackoverflow.com/a/36454808 ++ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { + @try { + tryBlock(); + return YES; + } + @catch (NSException *exception) { + *error = [[NSError alloc] initWithDomain: exception.name code: 0 userInfo: exception.userInfo]; + return NO; + } +} + +@end diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index dbb805971e..a71503e315 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -349,7 +349,15 @@ private fun MutableState.LinkShownView(fileId: Long, link: S text = stringResource(MR.strings.migrate_from_device_finalize_migration), textColor = MaterialTheme.colors.primary, click = { - finishMigration(fileId, ctrl) + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.migrate_from_device_remove_archive_question), + text = generalGetString(MR.strings.migrate_from_device_uploaded_archive_will_be_removed), + confirmText = generalGetString(MR.strings.continue_to_next_step), + destructive = true, + onConfirm = { + finishMigration(fileId, ctrl) + } + ) } ) {} SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_archive_will_be_deleted)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 4fa36b06f0..8312c213ec 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -199,10 +199,8 @@ private fun MutableState.PasteOrScanLinkView() { SectionSpacer() } - if (appPlatform.isDesktop || appPreferences.developerTools.get()) { - SectionView(stringResource(if (appPlatform.isAndroid) MR.strings.or_paste_archive_link else MR.strings.paste_archive_link).uppercase()) { - PasteLinkView() - } + SectionView(stringResource(if (appPlatform.isAndroid) MR.strings.or_paste_archive_link else MR.strings.paste_archive_link).uppercase()) { + PasteLinkView() } } @@ -561,7 +559,7 @@ private fun MutableState.startDownloading( ) state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) } else { - Log.d(TAG, "unsupported error: ${msg.responseType}") + Log.d(TAG, "unsupported error: ${msg.responseType}, ${json.encodeToString(msg.chatError)}") } } else -> Log.d(TAG, "unsupported event: ${msg.responseType}") diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 1a3eb2c72f..0c5a4eb749 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2174,6 +2174,8 @@ Creating archive link Cancel migration Finalize migration + Remove archive? + The uploaded database archive will be permanently removed from the servers. Migrate from another device on the new device and scan QR code.]]> Or securely share this file link Delete database from this device diff --git a/cabal.project b/cabal.project index 26a25613d6..57d4a84496 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 344a295845ceea6a8a926e3f4c10fe79bcf05abe + tag: dab1980d79b35634bea9a259b633bd06ed8d5ebf source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 5b16072588..6d432a90a5 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."344a295845ceea6a8a926e3f4c10fe79bcf05abe" = "13l8qmzx0bfvs089hb68x25nfh5v0ik0gq1iyv3y3qnffw8601cf"; + "https://github.com/simplex-chat/simplexmq.git"."dab1980d79b35634bea9a259b633bd06ed8d5ebf" = "0wf0p02bz8zq31mgfnfxy5allhbfr6vcmv8cii92qwj95m6ibwcp"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index b80e2ce805..a4c2d1da39 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -383,7 +383,6 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId = |] [":user_id" := userId, ":profile_id" := profileId] - type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, Bool, Maybe UIThemeEntityOverrides, Bool, Maybe CustomData) type ContactRow = Only ContactId :. ContactRow' From 388609563daf16891c8c04798117d7d282ea0765 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:22:14 +0400 Subject: [PATCH 104/440] core: update simplexmq (ntf encoding) (#4853) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b4226d567b..a8a02b7579 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -167,6 +167,11 @@ 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; + 64A208622C8F2CCC00AE9D01 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */; }; + 64A208632C8F2CCC00AE9D01 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085E2C8F2CCB00AE9D01 /* libffi.a */; }; + 64A208642C8F2CCC00AE9D01 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */; }; + 64A208652C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */; }; + 64A208662C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; }; @@ -215,11 +220,6 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E5BD84572C832BF9008C24D1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84522C832BF9008C24D1 /* libgmpxx.a */; }; - E5BD84582C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */; }; - E5BD84592C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */; }; - E5BD845A2C832BF9008C24D1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84552C832BF9008C24D1 /* libgmp.a */; }; - E5BD845B2C832BF9008C24D1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84562C832BF9008C24D1 /* libffi.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -506,6 +506,11 @@ 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; + 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 64A2085E2C8F2CCB00AE9D01 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a"; sourceTree = ""; }; + 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a"; sourceTree = ""; }; 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; }; 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; }; 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = ""; }; @@ -552,11 +557,6 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E5BD84522C832BF9008C24D1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a"; sourceTree = ""; }; - E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a"; sourceTree = ""; }; - E5BD84552C832BF9008C24D1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E5BD84562C832BF9008C24D1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -647,13 +647,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E5BD84572C832BF9008C24D1 /* libgmpxx.a in Frameworks */, - E5BD845A2C832BF9008C24D1 /* libgmp.a in Frameworks */, + 64A208662C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a in Frameworks */, + 64A208622C8F2CCC00AE9D01 /* libgmpxx.a in Frameworks */, + 64A208652C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E5BD845B2C832BF9008C24D1 /* libffi.a in Frameworks */, - E5BD84582C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a in Frameworks */, - E5BD84592C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + 64A208632C8F2CCC00AE9D01 /* libffi.a in Frameworks */, + 64A208642C8F2CCC00AE9D01 /* libgmp.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -731,11 +731,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E5BD84562C832BF9008C24D1 /* libffi.a */, - E5BD84552C832BF9008C24D1 /* libgmp.a */, - E5BD84522C832BF9008C24D1 /* libgmpxx.a */, - E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */, - E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */, + 64A2085E2C8F2CCB00AE9D01 /* libffi.a */, + 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */, + 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */, + 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */, + 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */, ); path = Libraries; sourceTree = ""; diff --git a/cabal.project b/cabal.project index 26a25613d6..fc958929af 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 344a295845ceea6a8a926e3f4c10fe79bcf05abe + tag: 946e16339e16e026f51185ebfb48c3a0c5a5b2e1 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 5b16072588..fa3c6d4839 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."344a295845ceea6a8a926e3f4c10fe79bcf05abe" = "13l8qmzx0bfvs089hb68x25nfh5v0ik0gq1iyv3y3qnffw8601cf"; + "https://github.com/simplex-chat/simplexmq.git"."946e16339e16e026f51185ebfb48c3a0c5a5b2e1" = "1jkx2f14h5krmy467zyifsc58dys89pkpn08cyf1q9v78in7nwfd"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From fb4475027d811308d38d32b9ec0b50a1e7006cfa Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 10 Sep 2024 09:31:53 +0100 Subject: [PATCH 105/440] ios: new user picker (#4821) * ios: new user picker (#4770) * current user picker progress * one hand picker * thin bullet icon * more user picker buttons * button clickable areas * divider padding * extra space after sun * send current user option to address view * add unread count badge * with anim for apperance close * edit current profile from picker * remove you section from settings * remove help and support * simplify * move settings and sun to same row * remove redundant vstack * long press on sun/moon switches to system setting * remove back button from migrate device * smooth profile transitions * close user picker on list profiles * fix dismiss on migrate from device * fix dismiss when deleting last visible user while having hidden users * picker visibility toggle tweaks * remove strange square from profile switcher click * dirty way to save auto accept settings on dismiss * Revert "dirty way to save auto accept settings on dismiss" This reverts commit e7b19ee8aa185b767941d12d5d4534d81f9ea6f5. * consistent animation on user picker toggle * change space between profiles * remove result * ignore result * unread badge * move to sheet * half sheet on one hand ui * fix dismiss on device migration * fix desktop connect * sun to meet other action icons * fill bullet list button * fix tap in settings to take full width * icon sizings and paddings * open settings in same sheet * apply same trick as other buttons for ligth toggle * layout * open profiles sheet large when +3 users * layout * layout * paddings * paddings * remove show progress * always small user picker * fixed height * open all actions as sheets * type, color * simpler and more effective way of avoid moving around on user select * dismiss user profiles sheet on user change * connect desktop back button remove * remove back buttons from user address view * remove porgress * header inside list * alert on auto accept unsaved changes * Cancel -> Discard * revert * fix connect to desktop * remove extra space * fix share inside multi sheet * user picker and options as separate sheet * revert showShareSheet * fix current profile and all profiles selection * change alert * update * cleanup user address * remove func * alert on unsaved changes in chat prefs * fix layout * cleanup --------- Co-authored-by: Evgeny Poberezkin * ios: fix switching profiles (#4822) * ios: different user picker layout (#4826) * ios: different user picker layout * remove section * layout, color * color * remove activeUser * fix gradient * recursive sheets * gradient padding * share sheet * layout * dismiss sheets --------- Co-authored-by: Levitating Pineapple * ios: use the same way to share from all sheets (#4829) * ios: close user picker before opening other sheets * Revert "share sheet" This reverts commit 006415582515f85fb6f634c7727e74295d4447ad. * dismiss/show via callback * Revert "ios: close user picker before opening other sheets" This reverts commit 19110398f8b566d3e925b91074c3c19d97b35f17. * ios: show alerts from sheets (#4839) * padding * remove gradient * cleanup * simplify settings * padding --------- Co-authored-by: Diogo Co-authored-by: Levitating Pineapple Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .gitignore | 1 + .../Shared/Views/Call/IncomingCallView.swift | 1 + .../Shared/Views/ChatList/ChatListView.swift | 78 +++-- .../Shared/Views/ChatList/UserPicker.swift | 327 ++++++++++-------- .../ios/Shared/Views/Helpers/ShareSheet.swift | 30 +- .../Views/Migration/MigrateFromDevice.swift | 13 +- .../RemoteAccess/ConnectDesktopView.swift | 7 - .../Views/UserSettings/PreferencesView.swift | 11 + .../Views/UserSettings/SettingsView.swift | 102 ++---- .../Views/UserSettings/UserAddressView.swift | 39 +-- .../Views/UserSettings/UserProfilesView.swift | 12 +- 11 files changed, 335 insertions(+), 286 deletions(-) diff --git a/.gitignore b/.gitignore index e3ea5d267b..645b55ec9d 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ website/package/generated* # Ignore build tool output, e.g. code coverage website/.nyc_output/ website/coverage/ +result # Ignore API documentation website/api-docs/ diff --git a/apps/ios/Shared/Views/Call/IncomingCallView.swift b/apps/ios/Shared/Views/Call/IncomingCallView.swift index 4960281d72..5479a9fada 100644 --- a/apps/ios/Shared/Views/Call/IncomingCallView.swift +++ b/apps/ios/Shared/Views/Call/IncomingCallView.swift @@ -38,6 +38,7 @@ struct IncomingCallView: View { } HStack { ProfilePreview(profileOf: invitation.contact, color: .white) + .padding(.vertical, 6) Spacer() callButton("Reject", "phone.down.fill", .red) { diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 8ad03236f1..156b8694c4 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -9,6 +9,17 @@ import SwiftUI import SimpleXChat +enum UserPickerSheet: Identifiable { + case address + case chatPreferences + case chatProfiles + case currentProfile + case useFromDesktop + case settings + + var id: Self { self } +} + struct ChatListView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @@ -18,9 +29,9 @@ struct ChatListView: View { @State private var searchText = "" @State private var searchShowingSimplexLink = false @State private var searchChatFilteredBySimplexLink: String? = nil - @State private var userPickerVisible = false - @State private var showConnectDesktop = false @State private var scrollToSearchBar = false + @State private var activeUserPickerSheet: UserPickerSheet? = nil + @State private var userPickerShown: Bool = false @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true @@ -46,21 +57,44 @@ struct ChatListView: View { ), destination: chatView ) { chatListView } - if userPickerVisible { - Rectangle().fill(.white.opacity(0.001)).onTapGesture { - withAnimation { - userPickerVisible.toggle() + } + .sheet(isPresented: $userPickerShown) { + UserPicker(activeSheet: $activeUserPickerSheet) + .sheet(item: $activeUserPickerSheet) { sheet in + if let currentUser = chatModel.currentUser { + switch sheet { + case .address: + NavigationView { + UserAddressView(shareViaProfile: currentUser.addressShared) + .navigationTitle("Public address") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + case .chatProfiles: + NavigationView { + UserProfilesView() + } + case .currentProfile: + NavigationView { + UserProfile() + .navigationTitle("Your current profile") + .modifier(ThemedBackground()) + } + case .chatPreferences: + NavigationView { + PreferencesView(profile: currentUser.profile, preferences: currentUser.fullPreferences, currentPreferences: currentUser.fullPreferences) + .navigationTitle("Your preferences") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + case .useFromDesktop: + ConnectDesktopView(viaSettings: false) + case .settings: + SettingsView(showSettings: $showSettings) + .navigationBarTitleDisplayMode(.large) + } } } - } - UserPicker( - showSettings: $showSettings, - showConnectDesktop: $showConnectDesktop, - userPickerVisible: $userPickerVisible - ) - } - .sheet(isPresented: $showConnectDesktop) { - ConnectDesktopView() } } @@ -73,7 +107,7 @@ struct ChatListView: View { .navigationBarHidden(searchMode || oneHandUI) } .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) - .onDisappear() { withAnimation { userPickerVisible = false } } + .onDisappear() { activeUserPickerSheet = nil } .refreshable { AlertManager.shared.showAlert(Alert( title: Text("Reconnect servers?"), @@ -164,7 +198,7 @@ struct ChatListView: View { let user = chatModel.currentUser ?? User.sampleData ZStack(alignment: .topTrailing) { ProfileImage(imageStr: user.image, size: 32, color: Color(uiColor: .quaternaryLabel)) - .padding(.trailing, 4) + .padding([.top, .trailing], 3) let allRead = chatModel.users .filter { u in !u.user.activeUser && !u.user.hidden } .allSatisfy { u in u.unreadCount == 0 } @@ -173,13 +207,7 @@ struct ChatListView: View { } } .onTapGesture { - if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 { - withAnimation { - userPickerVisible.toggle() - } - } else { - showSettings = true - } + userPickerShown = true } } @@ -269,7 +297,7 @@ struct ChatListView: View { } } - private func unreadBadge(_ text: Text? = Text(" "), size: CGFloat = 18) -> some View { + private func unreadBadge(size: CGFloat = 18) -> some View { Circle() .frame(width: size, height: size) .foregroundColor(theme.colors.primary) diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index 5041e093db..9d7f6bbd9c 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -8,179 +8,228 @@ import SimpleXChat struct UserPicker: View { @EnvironmentObject var m: ChatModel - @Environment(\.scenePhase) var scenePhase @EnvironmentObject var theme: AppTheme - @Binding var showSettings: Bool - @Binding var showConnectDesktop: Bool - @Binding var userPickerVisible: Bool - @State var scrollViewContentSize: CGSize = .zero - @State var disableScrolling: Bool = true - private let menuButtonHeight: CGFloat = 68 - @State var chatViewNameWidth: CGFloat = 0 - + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + @Environment(\.scenePhase) private var scenePhase: ScenePhase + @Environment(\.colorScheme) private var colorScheme: ColorScheme + @Environment(\.dismiss) private var dismiss: DismissAction + @Binding var activeSheet: UserPickerSheet? + @State private var switchingProfile = false var body: some View { - VStack { - Spacer().frame(height: 1) - VStack(spacing: 0) { - ScrollView { - ScrollViewReader { sp in - let users = m.users - .filter({ u in u.user.activeUser || !u.user.hidden }) - .sorted { u, _ in u.user.activeUser } - VStack(spacing: 0) { - ForEach(users) { u in - userView(u) - Divider() - if u.user.activeUser { Divider() } - } - } - .overlay { - GeometryReader { geo -> Color in - DispatchQueue.main.async { - scrollViewContentSize = geo.size - let scenes = UIApplication.shared.connectedScenes - if let windowScene = scenes.first as? UIWindowScene { - let layoutFrame = windowScene.windows[0].safeAreaLayoutGuide.layoutFrame - disableScrolling = scrollViewContentSize.height + menuButtonHeight + 10 < layoutFrame.height - } - } - return Color.clear - } - } - .onChange(of: userPickerVisible) { visible in - if visible, let u = users.first { - sp.scrollTo(u.id) + if #available(iOS 16.0, *) { + let v = viewBody.presentationDetents([.height(420)]) + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } + } else { + viewBody + } + } + + private var viewBody: some View { + let otherUsers = m.users.filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId } + return List { + Section(header: Text("You").foregroundColor(theme.colors.secondary)) { + if let user = m.currentUser { + openSheetOnTap(label: { + ZStack { + let v = ProfilePreview(profileOf: user) + .foregroundColor(.primary) + .padding(.leading, -8) + if #available(iOS 16.0, *) { + v + } else { + v.padding(.vertical, 4) } } + }) { + activeSheet = .currentProfile } - } - .simultaneousGesture(DragGesture(minimumDistance: disableScrolling ? 0 : 10000000)) - .frame(maxHeight: scrollViewContentSize.height) - menuButton("Use from desktop", icon: "desktopcomputer") { - showConnectDesktop = true - withAnimation { - userPickerVisible.toggle() + openSheetOnTap(title: m.userAddress == nil ? "Create public address" : "Your public address", icon: "qrcode") { + activeSheet = .address + } + + openSheetOnTap(title: "Chat preferences", icon: "switch.2") { + activeSheet = .chatPreferences } } - Divider() - menuButton("Settings", icon: "gearshape") { - showSettings = true - withAnimation { - userPickerVisible.toggle() + } + + Section { + if otherUsers.isEmpty { + openSheetOnTap(title: "Your chat profiles", icon: "person.crop.rectangle.stack") { + activeSheet = .chatProfiles + } + } else { + let v = userPickerRow(otherUsers, size: 44) + .padding(.leading, -11) + if #available(iOS 16.0, *) { + v + } else { + v.padding(.vertical, 4) + } + } + + openSheetOnTap(title: "Use from desktop", icon: "desktopcomputer") { + activeSheet = .useFromDesktop + } + + ZStack(alignment: .trailing) { + openSheetOnTap(title: "Settings", icon: "gearshape") { + activeSheet = .settings + } + Label {} icon: { + Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill") + .resizable() + .symbolRenderingMode(.monochrome) + .foregroundColor(theme.colors.secondary) + .frame(maxWidth: 20, maxHeight: 20) + } + .onTapGesture { + if (colorScheme == .light) { + ThemeManager.applyTheme(systemDarkThemeDefault.get()) + } else { + ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName) + } + } + .onLongPressGesture { + ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) } } } } - .clipShape(RoundedRectangle(cornerRadius: 16)) - .background( - Rectangle() - .fill(theme.colors.surface) - .cornerRadius(16) - .shadow(color: .black.opacity(0.12), radius: 24, x: 0, y: 0) - ) - .onPreferenceChange(DetermineWidth.Key.self) { chatViewNameWidth = $0 } - .frame(maxWidth: chatViewNameWidth > 0 ? min(300, chatViewNameWidth + 130) : 300) - .padding(8) - .opacity(userPickerVisible ? 1.0 : 0.0) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .onAppear { - // This check prevents the call of listUsers after the app is suspended, and the database is closed. - if case .active = scenePhase { - Task { - do { - let users = try await listUsersAsync() - await MainActor.run { m.users = users } - } catch { - logger.error("Error loading users \(responseError(error))") - } - } - } - } - } - - private func userView(_ u: UserInfo) -> some View { - let user = u.user - return Button(action: { - if user.activeUser { - showSettings = true - withAnimation { - userPickerVisible.toggle() - } - } else { + // This check prevents the call of listUsers after the app is suspended, and the database is closed. + if case .active = scenePhase { Task { do { - try await changeActiveUserAsync_(user.userId, viewPwd: nil) - await MainActor.run { userPickerVisible = false } + let users = try await listUsersAsync() + await MainActor.run { m.users = users } } catch { - await MainActor.run { - AlertManager.shared.showAlertMsg( - title: "Error switching profile!", - message: "Error: \(responseError(error))" - ) - } + logger.error("Error loading users \(responseError(error))") } } } - }, label: { - HStack(spacing: 0) { - ProfileImage(imageStr: user.image, size: 44, color: Color(uiColor: .tertiarySystemFill)) - .padding(.trailing, 12) - Text(user.chatViewName) - .fontWeight(user.activeUser ? .medium : .regular) - .foregroundColor(theme.colors.onBackground) - .overlay(DetermineWidth()) - Spacer() - if user.activeUser { - Image(systemName: "checkmark") - } else if u.unreadCount > 0 { - unreadCounter(u.unreadCount, color: user.showNtfs ? theme.colors.primary : theme.colors.secondary) - } else if !user.showNtfs { - Image(systemName: "speaker.slash") + } + .modifier(ThemedBackground(grouped: true)) + .disabled(switchingProfile) + } + + private func userPickerRow(_ users: [UserInfo], size: CGFloat) -> some View { + HStack(spacing: 6) { + let s = ScrollView(.horizontal) { + HStack(spacing: 27) { + ForEach(users) { u in + if !u.user.hidden && u.user.userId != m.currentUser?.userId { + userView(u, size: size) + } + } + } + .padding(.leading, 4) + .padding(.trailing, 22) + } + ZStack(alignment: .trailing) { + if #available(iOS 16.0, *) { + s.scrollIndicators(.hidden) + } else { + s + } + LinearGradient( + colors: [.clear, .black], + startPoint: .leading, + endPoint: .trailing + ) + .frame(width: size, height: size + 3) + .blendMode(.destinationOut) + .allowsHitTesting(false) + } + .compositingGroup() + .padding(.top, -3) // to fit unread badge + Spacer() + Image(systemName: "chevron.right") + .foregroundColor(theme.colors.secondary) + .padding(.trailing, 4) + .onTapGesture { + activeSheet = .chatProfiles + } + } + } + + private func userView(_ u: UserInfo, size: CGFloat) -> some View { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) + .padding([.top, .trailing], 3) + if (u.unreadCount > 0) { + unreadBadge(u) + } + } + .frame(width: size) + .onTapGesture { + switchingProfile = true + Task { + do { + try await changeActiveUserAsync_(u.user.userId, viewPwd: nil) + await MainActor.run { + switchingProfile = false + dismiss() + } + } catch { + await MainActor.run { + switchingProfile = false + AlertManager.shared.showAlertMsg( + title: "Error switching profile!", + message: "Error: \(responseError(error))" + ) + } } } - .padding(.trailing) - .padding([.leading, .vertical], 12) - }) - .buttonStyle(PressedButtonStyle(defaultColor: theme.colors.surface, pressedColor: Color(uiColor: .secondarySystemFill))) + } } - - private func menuButton(_ title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View { - Button(action: action) { - HStack(spacing: 0) { - Text(title) - .overlay(DetermineWidth()) - Spacer() - Image(systemName: icon) + + private func openSheetOnTap(title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View { + openSheetOnTap(label: { + ZStack(alignment: .leading) { + Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center) .symbolRenderingMode(.monochrome) .foregroundColor(theme.colors.secondary) + Text(title) + .foregroundColor(.primary) + .padding(.leading, 36) } - .padding(.horizontal) - .padding(.vertical, 22) - .frame(height: menuButtonHeight) - } - .buttonStyle(PressedButtonStyle(defaultColor: theme.colors.surface, pressedColor: Color(uiColor: .secondarySystemFill))) + }, action: action) + } + + private func openSheetOnTap(label: () -> V, action: @escaping () -> Void) -> some View { + Button(action: action, label: label) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + } + + private func unreadBadge(_ u: UserInfo) -> some View { + let size = dynamicSize(userFont).chatInfoSize + return unreadCountText(u.unreadCount) + .font(userFont <= .xxxLarge ? .caption : .caption2) + .foregroundColor(.white) + .padding(.horizontal, dynamicSize(userFont).unreadPadding) + .frame(minWidth: size, minHeight: size) + .background(u.user.showNtfs ? theme.colors.primary : theme.colors.secondary) + .cornerRadius(dynamicSize(userFont).unreadCorner) } -} - -private func unreadCounter(_ unread: Int, color: Color) -> some View { - unreadCountText(unread) - .font(.caption) - .foregroundColor(.white) - .padding(.horizontal, 4) - .frame(minWidth: 18, minHeight: 18) - .background(color) - .cornerRadius(10) } struct UserPicker_Previews: PreviewProvider { static var previews: some View { + @State var activeSheet: UserPickerSheet? + let m = ChatModel() m.users = [UserInfo.sampleData, UserInfo.sampleData] return UserPicker( - showSettings: Binding.constant(false), - showConnectDesktop: Binding.constant(false), - userPickerVisible: Binding.constant(true) + activeSheet: $activeSheet ) .environmentObject(m) } diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 88e4bffe9f..7b80dd1544 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -8,15 +8,22 @@ import SwiftUI -func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { +func getTopViewController() -> UIViewController? { let keyWindowScene = UIApplication.shared.connectedScenes.first { $0.activationState == .foregroundActive } as? UIWindowScene if let keyWindow = keyWindowScene?.windows.filter(\.isKeyWindow).first, - let rootViewController = keyWindow.rootViewController { + let rootViewController = keyWindow.rootViewController { // Find the top-most presented view controller var topController = rootViewController while let presentedViewController = topController.presentedViewController { topController = presentedViewController } + return topController + } + return nil +} + +func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { + if let topController = getTopViewController() { let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil) if let completed = completed { activityViewController.completionWithItemsHandler = { _, _, _, _ in @@ -26,3 +33,22 @@ func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { topController.present(activityViewController, animated: true) } } + +func showAlert( + title: String, + message: String? = nil, + buttonTitle: String, + buttonAction: @escaping () -> Void, + cancelButton: Bool +) -> Void { + if let topController = getTopViewController() { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: buttonTitle, style: .default) { _ in + buttonAction() + }) + if cancelButton { + alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel)) + } + topController.present(alert, animated: true) + } +} diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 9cc229ba80..4b9e001906 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -56,8 +56,6 @@ private enum MigrateFromDeviceViewAlert: Identifiable { struct MigrateFromDevice: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @Environment(\.dismiss) var dismiss: DismissAction - @Binding var showSettings: Bool @Binding var showProgressOnSettings: Bool @State private var migrationState: MigrationFromState = .chatStopInProgress @State private var useKeychain = storeDBPassphraseGroupDefault.get() @@ -106,9 +104,6 @@ struct MigrateFromDevice: View { finishedView(chatDeletion) } } - .modifier(BackButton(label: "Back", disabled: $backDisabled) { - dismiss() - }) .onChange(of: migrationState) { state in backDisabled = switch migrationState { case .chatStopInProgress, .archiving, .linkShown, .finished: true @@ -590,7 +585,7 @@ struct MigrateFromDevice: View { } catch let error { fatalError("Error starting chat \(responseError(error))") } - showSettings = false + dismissAllSheets(animated: true) } } catch let error { alert = .error(title: "Error deleting database", error: responseError(error)) @@ -613,9 +608,7 @@ struct MigrateFromDevice: View { } // Hide settings anyway if chatDbStatus is not ok, probably passphrase needs to be entered if dismiss || m.chatDbStatus != .ok { - await MainActor.run { - showSettings = false - } + dismissAllSheets(animated: true) } } @@ -767,6 +760,6 @@ private class MigrationChatReceiver { struct MigrateFromDevice_Previews: PreviewProvider { static var previews: some View { - MigrateFromDevice(showSettings: Binding.constant(true), showProgressOnSettings: Binding.constant(false)) + MigrateFromDevice(showProgressOnSettings: Binding.constant(false)) } } diff --git a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift index be063334d3..b1f68c09f4 100644 --- a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift +++ b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift @@ -59,13 +59,6 @@ struct ConnectDesktopView: View { var body: some View { if viaSettings { viewBody - .modifier(BackButton(label: "Back", disabled: Binding.constant(false)) { - if m.activeRemoteCtrl { - alert = .disconnectDesktop(action: .back) - } else { - dismiss() - } - }) } else { NavigationView { viewBody diff --git a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift index 0c10da2103..bd8171623a 100644 --- a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift +++ b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift @@ -32,6 +32,17 @@ struct PreferencesView: View { .disabled(currentPreferences == preferences) } } + .onDisappear { + if currentPreferences != preferences { + showAlert( + title: NSLocalizedString("Your chat preferences", comment: "alert title"), + message: NSLocalizedString("Chat preferences were changed.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: savePreferences, + cancelButton: true + ) + } + } } private func featureSection(_ feature: ChatFeature, _ allowFeature: Binding) -> some View { diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index d9c83803dd..463ac4ae07 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -262,7 +262,9 @@ struct SettingsView: View { var body: some View { ZStack { - settingsView() + NavigationView { + settingsView() + } if showProgress { progressView() } @@ -274,63 +276,7 @@ struct SettingsView: View { @ViewBuilder func settingsView() -> some View { let user = chatModel.currentUser - NavigationView { List { - Section(header: Text("You").foregroundColor(theme.colors.secondary)) { - if let user = user { - NavigationLink { - UserProfile() - .navigationTitle("Your current profile") - .modifier(ThemedBackground()) - } label: { - ProfilePreview(profileOf: user) - .padding(.leading, -8) - } - } - - NavigationLink { - UserProfilesView(showSettings: $showSettings) - } label: { - settingsRow("person.crop.rectangle.stack", color: theme.colors.secondary) { Text("Your chat profiles") } - } - - - if let user = user { - NavigationLink { - UserAddressView(shareViaProfile: user.addressShared) - .navigationTitle("SimpleX address") - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("qrcode", color: theme.colors.secondary) { Text("Your SimpleX address") } - } - - NavigationLink { - PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences) - .navigationTitle("Your preferences") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("switch.2", color: theme.colors.secondary) { Text("Chat preferences") } - } - } - - NavigationLink { - ConnectDesktopView(viaSettings: true) - } label: { - settingsRow("desktopcomputer", color: theme.colors.secondary) { Text("Use from desktop") } - } - - NavigationLink { - MigrateFromDevice(showSettings: $showSettings, showProgressOnSettings: $showProgress) - .navigationTitle("Migrate device") - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } - } - } - .disabled(chatModel.chatRunning != true) - Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) { NavigationLink { NotificationsView() @@ -381,10 +327,20 @@ struct SettingsView: View { } .disabled(chatModel.chatRunning != true) } - - chatDatabaseRow() } + Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) { + chatDatabaseRow() + NavigationLink { + MigrateFromDevice(showProgressOnSettings: $showProgress) + .navigationTitle("Migrate device") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } + } + } + Section(header: Text("Help").foregroundColor(theme.colors.secondary)) { if let user = user { NavigationLink { @@ -462,11 +418,10 @@ struct SettingsView: View { } .navigationTitle("Your settings") .modifier(ThemedBackground(grouped: true)) - } - .onDisappear { - chatModel.showingTerminal = false - chatModel.terminalItems = [] - } + .onDisappear { + chatModel.showingTerminal = false + chatModel.terminalItems = [] + } } private func chatDatabaseRow() -> some View { @@ -549,17 +504,18 @@ struct ProfilePreview: View { HStack { ProfileImage(imageStr: profileOf.image, size: 44, color: color) .padding(.trailing, 6) - .padding(.vertical, 6) - VStack(alignment: .leading) { - Text(profileOf.displayName) - .fontWeight(.bold) - .font(.title2) - if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { - Text(profileOf.fullName) - } - } + profileName().lineLimit(1) } } + + private func profileName() -> Text { + var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2) + if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { + t = t + Text(" (" + profileOf.fullName + ")") +// .font(.callout) + } + return t + } } struct SettingsView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index fa95c51d36..7efc8a46f5 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -14,7 +14,6 @@ struct UserAddressView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject private var chatModel: ChatModel @EnvironmentObject var theme: AppTheme - @State var viaCreateLinkView = false @State var shareViaProfile = false @State private var aas = AutoAcceptState() @State private var savedAAS = AutoAcceptState() @@ -22,7 +21,6 @@ struct UserAddressView: View { @State private var showMailView = false @State private var mailViewResult: Result? = nil @State private var alert: UserAddressAlert? - @State private var showSaveDialogue = false @State private var progressIndicator = false @FocusState private var keyboardVisible: Bool @@ -44,26 +42,19 @@ struct UserAddressView: View { var body: some View { ZStack { - if viaCreateLinkView { - userAddressScrollView() - } else { - userAddressScrollView() - .modifier(BackButton(disabled: Binding.constant(false)) { - if savedAAS == aas { - dismiss() - } else { - keyboardVisible = false - showSaveDialogue = true - } - }) - .confirmationDialog("Save settings?", isPresented: $showSaveDialogue) { - Button("Save auto-accept settings") { - saveAAS() - dismiss() - } - Button("Exit without saving") { dismiss() } + userAddressScrollView() + .onDisappear { + if savedAAS != aas { + showAlert( + title: NSLocalizedString("Auto-accept settings", comment: "alert title"), + message: NSLocalizedString("Settings were changed.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: saveAAS, + cancelButton: true + ) } - } + } + if progressIndicator { ZStack { if chatModel.userAddress != nil { @@ -238,7 +229,7 @@ struct UserAddressView: View { } } } label: { - Label("Create SimpleX address", systemImage: "qrcode") + Label("Create public address", systemImage: "qrcode") } } @@ -342,7 +333,7 @@ struct UserAddressView: View { } } } - + private struct AutoAcceptState: Equatable { var enable = false var incognito = false @@ -447,6 +438,8 @@ struct UserAddressView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.userAddress = UserContactLink(connReqContact: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D") + + return Group { UserAddressView() .environmentObject(chatModel) diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 06342db529..330ce56e0b 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -9,7 +9,6 @@ import SimpleXChat struct UserProfilesView: View { @EnvironmentObject private var m: ChatModel @EnvironmentObject private var theme: AppTheme - @Binding var showSettings: Bool @Environment(\.editMode) private var editMode @AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true @AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true @@ -96,8 +95,7 @@ struct UserProfilesView: View { } label: { Label("Add profile", systemImage: "plus") } - .frame(height: 44) - .padding(.vertical, 4) + .frame(height: 38) } } footer: { Text("Tap to activate profile.") @@ -285,7 +283,7 @@ struct UserProfilesView: View { await MainActor.run { onboardingStageDefault.set(.step1_SimpleXInfo) m.onboardingStage = .step1_SimpleXInfo - showSettings = false + dismissAllSheets() } } } else { @@ -308,14 +306,14 @@ struct UserProfilesView: View { Task { do { try await changeActiveUserAsync_(user.userId, viewPwd: userViewPassword(user)) + dismissAllSheets() } catch { await MainActor.run { alert = .activateUserError(error: responseError(error)) } } } } label: { HStack { - ProfileImage(imageStr: user.image, size: 44) - .padding(.vertical, 4) + ProfileImage(imageStr: user.image, size: 38) .padding(.trailing, 12) Text(user.chatViewName) Spacer() @@ -415,6 +413,6 @@ public func correctPassword(_ user: User, _ pwd: String) -> Bool { struct UserProfilesView_Previews: PreviewProvider { static var previews: some View { - UserProfilesView(showSettings: Binding.constant(true)) + UserProfilesView() } } From 87c55dbbf7196cb932ea533a5447f78ef7dd60be Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:08:16 +0400 Subject: [PATCH 106/440] multiplatform: fix delete messages alert (#4862) --- .../kotlin/chat/simplex/common/views/chat/ChatView.kt | 8 +++++++- .../chat/simplex/common/views/chat/item/ChatItemView.kt | 4 ++-- .../common/src/commonMain/resources/MR/base/strings.xml | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 92c023c518..690ba89ef9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -105,6 +105,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> { val perChatTheme = remember(chatInfo, CurrentColors.value.base) { if (chatInfo is ChatInfo.Direct) chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null } val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null + val fullDeleteAllowed = remember(chatInfo) { chatInfo.featureEnabled(ChatFeature.FullDelete) } SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) { ChatLayout( remoteHostId = remoteHostId, @@ -142,10 +143,15 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - chatInfo = chatInfo, deleteItems = { canDeleteForAll -> val itemIds = selectedChatItems.value + val questionText = + if (!canDeleteForAll || fullDeleteAllowed || chatInfo is ChatInfo.Local) + generalGetString(MR.strings.delete_messages_cannot_be_undone_warning) + else + generalGetString(MR.strings.delete_messages_mark_deleted_warning) if (itemIds != null) { deleteMessagesAlertDialog( itemIds.sorted(), - generalGetString(if (itemIds.size == 1) MR.strings.delete_message_mark_deleted_warning else MR.strings.delete_messages_mark_deleted_warning), + questionText = questionText, forAll = canDeleteForAll, deleteMessages = { ids, forAll -> deleteMessages(chatRh, chatInfo, ids, forAll, moderate = false) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 29717e3ecf..516e47e7ed 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -135,7 +135,7 @@ fun ChatItemView( } fun deleteMessageQuestionText(): String { - return if (!sent || fullDeleteAllowed) { + return if (!sent || fullDeleteAllowed || cInfo is ChatInfo.Local) { generalGetString(MR.strings.delete_message_cannot_be_undone_warning) } else { generalGetString(MR.strings.delete_message_mark_deleted_warning) @@ -637,7 +637,7 @@ fun DeleteItemAction( } deleteMessagesAlertDialog( itemIds, - generalGetString(if (itemIds.size == 1) MR.strings.delete_message_mark_deleted_warning else MR.strings.delete_messages_mark_deleted_warning), + generalGetString(MR.strings.delete_messages_cannot_be_undone_warning), forAll = false, deleteMessages = { ids, _ -> deleteMessages(ids) } ) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index b8bbeb5c53..57a94bdf44 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -318,6 +318,7 @@ Delete message? Delete %d messages? Message will be deleted - this cannot be undone! + Messages will be deleted - this cannot be undone! Message will be marked for deletion. The recipient(s) will be able to reveal this message. Messages will be marked for deletion. The recipient(s) will be able to reveal these messages. Delete member message? From 3ea82794514b1d9a76f909f45ecab447d858e436 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:08:16 +0400 Subject: [PATCH 107/440] multiplatform: fix delete messages alert (#4862) --- .../kotlin/chat/simplex/common/views/chat/ChatView.kt | 8 +++++++- .../chat/simplex/common/views/chat/item/ChatItemView.kt | 4 ++-- .../common/src/commonMain/resources/MR/base/strings.xml | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 92c023c518..690ba89ef9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -105,6 +105,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> { val perChatTheme = remember(chatInfo, CurrentColors.value.base) { if (chatInfo is ChatInfo.Direct) chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null } val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null + val fullDeleteAllowed = remember(chatInfo) { chatInfo.featureEnabled(ChatFeature.FullDelete) } SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) { ChatLayout( remoteHostId = remoteHostId, @@ -142,10 +143,15 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - chatInfo = chatInfo, deleteItems = { canDeleteForAll -> val itemIds = selectedChatItems.value + val questionText = + if (!canDeleteForAll || fullDeleteAllowed || chatInfo is ChatInfo.Local) + generalGetString(MR.strings.delete_messages_cannot_be_undone_warning) + else + generalGetString(MR.strings.delete_messages_mark_deleted_warning) if (itemIds != null) { deleteMessagesAlertDialog( itemIds.sorted(), - generalGetString(if (itemIds.size == 1) MR.strings.delete_message_mark_deleted_warning else MR.strings.delete_messages_mark_deleted_warning), + questionText = questionText, forAll = canDeleteForAll, deleteMessages = { ids, forAll -> deleteMessages(chatRh, chatInfo, ids, forAll, moderate = false) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 29717e3ecf..516e47e7ed 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -135,7 +135,7 @@ fun ChatItemView( } fun deleteMessageQuestionText(): String { - return if (!sent || fullDeleteAllowed) { + return if (!sent || fullDeleteAllowed || cInfo is ChatInfo.Local) { generalGetString(MR.strings.delete_message_cannot_be_undone_warning) } else { generalGetString(MR.strings.delete_message_mark_deleted_warning) @@ -637,7 +637,7 @@ fun DeleteItemAction( } deleteMessagesAlertDialog( itemIds, - generalGetString(if (itemIds.size == 1) MR.strings.delete_message_mark_deleted_warning else MR.strings.delete_messages_mark_deleted_warning), + generalGetString(MR.strings.delete_messages_cannot_be_undone_warning), forAll = false, deleteMessages = { ids, _ -> deleteMessages(ids) } ) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 0c5a4eb749..b81f733cd1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -318,6 +318,7 @@ Delete message? Delete %d messages? Message will be deleted - this cannot be undone! + Messages will be deleted - this cannot be undone! Message will be marked for deletion. The recipient(s) will be able to reveal this message. Messages will be marked for deletion. The recipient(s) will be able to reveal these messages. Delete member message? From acf2f1fbbe2522111122e566d9ca533a72b73726 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 11 Sep 2024 15:51:28 +0100 Subject: [PATCH 108/440] android, desktop: new user picker (#4796) * user picker as modal * android dirty layout * color mode switcher * close picker on desktop opening modals * cleanup * remote hosts working * icon buttons * profile picker modal for external shares * remove stroke * color changes * add unread badge to users row * chat database settings section * safe remove of you settings section * picker should now open for single user * remove create profile from settings * paddings * handle big names * fonts and align * simple animations and shadow * address messaging * active is grey * padding * hide non active devices from pills * picker positioning * pills order * change view all profiles icon * bigger space between profiles * hosts ordering and fixes * device pill in app bar * simplex address -> public * better switch of opacity bg * create public address * font match * add icon for dark mode * padding * profile name as header * h2 is too big * icon colors * icons * settings as modal * center settings * fix use from desktop * remove logs * bar colors * remove drawer unused code * animate shading * fade in fade out * add system mode toast * shading colors * stop pushing shade up * same button as ios for opening all profiles * simplify nav bar color set * broken transition change * color mix * gradient and horizontal scroll * separate title * align avatars to top * picker should always remain open * use chevron icon to see all profiles * improvements on status and nav color set * best case bars switching working * change bar and shading on theme change * remove unused var * reset navbar colors on navigate * updated icon color * protect android calls * desktop menu matching size of right side modals * remove shading from desktop * close user picker on settings click in desktop * bigger profile image smaller gap to name * fix spacer for row scroll on android * smaller profile name * remove unused code * small refactor * unused * move desktop/mobile connection * close drawer on swipe down 30% * progress dump on new android design * paddings in scroller * gradient * android paddings * split inactive user picker between platforms * move your chat profiles inside android specific * always show your chat profiles in desktop * fix profile creation in desktop * remove unused var * update android space between badges * initial desktop design * center android icons with avatar * centered avatars * unread badge * extra space in the end of user list for android * aligned paddings on desktop * desktop paddings * paddings * remove you * unread badge same style as chatlist * use bedtime moon for dark mode * chevron same size as sun/moon * chevron and gradient * paddings * split android and desktop scaffold for picker * move bars logic to android * remove android check * more android checks * initial version of swipable modal * muted as grey * unused * close drawer on 3/4 * better close control * make all animations match * move shadow with offset * always close pciker on selection * animated float doing nothing * sync animation * animation using single float * fixed warnings * better state update * fix scrim color * better handling of picker closure on desktop * landscape mode * intentation * rename UserPickerScaffold * hide shadow when picker not open * reset inactive user scroll position on pick * unused class * left panel after new menu can be without padding * small changes * make ActiveProfilePicker reusable to reduce code duplication * make picker scrollable * refactor * refactor and fix instant reload of profiles * refactor * icon sizes * returned back ability to scroll to the picker on Android * setting system theme on desktop's right click * box * refactor * picker pill * fix desktop shadow * small change * hiding keyboard when opening picker * state specifying --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../main/java/chat/simplex/app/SimplexApp.kt | 27 +- .../views/chatlist/UserPicker.android.kt | 222 +++++++ .../kotlin/chat/simplex/common/App.kt | 97 ++- .../chat/simplex/common/platform/Platform.kt | 4 +- .../chat/simplex/common/views/TerminalView.kt | 1 - .../simplex/common/views/chat/ChatView.kt | 1 - .../common/views/chatlist/ChatListView.kt | 57 +- .../common/views/chatlist/ShareListView.kt | 51 +- .../common/views/chatlist/UserPicker.kt | 614 +++++++++++------- .../common/views/helpers/AnimationUtils.kt | 2 + .../common/views/helpers/CloseSheetBar.kt | 11 +- .../simplex/common/views/helpers/Utils.kt | 8 +- .../common/views/newchat/NewChatView.kt | 145 +++-- .../common/views/usersettings/Appearance.kt | 34 + .../usersettings/SetDeliveryReceiptsView.kt | 5 +- .../common/views/usersettings/SettingsView.kt | 57 +- .../views/usersettings/UserAddressView.kt | 4 +- .../views/usersettings/UserProfileView.kt | 1 + .../views/usersettings/UserProfilesView.kt | 9 +- .../commonMain/resources/MR/base/strings.xml | 8 + .../resources/MR/images/ic_add_group.svg | 1 + .../resources/MR/images/ic_bedtime_moon.svg | 1 + .../views/chatlist/UserPicker.desktop.kt | 86 +++ 23 files changed, 945 insertions(+), 501 deletions(-) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_group.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bedtime_moon.svg create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index e0bd2b0861..49edde55bb 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -7,6 +7,7 @@ import chat.simplex.common.platform.Log import android.content.Intent import android.content.pm.ActivityInfo import android.os.* +import androidx.compose.animation.core.* import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.graphics.Color @@ -259,7 +260,6 @@ class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider override fun androidSetNightModeIfSupported() { if (Build.VERSION.SDK_INT < 31) return - val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM_THEME_NAME) { null } else { @@ -274,6 +274,31 @@ class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider uiModeManager.setApplicationNightMode(mode) } + override fun androidSetDrawerStatusAndNavBarColor( + isLight: Boolean, + drawerShadingColor: Color, + toolbarOnTop: Boolean, + navBarColor: Color, + ) { + val window = mainActivity.get()?.window ?: return + + @Suppress("DEPRECATION") + val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView) + // Blend status bar color to the animated color + val colors = CurrentColors.value.colors + val baseBackgroundColor = if (toolbarOnTop) colors.background.mixWith(colors.onBackground, 0.97f) else colors.background + window.statusBarColor = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb() + val navBar = navBarColor.toArgb() + + if (window.navigationBarColor != navBar) { + window.navigationBarColor = navBar + } + + if (windowInsetController?.isAppearanceLightNavigationBars != isLight) { + windowInsetController?.isAppearanceLightNavigationBars = isLight + } + } + override fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) { val window = mainActivity.get()?.window ?: return @Suppress("DEPRECATION") diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt new file mode 100644 index 0000000000..6c16a75874 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt @@ -0,0 +1,222 @@ +package chat.simplex.common.views.chatlist + +import SectionItemView +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.DrawerDefaults.ScrimOpacity +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.* +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.* +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.User +import chat.simplex.common.model.UserInfo +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow + +@Composable +actual fun UserPickerInactiveUsersSection( + users: List, + stopped: Boolean, + onShowAllProfilesClicked: () -> Unit, + onUserClicked: (user: User) -> Unit, +) { + val scrollState = rememberScrollState() + + if (users.isNotEmpty()) { + SectionItemView( + padding = PaddingValues( + start = 16.dp, + top = if (windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL else DEFAULT_PADDING_HALF, + bottom = DEFAULT_PADDING_HALF), + disabled = stopped + ) { + Box { + Row( + modifier = Modifier.padding(end = DEFAULT_PADDING + 30.dp).horizontalScroll(scrollState) + ) { + users.forEach { u -> + UserPickerInactiveUserBadge(u, stopped) { + onUserClicked(it) + withBGApi { + delay(500) + scrollState.scrollTo(0) + } + } + Spacer(Modifier.width(20.dp)) + } + Spacer(Modifier.width(60.dp)) + } + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier + .fillMaxWidth() + .padding(end = DEFAULT_PADDING + 30.dp) + .height(60.dp) + ) { + Canvas(modifier = Modifier.size(60.dp)) { + drawRect( + brush = Brush.horizontalGradient( + colors = listOf( + Color.Transparent, + CurrentColors.value.colors.surface, + ) + ), + ) + } + } + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(60.dp) + .fillMaxWidth() + .padding(end = DEFAULT_PADDING) + ) { + IconButton( + onClick = onShowAllProfilesClicked, + enabled = !stopped + ) { + Icon( + painterResource(MR.images.ic_chevron_right), + stringResource(MR.strings.your_chat_profiles), + tint = MaterialTheme.colors.secondary, + modifier = Modifier.size(34.dp) + ) + } + } + } + } + } else { + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + stringResource(MR.strings.your_chat_profiles), + onShowAllProfilesClicked + ) + } +} + +private fun calculateFraction(pos: Float) = + (pos / 1f).coerceIn(0f, 1f) + +@Composable +actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow, content: @Composable () -> Unit) { + val pickerIsVisible = pickerState.collectAsState().value.isVisible() + val dismissState = rememberDismissState(initialValue = if (pickerIsVisible) DismissValue.Default else DismissValue.DismissedToEnd) { + if (it == DismissValue.DismissedToEnd && pickerState.value.isVisible()) { + pickerState.value = AnimatedViewState.HIDING + } + true + } + val height = remember { mutableIntStateOf(0) } + val heightValue = height.intValue + val clickableModifier = if (pickerIsVisible) { + Modifier.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { pickerState.value = AnimatedViewState.HIDING }) + } else { + Modifier + } + Box( + Modifier + .fillMaxSize() + .then(clickableModifier) + .drawBehind { + val pos = when { + dismissState.progress.from == DismissValue.Default && dismissState.progress.to == DismissValue.Default -> 1f + dismissState.progress.from == DismissValue.DismissedToEnd && dismissState.progress.to == DismissValue.DismissedToEnd -> 0f + dismissState.progress.to == DismissValue.Default -> dismissState.progress.fraction + else -> 1 - dismissState.progress.fraction + } + val colors = CurrentColors.value.colors + val resultingColor = if (colors.isLight) colors.onSurface.copy(alpha = ScrimOpacity) else Color.Black.copy(0.64f) + val adjustedAlpha = resultingColor.alpha * calculateFraction(pos = pos) + val shadingColor = resultingColor.copy(alpha = adjustedAlpha) + + if (pickerState.value.isVisible()) { + platform.androidSetDrawerStatusAndNavBarColor( + isLight = colors.isLight, + drawerShadingColor = shadingColor, + toolbarOnTop = !appPrefs.oneHandUI.get(), + navBarColor = colors.surface + ) + } else if (ModalManager.start.modalCount.value == 0) { + platform.androidSetDrawerStatusAndNavBarColor( + isLight = colors.isLight, + drawerShadingColor = shadingColor, + toolbarOnTop = !appPrefs.oneHandUI.get(), + navBarColor = (if (appPrefs.oneHandUI.get() && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { + colors.background.mixWith(CurrentColors.value.colors.onBackground, 0.97f) + } else { + colors.background + }) + ) + } + drawRect( + if (pos != 0f) resultingColor else Color.Transparent, + alpha = calculateFraction(pos = pos) + ) + } + .graphicsLayer { + if (heightValue == 0) { + alpha = 0f + } + translationY = dismissState.offset.value + }, + contentAlignment = Alignment.BottomCenter + ) { + Box( + Modifier.onSizeChanged { height.intValue = it.height } + ) { + KeyChangeEffect(pickerIsVisible) { + if (pickerState.value.isVisible()) { + try { + dismissState.animateTo(DismissValue.Default, userPickerAnimSpec()) + } catch (e: CancellationException) { + Log.e(TAG, "Cancelled animateTo: ${e.stackTraceToString()}") + pickerState.value = AnimatedViewState.GONE + } + } else { + try { + dismissState.animateTo(DismissValue.DismissedToEnd, userPickerAnimSpec()) + } catch (e: CancellationException) { + Log.e(TAG, "Cancelled animateTo2: ${e.stackTraceToString()}") + pickerState.value = AnimatedViewState.VISIBLE + } + } + } + val draggableModifier = if (height.intValue != 0) + Modifier.draggableBottomDrawerModifier( + state = dismissState, + swipeDistance = height.intValue.toFloat(), + ) + else Modifier + Box(draggableModifier.then(modifier)) { + content() + } + } + } +} + +private fun Modifier.draggableBottomDrawerModifier( + state: DismissState, + swipeDistance: Float, +): Modifier = this.swipeable( + state = state, + anchors = mapOf(0f to DismissValue.Default, swipeDistance to DismissValue.DismissedToEnd), + thresholds = { _, _ -> FractionalThreshold(0.3f) }, + orientation = Orientation.Vertical, + resistance = null +) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 3cba89922d..b95aed45d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -6,14 +6,11 @@ import androidx.compose.animation.core.Animatable import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* -import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -42,12 +39,6 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlin.math.sqrt - -data class SettingsViewState( - val userPickerState: MutableStateFlow, - val scaffoldState: ScaffoldState -) @Composable fun AppScreen() { @@ -145,13 +136,11 @@ fun MainScreen() { userPickerState.value = AnimatedViewState.VISIBLE } } - val scaffoldState = rememberScaffoldState() - val settingsState = remember { SettingsViewState(userPickerState, scaffoldState) } SetupClipboardListener() if (appPlatform.isAndroid) { - AndroidScreen(settingsState) + AndroidScreen(userPickerState) } else { - DesktopScreen(settingsState) + DesktopScreen(userPickerState) } } } @@ -249,7 +238,7 @@ fun MainScreen() { val ANDROID_CALL_TOP_PADDING = 40.dp @Composable -fun AndroidScreen(settingsState: SettingsViewState) { +fun AndroidScreen(userPickerState: MutableStateFlow) { BoxWithConstraints { val call = remember { chatModel.activeCall} .value val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted @@ -262,7 +251,7 @@ fun AndroidScreen(settingsState: SettingsViewState) { } .padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp) ) { - StartPartOfScreen(settingsState) + StartPartOfScreen(userPickerState) } val scope = rememberCoroutineScope() val onComposed: suspend (chatId: String?) -> Unit = { chatId -> @@ -318,15 +307,15 @@ fun AndroidScreen(settingsState: SettingsViewState) { } @Composable -fun StartPartOfScreen(settingsState: SettingsViewState) { +fun StartPartOfScreen(userPickerState: MutableStateFlow) { if (chatModel.setDeliveryReceipts.value) { SetDeliveryReceiptsView(chatModel) } else { val stopped = chatModel.chatRunning.value == false if (chatModel.sharedContent.value == null) - ChatListView(chatModel, settingsState, AppLock::setPerformLA, stopped) + ChatListView(chatModel, userPickerState, AppLock::setPerformLA, stopped) else - ShareListView(chatModel, settingsState, stopped) + ShareListView(chatModel, stopped) } } @@ -367,49 +356,41 @@ fun EndPartOfScreen() { } @Composable -fun DesktopScreen(settingsState: SettingsViewState) { - Box { - // 56.dp is a size of unused space of settings drawer - Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier + 56.dp)) { - StartPartOfScreen(settingsState) - } - Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { - ModalManager.start.showInView() - SwitchingUsersView() - } - Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { - Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) { - CenterPartOfScreen() - } - if (ModalManager.end.hasModalsOpen()) { - VerticalDivider() - } - Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { - EndPartOfScreen() - } - } - val (userPickerState, scaffoldState ) = settingsState - val scope = rememberCoroutineScope() - if (scaffoldState.drawerState.isOpen || (ModalManager.start.hasModalsOpen && !ModalManager.center.hasModalsOpen)) { - Box( - Modifier - .fillMaxSize() - .padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { - ModalManager.start.closeModals() - scope.launch { settingsState.scaffoldState.drawerState.close() } - }) - ) - } - VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) +fun DesktopScreen(userPickerState: MutableStateFlow) { + Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { + StartPartOfScreen(userPickerState) tryOrShowError("UserPicker", error = {}) { - UserPicker(chatModel, userPickerState) { - scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() } - userPickerState.value = AnimatedViewState.GONE - } + UserPicker(chatModel, userPickerState, setPerformLA = AppLock::setPerformLA) } - ModalManager.fullscreen.showInView() } + Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { + ModalManager.start.showInView() + SwitchingUsersView() + } + Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { + Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) { + CenterPartOfScreen() + } + if (ModalManager.end.hasModalsOpen()) { + VerticalDivider() + } + Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { + EndPartOfScreen() + } + } + if (userPickerState.collectAsState().value.isVisible() || (ModalManager.start.hasModalsOpen && !ModalManager.center.hasModalsOpen)) { + Box( + Modifier + .fillMaxSize() + .padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) + .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { + ModalManager.start.closeModals() + userPickerState.value = AnimatedViewState.HIDING + }) + ) + } + VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) + ModalManager.fullscreen.showInView() } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index 44fcddb54c..5dfa5aa200 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -1,7 +1,6 @@ package chat.simplex.common.platform -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.* import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.* @@ -22,6 +21,7 @@ interface PlatformInterface { fun androidIsBackgroundCallAllowed(): Boolean = true fun androidSetNightModeIfSupported() {} fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) {} + fun androidSetDrawerStatusAndNavBarColor(isLight: Boolean, drawerShadingColor: Color, toolbarOnTop: Boolean, navBarColor: Color) {} fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {} fun androidPictureInPictureAllowed(): Boolean = true fun androidCallEnded() {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 5cb97d7d80..e44a174b53 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -109,7 +109,6 @@ fun TerminalLayout( } }, contentColor = LocalContentColor.current, - drawerContentColor = LocalContentColor.current, modifier = Modifier.navigationBarsWithImePadding() ) { contentPadding -> Surface( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 690ba89ef9..511230cc83 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -660,7 +660,6 @@ fun ChatLayout( modifier = Modifier.navigationBarsWithImePadding(), floatingActionButton = { floatingButton.value() }, contentColor = LocalContentColor.current, - drawerContentColor = LocalContentColor.current, backgroundColor = Color.Unspecified ) { contentPadding -> val wallpaperImage = MaterialTheme.wallpaper.type.image diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 11b1006f41..54c67674ad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -23,7 +23,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.* -import chat.simplex.common.SettingsViewState +import chat.simplex.common.AppLock import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts @@ -135,7 +135,7 @@ fun ToggleChatListCard() { } @Composable -fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { +fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { val oneHandUI = remember { appPrefs.oneHandUI.state } LaunchedEffect(Unit) { if (shouldShowWhatsNew(chatModel)) { @@ -153,18 +153,15 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf VideoPlayerHolder.stopAll() } } - val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } - val scope = rememberCoroutineScope() - val (userPickerState, scaffoldState ) = settingsState Scaffold( topBar = { if (!oneHandUI.value) { - Column(Modifier.padding(end = endPadding)) { + Column { ChatListToolbar( - scaffoldState.drawerState, userPickerState, stopped, + setPerformLA, ) Divider() } @@ -172,33 +169,17 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf }, bottomBar = { if (oneHandUI.value) { - Column(Modifier.padding(end = endPadding)) { + Column { Divider() ChatListToolbar( - scaffoldState.drawerState, userPickerState, stopped, - ) - } - } - }, - scaffoldState = scaffoldState, - drawerContent = { - tryOrShowError("Settings", error = { ErrorSettingsView() }) { - val handler = remember { AppBarHandler() } - CompositionLocalProvider( - LocalAppBarHandler provides handler - ) { - ModalView(showClose = appPlatform.isDesktop, close = { scope.launch { scaffoldState.drawerState.close() } }) { - SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) - } + setPerformLA, + ) } } }, contentColor = LocalContentColor.current, - drawerContentColor = LocalContentColor.current, - drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f), - drawerGesturesEnabled = appPlatform.isAndroid, floatingActionButton = { if (!oneHandUI.value && searchText.value.text.isEmpty() && !chatModel.desktopNoUserNoRemote && chatModel.chatRunning.value == true) { FloatingActionButton( @@ -208,7 +189,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf } }, Modifier - .padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp) + .padding(end = DEFAULT_PADDING - 16.dp, bottom = DEFAULT_PADDING - 16.dp) .size(AppBarHeight * fontSizeSqrtMultiplier), elevation = FloatingActionButtonDefaults.elevation( defaultElevation = 0.dp, @@ -224,7 +205,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf } } ) { - Box(Modifier.padding(it).padding(end = endPadding)) { + Box(Modifier.padding(it)) { Box( modifier = Modifier .fillMaxSize() @@ -252,11 +233,8 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf UserPicker( chatModel = chatModel, userPickerState = userPickerState, - contentAlignment = if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart - ) { - scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() } - userPickerState.value = AnimatedViewState.GONE - } + setPerformLA = AppLock::setPerformLA + ) } } } @@ -278,7 +256,7 @@ private fun ConnectButton(text: String, onClick: () -> Unit) { } @Composable -private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableStateFlow, stopped: Boolean) { +private fun ChatListToolbar(userPickerState: MutableStateFlow, stopped: Boolean, setPerformLA: (Boolean) -> Unit) { val serversSummary: MutableState = remember { mutableStateOf(null) } val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() val updatingProgress = remember { chatModel.updatingProgress }.value @@ -344,23 +322,22 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt } } } - val scope = rememberCoroutineScope() val clipboard = LocalClipboardManager.current DefaultTopAppBar( navigationButton = { if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) { - NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } } + NavigationButtonMenu { + ModalManager.start.showModalCloseable { close -> + SettingsView(chatModel, setPerformLA, close) + } + } } else { val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } } val allRead = users .filter { u -> !u.user.activeUser && !u.user.hidden } .all { u -> u.unreadCount == 0 } UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) { - if (users.size == 1 && chatModel.remoteHosts.isEmpty()) { - scope.launch { drawerState.open() } - } else { userPickerState.value = AnimatedViewState.VISIBLE - } } } }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index 886b82de7d..b4d0b05584 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -11,31 +11,24 @@ import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import chat.simplex.common.SettingsViewState import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* +import chat.simplex.common.views.newchat.ActiveProfilePicker import chat.simplex.res.MR -import kotlinx.coroutines.flow.MutableStateFlow @Composable -fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stopped: Boolean) { +fun ShareListView(chatModel: ChatModel, stopped: Boolean) { var searchInList by rememberSaveable { mutableStateOf("") } - val (userPickerState, scaffoldState) = settingsState - val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp val oneHandUI = remember { appPrefs.oneHandUI.state } Scaffold( - Modifier.padding(end = endPadding), contentColor = LocalContentColor.current, - drawerContentColor = LocalContentColor.current, - scaffoldState = scaffoldState, topBar = { if (!oneHandUI.value) { Column { - ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } + ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } Divider() } } @@ -44,7 +37,7 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe if (oneHandUI.value) { Column { Divider() - ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } + ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } } } } @@ -92,21 +85,6 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe } } } - if (appPlatform.isAndroid) { - tryOrShowError("UserPicker", error = {}) { - UserPicker( - chatModel, - userPickerState, - showSettings = false, - showCancel = true, - contentAlignment = if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart, - cancelClicked = { - chatModel.sharedContent.value = null - userPickerState.value = AnimatedViewState.GONE - } - ) - } - } } private fun hasSimplexLink(msg: String): Boolean { @@ -122,7 +100,7 @@ private fun EmptyList() { } @Composable -private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableStateFlow, stopped: Boolean, onSearchValueChanged: (String) -> Unit) { +private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchValueChanged: (String) -> Unit) { var showSearch by rememberSaveable { mutableStateOf(false) } val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false } if (showSearch) { @@ -138,7 +116,24 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState .filter { u -> !u.user.activeUser && !u.user.hidden } .all { u -> u.unreadCount == 0 } UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) { - userPickerState.value = AnimatedViewState.VISIBLE + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { + ActiveProfilePicker( + search = search, + rhId = chatModel.remoteHostId, + close = close, + contactConnection = null, + showIncognito = false + ) + } + ) + } } } else -> NavigationButtonBack(onButtonClicked = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index e15bc3863e..338690c8e8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -1,53 +1,51 @@ package chat.simplex.common.views.chatlist import SectionItemView -import androidx.compose.animation.core.* +import SectionView +import TextIconSpaced import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* import androidx.compose.ui.draw.* -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.text.TextStyle import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* import chat.simplex.common.model.* -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* import chat.simplex.common.views.CreateProfile +import chat.simplex.common.views.localauth.VerticalDivider +import chat.simplex.common.views.newchat.* import chat.simplex.common.views.remote.* -import chat.simplex.common.views.usersettings.doWithAuth +import chat.simplex.common.views.usersettings.* +import chat.simplex.common.views.usersettings.AppearanceScope.ColorModeSwitcher import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlin.math.roundToInt @Composable fun UserPicker( chatModel: ChatModel, userPickerState: MutableStateFlow, - showSettings: Boolean = true, - contentAlignment: Alignment = Alignment.TopStart, - showCancel: Boolean = false, - cancelClicked: () -> Unit = {}, - useFromDesktopClicked: () -> Unit = {}, - settingsClicked: () -> Unit = {}, + setPerformLA: (Boolean) -> Unit, ) { - val scope = rememberCoroutineScope() var newChat by remember { mutableStateOf(userPickerState.value) } if (newChat.isVisible()) { BackHandler { @@ -67,18 +65,32 @@ fun UserPicker( .sortedBy { it.hostDeviceName } } } - val animatedFloat = remember { Animatable(if (newChat.isVisible()) 0f else 1f) } + + val view = LocalMultiplatformView() LaunchedEffect(Unit) { launch { userPickerState.collect { newChat = it + if (it.isVisible()) { + hideKeyboard(view) + } launch { - animatedFloat.animateTo(if (newChat.isVisible()) 1f else 0f, newChatSheetAnimSpec()) if (newChat.isHiding()) userPickerState.value = AnimatedViewState.GONE } } } } + + LaunchedEffect(Unit) { + launch { + snapshotFlow { ModalManager.start.modalCount.value } + .filter { it > 0 } + .collect { + closePicker(userPickerState) + } + } + } + LaunchedEffect(Unit) { snapshotFlow { newChat.isVisible() } .distinctUntilChanged() @@ -124,110 +136,143 @@ fun UserPicker( } } } - val UsersView: @Composable ColumnScope.() -> Unit = { - users.forEach { u -> - UserProfilePickerItem(u.user, u.unreadCount, openSettings = settingsClicked) { - userPickerState.value = AnimatedViewState.HIDING - if (!u.user.activeUser) { - withBGApi { - controller.showProgressIfNeeded { - ModalManager.closeAllModalsEverywhere() - chatModel.controller.changeActiveUser(u.user.remoteHostId, u.user.userId, null) - } - } - } - } - Divider(Modifier.requiredHeight(1.dp)) - if (u.user.activeUser) Divider(Modifier.requiredHeight(0.5.dp)) - } - } - val xOffset = with(LocalDensity.current) { 10.dp.roundToPx() } - val maxWidth = with(LocalDensity.current) { windowWidth() * density } - Box(Modifier - .fillMaxSize() - .offset { IntOffset(if (newChat.isGone()) -maxWidth.value.roundToInt() else xOffset, 0) } - .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { userPickerState.value = AnimatedViewState.HIDING }) - .padding(bottom = 10.dp, top = 10.dp) - .graphicsLayer { - alpha = animatedFloat.value - translationY = (if (appPrefs.oneHandUI.state.value) -1 else 1) * (animatedFloat.value - 1) * xOffset - }, - contentAlignment = contentAlignment + + PlatformUserPicker( + modifier = Modifier + .height(IntrinsicSize.Min) + .fillMaxWidth() + .then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true) else Modifier) + .background(MaterialTheme.colors.surface) + .padding(vertical = DEFAULT_PADDING), + pickerState = userPickerState ) { - Column( - Modifier - .widthIn(min = 260.dp) - .width(IntrinsicSize.Min) - .height(IntrinsicSize.Min) - .shadow(8.dp, RoundedCornerShape(corner = CornerSize(25.dp)), clip = true) - .background(MaterialTheme.colors.surface, RoundedCornerShape(corner = CornerSize(25.dp))) - .clip(RoundedCornerShape(corner = CornerSize(25.dp))) - ) { - val currentRemoteHost = remember { chatModel.currentRemoteHost }.value - Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) { - if (remoteHosts.isNotEmpty()) { - if (currentRemoteHost == null && chatModel.localUserCreated.value == true) { - LocalDevicePickerItem(true) { - userPickerState.value = AnimatedViewState.HIDING - switchToLocalDevice() - } - Divider(Modifier.requiredHeight(1.dp)) - } else if (currentRemoteHost != null) { - val connecting = rememberSaveable { mutableStateOf(false) } - RemoteHostPickerItem(currentRemoteHost, - actionButtonClick = { - userPickerState.value = AnimatedViewState.HIDING - stopRemoteHostAndReloadHosts(currentRemoteHost, true) - }) { - userPickerState.value = AnimatedViewState.HIDING - switchToRemoteHost(currentRemoteHost, connecting) - } - Divider(Modifier.requiredHeight(1.dp)) - } - } + @Composable + fun FirstSection() { + if (remoteHosts.isNotEmpty()) { + val currentRemoteHost = remember { chatModel.currentRemoteHost }.value + val localDeviceActive = currentRemoteHost == null && chatModel.localUserCreated.value == true - UsersView() - - if (remoteHosts.isNotEmpty() && currentRemoteHost != null && chatModel.localUserCreated.value == true) { - LocalDevicePickerItem(false) { + DevicePickerRow( + localDeviceActive = localDeviceActive, + remoteHosts = remoteHosts, + onRemoteHostClick = { h, connecting -> + userPickerState.value = AnimatedViewState.HIDING + switchToRemoteHost(h, connecting) + }, + onLocalDeviceClick = { userPickerState.value = AnimatedViewState.HIDING switchToLocalDevice() + }, + onRemoteHostActionButtonClick = { h -> + userPickerState.value = AnimatedViewState.HIDING + stopRemoteHostAndReloadHosts(h, true) + } + ) + } + ActiveUserSection( + chatModel = chatModel, + userPickerState = userPickerState, + ) + } + + @Composable + fun SecondSection() { + GlobalSettingsSection( + chatModel = chatModel, + userPickerState = userPickerState, + setPerformLA = setPerformLA, + onUserClicked = { user -> + userPickerState.value = AnimatedViewState.HIDING + if (!user.activeUser) { + withBGApi { + controller.showProgressIfNeeded { + ModalManager.closeAllModalsEverywhere() + chatModel.controller.changeActiveUser(user.remoteHostId, user.userId, null) + } + } + } + }, + onShowAllProfilesClicked = { + doWithAuth( + generalGetString(MR.strings.auth_open_chat_profiles), + generalGetString(MR.strings.auth_log_in_using_credential) + ) { + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + val profileHidden = rememberSaveable { mutableStateOf(false) } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { UserProfilesView(chatModel, search, profileHidden) }) + } } - Divider(Modifier.requiredHeight(1.dp)) } - remoteHosts.filter { !it.activeHost }.forEach { h -> - val connecting = rememberSaveable { mutableStateOf(false) } - RemoteHostPickerItem(h, - actionButtonClick = { - userPickerState.value = AnimatedViewState.HIDING - stopRemoteHostAndReloadHosts(h, false) - }) { - userPickerState.value = AnimatedViewState.HIDING - switchToRemoteHost(h, connecting) - } - Divider(Modifier.requiredHeight(1.dp)) + ) + } + + if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) { + Column { + FirstSection() + Divider(Modifier.padding(DEFAULT_PADDING)) + SecondSection() + } + } else { + Row { + Box(Modifier.weight(1f)) { + FirstSection() + } + VerticalDivider() + Box(Modifier.weight(1f)) { + SecondSection() } } - if (appPlatform.isAndroid) { - UseFromDesktopPickerItem { - ModalManager.start.showCustomModal { close -> - ConnectDesktopView(close) - } - userPickerState.value = AnimatedViewState.GONE - } - Divider(Modifier.requiredHeight(1.dp)) - } else { - if (remoteHosts.isEmpty()) { - LinkAMobilePickerItem { - ModalManager.start.showModal { - ConnectMobileView() - } - userPickerState.value = AnimatedViewState.GONE - } - Divider(Modifier.requiredHeight(1.dp)) - } - if (chatModel.desktopNoUserNoRemote) { - CreateInitialProfile { + } + } +} + +@Composable +private fun ActiveUserSection( + chatModel: ChatModel, + userPickerState: MutableStateFlow, +) { + val showCustomModal: (@Composable() (ModalData.(ChatModel, () -> Unit) -> Unit)) -> () -> Unit = { modalView -> + { + ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } + } + } + val currentUser = remember { chatModel.currentUser }.value + val stopped = chatModel.chatRunning.value == false + + if (currentUser != null) { + SectionView { + SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { + ProfilePreview(currentUser.profile, stopped = stopped) + } + UserPickerOptionRow( + painterResource(MR.images.ic_qr_code), + if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_public_contact_address) else generalGetString(MR.strings.create_public_contact_address), + showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped + ) + UserPickerOptionRow( + painterResource(MR.images.ic_toggle_on), + stringResource(MR.strings.chat_preferences), + click = if (stopped) null else ({ + showCustomModal { m, close -> + PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close) + }() + }), + disabled = stopped + ) + } + } else { + SectionView { + if (chatModel.desktopNoUserNoRemote) { + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + generalGetString(MR.strings.create_chat_profile), + { doWithAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.center.showModalCloseable { close -> LaunchedEffect(Unit) { @@ -237,15 +282,76 @@ fun UserPicker( } } } - Divider(Modifier.requiredHeight(1.dp)) + ) + } + } + } +} + +@Composable +private fun GlobalSettingsSection( + chatModel: ChatModel, + userPickerState: MutableStateFlow, + setPerformLA: (Boolean) -> Unit, + onUserClicked: (user: User) -> Unit, + onShowAllProfilesClicked: () -> Unit +) { + val stopped = chatModel.chatRunning.value == false + val users by remember { + derivedStateOf { + chatModel.users + .filter { u -> !u.user.hidden && !u.user.activeUser } + } + } + + SectionView(headerBottomPadding = if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_PADDING else 0.dp) { + UserPickerInactiveUsersSection( + users = users, + onShowAllProfilesClicked = onShowAllProfilesClicked, + onUserClicked = onUserClicked, + stopped = stopped + ) + + if (appPlatform.isAndroid) { + val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) + + UserPickerOptionRow( + painterResource(MR.images.ic_desktop), + text, + click = { + ModalManager.start.showCustomModal { close -> + ConnectDesktopView(close) + } } - } - if (showSettings) { - SettingsPickerItem(settingsClicked) - } - if (showCancel) { - CancelPickerItem(cancelClicked) - } + ) + } else { + UserPickerOptionRow( + icon = painterResource(MR.images.ic_smartphone_300), + text = stringResource(if (remember { chat.simplex.common.platform.chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), + click = { + userPickerState.value = AnimatedViewState.HIDING + ModalManager.start.showModal { + ConnectMobileView() + } + }, + disabled = stopped + ) + } + + SectionItemView( + click = { + ModalManager.start.showModalCloseable { close -> + SettingsView(chatModel, setPerformLA, close) + } + }, + padding = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING + 2.dp) + ) { + val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) + Icon(painterResource(MR.images.ic_settings), text, tint = MaterialTheme.colors.secondary) + TextIconSpaced() + Text(text, color = Color.Unspecified) + Spacer(Modifier.weight(1f)) + ColorModeSwitcher() } } } @@ -296,7 +402,7 @@ fun UserProfilePickerItem( } } else if (!u.showNtfs) { Icon(painterResource(MR.images.ic_notifications_off), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) - } else { + } else { Box(Modifier.size(20.dp)) } } @@ -325,136 +431,157 @@ fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == tr } @Composable -fun RemoteHostPickerItem(h: RemoteHostInfo, onLongClick: () -> Unit = {}, actionButtonClick: () -> Unit = {}, onClick: () -> Unit) { - Row( - Modifier - .fillMaxWidth() - .background(color = if (h.activeHost) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) - .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) - .combinedClickable( - onClick = onClick, - onLongClick = onLongClick - ) - .onRightClick { onLongClick() } - .padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - RemoteHostRow(h) - if (h.sessionState is RemoteHostSessionState.Connected) { - HostDisconnectButton(actionButtonClick) - } else { - Box(Modifier.size(20.dp)) +fun UserPickerOptionRow(icon: Painter, text: String, click: (() -> Unit)? = null, disabled: Boolean = false) { + SectionItemView(click, disabled = disabled, extraPadding = true) { + Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.secondary) + TextIconSpaced() + Text(text = text, color = if (disabled) MaterialTheme.colors.secondary else Color.Unspecified) + } +} + +@Composable +fun UserPickerInactiveUserBadge(userInfo: UserInfo, stopped: Boolean, size: Dp = 60.dp, onClick: (user: User) -> Unit) { + Box { + IconButton( + onClick = { onClick(userInfo.user) }, + enabled = !stopped + ) { + Box { + ProfileImage(size = size, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) + + if (userInfo.unreadCount > 0) { + unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs) + } + } } } } @Composable -fun RemoteHostRow(h: RemoteHostInfo) { - Row( - Modifier - .widthIn(max = windowWidth() * 0.7f) - .padding(start = 17.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(painterResource(MR.images.ic_smartphone_300), h.hostDeviceName, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Text( - h.hostDeviceName, - modifier = Modifier.padding(start = 26.dp, end = 8.dp), - color = if (h.activeHost) MaterialTheme.colors.onBackground else MenuTextColor, - fontSize = 14.sp, - ) - } -} - -@Composable -fun LocalDevicePickerItem(active: Boolean, onLongClick: () -> Unit = {}, onClick: () -> Unit) { +private fun DevicePickerRow( + localDeviceActive: Boolean, + remoteHosts: List, + onLocalDeviceClick: () -> Unit, + onRemoteHostClick: (rh: RemoteHostInfo, connecting: MutableState) -> Unit, + onRemoteHostActionButtonClick: (rh: RemoteHostInfo) -> Unit, +) { Row( Modifier .fillMaxWidth() - .background(color = if (active) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) - .combinedClickable( - onClick = if (active) {{}} else onClick, - onLongClick = onLongClick, - interactionSource = remember { MutableInteractionSource() }, - indication = if (!active) LocalIndication.current else null - ) - .onRightClick { onLongClick() } - .padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING), - horizontalArrangement = Arrangement.SpaceBetween, + .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), + horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { - LocalDeviceRow(active) - Box(Modifier.size(20.dp)) + val activeHost = remoteHosts.firstOrNull { h -> h.activeHost } + + if (activeHost != null) { + val connecting = rememberSaveable { mutableStateOf(false) } + + DevicePill( + active = true, + icon = painterResource(MR.images.ic_smartphone_300), + text = activeHost.hostDeviceName, + actionButtonVisible = activeHost.sessionState is RemoteHostSessionState.Connected, + onActionButtonClick = { onRemoteHostActionButtonClick(activeHost) } + ) { + onRemoteHostClick(activeHost, connecting) + } + } + + DevicePill( + active = localDeviceActive, + icon = painterResource(MR.images.ic_desktop), + text = stringResource(MR.strings.this_device), + actionButtonVisible = false + ) { + onLocalDeviceClick() + } + + remoteHosts.filter { h -> h.sessionState is RemoteHostSessionState.Connected && !h.activeHost }.forEach { h -> + val connecting = rememberSaveable { mutableStateOf(false) } + + DevicePill( + active = h.activeHost, + icon = painterResource(MR.images.ic_smartphone_300), + text = h.hostDeviceName, + actionButtonVisible = h.sessionState is RemoteHostSessionState.Connected, + onActionButtonClick = { onRemoteHostActionButtonClick(h) } + ) { + onRemoteHostClick(h, connecting) + } + } } } @Composable -fun LocalDeviceRow(active: Boolean) { +expect fun UserPickerInactiveUsersSection( + users: List, + stopped: Boolean, + onShowAllProfilesClicked: () -> Unit, + onUserClicked: (user: User) -> Unit, +) + +@Composable +expect fun PlatformUserPicker( + modifier: Modifier, + pickerState: MutableStateFlow, + content: @Composable () -> Unit +) + +@Composable +fun DevicePill( + active: Boolean, + icon: Painter, + text: String, + actionButtonVisible: Boolean, + onActionButtonClick: (() -> Unit)? = null, + onClick: () -> Unit) { Row( Modifier - .widthIn(max = windowWidth() * 0.7f) - .padding(start = 17.dp, end = DEFAULT_PADDING), + .clip(RoundedCornerShape(8.dp)) + .border( + BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant), + shape = RoundedCornerShape(8.dp) + ) + .background(if (active) MaterialTheme.colors.secondaryVariant else Color.Transparent) + .clickable( + enabled = !active, + onClick = onClick, + interactionSource = remember { MutableInteractionSource() }, + indication = LocalIndication.current + ), verticalAlignment = Alignment.CenterVertically ) { - Icon(painterResource(MR.images.ic_desktop), stringResource(MR.strings.this_device), Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Text( - stringResource(MR.strings.this_device), - modifier = Modifier.padding(start = 26.dp, end = 8.dp), - color = if (active) MaterialTheme.colors.onBackground else MenuTextColor, - fontSize = 14.sp, - ) - } -} - -@Composable -private fun UseFromDesktopPickerItem(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_desktop), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) - } -} - -@Composable -private fun LinkAMobilePickerItem(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.link_a_mobile) - Icon(painterResource(MR.images.ic_smartphone_300), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) - } -} - -@Composable -private fun CreateInitialProfile(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.create_chat_profile) - Icon(painterResource(MR.images.ic_manage_accounts), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) - } -} - -@Composable -private fun SettingsPickerItem(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_settings), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) - } -} - -@Composable -private fun CancelPickerItem(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.cancel_verb) - Icon(painterResource(MR.images.ic_close), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) + Row( + Modifier.padding(horizontal = 6.dp, vertical = 4.dp) + ) { + Icon( + icon, + text, + Modifier.size(16.dp * fontSizeSqrtMultiplier), + tint = MaterialTheme.colors.onSurface + ) + Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON * fontSizeSqrtMultiplier)) + Text( + text, + color = MaterialTheme.colors.onSurface, + fontSize = 12.sp, + ) + if (onActionButtonClick != null && actionButtonVisible) { + val interactionSource = remember { MutableInteractionSource() } + val hovered = interactionSource.collectIsHoveredAsState().value + Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON * fontSizeSqrtMultiplier)) + IconButton(onActionButtonClick, Modifier.requiredSize(16.dp * fontSizeSqrtMultiplier)) { + Icon( + painterResource(if (hovered) MR.images.ic_wifi_off else MR.images.ic_wifi), + null, + Modifier.size(16.dp * fontSizeSqrtMultiplier).hoverable(interactionSource), + tint = if (hovered) WarningOrange else MaterialTheme.colors.onBackground + ) + } + } + } } } @@ -472,6 +599,29 @@ fun HostDisconnectButton(onClick: (() -> Unit)?) { } } +@Composable +private fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean) { + Text( + if (unreadCount > 0) unreadCountStr(unreadCount) else "", + color = Color.White, + fontSize = 10.sp, + style = TextStyle(textAlign = TextAlign.Center), + modifier = Modifier + .offset(y = 3.sp.toDp()) + .background(if (userMuted) MaterialTheme.colors.primaryVariant else MaterialTheme.colors.secondary, shape = CircleShape) + .badgeLayout() + .padding(horizontal = 2.sp.toDp()) + .padding(vertical = 2.sp.toDp()) + .align(Alignment.TopEnd) + ) +} + + +private suspend fun closePicker(userPickerState: MutableStateFlow) { + delay(500) + userPickerState.value = AnimatedViewState.HIDING +} + private fun switchToLocalDevice() { withBGApi { chatController.switchUIRemoteHost(null) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt index 6a400295ed..4fdbd97d23 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt @@ -7,3 +7,5 @@ fun chatListAnimationSpec() = tween(durationMillis = 250, easing = FastOu fun newChatSheetAnimSpec() = tween(256, 0, LinearEasing) fun audioProgressBarAnimationSpec() = tween(durationMillis = 30, easing = LinearEasing) + +fun userPickerAnimSpec() = tween(256, 0, FastOutSlowInEasing) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index 90f8299404..104c05309c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.* import chat.simplex.common.platform.appPlatform import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chatlist.DevicePill import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import kotlin.math.absoluteValue @@ -157,9 +158,13 @@ private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection? @Composable private fun HostDeviceTitle(hostDevice: Pair, extraPadding: Boolean = false) { Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) { - Icon(painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), null, Modifier.size(15.dp), tint = MaterialTheme.colors.secondary) - Spacer(Modifier.width(10.dp)) - Text(hostDevice.second, color = MaterialTheme.colors.secondary) + DevicePill( + active = true, + onClick = {}, + actionButtonVisible = false, + icon = painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), + text = hostDevice.second + ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 7512cf872e..7b504116cc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -588,9 +588,11 @@ fun KeyChangeEffect( var anyChange by remember { mutableStateOf(false) } LaunchedEffect(key1) { if (anyChange || key1 != prevKey) { - block(prevKey) + val prev = prevKey prevKey = key1 anyChange = true + // Call it as the last statement because the coroutine can be cancelled earlier + block(prev) } } } @@ -610,8 +612,8 @@ fun KeyChangeEffect( var anyChange by remember { mutableStateOf(false) } LaunchedEffect(key1, key2) { if (anyChange || key1 != initialKey || key2 != initialKey2) { - block() anyChange = true + block() } } } @@ -633,8 +635,8 @@ fun KeyChangeEffect( var anyChange by remember { mutableStateOf(false) } LaunchedEffect(key1, key2, key3) { if (anyChange || key1 != initialKey || key2 != initialKey2 || key3 != initialKey3) { - block() anyChange = true + block() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 544f5f72bb..a05de0e8b3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -2,7 +2,6 @@ package chat.simplex.common.views.newchat import SectionBottomSpacer import SectionItemView -import SectionSpacer import SectionTextFooter import SectionView import TextIconSpaced @@ -267,24 +266,13 @@ private fun ProfilePickerOption( ) } -private fun filteredProfiles(users: List, searchTextOrPassword: String): List { - val s = searchTextOrPassword.trim() - val lower = s.lowercase() - return users.filter { u -> - if ((u.activeUser || !u.hidden) && (s == "" || u.anyNameContains(lower))) { - true - } else { - correctPassword(u, s) - } - } -} - @Composable -private fun ActiveProfilePicker( +fun ActiveProfilePicker( search: MutableState, contactConnection: PendingContactConnection?, close: () -> Unit, - rhId: Long? + rhId: Long?, + showIncognito: Boolean = true ) { val switchingProfile = remember { mutableStateOf(false) } val incognito = remember { @@ -292,11 +280,9 @@ private fun ActiveProfilePicker( } val selectedProfile by remember { chatModel.currentUser } val searchTextOrPassword = rememberSaveable { search } - val profiles = remember { - chatModel.users.map { it.user }.sortedBy { !it.activeUser } - } - val filteredProfiles by remember { - derivedStateOf { filteredProfiles(profiles, searchTextOrPassword.value) } + // Intentionally don't use derivedStateOf in order to NOT change an order after user was selected + val filteredProfiles = remember(searchTextOrPassword.value) { + filteredProfiles(chatModel.users.map { it.user }.sortedBy { !it.activeUser }, searchTextOrPassword.value) } var progressByTimeout by rememberSaveable { mutableStateOf(false) } @@ -322,32 +308,38 @@ private fun ActiveProfilePicker( switchingProfile.value = true withApi { try { + var updatedConn: PendingContactConnection? = null; + if (contactConnection != null) { - val conn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId) - if (conn != null) { + updatedConn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId) + if (updatedConn != null) { withChats { - updateContactConnection(rhId, conn) - updateShownConnection(conn) + updateContactConnection(rhId, updatedConn) + updateShownConnection(updatedConn) } - controller.changeActiveUser_( - rhId = user.remoteHostId, - toUserId = user.userId, - viewPwd = if (user.hidden) searchTextOrPassword.value else null - ) - - if (chatModel.currentUser.value?.userId != user.userId) { - AlertManager.shared.showAlertMsg(generalGetString( - MR.strings.switching_profile_error_title), - String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName) - ) - } - - withChats { - updateContactConnection(user.remoteHostId, conn) - } - close.invoke() } } + + controller.changeActiveUser_( + rhId = user.remoteHostId, + toUserId = user.userId, + viewPwd = if (user.hidden) searchTextOrPassword.value else null + ) + + if (chatModel.currentUser.value?.userId != user.userId) { + AlertManager.shared.showAlertMsg(generalGetString( + MR.strings.switching_profile_error_title), + String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName) + ) + } + + if (updatedConn != null) { + withChats { + updateContactConnection(user.remoteHostId, updatedConn) + } + } + + close() } finally { switchingProfile.value = false } @@ -364,24 +356,21 @@ private fun ActiveProfilePicker( title = stringResource(MR.strings.incognito), selected = incognito, onSelected = { - if (!incognito) { - switchingProfile.value = true - withApi { - try { - if (contactConnection != null) { - val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) + if (incognito || switchingProfile.value || contactConnection == null) return@ProfilePickerOption - if (conn != null) { - withChats { - updateContactConnection(rhId, conn) - updateShownConnection(conn) - } - close.invoke() - } + switchingProfile.value = true + withApi { + try { + val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) + if (conn != null) { + withChats { + updateContactConnection(rhId, conn) + updateShownConnection(conn) } - } finally { - switchingProfile.value = false + close() } + } finally { + switchingProfile.value = false } } }, @@ -413,20 +402,18 @@ private fun ActiveProfilePicker( if (activeProfile != null) { val otherProfiles = filteredProfiles.filter { it.userId != activeProfile.userId } - - if (incognito) { - item { - IncognitoUserOption() - } - item { - ProfilePickerUserOption(activeProfile) - } - } else { - item { - ProfilePickerUserOption(activeProfile) - } - item { - IncognitoUserOption() + item { + when { + !showIncognito -> + ProfilePickerUserOption(activeProfile) + incognito -> { + IncognitoUserOption() + ProfilePickerUserOption(activeProfile) + } + else -> { + ProfilePickerUserOption(activeProfile) + IncognitoUserOption() + } } } @@ -434,8 +421,10 @@ private fun ActiveProfilePicker( ProfilePickerUserOption(p) } } else { - item { - IncognitoUserOption() + if (showIncognito) { + item { + IncognitoUserOption() + } } itemsIndexed(filteredProfiles) { _, p -> ProfilePickerUserOption(p) @@ -641,6 +630,18 @@ fun LinkTextView(link: String, share: Boolean) { } } +private fun filteredProfiles(users: List, searchTextOrPassword: String): List { + val s = searchTextOrPassword.trim() + val lower = s.lowercase() + return users.filter { u -> + if ((u.activeUser || !u.hidden) && (s == "" || u.anyNameContains(lower))) { + true + } else { + correctPassword(u, s) + } + } +} + private suspend fun verify(rhId: Long?, text: String?, close: () -> Unit): Boolean { if (text != null && strIsSimplexLink(text)) { connect(rhId, text, close) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 3747ae047a..bef837ba94 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -9,6 +9,7 @@ import SectionView import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.MaterialTheme.colors @@ -606,6 +607,39 @@ object AppearanceScope { } } + @Composable + fun ColorModeSwitcher() { + val currentTheme by CurrentColors.collectAsState() + val themeMode = if (remember { appPrefs.currentTheme.state }.value == DefaultTheme.SYSTEM_THEME_NAME) { + if (systemInDarkThemeCurrently) DefaultThemeMode.DARK else DefaultThemeMode.LIGHT + } else { + currentTheme.base.mode + } + + val onLongClick = { + ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) + showToast(generalGetString(MR.strings.system_mode_toast)) + + saveThemeToDatabase(null) + } + Box( + modifier = Modifier + .clip(CircleShape) + .combinedClickable( + onClick = { + ThemeManager.applyTheme(if (themeMode == DefaultThemeMode.LIGHT) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.themeName) + saveThemeToDatabase(null) + }, + onLongClick = onLongClick + ) + .onRightClick(onLongClick) + .size(44.dp), + contentAlignment = Alignment.Center + ) { + Icon(painterResource(if (themeMode == DefaultThemeMode.LIGHT) MR.images.ic_light_mode else MR.images.ic_bedtime_moon), stringResource(MR.strings.color_mode_light), tint = MaterialTheme.colors.secondary) + } + } + private var updateBackendJob: Job = Job() private fun saveThemeToDatabase(themeUserDestination: Pair?) { val remoteHostId = chatModel.remoteHostId() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index 2f6c0395ec..0229e7da2a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -73,10 +73,7 @@ private fun SetDeliveryReceiptsLayout( skip: () -> Unit, userCount: Int, ) { - // This view located in the left panel which means it has to have a padding from right side in order - // to see scroll bar. And this padding should be applied to upper element, not scrollable column modifier - val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp - Box(Modifier.padding(top = DEFAULT_PADDING, end = endPadding)) { + Box(Modifier.padding(top = DEFAULT_PADDING)) { ColumnWithScrollBar( Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 3e1522b288..bb4a0b61b0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -31,13 +31,11 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.migration.MigrateFromDeviceView import chat.simplex.common.views.onboarding.SimpleXInfo import chat.simplex.common.views.onboarding.WhatsNewView -import chat.simplex.common.views.remote.ConnectDesktopView -import chat.simplex.common.views.remote.ConnectMobileView import chat.simplex.res.MR import kotlinx.coroutines.* @Composable -fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerState: DrawerState) { +fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: () -> Unit) { val user = chatModel.currentUser.value val stopped = chatModel.chatRunning.value == false SettingsLayout( @@ -71,10 +69,9 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerSt } }, withAuth = ::doWithAuth, - drawerState = drawerState, ) KeyChangeEffect(chatModel.updatingProgress.value != null) { - drawerState.close() + close() } } @@ -96,18 +93,11 @@ fun SettingsLayout( showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit, - drawerState: DrawerState, ) { val scope = rememberCoroutineScope() - val closeSettings: () -> Unit = { scope.launch { drawerState.close() } } val view = LocalMultiplatformView() - if (drawerState.isOpen) { - BackHandler { - closeSettings() - } - LaunchedEffect(Unit) { - hideKeyboard(view) - } + LaunchedEffect(Unit) { + hideKeyboard(view) } val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current @@ -118,46 +108,22 @@ fun SettingsLayout( ) { AppBarTitle(stringResource(MR.strings.your_settings)) - SectionView(stringResource(MR.strings.settings_section_title_you)) { - val profileHidden = rememberSaveable { mutableStateOf(false) } - if (profile != null) { - SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { - ProfilePreview(profile, stopped = stopped) - } - SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden, drawerState) } } }, disabled = stopped) - SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped) - ChatPreferencesItem(showCustomModal, stopped = stopped) - } else if (chatModel.localUserCreated.value == false) { - SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.create_chat_profile), { - withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { - ModalManager.center.showModalCloseable { close -> - LaunchedEffect(Unit) { - closeSettings() - } - CreateProfile(chatModel, close) - } - } - }, disabled = stopped) - } - if (appPlatform.isDesktop) { - SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped) - } else { - SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal { it, close -> ConnectDesktopView(close) }, disabled = stopped) - } - SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped) - } - SectionDividerSpaced() - SectionView(stringResource(MR.strings.settings_section_title_settings)) { SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }) - DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) } SectionDividerSpaced() + SectionView(stringResource(MR.strings.settings_section_title_chat_database)) { + DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) + SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped) + } + + SectionDividerSpaced() + SectionView(stringResource(MR.strings.settings_section_title_help)) { SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped) @@ -535,7 +501,6 @@ fun PreviewSettingsLayout() { showCustomModal = { {} }, showVersion = {}, withAuth = { _, _, _ -> }, - drawerState = DrawerState(DrawerValue.Closed), ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index b357272e16..1f546fb863 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -174,7 +174,7 @@ private fun UserAddressLayout( saveAas: (AutoAcceptState, MutableState) -> Unit, ) { ColumnWithScrollBar { - AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId)) + AppBarTitle(stringResource(MR.strings.public_address), hostDevice(user?.remoteHostId)) Column( Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally, @@ -230,7 +230,7 @@ private fun UserAddressLayout( private fun CreateAddressButton(onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_qr_code), - stringResource(MR.strings.create_simplex_address), + stringResource(MR.strings.create_public_address), onClick, iconColor = MaterialTheme.colors.primary, textColor = MaterialTheme.colors.primary, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index e3636ec9c5..10acaffe1a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -34,6 +34,7 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { KeyChangeEffect(u.value?.remoteHostId, u.value?.userId) { close() } + if (user != null) { var profile by remember { mutableStateOf(user.profile.toProfile()) } UserProfileLayout( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index d4334dfed2..dcf8351166 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -36,11 +36,10 @@ import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* @Composable -fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: MutableState, drawerState: DrawerState) { +fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: MutableState) { val searchTextOrPassword = rememberSaveable { search } val users by remember { derivedStateOf { m.users.map { it.user } } } val filteredUsers by remember { derivedStateOf { filteredUsers(m, searchTextOrPassword.value) } } - val scope = rememberCoroutineScope() UserProfilesLayout( users = users, filteredUsers = filteredUsers, @@ -51,12 +50,6 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: addUser = { ModalManager.center.showModalCloseable { close -> CreateProfile(m, close) - if (appPlatform.isDesktop) { - // Hide settings to allow clicks to pass through to CreateProfile view - DisposableEffectOnGone(always = { scope.launch { drawerState.close() } }) { - // Show settings again to allow intercept clicks to close modals after profile creation finishes - scope.launch(NonCancellable) { drawerState.open() } } - } } }, activateUser = { user -> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index cfdb58b7e4..704c6533ae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -701,6 +701,10 @@ %s is verified %s is not verified + + Create public address + Your public address + Your settings Your SimpleX address @@ -849,6 +853,8 @@ Notifications will stop working until you re-launch the app + Public address + Create public address Create address Delete address? Your contacts will remain connected. @@ -1150,6 +1156,7 @@ YOU SETTINGS + CHAT DATABASE HELP SUPPORT SIMPLEX CHAT APP @@ -1714,6 +1721,7 @@ Remove image Font size Zoom + System mode Good afternoon! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_group.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_group.svg new file mode 100644 index 0000000000..158f4cfab0 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bedtime_moon.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bedtime_moon.svg new file mode 100644 index 0000000000..ed5bc12d4a --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bedtime_moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt new file mode 100644 index 0000000000..583d5437c3 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt @@ -0,0 +1,86 @@ +package chat.simplex.common.views.chatlist + +import androidx.compose.animation.* +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.User +import chat.simplex.common.model.UserInfo +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.MutableStateFlow + +@Composable +actual fun UserPickerInactiveUsersSection( + users: List, + stopped: Boolean, + onShowAllProfilesClicked: () -> Unit, + onUserClicked: (user: User) -> Unit, +) { + if (users.isNotEmpty()) { + val userRows = users.chunked(5) + val rowsToDisplay = if (userRows.size > 2) 2 else userRows.size + val horizontalPadding = DEFAULT_PADDING_HALF + 8.dp + + Column(Modifier + .padding(horizontal = horizontalPadding, vertical = DEFAULT_PADDING_HALF) + .height(55.dp * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) + ) { + ColumnWithScrollBar( + verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING) + ) { + val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (55.dp * 5)) / 5 + + userRows.forEach { row -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(spaceBetween), + ) { + row.forEach { u -> + UserPickerInactiveUserBadge(u, stopped, size = 55.dp) { + onUserClicked(u.user) + } + } + } + } + } + } + } + + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + stringResource(MR.strings.your_chat_profiles), + onShowAllProfilesClicked + ) +} + +@Composable +actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow, content: @Composable () -> Unit) { + AnimatedVisibility( + visible = pickerState.value.isVisible(), + enter = fadeIn(), + exit = fadeOut() + ) { + Box( + Modifier + .fillMaxSize() + .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { pickerState.value = AnimatedViewState.HIDING }), + contentAlignment = Alignment.TopStart + ) { + ColumnWithScrollBar(modifier) { + content() + } + } + } +} \ No newline at end of file From 46d774a8220d332b283ba3d8c1305d3dbb0d4a0c Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 11 Sep 2024 21:30:09 +0100 Subject: [PATCH 109/440] core: bulk forward missing files error handling (#4860) * add types * wip dump * collect errors * Update src/Simplex/Chat/View.hs Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * test with not received files * remove ciFileLoaded * undo refactoring * test for skipping missing file with text * add test for empty message * remove fdescribes * copy or cleanup files after collecting errors and forward reqs * don't forward w/t content * translate CIFSRcvAborted into FFENotAccepted * refactor * refactor --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- src/Simplex/Chat.hs | 211 ++++++++++++++++++--------------- src/Simplex/Chat/Controller.hs | 6 +- src/Simplex/Chat/Messages.hs | 39 +++--- src/Simplex/Chat/View.hs | 9 +- tests/ChatTests/Forward.hs | 45 +++++-- 5 files changed, 190 insertions(+), 120 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c140025648..8f9d985efe 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -980,47 +980,70 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") - APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of + APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL ignoreMissingFiles -> withUser $ \user -> case toCType of CTDirect -> do - cmrs <- prepareForward user - case L.nonEmpty cmrs of - Just cmrs' -> - withContactLock "forwardChatItem, to contact" toChatId $ - sendContactContentMessages user toChatId False itemTTL cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + cmrs <- prepareForwardOrFail user + withContactLock "forwardChatItem, to contact" toChatId $ + sendContactContentMessages user toChatId False itemTTL cmrs CTGroup -> do - cmrs <- prepareForward user - case L.nonEmpty cmrs of - Just cmrs' -> - withGroupLock "forwardChatItem, to group" toChatId $ - sendGroupContentMessages user toChatId False itemTTL cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + cmrs <- prepareForwardOrFail user + withGroupLock "forwardChatItem, to group" toChatId $ + sendGroupContentMessages user toChatId False itemTTL cmrs CTLocal -> do - cmrs <- prepareForward user - case L.nonEmpty cmrs of - Just cmrs' -> - createNoteFolderContentItems user toChatId cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + cmrs <- prepareForwardOrFail user + createNoteFolderContentItems user toChatId cmrs CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where - prepareForward :: User -> CM [ComposeMessageReq] + prepareForwardOrFail :: User -> CM (NonEmpty ComposeMessageReq) + prepareForwardOrFail user = do + (errs, cmrs) <- partitionEithers . L.toList <$> prepareForward user + case sortOn fst errs of + [] -> case L.nonEmpty (catMaybes cmrs) of + Nothing -> throwChatError $ CEInternalError "no chat items to forward" + Just cmrs' -> do + -- copy forwarded files, in case originals are deleted + withFilesFolder $ \filesFolder -> do + let toFolder cf@CryptoFile {filePath} = cf {filePath = filesFolder filePath} :: CryptoFile + forM_ cmrs' $ \case + (_, Just (fromCF, toCF)) -> + liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ + copyCryptoFile (toFolder fromCF) (toFolder toCF) + _ -> pure () + pure $ L.map fst cmrs' + errs'@((err, _) : _) -> do + -- cleanup files + withFilesFolder $ \filesFolder -> + forM_ cmrs $ \case + Just (_, Just (_, CryptoFile {filePath = toFPath})) -> do + let fsToPath = filesFolder toFPath + removeFile fsToPath `catchChatError` \e -> + logError ("prepareForwardOrFail: failed to clean up " <> tshow fsToPath <> ": " <> tshow e) + _ -> pure () + throwChatError $ case err of + FFENotAccepted _ -> CEForwardFilesNotAccepted files msgCount + FFEInProgress -> CEForwardFilesInProgress filesCount msgCount + FFEMissing -> CEForwardFilesMissing filesCount msgCount + FFEFailed -> CEForwardFilesFailed filesCount msgCount + where + msgCount = foldl' (\cnt (_, hasContent) -> if hasContent then cnt + 1 else cnt) 0 errs' + filesCount = foldl' (\cnt (e, _) -> if err == e then cnt + 1 else cnt) 0 errs' + files = foldl' (\ftIds -> \case (FFENotAccepted ftId, _) -> ftId : ftIds; _ -> ftIds) [] errs' + prepareForward :: User -> CM (NonEmpty (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile))))) prepareForward user = case fromCType of CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do ct <- withFastStore $ \db -> getContact db vr user fromChatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + items <- withFastStore $ \db -> mapM (getDirectChatItem db user fromChatId) itemIds mapM (ciComposeMsgReq ct) items where - getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) - getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId - ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq + ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) ciComposeMsgReq ct (CChatItem _ ci) = do (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci - let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + fc <- forwardContent ci mc + forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) + pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) where forwardName :: Contact -> ContactName forwardName Contact {profile = LocalProfile {displayName, localAlias}} @@ -1028,35 +1051,31 @@ processChatCommand' vr = \case | otherwise = displayName CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + items <- withFastStore $ \db -> mapM (getGroupChatItem db user fromChatId) itemIds mapM (ciComposeMsgReq gInfo) items where - getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) - getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId - ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq + ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) ciComposeMsgReq gInfo (CChatItem _ ci) = do (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci - let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + fc <- forwardContent ci mc + forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) + pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) where forwardName :: GroupInfo -> ContactName forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName CTLocal -> do - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + items <- withFastStore $ \db -> mapM (getLocalChatItem db user fromChatId) itemIds mapM ciComposeMsgReq items where - getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) - getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId - ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq + ciComposeMsgReq :: CChatItem 'CTLocal -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) ciComposeMsgReq (CChatItem _ ci) = do (mc, _) <- forwardMC ci - file <- forwardCryptoFile ci - let ciff = forwardCIFF ci Nothing - pure (ComposedMessage file Nothing mc, ciff) + fc <- forwardContent ci mc + forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do + let ciff = forwardCIFF ci Nothing + pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) CTContactRequest -> throwChatError $ CECommandError "not supported" CTContactConnection -> throwChatError $ CECommandError "not supported" where @@ -1070,48 +1089,53 @@ processChatCommand' vr = \case Nothing -> ciff Just CIFFUnknown -> ciff Just prevCIFF -> Just prevCIFF - forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile) - forwardCryptoFile ChatItem {file = Nothing} = pure Nothing - forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of - CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> - chatReadVar filesFolder >>= \case - Nothing -> - ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing) - Just filesFolder -> do - let fsFromPath = filesFolder filePath - ifM - (doesFileExist fsFromPath) - ( do - fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName - liftIO $ B.writeFile fsNewPath "" -- create empty file - encrypt <- chatReadVar encryptLocalFiles - cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing - let toCF = CryptoFile fsNewPath cfArgs - -- to keep forwarded file in case original is deleted - liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF - pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) - ) - (pure Nothing) - _ -> pure Nothing - copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () - copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do - fromSizeFull <- getFileSize fsFromPath - let fromSize = fromSizeFull - maybe 0 (const $ toInteger C.authTagSize) fromArgs - CF.withFile fromCF ReadMode $ \fromH -> - CF.withFile toCF WriteMode $ \toH -> do - copyChunks fromH toH fromSize - forM_ fromArgs $ \_ -> CF.hGetTag fromH - forM_ toArgs $ \_ -> liftIO $ CF.hPutTag toH + forwardContent :: ChatItem c d -> MsgContent -> CM (Either (ForwardFileError, Bool) (Maybe (MsgContent, Maybe (CryptoFile, CryptoFile)))) + forwardContent ChatItem {file = Nothing} mc = pure $ Right $ Just (mc, Nothing) + forwardContent ChatItem {file = Just ciFile} mc = case ciFile of + CIFile {fileId, fileName, fileStatus, fileSource = Just fromCF@CryptoFile {filePath}} -> case ciFileForwardError fileId fileStatus of + Just e -> pure $ ignoreOrError e + Nothing -> + chatReadVar filesFolder >>= \case + Nothing -> + ifM (doesFileExist filePath) (pure $ Right $ Just (mc, Just (fromCF, fromCF))) (pure $ ignoreOrError FFEMissing) + Just filesFolder -> + ifM (doesFileExist $ filesFolder filePath) forwardedFile (pure $ ignoreOrError FFEMissing) + where + forwardedFile = do + fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName + liftIO $ B.writeFile fsNewPath "" -- create empty file + encrypt <- chatReadVar encryptLocalFiles + cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing + let toCF = CryptoFile fsNewPath cfArgs + pure $ Right $ Just (mc, Just (fromCF, toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) + _ -> pure $ ignoreOrError FFEMissing where - copyChunks :: CF.CryptoFileHandle -> CF.CryptoFileHandle -> Integer -> ExceptT CF.FTCryptoError IO () - copyChunks r w size = do - let chSize = min size U.chunkSize - chSize' = fromIntegral chSize - size' = size - chSize - ch <- liftIO $ CF.hGet r chSize' - when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF" - liftIO . CF.hPut w $ LB.fromStrict ch - when (size' > 0) $ copyChunks r w size' + ignoreOrError err = if ignoreMissingFiles then Right (newContent mc) else Left (err, hasContent mc) + where + newContent mc' = case mc' of + MCImage {} -> Just (mc', Nothing) + _ | msgContentText mc' /= "" -> Just (MCText $ msgContentText mc', Nothing) + _ -> Nothing + hasContent mc' = isJust $ newContent mc' + copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () + copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do + fromSizeFull <- getFileSize fsFromPath + let fromSize = fromSizeFull - maybe 0 (const $ toInteger C.authTagSize) fromArgs + CF.withFile fromCF ReadMode $ \fromH -> + CF.withFile toCF WriteMode $ \toH -> do + copyChunks fromH toH fromSize + forM_ fromArgs $ \_ -> CF.hGetTag fromH + forM_ toArgs $ \_ -> liftIO $ CF.hPutTag toH + where + copyChunks :: CF.CryptoFileHandle -> CF.CryptoFileHandle -> Integer -> ExceptT CF.FTCryptoError IO () + copyChunks r w size = do + let chSize = min size U.chunkSize + chSize' = fromIntegral chSize + size' = size - chSize + ch <- liftIO $ CF.hGet r chSize' + when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF" + liftIO . CF.hPut w $ LB.fromStrict ch + when (size' > 0) $ copyChunks r w size' APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId APIChatRead chatRef@(ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of @@ -1831,17 +1855,17 @@ processChatCommand' vr = \case contactId <- withFastStore $ \db -> getContactIdByName db user fromContactName forwardedItemId <- withFastStore $ \db -> getDirectChatItemIdByText' db user contactId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing True ForwardGroupMessage toChatName fromGroupName fromMemberName_ forwardedMsg -> withUser $ \user -> do groupId <- withFastStore $ \db -> getGroupIdByName db user fromGroupName forwardedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId fromMemberName_ forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing True ForwardLocalMessage toChatName forwardedMsg -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) forwardedItemId <- withFastStore $ \db -> getLocalChatItemIdByText' db user folderId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing True SendMessage (ChatName cType name) msg -> withUser $ \user -> do let mc = MCText msg case cType of @@ -3332,9 +3356,10 @@ deleteFilesLocally files = delete fPath = removeFile fPath `catchAll` \_ -> removePathForcibly fPath `catchAll_` pure () - -- perform an action only if filesFolder is set (i.e. on mobile devices) - withFilesFolder :: (FilePath -> CM ()) -> CM () - withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action + +-- perform an action only if filesFolder is set (i.e. on mobile devices) +withFilesFolder :: (FilePath -> CM ()) -> CM () +withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action updateCallItemStatus :: User -> Contact -> Call -> WebRTCCallStatus -> Maybe MessageId -> CM () updateCallItemStatus user ct@Contact {contactId} Call {chatItemId} receivedStatus msgId_ = do @@ -7888,7 +7913,7 @@ chatCommandP = "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), - "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), + "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP <*> (" ignore_files=" *> onOffP <|> pure False)), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 7268d0734a..7c49e9e7e4 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -298,7 +298,7 @@ data ChatCommand | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} - | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} + | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int, ignoreMissingFiles :: Bool} | APIUserRead UserId | UserRead | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) @@ -1178,6 +1178,10 @@ data ChatErrorType | CEFallbackToSMPProhibited {fileId :: FileTransferId} | CEInlineFileProhibited {fileId :: FileTransferId} | CEInvalidQuote + | CEForwardFilesNotAccepted {files :: [FileTransferId], msgCount :: Int} -- contentCount is the count of messages if files are ignored + | CEForwardFilesInProgress {filesCount :: Int, msgCount :: Int} + | CEForwardFilesMissing {filesCount :: Int, msgCount :: Int} + | CEForwardFilesFailed {filesCount :: Int, msgCount :: Int} | CEInvalidForward | CEInvalidChatItemUpdate | CEInvalidChatItemDelete diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index f6c59cbcb5..0e4edbd685 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,7 +35,7 @@ import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time.Clock (UTCTime, diffUTCTime, nominalDay, NominalDiffTime) +import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay) import Data.Type.Equality import Data.Typeable (Typeable) import Database.SQLite.Simple.FromField (FromField (..)) @@ -577,23 +577,26 @@ ciFileEnded = \case CIFSRcvWarning {} -> False CIFSInvalid {} -> True -ciFileLoaded :: CIFileStatus d -> Bool -ciFileLoaded = \case - CIFSSndStored -> True - CIFSSndTransfer {} -> True - CIFSSndComplete -> True - CIFSSndCancelled -> True - CIFSSndError {} -> True - CIFSSndWarning {} -> True - CIFSRcvInvitation -> False - CIFSRcvAccepted -> False - CIFSRcvTransfer {} -> False - CIFSRcvAborted -> False - CIFSRcvCancelled -> False - CIFSRcvComplete -> True - CIFSRcvError {} -> False - CIFSRcvWarning {} -> False - CIFSInvalid {} -> False +data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing + deriving (Eq, Ord) + +ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError +ciFileForwardError fId = \case + CIFSSndStored -> Nothing + CIFSSndTransfer {} -> Nothing + CIFSSndComplete -> Nothing + CIFSSndCancelled -> Nothing + CIFSSndError {} -> Nothing + CIFSSndWarning {} -> Nothing + CIFSRcvInvitation -> Just $ FFENotAccepted fId + CIFSRcvAccepted -> Just FFEInProgress + CIFSRcvTransfer {} -> Just FFEInProgress + CIFSRcvAborted -> Just $ FFENotAccepted fId + CIFSRcvCancelled -> Just FFEFailed -- ? + CIFSRcvComplete -> Nothing + CIFSRcvError {} -> Just FFEFailed + CIFSRcvWarning {} -> Just FFEFailed + CIFSInvalid {} -> Just FFEFailed -- ? data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2158599c4b..e8814fb32d 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2034,7 +2034,11 @@ viewChatError isCmd logLevel testView = \case CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"] CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInvalidQuote -> ["cannot reply to this message"] - CEInvalidForward -> ["cannot forward this message"] + CEInvalidForward -> ["cannot forward message(s)"] + CEForwardFilesNotAccepted files msgCount -> [plain $ "Some files are not accepted: " <> intercalate ", " (map show files), showForwardMsgCount msgCount] + CEForwardFilesInProgress cnt msgCount -> [plain $ "Still receiving " <> show cnt <> " file(s)", showForwardMsgCount msgCount] + CEForwardFilesMissing cnt msgCount -> [plain $ show cnt <> " file(s) are missing", showForwardMsgCount msgCount] + CEForwardFilesFailed cnt msgCount -> [plain $ show cnt <> " file(s) failed", showForwardMsgCount msgCount] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] CEHasCurrentCall -> ["call already in progress"] @@ -2053,6 +2057,9 @@ viewChatError isCmd logLevel testView = \case CEPeerChatVRangeIncompatible -> ["peer chat protocol version range incompatible"] CEInternalError e -> ["internal chat error: " <> plain e] CEException e -> ["exception: " <> plain e] + where + showForwardMsgCount 0 = "No other messages to forward" + showForwardMsgCount msgCount = plain $ "Use ignore_files to forward " <> show msgCount <> " message(s)" -- e -> ["chat error: " <> sShow e] ChatErrorStore err -> case err of SEDuplicateName -> ["this display name is already used by user, contact or group"] diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index b339053abf..1897cddef5 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -671,6 +671,8 @@ testMultiForwardFiles = \alice bob cath -> withXFTPServer $ do setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp" copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test_3.jpg" + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test_4.jpg" copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" @@ -688,7 +690,10 @@ testMultiForwardFiles = let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" - alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + cm4 = "{\"filePath\": \"test_4.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 3\"}}" + cm5 = "{\"filePath\": \"test_3.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}" + + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "," <> cm5 <> "]") alice <# "@bob message without file" @@ -700,6 +705,13 @@ testMultiForwardFiles = alice <# "/f @bob test.pdf" alice <## "use /fc 2 to cancel sending" + alice <# "@bob sending file 3" + alice <# "/f @bob test_4.jpg" + alice <## "use /fc 3 to cancel sending" + + alice <# "/f @bob test_3.jpg" + alice <## "use /fc 4 to cancel sending" + bob <# "alice> message without file" bob <# "alice> sending file 1" @@ -710,8 +722,17 @@ testMultiForwardFiles = bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" bob <## "use /fr 2 [/ | ] to receive it" + bob <# "alice> sending file 3" + bob <# "alice> sends file test_4.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 3 [/ | ] to receive it" + + bob <# "alice> sends file test_3.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 4 [/ | ] to receive it" + alice <## "completed uploading file 1 (test.jpg) for bob" alice <## "completed uploading file 2 (test.pdf) for bob" + alice <## "completed uploading file 3 (test_4.jpg) for bob" + alice <## "completed uploading file 4 (test_3.jpg) for bob" bob ##> "/fr 1" bob @@ -720,6 +741,11 @@ testMultiForwardFiles = ] bob <## "completed receiving file 1 (test.jpg) from alice" + -- try to forward file without receiving 2nd file + let msgId1 = (read msgIdZero :: Int) + 1 + bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3) <> "," <> show (msgId1 + 4) <> "," <> show (msgId1 + 5)) + bob <### ["3 file(s) are missing", "Use ignore_files to forward 2 message(s)"] + bob ##> "/fr 2" bob <### [ "saving file 2 from alice to test.pdf", @@ -736,8 +762,7 @@ testMultiForwardFiles = dest2 `shouldBe` src2 -- forward file - let msgId1 = (read msgIdZero :: Int) + 1 - bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3)) + bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3) <> "," <> show (msgId1 + 4) <> "," <> show (msgId1 + 5) <> " ignore_files=on") -- messages printed for bob bob <# "@cath <- you @alice" @@ -749,12 +774,15 @@ testMultiForwardFiles = bob <# "@cath <- @alice" bob <## " sending file 1" bob <# "/f @cath test_1.jpg" - bob <## "use /fc 3 to cancel sending" + bob <## "use /fc 5 to cancel sending" bob <# "@cath <- @alice" bob <## " sending file 2" bob <# "/f @cath test_1.pdf" - bob <## "use /fc 4 to cancel sending" + bob <## "use /fc 6 to cancel sending" + + bob <# "@cath <- @alice" + bob <## " sending file 3" -- messages printed for cath cath <# "bob> -> forwarded" @@ -773,9 +801,12 @@ testMultiForwardFiles = cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)" cath <## "use /fr 2 [/ | ] to receive it" + cath <# "bob> -> forwarded" + cath <## " sending file 3" -- No file sent here + -- file transfer - bob <## "completed uploading file 3 (test_1.jpg) for cath" - bob <## "completed uploading file 4 (test_1.pdf) for cath" + bob <## "completed uploading file 5 (test_1.jpg) for cath" + bob <## "completed uploading file 6 (test_1.pdf) for cath" cath ##> "/fr 1" cath From 2ab5f141196c0c7cbd240ba703994aeb899f01d5 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:31:05 +0000 Subject: [PATCH 110/440] docs: remove outdated latest version number from downloads (#4854) --- docs/DOWNLOADS.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/DOWNLOADS.md b/docs/DOWNLOADS.md index 06850e76d2..df22ff64a2 100644 --- a/docs/DOWNLOADS.md +++ b/docs/DOWNLOADS.md @@ -1,14 +1,12 @@ --- title: Download SimpleX apps permalink: /downloads/index.html -revision: 03.07.2024 +revision: 09.09.2024 --- -| Updated 03.07.2024 | Languages: EN | +| Updated 09.09.2024 | Languages: EN | # Download SimpleX apps -The latest stable version is v5.8. - You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases). - [desktop](#desktop-app) From dfdb4af64618e4d123db3bad40e7ba12b7cbdc7e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 12 Sep 2024 08:52:09 +0100 Subject: [PATCH 111/440] Revert "core: bulk forward missing files error handling (#4860)" This reverts commit 46d774a8220d332b283ba3d8c1305d3dbb0d4a0c. --- src/Simplex/Chat.hs | 211 +++++++++++++++------------------ src/Simplex/Chat/Controller.hs | 6 +- src/Simplex/Chat/Messages.hs | 39 +++--- src/Simplex/Chat/View.hs | 9 +- tests/ChatTests/Forward.hs | 45 ++----- 5 files changed, 120 insertions(+), 190 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 8f9d985efe..c140025648 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -980,70 +980,47 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") - APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL ignoreMissingFiles -> withUser $ \user -> case toCType of + APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of CTDirect -> do - cmrs <- prepareForwardOrFail user - withContactLock "forwardChatItem, to contact" toChatId $ - sendContactContentMessages user toChatId False itemTTL cmrs + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + withContactLock "forwardChatItem, to contact" toChatId $ + sendContactContentMessages user toChatId False itemTTL cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTGroup -> do - cmrs <- prepareForwardOrFail user - withGroupLock "forwardChatItem, to group" toChatId $ - sendGroupContentMessages user toChatId False itemTTL cmrs + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + withGroupLock "forwardChatItem, to group" toChatId $ + sendGroupContentMessages user toChatId False itemTTL cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTLocal -> do - cmrs <- prepareForwardOrFail user - createNoteFolderContentItems user toChatId cmrs + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + createNoteFolderContentItems user toChatId cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where - prepareForwardOrFail :: User -> CM (NonEmpty ComposeMessageReq) - prepareForwardOrFail user = do - (errs, cmrs) <- partitionEithers . L.toList <$> prepareForward user - case sortOn fst errs of - [] -> case L.nonEmpty (catMaybes cmrs) of - Nothing -> throwChatError $ CEInternalError "no chat items to forward" - Just cmrs' -> do - -- copy forwarded files, in case originals are deleted - withFilesFolder $ \filesFolder -> do - let toFolder cf@CryptoFile {filePath} = cf {filePath = filesFolder filePath} :: CryptoFile - forM_ cmrs' $ \case - (_, Just (fromCF, toCF)) -> - liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ - copyCryptoFile (toFolder fromCF) (toFolder toCF) - _ -> pure () - pure $ L.map fst cmrs' - errs'@((err, _) : _) -> do - -- cleanup files - withFilesFolder $ \filesFolder -> - forM_ cmrs $ \case - Just (_, Just (_, CryptoFile {filePath = toFPath})) -> do - let fsToPath = filesFolder toFPath - removeFile fsToPath `catchChatError` \e -> - logError ("prepareForwardOrFail: failed to clean up " <> tshow fsToPath <> ": " <> tshow e) - _ -> pure () - throwChatError $ case err of - FFENotAccepted _ -> CEForwardFilesNotAccepted files msgCount - FFEInProgress -> CEForwardFilesInProgress filesCount msgCount - FFEMissing -> CEForwardFilesMissing filesCount msgCount - FFEFailed -> CEForwardFilesFailed filesCount msgCount - where - msgCount = foldl' (\cnt (_, hasContent) -> if hasContent then cnt + 1 else cnt) 0 errs' - filesCount = foldl' (\cnt (e, _) -> if err == e then cnt + 1 else cnt) 0 errs' - files = foldl' (\ftIds -> \case (FFENotAccepted ftId, _) -> ftId : ftIds; _ -> ftIds) [] errs' - prepareForward :: User -> CM (NonEmpty (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile))))) + prepareForward :: User -> CM [ComposeMessageReq] prepareForward user = case fromCType of CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do ct <- withFastStore $ \db -> getContact db vr user fromChatId - items <- withFastStore $ \db -> mapM (getDirectChatItem db user fromChatId) itemIds + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs mapM (ciComposeMsgReq ct) items where - ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) + getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) + getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId + ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq ciComposeMsgReq ct (CChatItem _ ci) = do (mc, mDir) <- forwardMC ci - fc <- forwardContent ci mc - forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do - let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) - pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) + file <- forwardCryptoFile ci + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) + pure (ComposedMessage file Nothing mc, ciff) where forwardName :: Contact -> ContactName forwardName Contact {profile = LocalProfile {displayName, localAlias}} @@ -1051,31 +1028,35 @@ processChatCommand' vr = \case | otherwise = displayName CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId - items <- withFastStore $ \db -> mapM (getGroupChatItem db user fromChatId) itemIds + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs mapM (ciComposeMsgReq gInfo) items where - ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) + getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) + getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId + ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq ciComposeMsgReq gInfo (CChatItem _ ci) = do (mc, mDir) <- forwardMC ci - fc <- forwardContent ci mc - forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do - let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) - pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) + file <- forwardCryptoFile ci + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) + pure (ComposedMessage file Nothing mc, ciff) where forwardName :: GroupInfo -> ContactName forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName CTLocal -> do - items <- withFastStore $ \db -> mapM (getLocalChatItem db user fromChatId) itemIds + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs mapM ciComposeMsgReq items where - ciComposeMsgReq :: CChatItem 'CTLocal -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) + getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) + getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId + ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq ciComposeMsgReq (CChatItem _ ci) = do (mc, _) <- forwardMC ci - fc <- forwardContent ci mc - forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do - let ciff = forwardCIFF ci Nothing - pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) + file <- forwardCryptoFile ci + let ciff = forwardCIFF ci Nothing + pure (ComposedMessage file Nothing mc, ciff) CTContactRequest -> throwChatError $ CECommandError "not supported" CTContactConnection -> throwChatError $ CECommandError "not supported" where @@ -1089,53 +1070,48 @@ processChatCommand' vr = \case Nothing -> ciff Just CIFFUnknown -> ciff Just prevCIFF -> Just prevCIFF - forwardContent :: ChatItem c d -> MsgContent -> CM (Either (ForwardFileError, Bool) (Maybe (MsgContent, Maybe (CryptoFile, CryptoFile)))) - forwardContent ChatItem {file = Nothing} mc = pure $ Right $ Just (mc, Nothing) - forwardContent ChatItem {file = Just ciFile} mc = case ciFile of - CIFile {fileId, fileName, fileStatus, fileSource = Just fromCF@CryptoFile {filePath}} -> case ciFileForwardError fileId fileStatus of - Just e -> pure $ ignoreOrError e - Nothing -> - chatReadVar filesFolder >>= \case - Nothing -> - ifM (doesFileExist filePath) (pure $ Right $ Just (mc, Just (fromCF, fromCF))) (pure $ ignoreOrError FFEMissing) - Just filesFolder -> - ifM (doesFileExist $ filesFolder filePath) forwardedFile (pure $ ignoreOrError FFEMissing) - where - forwardedFile = do - fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName - liftIO $ B.writeFile fsNewPath "" -- create empty file - encrypt <- chatReadVar encryptLocalFiles - cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing - let toCF = CryptoFile fsNewPath cfArgs - pure $ Right $ Just (mc, Just (fromCF, toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) - _ -> pure $ ignoreOrError FFEMissing + forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile) + forwardCryptoFile ChatItem {file = Nothing} = pure Nothing + forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of + CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> + chatReadVar filesFolder >>= \case + Nothing -> + ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing) + Just filesFolder -> do + let fsFromPath = filesFolder filePath + ifM + (doesFileExist fsFromPath) + ( do + fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName + liftIO $ B.writeFile fsNewPath "" -- create empty file + encrypt <- chatReadVar encryptLocalFiles + cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing + let toCF = CryptoFile fsNewPath cfArgs + -- to keep forwarded file in case original is deleted + liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF + pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) + ) + (pure Nothing) + _ -> pure Nothing + copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () + copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do + fromSizeFull <- getFileSize fsFromPath + let fromSize = fromSizeFull - maybe 0 (const $ toInteger C.authTagSize) fromArgs + CF.withFile fromCF ReadMode $ \fromH -> + CF.withFile toCF WriteMode $ \toH -> do + copyChunks fromH toH fromSize + forM_ fromArgs $ \_ -> CF.hGetTag fromH + forM_ toArgs $ \_ -> liftIO $ CF.hPutTag toH where - ignoreOrError err = if ignoreMissingFiles then Right (newContent mc) else Left (err, hasContent mc) - where - newContent mc' = case mc' of - MCImage {} -> Just (mc', Nothing) - _ | msgContentText mc' /= "" -> Just (MCText $ msgContentText mc', Nothing) - _ -> Nothing - hasContent mc' = isJust $ newContent mc' - copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () - copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do - fromSizeFull <- getFileSize fsFromPath - let fromSize = fromSizeFull - maybe 0 (const $ toInteger C.authTagSize) fromArgs - CF.withFile fromCF ReadMode $ \fromH -> - CF.withFile toCF WriteMode $ \toH -> do - copyChunks fromH toH fromSize - forM_ fromArgs $ \_ -> CF.hGetTag fromH - forM_ toArgs $ \_ -> liftIO $ CF.hPutTag toH - where - copyChunks :: CF.CryptoFileHandle -> CF.CryptoFileHandle -> Integer -> ExceptT CF.FTCryptoError IO () - copyChunks r w size = do - let chSize = min size U.chunkSize - chSize' = fromIntegral chSize - size' = size - chSize - ch <- liftIO $ CF.hGet r chSize' - when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF" - liftIO . CF.hPut w $ LB.fromStrict ch - when (size' > 0) $ copyChunks r w size' + copyChunks :: CF.CryptoFileHandle -> CF.CryptoFileHandle -> Integer -> ExceptT CF.FTCryptoError IO () + copyChunks r w size = do + let chSize = min size U.chunkSize + chSize' = fromIntegral chSize + size' = size - chSize + ch <- liftIO $ CF.hGet r chSize' + when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF" + liftIO . CF.hPut w $ LB.fromStrict ch + when (size' > 0) $ copyChunks r w size' APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId APIChatRead chatRef@(ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of @@ -1855,17 +1831,17 @@ processChatCommand' vr = \case contactId <- withFastStore $ \db -> getContactIdByName db user fromContactName forwardedItemId <- withFastStore $ \db -> getDirectChatItemIdByText' db user contactId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing True + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing ForwardGroupMessage toChatName fromGroupName fromMemberName_ forwardedMsg -> withUser $ \user -> do groupId <- withFastStore $ \db -> getGroupIdByName db user fromGroupName forwardedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId fromMemberName_ forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing True + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing ForwardLocalMessage toChatName forwardedMsg -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) forwardedItemId <- withFastStore $ \db -> getLocalChatItemIdByText' db user folderId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing True + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing SendMessage (ChatName cType name) msg -> withUser $ \user -> do let mc = MCText msg case cType of @@ -3356,10 +3332,9 @@ deleteFilesLocally files = delete fPath = removeFile fPath `catchAll` \_ -> removePathForcibly fPath `catchAll_` pure () - --- perform an action only if filesFolder is set (i.e. on mobile devices) -withFilesFolder :: (FilePath -> CM ()) -> CM () -withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action + -- perform an action only if filesFolder is set (i.e. on mobile devices) + withFilesFolder :: (FilePath -> CM ()) -> CM () + withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action updateCallItemStatus :: User -> Contact -> Call -> WebRTCCallStatus -> Maybe MessageId -> CM () updateCallItemStatus user ct@Contact {contactId} Call {chatItemId} receivedStatus msgId_ = do @@ -7913,7 +7888,7 @@ chatCommandP = "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), - "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP <*> (" ignore_files=" *> onOffP <|> pure False)), + "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 7c49e9e7e4..7268d0734a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -298,7 +298,7 @@ data ChatCommand | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} - | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int, ignoreMissingFiles :: Bool} + | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} | APIUserRead UserId | UserRead | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) @@ -1178,10 +1178,6 @@ data ChatErrorType | CEFallbackToSMPProhibited {fileId :: FileTransferId} | CEInlineFileProhibited {fileId :: FileTransferId} | CEInvalidQuote - | CEForwardFilesNotAccepted {files :: [FileTransferId], msgCount :: Int} -- contentCount is the count of messages if files are ignored - | CEForwardFilesInProgress {filesCount :: Int, msgCount :: Int} - | CEForwardFilesMissing {filesCount :: Int, msgCount :: Int} - | CEForwardFilesFailed {filesCount :: Int, msgCount :: Int} | CEInvalidForward | CEInvalidChatItemUpdate | CEInvalidChatItemDelete diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 0e4edbd685..f6c59cbcb5 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,7 +35,7 @@ import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay) +import Data.Time.Clock (UTCTime, diffUTCTime, nominalDay, NominalDiffTime) import Data.Type.Equality import Data.Typeable (Typeable) import Database.SQLite.Simple.FromField (FromField (..)) @@ -577,26 +577,23 @@ ciFileEnded = \case CIFSRcvWarning {} -> False CIFSInvalid {} -> True -data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing - deriving (Eq, Ord) - -ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError -ciFileForwardError fId = \case - CIFSSndStored -> Nothing - CIFSSndTransfer {} -> Nothing - CIFSSndComplete -> Nothing - CIFSSndCancelled -> Nothing - CIFSSndError {} -> Nothing - CIFSSndWarning {} -> Nothing - CIFSRcvInvitation -> Just $ FFENotAccepted fId - CIFSRcvAccepted -> Just FFEInProgress - CIFSRcvTransfer {} -> Just FFEInProgress - CIFSRcvAborted -> Just $ FFENotAccepted fId - CIFSRcvCancelled -> Just FFEFailed -- ? - CIFSRcvComplete -> Nothing - CIFSRcvError {} -> Just FFEFailed - CIFSRcvWarning {} -> Just FFEFailed - CIFSInvalid {} -> Just FFEFailed -- ? +ciFileLoaded :: CIFileStatus d -> Bool +ciFileLoaded = \case + CIFSSndStored -> True + CIFSSndTransfer {} -> True + CIFSSndComplete -> True + CIFSSndCancelled -> True + CIFSSndError {} -> True + CIFSSndWarning {} -> True + CIFSRcvInvitation -> False + CIFSRcvAccepted -> False + CIFSRcvTransfer {} -> False + CIFSRcvAborted -> False + CIFSRcvCancelled -> False + CIFSRcvComplete -> True + CIFSRcvError {} -> False + CIFSRcvWarning {} -> False + CIFSInvalid {} -> False data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index e8814fb32d..2158599c4b 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2034,11 +2034,7 @@ viewChatError isCmd logLevel testView = \case CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"] CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInvalidQuote -> ["cannot reply to this message"] - CEInvalidForward -> ["cannot forward message(s)"] - CEForwardFilesNotAccepted files msgCount -> [plain $ "Some files are not accepted: " <> intercalate ", " (map show files), showForwardMsgCount msgCount] - CEForwardFilesInProgress cnt msgCount -> [plain $ "Still receiving " <> show cnt <> " file(s)", showForwardMsgCount msgCount] - CEForwardFilesMissing cnt msgCount -> [plain $ show cnt <> " file(s) are missing", showForwardMsgCount msgCount] - CEForwardFilesFailed cnt msgCount -> [plain $ show cnt <> " file(s) failed", showForwardMsgCount msgCount] + CEInvalidForward -> ["cannot forward this message"] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] CEHasCurrentCall -> ["call already in progress"] @@ -2057,9 +2053,6 @@ viewChatError isCmd logLevel testView = \case CEPeerChatVRangeIncompatible -> ["peer chat protocol version range incompatible"] CEInternalError e -> ["internal chat error: " <> plain e] CEException e -> ["exception: " <> plain e] - where - showForwardMsgCount 0 = "No other messages to forward" - showForwardMsgCount msgCount = plain $ "Use ignore_files to forward " <> show msgCount <> " message(s)" -- e -> ["chat error: " <> sShow e] ChatErrorStore err -> case err of SEDuplicateName -> ["this display name is already used by user, contact or group"] diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index 1897cddef5..b339053abf 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -671,8 +671,6 @@ testMultiForwardFiles = \alice bob cath -> withXFTPServer $ do setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp" copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" - copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test_3.jpg" - copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test_4.jpg" copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" @@ -690,10 +688,7 @@ testMultiForwardFiles = let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" - cm4 = "{\"filePath\": \"test_4.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 3\"}}" - cm5 = "{\"filePath\": \"test_3.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}" - - alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "," <> cm5 <> "]") + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") alice <# "@bob message without file" @@ -705,13 +700,6 @@ testMultiForwardFiles = alice <# "/f @bob test.pdf" alice <## "use /fc 2 to cancel sending" - alice <# "@bob sending file 3" - alice <# "/f @bob test_4.jpg" - alice <## "use /fc 3 to cancel sending" - - alice <# "/f @bob test_3.jpg" - alice <## "use /fc 4 to cancel sending" - bob <# "alice> message without file" bob <# "alice> sending file 1" @@ -722,17 +710,8 @@ testMultiForwardFiles = bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" bob <## "use /fr 2 [/ | ] to receive it" - bob <# "alice> sending file 3" - bob <# "alice> sends file test_4.jpg (136.5 KiB / 139737 bytes)" - bob <## "use /fr 3 [/ | ] to receive it" - - bob <# "alice> sends file test_3.jpg (136.5 KiB / 139737 bytes)" - bob <## "use /fr 4 [/ | ] to receive it" - alice <## "completed uploading file 1 (test.jpg) for bob" alice <## "completed uploading file 2 (test.pdf) for bob" - alice <## "completed uploading file 3 (test_4.jpg) for bob" - alice <## "completed uploading file 4 (test_3.jpg) for bob" bob ##> "/fr 1" bob @@ -741,11 +720,6 @@ testMultiForwardFiles = ] bob <## "completed receiving file 1 (test.jpg) from alice" - -- try to forward file without receiving 2nd file - let msgId1 = (read msgIdZero :: Int) + 1 - bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3) <> "," <> show (msgId1 + 4) <> "," <> show (msgId1 + 5)) - bob <### ["3 file(s) are missing", "Use ignore_files to forward 2 message(s)"] - bob ##> "/fr 2" bob <### [ "saving file 2 from alice to test.pdf", @@ -762,7 +736,8 @@ testMultiForwardFiles = dest2 `shouldBe` src2 -- forward file - bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3) <> "," <> show (msgId1 + 4) <> "," <> show (msgId1 + 5) <> " ignore_files=on") + let msgId1 = (read msgIdZero :: Int) + 1 + bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3)) -- messages printed for bob bob <# "@cath <- you @alice" @@ -774,15 +749,12 @@ testMultiForwardFiles = bob <# "@cath <- @alice" bob <## " sending file 1" bob <# "/f @cath test_1.jpg" - bob <## "use /fc 5 to cancel sending" + bob <## "use /fc 3 to cancel sending" bob <# "@cath <- @alice" bob <## " sending file 2" bob <# "/f @cath test_1.pdf" - bob <## "use /fc 6 to cancel sending" - - bob <# "@cath <- @alice" - bob <## " sending file 3" + bob <## "use /fc 4 to cancel sending" -- messages printed for cath cath <# "bob> -> forwarded" @@ -801,12 +773,9 @@ testMultiForwardFiles = cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)" cath <## "use /fr 2 [/ | ] to receive it" - cath <# "bob> -> forwarded" - cath <## " sending file 3" -- No file sent here - -- file transfer - bob <## "completed uploading file 5 (test_1.jpg) for cath" - bob <## "completed uploading file 6 (test_1.pdf) for cath" + bob <## "completed uploading file 3 (test_1.jpg) for cath" + bob <## "completed uploading file 4 (test_1.pdf) for cath" cath ##> "/fr 1" cath From 5f0b5c5a9fe83d4f42c6b0f5bf6041349fc4e55c Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:51:57 +0000 Subject: [PATCH 112/440] desktop: fix vlc dependency (2) (#4869) --- apps/multiplatform/common/build.gradle.kts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 1670672753..1aaa061daa 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -99,10 +99,15 @@ kotlin { val desktopMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0") - implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") + implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") { + exclude("net.java.dev.jna") + } + // For jSystemThemeDetector only + implementation("net.java.dev.jna:jna-platform:5.14.0") implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT") implementation("org.slf4j:slf4j-simple:2.0.12") implementation("uk.co.caprica:vlcj:4.8.3") + implementation("net.java.dev.jna:jna:5.14.0") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a") implementation("com.squareup.okhttp3:okhttp:4.12.0") From f6f204467511ba716d3c4ee6832cfd14ffb49abc Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 12 Sep 2024 15:21:29 +0100 Subject: [PATCH 113/440] core: plan forwarding chat items, api types (#4865) * core: plan forwarding chat items, api types * remove empty content, refactor get items * another refactor * plan * test * more tests * text --- src/Simplex/Chat.hs | 192 ++++++++++++++++++++------------- src/Simplex/Chat/Controller.hs | 11 ++ src/Simplex/Chat/Messages.hs | 21 ++++ src/Simplex/Chat/View.hs | 17 ++- tests/ChatTests/Forward.hs | 91 ++++++++++++---- 5 files changed, 241 insertions(+), 91 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c140025648..fb5c4b4962 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -843,9 +843,7 @@ processChatCommand' vr = \case CTContactConnection -> pure $ chatCmdError (Just user) "not supported" APIDeleteChatItem (ChatRef cType chatId) itemIds mode -> withUser $ \user -> case cType of CTDirect -> withContactLock "deleteChatItem" chatId $ do - ct <- withStore $ \db -> getContact db vr user chatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + (ct, items) <- getCommandDirectChatItems user chatId itemIds case mode of CIDMInternal -> deleteDirectCIs user ct items True False CIDMBroadcast -> do @@ -858,13 +856,9 @@ processChatCommand' vr = \case if featureAllowed SCFFullDelete forUser ct then deleteDirectCIs user ct items True False else markDirectCIsDeleted user ct items True =<< liftIO getCurrentTime - where - getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) - getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user chatId itemId CTGroup -> withGroupLock "deleteChatItem" chatId $ do - Group gInfo ms <- withStore $ \db -> getGroup db vr user chatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + (gInfo, items) <- getCommandGroupChatItems user chatId itemIds + ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo case mode of CIDMInternal -> deleteGroupCIs user gInfo items True False Nothing =<< liftIO getCurrentTime CIDMBroadcast -> do @@ -874,17 +868,9 @@ processChatCommand' vr = \case events = L.nonEmpty $ map (`XMsgDel` Nothing) msgIds mapM_ (sendGroupMessages user gInfo ms) events delGroupChatItems user gInfo items Nothing - where - getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) - getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user chatId itemId CTLocal -> do - nf <- withStore $ \db -> getNoteFolder db user chatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + (nf, items) <- getCommandLocalChatItems user chatId itemIds deleteLocalCIs user nf items True False - where - getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) - getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user chatId itemId CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where @@ -902,9 +888,8 @@ processChatCommand' vr = \case itemsMsgIds :: [CChatItem c] -> [SharedMsgId] itemsMsgIds = mapMaybe (\(CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId}}) -> itemSharedMsgId) APIDeleteMemberChatItem gId itemIds -> withUser $ \user -> withGroupLock "deleteChatItem" gId $ do - Group gInfo@GroupInfo {membership} ms <- withStore $ \db -> getGroup db vr user gId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db user) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + (gInfo@GroupInfo {membership}, items) <- getCommandGroupChatItems user gId itemIds + ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo assertDeletable gInfo items assertUserGroupRole gInfo GRAdmin let msgMemIds = itemsMsgMemIds gInfo items @@ -912,8 +897,6 @@ processChatCommand' vr = \case mapM_ (sendGroupMessages user gInfo ms) events delGroupChatItems user gInfo items (Just membership) where - getGroupCI :: DB.Connection -> User -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) - getGroupCI db user itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user gId itemId assertDeletable :: GroupInfo -> [CChatItem 'CTGroup] -> CM () assertDeletable GroupInfo {membership = GroupMember {memberRole = membershipMemRole}} items = unless (all itemDeletable items) $ throwChatError CEInvalidChatItemDelete @@ -980,6 +963,51 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") + APIPlanForwardChatItems (ChatRef fromCType fromChatId) itemIds -> withUser $ \user -> case fromCType of + CTDirect -> planForward user . snd =<< getCommandDirectChatItems user fromChatId itemIds + CTGroup -> planForward user . snd =<< getCommandGroupChatItems user fromChatId itemIds + CTLocal -> planForward user . snd =<< getCommandLocalChatItems user fromChatId itemIds + CTContactRequest -> pure $ chatCmdError (Just user) "not supported" + CTContactConnection -> pure $ chatCmdError (Just user) "not supported" + where + planForward :: User -> [CChatItem c] -> CM ChatResponse + planForward user items = do + (itemIds', forwardErrors) <- unzip <$> mapM planItemForward items + let forwardConfirmation = case catMaybes forwardErrors of + [] -> Nothing + errs -> Just $ case mainErr of + FFENotAccepted _ -> FCFilesNotAccepted fileIds + FFEInProgress -> FCFilesInProgress filesCount + FFEMissing -> FCFilesMissing filesCount + FFEFailed -> FCFilesFailed filesCount + where + mainErr = minimum errs + fileIds = catMaybes $ map (\case FFENotAccepted ftId -> Just ftId; _ -> Nothing) errs + filesCount = length $ filter (mainErr ==) errs + pure CRForwardPlan {user, itemsCount = length itemIds, chatItemIds = catMaybes itemIds', forwardConfirmation} + where + planItemForward :: CChatItem c -> CM (Maybe ChatItemId, Maybe ForwardFileError) + planItemForward (CChatItem _ ci) = forwardMsgContent ci >>= maybe (pure (Nothing, Nothing)) (forwardContentPlan ci) + forwardContentPlan :: ChatItem c d -> MsgContent -> CM (Maybe ChatItemId, Maybe ForwardFileError) + forwardContentPlan ChatItem {file, meta = CIMeta {itemId}} mc = case file of + Nothing -> pure (Just itemId, Nothing) + Just CIFile {fileId, fileStatus, fileSource} -> case ciFileForwardError fileId fileStatus of + Just err -> pure $ itemIdWithoutFile err + Nothing -> case fileSource of + Just CryptoFile {filePath} -> do + exists <- doesFileExist . maybe filePath ( filePath) =<< chatReadVar filesFolder + pure $ if exists then (Just itemId, Nothing) else itemIdWithoutFile FFEMissing + Nothing -> pure $ itemIdWithoutFile FFEMissing + where + itemIdWithoutFile err = (if hasContent then Just itemId else Nothing, Just err) + hasContent = case mc of + MCText _ -> True + MCLink {} -> True + MCImage {} -> True + MCVideo {text} -> text /= "" + MCVoice {text} -> text /= "" + MCFile t -> t /= "" + MCUnknown {} -> True APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of CTDirect -> do cmrs <- prepareForward user @@ -987,96 +1015,76 @@ processChatCommand' vr = \case Just cmrs' -> withContactLock "forwardChatItem, to contact" toChatId $ sendContactContentMessages user toChatId False itemTTL cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + Nothing -> pure $ CRNewChatItems user [] CTGroup -> do cmrs <- prepareForward user case L.nonEmpty cmrs of Just cmrs' -> withGroupLock "forwardChatItem, to group" toChatId $ sendGroupContentMessages user toChatId False itemTTL cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + Nothing -> pure $ CRNewChatItems user [] CTLocal -> do cmrs <- prepareForward user case L.nonEmpty cmrs of Just cmrs' -> createNoteFolderContentItems user toChatId cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + Nothing -> pure $ CRNewChatItems user [] CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where prepareForward :: User -> CM [ComposeMessageReq] prepareForward user = case fromCType of CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do - ct <- withFastStore $ \db -> getContact db vr user fromChatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - mapM (ciComposeMsgReq ct) items + (ct, items) <- getCommandDirectChatItems user fromChatId itemIds + catMaybes <$> mapM (\ci -> ciComposeMsgReq ct ci <$$> prepareMsgReq ci) items where - getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) - getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId - ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq - ciComposeMsgReq ct (CChatItem _ ci) = do - (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci + ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq + ciComposeMsgReq ct (CChatItem md ci) (mc', file) = let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) (toMsgDirection md) (Just fromChatId) (Just itemId)) + in (ComposedMessage file Nothing mc', ciff) where forwardName :: Contact -> ContactName forwardName Contact {profile = LocalProfile {displayName, localAlias}} | localAlias /= "" = localAlias | otherwise = displayName CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do - gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - mapM (ciComposeMsgReq gInfo) items + (gInfo, items) <- getCommandGroupChatItems user fromChatId itemIds + catMaybes <$> mapM (\ci -> ciComposeMsgReq gInfo ci <$$> prepareMsgReq ci) items where - getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) - getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId - ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq - ciComposeMsgReq gInfo (CChatItem _ ci) = do - (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci + ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq + ciComposeMsgReq gInfo (CChatItem md ci) (mc', file) = do let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) (toMsgDirection md) (Just fromChatId) (Just itemId)) + in (ComposedMessage file Nothing mc', ciff) where forwardName :: GroupInfo -> ContactName forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName CTLocal -> do - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - mapM ciComposeMsgReq items + (_, items) <- getCommandLocalChatItems user fromChatId itemIds + catMaybes <$> mapM (\ci -> ciComposeMsgReq ci <$$> prepareMsgReq ci) items where - getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) - getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId - ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq - ciComposeMsgReq (CChatItem _ ci) = do - (mc, _) <- forwardMC ci - file <- forwardCryptoFile ci + ciComposeMsgReq :: CChatItem 'CTLocal -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq + ciComposeMsgReq (CChatItem _ ci) (mc', file) = let ciff = forwardCIFF ci Nothing - pure (ComposedMessage file Nothing mc, ciff) + in (ComposedMessage file Nothing mc', ciff) CTContactRequest -> throwChatError $ CECommandError "not supported" CTContactConnection -> throwChatError $ CECommandError "not supported" where - forwardMC :: ChatItem c d -> CM (MsgContent, MsgDirection) - forwardMC ChatItem {meta = CIMeta {itemDeleted = Just _}} = throwChatError CEInvalidForward - forwardMC ChatItem {content = CISndMsgContent fmc} = pure (fmc, MDSnd) - forwardMC ChatItem {content = CIRcvMsgContent fmc} = pure (fmc, MDRcv) - forwardMC _ = throwChatError CEInvalidForward + prepareMsgReq :: CChatItem c -> CM (Maybe (MsgContent, Maybe CryptoFile)) + prepareMsgReq (CChatItem _ ci) = forwardMsgContent ci $>>= forwardContent ci forwardCIFF :: ChatItem c d -> Maybe CIForwardedFrom -> Maybe CIForwardedFrom forwardCIFF ChatItem {meta = CIMeta {itemForwarded}} ciff = case itemForwarded of Nothing -> ciff Just CIFFUnknown -> ciff Just prevCIFF -> Just prevCIFF - forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile) - forwardCryptoFile ChatItem {file = Nothing} = pure Nothing - forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of + forwardContent :: ChatItem c d -> MsgContent -> CM (Maybe (MsgContent, Maybe CryptoFile)) + forwardContent ChatItem {file = Nothing} mc = pure $ Just (mc, Nothing) + forwardContent ChatItem {file = Just ciFile} mc = case ciFile of CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> chatReadVar filesFolder >>= \case Nothing -> - ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing) + ifM (doesFileExist filePath) (pure $ Just (mc, Just fromCF)) (pure contentWithoutFile) Just filesFolder -> do let fsFromPath = filesFolder filePath ifM @@ -1089,10 +1097,17 @@ processChatCommand' vr = \case let toCF = CryptoFile fsNewPath cfArgs -- to keep forwarded file in case original is deleted liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF - pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) + pure $ Just (mc, Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) ) - (pure Nothing) - _ -> pure Nothing + (pure contentWithoutFile) + _ -> pure contentWithoutFile + where + contentWithoutFile = case mc of + MCImage {} -> Just (mc, Nothing) + MCLink {} -> Just (mc, Nothing) + _ | contentText /= "" -> Just (MCText contentText, Nothing) + _ -> Nothing + contentText = msgContentText mc copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do fromSizeFull <- getFileSize fsFromPath @@ -3087,6 +3102,38 @@ processChatCommand' vr = \case | (msg_, (ComposedMessage {msgContent}, itemForwarded), f, q) <- zipWith4 (,,,) msgs_ (L.toList cmrs') (L.toList ciFiles_) (L.toList quotedItems_) ] + getCommandDirectChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (Contact, [CChatItem 'CTDirect]) + getCommandDirectChatItems user ctId itemIds = do + ct <- withFastStore $ \db -> getContact db vr user ctId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure (ct, items) + where + getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) + getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user ctId itemId + getCommandGroupChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (GroupInfo, [CChatItem 'CTGroup]) + getCommandGroupChatItems user gId itemIds = do + gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure (gInfo, items) + where + getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) + getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user gId itemId + getCommandLocalChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (NoteFolder, [CChatItem 'CTLocal]) + getCommandLocalChatItems user nfId itemIds = do + nf <- withStore $ \db -> getNoteFolder db user nfId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure (nf, items) + where + getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) + getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user nfId itemId + forwardMsgContent :: ChatItem c d -> CM (Maybe MsgContent) + forwardMsgContent ChatItem {meta = CIMeta {itemDeleted = Just _}} = pure Nothing -- this can be deleted after selection + forwardMsgContent ChatItem {content = CISndMsgContent fmc} = pure $ Just fmc + forwardMsgContent ChatItem {content = CIRcvMsgContent fmc} = pure $ Just fmc + forwardMsgContent _ = throwChatError CEInvalidForward createNoteFolderContentItems :: User -> NoteFolderId -> NonEmpty ComposeMessageReq -> CM ChatResponse createNoteFolderContentItems user folderId cmrs = do assertNoQuotes @@ -7888,6 +7935,7 @@ chatCommandP = "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), + "/_forward plan " *> (APIPlanForwardChatItems <$> chatRefP <*> _strP), "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 7268d0734a..99739cc6bf 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -298,6 +298,7 @@ data ChatCommand | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} + | APIPlanForwardChatItems {fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId} | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} | APIUserRead UserId | UserRead @@ -649,6 +650,7 @@ data ChatResponse | CRContactRequestAlreadyAccepted {user :: User, contact :: Contact} | CRLeftMemberUser {user :: User, groupInfo :: GroupInfo} | CRGroupDeletedUser {user :: User, groupInfo :: GroupInfo} + | CRForwardPlan {user :: User, itemsCount :: Int, chatItemIds :: [ChatItemId], forwardConfirmation :: Maybe ForwardConfirmation} | CRRcvFileDescrReady {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer, rcvFileDescr :: RcvFileDescr} | CRRcvFileAccepted {user :: User, chatItem :: AChatItem} | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} @@ -905,6 +907,13 @@ connectionPlanProceed = \case GLPConnectingConfirmReconnect -> True _ -> False +data ForwardConfirmation + = FCFilesNotAccepted {fileIds :: [FileTransferId]} + | FCFilesInProgress {filesCount :: Int} + | FCFilesMissing {filesCount :: Int} + | FCFilesFailed {filesCount :: Int} + deriving (Show) + newtype UserPwd = UserPwd {unUserPwd :: Text} deriving (Eq, Show) @@ -1463,6 +1472,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan) +$(JQ.deriveJSON defaultJSON ''ForwardConfirmation) + $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RHE") ''RemoteHostError) diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index f6c59cbcb5..2b21857408 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -595,6 +595,27 @@ ciFileLoaded = \case CIFSRcvWarning {} -> False CIFSInvalid {} -> False +data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing + deriving (Eq, Ord) + +ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError +ciFileForwardError fId = \case + CIFSSndStored -> Nothing + CIFSSndTransfer {} -> Nothing + CIFSSndComplete -> Nothing + CIFSSndCancelled -> Nothing + CIFSSndError {} -> Nothing + CIFSSndWarning {} -> Nothing + CIFSRcvInvitation -> Just $ FFENotAccepted fId + CIFSRcvAccepted -> Just FFEInProgress + CIFSRcvTransfer {} -> Just FFEInProgress + CIFSRcvAborted -> Just $ FFENotAccepted fId + CIFSRcvCancelled -> Just FFEFailed + CIFSRcvComplete -> Nothing + CIFSRcvError {} -> Just FFEFailed + CIFSRcvWarning {} -> Just FFEFailed + CIFSInvalid {} -> Just FFEFailed + data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d) deriving instance Show ACIFileStatus diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2158599c4b..cb686ef2b0 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -203,6 +203,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um] CRUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m] CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"] + CRForwardPlan u count itemIds fc -> ttyUser u $ viewForwardPlan count itemIds fc CRRcvFileDescrReady _ _ _ _ -> [] CRRcvFileProgressXFTP {} -> [] CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci @@ -930,6 +931,20 @@ viewUserContactLinkDeleted = "To create a new chat address use " <> highlight' "/ad" ] +viewForwardPlan :: Int -> [ChatItemId] -> Maybe ForwardConfirmation -> [StyledString] +viewForwardPlan count itemIds = maybe [forwardCount] $ \fc -> [confirmation fc, forwardCount] + where + confirmation = \case + FCFilesNotAccepted fileIds -> plain $ "Files can be received: " <> intercalate ", " (map show fileIds) + FCFilesInProgress cnt -> plain $ "Still receiving " <> show cnt <> " file(s)" + FCFilesMissing cnt -> plain $ show cnt <> " file(s) are missing" + FCFilesFailed cnt -> plain $ "Receiving " <> show cnt <> " file(s) failed" + forwardCount + | count == len = "all messages can be forwarded" + | len == 0 = "nothing to forward" + | otherwise = plain $ show len <> " message(s) out of " <> show count <> " can be forwarded" + len = length itemIds + connReqContact_ :: StyledString -> ConnReqContact -> [StyledString] connReqContact_ intro cReq = [ intro, @@ -2034,7 +2049,7 @@ viewChatError isCmd logLevel testView = \case CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"] CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInvalidQuote -> ["cannot reply to this message"] - CEInvalidForward -> ["cannot forward this message"] + CEInvalidForward -> ["cannot forward message(s)"] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] CEHasCurrentCall -> ["call already in progress"] diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index b339053abf..8f1595fd6c 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -7,7 +7,11 @@ import ChatClient import ChatTests.Utils import Control.Concurrent (threadDelay) import qualified Data.ByteString.Char8 as B -import System.Directory (copyFile, doesFileExist) +import Data.List (intercalate) +import qualified Data.Text as T +import System.Directory (copyFile, doesFileExist, removeFile) +import Simplex.Chat (fixedImagePreview) +import Simplex.Chat.Types (ImageData (..)) import Test.Hspec hiding (it) chatForwardTests :: SpecWith FilePath @@ -613,6 +617,8 @@ testForwardContactToContactMulti = alice <# "bob> hey" msgId2 <- lastItemId alice + alice ##> ("/_forward plan @2 " <> msgId1 <> "," <> msgId2) + alice <## "all messages can be forwarded" alice ##> ("/_forward @3 @2 " <> msgId1 <> "," <> msgId2) alice <# "@cath <- you @bob" alice <## " hi" @@ -642,6 +648,8 @@ testForwardGroupToGroupMulti = alice <# "#team bob> hey" msgId2 <- lastItemId alice + alice ##> ("/_forward plan #1 " <> msgId1 <> "," <> msgId2) + alice <## "all messages can be forwarded" alice ##> ("/_forward #2 #1 " <> msgId1 <> "," <> msgId2) alice <# "#club <- you #team" alice <## " hi" @@ -672,6 +680,7 @@ testMultiForwardFiles = setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp" copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf" setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" connectUsers alice bob @@ -686,32 +695,46 @@ testMultiForwardFiles = -- send original files let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" - cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" - cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" - alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + ImageData img = fixedImagePreview + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"image\", \"image\":\"" <> T.unpack img <> "\", \"text\": \"\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}" + cm4 = "{\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"message with large file\"}}" + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "]") alice <# "@bob message without file" - alice <# "@bob sending file 1" alice <# "/f @bob test.jpg" alice <## "use /fc 1 to cancel sending" - alice <# "@bob sending file 2" alice <# "/f @bob test.pdf" alice <## "use /fc 2 to cancel sending" + alice <# "@bob message with large file" + alice <# "/f @bob test_1MB.pdf" + alice <## "use /fc 3 to cancel sending" + bob <# "alice> message without file" - bob <# "alice> sending file 1" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" bob <## "use /fr 1 [/ | ] to receive it" - bob <# "alice> sending file 2" bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" bob <## "use /fr 2 [/ | ] to receive it" + bob <# "alice> message with large file" + bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)" + bob <## "use /fr 3 [/ | ] to receive it" + alice <## "completed uploading file 1 (test.jpg) for bob" alice <## "completed uploading file 2 (test.pdf) for bob" + alice <## "completed uploading file 3 (test_1MB.pdf) for bob" + + -- IDs to forward + let msgId1 = (read msgIdZero :: Int) + 1 + msgIds = intercalate "," $ map show [msgId1, msgId1 + 1, msgId1 + 2, msgId1 + 3, msgId1 + 4] + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "Files can be received: 1, 2, 3" + bob <## "4 message(s) out of 5 can be forwarded" bob ##> "/fr 1" bob @@ -720,6 +743,10 @@ testMultiForwardFiles = ] bob <## "completed receiving file 1 (test.jpg) from alice" + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "Files can be received: 2, 3" + bob <## "4 message(s) out of 5 can be forwarded" + bob ##> "/fr 2" bob <### [ "saving file 2 from alice to test.pdf", @@ -736,8 +763,10 @@ testMultiForwardFiles = dest2 `shouldBe` src2 -- forward file - let msgId1 = (read msgIdZero :: Int) + 1 - bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3)) + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "Files can be received: 3" + bob <## "all messages can be forwarded" + bob ##> ("/_forward @3 @2 " <> msgIds) -- messages printed for bob bob <# "@cath <- you @alice" @@ -747,14 +776,17 @@ testMultiForwardFiles = bob <## " message without file" bob <# "@cath <- @alice" - bob <## " sending file 1" + bob <## " test_1.jpg" bob <# "/f @cath test_1.jpg" - bob <## "use /fc 3 to cancel sending" + bob <## "use /fc 4 to cancel sending" bob <# "@cath <- @alice" - bob <## " sending file 2" + bob <## " test_1.pdf" bob <# "/f @cath test_1.pdf" - bob <## "use /fc 4 to cancel sending" + bob <## "use /fc 5 to cancel sending" + + bob <# "@cath <- @alice" + bob <## " message with large file" -- messages printed for cath cath <# "bob> -> forwarded" @@ -764,18 +796,21 @@ testMultiForwardFiles = cath <## " message without file" cath <# "bob> -> forwarded" - cath <## " sending file 1" + cath <## " test_1.jpg" cath <# "bob> sends file test_1.jpg (136.5 KiB / 139737 bytes)" cath <## "use /fr 1 [/ | ] to receive it" cath <# "bob> -> forwarded" - cath <## " sending file 2" + cath <## " test_1.pdf" cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)" cath <## "use /fr 2 [/ | ] to receive it" + cath <# "bob> -> forwarded" + cath <## " message with large file" + -- file transfer - bob <## "completed uploading file 3 (test_1.jpg) for cath" - bob <## "completed uploading file 4 (test_1.pdf) for cath" + bob <## "completed uploading file 4 (test_1.jpg) for cath" + bob <## "completed uploading file 5 (test_1.pdf) for cath" cath ##> "/fr 1" cath @@ -801,6 +836,26 @@ testMultiForwardFiles = dest2C <- B.readFile "./tests/tmp/cath_app_files/test_1.pdf" dest2C `shouldBe` src2B + bob ##> "/fr 3" + bob + <### [ "saving file 3 from alice to test_1MB.pdf", + "started receiving file 3 (test_1MB.pdf) from alice" + ] + bob <## "completed receiving file 3 (test_1MB.pdf) from alice" + + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "all messages can be forwarded" + + removeFile "./tests/tmp/bob_app_files/test_1MB.pdf" + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "1 file(s) are missing" + bob <## "all messages can be forwarded" + + removeFile "./tests/tmp/bob_app_files/test.pdf" + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "2 file(s) are missing" + bob <## "4 message(s) out of 5 can be forwarded" + -- deleting original file doesn't delete forwarded file checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do bob ##> "/clear alice" From 7aec147ceca6292f8ce918ba4c5a4d6f48614b5e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 12 Sep 2024 22:19:17 +0100 Subject: [PATCH 114/440] core: update simplexmq 6.0.4.0 --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index fc958929af..51b35e0db7 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 946e16339e16e026f51185ebfb48c3a0c5a5b2e1 + tag: f5e666ae4f41351d5d5ac416cd6fb1d5fadc8ab7 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index fa3c6d4839..ec230ee09e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."946e16339e16e026f51185ebfb48c3a0c5a5b2e1" = "1jkx2f14h5krmy467zyifsc58dys89pkpn08cyf1q9v78in7nwfd"; + "https://github.com/simplex-chat/simplexmq.git"."f5e666ae4f41351d5d5ac416cd6fb1d5fadc8ab7" = "1cq9apm9vp40v4ck0wcbis4463q3cjd9fbx5511hhh6lah6llifc"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 4cfda911247b997afb767da2f7e2b322117414b3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 13 Sep 2024 09:35:11 +0100 Subject: [PATCH 115/440] core: fix ForwardConfirmation encoding (#4872) --- src/Simplex/Chat/Controller.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 99739cc6bf..7dccbb663d 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -1472,7 +1472,7 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan) -$(JQ.deriveJSON defaultJSON ''ForwardConfirmation) +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType) From 2539255957c64739801804cfe8b39ad6dde52f22 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:35:12 +0000 Subject: [PATCH 116/440] android: remove Worker provider's interface implementation (#4874) --- .../android/src/main/java/chat/simplex/app/SimplexApp.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index d10f16341a..4d3b390189 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit const val TAG = "SIMPLEX" -class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider { +class SimplexApp: Application(), LifecycleEventObserver { val chatModel: ChatModel get() = chatController.chatModel @@ -363,9 +363,4 @@ class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider } } } - - // Fix for an exception: - // WorkManager is not initialized properly. You have explicitly disabled WorkManagerInitializer in your manifest, have not manually called WorkManager#initialize at this point, and your Application does not implement Configuration.Provider. - override val workManagerConfiguration: Configuration - get() = Configuration.Builder().build() } From 4447b66b4ef09f3c2d8b0507076deb561bf71a3b Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 13 Sep 2024 19:02:32 +0000 Subject: [PATCH 117/440] android: fix showing logs from core (#4880) --- .../main/java/chat/simplex/app/SimplexApp.kt | 2 +- .../common/platform/AppCommon.android.kt | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 49edde55bb..b6773ceb42 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -66,7 +66,7 @@ class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider } } context = this - initHaskell() + initHaskell(packageName) initMultiplatform() runMigrations() tmpDir.deleteRecursively() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index 8cd51e8298..cd1672f3e9 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -31,22 +31,19 @@ lateinit var androidAppContext: Context var mainActivity: WeakReference = WeakReference(null) var callActivity: WeakReference = WeakReference(null) -fun initHaskell() { - val socketName = "chat.simplex.app.local.socket.address.listen.native.cmd2" + Random.nextLong(100000) +fun initHaskell(packageName: String) { val s = Semaphore(0) thread(name="stdout/stderr pipe") { Log.d(TAG, "starting server") - var server: LocalServerSocket? = null - for (i in 0..100) { - try { - server = LocalServerSocket(socketName + i) - break - } catch (e: IOException) { - Log.e(TAG, e.stackTraceToString()) - } - } - if (server == null) { - throw Error("Unable to setup local server socket. Contact developers") + val server: LocalServerSocket + try { + server = LocalServerSocket(packageName) + } catch (e: IOException) { + Log.e(TAG, e.stackTraceToString()) + Log.e(TAG, "Unable to setup local server socket. Contact developers") + s.release() + // Will not have logs from backend + return@thread } Log.d(TAG, "started server") s.release() @@ -60,7 +57,7 @@ fun initHaskell() { Log.d(TAG, "starting receiver loop") while (true) { val line = input.readLine() ?: break - Log.w("$TAG (stdout/stderr)", line) + Log.w(TAG, "(stdout/stderr) $line") logbuffer.add(line) } Log.w(TAG, "exited receiver loop") @@ -70,7 +67,7 @@ fun initHaskell() { System.loadLibrary("app-lib") s.acquire() - pipeStdOutToSocket(socketName) + pipeStdOutToSocket(packageName) initHS() } From c6ab8ec6b35c83030527ad62e54f59ca748c5cc1 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 13 Sep 2024 23:16:23 +0400 Subject: [PATCH 118/440] core: cleanup empty file on error; check file status on forward (#4878) --- src/Simplex/Chat.hs | 63 +++++++++++++++++++++--------------- src/Simplex/Chat/Messages.hs | 4 +-- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index fb5c4b4962..fa80a2e7ec 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -995,7 +995,7 @@ processChatCommand' vr = \case Just err -> pure $ itemIdWithoutFile err Nothing -> case fileSource of Just CryptoFile {filePath} -> do - exists <- doesFileExist . maybe filePath ( filePath) =<< chatReadVar filesFolder + exists <- doesFileExist =<< lift (toFSFilePath filePath) pure $ if exists then (Just itemId, Nothing) else itemIdWithoutFile FFEMissing Nothing -> pure $ itemIdWithoutFile FFEMissing where @@ -1079,27 +1079,28 @@ processChatCommand' vr = \case Just CIFFUnknown -> ciff Just prevCIFF -> Just prevCIFF forwardContent :: ChatItem c d -> MsgContent -> CM (Maybe (MsgContent, Maybe CryptoFile)) - forwardContent ChatItem {file = Nothing} mc = pure $ Just (mc, Nothing) - forwardContent ChatItem {file = Just ciFile} mc = case ciFile of - CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> - chatReadVar filesFolder >>= \case - Nothing -> - ifM (doesFileExist filePath) (pure $ Just (mc, Just fromCF)) (pure contentWithoutFile) - Just filesFolder -> do - let fsFromPath = filesFolder filePath - ifM - (doesFileExist fsFromPath) - ( do - fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName - liftIO $ B.writeFile fsNewPath "" -- create empty file - encrypt <- chatReadVar encryptLocalFiles - cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing - let toCF = CryptoFile fsNewPath cfArgs - -- to keep forwarded file in case original is deleted - liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF - pure $ Just (mc, Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) - ) - (pure contentWithoutFile) + forwardContent ChatItem {file} mc = case file of + Nothing -> pure $ Just (mc, Nothing) + Just CIFile {fileName, fileStatus, fileSource = Just fromCF@CryptoFile {filePath}} + | ciFileLoaded fileStatus -> + chatReadVar filesFolder >>= \case + Nothing -> + ifM (doesFileExist filePath) (pure $ Just (mc, Just fromCF)) (pure contentWithoutFile) + Just filesFolder -> do + let fsFromPath = filesFolder filePath + ifM + (doesFileExist fsFromPath) + ( do + fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName + liftIO $ B.writeFile fsNewPath "" -- create empty file + encrypt <- chatReadVar encryptLocalFiles + cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing + let toCF = CryptoFile fsNewPath cfArgs + -- to keep forwarded file in case original is deleted + liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF + pure $ Just (mc, Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) + ) + (pure contentWithoutFile) _ -> pure contentWithoutFile where contentWithoutFile = case mc of @@ -3444,7 +3445,7 @@ callStatusItemContent user Contact {contactId} chatItemId receivedStatus = do -- used during file transfer for actual operations with file system toFSFilePath :: FilePath -> CM' FilePath toFSFilePath f = - maybe f ( f) <$> (readTVarIO =<< asks filesFolder) + maybe f ( f) <$> (chatReadVar' filesFolder) setFileToEncrypt :: RcvFileTransfer -> CM RcvFileTransfer setFileToEncrypt ft@RcvFileTransfer {fileId} = do @@ -3566,7 +3567,9 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} relaysNotApproved :: [XFTPServer] -> CM () relaysNotApproved unknownSrvs = do aci_ <- resetRcvCIFileStatus user fileId CIFSRcvInvitation - forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci + forM_ aci_ $ \aci -> do + cleanupACIFile aci + toView $ CRChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs getNetworkConfig :: CM' NetworkConfig @@ -4290,14 +4293,22 @@ processAgentMsgRcvFile _corrId aFileId msg = do RFERR e | e == FILE NOT_APPROVED -> do aci_ <- resetRcvCIFileStatus user fileId CIFSRcvAborted + forM_ aci_ cleanupACIFile agentXFTPDeleteRcvFile aFileId fileId forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci | otherwise -> do - ci <- withStore $ \db -> do + aci_ <- withStore $ \db -> do liftIO $ updateFileCancelled db user fileId (CIFSRcvError $ agentFileError e) lookupChatItemByFileId db vr user fileId + forM_ aci_ cleanupACIFile agentXFTPDeleteRcvFile aFileId fileId - toView $ CRRcvFileError user ci e ft + toView $ CRRcvFileError user aci_ e ft + +cleanupACIFile :: AChatItem -> CM () +cleanupACIFile (AChatItem _ _ _ ChatItem {file = Just CIFile {fileSource = Just CryptoFile {filePath}}}) = do + fsFilePath <- lift $ toFSFilePath filePath + removeFile fsFilePath `catchChatError` \_ -> pure () +cleanupACIFile _ = pure () processAgentMessageConn :: VersionRangeChat -> User -> ACorrId -> ConnId -> AEvent 'AEConn -> CM () processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = do diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 2b21857408..50e68e5bf4 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,7 +35,7 @@ import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time.Clock (UTCTime, diffUTCTime, nominalDay, NominalDiffTime) +import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay) import Data.Type.Equality import Data.Typeable (Typeable) import Database.SQLite.Simple.FromField (FromField (..)) @@ -596,7 +596,7 @@ ciFileLoaded = \case CIFSInvalid {} -> False data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing - deriving (Eq, Ord) + deriving (Eq, Ord) ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError ciFileForwardError fId = \case From b5f91f2d31733dac3507510146ec308115e5102c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 14 Sep 2024 10:57:55 +0100 Subject: [PATCH 119/440] core: test forwarding image preview without attached file (#4877) --- tests/ChatTests/Forward.hs | 50 +++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index 8f1595fd6c..3b861a8417 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -681,6 +681,7 @@ testMultiForwardFiles = copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf" + copyFile "./tests/fixtures/logo.jpg" "./tests/tmp/alice_app_files/logo.jpg" setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" connectUsers alice bob @@ -699,7 +700,8 @@ testMultiForwardFiles = cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"image\", \"image\":\"" <> T.unpack img <> "\", \"text\": \"\"}}" cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}" cm4 = "{\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"message with large file\"}}" - alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "]") + cm5 = "{\"filePath\": \"logo.jpg\", \"msgContent\": {\"type\": \"image\", \"image\":\"" <> T.unpack img <> "\", \"text\": \"\"}}" + alice ##> ("/_send @2 json [" <> intercalate "," [cm1, cm2, cm3, cm4, cm5] <> "]") alice <# "@bob message without file" @@ -713,6 +715,9 @@ testMultiForwardFiles = alice <# "/f @bob test_1MB.pdf" alice <## "use /fc 3 to cancel sending" + alice <# "/f @bob logo.jpg" + alice <## "use /fc 4 to cancel sending" + bob <# "alice> message without file" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -725,16 +730,20 @@ testMultiForwardFiles = bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)" bob <## "use /fr 3 [/ | ] to receive it" + bob <# "alice> sends file logo.jpg (31.3 KiB / 32080 bytes)" + bob <## "use /fr 4 [/ | ] to receive it" + alice <## "completed uploading file 1 (test.jpg) for bob" alice <## "completed uploading file 2 (test.pdf) for bob" alice <## "completed uploading file 3 (test_1MB.pdf) for bob" + alice <## "completed uploading file 4 (logo.jpg) for bob" -- IDs to forward let msgId1 = (read msgIdZero :: Int) + 1 - msgIds = intercalate "," $ map show [msgId1, msgId1 + 1, msgId1 + 2, msgId1 + 3, msgId1 + 4] + msgIds = intercalate "," $ map (show . (msgId1 +)) [0..5] bob ##> ("/_forward plan @2 " <> msgIds) - bob <## "Files can be received: 1, 2, 3" - bob <## "4 message(s) out of 5 can be forwarded" + bob <## "Files can be received: 1, 2, 3, 4" + bob <## "5 message(s) out of 6 can be forwarded" bob ##> "/fr 1" bob @@ -744,8 +753,8 @@ testMultiForwardFiles = bob <## "completed receiving file 1 (test.jpg) from alice" bob ##> ("/_forward plan @2 " <> msgIds) - bob <## "Files can be received: 2, 3" - bob <## "4 message(s) out of 5 can be forwarded" + bob <## "Files can be received: 2, 3, 4" + bob <## "5 message(s) out of 6 can be forwarded" bob ##> "/fr 2" bob @@ -764,7 +773,7 @@ testMultiForwardFiles = -- forward file bob ##> ("/_forward plan @2 " <> msgIds) - bob <## "Files can be received: 3" + bob <## "Files can be received: 3, 4" bob <## "all messages can be forwarded" bob ##> ("/_forward @3 @2 " <> msgIds) @@ -778,16 +787,19 @@ testMultiForwardFiles = bob <# "@cath <- @alice" bob <## " test_1.jpg" bob <# "/f @cath test_1.jpg" - bob <## "use /fc 4 to cancel sending" + bob <## "use /fc 5 to cancel sending" bob <# "@cath <- @alice" bob <## " test_1.pdf" bob <# "/f @cath test_1.pdf" - bob <## "use /fc 5 to cancel sending" + bob <## "use /fc 6 to cancel sending" bob <# "@cath <- @alice" bob <## " message with large file" + bob <# "@cath <- @alice" + bob <## "" + -- messages printed for cath cath <# "bob> -> forwarded" cath <## " hi" @@ -808,9 +820,12 @@ testMultiForwardFiles = cath <# "bob> -> forwarded" cath <## " message with large file" + cath <# "bob> -> forwarded" + cath <## "" + -- file transfer - bob <## "completed uploading file 4 (test_1.jpg) for cath" - bob <## "completed uploading file 5 (test_1.pdf) for cath" + bob <## "completed uploading file 5 (test_1.jpg) for cath" + bob <## "completed uploading file 6 (test_1.pdf) for cath" cath ##> "/fr 1" cath @@ -843,6 +858,17 @@ testMultiForwardFiles = ] bob <## "completed receiving file 3 (test_1MB.pdf) from alice" + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "Files can be received: 4" + bob <## "all messages can be forwarded" + + bob ##> "/fr 4" + bob + <### [ "saving file 4 from alice to logo.jpg", + "started receiving file 4 (logo.jpg) from alice" + ] + bob <## "completed receiving file 4 (logo.jpg) from alice" + bob ##> ("/_forward plan @2 " <> msgIds) bob <## "all messages can be forwarded" @@ -854,7 +880,7 @@ testMultiForwardFiles = removeFile "./tests/tmp/bob_app_files/test.pdf" bob ##> ("/_forward plan @2 " <> msgIds) bob <## "2 file(s) are missing" - bob <## "4 message(s) out of 5 can be forwarded" + bob <## "5 message(s) out of 6 can be forwarded" -- deleting original file doesn't delete forwarded file checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do From c22d23750f31de52e395c90cd4cbab12708afde1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 16 Sep 2024 07:33:48 +0100 Subject: [PATCH 120/440] core: support different SOCKS proxy authentication modes (#4886) * core: support different SOCKS proxy authentication modes * use defaultSocksProxyWithAuth * hostMode CLI option * simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 16 +++++++------ src/Simplex/Chat/Controller.hs | 10 +++++---- src/Simplex/Chat/Options.hs | 41 ++++++++++++++++++++++++++++++---- 5 files changed, 54 insertions(+), 17 deletions(-) diff --git a/cabal.project b/cabal.project index 57d4a84496..92f5d475e9 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: dab1980d79b35634bea9a259b633bd06ed8d5ebf + tag: fa772af6c63fab8f04d9d32d8e8397d75d7d0391 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 6d432a90a5..c3b85f9b53 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."dab1980d79b35634bea9a259b633bd06ed8d5ebf" = "0wf0p02bz8zq31mgfnfxy5allhbfr6vcmv8cii92qwj95m6ibwcp"; + "https://github.com/simplex-chat/simplexmq.git"."fa772af6c63fab8f04d9d32d8e8397d75d7d0391" = "07d0f89msb6p05y67q90ky9jr1rygg7v3xlkga7y255mmpjsqbip"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 1b4227eb39..d20499455a 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -105,7 +105,7 @@ import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), Migrati import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations -import Simplex.Messaging.Client (NetworkConfig (..), ProxyClientError (..), SocksMode (SMAlways), defaultNetworkConfig) +import Simplex.Messaging.Client (NetworkConfig (..), ProxyClientError (..), SocksMode (SMAlways), defaultNetworkConfig, textToHostMode) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -114,12 +114,12 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Transport (TransportError (..)) -import Simplex.Messaging.Transport.Client (defaultSocksProxy) +import Simplex.Messaging.Transport.Client (defaultSocksProxyWithAuth) import Simplex.Messaging.Util import Simplex.Messaging.Version import Simplex.RemoteControl.Invitation (RCInvitation (..), RCSignedInvitation (..)) @@ -341,11 +341,11 @@ newChatController userServers user' = useServers config protocol <$> withTransaction chatStore (`getProtocolServers` user') updateNetworkConfig :: NetworkConfig -> SimpleNetCfg -> NetworkConfig -updateNetworkConfig cfg SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} = +updateNetworkConfig cfg SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} = let cfg1 = maybe cfg (\smpProxyMode -> cfg {smpProxyMode}) smpProxyMode_ cfg2 = maybe cfg1 (\smpProxyFallback -> cfg1 {smpProxyFallback}) smpProxyFallback_ cfg3 = maybe cfg2 (\tcpTimeout -> cfg2 {tcpTimeout, tcpConnectTimeout = (tcpTimeout * 3) `div` 2}) tcpTimeout_ - in cfg3 {socksProxy, socksMode, logTLSErrors} + in cfg3 {socksProxy, socksMode, hostMode, requiredHostMode, logTLSErrors} withChatLock :: String -> CM a -> CM a withChatLock name action = asks chatLock >>= \l -> withLock l name action @@ -8052,14 +8052,16 @@ chatCommandP = <|> ("yes" $> TMEEnableKeepTTL) <|> ("no" $> TMEDisableKeepTTL) netCfgP = do - socksProxy <- "socks=" *> ("off" $> Nothing <|> "on" $> Just defaultSocksProxy <|> Just <$> strP) + socksProxy <- "socks=" *> ("off" $> Nothing <|> "on" $> Just defaultSocksProxyWithAuth <|> Just <$> strP) socksMode <- " socks-mode=" *> strP <|> pure SMAlways + hostMode <- " host-mode=" *> (textToHostMode . safeDecodeUtf8 <$?> A.takeTill (== ' ')) <|> pure (defaultHostMode socksProxy) + requiredHostMode <- " required-host-mode" *> onOffP <|> pure False smpProxyMode_ <- optional $ " smp-proxy=" *> strP smpProxyFallback_ <- optional $ " smp-proxy-fallback=" *> strP t_ <- optional $ " timeout=" *> A.decimal logTLSErrors <- " log=" *> onOffP <|> pure False let tcpTimeout_ = (1000000 *) <$> t_ - pure $ SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} + pure $ SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} dbKeyP = nonEmptyKey <$?> strP nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False} diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index c4f056c778..b3fccf95ad 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -76,7 +76,7 @@ import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction, withTransactionPriority) import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB -import Simplex.Messaging.Client (SMPProxyFallback (..), SMPProxyMode (..), SocksMode (..)) +import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyMode (..), SocksMode (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -87,7 +87,7 @@ import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, p import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, NtfServer, ProtocolType (..), ProtocolTypeI, QueueId, SMPMsgMeta (..), SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.Transport (TLS, simplexMQVersion) -import Simplex.Messaging.Transport.Client (SocksProxy, TransportHost) +import Simplex.Messaging.Transport.Client (SocksProxyWithAuth, TransportHost) import Simplex.Messaging.Util (allFinally, catchAllErrors, catchAllErrors', tryAllErrors, tryAllErrors', (<$$>)) import Simplex.RemoteControl.Client import Simplex.RemoteControl.Invitation (RCSignedInvitation, RCVerifiedInvitation) @@ -974,8 +974,10 @@ data AppFilePathsConfig = AppFilePathsConfig deriving (Show) data SimpleNetCfg = SimpleNetCfg - { socksProxy :: Maybe SocksProxy, + { socksProxy :: Maybe SocksProxyWithAuth, socksMode :: SocksMode, + hostMode :: HostMode, + requiredHostMode :: Bool, smpProxyMode_ :: Maybe SMPProxyMode, smpProxyFallback_ :: Maybe SMPProxyFallback, tcpTimeout_ :: Maybe Int, @@ -984,7 +986,7 @@ data SimpleNetCfg = SimpleNetCfg deriving (Show) defaultSimpleNetCfg :: SimpleNetCfg -defaultSimpleNetCfg = SimpleNetCfg Nothing SMAlways Nothing Nothing Nothing False +defaultSimpleNetCfg = SimpleNetCfg Nothing SMAlways HMOnionViaSocks True Nothing Nothing Nothing False data ContactSubStatus = ContactSubStatus { contact :: Contact, diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index 0b5961b042..cd44966cc2 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -13,6 +13,7 @@ module Simplex.Chat.Options coreChatOptsP, getChatOpts, protocolServersP, + defaultHostMode, ) where @@ -20,6 +21,7 @@ import Control.Logger.Simple (LogLevel (..)) import qualified Data.Attoparsec.ByteString.Char8 as A import Data.ByteArray (ScrubbedBytes) import qualified Data.ByteString.Char8 as B +import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) @@ -27,11 +29,11 @@ import Numeric.Natural (Natural) import Options.Applicative import Simplex.Chat.Controller (ChatLogLevel (..), SimpleNetCfg (..), updateStr, versionNumber, versionString) import Simplex.FileTransfer.Description (mb) -import Simplex.Messaging.Client (SocksMode (..)) +import Simplex.Messaging.Client (HostMode (..), SocksMode (..), textToHostMode) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (parseAll) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI, SMPServerWithAuth, XFTPServerWithAuth) -import Simplex.Messaging.Transport.Client (defaultSocksProxy) +import Simplex.Messaging.Transport.Client (SocksProxyWithAuth (..), SocksAuth (..), defaultSocksProxyWithAuth) import System.FilePath (combine) data ChatOpts = ChatOpts @@ -123,7 +125,7 @@ coreChatOptsP appDir defaultDbFileName = do <> value [] ) socksProxy <- - flag' (Just defaultSocksProxy) (short 'x' <> help "Use local SOCKS5 proxy at :9050") + flag' (Just defaultSocksProxyWithAuth) (short 'x' <> help "Use local SOCKS5 proxy at :9050") <|> option strParse ( long "socks-proxy" @@ -139,6 +141,19 @@ coreChatOptsP appDir defaultDbFileName = do <> help "Use SOCKS5 proxy: always (default), onion (with onion-only relays)" <> value SMAlways ) + hostMode_ <- + optional $ + option + parseHostMode + ( long "host-mode" + <> metavar "HOST_MODE" + <> help "Preferred server host type: onion (when SOCKS proxy with isolate-by-auth is used), public" + ) + requiredHostMode <- + switch + ( long "required-host-mode" + <> help "Refuse connection if preferred server host type is not available" + ) smpProxyMode_ <- optional $ option @@ -226,7 +241,17 @@ coreChatOptsP appDir defaultDbFileName = do dbKey, smpServers, xftpServers, - simpleNetCfg = SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_, tcpTimeout_ = Just $ useTcpTimeout socksProxy t, logTLSErrors}, + simpleNetCfg = + SimpleNetCfg + { socksProxy, + socksMode, + hostMode = fromMaybe (defaultHostMode socksProxy) hostMode_, + requiredHostMode, + smpProxyMode_, + smpProxyFallback_, + tcpTimeout_ = Just $ useTcpTimeout socksProxy t, + logTLSErrors + }, logLevel, logConnections = logConnections || logLevel <= CLLInfo, logServerHosts = logServerHosts || logLevel <= CLLInfo, @@ -240,6 +265,11 @@ coreChatOptsP appDir defaultDbFileName = do useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p defaultDbFilePath = combine appDir defaultDbFileName +defaultHostMode :: Maybe SocksProxyWithAuth -> HostMode +defaultHostMode = \case + Just (SocksProxyWithAuth SocksIsolateByAuth _) -> HMOnionViaSocks; + _ -> HMPublic + chatOptsP :: FilePath -> FilePath -> Parser ChatOpts chatOptsP appDir defaultDbFileName = do coreOptions <- coreChatOptsP appDir defaultDbFileName @@ -360,6 +390,9 @@ parseProtocolServers = eitherReader $ parseAll protocolServersP . B.pack strParse :: StrEncoding a => ReadM a strParse = eitherReader $ parseAll strP . encodeUtf8 . T.pack +parseHostMode :: ReadM HostMode +parseHostMode = eitherReader $ textToHostMode . T.pack + parseServerPort :: ReadM (Maybe String) parseServerPort = eitherReader $ parseAll serverPortP . B.pack From 0cad378bc55cfff8eead6485e8016b1f992d8d70 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 16 Sep 2024 13:17:18 +0100 Subject: [PATCH 121/440] core: 6.1.0.0 (simplexmq 6.1.0.0) From de7882c9044e1dc5b0aa9d28f526725df70e2a24 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Mon, 16 Sep 2024 15:28:45 +0300 Subject: [PATCH 122/440] ios: update user profile sheet design (#4871) * ios: update user profile sheet design * revert views * improve validation * minor * align with create profile * alert on dismiss * revert x appearance * update size * move the fullname * focus on appear * profile image * localizations --------- Co-authored-by: Evgeny Poberezkin --- .../Shared/Views/ChatList/ChatListView.swift | 4 +- .../Shared/Views/ChatList/UserPicker.swift | 2 +- .../Shared/Views/Database/DatabaseView.swift | 4 +- .../Views/Migration/MigrateFromDevice.swift | 2 +- .../Views/Migration/MigrateToDevice.swift | 2 +- .../Views/UserSettings/UserAddressView.swift | 2 +- .../Views/UserSettings/UserProfile.swift | 226 ++++++++---------- .../bg.xcloc/Localized Contents/bg.xliff | 80 ++++--- .../cs.xcloc/Localized Contents/cs.xliff | 78 +++--- .../de.xcloc/Localized Contents/de.xliff | 80 ++++--- .../en.xcloc/Localized Contents/en.xliff | 90 ++++--- .../es.xcloc/Localized Contents/es.xliff | 80 ++++--- .../fi.xcloc/Localized Contents/fi.xliff | 78 +++--- .../fr.xcloc/Localized Contents/fr.xliff | 80 ++++--- .../hu.xcloc/Localized Contents/hu.xliff | 80 ++++--- .../it.xcloc/Localized Contents/it.xliff | 80 ++++--- .../ja.xcloc/Localized Contents/ja.xliff | 78 +++--- .../nl.xcloc/Localized Contents/nl.xliff | 80 ++++--- .../pl.xcloc/Localized Contents/pl.xliff | 80 ++++--- .../ru.xcloc/Localized Contents/ru.xliff | 80 ++++--- .../th.xcloc/Localized Contents/th.xliff | 78 +++--- .../tr.xcloc/Localized Contents/tr.xliff | 80 ++++--- .../uk.xcloc/Localized Contents/uk.xliff | 80 ++++--- .../Localized Contents/zh-Hans.xliff | 79 +++--- .../common/views/chatlist/UserPicker.kt | 2 +- .../views/usersettings/UserAddressView.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 6 - 27 files changed, 925 insertions(+), 690 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 156b8694c4..4d1c182554 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -66,7 +66,7 @@ struct ChatListView: View { case .address: NavigationView { UserAddressView(shareViaProfile: currentUser.addressShared) - .navigationTitle("Public address") + .navigationTitle("SimpleX address") .navigationBarTitleDisplayMode(.large) .modifier(ThemedBackground(grouped: true)) } @@ -78,7 +78,7 @@ struct ChatListView: View { NavigationView { UserProfile() .navigationTitle("Your current profile") - .modifier(ThemedBackground()) + .modifier(ThemedBackground(grouped: true)) } case .chatPreferences: NavigationView { diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index 9d7f6bbd9c..efe54cb036 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -49,7 +49,7 @@ struct UserPicker: View { activeSheet = .currentProfile } - openSheetOnTap(title: m.userAddress == nil ? "Create public address" : "Your public address", icon: "qrcode") { + openSheetOnTap(title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", icon: "qrcode") { activeSheet = .address } diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index f5b5287971..d0de0e1bd3 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -270,12 +270,12 @@ struct DatabaseView: View { case let .archiveImportedWithErrors(errs): return Alert( title: Text("Chat database imported"), - message: Text("Restart the app to use imported chat database") + Text(verbatim: "\n\n") + Text("Some non-fatal errors occurred during import:") + archiveErrorsText(errs) + message: Text("Restart the app to use imported chat database") + Text(verbatim: "\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), + message: Text("You may save the exported archive.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), dismissButton: .default(Text("Continue")) { showShareSheet(items: [archivePath]) } diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 5f7b8a4534..73e5b97057 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -177,7 +177,7 @@ struct MigrateFromDevice: View { 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), + message: Text("You may migrate the exported database.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), dismissButton: .default(Text("Continue")) { Task { await uploadArchive(path: archivePath) } } diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index e2df68a0e4..fe0eec609b 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -571,7 +571,7 @@ struct MigrateToDevice: View { AlertManager.shared.showAlert( Alert( title: Text("Error migrating settings"), - message: Text ("Not all settings were migrated. Repeat migration if you need them.") + Text("\n\n") + Text(responseError(error))) + message: Text ("Some app settings were not migrated.") + Text("\n") + Text(responseError(error))) ) } hideView() diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 7efc8a46f5..2469dc59db 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -229,7 +229,7 @@ struct UserAddressView: View { } } } label: { - Label("Create public address", systemImage: "qrcode") + Label("Create SimpleX address", systemImage: "qrcode") } } diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 198fd495bd..6ca661ae48 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -11,8 +11,11 @@ import SimpleXChat struct UserProfile: View { @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner @State private var profile = Profile(displayName: "", fullName: "") - @State private var editProfile = false + @State private var currentProfileHash: Int? + // Modals @State private var showChooseSource = false @State private var showImagePicker = false @State private var showTakePhoto = false @@ -21,85 +24,83 @@ struct UserProfile: View { @FocusState private var focusDisplayName var body: some View { - let user: User = chatModel.currentUser! - - return VStack(alignment: .leading) { - Text("Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile.") - .padding(.bottom) - - if editProfile { - ZStack(alignment: .center) { - ZStack(alignment: .topTrailing) { + List { + Group { + if profile.image != nil { + ZStack(alignment: .bottomTrailing) { + ZStack(alignment: .topTrailing) { + profileImageView(profile.image) + .onTapGesture { showChooseSource = true } + overlayButton("multiply", edge: .top) { profile.image = nil } + } + overlayButton("camera", edge: .bottom) { showChooseSource = true } + } + } else { + ZStack(alignment: .center) { profileImageView(profile.image) - if user.image != nil { - Button { - profile.image = nil - } label: { - Image(systemName: "multiply") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) - } + editImageButton { showChooseSource = true } + } + } + } + .frame(maxWidth: .infinity, alignment: .center) + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .padding(.top) + .contentShape(Rectangle()) + + Section { + HStack { + TextField("Enter your name…", text: $profile.displayName) + .focused($focusDisplayName) + if !validDisplayName(profile.displayName) { + Button { + alert = .invalidNameError(validName: mkValidName(profile.displayName)) + } label: { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) } } - - editImageButton { showChooseSource = true } } - .frame(maxWidth: .infinity, alignment: .center) - - VStack(alignment: .leading) { - ZStack(alignment: .leading) { - if !validNewProfileName(user) { - Button { - alert = .invalidNameError(validName: mkValidName(profile.displayName)) - } label: { - Image(systemName: "exclamationmark.circle").foregroundColor(.red) - } - } else { - Image(systemName: "exclamationmark.circle").foregroundColor(.clear) - } - profileNameTextEdit("Profile name", $profile.displayName) - .focused($focusDisplayName) - } - .padding(.bottom) - if showFullName(user) { - profileNameTextEdit("Full name (optional)", $profile.fullName) - .padding(.bottom) - } - HStack(spacing: 20) { - Button("Cancel") { editProfile = false } - Button("Save (and notify contacts)") { saveProfile() } - .disabled(!canSaveProfile(user)) - } + if let user = chatModel.currentUser, showFullName(user) { + TextField("Full name (optional)", text: $profile.fullName) } - .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) - } else { - ZStack(alignment: .center) { - profileImageView(user.image) - .onTapGesture { startEditingImage(user) } + } footer: { + Text("Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile.") + } - if user.image == nil { - editImageButton { startEditingImage(user) } - } + Section { + Button(action: getCurrentProfile) { + Text("Reset") } - .frame(maxWidth: .infinity, alignment: .center) - - VStack(alignment: .leading) { - profileNameView("Profile name:", user.profile.displayName) - if showFullName(user) { - profileNameView("Full name:", user.profile.fullName) - } - Button("Edit") { - profile = fromLocalProfile(user.profile) - editProfile = true - focusDisplayName = true - } + .disabled(currentProfileHash == profile.hashValue) + Button(action: saveProfile) { + Text("Save (and notify contacts)") } - .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) + .disabled(!canSaveProfile) } } - .padding() - .frame(maxHeight: .infinity, alignment: .top) + // Lifecycle + .onAppear { + getCurrentProfile() + } + .onDisappear { + if canSaveProfile { + showAlert( + title: NSLocalizedString("Save your profile?", comment: "alert title"), + message: NSLocalizedString("Your profile was changed. If you save it, the updated profile will be sent to all your contacts.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save (and notify contacts)", comment: "alert button"), + buttonAction: saveProfile, + cancelButton: true + ) + } + } + .onChange(of: chosenImage) { image in + if let image { + profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) + } else { + profile.image = nil + } + } + // Modals .confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) { Button("Take picture") { showTakePhoto = true @@ -126,57 +127,49 @@ struct UserProfile: View { } } } - .onChange(of: chosenImage) { image in - if let image = image { - profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) - } else { - profile.image = nil - } - } .alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) } } - func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding) -> some View { - TextField(label, text: name) - .padding(.leading, 32) - } - - func profileNameView(_ label: LocalizedStringKey, _ name: String) -> some View { - HStack { - Text(label) - Text(name).fontWeight(.bold) - } - .padding(.bottom) - } - - func startEditingImage(_ user: User) { - profile = fromLocalProfile(user.profile) - editProfile = true - showChooseSource = true - } - - private func validNewProfileName(_ user: User) -> Bool { - profile.displayName == user.profile.displayName || validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces)) + @ViewBuilder + private func overlayButton( + _ systemName: String, + edge: Edge.Set, + action: @escaping () -> Void + ) -> some View { + Image(systemName: systemName) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 12) + .foregroundColor(theme.colors.primary) + .padding(6) + .frame(width: 36, height: 36, alignment: .center) + .background(radius >= 20 ? Color.clear : theme.colors.background.opacity(0.5)) + .clipShape(Circle()) + .contentShape(Circle()) + .padding([.trailing, edge], -12) + .onTapGesture(perform: action) } private func showFullName(_ user: User) -> Bool { user.profile.fullName != "" && user.profile.fullName != user.profile.displayName } - - private func canSaveProfile(_ user: User) -> Bool { - profile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName(user) + + private var canSaveProfile: Bool { + currentProfileHash != profile.hashValue && + profile.displayName.trimmingCharacters(in: .whitespaces) != "" && + validDisplayName(profile.displayName) } - func saveProfile() { + private func saveProfile() { + focusDisplayName = false Task { do { profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) if let (newProfile, _) = try await apiUpdateProfile(profile: profile) { - DispatchQueue.main.async { + await MainActor.run { chatModel.updateCurrentUser(newProfile) - profile = newProfile + getCurrentProfile() } - editProfile = false } else { alert = .duplicateUserError } @@ -185,6 +178,13 @@ struct UserProfile: View { } } } + + private func getCurrentProfile() { + if let user = chatModel.currentUser { + profile = fromLocalProfile(user.profile) + currentProfileHash = profile.hashValue + } + } } func profileImageView(_ imageStr: String?) -> some View { @@ -201,19 +201,3 @@ func editImageButton(action: @escaping () -> Void) -> some View { .frame(width: 48) } } - -struct UserProfile_Previews: PreviewProvider { - static var previews: some View { - let chatModel1 = ChatModel() - chatModel1.currentUser = User.sampleData - let chatModel2 = ChatModel() - chatModel2.currentUser = User.sampleData - chatModel2.currentUser?.profile.image = "data:image/jpg;base64,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" - return Group { - UserProfile() - .environmentObject(chatModel1) - UserProfile() - .environmentObject(chatModel2) - } - } -} diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 89e628e2b6..f55c87b1b8 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1007,6 +1007,10 @@ Автоматично приемане на изображения No comment provided by engineer. + + Auto-accept settings + alert title + Back Назад @@ -1171,7 +1175,7 @@ Cancel Отказ - No comment provided by engineer. + alert button Cancel migration @@ -1314,6 +1318,10 @@ Чат настройки No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2789,6 +2797,10 @@ This is your own one-time link! Грешка при зареждане на %@ сървъри No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Грешка при отваряне на чата @@ -3210,11 +3222,6 @@ Error: %2$@ Пълно име (незадължително) No comment provided by engineer. - - Full name: - Пълно име: - No comment provided by engineer. - Fully decentralized – visible only to members. Напълно децентрализирана – видима е само за членовете. @@ -4907,16 +4914,6 @@ Error: %@ Профилни изображения No comment provided by engineer. - - Profile name - Име на профила - No comment provided by engineer. - - - Profile name: - Име на профила: - No comment provided by engineer. - Profile password Профилна парола @@ -5224,6 +5221,10 @@ Enable in *Network & servers* settings. Премахване No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5409,12 +5410,13 @@ Enable in *Network & servers* settings. Save Запази - chat item action + alert button + chat item action Save (and notify contacts) Запази (и уведоми контактите) - No comment provided by engineer. + alert button Save and notify contact @@ -5440,11 +5442,6 @@ Enable in *Network & servers* settings. Запази архив No comment provided by engineer. - - Save auto-accept settings - Запази настройките за автоматично приемане - No comment provided by engineer. - Save group profile Запази профила на групата @@ -5480,16 +5477,15 @@ Enable in *Network & servers* settings. Запази сървърите? No comment provided by engineer. - - Save settings? - Запази настройките? - No comment provided by engineer. - Save welcome message? Запази съобщението при посрещане? No comment provided by engineer. + + Save your profile? + alert title + Saved Запазено @@ -5905,6 +5901,10 @@ Enable in *Network & servers* settings. Настройки No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Променете формата на профилните изображения @@ -6101,6 +6101,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6459,6 +6463,10 @@ It can happen because of some bug or when the connection is compromised.Текстът, който поставихте, не е SimpleX линк за връзка. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7511,6 +7519,10 @@ Repeat connection request? Вашата чат база данни не е криптирана - задайте парола, за да я криптирате. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Вашите чат профили @@ -7565,13 +7577,15 @@ Repeat connection request? Вашият профил **%@** ще бъде споделен. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. -SimpleX сървърите не могат да видят вашия профил. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 65bc83d096..3b29a1e51f 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -977,6 +977,10 @@ Automaticky přijímat obrázky No comment provided by engineer. + + Auto-accept settings + alert title + Back Zpět @@ -1131,7 +1135,7 @@ Cancel Zrušit - No comment provided by engineer. + alert button Cancel migration @@ -1270,6 +1274,10 @@ Předvolby chatu No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2694,6 +2702,10 @@ This is your own one-time link! Chyba načítání %@ serverů No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3099,11 +3111,6 @@ Error: %2$@ Celé jméno (volitelně) No comment provided by engineer. - - Full name: - Celé jméno: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -4729,14 +4736,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password Heslo profilu @@ -5038,6 +5037,10 @@ Enable in *Network & servers* settings. Odstranit No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5216,12 +5219,13 @@ Enable in *Network & servers* settings. Save Uložit - chat item action + alert button + chat item action Save (and notify contacts) Uložit (a informovat kontakty) - No comment provided by engineer. + alert button Save and notify contact @@ -5247,11 +5251,6 @@ Enable in *Network & servers* settings. Uložit archiv No comment provided by engineer. - - Save auto-accept settings - Uložit nastavení automatického přijímání - No comment provided by engineer. - Save group profile Uložení profilu skupiny @@ -5287,16 +5286,15 @@ Enable in *Network & servers* settings. Uložit servery? No comment provided by engineer. - - Save settings? - Uložit nastavení? - No comment provided by engineer. - Save welcome message? Uložit uvítací zprávu? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5703,6 +5701,10 @@ Enable in *Network & servers* settings. Nastavení No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5894,6 +5896,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6243,6 +6249,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7240,6 +7250,10 @@ Repeat connection request? Vaše chat databáze není šifrována – nastavte přístupovou frázi pro její šifrování. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Vaše chat profily @@ -7293,13 +7307,15 @@ Repeat connection request? Váš profil **%@** bude sdílen. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. -Servery SimpleX nevidí váš profil. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 357b1c3c47..cf2f85f2c0 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1024,6 +1024,10 @@ Bilder automatisch akzeptieren No comment provided by engineer. + + Auto-accept settings + alert title + Back Zurück @@ -1197,7 +1201,7 @@ Cancel Abbrechen - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Chat-Präferenzen No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Chat-Design @@ -2870,6 +2878,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Laden von %@ Servern No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Fehler beim Öffnen des Chats @@ -3309,11 +3321,6 @@ Fehler: %2$@ Vollständiger Name (optional) No comment provided by engineer. - - Full name: - Vollständiger Name: - No comment provided by engineer. - Fully decentralized – visible only to members. Vollständig dezentralisiert – nur für Mitglieder sichtbar. @@ -5045,16 +5052,6 @@ Fehler: %@ Profil-Bilder No comment provided by engineer. - - Profile name - Profilname - No comment provided by engineer. - - - Profile name: - Profilname: - No comment provided by engineer. - Profile password Passwort für Profil @@ -5378,6 +5375,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Entfernen No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Bild entfernen @@ -5571,12 +5572,13 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save Speichern - chat item action + alert button + chat item action Save (and notify contacts) Speichern (und Kontakte benachrichtigen) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Archiv speichern No comment provided by engineer. - - Save auto-accept settings - Einstellungen von "Automatisch akzeptieren" speichern - No comment provided by engineer. - Save group profile Gruppenprofil speichern @@ -5643,16 +5640,15 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Alle Server speichern? No comment provided by engineer. - - Save settings? - Einstellungen speichern? - No comment provided by engineer. - Save welcome message? Begrüßungsmeldung speichern? No comment provided by engineer. + + Save your profile? + alert title + Saved Abgespeichert @@ -6092,6 +6088,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Einstellungen No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Form der Profil-Bilder @@ -6296,6 +6296,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Weich blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Einzelne Datei(en) wurde(n) nicht exportiert: @@ -6667,6 +6671,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Der von Ihnen eingefügte Text ist kein SimpleX-Link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Design @@ -7750,6 +7758,10 @@ Verbindungsanfrage wiederholen? Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Ihre Chat-Profile @@ -7804,13 +7816,15 @@ Verbindungsanfrage wiederholen? Ihr Profil **%@** wird geteilt. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. -SimpleX-Server können Ihr Profil nicht einsehen. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 006996c8d9..6eb935222f 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1025,6 +1025,11 @@ Auto-accept images No comment provided by engineer. + + Auto-accept settings + Auto-accept settings + alert title + Back Back @@ -1198,7 +1203,7 @@ Cancel Cancel - No comment provided by engineer. + alert button Cancel migration @@ -1346,6 +1351,11 @@ Chat preferences No comment provided by engineer. + + Chat preferences were changed. + Chat preferences were changed. + alert message + Chat theme Chat theme @@ -2874,6 +2884,11 @@ This is your own one-time link! Error loading %@ servers No comment provided by engineer. + + Error migrating settings + Error migrating settings + No comment provided by engineer. + Error opening chat Error opening chat @@ -3314,11 +3329,6 @@ Error: %2$@ Full name (optional) No comment provided by engineer. - - Full name: - Full name: - No comment provided by engineer. - Fully decentralized – visible only to members. Fully decentralized – visible only to members. @@ -5051,16 +5061,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - Profile name - No comment provided by engineer. - - - Profile name: - Profile name: - No comment provided by engineer. - Profile password Profile password @@ -5384,6 +5384,11 @@ Enable in *Network & servers* settings. Remove No comment provided by engineer. + + Remove archive? + Remove archive? + No comment provided by engineer. + Remove image Remove image @@ -5577,12 +5582,13 @@ Enable in *Network & servers* settings. Save Save - chat item action + alert button + chat item action Save (and notify contacts) Save (and notify contacts) - No comment provided by engineer. + alert button Save and notify contact @@ -5609,11 +5615,6 @@ Enable in *Network & servers* settings. Save archive No comment provided by engineer. - - Save auto-accept settings - Save auto-accept settings - No comment provided by engineer. - Save group profile Save group profile @@ -5649,16 +5650,16 @@ Enable in *Network & servers* settings. Save servers? No comment provided by engineer. - - Save settings? - Save settings? - No comment provided by engineer. - Save welcome message? Save welcome message? No comment provided by engineer. + + Save your profile? + Save your profile? + alert title + Saved Saved @@ -6099,6 +6100,11 @@ Enable in *Network & servers* settings. Settings No comment provided by engineer. + + Settings were changed. + Settings were changed. + alert message + Shape profile images Shape profile images @@ -6304,6 +6310,11 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Some file(s) were not exported: @@ -6676,6 +6687,11 @@ It can happen because of some bug or when the connection is compromised.The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Themes @@ -7759,6 +7775,11 @@ Repeat connection request? Your chat database is not encrypted - set passphrase to encrypt it. No comment provided by engineer. + + Your chat preferences + Your chat preferences + alert title + Your chat profiles Your chat profiles @@ -7814,13 +7835,16 @@ Repeat connection request? Your profile **%@** will be shared. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Your profile, contacts and delivered messages are stored on your device. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 529b185e25..a10a1594de 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1024,6 +1024,10 @@ Aceptar imágenes automáticamente No comment provided by engineer. + + Auto-accept settings + alert title + Back Volver @@ -1197,7 +1201,7 @@ Cancel Cancelar - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Preferencias de Chat No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Tema de chat @@ -2870,6 +2878,10 @@ This is your own one-time link! Error al cargar servidores %@ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Error al abrir chat @@ -3309,11 +3321,6 @@ Error: %2$@ Nombre completo (opcional) No comment provided by engineer. - - Full name: - Nombre completo: - No comment provided by engineer. - Fully decentralized – visible only to members. Completamente descentralizado y sólo visible para los miembros. @@ -5045,16 +5052,6 @@ Error: %@ Forma de los perfiles No comment provided by engineer. - - Profile name - Nombre del perfil - No comment provided by engineer. - - - Profile name: - Nombre del perfil: - No comment provided by engineer. - Profile password Contraseña del perfil @@ -5378,6 +5375,10 @@ Actívalo en ajustes de *Servidores y Redes*. Eliminar No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Eliminar imagen @@ -5571,12 +5572,13 @@ Actívalo en ajustes de *Servidores y Redes*. Save Guardar - chat item action + alert button + chat item action Save (and notify contacts) Guardar (y notificar contactos) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Actívalo en ajustes de *Servidores y Redes*. Guardar archivo No comment provided by engineer. - - Save auto-accept settings - Guardar configuración de auto aceptar - No comment provided by engineer. - Save group profile Guardar perfil de grupo @@ -5643,16 +5640,15 @@ Actívalo en ajustes de *Servidores y Redes*. ¿Guardar servidores? No comment provided by engineer. - - Save settings? - ¿Guardar configuración? - No comment provided by engineer. - Save welcome message? ¿Guardar mensaje de bienvenida? No comment provided by engineer. + + Save your profile? + alert title + Saved Guardado @@ -6092,6 +6088,10 @@ Actívalo en ajustes de *Servidores y Redes*. Configuración No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Dar forma a las imágenes de perfil @@ -6296,6 +6296,10 @@ Actívalo en ajustes de *Servidores y Redes*. Suave blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Algunos archivos no han sido exportados: @@ -6667,6 +6671,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El texto pegado no es un enlace SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Temas @@ -7750,6 +7758,10 @@ Repeat connection request? La base de datos no está cifrada - establece una contraseña para cifrarla. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Mis perfiles @@ -7804,13 +7816,15 @@ Repeat connection request? El perfil **%@** será compartido. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. -Los servidores SimpleX no pueden ver tu perfil. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Tu perfil, contactos y mensajes se almacenan en tu dispositivo. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index fc31e4f2df..cf2ea3c36d 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -971,6 +971,10 @@ Hyväksy kuvat automaattisesti No comment provided by engineer. + + Auto-accept settings + alert title + Back Takaisin @@ -1124,7 +1128,7 @@ Cancel Peruuta - No comment provided by engineer. + alert button Cancel migration @@ -1263,6 +1267,10 @@ Chat-asetukset No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2685,6 +2693,10 @@ This is your own one-time link! Virhe %@-palvelimien lataamisessa No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3089,11 +3101,6 @@ Error: %2$@ Koko nimi (valinnainen) No comment provided by engineer. - - Full name: - Koko nimi: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -4717,14 +4724,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password Profiilin salasana @@ -5026,6 +5025,10 @@ Enable in *Network & servers* settings. Poista No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5204,12 +5207,13 @@ Enable in *Network & servers* settings. Save Tallenna - chat item action + alert button + chat item action Save (and notify contacts) Tallenna (ja ilmoita kontakteille) - No comment provided by engineer. + alert button Save and notify contact @@ -5235,11 +5239,6 @@ Enable in *Network & servers* settings. Tallenna arkisto No comment provided by engineer. - - Save auto-accept settings - Tallenna automaattisen hyväksynnän asetukset - No comment provided by engineer. - Save group profile Tallenna ryhmäprofiili @@ -5275,16 +5274,15 @@ Enable in *Network & servers* settings. Tallenna palvelimet? No comment provided by engineer. - - Save settings? - Tallenna asetukset? - No comment provided by engineer. - Save welcome message? Tallenna tervetuloviesti? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5690,6 +5688,10 @@ Enable in *Network & servers* settings. Asetukset No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5880,6 +5882,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6229,6 +6235,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7225,6 +7235,10 @@ Repeat connection request? Keskustelut-tietokantasi ei ole salattu - aseta tunnuslause sen salaamiseksi. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Keskusteluprofiilisi @@ -7278,13 +7292,15 @@ Repeat connection request? Profiilisi **%@** jaetaan. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. -SimpleX-palvelimet eivät näe profiiliasi. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 6970039315..547d0f3674 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1024,6 +1024,10 @@ Images auto-acceptées No comment provided by engineer. + + Auto-accept settings + alert title + Back Retour @@ -1197,7 +1201,7 @@ Cancel Annuler - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Préférences de chat No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Thème de chat @@ -2870,6 +2878,10 @@ Il s'agit de votre propre lien unique ! Erreur lors du chargement des serveurs %@ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Erreur lors de l'ouverture du chat @@ -3309,11 +3321,6 @@ Erreur : %2$@ Nom complet (optionnel) No comment provided by engineer. - - Full name: - Nom complet : - No comment provided by engineer. - Fully decentralized – visible only to members. Entièrement décentralisé – visible que par ses membres. @@ -5045,16 +5052,6 @@ Erreur : %@ Images de profil No comment provided by engineer. - - Profile name - Nom du profil - No comment provided by engineer. - - - Profile name: - Nom du profil : - No comment provided by engineer. - Profile password Mot de passe de profil @@ -5378,6 +5375,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Supprimer No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Enlever l'image @@ -5571,12 +5572,13 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save Enregistrer - chat item action + alert button + chat item action Save (and notify contacts) Enregistrer (et en informer les contacts) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Enregistrer l'archive No comment provided by engineer. - - Save auto-accept settings - Enregistrer les paramètres de validation automatique - No comment provided by engineer. - Save group profile Enregistrer le profil du groupe @@ -5643,16 +5640,15 @@ Activez-le dans les paramètres *Réseau et serveurs*. Enregistrer les serveurs ? No comment provided by engineer. - - Save settings? - Enregistrer les paramètres ? - No comment provided by engineer. - Save welcome message? Enregistrer le message d'accueil ? No comment provided by engineer. + + Save your profile? + alert title + Saved Enregistré @@ -6092,6 +6088,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Paramètres No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Images de profil modelable @@ -6296,6 +6296,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Léger blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Certains fichiers n'ont pas été exportés : @@ -6667,6 +6671,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le texte collé n'est pas un lien SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Thèmes @@ -7750,6 +7758,10 @@ Répéter la demande de connexion ? Votre base de données de chat n'est pas chiffrée - définisez une phrase secrète. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Vos profils de chat @@ -7804,13 +7816,15 @@ Répéter la demande de connexion ? Votre profil **%@** sera partagé. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. -Les serveurs SimpleX ne peuvent pas voir votre profil. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index de95de3421..89bce685a0 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1024,6 +1024,10 @@ Képek automatikus elfogadása No comment provided by engineer. + + Auto-accept settings + alert title + Back Vissza @@ -1197,7 +1201,7 @@ Cancel Mégse - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Csevegési beállítások No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Csevegés témája @@ -2870,6 +2878,10 @@ Ez az egyszer használatos hivatkozása! Hiba a %@ kiszolgálók betöltésekor No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Hiba a csevegés megnyitásakor @@ -3309,11 +3321,6 @@ Hiba: %2$@ Teljes név (opcionális) No comment provided by engineer. - - Full name: - Teljes név: - No comment provided by engineer. - Fully decentralized – visible only to members. Teljesen decentralizált - kizárólag tagok számára látható. @@ -5045,16 +5052,6 @@ Hiba: %@ Profilképek No comment provided by engineer. - - Profile name - Profilnév - No comment provided by engineer. - - - Profile name: - Profil neve: - No comment provided by engineer. - Profile password Profiljelszó @@ -5378,6 +5375,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Eltávolítás No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Kép eltávolítása @@ -5571,12 +5572,13 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Save Mentés - chat item action + alert button + chat item action Save (and notify contacts) Mentés (és az ismerősök értesítése) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Archívum mentése No comment provided by engineer. - - Save auto-accept settings - Automatikus elfogadási beállítások mentése - No comment provided by engineer. - Save group profile Csoportprofil elmentése @@ -5643,16 +5640,15 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Kiszolgálók mentése? No comment provided by engineer. - - Save settings? - Beállítások mentése? - No comment provided by engineer. - Save welcome message? Üdvözlőszöveg mentése? No comment provided by engineer. + + Save your profile? + alert title + Saved Mentett @@ -6092,6 +6088,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Beállítások No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Profilkép alakzat @@ -6296,6 +6296,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Enyhe blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Néhány fájl nem került exportálásra: @@ -6667,6 +6671,10 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. A beillesztett szöveg nem egy SimpleX hivatkozás. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Témák @@ -7750,6 +7758,10 @@ Kapcsolódási kérés megismétlése? A csevegési adatbázis nincs titkosítva – adjon meg egy jelmondatot a titkosításhoz. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Csevegési profilok @@ -7804,13 +7816,15 @@ Kapcsolódási kérés megismétlése? A(z) **%@** nevű profilja megosztásra fog kerülni. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra. -A SimpleX kiszolgálók nem látjhatják profilját. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra. A SimpleX kiszolgálók nem látjhatják profilját. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Profilja, ismerősei és az elküldött üzenetei az eszközön kerülnek tárolásra. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 700d181aab..a5e013fec2 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1024,6 +1024,10 @@ Auto-accetta immagini No comment provided by engineer. + + Auto-accept settings + alert title + Back Indietro @@ -1197,7 +1201,7 @@ Cancel Annulla - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Preferenze della chat No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Tema della chat @@ -2870,6 +2878,10 @@ Questo è il tuo link una tantum! Errore nel caricamento dei server %@ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Errore di apertura della chat @@ -3309,11 +3321,6 @@ Errore: %2$@ Nome completo (facoltativo) No comment provided by engineer. - - Full name: - Nome completo: - No comment provided by engineer. - Fully decentralized – visible only to members. Completamente decentralizzato: visibile solo ai membri. @@ -5045,16 +5052,6 @@ Errore: %@ Immagini del profilo No comment provided by engineer. - - Profile name - Nome del profilo - No comment provided by engineer. - - - Profile name: - Nome del profilo: - No comment provided by engineer. - Profile password Password del profilo @@ -5378,6 +5375,10 @@ Attivalo nelle impostazioni *Rete e server*. Rimuovi No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Rimuovi immagine @@ -5571,12 +5572,13 @@ Attivalo nelle impostazioni *Rete e server*. Save Salva - chat item action + alert button + chat item action Save (and notify contacts) Salva (e avvisa i contatti) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Attivalo nelle impostazioni *Rete e server*. Salva archivio No comment provided by engineer. - - Save auto-accept settings - Salva le impostazioni di accettazione automatica - No comment provided by engineer. - Save group profile Salva il profilo del gruppo @@ -5643,16 +5640,15 @@ Attivalo nelle impostazioni *Rete e server*. Salvare i server? No comment provided by engineer. - - Save settings? - Salvare le impostazioni? - No comment provided by engineer. - Save welcome message? Salvare il messaggio di benvenuto? No comment provided by engineer. + + Save your profile? + alert title + Saved Salvato @@ -6092,6 +6088,10 @@ Attivalo nelle impostazioni *Rete e server*. Impostazioni No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Forma delle immagini del profilo @@ -6296,6 +6296,10 @@ Attivalo nelle impostazioni *Rete e server*. Leggera blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Alcuni file non sono stati esportati: @@ -6667,6 +6671,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il testo che hai incollato non è un link SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Temi @@ -7750,6 +7758,10 @@ Ripetere la richiesta di connessione? Il tuo database della chat non è crittografato: imposta la password per crittografarlo. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles I tuoi profili di chat @@ -7804,13 +7816,15 @@ Ripetere la richiesta di connessione? Verrà condiviso il tuo profilo **%@**. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. -I server di SimpleX non possono vedere il tuo profilo. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 0b9b431c8b..3edfb59c57 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -994,6 +994,10 @@ 画像を自動的に受信 No comment provided by engineer. + + Auto-accept settings + alert title + Back 戻る @@ -1148,7 +1152,7 @@ Cancel 中止 - No comment provided by engineer. + alert button Cancel migration @@ -1287,6 +1291,10 @@ チャット設定 No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2710,6 +2718,10 @@ This is your own one-time link! %@ サーバーのロード中にエラーが発生 No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3114,11 +3126,6 @@ Error: %2$@ フルネーム (任意): No comment provided by engineer. - - Full name: - フルネーム: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -4743,14 +4750,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password プロフィールのパスワード @@ -5051,6 +5050,10 @@ Enable in *Network & servers* settings. 削除 No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5229,12 +5232,13 @@ Enable in *Network & servers* settings. Save 保存 - chat item action + alert button + chat item action Save (and notify contacts) 保存(連絡先に通知) - No comment provided by engineer. + alert button Save and notify contact @@ -5260,11 +5264,6 @@ Enable in *Network & servers* settings. アーカイブを保存 No comment provided by engineer. - - Save auto-accept settings - 自動受け入れ設定を保存する - No comment provided by engineer. - Save group profile グループプロフィールの保存 @@ -5300,16 +5299,15 @@ Enable in *Network & servers* settings. サーバを保存しますか? No comment provided by engineer. - - Save settings? - 設定を保存しますか? - No comment provided by engineer. - Save welcome message? ウェルカムメッセージを保存しますか? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5708,6 +5706,10 @@ Enable in *Network & servers* settings. 設定 No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5899,6 +5901,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6248,6 +6254,10 @@ It can happen because of some bug or when the connection is compromised.The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7243,6 +7253,10 @@ Repeat connection request? チャット データベースは暗号化されていません - 暗号化するにはパスフレーズを設定してください。 No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles あなたのチャットプロフィール @@ -7296,13 +7310,15 @@ Repeat connection request? あなたのプロファイル **%@** が共有されます。 No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - プロフィールはデバイスに保存され、連絡先とのみ共有されます。 -SimpleX サーバーはあなたのプロファイルを参照できません。 + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。 No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。 diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index cd52825882..61fdcc1258 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1024,6 +1024,10 @@ Afbeeldingen automatisch accepteren No comment provided by engineer. + + Auto-accept settings + alert title + Back Terug @@ -1197,7 +1201,7 @@ Cancel Annuleren - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Gesprek voorkeuren No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Chat thema @@ -2870,6 +2878,10 @@ Dit is uw eigen eenmalige link! Fout bij het laden van %@ servers No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Fout bij het openen van de chat @@ -3309,11 +3321,6 @@ Fout: %2$@ Volledige naam (optioneel) No comment provided by engineer. - - Full name: - Volledige naam: - No comment provided by engineer. - Fully decentralized – visible only to members. Volledig gedecentraliseerd – alleen zichtbaar voor leden. @@ -5045,16 +5052,6 @@ Fout: %@ Profiel afbeeldingen No comment provided by engineer. - - Profile name - Profielnaam - No comment provided by engineer. - - - Profile name: - Profielnaam: - No comment provided by engineer. - Profile password Profiel wachtwoord @@ -5378,6 +5375,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Verwijderen No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Verwijder afbeelding @@ -5571,12 +5572,13 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save Opslaan - chat item action + alert button + chat item action Save (and notify contacts) Bewaar (en informeer contacten) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Bewaar archief No comment provided by engineer. - - Save auto-accept settings - Sla instellingen voor automatisch accepteren op - No comment provided by engineer. - Save group profile Groep profiel opslaan @@ -5643,16 +5640,15 @@ Schakel dit in in *Netwerk en servers*-instellingen. Servers opslaan? No comment provided by engineer. - - Save settings? - Instellingen opslaan? - No comment provided by engineer. - Save welcome message? Welkom bericht opslaan? No comment provided by engineer. + + Save your profile? + alert title + Saved Opgeslagen @@ -6092,6 +6088,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Instellingen No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Vorm profiel afbeeldingen @@ -6296,6 +6296,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Sommige bestanden zijn niet geëxporteerd: @@ -6667,6 +6671,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De tekst die u hebt geplakt is geen SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Thema's @@ -7750,6 +7758,10 @@ Verbindingsverzoek herhalen? Uw chat database is niet versleuteld, stel een wachtwoord in om deze te versleutelen. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Uw chat profielen @@ -7804,13 +7816,15 @@ Verbindingsverzoek herhalen? Uw profiel **%@** wordt gedeeld. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. -SimpleX servers kunnen uw profiel niet zien. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index a474f30768..87f19d69a3 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1024,6 +1024,10 @@ Automatyczne akceptowanie obrazów No comment provided by engineer. + + Auto-accept settings + alert title + Back Wstecz @@ -1197,7 +1201,7 @@ Cancel Anuluj - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Preferencje czatu No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Motyw czatu @@ -2870,6 +2878,10 @@ To jest twój jednorazowy link! Błąd ładowania %@ serwerów No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Błąd otwierania czatu @@ -3309,11 +3321,6 @@ Błąd: %2$@ Pełna nazwa (opcjonalna) No comment provided by engineer. - - Full name: - Pełna nazwa: - No comment provided by engineer. - Fully decentralized – visible only to members. W pełni zdecentralizowana – widoczna tylko dla członków. @@ -5045,16 +5052,6 @@ Błąd: %@ Zdjęcia profilowe No comment provided by engineer. - - Profile name - Nazwa profilu - No comment provided by engineer. - - - Profile name: - Nazwa profilu: - No comment provided by engineer. - Profile password Hasło profilu @@ -5378,6 +5375,10 @@ Włącz w ustawianiach *Sieć i serwery* . Usuń No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Usuń obraz @@ -5571,12 +5572,13 @@ Włącz w ustawianiach *Sieć i serwery* . Save Zapisz - chat item action + alert button + chat item action Save (and notify contacts) Zapisz (i powiadom kontakty) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Włącz w ustawianiach *Sieć i serwery* . Zapisz archiwum No comment provided by engineer. - - Save auto-accept settings - Zapisz ustawienia automatycznej akceptacji - No comment provided by engineer. - Save group profile Zapisz profil grupy @@ -5643,16 +5640,15 @@ Włącz w ustawianiach *Sieć i serwery* . Zapisać serwery? No comment provided by engineer. - - Save settings? - Zapisać ustawienia? - No comment provided by engineer. - Save welcome message? Zapisać wiadomość powitalną? No comment provided by engineer. + + Save your profile? + alert title + Saved Zapisane @@ -6092,6 +6088,10 @@ Włącz w ustawianiach *Sieć i serwery* . Ustawienia No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Kształtuj obrazy profilowe @@ -6296,6 +6296,10 @@ Włącz w ustawianiach *Sieć i serwery* . Łagodny blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Niektóre plik(i) nie zostały wyeksportowane: @@ -6667,6 +6671,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Tekst, który wkleiłeś nie jest linkiem SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Motywy @@ -7750,6 +7758,10 @@ Powtórzyć prośbę połączenia? Baza danych czatu nie jest szyfrowana - ustaw hasło, aby ją zaszyfrować. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Twoje profile czatu @@ -7804,13 +7816,15 @@ Powtórzyć prośbę połączenia? Twój profil **%@** zostanie udostępniony. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. -Serwery SimpleX nie mogą zobaczyć Twojego profilu. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 957e599e85..f2e278c9a4 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1024,6 +1024,10 @@ Автоприем изображений No comment provided by engineer. + + Auto-accept settings + alert title + Back Назад @@ -1197,7 +1201,7 @@ Cancel Отменить - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Предпочтения No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Тема чата @@ -2870,6 +2878,10 @@ This is your own one-time link! Ошибка загрузки %@ серверов No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Ошибка доступа к чату @@ -3309,11 +3321,6 @@ Error: %2$@ Полное имя (не обязательно) No comment provided by engineer. - - Full name: - Полное имя: - No comment provided by engineer. - Fully decentralized – visible only to members. Группа полностью децентрализована – она видна только членам. @@ -5045,16 +5052,6 @@ Error: %@ Картинки профилей No comment provided by engineer. - - Profile name - Имя профиля - No comment provided by engineer. - - - Profile name: - Имя профиля: - No comment provided by engineer. - Profile password Пароль профиля @@ -5378,6 +5375,10 @@ Enable in *Network & servers* settings. Удалить No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Удалить изображение @@ -5571,12 +5572,13 @@ Enable in *Network & servers* settings. Save Сохранить - chat item action + alert button + chat item action Save (and notify contacts) Сохранить (и уведомить контакты) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Enable in *Network & servers* settings. Сохранить архив No comment provided by engineer. - - Save auto-accept settings - Сохранить настройки автоприема - No comment provided by engineer. - Save group profile Сохранить профиль группы @@ -5643,16 +5640,15 @@ Enable in *Network & servers* settings. Сохранить серверы? No comment provided by engineer. - - Save settings? - Сохранить настройки? - No comment provided by engineer. - Save welcome message? Сохранить приветственное сообщение? No comment provided by engineer. + + Save your profile? + alert title + Saved Сохранено @@ -6092,6 +6088,10 @@ Enable in *Network & servers* settings. Настройки No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Форма картинок профилей @@ -6296,6 +6296,10 @@ Enable in *Network & servers* settings. Слабое blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Некоторые файл(ы) не были экспортированы: @@ -6667,6 +6671,10 @@ It can happen because of some bug or when the connection is compromised.Вставленный текст не является SimpleX-ссылкой. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Темы @@ -7750,6 +7758,10 @@ Repeat connection request? База данных НЕ зашифрована. Установите пароль, чтобы защитить Ваши данные. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Ваши профили чата @@ -7804,13 +7816,15 @@ Repeat connection request? Будет отправлен Ваш профиль **%@**. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. -SimpleX серверы не могут получить доступ к Вашему профилю. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 366f67d0cd..870c01af8f 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -963,6 +963,10 @@ ยอมรับภาพอัตโนมัติ No comment provided by engineer. + + Auto-accept settings + alert title + Back กลับ @@ -1116,7 +1120,7 @@ Cancel ยกเลิก - No comment provided by engineer. + alert button Cancel migration @@ -1255,6 +1259,10 @@ ค่ากําหนดในการแชท No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2670,6 +2678,10 @@ This is your own one-time link! โหลดเซิร์ฟเวอร์ %@ ผิดพลาด No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3074,11 +3086,6 @@ Error: %2$@ ชื่อเต็ม (ไม่บังคับ) No comment provided by engineer. - - Full name: - ชื่อเต็ม: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -4696,14 +4703,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password รหัสผ่านโปรไฟล์ @@ -5003,6 +5002,10 @@ Enable in *Network & servers* settings. ลบ No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5181,12 +5184,13 @@ Enable in *Network & servers* settings. Save บันทึก - chat item action + alert button + chat item action Save (and notify contacts) บันทึก (และแจ้งผู้ติดต่อ) - No comment provided by engineer. + alert button Save and notify contact @@ -5212,11 +5216,6 @@ Enable in *Network & servers* settings. บันทึกไฟล์เก็บถาวร No comment provided by engineer. - - Save auto-accept settings - บันทึกการตั้งค่าการยอมรับอัตโนมัติ - No comment provided by engineer. - Save group profile บันทึกโปรไฟล์กลุ่ม @@ -5252,16 +5251,15 @@ Enable in *Network & servers* settings. บันทึกเซิร์ฟเวอร์? No comment provided by engineer. - - Save settings? - บันทึกการตั้งค่า? - No comment provided by engineer. - Save welcome message? บันทึกข้อความต้อนรับ? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5665,6 +5663,10 @@ Enable in *Network & servers* settings. การตั้งค่า No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5853,6 +5855,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6203,6 +6209,10 @@ It can happen because of some bug or when the connection is compromised.The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7194,6 +7204,10 @@ Repeat connection request? ฐานข้อมูลการแชทของคุณไม่ได้ถูก encrypt - ตั้งรหัสผ่านเพื่อ encrypt No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles โปรไฟล์แชทของคุณ @@ -7246,13 +7260,15 @@ Repeat connection request? Your profile **%@** will be shared. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น -เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้ + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้ No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 13fa3c8a84..3e1199666f 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1009,6 +1009,10 @@ Fotoğrafları otomatik kabul et No comment provided by engineer. + + Auto-accept settings + alert title + Back Geri @@ -1173,7 +1177,7 @@ Cancel İptal et - No comment provided by engineer. + alert button Cancel migration @@ -1317,6 +1321,10 @@ Sohbet tercihleri No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2797,6 +2805,10 @@ Bu senin kendi tek kullanımlık bağlantın! %@ sunucuları yüklenirken hata oluştu No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Sohbeti açarken sorun oluştu @@ -3223,11 +3235,6 @@ Hata: %2$@ Bütün isim (opsiyonel) No comment provided by engineer. - - Full name: - Bütün isim: - No comment provided by engineer. - Fully decentralized – visible only to members. Tamamiyle merkezi olmayan - sadece kişilere görünür. @@ -4926,16 +4933,6 @@ Hata: %@ Profil resimleri No comment provided by engineer. - - Profile name - Profil ismi - No comment provided by engineer. - - - Profile name: - Profil ismi: - No comment provided by engineer. - Profile password Profil parolası @@ -5246,6 +5243,10 @@ Enable in *Network & servers* settings. Sil No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5432,12 +5433,13 @@ Enable in *Network & servers* settings. Save Kaydet - chat item action + alert button + chat item action Save (and notify contacts) Kaydet (ve kişilere bildir) - No comment provided by engineer. + alert button Save and notify contact @@ -5463,11 +5465,6 @@ Enable in *Network & servers* settings. Arşivi kaydet No comment provided by engineer. - - Save auto-accept settings - Otomatik kabul et ayarlarını kaydet - No comment provided by engineer. - Save group profile Grup profilini kaydet @@ -5503,16 +5500,15 @@ Enable in *Network & servers* settings. Sunucular kaydedilsin mi? No comment provided by engineer. - - Save settings? - Ayarlar kaydedilsin mi? - No comment provided by engineer. - Save welcome message? Hoşgeldin mesajı kaydedilsin mi? No comment provided by engineer. + + Save your profile? + alert title + Saved Kaydedildi @@ -5932,6 +5928,10 @@ Enable in *Network & servers* settings. Ayarlar No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Profil resimlerini şekillendir @@ -6130,6 +6130,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6489,6 +6493,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Yapıştırdığın metin bir SimpleX bağlantısı değildir. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7548,6 +7556,10 @@ Bağlantı isteği tekrarlansın mı? Sohbet veritabanınız şifrelenmemiş - şifrelemek için parola ayarlayın. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Sohbet profillerin @@ -7602,13 +7614,15 @@ Bağlantı isteği tekrarlansın mı? Profiliniz **%@** paylaşılacaktır. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. -SimpleX sunucuları profilinizi göremez. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 69685620ba..d371b29109 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1024,6 +1024,10 @@ Автоматичне прийняття зображень No comment provided by engineer. + + Auto-accept settings + alert title + Back Назад @@ -1197,7 +1201,7 @@ Cancel Скасувати - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Налаштування чату No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Тема чату @@ -2870,6 +2878,10 @@ This is your own one-time link! Помилка завантаження %@ серверів No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Помилка відкриття чату @@ -3309,11 +3321,6 @@ Error: %2$@ Повне ім'я (необов'язково) No comment provided by engineer. - - Full name: - Повне ім'я: - No comment provided by engineer. - Fully decentralized – visible only to members. Повністю децентралізована - видима лише для учасників. @@ -5045,16 +5052,6 @@ Error: %@ Зображення профілю No comment provided by engineer. - - Profile name - Назва профілю - No comment provided by engineer. - - - Profile name: - Ім'я профілю: - No comment provided by engineer. - Profile password Пароль до профілю @@ -5378,6 +5375,10 @@ Enable in *Network & servers* settings. Видалити No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Видалити зображення @@ -5571,12 +5572,13 @@ Enable in *Network & servers* settings. Save Зберегти - chat item action + alert button + chat item action Save (and notify contacts) Зберегти (і повідомити контактам) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Enable in *Network & servers* settings. Зберегти архів No comment provided by engineer. - - Save auto-accept settings - Зберегти налаштування автоприйому - No comment provided by engineer. - Save group profile Зберегти профіль групи @@ -5643,16 +5640,15 @@ Enable in *Network & servers* settings. Зберегти сервери? No comment provided by engineer. - - Save settings? - Зберегти налаштування? - No comment provided by engineer. - Save welcome message? Зберегти вітальне повідомлення? No comment provided by engineer. + + Save your profile? + alert title + Saved Збережено @@ -6092,6 +6088,10 @@ Enable in *Network & servers* settings. Налаштування No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Сформуйте зображення профілю @@ -6296,6 +6296,10 @@ Enable in *Network & servers* settings. М'який blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Деякі файли не було експортовано: @@ -6667,6 +6671,10 @@ It can happen because of some bug or when the connection is compromised.Текст, який ви вставили, не є посиланням SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Теми @@ -7750,6 +7758,10 @@ Repeat connection request? Ваша база даних чату не зашифрована - встановіть ключову фразу, щоб зашифрувати її. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Ваші профілі чату @@ -7804,13 +7816,15 @@ Repeat connection request? Ваш профіль **%@** буде опублікований. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. -Сервери SimpleX не бачать ваш профіль. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index b524846ffd..d7d6161be8 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -994,6 +994,10 @@ 自动接受图片 No comment provided by engineer. + + Auto-accept settings + alert title + Back 返回 @@ -1158,7 +1162,7 @@ Cancel 取消 - No comment provided by engineer. + alert button Cancel migration @@ -1301,6 +1305,10 @@ 聊天偏好设置 No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2762,6 +2770,10 @@ This is your own one-time link! 加载 %@ 服务器错误 No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3181,11 +3193,6 @@ Error: %2$@ 全名(可选) No comment provided by engineer. - - Full name: - 全名: - No comment provided by engineer. - Fully decentralized – visible only to members. 完全去中心化 - 仅对成员可见。 @@ -4861,15 +4868,6 @@ Error: %@ 个人资料图 No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - 显示名: - No comment provided by engineer. - Profile password 个人资料密码 @@ -5174,6 +5172,10 @@ Enable in *Network & servers* settings. 移除 No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5359,12 +5361,13 @@ Enable in *Network & servers* settings. Save 保存 - chat item action + alert button + chat item action Save (and notify contacts) 保存(并通知联系人) - No comment provided by engineer. + alert button Save and notify contact @@ -5390,11 +5393,6 @@ Enable in *Network & servers* settings. 保存存档 No comment provided by engineer. - - Save auto-accept settings - 保存自动接受设置 - No comment provided by engineer. - Save group profile 保存群组资料 @@ -5430,16 +5428,15 @@ Enable in *Network & servers* settings. 保存服务器? No comment provided by engineer. - - Save settings? - 保存设置? - No comment provided by engineer. - Save welcome message? 保存欢迎信息? No comment provided by engineer. + + Save your profile? + alert title + Saved 已保存 @@ -5855,6 +5852,10 @@ Enable in *Network & servers* settings. 设置 No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images 改变个人资料图形状 @@ -6051,6 +6052,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6409,6 +6414,10 @@ It can happen because of some bug or when the connection is compromised.您粘贴的文本不是 SimpleX 链接。 No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7448,6 +7457,10 @@ Repeat connection request? 您的聊天数据库未加密——设置密码来加密。 No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles 您的聊天资料 @@ -7501,13 +7514,15 @@ Repeat connection request? 您的个人资料 **%@** 将被共享。 No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - 您的资料存储在您的设备上并仅与您的联系人共享。 -SimpleX 服务器无法看到您的资料。 + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + 您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。 No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your profile, contacts and delivered messages are stored on your device. 您的资料、联系人和发送的消息存储在您的设备上。 diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 338690c8e8..41fe127093 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -252,7 +252,7 @@ private fun ActiveUserSection( } UserPickerOptionRow( painterResource(MR.images.ic_qr_code), - if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_public_contact_address) else generalGetString(MR.strings.create_public_contact_address), + if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_simplex_contact_address) else generalGetString(MR.strings.create_simplex_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped ) UserPickerOptionRow( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 1f546fb863..b357272e16 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -174,7 +174,7 @@ private fun UserAddressLayout( saveAas: (AutoAcceptState, MutableState) -> Unit, ) { ColumnWithScrollBar { - AppBarTitle(stringResource(MR.strings.public_address), hostDevice(user?.remoteHostId)) + AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId)) Column( Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally, @@ -230,7 +230,7 @@ private fun UserAddressLayout( private fun CreateAddressButton(onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_qr_code), - stringResource(MR.strings.create_public_address), + stringResource(MR.strings.create_simplex_address), onClick, iconColor = MaterialTheme.colors.primary, textColor = MaterialTheme.colors.primary, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 704c6533ae..f3e491245b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -701,10 +701,6 @@ %s is verified %s is not verified - - Create public address - Your public address - Your settings Your SimpleX address @@ -853,8 +849,6 @@ Notifications will stop working until you re-launch the app - Public address - Create public address Create address Delete address? Your contacts will remain connected. From 4f99075b148a9052d81272a7b344de384ecf63ff Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 16 Sep 2024 16:50:37 +0100 Subject: [PATCH 123/440] ui: translations (#4889) * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 36.4% (741 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Indonesian) Currently translated at 5.8% (118 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 36.4% (741 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Indonesian) Currently translated at 5.8% (118 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Indonesian) Currently translated at 6.3% (130 of 2032 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% (2032 of 2032 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% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 83.5% (1697 of 2032 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 12.9% (263 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 37.1% (755 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 37.8% (769 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Czech) Currently translated at 88.7% (1804 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Vietnamese) Currently translated at 38.4% (782 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * process localizations * fix links --------- Co-authored-by: summoner001 Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: rbasliana Co-authored-by: Ghost of Sparta Co-authored-by: mlanp Co-authored-by: re me Co-authored-by: Mateus Pereira Co-authored-by: rbasliana <91536894+rbasliana@users.noreply.github.com> Co-authored-by: zenobit --- .../de.xcloc/Localized Contents/de.xliff | 26 +- .../hu.xcloc/Localized Contents/hu.xliff | 148 +-- .../Localized Contents/zh-Hans.xliff | 399 ++++++- .../zh-Hans.lproj/InfoPlist.strings | 14 +- .../zh-Hans.lproj/Localizable.strings | 116 +- apps/ios/bg.lproj/Localizable.strings | 24 +- apps/ios/cs.lproj/Localizable.strings | 18 +- apps/ios/de.lproj/Localizable.strings | 50 +- apps/ios/es.lproj/Localizable.strings | 24 +- apps/ios/fi.lproj/Localizable.strings | 18 +- apps/ios/fr.lproj/Localizable.strings | 24 +- apps/ios/hu.lproj/Localizable.strings | 168 ++- apps/ios/it.lproj/Localizable.strings | 24 +- apps/ios/ja.lproj/Localizable.strings | 18 +- apps/ios/nl.lproj/Localizable.strings | 24 +- apps/ios/pl.lproj/Localizable.strings | 24 +- apps/ios/ru.lproj/Localizable.strings | 24 +- apps/ios/th.lproj/Localizable.strings | 18 +- apps/ios/tr.lproj/Localizable.strings | 24 +- apps/ios/uk.lproj/Localizable.strings | 24 +- apps/ios/zh-Hans.lproj/Localizable.strings | 997 +++++++++++++++++- .../SimpleX--iOS--InfoPlist.strings | 3 + .../commonMain/resources/MR/cs/strings.xml | 7 +- .../commonMain/resources/MR/de/strings.xml | 36 +- .../commonMain/resources/MR/hu/strings.xml | 272 ++--- .../commonMain/resources/MR/in/strings.xml | 168 ++- .../resources/MR/pt-rBR/strings.xml | 461 +++++++- .../commonMain/resources/MR/vi/strings.xml | 57 + 28 files changed, 2529 insertions(+), 681 deletions(-) diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index cf2f85f2c0..884c322dae 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1405,22 +1405,22 @@ Clear - Löschen + Entfernen swipe action Clear conversation - Chatinhalte löschen + Chat-Inhalte entfernen No comment provided by engineer. Clear conversation? - Unterhaltung löschen? + Chat-Inhalte entfernen? No comment provided by engineer. Clear private notes? - Private Notizen löschen? + Private Notizen entfernen? No comment provided by engineer. @@ -1734,7 +1734,7 @@ Das ist Ihr eigener Einmal-Link! Conversation deleted! - Unterhaltung gelöscht! + Chat-Inhalte entfernt! No comment provided by engineer. @@ -3478,7 +3478,7 @@ Fehler: %2$@ Group will be deleted for you - this cannot be undone! - Die Gruppe wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! + Die Gruppe wird nur bei Ihnen gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -3933,7 +3933,7 @@ Das ist Ihr Link für die Gruppe %@! Keep conversation - Unterhaltung behalten + Chat-Inhalte beibehalten No comment provided by engineer. @@ -4635,7 +4635,7 @@ Dies erfordert die Aktivierung eines VPNs. Only delete conversation - Nur die Unterhaltung löschen + Nur die Chat-Inhalte löschen No comment provided by engineer. @@ -5176,7 +5176,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reachable chat toolbar - Erreichbare Chat-Symbolleiste + Chat-Symbolleiste unten No comment provided by engineer. @@ -5716,7 +5716,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Search or paste SimpleX link - Suchen oder fügen Sie den SimpleX-Link ein + Suchen oder SimpleX-Link einfügen No comment provided by engineer. @@ -6628,7 +6628,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The messages will be deleted for all members. - Die Nachrichten werden für alle Mitglieder gelöscht werden. + Die Nachrichten werden für alle Gruppenmitglieder gelöscht. No comment provided by engineer. @@ -7131,7 +7131,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use the app with one hand. - Die App mit einer Hand nutzen. + Die App mit einer Hand bedienen. No comment provided by engineer. @@ -7563,7 +7563,7 @@ Verbindungsanfrage wiederholen? You can still view conversation with %@ in the list of chats. - Sie können in der Chatliste weiterhin die Unterhaltung mit %@ einsehen. + Sie können in der Chat-Liste weiterhin die Unterhaltung mit %@ einsehen. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 89bce685a0..063dd3d14a 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -427,8 +427,8 @@ - voice messages up to 5 minutes. - custom time to disappear. - editing history. - - hangüzenetek legfeljebb 5 perces időtartamig. -- egyedi eltűnési időhatár megadása. + - 5 perc hosszúságú hangüzenetek. +- egyedi üzenet-eltűnési időkorlát. - előzmények szerkesztése. No comment provided by engineer. @@ -496,7 +496,7 @@ <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> <p>Üdvözlöm!</p> -<p><a href=„%@”>Csatlakozzon hozzám a SimpleX Chaten</a></p> +<p><a href=„%@”>Csatlakozzon hozzám a SimpleX Chaten keresztül</a></p> email text @@ -570,7 +570,7 @@ Accept connection request? - Kapcsolódási kérelem elfogadása? + Ismerőskérelem elfogadása? No comment provided by engineer. @@ -1050,7 +1050,7 @@ Bad message hash - Hibás az üzenet ellenőrzőösszege + Hibás az üzenet hasító értéke No comment provided by engineer. @@ -1530,7 +1530,7 @@ Connect to desktop - Kapcsolódás számítógéphez + Társítás számítógéppel No comment provided by engineer. @@ -1554,7 +1554,7 @@ Ez az ön SimpleX címe! Connect to yourself? This is your own one-time link! Kapcsolódás saját magához? -Ez az egyszer használatos hivatkozása! +Ez az ön egyszer használatos hivatkozása! No comment provided by engineer. @@ -1584,7 +1584,7 @@ Ez az egyszer használatos hivatkozása! Connected desktop - Csatlakoztatott számítógép + Társított számítógép No comment provided by engineer. @@ -1679,7 +1679,7 @@ Ez az egyszer használatos hivatkozása! Contact already exists - Létező ismerős + Az ismerős már létezik No comment provided by engineer. @@ -1793,7 +1793,7 @@ Ez az egyszer használatos hivatkozása! Create group link - Csoportos hivatkozás létrehozása + Csoporthivatkozás létrehozása No comment provided by engineer. @@ -1848,7 +1848,7 @@ Ez az egyszer használatos hivatkozása! Creating archive link - Archív hivatkozás létrehozása + Archívum hivatkozás létrehozása No comment provided by engineer. @@ -2177,7 +2177,7 @@ Ez az egyszer használatos hivatkozása! Delete pending connection? - Függő kapcsolatfelvételi kérések törlése? + Függőben lévő ismerőskérelem törlése? No comment provided by engineer. @@ -2487,7 +2487,7 @@ Ez az egyszer használatos hivatkozása! Duplicate display name! - Duplikált megjelenítési név! + Duplikált megjelenített név! No comment provided by engineer. @@ -2732,7 +2732,7 @@ Ez az egyszer használatos hivatkozása! Error adding member(s) - Hiba a tag(-ok) hozzáadásakor + Hiba a tag(ok) hozzáadásakor No comment provided by engineer. @@ -2775,7 +2775,7 @@ Ez az egyszer használatos hivatkozása! Error creating group link - Hiba a csoport hivatkozásának létrehozásakor + Hiba a csoporthivatkozás létrehozásakor No comment provided by engineer. @@ -2998,7 +2998,7 @@ Ez az egyszer használatos hivatkozása! Error updating group link - Hiba a csoport hivatkozás frissítésekor + Hiba a csoporthivatkozás frissítésekor No comment provided by engineer. @@ -3398,12 +3398,12 @@ Hiba: %2$@ Group link - Csoport hivatkozás + Csoporthivatkozás No comment provided by engineer. Group links - Csoport hivatkozások + Csoporthivatkozások No comment provided by engineer. @@ -3473,7 +3473,7 @@ Hiba: %2$@ Group will be deleted for all members - this cannot be undone! - Csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza! + A csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza! No comment provided by engineer. @@ -3770,12 +3770,12 @@ Hiba: %2$@ Invalid connection link - Érvénytelen kapcsolati hivatkozás + Érvénytelen kapcsolattartási hivatkozás No comment provided by engineer. Invalid display name! - Érvénytelen megjelenítendő felhaszálónév! + Érvénytelen megjelenítendő név! No comment provided by engineer. @@ -3993,7 +3993,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Let's talk in SimpleX Chat - Beszélgessünk a SimpleX Chat-ben + Beszélgessünk a SimpleX Chatben email subject @@ -4008,17 +4008,17 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Link mobile and desktop apps! 🔗 - Társítsa össze a mobil és az asztali alkalmazásokat! 🔗 + Társítsa össze a mobil és asztali alkalmazásokat! 🔗 No comment provided by engineer. Linked desktop options - Összekapcsolt számítógép beállítások + Társított számítógép beállítások No comment provided by engineer. Linked desktops - Összekapcsolt számítógépek + Társított számítógépek No comment provided by engineer. @@ -4083,7 +4083,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Sokan kérdezték: *ha a SimpleX-nek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?* + Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?* No comment provided by engineer. @@ -4342,12 +4342,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Moderated at - Moderálva lett ekkor: + Moderálva ekkor: No comment provided by engineer. Moderated at: %@ - Moderálva lett ekkor: %@ + Moderálva ekkor: %@ copied message info @@ -4432,7 +4432,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! New contact request - Új kapcsolattartási kérelem + Új ismerőskérelem notification @@ -4492,7 +4492,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! No contacts selected - Nem kerültek ismerősök kiválasztásra + Nincs kiválasztva ismerős No comment provided by engineer. @@ -4557,7 +4557,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Nothing selected - Semmi sincs kiválasztva + Nincs kiválasztva semmi No comment provided by engineer. @@ -4601,7 +4601,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Old database archive - Régi adatbázis archívum + Régi adatbázis-archívum No comment provided by engineer. @@ -4630,7 +4630,7 @@ VPN engedélyezése szükséges. Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. - Csak a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végponttól-végpontig titkosítással** küldött üzeneteket. + Csak a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket. No comment provided by engineer. @@ -4765,7 +4765,7 @@ VPN engedélyezése szükséges. Or securely share this file link - Vagy a fájl hivítkozásának biztonságos megosztása + Vagy ossza meg biztonságosan ezt a fájlhivatkozást No comment provided by engineer. @@ -4825,7 +4825,7 @@ VPN engedélyezése szükséges. Past member %@ - Már nem tag - %@ + %@ (már nem tag) past/unknown group member @@ -4845,12 +4845,12 @@ VPN engedélyezése szükséges. Paste the link you received - Fogadott hivatkozás beillesztése + Kapott hivatkozás beillesztése No comment provided by engineer. Pending - Függő + Függőben No comment provided by engineer. @@ -4897,7 +4897,7 @@ Minden további problémát osszon meg a fejlesztőkkel. Please check that you used the correct link or ask your contact to send you another one. - Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg ismerősét, hogy küldjön egy másikat. + Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg az ismerősét, hogy küldjön egy másikat. No comment provided by engineer. @@ -5451,7 +5451,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reset - Alaphelyzetbe állítás + Visszaállítás No comment provided by engineer. @@ -5471,7 +5471,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reset colors - Színek alaphelyzetbe állítása + Színek visszaállítása No comment provided by engineer. @@ -5481,7 +5481,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reset to defaults - Alaphelyzetbe állítás + Visszaállítás alaphelyzetbe No comment provided by engineer. @@ -6020,12 +6020,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Servers info - információk a kiszolgálókról + Információk a kiszolgálókról No comment provided by engineer. Servers statistics will be reset - this cannot be undone! - A kiszolgálók statisztikái visszaállnak - ez nem vonható vissza! + A kiszolgálók statisztikái visszaállnak - ez a művelet nem vonható vissza! No comment provided by engineer. @@ -6243,7 +6243,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX group link - SimpleX csoport hivatkozás + SimpleX csoporthivatkozás simplex link type @@ -6437,7 +6437,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Support SimpleX Chat - Támogassa a SimpleX Chatet + SimpleX Chat támogatása No comment provided by engineer. @@ -6556,7 +6556,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Thanks to the users – contribute via Weblate! - Köszönet a felhasználóknak - hozzájárulás a Weblaten! + Köszönet a felhasználóknak - hozzájárulás a Weblate-en! No comment provided by engineer. @@ -6588,12 +6588,12 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The code you scanned is not a SimpleX link QR code. - A beolvasott kód nem egy SimpleX hivatkozás QR-kód. + A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozás. No comment provided by engineer. The connection you accepted will be cancelled! - Az ön által elfogadott kapcsolat vissza lesz vonva! + Az ön által elfogadott kérelem vissza lesz vonva! No comment provided by engineer. @@ -6613,7 +6613,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The hash of the previous message is different. - Az előző üzenet ellenőrzőösszege különbözik. + Az előző üzenet hasító értéke különbözik. No comment provided by engineer. @@ -6722,7 +6722,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This display name is invalid. Please choose another name. - Ez a megjelenített felhasználónév érvénytelen. Válasszon egy másik nevet. + Ez a megjelenített név érvénytelen. Válasszon egy másik nevet. No comment provided by engineer. @@ -6742,12 +6742,12 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This is your own one-time link! - Ez az egyszer használatos hivatkozása! + Ez az ön egyszer használatos hivatkozása! No comment provided by engineer. This link was used with another mobile device, please create a new link on the desktop. - Ezt a hivatkozást egy másik mobilleszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén. + Ezt a hivatkozást egy másik mobileszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén. No comment provided by engineer. @@ -6854,12 +6854,12 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Trying to connect to the server used to receive messages from this contact (error: %@). - Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál (hiba: %@). + Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál (hiba: %@). No comment provided by engineer. Trying to connect to the server used to receive messages from this contact. - Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál. + Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. No comment provided by engineer. @@ -6965,8 +6965,8 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unless your contact deleted the connection or this link was already used, it might be a bug - please report it. To connect, please ask your contact to create another connection link and check that you have a stable network connection. - Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, lehet hogy ez egy hiba – jelentse a problémát. -A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. + Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát. +A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. No comment provided by engineer. @@ -7091,7 +7091,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Use from desktop - Használat számítógépről + Társítás számítógéppel No comment provided by engineer. @@ -7431,7 +7431,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol You are already connected to %@. - Már kapcsolódva van hozzá: %@. + Ön már kapcsolódva van ehhez: %@. No comment provided by engineer. @@ -7473,7 +7473,7 @@ Csatlakozási kérés megismétlése? You are connected to the server used to receive messages from this contact. - Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál. + Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. No comment provided by engineer. @@ -7523,7 +7523,7 @@ Csatlakozási kérés megismétlése? You can make it visible to your SimpleX contacts via Settings. - Láthatóvá teheti SimpleX ismerősök számára a Beállításokban. + Láthatóvá teheti a SimpleXbeli ismerősei számára a „Beállításokban”. No comment provided by engineer. @@ -7670,7 +7670,7 @@ Kapcsolódási kérés megismétlése? You will be connected when group link host's device is online, please wait or check later! - Akkor lesz kapcsolódva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, amikor a csoporthivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! No comment provided by engineer. @@ -7735,7 +7735,7 @@ Kapcsolódási kérés megismétlése? Your SimpleX address - Az ön SimpleX címe + Profil SimpleX címe No comment provided by engineer. @@ -7862,7 +7862,7 @@ Kapcsolódási kérés megismétlése? [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat) + [Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. @@ -7942,7 +7942,7 @@ Kapcsolódási kérés megismétlése? bad message hash - hibás az üzenet ellenőrzőösszege + hibás az üzenet hasító értéke integrity error chat item @@ -7972,7 +7972,7 @@ Kapcsolódási kérés megismétlése? call error - hiba a hívásban + híváshiba call status @@ -8067,7 +8067,7 @@ Kapcsolódási kérés megismétlése? connecting call… - hívás kapcsolódik… + kapcsolódási hívás… call status @@ -8307,12 +8307,12 @@ Kapcsolódási kérés megismétlése? incognito via group link - inkognitó a csoportos hivatkozáson keresztül + inkognitó a csoporthivatkozáson keresztül chat list item description incognito via one-time link - inkognitó az egyszer használatos hivatkozáson keresztül + inkognitó egy egyszer használatos hivatkozáson keresztül chat list item description @@ -8362,7 +8362,7 @@ Kapcsolódási kérés megismétlése? invited via your group link - meghíva az ön csoport hivatkozásán keresztül + meghíva az ön csoporthivatkozásán keresztül rcv group event chat item @@ -8382,7 +8382,7 @@ Kapcsolódási kérés megismétlése? marked deleted - töröltnek jelölve + törlésre jelölve marked deleted chat item preview text @@ -8524,7 +8524,7 @@ Kapcsolódási kérés megismétlése? received answer… - fogadott válasz… + válasz fogadása… No comment provided by engineer. @@ -8698,12 +8698,12 @@ utoljára fogadott üzenet: %2$@ via group link - csoport hivatkozáson keresztül + a csoporthivatkozáson keresztül chat list item description via one-time link - egyszer használatos hivatkozáson keresztül + egy egyszer használatos hivatkozáson keresztül chat list item description @@ -8768,7 +8768,7 @@ utoljára fogadott üzenet: %2$@ you blocked %@ - ön letiltotta %@-t + ön letiltotta őt: %@ snd group event chat item diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index d7d6161be8..b7abefd465 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ 已下载 No comment provided by engineer. @@ -128,6 +129,7 @@ %@ uploaded + %@ 已上传 No comment provided by engineer. @@ -226,14 +228,17 @@ %lld messages blocked by admin + %lld 被管理员阻止的消息 No comment provided by engineer. %lld messages marked deleted + %lld 标记为已删除的消息 No comment provided by engineer. %lld messages moderated by %@ + %lld 审核的留言 by %@ No comment provided by engineer. @@ -308,10 +313,12 @@ (new) + (新) No comment provided by engineer. (this device v%@) + (此设备 v%@) No comment provided by engineer. @@ -321,6 +328,7 @@ **Add contact**: to create a new invitation link, or connect via a link you received. + **添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接. No comment provided by engineer. @@ -330,6 +338,7 @@ **Create group**: to create a new group. + **创建群组**: 创建一个新群组. No comment provided by engineer. @@ -344,6 +353,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **请注意**: 在两台设备上使用相同的数据库将破坏来自您的连接的消息解密,作为一种安全保护. No comment provided by engineer. @@ -363,6 +373,7 @@ **Warning**: the archive will be removed. + **警告**: 存档将被删除. No comment provided by engineer. @@ -428,6 +439,7 @@ 0 sec + 0 秒 time to disappear @@ -546,6 +558,7 @@ Accent + 强调 No comment provided by engineer. @@ -573,14 +586,17 @@ Acknowledged + 确认 No comment provided by engineer. Acknowledgement errors + 确认错误 No comment provided by engineer. Active connections + 活动连接 No comment provided by engineer. @@ -625,14 +641,17 @@ Additional accent + 附加重音 No comment provided by engineer. Additional accent 2 + 附加重音 2 No comment provided by engineer. Additional secondary + 附加二级 No comment provided by engineer. @@ -662,6 +681,7 @@ Advanced settings + 高级设置 No comment provided by engineer. @@ -681,6 +701,7 @@ All data is private to your device. + 所有数据都是您设备的私有数据. No comment provided by engineer. @@ -700,10 +721,12 @@ All new messages from %@ will be hidden! + 来自 %@ 的所有新消息都将被隐藏! No comment provided by engineer. All profiles + 所有配置文件 No comment provided by engineer. @@ -733,6 +756,7 @@ Allow calls? + 允许通话? No comment provided by engineer. @@ -742,11 +766,12 @@ Allow downgrade + 允许降级 No comment provided by engineer. Allow irreversible message deletion only if your contact allows it to you. (24 hours) - 仅有您的联系人许可后才允许不可撤回消息移除。 + 仅有您的联系人许可后才允许不可撤回消息移除 No comment provided by engineer. @@ -771,11 +796,12 @@ Allow sharing + 允许共享 No comment provided by engineer. Allow to irreversibly delete sent messages. (24 hours) - 允许不可撤回地删除已发送消息。 + 允许不可撤回地删除已发送消息 No comment provided by engineer. @@ -815,7 +841,7 @@ Allow your contacts to irreversibly delete sent messages. (24 hours) - 允许您的联系人不可撤回地删除已发送消息。 + 允许您的联系人不可撤回地删除已发送消息 No comment provided by engineer. @@ -845,6 +871,7 @@ Always use private routing. + 始终使用私有路由。 No comment provided by engineer. @@ -914,6 +941,7 @@ Apply to + 应用于 No comment provided by engineer. @@ -923,10 +951,12 @@ Archive contacts to chat later. + 存档联系人以便稍后聊天. No comment provided by engineer. Archived contacts + 已存档的联系人 No comment provided by engineer. @@ -1005,6 +1035,7 @@ Background + 背景 No comment provided by engineer. @@ -1034,10 +1065,12 @@ Better networking + 更好的网络 No comment provided by engineer. Black + 黑色 No comment provided by engineer. @@ -1077,10 +1110,12 @@ Blur for better privacy. + 模糊处理,提高私密性. No comment provided by engineer. Blur media + 模糊媒体 No comment provided by engineer. @@ -1090,7 +1125,7 @@ Both you and your contact can irreversibly delete sent messages. (24 hours) - 您和您的联系人都可以不可逆转地删除已发送的消息。 + 您和您的联系人都可以不可逆转地删除已发送的消息 No comment provided by engineer. @@ -1130,6 +1165,7 @@ Calls prohibited! + 禁止来电! No comment provided by engineer. @@ -1139,10 +1175,12 @@ Can't call contact + 无法呼叫联系人 No comment provided by engineer. Can't call member + 无法呼叫成员 No comment provided by engineer. @@ -1157,6 +1195,7 @@ Can't message member + 无法向成员发送消息 No comment provided by engineer. @@ -1176,6 +1215,7 @@ Cannot forward message + 无法转发消息 No comment provided by engineer. @@ -1185,6 +1225,7 @@ Capacity exceeded - recipient did not receive previously sent messages. + 超出容量-收件人未收到以前发送的邮件。 snd error text @@ -1250,6 +1291,7 @@ Chat colors + 聊天颜色 No comment provided by engineer. @@ -1269,6 +1311,7 @@ Chat database exported + 导出的聊天数据库 No comment provided by engineer. @@ -1293,6 +1336,7 @@ Chat list + 聊天列表 No comment provided by engineer. @@ -1311,6 +1355,7 @@ Chat theme + 聊天主题 No comment provided by engineer. @@ -1330,6 +1375,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + 在新设备上选择“从另一个设备迁移”并扫描二维码。 No comment provided by engineer. @@ -1344,14 +1390,17 @@ Chunks deleted + 已删除的块 No comment provided by engineer. Chunks downloaded + 下载的块 No comment provided by engineer. Chunks uploaded + 已下载的区块 No comment provided by engineer. @@ -1381,10 +1430,12 @@ Color chats with the new themes. + 使用新主题为聊天着色。 No comment provided by engineer. Color mode + 颜色模式 No comment provided by engineer. @@ -1399,6 +1450,7 @@ Completed + 已完成 No comment provided by engineer. @@ -1408,6 +1460,7 @@ Configured %@ servers + 已配置 %@ 服务器 No comment provided by engineer. @@ -1422,6 +1475,7 @@ Confirm contact deletion? + 确认删除联系人? No comment provided by engineer. @@ -1431,6 +1485,7 @@ Confirm files from unknown servers. + 确认来自未知服务器的文件。 No comment provided by engineer. @@ -1480,6 +1535,7 @@ Connect to your friends faster. + 更快地与您的朋友联系。 No comment provided by engineer. @@ -1490,15 +1546,20 @@ Connect to yourself? This is your own SimpleX address! + 与自己建立联系? +这是您自己的 SimpleX 地址! No comment provided by engineer. Connect to yourself? This is your own one-time link! + 与自己建立联系? +这是您自己的一次性链接! No comment provided by engineer. Connect via contact address + 通过联系地址连接 No comment provided by engineer. @@ -1513,10 +1574,12 @@ This is your own one-time link! Connect with %@ + 与 %@连接 No comment provided by engineer. Connected + 已连接 No comment provided by engineer. @@ -1526,6 +1589,7 @@ This is your own one-time link! Connected servers + 已连接的服务器 No comment provided by engineer. @@ -1535,6 +1599,7 @@ This is your own one-time link! Connecting + 正在连接 No comment provided by engineer. @@ -1549,6 +1614,7 @@ This is your own one-time link! Connecting to contact, please wait or check later! + 正在连接到联系人,请稍候或稍后检查! No comment provided by engineer. @@ -1563,6 +1629,7 @@ This is your own one-time link! Connection and servers status. + 连接和服务器状态。 No comment provided by engineer. @@ -1577,6 +1644,7 @@ This is your own one-time link! Connection notifications + 连接通知 No comment provided by engineer. @@ -1596,10 +1664,12 @@ This is your own one-time link! Connection with desktop stopped + 与桌面的连接已停止 No comment provided by engineer. Connections + 连接 No comment provided by engineer. @@ -1614,6 +1684,7 @@ This is your own one-time link! Contact deleted! + 联系人已删除! No comment provided by engineer. @@ -1628,6 +1699,7 @@ This is your own one-time link! Contact is deleted. + 联系人被删除。 No comment provided by engineer. @@ -1642,6 +1714,7 @@ This is your own one-time link! Contact will be deleted - this cannot be undone! + 联系人将被删除-这是无法撤消的! No comment provided by engineer. @@ -1661,6 +1734,7 @@ This is your own one-time link! Conversation deleted! + 对话已删除! No comment provided by engineer. @@ -1670,6 +1744,7 @@ This is your own one-time link! Copy error + 复制错误 No comment provided by engineer. @@ -1683,6 +1758,7 @@ This is your own one-time link! Correct name to %@? + 将名称更正为 %@? No comment provided by engineer. @@ -1697,7 +1773,7 @@ This is your own one-time link! Create a group using a random profile. - 使用随机身份创建群组 + 使用随机身份创建群组. No comment provided by engineer. @@ -1752,6 +1828,7 @@ This is your own one-time link! Created + 已创建 No comment provided by engineer. @@ -1761,6 +1838,7 @@ This is your own one-time link! Created at: %@ + 创建于:%@ copied message info @@ -1790,6 +1868,7 @@ This is your own one-time link! Current profile + 当前配置文件 No comment provided by engineer. @@ -1804,6 +1883,7 @@ This is your own one-time link! Customize theme + 自定义主题 No comment provided by engineer. @@ -1813,6 +1893,7 @@ This is your own one-time link! Dark mode colors + 深色模式颜色 No comment provided by engineer. @@ -1915,6 +1996,7 @@ This is your own one-time link! Debug delivery + 调试交付 No comment provided by engineer. @@ -1935,10 +2017,12 @@ This is your own one-time link! Delete %lld messages of members? + 删除成员的 %lld 消息? No comment provided by engineer. Delete %lld messages? + 删除 %lld 消息? No comment provided by engineer. @@ -1998,6 +2082,7 @@ This is your own one-time link! Delete contact? + 删除联系人? No comment provided by engineer. @@ -2107,6 +2192,7 @@ This is your own one-time link! Delete up to 20 messages at once. + 一次最多删除 20 条信息。 No comment provided by engineer. @@ -2116,10 +2202,12 @@ This is your own one-time link! Delete without notification + 删除而不通知 No comment provided by engineer. Deleted + 已删除 No comment provided by engineer. @@ -2134,6 +2222,7 @@ This is your own one-time link! Deletion errors + 删除错误 No comment provided by engineer. @@ -2163,6 +2252,7 @@ This is your own one-time link! Desktop app version %@ is not compatible with this app. + 桌面应用程序版本 %@ 与此应用程序不兼容。 No comment provided by engineer. @@ -2172,22 +2262,27 @@ This is your own one-time link! Destination server address of %@ is incompatible with forwarding server %@ settings. + 目标服务器地址 %@ 与转发服务器 %@ 设置不兼容。 No comment provided by engineer. Destination server error: %@ + 目标服务器错误:%@ snd error text Destination server version of %@ is incompatible with forwarding server %@. + 目标服务器版本 %@ 与转发服务器 %@ 不兼容。 No comment provided by engineer. Detailed statistics + 详细的统计数据 No comment provided by engineer. Details + 详细信息 No comment provided by engineer. @@ -2197,6 +2292,7 @@ This is your own one-time link! Developer options + 开发者选项 No comment provided by engineer. @@ -2251,6 +2347,7 @@ This is your own one-time link! Disabled + 禁用 No comment provided by engineer. @@ -2305,6 +2402,7 @@ This is your own one-time link! Do NOT send messages directly, even if your or destination server does not support private routing. + 请勿直接发送消息,即使您的服务器或目标服务器不支持私有路由。 No comment provided by engineer. @@ -2314,6 +2412,7 @@ This is your own one-time link! Do NOT use private routing. + 不要使用私有路由。 No comment provided by engineer. @@ -2353,6 +2452,7 @@ This is your own one-time link! Download errors + 下载错误 No comment provided by engineer. @@ -2367,10 +2467,12 @@ This is your own one-time link! Downloaded + 已下载 No comment provided by engineer. Downloaded files + 下载的文件 No comment provided by engineer. @@ -2475,6 +2577,7 @@ This is your own one-time link! Enabled + 已启用 No comment provided by engineer. @@ -2514,6 +2617,7 @@ This is your own one-time link! Encrypted message: app is stopped + 加密消息:应用程序已停止 notification @@ -2563,6 +2667,7 @@ This is your own one-time link! Enter group name… + 输入组名称… No comment provided by engineer. @@ -2602,6 +2707,7 @@ This is your own one-time link! Enter your name… + 请输入您的姓名… No comment provided by engineer. @@ -2654,6 +2760,7 @@ This is your own one-time link! Error connecting to forwarding server %@. Please try later. + 连接到转发服务器 %@ 时出错。请稍后尝试。 No comment provided by engineer. @@ -2753,6 +2860,7 @@ This is your own one-time link! Error exporting theme: %@ + 导出主题时出错: %@ No comment provided by engineer. @@ -2776,6 +2884,7 @@ This is your own one-time link! Error opening chat + 打开聊天时出错 No comment provided by engineer. @@ -2785,10 +2894,12 @@ This is your own one-time link! Error reconnecting server + 重新连接服务器时出错 No comment provided by engineer. Error reconnecting servers + 重新连接服务器时出错 No comment provided by engineer. @@ -2798,6 +2909,7 @@ This is your own one-time link! Error resetting statistics + 重置统计信息时出错 No comment provided by engineer. @@ -2837,6 +2949,7 @@ This is your own one-time link! Error scanning code: %@ + 扫描代码时出错:%@ No comment provided by engineer. @@ -2936,6 +3049,7 @@ This is your own one-time link! Errors + 错误 No comment provided by engineer. @@ -2965,6 +3079,7 @@ This is your own one-time link! Export theme + 导出主题 No comment provided by engineer. @@ -3004,22 +3119,27 @@ This is your own one-time link! File error + 文件错误 No comment provided by engineer. File not found - most likely file was deleted or cancelled. + 找不到文件 - 很可能文件已被删除或取消。 file error text File server error: %@ + 文件服务器错误:%@ file error text File status + 文件状态 No comment provided by engineer. File status: %@ + 文件状态:%@ copied message info @@ -3044,6 +3164,7 @@ This is your own one-time link! Files + 文件 No comment provided by engineer. @@ -3083,7 +3204,7 @@ This is your own one-time link! Finalize migration on another device. - 在另一部设备上完成迁移 + 在另一部设备上完成迁移. No comment provided by engineer. @@ -3153,24 +3274,31 @@ This is your own one-time link! Forwarding server %@ failed to connect to destination server %@. Please try later. + 转发服务器 %@ 无法连接到目标服务器 %@。请稍后尝试。 No comment provided by engineer. Forwarding server address is incompatible with network settings: %@. + 转发服务器地址与网络设置不兼容:%@。 No comment provided by engineer. Forwarding server version is incompatible with network settings: %@. + 转发服务器版本与网络设置不兼容:%@。 No comment provided by engineer. Forwarding server: %1$@ Destination server error: %2$@ + 转发服务器: %1$@ +目标服务器错误: %2$@ snd error text Forwarding server: %1$@ Error: %2$@ + 转发服务器: %1$@ +错误: %2$@ snd error text @@ -3215,10 +3343,12 @@ Error: %2$@ Good afternoon! + 下午好! message preview Good morning! + 早上好! message preview @@ -3228,6 +3358,7 @@ Error: %2$@ Group already exists + 群组已存在 No comment provided by engineer. @@ -3282,7 +3413,7 @@ Error: %2$@ Group members can irreversibly delete sent messages. (24 hours) - 群组成员可以不可撤回地删除已发送的消息。 + 群组成员可以不可撤回地删除已发送的消息 No comment provided by engineer. @@ -3427,6 +3558,7 @@ Error: %2$@ Hungarian interface + 匈牙利语界面 No comment provided by engineer. @@ -3501,6 +3633,7 @@ Error: %2$@ Import theme + 导入主题 No comment provided by engineer. @@ -3627,6 +3760,7 @@ Error: %2$@ Interface colors + 界面颜色 No comment provided by engineer. @@ -3661,6 +3795,7 @@ Error: %2$@ Invalid response + 无效的响应 No comment provided by engineer. @@ -3731,6 +3866,7 @@ Error: %2$@ It protects your IP address and connections. + 它可以保护您的 IP 地址和连接。 No comment provided by engineer. @@ -3775,11 +3911,14 @@ Error: %2$@ Join with current profile + 使用当前档案加入 No comment provided by engineer. Join your group? This is your link for group %@! + 加入您的群组? +这是您组 %@ 的链接! No comment provided by engineer. @@ -3794,10 +3933,12 @@ This is your link for group %@! Keep conversation + 保持对话 No comment provided by engineer. Keep the app open to use it from desktop + 保持应用程序打开状态以从桌面使用它 No comment provided by engineer. @@ -3972,10 +4113,12 @@ This is your link for group %@! Media & file servers + Media & file servers No comment provided by engineer. Medium + 中等 blur media @@ -3985,6 +4128,7 @@ This is your link for group %@! Member inactive + 成员不活跃 item status text @@ -4004,6 +4148,7 @@ This is your link for group %@! Menus + 菜单 No comment provided by engineer. @@ -4018,6 +4163,7 @@ This is your link for group %@! Message delivery warning + 消息传递警告 item status text @@ -4027,14 +4173,17 @@ This is your link for group %@! Message forwarded + 消息已转发 item status text Message may be delivered later if member becomes active. + 如果 member 变为活动状态,则稍后可能会发送消息。 item status description Message queue info + 消息队列信息 No comment provided by engineer. @@ -4054,10 +4203,12 @@ This is your link for group %@! Message reception + 消息接收 No comment provided by engineer. Message servers + 消息服务器 No comment provided by engineer. @@ -4071,10 +4222,12 @@ This is your link for group %@! Message status + 消息状态 No comment provided by engineer. Message status: %@ + 消息状态:%@ copied message info @@ -4099,22 +4252,27 @@ This is your link for group %@! Messages from %@ will be shown! + 将显示来自 %@ 的消息! No comment provided by engineer. Messages received + 收到的消息 No comment provided by engineer. Messages sent + 已发送的消息 No comment provided by engineer. Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + 消息、文件和通话受到 **端到端加密** 的保护,具有完全正向保密、否认和闯入恢复。 No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + 消息、文件和通话受到 **抗量子 e2e 加密** 的保护,具有完全正向保密、否认和闯入恢复。 No comment provided by engineer. @@ -4239,6 +4397,7 @@ This is your link for group %@! Network issues - message expired after many attempts to send it. + 网络问题 - 消息在多次尝试发送后过期。 snd error text @@ -4268,6 +4427,7 @@ This is your link for group %@! New chat experience 🎉 + 新的聊天体验 🎉 No comment provided by engineer. @@ -4302,6 +4462,7 @@ This is your link for group %@! New media options + 新媒体选项 No comment provided by engineer. @@ -4351,6 +4512,7 @@ This is your link for group %@! No direct connection yet, message is forwarded by admin. + 还没有直接连接,消息由管理员转发。 item status description @@ -4370,6 +4532,7 @@ This is your link for group %@! No info, try to reload + 无信息,尝试重新加载 No comment provided by engineer. @@ -4394,6 +4557,7 @@ This is your link for group %@! Nothing selected + 未选中任何内容 No comment provided by engineer. @@ -4448,13 +4612,15 @@ This is your link for group %@! Onion hosts will be **required** for connection. Requires compatible VPN. - Onion 主机将用于连接。需要启用 VPN。 + Onion 主机将是连接所必需的。 +需要兼容的 VPN。 No comment provided by engineer. Onion hosts will be used when available. Requires compatible VPN. - 当可用时,将使用 Onion 主机。需要启用 VPN。 + 如果可用,将使用洋葱主机。 +需要兼容的 VPN。 No comment provided by engineer. @@ -4469,6 +4635,7 @@ Requires compatible VPN. Only delete conversation + 仅删除对话 No comment provided by engineer. @@ -4493,7 +4660,7 @@ Requires compatible VPN. Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) - 只有您可以不可撤回地删除消息(您的联系人可以将它们标记为删除)。 + 只有您可以不可撤回地删除消息(您的联系人可以将它们标记为删除) No comment provided by engineer. @@ -4518,7 +4685,7 @@ Requires compatible VPN. Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours) - 只有您的联系人才能不可撤回地删除消息(您可以将它们标记为删除)。 + 只有您的联系人才能不可撤回地删除消息(您可以将它们标记为删除) No comment provided by engineer. @@ -4563,10 +4730,12 @@ Requires compatible VPN. Open migration to another device + 打开迁移到另一台设备 authentication reason Open server settings + 打开服务器设置 No comment provided by engineer. @@ -4581,6 +4750,7 @@ Requires compatible VPN. Opening app… + 正在打开应用程序… No comment provided by engineer. @@ -4610,6 +4780,7 @@ Requires compatible VPN. Other %@ servers + 其他 %@ 服务器 No comment provided by engineer. @@ -4654,6 +4825,7 @@ Requires compatible VPN. Past member %@ + 前任成员 %@ past/unknown group member @@ -4678,6 +4850,7 @@ Requires compatible VPN. Pending + 待定 No comment provided by engineer. @@ -4702,10 +4875,12 @@ Requires compatible VPN. Play from the chat list. + 从聊天列表播放。 No comment provided by engineer. Please ask your contact to enable calls. + 请要求您的联系人开通通话功能。 No comment provided by engineer. @@ -4716,6 +4891,8 @@ Requires compatible VPN. Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + 请检查移动设备和桌面是否连接到同一本地网络,以及桌面防火墙是否允许连接。 +请与开发人员分享任何其他问题。 No comment provided by engineer. @@ -4741,6 +4918,8 @@ Please share any other issues with the developers. Please contact developers. Error: %@ + 请联系开发人员。 +错误:%@ No comment provided by engineer. @@ -4815,6 +4994,7 @@ Error: %@ Previously connected servers + 以前连接的服务器 No comment provided by engineer. @@ -4834,10 +5014,12 @@ Error: %@ Private message routing + 私有消息路由 No comment provided by engineer. Private message routing 🚀 + 私有消息路由 🚀 No comment provided by engineer. @@ -4847,10 +5029,12 @@ Error: %@ Private routing + 专用路由 No comment provided by engineer. Private routing error + 专用路由错误 No comment provided by engineer. @@ -4875,6 +5059,7 @@ Error: %@ Profile theme + 个人资料主题 No comment provided by engineer. @@ -4904,6 +5089,7 @@ Error: %@ Prohibit sending SimpleX links. + 禁止发送 SimpleX 链接。 No comment provided by engineer. @@ -4928,6 +5114,7 @@ Error: %@ Protect IP address + 保护 IP 地址 No comment provided by engineer. @@ -4938,6 +5125,8 @@ Error: %@ Protect your IP address from the messaging relays chosen by your contacts. Enable in *Network & servers* settings. + 保护您的 IP 地址免受联系人选择的消息中继的攻击。 +在*网络和服务器*设置中启用。 No comment provided by engineer. @@ -4957,10 +5146,12 @@ Enable in *Network & servers* settings. Proxied + 代理 No comment provided by engineer. Proxied servers + 代理服务器 No comment provided by engineer. @@ -4970,6 +5161,7 @@ Enable in *Network & servers* settings. Push server + 推送服务器 No comment provided by engineer. @@ -4984,6 +5176,7 @@ Enable in *Network & servers* settings. Reachable chat toolbar + 可访问的聊天工具栏 No comment provided by engineer. @@ -5033,6 +5226,7 @@ Enable in *Network & servers* settings. Receive errors + 接收错误 No comment provided by engineer. @@ -5057,14 +5251,17 @@ Enable in *Network & servers* settings. Received messages + 收到的消息 No comment provided by engineer. Received reply + 已收到回复 No comment provided by engineer. Received total + 接收总数 No comment provided by engineer. @@ -5084,6 +5281,7 @@ Enable in *Network & servers* settings. Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). + 最近的历史记录和改进的 [目录机器人](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). No comment provided by engineer. @@ -5098,6 +5296,7 @@ Enable in *Network & servers* settings. Reconnect + 重新连接 No comment provided by engineer. @@ -5107,18 +5306,22 @@ Enable in *Network & servers* settings. Reconnect all servers + 重新连接所有服务器 No comment provided by engineer. Reconnect all servers? + 重新连接所有服务器? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + 重新连接服务器以强制发送信息。它使用额外的流量。 No comment provided by engineer. Reconnect server? + 重新连接服务器? No comment provided by engineer. @@ -5178,6 +5381,7 @@ Enable in *Network & servers* settings. Remove image + 移除图片 No comment provided by engineer. @@ -5252,14 +5456,17 @@ Enable in *Network & servers* settings. Reset all hints + 重置所有提示 No comment provided by engineer. Reset all statistics + 重置所有统计信息 No comment provided by engineer. Reset all statistics? + 重置所有统计信息? No comment provided by engineer. @@ -5269,6 +5476,7 @@ Enable in *Network & servers* settings. Reset to app theme + 重置为应用程序主题 No comment provided by engineer. @@ -5278,6 +5486,7 @@ Enable in *Network & servers* settings. Reset to user theme + 重置为用户主题 No comment provided by engineer. @@ -5347,10 +5556,12 @@ Enable in *Network & servers* settings. SMP server + SMP 服务器 No comment provided by engineer. Safely receive files + 安全接收文件 No comment provided by engineer. @@ -5381,6 +5592,7 @@ Enable in *Network & servers* settings. Save and reconnect + 保存并重新连接 No comment provided by engineer. @@ -5459,10 +5671,12 @@ Enable in *Network & servers* settings. Scale + 规模 No comment provided by engineer. Scan / Paste link + 扫描 / 粘贴链接 No comment provided by engineer. @@ -5507,6 +5721,7 @@ Enable in *Network & servers* settings. Secondary + 二级 No comment provided by engineer. @@ -5516,6 +5731,7 @@ Enable in *Network & servers* settings. Secured + 担保 No comment provided by engineer. @@ -5539,10 +5755,12 @@ Enable in *Network & servers* settings. Selected %lld + 选定的 %lld No comment provided by engineer. Selected chat preferences prohibit this message. + 选定的聊天首选项禁止此消息。 No comment provided by engineer. @@ -5592,6 +5810,7 @@ Enable in *Network & servers* settings. Send errors + 发送错误 No comment provided by engineer. @@ -5606,14 +5825,17 @@ Enable in *Network & servers* settings. Send message to enable calls. + 发送消息以启用呼叫。 No comment provided by engineer. Send messages directly when IP address is protected and your or destination server does not support private routing. + 当 IP 地址受到保护并且您或目标服务器不支持私有路由时,直接发送消息。 No comment provided by engineer. Send messages directly when your or destination server does not support private routing. + 当您或目标服务器不支持私有路由时,直接发送消息。 No comment provided by engineer. @@ -5708,6 +5930,7 @@ Enable in *Network & servers* settings. Sent directly + 直接发送 No comment provided by engineer. @@ -5722,6 +5945,7 @@ Enable in *Network & servers* settings. Sent messages + 已发送的消息 No comment provided by engineer. @@ -5731,26 +5955,32 @@ Enable in *Network & servers* settings. Sent reply + 已发送回复 No comment provided by engineer. Sent total + 发送总数 No comment provided by engineer. Sent via proxy + 通过代理发送 No comment provided by engineer. Server address + 服务器地址 No comment provided by engineer. Server address is incompatible with network settings. + 服务器地址与网络设置不兼容。 srv error text. Server address is incompatible with network settings: %@. + 服务器地址与网络设置不兼容:%@。 No comment provided by engineer. @@ -5770,14 +6000,17 @@ Enable in *Network & servers* settings. Server type + 服务器类型 No comment provided by engineer. Server version is incompatible with network settings. + 服务器版本与网络设置不兼容。 srv error text Server version is incompatible with your app: %@. + 服务器版本与你的应用程序不兼容:%@。 No comment provided by engineer. @@ -5787,10 +6020,12 @@ Enable in *Network & servers* settings. Servers info + 服务器信息 No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + 服务器统计信息将被重置 - 此操作无法撤消! No comment provided by engineer. @@ -5810,6 +6045,7 @@ Enable in *Network & servers* settings. Set default theme + 设置默认主题 No comment provided by engineer. @@ -5883,6 +6119,7 @@ Enable in *Network & servers* settings. Share from other apps. + 从其他应用程序共享。 No comment provided by engineer. @@ -5901,6 +6138,7 @@ Enable in *Network & servers* settings. Share to SimpleX + 分享到 SimpleX No comment provided by engineer. @@ -5930,10 +6168,12 @@ Enable in *Network & servers* settings. Show message status + 显示消息状态 No comment provided by engineer. Show percentage + 显示百分比 No comment provided by engineer. @@ -5943,6 +6183,7 @@ Enable in *Network & servers* settings. Show → on messages sent via private routing. + 显示 → 通过专用路由发送的信息. No comment provided by engineer. @@ -5952,6 +6193,7 @@ Enable in *Network & servers* settings. SimpleX + SimpleX No comment provided by engineer. @@ -6031,6 +6273,7 @@ Enable in *Network & servers* settings. Size + 大小 No comment provided by engineer. @@ -6050,6 +6293,7 @@ Enable in *Network & servers* settings. Soft + blur media @@ -6058,6 +6302,7 @@ Enable in *Network & servers* settings. Some file(s) were not exported: + 某些文件未导出: No comment provided by engineer. @@ -6067,6 +6312,7 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: + 导入过程中出现一些非致命错误: No comment provided by engineer. @@ -6076,7 +6322,7 @@ Enable in *Network & servers* settings. Square, circle, or anything in between. - 方形、圆形、或两者之间的任意形状 + 方形、圆形、或两者之间的任意形状. No comment provided by engineer. @@ -6096,10 +6342,12 @@ Enable in *Network & servers* settings. Starting from %@. + 从 %@ 开始。 No comment provided by engineer. Statistics + 统计 No comment provided by engineer. @@ -6164,6 +6412,7 @@ Enable in *Network & servers* settings. Strong + 加粗 blur media @@ -6173,14 +6422,17 @@ Enable in *Network & servers* settings. Subscribed + 已订阅 No comment provided by engineer. Subscription errors + 订阅错误 No comment provided by engineer. Subscriptions ignored + 忽略订阅 No comment provided by engineer. @@ -6200,6 +6452,7 @@ Enable in *Network & servers* settings. TCP connection + TCP 连接 No comment provided by engineer. @@ -6268,6 +6521,7 @@ Enable in *Network & servers* settings. Temporary file error + 临时文件错误 No comment provided by engineer. @@ -6324,6 +6578,7 @@ It can happen because of some bug or when the connection is compromised. The app will ask to confirm downloads from unknown file servers (except .onion). + 该应用程序将要求确认从未知文件服务器(.onion 除外)下载。 No comment provided by engineer. @@ -6373,10 +6628,12 @@ It can happen because of some bug or when the connection is compromised. The messages will be deleted for all members. + 将删除所有成员的消息。 No comment provided by engineer. The messages will be marked as moderated for all members. + 对于所有成员,这些消息将被标记为已审核。 No comment provided by engineer. @@ -6420,6 +6677,7 @@ It can happen because of some bug or when the connection is compromised. Themes + 主题 No comment provided by engineer. @@ -6489,6 +6747,7 @@ It can happen because of some bug or when the connection is compromised. This link was used with another mobile device, please create a new link on the desktop. + 此链接已在其他移动设备上使用,请在桌面上创建新链接。 No comment provided by engineer. @@ -6498,6 +6757,7 @@ It can happen because of some bug or when the connection is compromised. Title + 标题 No comment provided by engineer. @@ -6532,6 +6792,7 @@ It can happen because of some bug or when the connection is compromised. To protect your IP address, private routing uses your SMP servers to deliver messages. + 为了保护您的 IP 地址,私有路由使用您的 SMP 服务器来传递邮件。 No comment provided by engineer. @@ -6563,6 +6824,7 @@ You will be prompted to complete authentication before this feature is enabled.< Toggle chat list: + 切换聊天列表: No comment provided by engineer. @@ -6572,10 +6834,12 @@ You will be prompted to complete authentication before this feature is enabled.< Toolbar opacity + 工具栏不透明度 No comment provided by engineer. Total + 共计 No comment provided by engineer. @@ -6585,6 +6849,7 @@ You will be prompted to complete authentication before this feature is enabled.< Transport sessions + 传输会话 No comment provided by engineer. @@ -6599,6 +6864,7 @@ You will be prompted to complete authentication before this feature is enabled.< Turkish interface + 土耳其语界面 No comment provided by engineer. @@ -6688,6 +6954,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! + 未知服务器! No comment provided by engineer. @@ -6754,6 +7021,7 @@ To connect, please ask your contact to create another connection link and check Update settings? + 更新设置? No comment provided by engineer. @@ -6768,6 +7036,7 @@ To connect, please ask your contact to create another connection link and check Upload errors + 上传错误 No comment provided by engineer. @@ -6782,10 +7051,12 @@ To connect, please ask your contact to create another connection link and check Uploaded + 已上传 No comment provided by engineer. Uploaded files + 已上传的文件 No comment provided by engineer. @@ -6835,14 +7106,17 @@ To connect, please ask your contact to create another connection link and check Use only local notifications? + 仅使用本地通知? No comment provided by engineer. Use private routing with unknown servers when IP address is not protected. + 当 IP 地址不受保护时,对未知服务器使用私有路由。 No comment provided by engineer. Use private routing with unknown servers. + 对未知服务器使用私有路由。 No comment provided by engineer. @@ -6852,11 +7126,12 @@ To connect, please ask your contact to create another connection link and check Use the app while in the call. - 通话时使用本应用 + 通话时使用本应用. No comment provided by engineer. Use the app with one hand. + 用一只手使用应用程序。 No comment provided by engineer. @@ -6866,6 +7141,7 @@ To connect, please ask your contact to create another connection link and check User selection + 用户选择 No comment provided by engineer. @@ -6980,6 +7256,7 @@ To connect, please ask your contact to create another connection link and check Waiting for desktop... + 正在等待桌面... No comment provided by engineer. @@ -6999,15 +7276,17 @@ To connect, please ask your contact to create another connection link and check Wallpaper accent + 壁纸装饰 No comment provided by engineer. Wallpaper background + 壁纸背景 No comment provided by engineer. Warning: starting chat on multiple devices is not supported and will cause message delivery failures - 警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败。 + 警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败 No comment provided by engineer. @@ -7092,10 +7371,12 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to file servers. + 如果没有 Tor 或 VPN,您的 IP 地址将对文件服务器可见。 No comment provided by engineer. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + 如果没有 Tor 或 VPN,您的 IP 地址将对以下 XFTP 中继可见:%@。 No comment provided by engineer. @@ -7105,10 +7386,12 @@ To connect, please ask your contact to create another connection link and check Wrong key or unknown connection - most likely this connection is deleted. + 密钥错误或连接未知 - 很可能此连接已被删除。 snd error text Wrong key or unknown file chunk address - most likely file is deleted. + 密钥错误或文件块地址未知 - 很可能文件已删除。 file error text @@ -7118,6 +7401,7 @@ To connect, please ask your contact to create another connection link and check XFTP server + XFTP 服务器 No comment provided by engineer. @@ -7127,6 +7411,7 @@ To connect, please ask your contact to create another connection link and check You **must not** use the same database on two devices. + 您 **不得** 在两台设备上使用相同的数据库。 No comment provided by engineer. @@ -7151,6 +7436,7 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. + 您已连接到 %@。 No comment provided by engineer. @@ -7160,14 +7446,17 @@ To connect, please ask your contact to create another connection link and check You are already in group %@. + 您已在组 %@ 中。 No comment provided by engineer. You are already joining the group %@. + 您已加入组 %@。 No comment provided by engineer. You are already joining the group via this link! + 您已经通过此链接加入群组! No comment provided by engineer. @@ -7178,6 +7467,8 @@ To connect, please ask your contact to create another connection link and check You are already joining the group! Repeat join request? + 您已经加入了这个群组! +重复加入请求? No comment provided by engineer. @@ -7192,6 +7483,7 @@ Repeat join request? You are not connected to these servers. Private routing is used to deliver messages to them. + 您未连接到这些服务器。私有路由用于向他们发送消息。 No comment provided by engineer. @@ -7201,6 +7493,7 @@ Repeat join request? You can change it in Appearance settings. + 您可以在外观设置中更改它。 No comment provided by engineer. @@ -7240,6 +7533,7 @@ Repeat join request? You can send messages to %@ from Archived contacts. + 您可以从存档的联系人向%@发送消息。 No comment provided by engineer. @@ -7269,6 +7563,7 @@ Repeat join request? You can still view conversation with %@ in the list of chats. + 您仍然可以在聊天列表中查看与 %@的对话。 No comment provided by engineer. @@ -7309,6 +7604,8 @@ Repeat join request? You have already requested connection! Repeat connection request? + 您已经请求连接了! +重复连接请求? No comment provided by engineer. @@ -7333,10 +7630,12 @@ Repeat connection request? You may migrate the exported database. + 您可以迁移导出的数据库。 No comment provided by engineer. You may save the exported archive. + 您可以保存导出的档案。 No comment provided by engineer. @@ -7346,6 +7645,7 @@ Repeat connection request? You need to allow your contact to call to be able to call them. + 您需要允许您的联系人呼叫才能呼叫他们。 No comment provided by engineer. @@ -7370,6 +7670,7 @@ Repeat connection request? You will be connected when group link host's device is online, please wait or check later! + 当 Group Link Host 的设备在线时,您将被连接,请稍候或稍后检查! No comment provided by engineer. @@ -7507,6 +7808,7 @@ Repeat connection request? Your profile + 您的个人资料 No comment provided by engineer. @@ -7615,10 +7917,12 @@ Repeat connection request? and %lld other events + 和 %lld 其他事件 No comment provided by engineer. attempts + 尝试 No comment provided by engineer. @@ -7663,6 +7967,7 @@ Repeat connection request? call + 呼叫 No comment provided by engineer. @@ -7782,6 +8087,7 @@ Repeat connection request? contact %1$@ changed to %2$@ + 联系人 %1$@ 已更改为 %2$@ profile update event chat item @@ -7816,6 +8122,7 @@ Repeat connection request? decryption errors + 解密错误 No comment provided by engineer. @@ -7870,6 +8177,7 @@ Repeat connection request? duplicates + 复本 No comment provided by engineer. @@ -7954,6 +8262,7 @@ Repeat connection request? expired + 过期 No comment provided by engineer. @@ -7988,6 +8297,7 @@ Repeat connection request? inactive + 无效 No comment provided by engineer. @@ -8032,6 +8342,7 @@ Repeat connection request? invite + 邀请 No comment provided by engineer. @@ -8081,6 +8392,7 @@ Repeat connection request? member %1$@ changed to %2$@ + 成员 %1$@ 已更改为 %2$@ profile update event chat item @@ -8090,6 +8402,7 @@ Repeat connection request? message + 消息 No comment provided by engineer. @@ -8124,6 +8437,7 @@ Repeat connection request? mute + 静音 No comment provided by engineer. @@ -8180,10 +8494,12 @@ Repeat connection request? other + 其他 No comment provided by engineer. other errors + 其他错误 No comment provided by engineer. @@ -8253,10 +8569,12 @@ Repeat connection request? saved from %@ + 保存自 %@ No comment provided by engineer. search + 搜索 No comment provided by engineer. @@ -8288,6 +8606,9 @@ Repeat connection request? server queue info: %1$@ last received msg: %2$@ + 服务器队列信息: %1$@ + +上次收到的消息: %2$@ queue info @@ -8322,6 +8643,7 @@ last received msg: %2$@ unblocked %@ + 未阻止 %@ rcv group event chat item @@ -8331,6 +8653,7 @@ last received msg: %2$@ unknown servers + 未知服务器 No comment provided by engineer. @@ -8340,10 +8663,12 @@ last received msg: %2$@ unmute + 取消静音 No comment provided by engineer. unprotected + 未受保护 No comment provided by engineer. @@ -8358,6 +8683,7 @@ last received msg: %2$@ v%@ + v%@ No comment provided by engineer. @@ -8387,6 +8713,7 @@ last received msg: %2$@ video + 视频 No comment provided by engineer. @@ -8416,6 +8743,7 @@ last received msg: %2$@ when IP hidden + 当 IP 隐藏时 No comment provided by engineer. @@ -8440,6 +8768,7 @@ last received msg: %2$@ you blocked %@ + 你阻止了%@ snd group event chat item @@ -8484,6 +8813,7 @@ last received msg: %2$@ you unblocked %@ + 您解封了 %@ snd group event chat item @@ -8520,6 +8850,7 @@ last received msg: %2$@ SimpleX uses local network access to allow using user chat profile via desktop app on the same network. + SimpleX 使用本地网络访问,允许通过同一网络上的桌面应用程序使用用户聊天配置文件。 Privacy - Local Network Usage Description @@ -8563,14 +8894,17 @@ last received msg: %2$@ SimpleX SE + SimpleX SE Bundle display name SimpleX SE + SimpleX SE Bundle name Copyright © 2024 SimpleX Chat. All rights reserved. + 版权所有 © 2024 SimpleX Chat。保留所有权利。 Copyright (human-readable) @@ -8582,150 +8916,187 @@ last received msg: %2$@ %@ + %@ No comment provided by engineer. App is locked! + 应用程序已锁定! No comment provided by engineer. Cancel + 取消 No comment provided by engineer. Cannot access keychain to save database password + 无法访问钥匙串以保存数据库密码 No comment provided by engineer. Cannot forward message + 无法转发消息 No comment provided by engineer. Comment + 评论 No comment provided by engineer. Currently maximum supported file size is %@. + 当前支持的最大文件大小为 %@。 No comment provided by engineer. Database downgrade required + 需要数据库降级 No comment provided by engineer. Database encrypted! + 数据库已加密! No comment provided by engineer. Database error + 数据库错误 No comment provided by engineer. Database passphrase is different from saved in the keychain. + 数据库密码与保存在钥匙串中的密码不同。 No comment provided by engineer. Database passphrase is required to open chat. + 需要数据库密码才能打开聊天。 No comment provided by engineer. Database upgrade required + 需要升级数据库 No comment provided by engineer. Error preparing file + 准备文件时出错 No comment provided by engineer. Error preparing message + 准备消息时出错 No comment provided by engineer. Error: %@ + 错误:%@ No comment provided by engineer. File error + 文件错误 No comment provided by engineer. Incompatible database version + 不兼容的数据库版本 No comment provided by engineer. Invalid migration confirmation + 无效的迁移确认 No comment provided by engineer. Keychain error + 钥匙串错误 No comment provided by engineer. Large file! + 大文件! No comment provided by engineer. No active profile + 无活动配置文件 No comment provided by engineer. Ok + 好的 No comment provided by engineer. Open the app to downgrade the database. + 打开应用程序以降级数据库。 No comment provided by engineer. Open the app to upgrade the database. + 打开应用程序以升级数据库。 No comment provided by engineer. Passphrase + 密码 No comment provided by engineer. Please create a profile in the SimpleX app + 请在 SimpleX 应用程序中创建配置文件 No comment provided by engineer. Selected chat preferences prohibit this message. + 选定的聊天首选项禁止此消息。 No comment provided by engineer. Sending a message takes longer than expected. + 发送消息所需的时间比预期的要长。 No comment provided by engineer. Sending message… + 正在发送消息… No comment provided by engineer. Share + 共享 No comment provided by engineer. Slow network? + 网络速度慢? No comment provided by engineer. Unknown database error: %@ + 未知数据库错误: %@ No comment provided by engineer. Unsupported format + 不支持的格式 No comment provided by engineer. Wait + 等待 No comment provided by engineer. Wrong database passphrase + 数据库密码错误 No comment provided by engineer. You can allow sharing in Privacy & Security / SimpleX Lock settings. + 您可以在 "隐私与安全"/"SimpleX Lock "设置中允许共享。 No comment provided by engineer. diff --git a/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings b/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings index 388ac01f7f..760be62885 100644 --- a/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ -/* - InfoPlist.strings - SimpleX +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX SE"; + +/* Bundle name */ +"CFBundleName" = "SimpleX SE"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "版权所有 © 2024 SimpleX Chat。保留所有权利。"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings b/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings index 5ef592ec70..362e2edb74 100644 --- a/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings @@ -1,7 +1,111 @@ -/* - Localizable.strings - SimpleX +/* No comment provided by engineer. */ +"%@" = "%@"; + +/* No comment provided by engineer. */ +"App is locked!" = "应用程序已锁定!"; + +/* No comment provided by engineer. */ +"Cancel" = "取消"; + +/* No comment provided by engineer. */ +"Cannot access keychain to save database password" = "无法访问钥匙串以保存数据库密码"; + +/* No comment provided by engineer. */ +"Cannot forward message" = "无法转发消息"; + +/* No comment provided by engineer. */ +"Comment" = "评论"; + +/* No comment provided by engineer. */ +"Currently maximum supported file size is %@." = "当前支持的最大文件大小为 %@。"; + +/* No comment provided by engineer. */ +"Database downgrade required" = "需要数据库降级"; + +/* No comment provided by engineer. */ +"Database encrypted!" = "数据库已加密!"; + +/* No comment provided by engineer. */ +"Database error" = "数据库错误"; + +/* No comment provided by engineer. */ +"Database passphrase is different from saved in the keychain." = "数据库密码与保存在钥匙串中的密码不同。"; + +/* No comment provided by engineer. */ +"Database passphrase is required to open chat." = "需要数据库密码才能打开聊天。"; + +/* No comment provided by engineer. */ +"Database upgrade required" = "需要升级数据库"; + +/* No comment provided by engineer. */ +"Error preparing file" = "准备文件时出错"; + +/* No comment provided by engineer. */ +"Error preparing message" = "准备消息时出错"; + +/* No comment provided by engineer. */ +"Error: %@" = "错误:%@"; + +/* No comment provided by engineer. */ +"File error" = "文件错误"; + +/* No comment provided by engineer. */ +"Incompatible database version" = "不兼容的数据库版本"; + +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "无效的迁移确认"; + +/* No comment provided by engineer. */ +"Keychain error" = "钥匙串错误"; + +/* No comment provided by engineer. */ +"Large file!" = "大文件!"; + +/* No comment provided by engineer. */ +"No active profile" = "无活动配置文件"; + +/* No comment provided by engineer. */ +"Ok" = "好的"; + +/* No comment provided by engineer. */ +"Open the app to downgrade the database." = "打开应用程序以降级数据库。"; + +/* No comment provided by engineer. */ +"Open the app to upgrade the database." = "打开应用程序以升级数据库。"; + +/* No comment provided by engineer. */ +"Passphrase" = "密码"; + +/* No comment provided by engineer. */ +"Please create a profile in the SimpleX app" = "请在 SimpleX 应用程序中创建配置文件"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "选定的聊天首选项禁止此消息。"; + +/* No comment provided by engineer. */ +"Sending a message takes longer than expected." = "发送消息所需的时间比预期的要长。"; + +/* No comment provided by engineer. */ +"Sending message…" = "正在发送消息…"; + +/* No comment provided by engineer. */ +"Share" = "共享"; + +/* No comment provided by engineer. */ +"Slow network?" = "网络速度慢?"; + +/* No comment provided by engineer. */ +"Unknown database error: %@" = "未知数据库错误: %@"; + +/* No comment provided by engineer. */ +"Unsupported format" = "不支持的格式"; + +/* No comment provided by engineer. */ +"Wait" = "等待"; + +/* No comment provided by engineer. */ +"Wrong database passphrase" = "数据库密码错误"; + +/* No comment provided by engineer. */ +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "您可以在 \"隐私与安全\"/\"SimpleX Lock \"设置中允许共享。"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index aa43902c81..b044c8bc1e 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -697,7 +697,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Не може да поканят контактите!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Отказ"; /* No comment provided by engineer. */ @@ -1897,9 +1897,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Пълно име (незадължително)"; -/* No comment provided by engineer. */ -"Full name:" = "Пълно име:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Напълно децентрализирана – видима е само за членовете."; @@ -2940,12 +2937,6 @@ /* No comment provided by engineer. */ "Profile images" = "Профилни изображения"; -/* No comment provided by engineer. */ -"Profile name" = "Име на профила"; - -/* No comment provided by engineer. */ -"Profile name:" = "Име на профила:"; - /* No comment provided by engineer. */ "Profile password" = "Профилна парола"; @@ -3211,10 +3202,11 @@ /* No comment provided by engineer. */ "Safer groups" = "По-безопасни групи"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Запази"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Запази (и уведоми контактите)"; /* No comment provided by engineer. */ @@ -3229,9 +3221,6 @@ /* No comment provided by engineer. */ "Save archive" = "Запази архив"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Запази настройките за автоматично приемане"; - /* No comment provided by engineer. */ "Save group profile" = "Запази профила на групата"; @@ -3253,9 +3242,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Запази сървърите?"; -/* No comment provided by engineer. */ -"Save settings?" = "Запази настройките?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Запази съобщението при посрещане?"; @@ -4430,7 +4416,7 @@ "Your profile **%@** will be shared." = "Вашият профил **%@** ще бъде споделен."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти.\nSimpleX сървърите не могат да видят вашия профил."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство."; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 220550c682..f46da2d120 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -562,7 +562,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Nelze pozvat kontakty!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Zrušit"; /* feature offered item */ @@ -1543,9 +1543,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Celé jméno (volitelně)"; -/* No comment provided by engineer. */ -"Full name:" = "Celé jméno:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Plně přepracováno, prácuje na pozadí!"; @@ -2602,10 +2599,11 @@ /* No comment provided by engineer. */ "Run chat" = "Spustit chat"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Uložit"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Uložit (a informovat kontakty)"; /* No comment provided by engineer. */ @@ -2620,9 +2618,6 @@ /* No comment provided by engineer. */ "Save archive" = "Uložit archiv"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Uložit nastavení automatického přijímání"; - /* No comment provided by engineer. */ "Save group profile" = "Uložení profilu skupiny"; @@ -2644,9 +2639,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Uložit servery?"; -/* No comment provided by engineer. */ -"Save settings?" = "Uložit nastavení?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Uložit uvítací zprávu?"; @@ -3554,7 +3546,7 @@ "Your profile **%@** will be shared." = "Váš profil **%@** bude sdílen."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty.\nServery SimpleX nevidí váš profil."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index bab14eda6b..b69b9fa370 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Mitglied kann nicht benachrichtigt werden"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Abbrechen"; /* No comment provided by engineer. */ @@ -921,16 +921,16 @@ "Chunks uploaded" = "Daten-Pakete hochgeladen"; /* swipe action */ -"Clear" = "Löschen"; +"Clear" = "Entfernen"; /* No comment provided by engineer. */ -"Clear conversation" = "Chatinhalte löschen"; +"Clear conversation" = "Chat-Inhalte entfernen"; /* No comment provided by engineer. */ -"Clear conversation?" = "Unterhaltung löschen?"; +"Clear conversation?" = "Chat-Inhalte entfernen?"; /* No comment provided by engineer. */ -"Clear private notes?" = "Private Notizen löschen?"; +"Clear private notes?" = "Private Notizen entfernen?"; /* No comment provided by engineer. */ "Clear verification" = "Überprüfung zurücknehmen"; @@ -1167,7 +1167,7 @@ "Continue" = "Weiter"; /* No comment provided by engineer. */ -"Conversation deleted!" = "Unterhaltung gelöscht!"; +"Conversation deleted!" = "Chat-Inhalte entfernt!"; /* No comment provided by engineer. */ "Copy" = "Kopieren"; @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Vollständiger Name (optional)"; -/* No comment provided by engineer. */ -"Full name:" = "Vollständiger Name:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Vollständig dezentralisiert – nur für Mitglieder sichtbar."; @@ -2306,7 +2303,7 @@ "Group will be deleted for all members - this cannot be undone!" = "Die Gruppe wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ -"Group will be deleted for you - this cannot be undone!" = "Die Gruppe wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; +"Group will be deleted for you - this cannot be undone!" = "Die Gruppe wird nur bei Ihnen gelöscht. Dies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ "Help" = "Hilfe"; @@ -2630,7 +2627,7 @@ "Keep" = "Behalten"; /* No comment provided by engineer. */ -"Keep conversation" = "Unterhaltung behalten"; +"Keep conversation" = "Chat-Inhalte beibehalten"; /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Die App muss geöffnet bleiben, um sie vom Desktop aus nutzen zu können"; @@ -3115,7 +3112,7 @@ "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden."; /* No comment provided by engineer. */ -"Only delete conversation" = "Nur die Unterhaltung löschen"; +"Only delete conversation" = "Nur die Chat-Inhalte löschen"; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Gruppen-Präferenzen können nur von Gruppen-Eigentümern geändert werden."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Profil-Bilder"; -/* No comment provided by engineer. */ -"Profile name" = "Profilname"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profilname:"; - /* No comment provided by engineer. */ "Profile password" = "Passwort für Profil"; @@ -3460,7 +3451,7 @@ "Rate the app" = "Bewerten Sie die App"; /* No comment provided by engineer. */ -"Reachable chat toolbar" = "Erreichbare Chat-Symbolleiste"; +"Reachable chat toolbar" = "Chat-Symbolleiste unten"; /* chat item menu */ "React…" = "Reagiere…"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Sicherere Gruppen"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Speichern"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Speichern (und Kontakte benachrichtigen)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Archiv speichern"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Einstellungen von \"Automatisch akzeptieren\" speichern"; - /* No comment provided by engineer. */ "Save group profile" = "Gruppenprofil speichern"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Alle Server speichern?"; -/* No comment provided by engineer. */ -"Save settings?" = "Einstellungen speichern?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Begrüßungsmeldung speichern?"; @@ -3815,7 +3801,7 @@ "Search bar accepts invitation links." = "In der Suchleiste werden nun auch Einladungslinks akzeptiert."; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Suchen oder fügen Sie den SimpleX-Link ein"; +"Search or paste SimpleX link" = "Suchen oder SimpleX-Link einfügen"; /* network option */ "sec" = "sek"; @@ -4385,7 +4371,7 @@ "The message will be marked as moderated for all members." = "Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet."; /* No comment provided by engineer. */ -"The messages will be deleted for all members." = "Die Nachrichten werden für alle Mitglieder gelöscht werden."; +"The messages will be deleted for all members." = "Die Nachrichten werden für alle Gruppenmitglieder gelöscht."; /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden."; @@ -4709,7 +4695,7 @@ "Use the app while in the call." = "Die App kann während eines Anrufs genutzt werden."; /* No comment provided by engineer. */ -"Use the app with one hand." = "Die App mit einer Hand nutzen."; +"Use the app with one hand." = "Die App mit einer Hand bedienen."; /* No comment provided by engineer. */ "User profile" = "Benutzerprofil"; @@ -5021,7 +5007,7 @@ "You can start chat via app Settings / Database or by restarting the app" = "Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten"; /* No comment provided by engineer. */ -"You can still view conversation with %@ in the list of chats." = "Sie können in der Chatliste weiterhin die Unterhaltung mit %@ einsehen."; +"You can still view conversation with %@ in the list of chats." = "Sie können in der Chat-Liste weiterhin die Unterhaltung mit %@ einsehen."; /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "Sie können die SimpleX-Sperre über die Einstellungen aktivieren."; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.\nSimpleX-Server können Ihr Profil nicht einsehen."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 3dce4e4474..ed5efe14ab 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "No se pueden enviar mensajes al miembro"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Cancelar"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Nombre completo (opcional)"; -/* No comment provided by engineer. */ -"Full name:" = "Nombre completo:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Completamente descentralizado y sólo visible para los miembros."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Forma de los perfiles"; -/* No comment provided by engineer. */ -"Profile name" = "Nombre del perfil"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nombre del perfil:"; - /* No comment provided by engineer. */ "Profile password" = "Contraseña del perfil"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Grupos más seguros"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Guardar"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Guardar (y notificar contactos)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Guardar archivo"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Guardar configuración de auto aceptar"; - /* No comment provided by engineer. */ "Save group profile" = "Guardar perfil de grupo"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "¿Guardar servidores?"; -/* No comment provided by engineer. */ -"Save settings?" = "¿Guardar configuración?"; - /* No comment provided by engineer. */ "Save welcome message?" = "¿Guardar mensaje de bienvenida?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "El perfil **%@** será compartido."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos.\nLos servidores SimpleX no pueden ver tu perfil."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes se almacenan en tu dispositivo."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index a7820e69b1..ee7056709e 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -547,7 +547,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Kontakteja ei voi kutsua!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Peruuta"; /* feature offered item */ @@ -1519,9 +1519,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Koko nimi (valinnainen)"; -/* No comment provided by engineer. */ -"Full name:" = "Koko nimi:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Täysin uudistettu - toimii taustalla!"; @@ -2572,10 +2569,11 @@ /* No comment provided by engineer. */ "Run chat" = "Käynnistä chat"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Tallenna"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Tallenna (ja ilmoita kontakteille)"; /* No comment provided by engineer. */ @@ -2590,9 +2588,6 @@ /* No comment provided by engineer. */ "Save archive" = "Tallenna arkisto"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Tallenna automaattisen hyväksynnän asetukset"; - /* No comment provided by engineer. */ "Save group profile" = "Tallenna ryhmäprofiili"; @@ -2614,9 +2609,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Tallenna palvelimet?"; -/* No comment provided by engineer. */ -"Save settings?" = "Tallenna asetukset?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Tallenna tervetuloviesti?"; @@ -3512,7 +3504,7 @@ "Your profile **%@** will be shared." = "Profiilisi **%@** jaetaan."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa.\nSimpleX-palvelimet eivät näe profiiliasi."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index b4f256762c..e1c4c20461 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Impossible d'envoyer un message à ce membre"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Annuler"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Nom complet (optionnel)"; -/* No comment provided by engineer. */ -"Full name:" = "Nom complet :"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Entièrement décentralisé – visible que par ses membres."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Images de profil"; -/* No comment provided by engineer. */ -"Profile name" = "Nom du profil"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nom du profil :"; - /* No comment provided by engineer. */ "Profile password" = "Mot de passe de profil"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Groupes plus sûrs"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Enregistrer"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Enregistrer (et en informer les contacts)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Enregistrer l'archive"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Enregistrer les paramètres de validation automatique"; - /* No comment provided by engineer. */ "Save group profile" = "Enregistrer le profil du groupe"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Enregistrer les serveurs ?"; -/* No comment provided by engineer. */ -"Save settings?" = "Enregistrer les paramètres ?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Enregistrer le message d'accueil ?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Votre profil **%@** sera partagé."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts.\nLes serveurs SimpleX ne peuvent pas voir votre profil."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 8c70bcd626..54c0b6bd50 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -29,7 +29,7 @@ "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- opcionális értesítés a törölt kapcsolatokról.\n- profilnevek szóközökkel.\n- és még sok más!"; /* No comment provided by engineer. */ -"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- hangüzenetek legfeljebb 5 perces időtartamig.\n- egyedi eltűnési időhatár megadása.\n- előzmények szerkesztése."; +"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 5 perc hosszúságú hangüzenetek.\n- egyedi üzenet-eltűnési időkorlát.\n- előzmények szerkesztése."; /* No comment provided by engineer. */ ", " = ", "; @@ -62,7 +62,7 @@ "[Send us email](mailto:chat@simplex.chat)" = "[Küldjön nekünk e-mailt](mailto:chat@simplex.chat)"; /* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat)"; +"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ "**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; @@ -263,7 +263,7 @@ "`a + b`" = "a + b"; /* email text */ -"

    Hi!

    \n

    Connect to me via SimpleX Chat

    " = "

    Üdvözlöm!

    \n

    Csatlakozzon hozzám a SimpleX Chaten

    "; +"

    Hi!

    \n

    Connect to me via SimpleX Chat

    " = "

    Üdvözlöm!

    \n

    Csatlakozzon hozzám a SimpleX Chaten keresztül

    "; /* No comment provided by engineer. */ "~strike~" = "\\~áthúzott~"; @@ -343,7 +343,7 @@ "Accept" = "Elfogadás"; /* No comment provided by engineer. */ -"Accept connection request?" = "Kapcsolódási kérelem elfogadása?"; +"Accept connection request?" = "Ismerőskérelem elfogadása?"; /* notification body */ "Accept contact request from %@?" = "Elfogadja %@ kapcsolat kérését?"; @@ -659,10 +659,10 @@ "Bad desktop address" = "Hibás számítógép cím"; /* integrity error chat item */ -"bad message hash" = "hibás az üzenet ellenőrzőösszege"; +"bad message hash" = "hibás az üzenet hasító értéke"; /* No comment provided by engineer. */ -"Bad message hash" = "Hibás az üzenet ellenőrzőösszege"; +"Bad message hash" = "Hibás az üzenet hasító értéke"; /* integrity error chat item */ "bad message ID" = "téves üzenet ID"; @@ -749,7 +749,7 @@ "Call already ended!" = "A hívás már befejeződött!"; /* call status */ -"call error" = "hiba a hívásban"; +"call error" = "híváshiba"; /* call status */ "call in progress" = "hívás folyamatban"; @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Nem lehet üzenetet küldeni a tagnak"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Mégse"; /* No comment provided by engineer. */ @@ -1002,7 +1002,7 @@ "Connect incognito" = "Kapcsolódás inkognitóban"; /* No comment provided by engineer. */ -"Connect to desktop" = "Kapcsolódás számítógéphez"; +"Connect to desktop" = "Társítás számítógéppel"; /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "Kapcsolódás a SimpleX Chat fejlesztőkhöz."; @@ -1014,7 +1014,7 @@ "Connect to yourself?" = "Kapcsolódás saját magához?"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódás saját magához?\nEz az egyszer használatos hivatkozása!"; +"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódás saját magához?\nEz az ön egyszer használatos hivatkozása!"; /* No comment provided by engineer. */ "Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz az ön SimpleX címe!"; @@ -1038,7 +1038,7 @@ "Connected" = "Kapcsolódva"; /* No comment provided by engineer. */ -"Connected desktop" = "Csatlakoztatott számítógép"; +"Connected desktop" = "Társított számítógép"; /* rcv group event chat item */ "connected directly" = "közvetlenül kapcsolódva"; @@ -1068,7 +1068,7 @@ "connecting (introduction invitation)" = "kapcsolódás (bemutatkozó meghívó)"; /* call status */ -"connecting call" = "hívás kapcsolódik…"; +"connecting call" = "kapcsolódási hívás…"; /* No comment provided by engineer. */ "Connecting server…" = "Kapcsolódás a kiszolgálóhoz…"; @@ -1128,7 +1128,7 @@ "Contact allows" = "Ismerős engedélyezi"; /* No comment provided by engineer. */ -"Contact already exists" = "Létező ismerős"; +"Contact already exists" = "Az ismerős már létezik"; /* No comment provided by engineer. */ "Contact deleted!" = "Ismerős törölve!"; @@ -1197,7 +1197,7 @@ "Create group" = "Csoport létrehozása"; /* No comment provided by engineer. */ -"Create group link" = "Csoportos hivatkozás létrehozása"; +"Create group link" = "Csoporthivatkozás létrehozása"; /* No comment provided by engineer. */ "Create link" = "Hivatkozás létrehozása"; @@ -1233,7 +1233,7 @@ "Created on %@" = "Létrehozva %@"; /* No comment provided by engineer. */ -"Creating archive link" = "Archív hivatkozás létrehozása"; +"Creating archive link" = "Archívum hivatkozás létrehozása"; /* No comment provided by engineer. */ "Creating link…" = "Hivatkozás létrehozása…"; @@ -1450,7 +1450,7 @@ "Delete old database?" = "Régi adatbázis törlése?"; /* No comment provided by engineer. */ -"Delete pending connection?" = "Függő kapcsolatfelvételi kérések törlése?"; +"Delete pending connection?" = "Függőben lévő ismerőskérelem törlése?"; /* No comment provided by engineer. */ "Delete profile" = "Profil törlése"; @@ -1654,7 +1654,7 @@ "Downloading link details" = "Letöltési hivatkozás részletei"; /* No comment provided by engineer. */ -"Duplicate display name!" = "Duplikált megjelenítési név!"; +"Duplicate display name!" = "Duplikált megjelenített név!"; /* integrity error chat item */ "duplicate message" = "duplikált üzenet"; @@ -1852,7 +1852,7 @@ "Error accessing database file" = "Hiba az adatbázisfájl elérésekor"; /* No comment provided by engineer. */ -"Error adding member(s)" = "Hiba a tag(-ok) hozzáadásakor"; +"Error adding member(s)" = "Hiba a tag(ok) hozzáadásakor"; /* No comment provided by engineer. */ "Error changing address" = "Hiba a cím megváltoztatásakor"; @@ -1873,7 +1873,7 @@ "Error creating group" = "Hiba a csoport létrehozásakor"; /* No comment provided by engineer. */ -"Error creating group link" = "Hiba a csoport hivatkozásának létrehozásakor"; +"Error creating group link" = "Hiba a csoporthivatkozás létrehozásakor"; /* No comment provided by engineer. */ "Error creating member contact" = "Hiba az ismerőssel történő kapcsolat létrehozásában"; @@ -2002,7 +2002,7 @@ "Error synchronizing connection" = "Hiba a kapcsolat szinkronizálása közben"; /* No comment provided by engineer. */ -"Error updating group link" = "Hiba a csoport hivatkozás frissítésekor"; +"Error updating group link" = "Hiba a csoporthivatkozás frissítésekor"; /* No comment provided by engineer. */ "Error updating message" = "Hiba az üzenet frissítésekor"; @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Teljes név (opcionális)"; -/* No comment provided by engineer. */ -"Full name:" = "Teljes név:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Teljesen decentralizált - kizárólag tagok számára látható."; @@ -2255,10 +2252,10 @@ "Group invitation is no longer valid, it was removed by sender." = "A csoport meghívó már nem érvényes, a küldője törölte."; /* No comment provided by engineer. */ -"Group link" = "Csoport hivatkozás"; +"Group link" = "Csoporthivatkozás"; /* No comment provided by engineer. */ -"Group links" = "Csoport hivatkozások"; +"Group links" = "Csoporthivatkozások"; /* No comment provided by engineer. */ "Group members can add message reactions." = "Csoporttagok üzenetreakciókat adhatnak hozzá."; @@ -2303,7 +2300,7 @@ "Group welcome message" = "Csoport üdvözlő üzenete"; /* No comment provided by engineer. */ -"Group will be deleted for all members - this cannot be undone!" = "Csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza!"; +"Group will be deleted for all members - this cannot be undone!" = "A csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "A csoport törlésre kerül az ön számára - ez a művelet nem vonható vissza!"; @@ -2444,10 +2441,10 @@ "incognito via contact address link" = "inkognitó a kapcsolattartási hivatkozáson keresztül"; /* chat list item description */ -"incognito via group link" = "inkognitó a csoportos hivatkozáson keresztül"; +"incognito via group link" = "inkognitó a csoporthivatkozáson keresztül"; /* chat list item description */ -"incognito via one-time link" = "inkognitó az egyszer használatos hivatkozáson keresztül"; +"incognito via one-time link" = "inkognitó egy egyszer használatos hivatkozáson keresztül"; /* notification */ "Incoming audio call" = "Bejövő hanghívás"; @@ -2501,13 +2498,13 @@ "invalid chat data" = "érvénytelen csevegés adat"; /* No comment provided by engineer. */ -"Invalid connection link" = "Érvénytelen kapcsolati hivatkozás"; +"Invalid connection link" = "Érvénytelen kapcsolattartási hivatkozás"; /* invalid chat item */ "invalid data" = "érvénytelen adat"; /* No comment provided by engineer. */ -"Invalid display name!" = "Érvénytelen megjelenítendő felhaszálónév!"; +"Invalid display name!" = "Érvénytelen megjelenítendő név!"; /* No comment provided by engineer. */ "Invalid link" = "Érvénytelen hivatkozás"; @@ -2558,7 +2555,7 @@ "invited to connect" = "meghívta, hogy csatlakozzon"; /* rcv group event chat item */ -"invited via your group link" = "meghíva az ön csoport hivatkozásán keresztül"; +"invited via your group link" = "meghíva az ön csoporthivatkozásán keresztül"; /* No comment provided by engineer. */ "iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál - lehetővé teszi a push-értesítések fogadását."; @@ -2666,7 +2663,7 @@ "left" = "elhagyta a csoportot"; /* email subject */ -"Let's talk in SimpleX Chat" = "Beszélgessünk a SimpleX Chat-ben"; +"Let's talk in SimpleX Chat" = "Beszélgessünk a SimpleX Chatben"; /* No comment provided by engineer. */ "Light" = "Világos"; @@ -2675,13 +2672,13 @@ "Limitations" = "Korlátozások"; /* No comment provided by engineer. */ -"Link mobile and desktop apps! 🔗" = "Társítsa össze a mobil és az asztali alkalmazásokat! 🔗"; +"Link mobile and desktop apps! 🔗" = "Társítsa össze a mobil és asztali alkalmazásokat! 🔗"; /* No comment provided by engineer. */ -"Linked desktop options" = "Összekapcsolt számítógép beállítások"; +"Linked desktop options" = "Társított számítógép beállítások"; /* No comment provided by engineer. */ -"Linked desktops" = "Összekapcsolt számítógépek"; +"Linked desktops" = "Társított számítógépek"; /* No comment provided by engineer. */ "LIVE" = "ÉLŐ"; @@ -2723,7 +2720,7 @@ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva."; /* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kérdezték: *ha a SimpleX-nek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*"; +"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*"; /* No comment provided by engineer. */ "Mark deleted for everyone" = "Jelölje meg mindenki számára töröltként"; @@ -2738,7 +2735,7 @@ "Markdown in messages" = "Markdown az üzenetekben"; /* marked deleted chat item preview text */ -"marked deleted" = "töröltnek jelölve"; +"marked deleted" = "törlésre jelölve"; /* No comment provided by engineer. */ "Max 30 seconds, received instantly." = "Max. 30 másodperc, azonnal érkezett."; @@ -2903,10 +2900,10 @@ "moderated" = "moderált"; /* No comment provided by engineer. */ -"Moderated at" = "Moderálva lett ekkor:"; +"Moderated at" = "Moderálva ekkor:"; /* copied message info */ -"Moderated at: %@" = "Moderálva lett ekkor: %@"; +"Moderated at: %@" = "Moderálva ekkor: %@"; /* marked deleted chat item preview text */ "moderated by %@" = "moderálva lett %@ által"; @@ -2966,7 +2963,7 @@ "New chat experience 🎉" = "Új csevegési élmény 🎉"; /* notification */ -"New contact request" = "Új kapcsolattartási kérelem"; +"New contact request" = "Új ismerőskérelem"; /* notification */ "New contact:" = "Új kapcsolat:"; @@ -3011,7 +3008,7 @@ "No app password" = "Nincs alkalmazás jelszó"; /* No comment provided by engineer. */ -"No contacts selected" = "Nem kerültek ismerősök kiválasztásra"; +"No contacts selected" = "Nincs kiválasztva ismerős"; /* No comment provided by engineer. */ "No contacts to add" = "Nincs hozzáadandó ismerős"; @@ -3056,7 +3053,7 @@ "Not compatible!" = "Nem kompatibilis!"; /* No comment provided by engineer. */ -"Nothing selected" = "Semmi sincs kiválasztva"; +"Nothing selected" = "Nincs kiválasztva semmi"; /* No comment provided by engineer. */ "Notifications" = "Értesítések"; @@ -3094,7 +3091,7 @@ "Old database" = "Régi adatbázis"; /* No comment provided by engineer. */ -"Old database archive" = "Régi adatbázis archívum"; +"Old database archive" = "Régi adatbázis-archívum"; /* group pref value */ "on" = "bekapcsolva"; @@ -3112,7 +3109,7 @@ "Onion hosts will not be used." = "Onion kiszolgálók nem lesznek használva."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végponttól-végpontig titkosítással** küldött üzeneteket."; +"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket."; /* No comment provided by engineer. */ "Only delete conversation" = "Csak a beszélgetés törlése"; @@ -3193,7 +3190,7 @@ "Or scan QR code" = "Vagy QR-kód beolvasása"; /* No comment provided by engineer. */ -"Or securely share this file link" = "Vagy a fájl hivítkozásának biztonságos megosztása"; +"Or securely share this file link" = "Vagy ossza meg biztonságosan ezt a fájlhivatkozást"; /* No comment provided by engineer. */ "Or show this code" = "Vagy mutassa meg ezt a kódot"; @@ -3235,7 +3232,7 @@ "Password to show" = "Jelszó megjelenítése"; /* past/unknown group member */ -"Past member %@" = "Már nem tag - %@"; +"Past member %@" = "%@ (már nem tag)"; /* No comment provided by engineer. */ "Paste desktop address" = "Számítógép címének beillesztése"; @@ -3247,13 +3244,13 @@ "Paste link to connect!" = "Hivatkozás beillesztése a kapcsolódáshoz!"; /* No comment provided by engineer. */ -"Paste the link you received" = "Fogadott hivatkozás beillesztése"; +"Paste the link you received" = "Kapott hivatkozás beillesztése"; /* No comment provided by engineer. */ "peer-to-peer" = "ponttól-pontig"; /* No comment provided by engineer. */ -"Pending" = "Függő"; +"Pending" = "Függőben"; /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "Az emberek csak az ön által megosztott hivatkozáson keresztül kapcsolódhatnak."; @@ -3286,7 +3283,7 @@ "Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "Ellenőrizze, hogy a mobil és az asztali számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint az asztali számítógép tűzfalában engedélyezve van-e a kapcsolat.\nMinden további problémát osszon meg a fejlesztőkkel."; /* No comment provided by engineer. */ -"Please check that you used the correct link or ask your contact to send you another one." = "Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg ismerősét, hogy küldjön egy másikat."; +"Please check that you used the correct link or ask your contact to send you another one." = "Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg az ismerősét, hogy küldjön egy másikat."; /* No comment provided by engineer. */ "Please check your network connection with %@ and try again." = "Ellenőrizze hálózati kapcsolatát a(z) %@ segítségével, és próbálja újra."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Profilképek"; -/* No comment provided by engineer. */ -"Profile name" = "Profilnév"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profil neve:"; - /* No comment provided by engineer. */ "Profile password" = "Profiljelszó"; @@ -3493,7 +3484,7 @@ "Receive errors" = "Üzenetfogadási hibák"; /* No comment provided by engineer. */ -"received answer…" = "fogadott válasz…"; +"received answer…" = "válasz fogadása…"; /* No comment provided by engineer. */ "Received at" = "Fogadva ekkor:"; @@ -3647,7 +3638,7 @@ "Required" = "Szükséges"; /* No comment provided by engineer. */ -"Reset" = "Alaphelyzetbe állítás"; +"Reset" = "Visszaállítás"; /* No comment provided by engineer. */ "Reset all hints" = "Tippek visszaállítása"; @@ -3659,13 +3650,13 @@ "Reset all statistics?" = "Minden statisztika visszaállítása?"; /* No comment provided by engineer. */ -"Reset colors" = "Színek alaphelyzetbe állítása"; +"Reset colors" = "Színek visszaállítása"; /* No comment provided by engineer. */ "Reset to app theme" = "Alkalmazás témájának visszaállítása"; /* No comment provided by engineer. */ -"Reset to defaults" = "Alaphelyzetbe állítás"; +"Reset to defaults" = "Visszaállítás alaphelyzetbe"; /* No comment provided by engineer. */ "Reset to user theme" = "Felhasználó által létrehozott téma visszaállítása"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Biztonságosabb csoportok"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Mentés"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Mentés (és az ismerősök értesítése)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Archívum mentése"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Automatikus elfogadási beállítások mentése"; - /* No comment provided by engineer. */ "Save group profile" = "Csoportprofil elmentése"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Kiszolgálók mentése?"; -/* No comment provided by engineer. */ -"Save settings?" = "Beállítások mentése?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Üdvözlőszöveg mentése?"; @@ -4013,10 +3999,10 @@ "Servers" = "Kiszolgálók"; /* No comment provided by engineer. */ -"Servers info" = "információk a kiszolgálókról"; +"Servers info" = "Információk a kiszolgálókról"; /* No comment provided by engineer. */ -"Servers statistics will be reset - this cannot be undone!" = "A kiszolgálók statisztikái visszaállnak - ez nem vonható vissza!"; +"Servers statistics will be reset - this cannot be undone!" = "A kiszolgálók statisztikái visszaállnak - ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ "Session code" = "Munkamenet kód"; @@ -4136,7 +4122,7 @@ "SimpleX encrypted message or connection event" = "SimpleX titkosított üzenet vagy kapcsolati esemény"; /* simplex link type */ -"SimpleX group link" = "SimpleX csoport hivatkozás"; +"SimpleX group link" = "SimpleX csoporthivatkozás"; /* chat feature */ "SimpleX links" = "SimpleX hivatkozások"; @@ -4274,7 +4260,7 @@ "Subscriptions ignored" = "Elutasított feliratkozások"; /* No comment provided by engineer. */ -"Support SimpleX Chat" = "Támogassa a SimpleX Chatet"; +"Support SimpleX Chat" = "SimpleX Chat támogatása"; /* No comment provided by engineer. */ "System" = "Rendszer"; @@ -4343,7 +4329,7 @@ "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Köszönet a felhasználóknak – [hozzájárulás a Weblate-en keresztül](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; /* No comment provided by engineer. */ -"Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak - hozzájárulás a Weblaten!"; +"Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak - hozzájárulás a Weblate-en!"; /* No comment provided by engineer. */ "The 1st platform without any user identifiers – private by design." = "Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre."; @@ -4358,10 +4344,10 @@ "The attempt to change database passphrase was not completed." = "Az adatbázis jelmondatának megváltoztatására tett kísérlet nem fejeződött be."; /* No comment provided by engineer. */ -"The code you scanned is not a SimpleX link QR code." = "A beolvasott kód nem egy SimpleX hivatkozás QR-kód."; +"The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozás."; /* No comment provided by engineer. */ -"The connection you accepted will be cancelled!" = "Az ön által elfogadott kapcsolat vissza lesz vonva!"; +"The connection you accepted will be cancelled!" = "Az ön által elfogadott kérelem vissza lesz vonva!"; /* No comment provided by engineer. */ "The contact you shared this link with will NOT be able to connect!" = "Ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni!"; @@ -4373,7 +4359,7 @@ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!"; /* No comment provided by engineer. */ -"The hash of the previous message is different." = "Az előző üzenet ellenőrzőösszege különbözik."; +"The hash of the previous message is different." = "Az előző üzenet hasító értéke különbözik."; /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő."; @@ -4442,7 +4428,7 @@ "This device name" = "Ennek az eszköznek a neve"; /* No comment provided by engineer. */ -"This display name is invalid. Please choose another name." = "Ez a megjelenített felhasználónév érvénytelen. Válasszon egy másik nevet."; +"This display name is invalid. Please choose another name." = "Ez a megjelenített név érvénytelen. Válasszon egy másik nevet."; /* No comment provided by engineer. */ "This group has over %lld members, delivery receipts are not sent." = "Ennek a csoportnak több mint %lld tagja van, a kézbesítési jelentések nem kerülnek elküldésre."; @@ -4451,13 +4437,13 @@ "This group no longer exists." = "Ez a csoport már nem létezik."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Ez az egyszer használatos hivatkozása!"; +"This is your own one-time link!" = "Ez az ön egyszer használatos hivatkozása!"; /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Ez az ön SimpleX címe!"; /* No comment provided by engineer. */ -"This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozást egy másik mobilleszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén."; +"This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozást egy másik mobileszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén."; /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Ez a beállítás a jelenlegi **%@** profiljában lévő üzenetekre érvényes."; @@ -4520,10 +4506,10 @@ "Transport sessions" = "Munkamenetek átvitele"; /* No comment provided by engineer. */ -"Trying to connect to the server used to receive messages from this contact (error: %@)." = "Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál (hiba: %@)."; +"Trying to connect to the server used to receive messages from this contact (error: %@)." = "Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál (hiba: %@)."; /* No comment provided by engineer. */ -"Trying to connect to the server used to receive messages from this contact." = "Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál."; +"Trying to connect to the server used to receive messages from this contact." = "Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál."; /* No comment provided by engineer. */ "Turkish interface" = "Török kezelőfelület"; @@ -4598,7 +4584,7 @@ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakítások elkerülése érdekében."; /* No comment provided by engineer. */ -"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, lehet hogy ez egy hiba – jelentse a problémát.\nA kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e."; +"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát.\nA kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e."; /* No comment provided by engineer. */ "Unlink" = "Szétkapcsolás"; @@ -4682,7 +4668,7 @@ "Use for new connections" = "Alkalmazás új kapcsolatokhoz"; /* No comment provided by engineer. */ -"Use from desktop" = "Használat számítógépről"; +"Use from desktop" = "Társítás számítógéppel"; /* No comment provided by engineer. */ "Use iOS call interface" = "Az iOS hívófelület használata"; @@ -4754,10 +4740,10 @@ "via contact address link" = "kapcsolattartási cím-hivatkozáson keresztül"; /* chat list item description */ -"via group link" = "csoport hivatkozáson keresztül"; +"via group link" = "a csoporthivatkozáson keresztül"; /* chat list item description */ -"via one-time link" = "egyszer használatos hivatkozáson keresztül"; +"via one-time link" = "egy egyszer használatos hivatkozáson keresztül"; /* No comment provided by engineer. */ "via relay" = "átjátszón keresztül"; @@ -4934,7 +4920,7 @@ "You already have a chat profile with the same display name. Please choose another name." = "Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet."; /* No comment provided by engineer. */ -"You are already connected to %@." = "Már kapcsolódva van hozzá: %@."; +"You are already connected to %@." = "Ön már kapcsolódva van ehhez: %@."; /* No comment provided by engineer. */ "You are already connecting to %@." = "Már folyamatban van a kapcsolódás ehhez: %@."; @@ -4958,7 +4944,7 @@ "You are already joining the group!\nRepeat join request?" = "Csatlakozás folyamatban!\nCsatlakozási kérés megismétlése?"; /* No comment provided by engineer. */ -"You are connected to the server used to receive messages from this contact." = "Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál."; +"You are connected to the server used to receive messages from this contact." = "Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál."; /* No comment provided by engineer. */ "you are invited to group" = "meghívást kapott a csoportba"; @@ -4973,7 +4959,7 @@ "you are observer" = "megfigyelő szerep"; /* snd group event chat item */ -"you blocked %@" = "ön letiltotta %@-t"; +"you blocked %@" = "ön letiltotta őt: %@"; /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Hívásokat fogadhat a lezárási képernyőről, eszköz- és alkalmazáshitelesítés nélkül."; @@ -4997,7 +4983,7 @@ "You can hide or mute a user profile - swipe it to the right." = "Elrejtheti vagy lenémíthatja a felhasználó profiljait - csúsztassa jobbra a profilt."; /* No comment provided by engineer. */ -"You can make it visible to your SimpleX contacts via Settings." = "Láthatóvá teheti SimpleX ismerősök számára a Beállításokban."; +"You can make it visible to your SimpleX contacts via Settings." = "Láthatóvá teheti a SimpleXbeli ismerősei számára a „Beállításokban”."; /* notification body */ "You can now chat with %@" = "Mostantól küldhet üzeneteket %@ számára"; @@ -5111,7 +5097,7 @@ "You will be connected to group when the group host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; /* No comment provided by engineer. */ -"You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; +"You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoporthivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; /* No comment provided by engineer. */ "You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolódási kérelme elfogadásra kerül, várjon, vagy ellenőrizze később!"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "A(z) **%@** nevű profilja megosztásra fog kerülni."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra.\nA SimpleX kiszolgálók nem látjhatják profilját."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra. A SimpleX kiszolgálók nem látjhatják profilját."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profilja, ismerősei és az elküldött üzenetei az eszközön kerülnek tárolásra."; @@ -5207,7 +5193,7 @@ "Your settings" = "Beállítások"; /* No comment provided by engineer. */ -"Your SimpleX address" = "Az ön SimpleX címe"; +"Your SimpleX address" = "Profil SimpleX címe"; /* No comment provided by engineer. */ "Your SMP servers" = "SMP kiszolgálók"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index f3fa0424cc..c60c1537a6 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Impossibile inviare un messaggio al membro"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Annulla"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Nome completo (facoltativo)"; -/* No comment provided by engineer. */ -"Full name:" = "Nome completo:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Completamente decentralizzato: visibile solo ai membri."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Immagini del profilo"; -/* No comment provided by engineer. */ -"Profile name" = "Nome del profilo"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nome del profilo:"; - /* No comment provided by engineer. */ "Profile password" = "Password del profilo"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Gruppi più sicuri"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Salva"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Salva (e avvisa i contatti)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Salva archivio"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Salva le impostazioni di accettazione automatica"; - /* No comment provided by engineer. */ "Save group profile" = "Salva il profilo del gruppo"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Salvare i server?"; -/* No comment provided by engineer. */ -"Save settings?" = "Salvare le impostazioni?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Salvare il messaggio di benvenuto?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Verrà condiviso il tuo profilo **%@**."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti.\nI server di SimpleX non possono vedere il tuo profilo."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 2a924539c8..8a6c48532c 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -619,7 +619,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "連絡先を招待できません!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "中止"; /* feature offered item */ @@ -1594,9 +1594,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "フルネーム (任意):"; -/* No comment provided by engineer. */ -"Full name:" = "フルネーム:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "完全に再実装されました - バックグラウンドで動作します!"; @@ -2647,10 +2644,11 @@ /* No comment provided by engineer. */ "Run chat" = "チャット起動"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "保存"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "保存(連絡先に通知)"; /* No comment provided by engineer. */ @@ -2665,9 +2663,6 @@ /* No comment provided by engineer. */ "Save archive" = "アーカイブを保存"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "自動受け入れ設定を保存する"; - /* No comment provided by engineer. */ "Save group profile" = "グループプロフィールの保存"; @@ -2689,9 +2684,6 @@ /* No comment provided by engineer. */ "Save servers?" = "サーバを保存しますか?"; -/* No comment provided by engineer. */ -"Save settings?" = "設定を保存しますか?"; - /* No comment provided by engineer. */ "Save welcome message?" = "ウェルカムメッセージを保存しますか?"; @@ -3566,7 +3558,7 @@ "Your profile **%@** will be shared." = "あなたのプロファイル **%@** が共有されます。"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。\nSimpleX サーバーはあなたのプロファイルを参照できません。"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。"; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 7d452743c6..6c566fa7da 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Kan geen bericht sturen naar lid"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Annuleren"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Volledige naam (optioneel)"; -/* No comment provided by engineer. */ -"Full name:" = "Volledige naam:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Volledig gedecentraliseerd – alleen zichtbaar voor leden."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Profiel afbeeldingen"; -/* No comment provided by engineer. */ -"Profile name" = "Profielnaam"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profielnaam:"; - /* No comment provided by engineer. */ "Profile password" = "Profiel wachtwoord"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Veiligere groepen"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Opslaan"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Bewaar (en informeer contacten)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Bewaar archief"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Sla instellingen voor automatisch accepteren op"; - /* No comment provided by engineer. */ "Save group profile" = "Groep profiel opslaan"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Servers opslaan?"; -/* No comment provided by engineer. */ -"Save settings?" = "Instellingen opslaan?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Welkom bericht opslaan?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Uw profiel **%@** wordt gedeeld."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten.\nSimpleX servers kunnen uw profiel niet zien."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 179b2d3848..7def1c7e02 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Nie można wysłać wiadomości do członka"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Anuluj"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Pełna nazwa (opcjonalna)"; -/* No comment provided by engineer. */ -"Full name:" = "Pełna nazwa:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "W pełni zdecentralizowana – widoczna tylko dla członków."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Zdjęcia profilowe"; -/* No comment provided by engineer. */ -"Profile name" = "Nazwa profilu"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nazwa profilu:"; - /* No comment provided by engineer. */ "Profile password" = "Hasło profilu"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Bezpieczniejsze grupy"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Zapisz"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Zapisz (i powiadom kontakty)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Zapisz archiwum"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Zapisz ustawienia automatycznej akceptacji"; - /* No comment provided by engineer. */ "Save group profile" = "Zapisz profil grupy"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Zapisać serwery?"; -/* No comment provided by engineer. */ -"Save settings?" = "Zapisać ustawienia?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Zapisać wiadomość powitalną?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Twój profil **%@** zostanie udostępniony."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom.\nSerwery SimpleX nie mogą zobaczyć Twojego profilu."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 5d74647d07..038041d0e6 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Не удается написать члену группы"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Отменить"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Полное имя (не обязательно)"; -/* No comment provided by engineer. */ -"Full name:" = "Полное имя:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Группа полностью децентрализована – она видна только членам."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Картинки профилей"; -/* No comment provided by engineer. */ -"Profile name" = "Имя профиля"; - -/* No comment provided by engineer. */ -"Profile name:" = "Имя профиля:"; - /* No comment provided by engineer. */ "Profile password" = "Пароль профиля"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Более безопасные группы"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Сохранить"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Сохранить (и уведомить контакты)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Сохранить архив"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Сохранить настройки автоприема"; - /* No comment provided by engineer. */ "Save group profile" = "Сохранить профиль группы"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Сохранить серверы?"; -/* No comment provided by engineer. */ -"Save settings?" = "Сохранить настройки?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Сохранить приветственное сообщение?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Будет отправлен Ваш профиль **%@**."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам.\nSimpleX серверы не могут получить доступ к Вашему профилю."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 9726441a1f..994a10c9db 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -523,7 +523,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "ไม่สามารถเชิญผู้ติดต่อได้!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "ยกเลิก"; /* feature offered item */ @@ -1468,9 +1468,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "ชื่อเต็ม (ไม่บังคับ)"; -/* No comment provided by engineer. */ -"Full name:" = "ชื่อเต็ม:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "ดำเนินการใหม่อย่างสมบูรณ์ - ทำงานในพื้นหลัง!"; @@ -2503,10 +2500,11 @@ /* No comment provided by engineer. */ "Run chat" = "เรียกใช้แชท"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "บันทึก"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "บันทึก (และแจ้งผู้ติดต่อ)"; /* No comment provided by engineer. */ @@ -2521,9 +2519,6 @@ /* No comment provided by engineer. */ "Save archive" = "บันทึกไฟล์เก็บถาวร"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "บันทึกการตั้งค่าการยอมรับอัตโนมัติ"; - /* No comment provided by engineer. */ "Save group profile" = "บันทึกโปรไฟล์กลุ่ม"; @@ -2545,9 +2540,6 @@ /* No comment provided by engineer. */ "Save servers?" = "บันทึกเซิร์ฟเวอร์?"; -/* No comment provided by engineer. */ -"Save settings?" = "บันทึกการตั้งค่า?"; - /* No comment provided by engineer. */ "Save welcome message?" = "บันทึกข้อความต้อนรับ?"; @@ -3413,7 +3405,7 @@ "Your privacy" = "ความเป็นส่วนตัวของคุณ"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น\nเซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้"; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 892f38fcbc..f39140340b 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -703,7 +703,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Kişiler davet edilemiyor!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "İptal et"; /* No comment provided by engineer. */ @@ -1930,9 +1930,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Bütün isim (opsiyonel)"; -/* No comment provided by engineer. */ -"Full name:" = "Bütün isim:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Tamamiyle merkezi olmayan - sadece kişilere görünür."; @@ -2991,12 +2988,6 @@ /* No comment provided by engineer. */ "Profile images" = "Profil resimleri"; -/* No comment provided by engineer. */ -"Profile name" = "Profil ismi"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profil ismi:"; - /* No comment provided by engineer. */ "Profile password" = "Profil parolası"; @@ -3271,10 +3262,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Daha güvenli gruplar"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Kaydet"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Kaydet (ve kişilere bildir)"; /* No comment provided by engineer. */ @@ -3289,9 +3281,6 @@ /* No comment provided by engineer. */ "Save archive" = "Arşivi kaydet"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Otomatik kabul et ayarlarını kaydet"; - /* No comment provided by engineer. */ "Save group profile" = "Grup profilini kaydet"; @@ -3313,9 +3302,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Sunucular kaydedilsin mi?"; -/* No comment provided by engineer. */ -"Save settings?" = "Ayarlar kaydedilsin mi?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Hoşgeldin mesajı kaydedilsin mi?"; @@ -4544,7 +4530,7 @@ "Your profile **%@** will be shared." = "Profiliniz **%@** paylaşılacaktır."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır.\nSimpleX sunucuları profilinizi göremez."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 1908d386a3..4218065a2e 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Не можу надіслати повідомлення користувачеві"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Скасувати"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Повне ім'я (необов'язково)"; -/* No comment provided by engineer. */ -"Full name:" = "Повне ім'я:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Повністю децентралізована - видима лише для учасників."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Зображення профілю"; -/* No comment provided by engineer. */ -"Profile name" = "Назва профілю"; - -/* No comment provided by engineer. */ -"Profile name:" = "Ім'я профілю:"; - /* No comment provided by engineer. */ "Profile password" = "Пароль до профілю"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Безпечніші групи"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Зберегти"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Зберегти (і повідомити контактам)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Зберегти архів"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Зберегти налаштування автоприйому"; - /* No comment provided by engineer. */ "Save group profile" = "Зберегти профіль групи"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Зберегти сервери?"; -/* No comment provided by engineer. */ -"Save settings?" = "Зберегти налаштування?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Зберегти вітальне повідомлення?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Ваш профіль **%@** буде опублікований."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам.\nСервери SimpleX не бачать ваш профіль."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 1d37572498..0f78665b83 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -46,6 +46,12 @@ /* No comment provided by engineer. */ "(" = "("; +/* No comment provided by engineer. */ +"(new)" = "(新)"; + +/* No comment provided by engineer. */ +"(this device v%@)" = "(此设备 v%@)"; + /* No comment provided by engineer. */ ")" = ")"; @@ -58,9 +64,15 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[在 GitHub 上加星](https://github.com/simplex-chat/simplex-chat)"; +/* No comment provided by engineer. */ +"**Add contact**: to create a new invitation link, or connect via a link you received." = "**添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接."; + /* No comment provided by engineer. */ "**Add new contact**: to create your one-time QR Code for your contact." = "**添加新联系人**:为您的联系人创建一次性二维码或者链接。"; +/* No comment provided by engineer. */ +"**Create group**: to create a new group." = "**创建群组**: 创建一个新群组."; + /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**端到端加密** 语音通话"; @@ -73,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**最私密**:不使用 SimpleX Chat 通知服务器,在后台定期检查消息(取决于您多经常使用应用程序)。"; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**请注意**: 在两台设备上使用相同的数据库将破坏来自您的连接的消息解密,作为一种安全保护."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**请注意**:如果您丢失密码,您将无法恢复或者更改密码。"; @@ -82,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**警告**:及时推送通知需要保存在钥匙串的密码。"; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**警告**: 存档将被删除."; + /* No comment provided by engineer. */ "*bold*" = "\\*加粗*"; @@ -124,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ 已连接"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ 已下载"; + /* notification title */ "%@ is connected!" = "%@ 已连接!"; @@ -133,6 +154,9 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ 已认证"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ 已上传"; + /* notification title */ "%@ wants to connect!" = "%@ 要连接!"; @@ -187,6 +211,15 @@ /* No comment provided by engineer. */ "%lld messages blocked" = "%lld 条消息已屏蔽"; +/* No comment provided by engineer. */ +"%lld messages blocked by admin" = "%lld 被管理员阻止的消息"; + +/* No comment provided by engineer. */ +"%lld messages marked deleted" = "%lld 标记为已删除的消息"; + +/* No comment provided by engineer. */ +"%lld messages moderated by %@" = "%lld 审核的留言 by %@"; + /* No comment provided by engineer. */ "%lld minutes" = "%lld 分钟"; @@ -235,6 +268,9 @@ /* No comment provided by engineer. */ "~strike~" = "\\~删去~"; +/* time to disappear */ +"0 sec" = "0 秒"; + /* No comment provided by engineer. */ "0s" = "0秒"; @@ -298,6 +334,9 @@ /* No comment provided by engineer. */ "above, then choose:" = "上面,然后选择:"; +/* No comment provided by engineer. */ +"Accent" = "强调"; + /* accept contact request via notification accept incoming call via notification swipe action */ @@ -316,6 +355,15 @@ /* call status */ "accepted call" = "已接受通话"; +/* No comment provided by engineer. */ +"Acknowledged" = "确认"; + +/* No comment provided by engineer. */ +"Acknowledgement errors" = "确认错误"; + +/* No comment provided by engineer. */ +"Active connections" = "活动连接"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。"; @@ -340,6 +388,15 @@ /* No comment provided by engineer. */ "Add welcome message" = "添加欢迎信息"; +/* No comment provided by engineer. */ +"Additional accent" = "附加重音"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "附加重音 2"; + +/* No comment provided by engineer. */ +"Additional secondary" = "附加二级"; + /* No comment provided by engineer. */ "Address" = "地址"; @@ -361,6 +418,9 @@ /* No comment provided by engineer. */ "Advanced network settings" = "高级网络设置"; +/* No comment provided by engineer. */ +"Advanced settings" = "高级设置"; + /* chat item text */ "agreeing encryption for %@…" = "正在协商将加密应用于 %@…"; @@ -376,6 +436,9 @@ /* No comment provided by engineer. */ "All data is erased when it is entered." = "所有数据在输入后将被删除。"; +/* No comment provided by engineer. */ +"All data is private to your device." = "所有数据都是您设备的私有数据."; + /* No comment provided by engineer. */ "All group members will remain connected." = "所有群组成员将保持连接。"; @@ -388,6 +451,12 @@ /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "所有聊天记录和消息将被删除——这一行为无法撤销!只有您的消息会被删除。"; +/* No comment provided by engineer. */ +"All new messages from %@ will be hidden!" = "来自 %@ 的所有新消息都将被隐藏!"; + +/* No comment provided by engineer. */ +"All profiles" = "所有配置文件"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "所有联系人会保持连接。"; @@ -403,11 +472,17 @@ /* No comment provided by engineer. */ "Allow calls only if your contact allows them." = "仅当您的联系人允许时才允许呼叫。"; +/* No comment provided by engineer. */ +"Allow calls?" = "允许通话?"; + /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "仅当您的联系人允许时才允许限时消息。"; /* No comment provided by engineer. */ -"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "仅有您的联系人许可后才允许不可撤回消息移除。"; +"Allow downgrade" = "允许降级"; + +/* No comment provided by engineer. */ +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "仅有您的联系人许可后才允许不可撤回消息移除"; /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "只有您的联系人允许时才允许消息回应。"; @@ -422,7 +497,10 @@ "Allow sending disappearing messages." = "允许发送限时消息。"; /* No comment provided by engineer. */ -"Allow to irreversibly delete sent messages. (24 hours)" = "允许不可撤回地删除已发送消息。"; +"Allow sharing" = "允许共享"; + +/* No comment provided by engineer. */ +"Allow to irreversibly delete sent messages. (24 hours)" = "允许不可撤回地删除已发送消息"; /* No comment provided by engineer. */ "Allow to send files and media." = "允许发送文件和媒体。"; @@ -446,7 +524,7 @@ "Allow your contacts to call you." = "允许您的联系人给您打电话。"; /* No comment provided by engineer. */ -"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "允许您的联系人不可撤回地删除已发送消息。"; +"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "允许您的联系人不可撤回地删除已发送消息"; /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "允许您的联系人发送限时消息。"; @@ -466,12 +544,18 @@ /* pref value */ "always" = "始终"; +/* No comment provided by engineer. */ +"Always use private routing." = "始终使用私有路由。"; + /* No comment provided by engineer. */ "Always use relay" = "一直使用中继"; /* No comment provided by engineer. */ "An empty chat profile with the provided name is created, and the app opens as usual." = "已创建一个包含所提供名字的空白聊天资料,应用程序照常打开。"; +/* No comment provided by engineer. */ +"and %lld other events" = "和 %lld 其他事件"; + /* No comment provided by engineer. */ "Answer call" = "接听来电"; @@ -505,15 +589,27 @@ /* No comment provided by engineer. */ "Apply" = "应用"; +/* No comment provided by engineer. */ +"Apply to" = "应用于"; + /* No comment provided by engineer. */ "Archive and upload" = "存档和上传"; +/* No comment provided by engineer. */ +"Archive contacts to chat later." = "存档联系人以便稍后聊天."; + +/* No comment provided by engineer. */ +"Archived contacts" = "已存档的联系人"; + /* No comment provided by engineer. */ "Archiving database" = "正在存档数据库"; /* No comment provided by engineer. */ "Attach" = "附件"; +/* No comment provided by engineer. */ +"attempts" = "尝试"; + /* No comment provided by engineer. */ "Audio & video calls" = "语音和视频通话"; @@ -556,6 +652,9 @@ /* No comment provided by engineer. */ "Back" = "返回"; +/* No comment provided by engineer. */ +"Background" = "背景"; + /* No comment provided by engineer. */ "Bad desktop address" = "糟糕的桌面地址"; @@ -577,6 +676,12 @@ /* No comment provided by engineer. */ "Better messages" = "更好的消息"; +/* No comment provided by engineer. */ +"Better networking" = "更好的网络"; + +/* No comment provided by engineer. */ +"Black" = "黑色"; + /* No comment provided by engineer. */ "Block" = "封禁"; @@ -607,6 +712,12 @@ /* No comment provided by engineer. */ "Blocked by admin" = "由管理员封禁"; +/* No comment provided by engineer. */ +"Blur for better privacy." = "模糊处理,提高私密性."; + +/* No comment provided by engineer. */ +"Blur media" = "模糊媒体"; + /* No comment provided by engineer. */ "bold" = "加粗"; @@ -614,7 +725,7 @@ "Both you and your contact can add message reactions." = "您和您的联系人都可以添加消息回应。"; /* No comment provided by engineer. */ -"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "您和您的联系人都可以不可逆转地删除已发送的消息。"; +"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "您和您的联系人都可以不可逆转地删除已发送的消息"; /* No comment provided by engineer. */ "Both you and your contact can make calls." = "您和您的联系人都可以拨打电话。"; @@ -631,6 +742,9 @@ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。"; +/* No comment provided by engineer. */ +"call" = "呼叫"; + /* No comment provided by engineer. */ "Call already ended!" = "通话已结束!"; @@ -646,9 +760,18 @@ /* No comment provided by engineer. */ "Calls" = "通话"; +/* No comment provided by engineer. */ +"Calls prohibited!" = "禁止来电!"; + /* No comment provided by engineer. */ "Camera not available" = "相机不可用"; +/* No comment provided by engineer. */ +"Can't call contact" = "无法呼叫联系人"; + +/* No comment provided by engineer. */ +"Can't call member" = "无法呼叫成员"; + /* No comment provided by engineer. */ "Can't invite contact!" = "无法邀请联系人!"; @@ -656,6 +779,9 @@ "Can't invite contacts!" = "无法邀请联系人!"; /* No comment provided by engineer. */ +"Can't message member" = "无法向成员发送消息"; + +/* alert button */ "Cancel" = "取消"; /* No comment provided by engineer. */ @@ -667,9 +793,15 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "无法访问钥匙串以保存数据库密码"; +/* No comment provided by engineer. */ +"Cannot forward message" = "无法转发消息"; + /* No comment provided by engineer. */ "Cannot receive file" = "无法接收文件"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "超出容量-收件人未收到以前发送的邮件。"; + /* No comment provided by engineer. */ "Cellular" = "移动网络"; @@ -722,6 +854,9 @@ /* No comment provided by engineer. */ "Chat archive" = "聊天档案"; +/* No comment provided by engineer. */ +"Chat colors" = "聊天颜色"; + /* No comment provided by engineer. */ "Chat console" = "聊天控制台"; @@ -731,6 +866,9 @@ /* No comment provided by engineer. */ "Chat database deleted" = "聊天数据库已删除"; +/* No comment provided by engineer. */ +"Chat database exported" = "导出的聊天数据库"; + /* No comment provided by engineer. */ "Chat database imported" = "聊天数据库已导入"; @@ -743,12 +881,18 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "聊天已停止。如果你已经在另一台设备商使用过此数据库,你应该在启动聊天前将数据库传输回来。"; +/* No comment provided by engineer. */ +"Chat list" = "聊天列表"; + /* No comment provided by engineer. */ "Chat migrated!" = "已迁移聊天!"; /* No comment provided by engineer. */ "Chat preferences" = "聊天偏好设置"; +/* No comment provided by engineer. */ +"Chat theme" = "聊天主题"; + /* No comment provided by engineer. */ "Chats" = "聊天"; @@ -758,12 +902,24 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "中文和西班牙文界面"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "在新设备上选择“从另一个设备迁移”并扫描二维码。"; + /* No comment provided by engineer. */ "Choose file" = "选择文件"; /* No comment provided by engineer. */ "Choose from library" = "从库中选择"; +/* No comment provided by engineer. */ +"Chunks deleted" = "已删除的块"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "下载的块"; + +/* No comment provided by engineer. */ +"Chunks uploaded" = "已下载的区块"; + /* swipe action */ "Clear" = "清除"; @@ -779,6 +935,12 @@ /* No comment provided by engineer. */ "Clear verification" = "清除验证"; +/* No comment provided by engineer. */ +"Color chats with the new themes." = "使用新主题为聊天着色。"; + +/* No comment provided by engineer. */ +"Color mode" = "颜色模式"; + /* No comment provided by engineer. */ "colored" = "彩色"; @@ -791,15 +953,27 @@ /* No comment provided by engineer. */ "complete" = "完整的"; +/* No comment provided by engineer. */ +"Completed" = "已完成"; + /* No comment provided by engineer. */ "Configure ICE servers" = "配置 ICE 服务器"; +/* No comment provided by engineer. */ +"Configured %@ servers" = "已配置 %@ 服务器"; + /* No comment provided by engineer. */ "Confirm" = "确认"; +/* No comment provided by engineer. */ +"Confirm contact deletion?" = "确认删除联系人?"; + /* No comment provided by engineer. */ "Confirm database upgrades" = "确认数据库升级"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "确认来自未知服务器的文件。"; + /* No comment provided by engineer. */ "Confirm network settings" = "确认网络设置"; @@ -833,30 +1007,54 @@ /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "连接到 SimpleX Chat 开发者。"; +/* No comment provided by engineer. */ +"Connect to your friends faster." = "更快地与您的朋友联系。"; + /* No comment provided by engineer. */ "Connect to yourself?" = "连接到你自己?"; +/* No comment provided by engineer. */ +"Connect to yourself?\nThis is your own one-time link!" = "与自己建立联系?\n这是您自己的一次性链接!"; + +/* No comment provided by engineer. */ +"Connect to yourself?\nThis is your own SimpleX address!" = "与自己建立联系?\n这是您自己的 SimpleX 地址!"; + +/* No comment provided by engineer. */ +"Connect via contact address" = "通过联系地址连接"; + /* No comment provided by engineer. */ "Connect via link" = "通过链接连接"; /* No comment provided by engineer. */ "Connect via one-time link" = "通过一次性链接连接"; +/* No comment provided by engineer. */ +"Connect with %@" = "与 %@连接"; + /* No comment provided by engineer. */ "connected" = "已连接"; +/* No comment provided by engineer. */ +"Connected" = "已连接"; + /* No comment provided by engineer. */ "Connected desktop" = "已连接的桌面"; /* rcv group event chat item */ "connected directly" = "已直连"; +/* No comment provided by engineer. */ +"Connected servers" = "已连接的服务器"; + /* No comment provided by engineer. */ "Connected to desktop" = "已连接到桌面"; /* No comment provided by engineer. */ "connecting" = "连接中"; +/* No comment provided by engineer. */ +"Connecting" = "正在连接"; + /* No comment provided by engineer. */ "connecting (accepted)" = "连接中(已接受)"; @@ -878,6 +1076,9 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "连接服务器中……(错误:%@)"; +/* No comment provided by engineer. */ +"Connecting to contact, please wait or check later!" = "正在连接到联系人,请稍候或稍后检查!"; + /* No comment provided by engineer. */ "Connecting to desktop" = "正连接到桌面"; @@ -887,6 +1088,9 @@ /* No comment provided by engineer. */ "Connection" = "连接"; +/* No comment provided by engineer. */ +"Connection and servers status." = "连接和服务器状态。"; + /* No comment provided by engineer. */ "Connection error" = "连接错误"; @@ -896,6 +1100,9 @@ /* chat list item title (it should not be shown */ "connection established" = "连接已建立"; +/* No comment provided by engineer. */ +"Connection notifications" = "连接通知"; + /* No comment provided by engineer. */ "Connection request sent!" = "已发送连接请求!"; @@ -905,15 +1112,27 @@ /* No comment provided by engineer. */ "Connection timeout" = "连接超时"; +/* No comment provided by engineer. */ +"Connection with desktop stopped" = "与桌面的连接已停止"; + /* connection information */ "connection:%@" = "连接:%@"; +/* No comment provided by engineer. */ +"Connections" = "连接"; + +/* profile update event chat item */ +"contact %@ changed to %@" = "联系人 %1$@ 已更改为 %2$@"; + /* No comment provided by engineer. */ "Contact allows" = "联系人允许"; /* No comment provided by engineer. */ "Contact already exists" = "联系人已存在"; +/* No comment provided by engineer. */ +"Contact deleted!" = "联系人已删除!"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "联系人具有端到端加密"; @@ -926,12 +1145,18 @@ /* notification */ "Contact is connected" = "联系已连接"; +/* No comment provided by engineer. */ +"Contact is deleted." = "联系人被删除。"; + /* No comment provided by engineer. */ "Contact name" = "联系人姓名"; /* No comment provided by engineer. */ "Contact preferences" = "联系人偏好设置"; +/* No comment provided by engineer. */ +"Contact will be deleted - this cannot be undone!" = "联系人将被删除-这是无法撤消的!"; + /* No comment provided by engineer. */ "Contacts" = "联系人"; @@ -941,17 +1166,26 @@ /* No comment provided by engineer. */ "Continue" = "继续"; +/* No comment provided by engineer. */ +"Conversation deleted!" = "对话已删除!"; + /* No comment provided by engineer. */ "Copy" = "复制"; +/* No comment provided by engineer. */ +"Copy error" = "复制错误"; + /* No comment provided by engineer. */ "Core version: v%@" = "核心版本: v%@"; +/* No comment provided by engineer. */ +"Correct name to %@?" = "将名称更正为 %@?"; + /* No comment provided by engineer. */ "Create" = "创建"; /* No comment provided by engineer. */ -"Create a group using a random profile." = "使用随机身份创建群组"; +"Create a group using a random profile." = "使用随机身份创建群组."; /* No comment provided by engineer. */ "Create an address to let people connect with you." = "创建一个地址,让人们与您联系。"; @@ -986,9 +1220,15 @@ /* No comment provided by engineer. */ "Create your profile" = "创建您的资料"; +/* No comment provided by engineer. */ +"Created" = "已创建"; + /* No comment provided by engineer. */ "Created at" = "创建于"; +/* copied message info */ +"Created at: %@" = "创建于:%@"; + /* No comment provided by engineer. */ "Created on %@" = "创建于 %@"; @@ -1007,6 +1247,9 @@ /* No comment provided by engineer. */ "Current passphrase…" = "现有密码……"; +/* No comment provided by engineer. */ +"Current profile" = "当前配置文件"; + /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "目前支持的最大文件大小为 %@。"; @@ -1016,9 +1259,15 @@ /* No comment provided by engineer. */ "Custom time" = "自定义时间"; +/* No comment provided by engineer. */ +"Customize theme" = "自定义主题"; + /* No comment provided by engineer. */ "Dark" = "深色"; +/* No comment provided by engineer. */ +"Dark mode colors" = "深色模式颜色"; + /* No comment provided by engineer. */ "Database downgrade" = "数据库降级"; @@ -1079,12 +1328,18 @@ /* time unit */ "days" = "天"; +/* No comment provided by engineer. */ +"Debug delivery" = "调试交付"; + /* No comment provided by engineer. */ "Decentralized" = "分散式"; /* message decrypt error item */ "Decryption error" = "解密错误"; +/* No comment provided by engineer. */ +"decryption errors" = "解密错误"; + /* pref value */ "default (%@)" = "默认 (%@)"; @@ -1098,6 +1353,12 @@ swipe action */ "Delete" = "删除"; +/* No comment provided by engineer. */ +"Delete %lld messages of members?" = "删除成员的 %lld 消息?"; + +/* No comment provided by engineer. */ +"Delete %lld messages?" = "删除 %lld 消息?"; + /* No comment provided by engineer. */ "Delete address" = "删除地址"; @@ -1131,6 +1392,9 @@ /* No comment provided by engineer. */ "Delete contact" = "删除联系人"; +/* No comment provided by engineer. */ +"Delete contact?" = "删除联系人?"; + /* No comment provided by engineer. */ "Delete database" = "删除数据库"; @@ -1194,12 +1458,21 @@ /* server test step */ "Delete queue" = "删除队列"; +/* No comment provided by engineer. */ +"Delete up to 20 messages at once." = "一次最多删除 20 条信息。"; + /* No comment provided by engineer. */ "Delete user profile?" = "删除用户资料?"; +/* No comment provided by engineer. */ +"Delete without notification" = "删除而不通知"; + /* deleted chat item */ "deleted" = "已删除"; +/* No comment provided by engineer. */ +"Deleted" = "已删除"; + /* No comment provided by engineer. */ "Deleted at" = "已删除于"; @@ -1212,6 +1485,9 @@ /* rcv group event chat item */ "deleted group" = "已删除群组"; +/* No comment provided by engineer. */ +"Deletion errors" = "删除错误"; + /* No comment provided by engineer. */ "Delivery" = "传送"; @@ -1227,12 +1503,33 @@ /* No comment provided by engineer. */ "Desktop address" = "桌面地址"; +/* No comment provided by engineer. */ +"Desktop app version %@ is not compatible with this app." = "桌面应用程序版本 %@ 与此应用程序不兼容。"; + /* No comment provided by engineer. */ "Desktop devices" = "桌面设备"; +/* No comment provided by engineer. */ +"Destination server address of %@ is incompatible with forwarding server %@ settings." = "目标服务器地址 %@ 与转发服务器 %@ 设置不兼容。"; + +/* snd error text */ +"Destination server error: %@" = "目标服务器错误:%@"; + +/* No comment provided by engineer. */ +"Destination server version of %@ is incompatible with forwarding server %@." = "目标服务器版本 %@ 与转发服务器 %@ 不兼容。"; + +/* No comment provided by engineer. */ +"Detailed statistics" = "详细的统计数据"; + +/* No comment provided by engineer. */ +"Details" = "详细信息"; + /* No comment provided by engineer. */ "Develop" = "开发"; +/* No comment provided by engineer. */ +"Developer options" = "开发者选项"; + /* No comment provided by engineer. */ "Developer tools" = "开发者工具"; @@ -1272,6 +1569,9 @@ /* No comment provided by engineer. */ "disabled" = "关闭"; +/* No comment provided by engineer. */ +"Disabled" = "禁用"; + /* No comment provided by engineer. */ "Disappearing message" = "限时消息"; @@ -1308,6 +1608,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "不给新成员发送历史消息。"; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "请勿直接发送消息,即使您的服务器或目标服务器不支持私有路由。"; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "不要使用私有路由。"; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "请勿使用 SimpleX 进行紧急通话。"; @@ -1326,12 +1632,21 @@ /* chat item action */ "Download" = "下载"; +/* No comment provided by engineer. */ +"Download errors" = "下载错误"; + /* No comment provided by engineer. */ "Download failed" = "下载失败了"; /* server test step */ "Download file" = "下载文件"; +/* No comment provided by engineer. */ +"Downloaded" = "已下载"; + +/* No comment provided by engineer. */ +"Downloaded files" = "下载的文件"; + /* No comment provided by engineer. */ "Downloading archive" = "正在下载存档"; @@ -1344,6 +1659,9 @@ /* integrity error chat item */ "duplicate message" = "重复的消息"; +/* No comment provided by engineer. */ +"duplicates" = "复本"; + /* No comment provided by engineer. */ "Duration" = "时长"; @@ -1401,6 +1719,9 @@ /* enabled status */ "enabled" = "已启用"; +/* No comment provided by engineer. */ +"Enabled" = "已启用"; + /* No comment provided by engineer. */ "Enabled for" = "启用对象"; @@ -1428,6 +1749,9 @@ /* notification */ "Encrypted message or another event" = "加密消息或其他事件"; +/* notification */ +"Encrypted message: app is stopped" = "加密消息:应用程序已停止"; + /* notification */ "Encrypted message: database error" = "加密消息:数据库错误"; @@ -1482,6 +1806,9 @@ /* No comment provided by engineer. */ "Enter correct passphrase." = "输入正确密码。"; +/* No comment provided by engineer. */ +"Enter group name…" = "输入组名称…"; + /* No comment provided by engineer. */ "Enter Passcode" = "输入密码"; @@ -1506,6 +1833,9 @@ /* placeholder */ "Enter welcome message… (optional)" = "输入欢迎消息……(可选)"; +/* No comment provided by engineer. */ +"Enter your name…" = "请输入您的姓名…"; + /* No comment provided by engineer. */ "error" = "错误"; @@ -1533,6 +1863,9 @@ /* No comment provided by engineer. */ "Error changing setting" = "更改设置错误"; +/* No comment provided by engineer. */ +"Error connecting to forwarding server %@. Please try later." = "连接到转发服务器 %@ 时出错。请稍后尝试。"; + /* No comment provided by engineer. */ "Error creating address" = "创建地址错误"; @@ -1590,6 +1923,9 @@ /* No comment provided by engineer. */ "Error exporting chat database" = "导出聊天数据库错误"; +/* No comment provided by engineer. */ +"Error exporting theme: %@" = "导出主题时出错: %@"; + /* No comment provided by engineer. */ "Error importing chat database" = "导入聊天数据库错误"; @@ -1599,12 +1935,24 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "加载 %@ 服务器错误"; +/* No comment provided by engineer. */ +"Error opening chat" = "打开聊天时出错"; + /* No comment provided by engineer. */ "Error receiving file" = "接收文件错误"; +/* No comment provided by engineer. */ +"Error reconnecting server" = "重新连接服务器时出错"; + +/* No comment provided by engineer. */ +"Error reconnecting servers" = "重新连接服务器时出错"; + /* No comment provided by engineer. */ "Error removing member" = "删除成员错误"; +/* No comment provided by engineer. */ +"Error resetting statistics" = "重置统计信息时出错"; + /* No comment provided by engineer. */ "Error saving %@ servers" = "保存 %@ 服务器错误"; @@ -1626,6 +1974,9 @@ /* No comment provided by engineer. */ "Error saving user password" = "保存用户密码时出错"; +/* No comment provided by engineer. */ +"Error scanning code: %@" = "扫描代码时出错:%@"; + /* No comment provided by engineer. */ "Error sending email" = "发送电邮错误"; @@ -1681,6 +2032,9 @@ /* No comment provided by engineer. */ "Error: URL is invalid" = "错误:URL 无效"; +/* No comment provided by engineer. */ +"Errors" = "错误"; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "即使在对话中被禁用。"; @@ -1693,12 +2047,18 @@ /* chat item action */ "Expand" = "展开"; +/* No comment provided by engineer. */ +"expired" = "过期"; + /* No comment provided by engineer. */ "Export database" = "导出数据库"; /* No comment provided by engineer. */ "Export error:" = "导出错误:"; +/* No comment provided by engineer. */ +"Export theme" = "导出主题"; + /* No comment provided by engineer. */ "Exported database archive." = "导出数据库归档。"; @@ -1720,6 +2080,21 @@ /* swipe action */ "Favorite" = "最喜欢"; +/* No comment provided by engineer. */ +"File error" = "文件错误"; + +/* file error text */ +"File not found - most likely file was deleted or cancelled." = "找不到文件 - 很可能文件已被删除或取消。"; + +/* file error text */ +"File server error: %@" = "文件服务器错误:%@"; + +/* No comment provided by engineer. */ +"File status" = "文件状态"; + +/* copied message info */ +"File status: %@" = "文件状态:%@"; + /* No comment provided by engineer. */ "File will be deleted from servers." = "文件将从服务器中删除。"; @@ -1732,6 +2107,9 @@ /* No comment provided by engineer. */ "File: %@" = "文件:%@"; +/* No comment provided by engineer. */ +"Files" = "文件"; + /* No comment provided by engineer. */ "Files & media" = "文件和媒体"; @@ -1754,7 +2132,7 @@ "Finalize migration" = "完成迁移"; /* No comment provided by engineer. */ -"Finalize migration on another device." = "在另一部设备上完成迁移"; +"Finalize migration on another device." = "在另一部设备上完成迁移."; /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "终于我们有它们了! 🚀"; @@ -1798,6 +2176,21 @@ /* No comment provided by engineer. */ "Forwarded from" = "转发自"; +/* No comment provided by engineer. */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "转发服务器 %@ 无法连接到目标服务器 %@。请稍后尝试。"; + +/* No comment provided by engineer. */ +"Forwarding server address is incompatible with network settings: %@." = "转发服务器地址与网络设置不兼容:%@。"; + +/* No comment provided by engineer. */ +"Forwarding server version is incompatible with network settings: %@." = "转发服务器版本与网络设置不兼容:%@。"; + +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "转发服务器: %1$@\n目标服务器错误: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "转发服务器: %1$@\n错误: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "找到了桌面"; @@ -1810,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "全名(可选)"; -/* No comment provided by engineer. */ -"Full name:" = "全名:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "完全去中心化 - 仅对成员可见。"; @@ -1825,9 +2215,18 @@ /* No comment provided by engineer. */ "GIFs and stickers" = "GIF 和贴纸"; +/* message preview */ +"Good afternoon!" = "下午好!"; + +/* message preview */ +"Good morning!" = "早上好!"; + /* No comment provided by engineer. */ "Group" = "群组"; +/* No comment provided by engineer. */ +"Group already exists" = "群组已存在"; + /* No comment provided by engineer. */ "Group already exists!" = "群已存在!"; @@ -1862,7 +2261,7 @@ "Group members can add message reactions." = "群组成员可以添加信息回应。"; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "群组成员可以不可撤回地删除已发送的消息。"; +"Group members can irreversibly delete sent messages. (24 hours)" = "群组成员可以不可撤回地删除已发送的消息"; /* No comment provided by engineer. */ "Group members can send direct messages." = "群组成员可以私信。"; @@ -1954,6 +2353,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "如何使用您的服务器"; +/* No comment provided by engineer. */ +"Hungarian interface" = "匈牙利语界面"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE 服务器(每行一个)"; @@ -1996,6 +2398,9 @@ /* No comment provided by engineer. */ "Import failed" = "导入失败了"; +/* No comment provided by engineer. */ +"Import theme" = "导入主题"; + /* No comment provided by engineer. */ "Importing archive" = "正在导入存档"; @@ -2017,6 +2422,9 @@ /* No comment provided by engineer. */ "In-call sounds" = "通话声音"; +/* No comment provided by engineer. */ +"inactive" = "无效"; + /* No comment provided by engineer. */ "Incognito" = "隐身聊天"; @@ -2080,6 +2488,9 @@ /* No comment provided by engineer. */ "Interface" = "界面"; +/* No comment provided by engineer. */ +"Interface colors" = "界面颜色"; + /* invalid chat data */ "invalid chat" = "无效聊天"; @@ -2107,6 +2518,9 @@ /* No comment provided by engineer. */ "Invalid QR code" = "无效的二维码"; +/* No comment provided by engineer. */ +"Invalid response" = "无效的响应"; + /* No comment provided by engineer. */ "Invalid server address!" = "无效的服务器地址!"; @@ -2119,6 +2533,9 @@ /* group name */ "invitation to group %@" = "邀请您加入群组 %@"; +/* No comment provided by engineer. */ +"invite" = "邀请"; + /* No comment provided by engineer. */ "Invite friends" = "邀请朋友"; @@ -2164,6 +2581,9 @@ /* No comment provided by engineer. */ "It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "它可能在以下情况发生:\n1. 消息在发送客户端 2 天后或在服务器上 30 天后过期。\n2. 消息解密失败,因为您或您的联系人使用了旧的数据库备份。\n3.连接被破坏。"; +/* No comment provided by engineer. */ +"It protects your IP address and connections." = "它可以保护您的 IP 地址和连接。"; + /* No comment provided by engineer. */ "It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "您似乎已经通过此链接连接。如果不是这样,则有一个错误 (%@)。"; @@ -2194,12 +2614,24 @@ /* No comment provided by engineer. */ "Join incognito" = "加入隐身聊天"; +/* No comment provided by engineer. */ +"Join with current profile" = "使用当前档案加入"; + +/* No comment provided by engineer. */ +"Join your group?\nThis is your link for group %@!" = "加入您的群组?\n这是您组 %@ 的链接!"; + /* No comment provided by engineer. */ "Joining group" = "加入群组中"; /* No comment provided by engineer. */ "Keep" = "保留"; +/* No comment provided by engineer. */ +"Keep conversation" = "保持对话"; + +/* No comment provided by engineer. */ +"Keep the app open to use it from desktop" = "保持应用程序打开状态以从桌面使用它"; + /* No comment provided by engineer. */ "Keep unused invitation?" = "保留未使用的邀请吗?"; @@ -2308,15 +2740,27 @@ /* No comment provided by engineer. */ "Max 30 seconds, received instantly." = "最长30秒,立即接收。"; +/* No comment provided by engineer. */ +"Media & file servers" = "Media & file servers"; + +/* blur media */ +"Medium" = "中等"; + /* member role */ "member" = "成员"; /* No comment provided by engineer. */ "Member" = "成员"; +/* profile update event chat item */ +"member %@ changed to %@" = "成员 %1$@ 已更改为 %2$@"; + /* rcv group event chat item */ "member connected" = "已连接"; +/* item status text */ +"Member inactive" = "成员不活跃"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "成员角色将更改为 \"%@\"。所有群成员将收到通知。"; @@ -2326,15 +2770,33 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "成员将被移出群组——此操作无法撤消!"; +/* No comment provided by engineer. */ +"Menus" = "菜单"; + +/* No comment provided by engineer. */ +"message" = "消息"; + /* item status text */ "Message delivery error" = "消息传递错误"; /* No comment provided by engineer. */ "Message delivery receipts!" = "消息送达回执!"; +/* item status text */ +"Message delivery warning" = "消息传递警告"; + /* No comment provided by engineer. */ "Message draft" = "消息草稿"; +/* item status text */ +"Message forwarded" = "消息已转发"; + +/* item status description */ +"Message may be delivered later if member becomes active." = "如果 member 变为活动状态,则稍后可能会发送消息。"; + +/* No comment provided by engineer. */ +"Message queue info" = "消息队列信息"; + /* chat feature */ "Message reactions" = "消息回应"; @@ -2347,9 +2809,21 @@ /* notification */ "message received" = "消息已收到"; +/* No comment provided by engineer. */ +"Message reception" = "消息接收"; + +/* No comment provided by engineer. */ +"Message servers" = "消息服务器"; + /* No comment provided by engineer. */ "Message source remains private." = "消息来源保持私密。"; +/* No comment provided by engineer. */ +"Message status" = "消息状态"; + +/* copied message info */ +"Message status: %@" = "消息状态:%@"; + /* No comment provided by engineer. */ "Message text" = "消息正文"; @@ -2362,6 +2836,21 @@ /* No comment provided by engineer. */ "Messages & files" = "消息"; +/* No comment provided by engineer. */ +"Messages from %@ will be shown!" = "将显示来自 %@ 的消息!"; + +/* No comment provided by engineer. */ +"Messages received" = "收到的消息"; + +/* No comment provided by engineer. */ +"Messages sent" = "已发送的消息"; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "消息、文件和通话受到 **端到端加密** 的保护,具有完全正向保密、否认和闯入恢复。"; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "消息、文件和通话受到 **抗量子 e2e 加密** 的保护,具有完全正向保密、否认和闯入恢复。"; + /* No comment provided by engineer. */ "Migrate device" = "迁移设备"; @@ -2434,6 +2923,9 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "多个聊天资料"; +/* No comment provided by engineer. */ +"mute" = "静音"; + /* swipe action */ "Mute" = "静音"; @@ -2449,6 +2941,9 @@ /* No comment provided by engineer. */ "Network connection" = "网络连接"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "网络问题 - 消息在多次尝试发送后过期。"; + /* No comment provided by engineer. */ "Network management" = "网络管理"; @@ -2464,6 +2959,9 @@ /* No comment provided by engineer. */ "New chat" = "新聊天"; +/* No comment provided by engineer. */ +"New chat experience 🎉" = "新的聊天体验 🎉"; + /* notification */ "New contact request" = "新联系人请求"; @@ -2482,6 +2980,9 @@ /* No comment provided by engineer. */ "New in %@" = "%@ 的新内容"; +/* No comment provided by engineer. */ +"New media options" = "新媒体选项"; + /* No comment provided by engineer. */ "New member role" = "新成员角色"; @@ -2518,6 +3019,9 @@ /* No comment provided by engineer. */ "No device token!" = "无设备令牌!"; +/* item status description */ +"No direct connection yet, message is forwarded by admin." = "还没有直接连接,消息由管理员转发。"; + /* No comment provided by engineer. */ "no e2e encryption" = "无端到端加密"; @@ -2530,6 +3034,9 @@ /* No comment provided by engineer. */ "No history" = "无历史记录"; +/* No comment provided by engineer. */ +"No info, try to reload" = "无信息,尝试重新加载"; + /* No comment provided by engineer. */ "No network connection" = "无网络连接"; @@ -2545,6 +3052,9 @@ /* No comment provided by engineer. */ "Not compatible!" = "不兼容!"; +/* No comment provided by engineer. */ +"Nothing selected" = "未选中任何内容"; + /* No comment provided by engineer. */ "Notifications" = "通知"; @@ -2590,10 +3100,10 @@ "One-time invitation link" = "一次性邀请链接"; /* No comment provided by engineer. */ -"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion 主机将用于连接。需要启用 VPN。"; +"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion 主机将是连接所必需的。\n需要兼容的 VPN。"; /* No comment provided by engineer. */ -"Onion hosts will be used when available.\nRequires compatible VPN." = "当可用时,将使用 Onion 主机。需要启用 VPN。"; +"Onion hosts will be used when available.\nRequires compatible VPN." = "如果可用,将使用洋葱主机。\n需要兼容的 VPN。"; /* No comment provided by engineer. */ "Onion hosts will not be used." = "将不会使用 Onion 主机。"; @@ -2601,6 +3111,9 @@ /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "只有客户端设备存储用户资料、联系人、群组和**双层端到端加密**发送的消息。"; +/* No comment provided by engineer. */ +"Only delete conversation" = "仅删除对话"; + /* No comment provided by engineer. */ "Only group owners can change group preferences." = "只有群主可以改变群组偏好设置。"; @@ -2614,7 +3127,7 @@ "Only you can add message reactions." = "只有您可以添加消息回应。"; /* No comment provided by engineer. */ -"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "只有您可以不可撤回地删除消息(您的联系人可以将它们标记为删除)。"; +"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "只有您可以不可撤回地删除消息(您的联系人可以将它们标记为删除)"; /* No comment provided by engineer. */ "Only you can make calls." = "只有您可以拨打电话。"; @@ -2629,7 +3142,7 @@ "Only your contact can add message reactions." = "只有您的联系人可以添加消息回应。"; /* No comment provided by engineer. */ -"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "只有您的联系人才能不可撤回地删除消息(您可以将它们标记为删除)。"; +"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "只有您的联系人才能不可撤回地删除消息(您可以将它们标记为删除)"; /* No comment provided by engineer. */ "Only your contact can make calls." = "只有您的联系人可以拨打电话。"; @@ -2652,6 +3165,12 @@ /* No comment provided by engineer. */ "Open group" = "打开群"; +/* authentication reason */ +"Open migration to another device" = "打开迁移到另一台设备"; + +/* No comment provided by engineer. */ +"Open server settings" = "打开服务器设置"; + /* No comment provided by engineer. */ "Open Settings" = "打开设置"; @@ -2661,6 +3180,9 @@ /* No comment provided by engineer. */ "Open-source protocol and code – anybody can run the servers." = "开源协议和代码——任何人都可以运行服务器。"; +/* No comment provided by engineer. */ +"Opening app…" = "正在打开应用程序…"; + /* No comment provided by engineer. */ "Or paste archive link" = "或粘贴存档链接"; @@ -2673,9 +3195,18 @@ /* No comment provided by engineer. */ "Or show this code" = "或者显示此码"; +/* No comment provided by engineer. */ +"other" = "其他"; + /* No comment provided by engineer. */ "Other" = "其他"; +/* No comment provided by engineer. */ +"Other %@ servers" = "其他 %@ 服务器"; + +/* No comment provided by engineer. */ +"other errors" = "其他错误"; + /* member role */ "owner" = "群主"; @@ -2700,6 +3231,9 @@ /* No comment provided by engineer. */ "Password to show" = "显示密码"; +/* past/unknown group member */ +"Past member %@" = "前任成员 %@"; + /* No comment provided by engineer. */ "Paste desktop address" = "粘贴桌面地址"; @@ -2715,6 +3249,9 @@ /* No comment provided by engineer. */ "peer-to-peer" = "点对点"; +/* No comment provided by engineer. */ +"Pending" = "待定"; + /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "人们只能通过您共享的链接与您建立联系。"; @@ -2733,9 +3270,18 @@ /* No comment provided by engineer. */ "PING interval" = "PING 间隔"; +/* No comment provided by engineer. */ +"Play from the chat list." = "从聊天列表播放。"; + +/* No comment provided by engineer. */ +"Please ask your contact to enable calls." = "请要求您的联系人开通通话功能。"; + /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "请让您的联系人启用发送语音消息。"; +/* No comment provided by engineer. */ +"Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "请检查移动设备和桌面是否连接到同一本地网络,以及桌面防火墙是否允许连接。\n请与开发人员分享任何其他问题。"; + /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "请检查您使用的链接是否正确,或者让您的联系人给您发送另一个链接。"; @@ -2748,6 +3294,9 @@ /* No comment provided by engineer. */ "Please confirm that network settings are correct for this device." = "请确认网络设置对此这台设备正确无误。"; +/* No comment provided by engineer. */ +"Please contact developers.\nError: %@" = "请联系开发人员。\n错误:%@"; + /* No comment provided by engineer. */ "Please contact group admin." = "请联系群组管理员。"; @@ -2790,6 +3339,9 @@ /* No comment provided by engineer. */ "Preview" = "预览"; +/* No comment provided by engineer. */ +"Previously connected servers" = "以前连接的服务器"; + /* No comment provided by engineer. */ "Privacy & security" = "隐私和安全"; @@ -2799,9 +3351,21 @@ /* No comment provided by engineer. */ "Private filenames" = "私密文件名"; +/* No comment provided by engineer. */ +"Private message routing" = "私有消息路由"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "私有消息路由 🚀"; + /* name of notes to self */ "Private notes" = "私密笔记"; +/* No comment provided by engineer. */ +"Private routing" = "专用路由"; + +/* No comment provided by engineer. */ +"Private routing error" = "专用路由错误"; + /* No comment provided by engineer. */ "Profile and server connections" = "资料和服务器连接"; @@ -2812,10 +3376,10 @@ "Profile images" = "个人资料图"; /* No comment provided by engineer. */ -"Profile name:" = "显示名:"; +"Profile password" = "个人资料密码"; /* No comment provided by engineer. */ -"Profile password" = "个人资料密码"; +"Profile theme" = "个人资料主题"; /* No comment provided by engineer. */ "Profile update will be sent to your contacts." = "个人资料更新将被发送给您的联系人。"; @@ -2841,24 +3405,42 @@ /* No comment provided by engineer. */ "Prohibit sending files and media." = "禁止发送文件和媒体。"; +/* No comment provided by engineer. */ +"Prohibit sending SimpleX links." = "禁止发送 SimpleX 链接。"; + /* No comment provided by engineer. */ "Prohibit sending voice messages." = "禁止发送语音消息。"; /* No comment provided by engineer. */ "Protect app screen" = "保护应用程序屏幕"; +/* No comment provided by engineer. */ +"Protect IP address" = "保护 IP 地址"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "使用密码保护您的聊天资料!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "保护您的 IP 地址免受联系人选择的消息中继的攻击。\n在*网络和服务器*设置中启用。"; + /* No comment provided by engineer. */ "Protocol timeout" = "协议超时"; /* No comment provided by engineer. */ "Protocol timeout per KB" = "每 KB 协议超时"; +/* No comment provided by engineer. */ +"Proxied" = "代理"; + +/* No comment provided by engineer. */ +"Proxied servers" = "代理服务器"; + /* No comment provided by engineer. */ "Push notifications" = "推送通知"; +/* No comment provided by engineer. */ +"Push server" = "推送服务器"; + /* chat item text */ "quantum resistant e2e encryption" = "抗量子端到端加密"; @@ -2868,6 +3450,9 @@ /* No comment provided by engineer. */ "Rate the app" = "评价此应用程序"; +/* No comment provided by engineer. */ +"Reachable chat toolbar" = "可访问的聊天工具栏"; + /* chat item menu */ "React…" = "回应…"; @@ -2895,6 +3480,9 @@ /* No comment provided by engineer. */ "Receipts are disabled" = "回执已禁用"; +/* No comment provided by engineer. */ +"Receive errors" = "接收错误"; + /* No comment provided by engineer. */ "received answer…" = "已收到回复……"; @@ -2913,6 +3501,15 @@ /* message info title */ "Received message" = "收到的信息"; +/* No comment provided by engineer. */ +"Received messages" = "收到的消息"; + +/* No comment provided by engineer. */ +"Received reply" = "已收到回复"; + +/* No comment provided by engineer. */ +"Received total" = "接收总数"; + /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "接收地址将变更到不同的服务器。地址更改将在发件人上线后完成。"; @@ -2922,15 +3519,33 @@ /* No comment provided by engineer. */ "Receiving via" = "接收通过"; +/* No comment provided by engineer. */ +"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "最近的历史记录和改进的 [目录机器人](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; + /* No comment provided by engineer. */ "Recipient(s) can't see who this message is from." = "收件人看不到这条消息来自何人。"; /* No comment provided by engineer. */ "Recipients see updates as you type them." = "对方会在您键入时看到更新。"; +/* No comment provided by engineer. */ +"Reconnect" = "重新连接"; + /* No comment provided by engineer. */ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "重新连接所有已连接的服务器以强制发送信息。这会耗费更多流量。"; +/* No comment provided by engineer. */ +"Reconnect all servers" = "重新连接所有服务器"; + +/* No comment provided by engineer. */ +"Reconnect all servers?" = "重新连接所有服务器?"; + +/* No comment provided by engineer. */ +"Reconnect server to force message delivery. It uses additional traffic." = "重新连接服务器以强制发送信息。它使用额外的流量。"; + +/* No comment provided by engineer. */ +"Reconnect server?" = "重新连接服务器?"; + /* No comment provided by engineer. */ "Reconnect servers?" = "是否重新连接服务器?"; @@ -2965,6 +3580,9 @@ /* No comment provided by engineer. */ "Remove" = "移除"; +/* No comment provided by engineer. */ +"Remove image" = "移除图片"; + /* No comment provided by engineer. */ "Remove member" = "删除成员"; @@ -3022,12 +3640,27 @@ /* No comment provided by engineer. */ "Reset" = "重置"; +/* No comment provided by engineer. */ +"Reset all hints" = "重置所有提示"; + +/* No comment provided by engineer. */ +"Reset all statistics" = "重置所有统计信息"; + +/* No comment provided by engineer. */ +"Reset all statistics?" = "重置所有统计信息?"; + /* No comment provided by engineer. */ "Reset colors" = "重置颜色"; +/* No comment provided by engineer. */ +"Reset to app theme" = "重置为应用程序主题"; + /* No comment provided by engineer. */ "Reset to defaults" = "重置为默认"; +/* No comment provided by engineer. */ +"Reset to user theme" = "重置为用户主题"; + /* No comment provided by engineer. */ "Restart the app to create a new chat profile" = "重新启动应用程序以创建新的聊天资料"; @@ -3068,12 +3701,16 @@ "Run chat" = "运行聊天程序"; /* No comment provided by engineer. */ -"Safer groups" = "更安全的群组"; - -/* chat item action */ -"Save" = "保存"; +"Safely receive files" = "安全接收文件"; /* No comment provided by engineer. */ +"Safer groups" = "更安全的群组"; + +/* alert button + chat item action */ +"Save" = "保存"; + +/* alert button */ "Save (and notify contacts)" = "保存(并通知联系人)"; /* No comment provided by engineer. */ @@ -3082,15 +3719,15 @@ /* No comment provided by engineer. */ "Save and notify group members" = "保存并通知群组成员"; +/* No comment provided by engineer. */ +"Save and reconnect" = "保存并重新连接"; + /* No comment provided by engineer. */ "Save and update group profile" = "保存和更新组配置文件"; /* No comment provided by engineer. */ "Save archive" = "保存存档"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "保存自动接受设置"; - /* No comment provided by engineer. */ "Save group profile" = "保存群组资料"; @@ -3112,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "保存服务器?"; -/* No comment provided by engineer. */ -"Save settings?" = "保存设置?"; - /* No comment provided by engineer. */ "Save welcome message?" = "保存欢迎信息?"; @@ -3127,12 +3761,21 @@ /* No comment provided by engineer. */ "Saved from" = "保存自"; +/* No comment provided by engineer. */ +"saved from %@" = "保存自 %@"; + /* message info title */ "Saved message" = "已保存的消息"; /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "已保存的WebRTC ICE服务器将被删除"; +/* No comment provided by engineer. */ +"Scale" = "规模"; + +/* No comment provided by engineer. */ +"Scan / Paste link" = "扫描 / 粘贴链接"; + /* No comment provided by engineer. */ "Scan code" = "扫码"; @@ -3148,6 +3791,9 @@ /* No comment provided by engineer. */ "Scan server QR code" = "扫描服务器二维码"; +/* No comment provided by engineer. */ +"search" = "搜索"; + /* No comment provided by engineer. */ "Search" = "搜索"; @@ -3160,6 +3806,9 @@ /* network option */ "sec" = "秒"; +/* No comment provided by engineer. */ +"Secondary" = "二级"; + /* time unit */ "seconds" = "秒"; @@ -3169,6 +3818,9 @@ /* server test step */ "Secure queue" = "保护队列"; +/* No comment provided by engineer. */ +"Secured" = "担保"; + /* No comment provided by engineer. */ "Security assessment" = "安全评估"; @@ -3181,6 +3833,12 @@ /* chat item action */ "Select" = "选择"; +/* No comment provided by engineer. */ +"Selected %lld" = "选定的 %lld"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "选定的聊天首选项禁止此消息。"; + /* No comment provided by engineer. */ "Self-destruct" = "自毁"; @@ -3211,12 +3869,24 @@ /* No comment provided by engineer. */ "Send disappearing message" = "发送限时消息中"; +/* No comment provided by engineer. */ +"Send errors" = "发送错误"; + /* No comment provided by engineer. */ "Send link previews" = "发送链接预览"; /* No comment provided by engineer. */ "Send live message" = "发送实时消息"; +/* No comment provided by engineer. */ +"Send message to enable calls." = "发送消息以启用呼叫。"; + +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "当 IP 地址受到保护并且您或目标服务器不支持私有路由时,直接发送消息。"; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "当您或目标服务器不支持私有路由时,直接发送消息。"; + /* No comment provided by engineer. */ "Send notifications" = "发送通知"; @@ -3271,15 +3941,42 @@ /* copied message info */ "Sent at: %@" = "已发送于:%@"; +/* No comment provided by engineer. */ +"Sent directly" = "直接发送"; + /* notification */ "Sent file event" = "已发送文件项目"; /* message info title */ "Sent message" = "已发信息"; +/* No comment provided by engineer. */ +"Sent messages" = "已发送的消息"; + /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "已发送的消息将在设定的时间后被删除。"; +/* No comment provided by engineer. */ +"Sent reply" = "已发送回复"; + +/* No comment provided by engineer. */ +"Sent total" = "发送总数"; + +/* No comment provided by engineer. */ +"Sent via proxy" = "通过代理发送"; + +/* No comment provided by engineer. */ +"Server address" = "服务器地址"; + +/* No comment provided by engineer. */ +"Server address is incompatible with network settings: %@." = "服务器地址与网络设置不兼容:%@。"; + +/* srv error text. */ +"Server address is incompatible with network settings." = "服务器地址与网络设置不兼容。"; + +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "服务器队列信息: %1$@\n\n上次收到的消息: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "服务器需要授权才能创建队列,检查密码"; @@ -3289,9 +3986,24 @@ /* No comment provided by engineer. */ "Server test failed!" = "服务器测试失败!"; +/* No comment provided by engineer. */ +"Server type" = "服务器类型"; + +/* srv error text */ +"Server version is incompatible with network settings." = "服务器版本与网络设置不兼容。"; + +/* No comment provided by engineer. */ +"Server version is incompatible with your app: %@." = "服务器版本与你的应用程序不兼容:%@。"; + /* No comment provided by engineer. */ "Servers" = "服务器"; +/* No comment provided by engineer. */ +"Servers info" = "服务器信息"; + +/* No comment provided by engineer. */ +"Servers statistics will be reset - this cannot be undone!" = "服务器统计信息将被重置 - 此操作无法撤消!"; + /* No comment provided by engineer. */ "Session code" = "会话码"; @@ -3301,6 +4013,9 @@ /* No comment provided by engineer. */ "Set contact name…" = "设置联系人姓名……"; +/* No comment provided by engineer. */ +"Set default theme" = "设置默认主题"; + /* No comment provided by engineer. */ "Set group preferences" = "设置群组偏好设置"; @@ -3346,15 +4061,24 @@ /* No comment provided by engineer. */ "Share address with contacts?" = "与联系人分享地址?"; +/* No comment provided by engineer. */ +"Share from other apps." = "从其他应用程序共享。"; + /* No comment provided by engineer. */ "Share link" = "分享链接"; /* No comment provided by engineer. */ "Share this 1-time invite link" = "分享此一次性邀请链接"; +/* No comment provided by engineer. */ +"Share to SimpleX" = "分享到 SimpleX"; + /* No comment provided by engineer. */ "Share with contacts" = "与联系人分享"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "显示 → 通过专用路由发送的信息."; + /* No comment provided by engineer. */ "Show calls in phone history" = "在电话历史记录中显示通话"; @@ -3364,6 +4088,12 @@ /* No comment provided by engineer. */ "Show last messages" = "显示最近的消息"; +/* No comment provided by engineer. */ +"Show message status" = "显示消息状态"; + +/* No comment provided by engineer. */ +"Show percentage" = "显示百分比"; + /* No comment provided by engineer. */ "Show preview" = "显示预览"; @@ -3373,6 +4103,9 @@ /* No comment provided by engineer. */ "Show:" = "显示:"; +/* No comment provided by engineer. */ +"SimpleX" = "SimpleX"; + /* No comment provided by engineer. */ "SimpleX address" = "SimpleX 地址"; @@ -3418,6 +4151,9 @@ /* No comment provided by engineer. */ "Simplified incognito mode" = "简化的隐身模式"; +/* No comment provided by engineer. */ +"Size" = "大小"; + /* No comment provided by engineer. */ "Skip" = "跳过"; @@ -3427,14 +4163,26 @@ /* No comment provided by engineer. */ "Small groups (max 20)" = "小群组(最多 20 人)"; +/* No comment provided by engineer. */ +"SMP server" = "SMP 服务器"; + +/* blur media */ +"Soft" = "软"; + +/* No comment provided by engineer. */ +"Some file(s) were not exported:" = "某些文件未导出:"; + /* No comment provided by engineer. */ "Some non-fatal errors occurred during import - you may see Chat console for more details." = "导入过程中发生了一些非致命错误——您可以查看聊天控制台了解更多详细信息。"; +/* No comment provided by engineer. */ +"Some non-fatal errors occurred during import:" = "导入过程中出现一些非致命错误:"; + /* notification title */ "Somebody" = "某人"; /* No comment provided by engineer. */ -"Square, circle, or anything in between." = "方形、圆形、或两者之间的任意形状"; +"Square, circle, or anything in between." = "方形、圆形、或两者之间的任意形状."; /* chat item text */ "standard end-to-end encryption" = "标准端到端加密"; @@ -3448,9 +4196,15 @@ /* No comment provided by engineer. */ "Start migration" = "开始迁移"; +/* No comment provided by engineer. */ +"Starting from %@." = "从 %@ 开始。"; + /* No comment provided by engineer. */ "starting…" = "启动中……"; +/* No comment provided by engineer. */ +"Statistics" = "统计"; + /* No comment provided by engineer. */ "Stop" = "停止"; @@ -3490,9 +4244,21 @@ /* No comment provided by engineer. */ "strike" = "删去"; +/* blur media */ +"Strong" = "加粗"; + /* No comment provided by engineer. */ "Submit" = "提交"; +/* No comment provided by engineer. */ +"Subscribed" = "已订阅"; + +/* No comment provided by engineer. */ +"Subscription errors" = "订阅错误"; + +/* No comment provided by engineer. */ +"Subscriptions ignored" = "忽略订阅"; + /* No comment provided by engineer. */ "Support SimpleX Chat" = "支持 SimpleX Chat"; @@ -3526,6 +4292,9 @@ /* No comment provided by engineer. */ "Tap to scan" = "轻按扫描"; +/* No comment provided by engineer. */ +"TCP connection" = "TCP 连接"; + /* No comment provided by engineer. */ "TCP connection timeout" = "TCP 连接超时"; @@ -3538,6 +4307,9 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; +/* No comment provided by engineer. */ +"Temporary file error" = "临时文件错误"; + /* server test failure */ "Test failed at step %@." = "在步骤 %@ 上测试失败。"; @@ -3565,6 +4337,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。"; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "该应用程序将要求确认从未知文件服务器(.onion 除外)下载。"; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "更改数据库密码的尝试未完成。"; @@ -3595,6 +4370,12 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "该消息将对所有成员标记为已被管理员移除。"; +/* No comment provided by engineer. */ +"The messages will be deleted for all members." = "将删除所有成员的消息。"; + +/* No comment provided by engineer. */ +"The messages will be marked as moderated for all members." = "对于所有成员,这些消息将被标记为已审核。"; + /* No comment provided by engineer. */ "The next generation of private messaging" = "下一代私密通讯软件"; @@ -3616,6 +4397,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "您粘贴的文本不是 SimpleX 链接。"; +/* No comment provided by engineer. */ +"Themes" = "主题"; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "这些设置适用于您当前的配置文件 **%@**。"; @@ -3658,9 +4442,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "这是你自己的 SimpleX 地址!"; +/* No comment provided by engineer. */ +"This link was used with another mobile device, please create a new link on the desktop." = "此链接已在其他移动设备上使用,请在桌面上创建新链接。"; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "此设置适用于您当前聊天资料 **%@** 中的消息。"; +/* No comment provided by engineer. */ +"Title" = "标题"; + /* No comment provided by engineer. */ "To ask any questions and to receive updates:" = "要提出任何问题并接收更新,请:"; @@ -3682,6 +4472,9 @@ /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "为保护您的信息,请打开 SimpleX 锁定。\n在启用此功能之前,系统将提示您完成身份验证。"; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "为了保护您的 IP 地址,私有路由使用您的 SMP 服务器来传递邮件。"; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "请授权使用麦克风以录制语音消息。"; @@ -3694,18 +4487,33 @@ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。"; +/* No comment provided by engineer. */ +"Toggle chat list:" = "切换聊天列表:"; + /* No comment provided by engineer. */ "Toggle incognito when connecting." = "在连接时切换隐身模式。"; +/* No comment provided by engineer. */ +"Toolbar opacity" = "工具栏不透明度"; + +/* No comment provided by engineer. */ +"Total" = "共计"; + /* No comment provided by engineer. */ "Transport isolation" = "传输隔离"; +/* No comment provided by engineer. */ +"Transport sessions" = "传输会话"; + /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact (error: %@)." = "正在尝试连接到用于从该联系人接收消息的服务器(错误:%@)。"; /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact." = "正在尝试连接到用于从该联系人接收消息的服务器。"; +/* No comment provided by engineer. */ +"Turkish interface" = "土耳其语界面"; + /* No comment provided by engineer. */ "Turn off" = "关闭"; @@ -3730,6 +4538,9 @@ /* No comment provided by engineer. */ "Unblock member?" = "解封成员吗?"; +/* rcv group event chat item */ +"unblocked %@" = "未阻止 %@"; + /* No comment provided by engineer. */ "Unexpected migration state" = "未预料的迁移状态"; @@ -3760,6 +4571,12 @@ /* No comment provided by engineer. */ "Unknown error" = "未知错误"; +/* No comment provided by engineer. */ +"unknown servers" = "未知服务器"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "未知服务器!"; + /* No comment provided by engineer. */ "unknown status" = "未知状态"; @@ -3781,9 +4598,15 @@ /* authentication reason */ "Unlock app" = "解锁应用程序"; +/* No comment provided by engineer. */ +"unmute" = "取消静音"; + /* swipe action */ "Unmute" = "取消静音"; +/* No comment provided by engineer. */ +"unprotected" = "未受保护"; + /* swipe action */ "Unread" = "未读"; @@ -3799,6 +4622,9 @@ /* No comment provided by engineer. */ "Update network settings?" = "更新网络设置?"; +/* No comment provided by engineer. */ +"Update settings?" = "更新设置?"; + /* rcv group event chat item */ "updated group profile" = "已更新的群组资料"; @@ -3811,12 +4637,21 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "升级并打开聊天"; +/* No comment provided by engineer. */ +"Upload errors" = "上传错误"; + /* No comment provided by engineer. */ "Upload failed" = "上传失败了"; /* server test step */ "Upload file" = "上传文件"; +/* No comment provided by engineer. */ +"Uploaded" = "已上传"; + +/* No comment provided by engineer. */ +"Uploaded files" = "已上传的文件"; + /* No comment provided by engineer. */ "Uploading archive" = "正在上传存档"; @@ -3841,6 +4676,15 @@ /* No comment provided by engineer. */ "Use new incognito profile" = "使用新的隐身配置文件"; +/* No comment provided by engineer. */ +"Use only local notifications?" = "仅使用本地通知?"; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "当 IP 地址不受保护时,对未知服务器使用私有路由。"; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "对未知服务器使用私有路由。"; + /* No comment provided by engineer. */ "Use server" = "使用服务器"; @@ -3848,14 +4692,23 @@ "Use SimpleX Chat servers?" = "使用 SimpleX Chat 服务器?"; /* No comment provided by engineer. */ -"Use the app while in the call." = "通话时使用本应用"; +"Use the app while in the call." = "通话时使用本应用."; + +/* No comment provided by engineer. */ +"Use the app with one hand." = "用一只手使用应用程序。"; /* No comment provided by engineer. */ "User profile" = "用户资料"; +/* No comment provided by engineer. */ +"User selection" = "用户选择"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "使用 SimpleX Chat 服务器。"; +/* No comment provided by engineer. */ +"v%@" = "v%@"; + /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; @@ -3898,6 +4751,9 @@ /* No comment provided by engineer. */ "Via secure quantum resistant protocol." = "通过安全的、抗量子计算机破解的协议。"; +/* No comment provided by engineer. */ +"video" = "视频"; + /* No comment provided by engineer. */ "Video call" = "视频通话"; @@ -3943,6 +4799,9 @@ /* No comment provided by engineer. */ "waiting for confirmation…" = "等待确认中……"; +/* No comment provided by engineer. */ +"Waiting for desktop..." = "正在等待桌面..."; + /* No comment provided by engineer. */ "Waiting for file" = "等待文件中"; @@ -3952,11 +4811,17 @@ /* No comment provided by engineer. */ "Waiting for video" = "等待视频中"; +/* No comment provided by engineer. */ +"Wallpaper accent" = "壁纸装饰"; + +/* No comment provided by engineer. */ +"Wallpaper background" = "壁纸背景"; + /* No comment provided by engineer. */ "wants to connect to you!" = "想要与您连接!"; /* No comment provided by engineer. */ -"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败。"; +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败"; /* No comment provided by engineer. */ "Warning: you may lose some data!" = "警告:您可能会丢失部分数据!"; @@ -3985,6 +4850,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "连接音频和视频通话时。"; +/* No comment provided by engineer. */ +"when IP hidden" = "当 IP 隐藏时"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "当人们请求连接时,您可以接受或拒绝它。"; @@ -4009,12 +4877,27 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "降低了电量使用。"; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "如果没有 Tor 或 VPN,您的 IP 地址将对文件服务器可见。"; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "如果没有 Tor 或 VPN,您的 IP 地址将对以下 XFTP 中继可见:%@。"; + /* No comment provided by engineer. */ "Wrong database passphrase" = "数据库密码错误"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "密钥错误或连接未知 - 很可能此连接已被删除。"; + +/* file error text */ +"Wrong key or unknown file chunk address - most likely file is deleted." = "密钥错误或文件块地址未知 - 很可能文件已删除。"; + /* No comment provided by engineer. */ "Wrong passphrase!" = "密码错误!"; +/* No comment provided by engineer. */ +"XFTP server" = "XFTP 服务器"; + /* pref value */ "yes" = "是"; @@ -4024,6 +4907,9 @@ /* No comment provided by engineer. */ "You" = "您"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "您 **不得** 在两台设备上使用相同的数据库。"; + /* No comment provided by engineer. */ "You accepted connection" = "您已接受连接"; @@ -4036,12 +4922,27 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "您已经连接到 %@。"; +/* No comment provided by engineer. */ +"You are already connecting to %@." = "您已连接到 %@。"; + /* No comment provided by engineer. */ "You are already connecting via this one-time link!" = "你已经在通过这个一次性链接进行连接!"; +/* No comment provided by engineer. */ +"You are already in group %@." = "您已在组 %@ 中。"; + +/* No comment provided by engineer. */ +"You are already joining the group %@." = "您已加入组 %@。"; + +/* No comment provided by engineer. */ +"You are already joining the group via this link!" = "您已经通过此链接加入群组!"; + /* No comment provided by engineer. */ "You are already joining the group via this link." = "你已经在通过此链接加入该群。"; +/* No comment provided by engineer. */ +"You are already joining the group!\nRepeat join request?" = "您已经加入了这个群组!\n重复加入请求?"; + /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "您已连接到用于接收该联系人消息的服务器。"; @@ -4051,12 +4952,21 @@ /* No comment provided by engineer. */ "You are invited to group" = "您被邀请加入群组"; +/* No comment provided by engineer. */ +"You are not connected to these servers. Private routing is used to deliver messages to them." = "您未连接到这些服务器。私有路由用于向他们发送消息。"; + /* No comment provided by engineer. */ "you are observer" = "您是观察者"; +/* snd group event chat item */ +"you blocked %@" = "你阻止了%@"; + /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "您可以从锁屏上接听电话,无需设备和应用程序的认证。"; +/* No comment provided by engineer. */ +"You can change it in Appearance settings." = "您可以在外观设置中更改它。"; + /* No comment provided by engineer. */ "You can create it later" = "您可以以后创建它"; @@ -4078,6 +4988,9 @@ /* notification body */ "You can now chat with %@" = "您现在可以给 %@ 发送消息"; +/* No comment provided by engineer. */ +"You can send messages to %@ from Archived contacts." = "您可以从存档的联系人向%@发送消息。"; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "您可以通过设置来设置锁屏通知预览。"; @@ -4093,6 +5006,9 @@ /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "您可以通过应用程序设置/数据库或重新启动应用程序开始聊天"; +/* No comment provided by engineer. */ +"You can still view conversation with %@ in the list of chats." = "您仍然可以在聊天列表中查看与 %@的对话。"; + /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "您可以通过设置开启 SimpleX 锁定。"; @@ -4126,6 +5042,9 @@ /* No comment provided by engineer. */ "You have already requested connection via this address!" = "你已经请求通过此地址进行连接!"; +/* No comment provided by engineer. */ +"You have already requested connection!\nRepeat connection request?" = "您已经请求连接了!\n重复连接请求?"; + /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "您必须在每次应用程序启动时输入密码——它不存储在设备上。"; @@ -4141,9 +5060,18 @@ /* snd group event chat item */ "you left" = "您已离开"; +/* No comment provided by engineer. */ +"You may migrate the exported database." = "您可以迁移导出的数据库。"; + +/* No comment provided by engineer. */ +"You may save the exported archive." = "您可以保存导出的档案。"; + /* No comment provided by engineer. */ "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "您只能在一台设备上使用最新版本的聊天数据库,否则您可能会停止接收来自某些联系人的消息。"; +/* No comment provided by engineer. */ +"You need to allow your contact to call to be able to call them." = "您需要允许您的联系人呼叫才能呼叫他们。"; + /* No comment provided by engineer. */ "You need to allow your contact to send voice messages to be able to send them." = "您需要允许您的联系人发送语音消息,以便您能够发送语音消息。"; @@ -4162,9 +5090,15 @@ /* chat list item description */ "you shared one-time link incognito" = "您分享了一次性链接隐身聊天"; +/* snd group event chat item */ +"you unblocked %@" = "您解封了 %@"; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "您将在组主设备上线时连接到该群组,请稍等或稍后再检查!"; +/* No comment provided by engineer. */ +"You will be connected when group link host's device is online, please wait or check later!" = "当 Group Link Host 的设备在线时,您将被连接,请稍候或稍后检查!"; + /* No comment provided by engineer. */ "You will be connected when your connection request is accepted, please wait or check later!" = "当您的连接请求被接受后,您将可以连接,请稍等或稍后检查!"; @@ -4234,11 +5168,14 @@ /* No comment provided by engineer. */ "Your privacy" = "您的隐私设置"; +/* No comment provided by engineer. */ +"Your profile" = "您的个人资料"; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "您的个人资料 **%@** 将被共享。"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。\nSimpleX 服务器无法看到您的资料。"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。"; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "您的资料、联系人和发送的消息存储在您的设备上。"; diff --git a/apps/ios/zh-Hans.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/zh-Hans.lproj/SimpleX--iOS--InfoPlist.strings index b3192851c8..199d5faf7c 100644 --- a/apps/ios/zh-Hans.lproj/SimpleX--iOS--InfoPlist.strings +++ b/apps/ios/zh-Hans.lproj/SimpleX--iOS--InfoPlist.strings @@ -7,6 +7,9 @@ /* Privacy - Face ID Usage Description */ "NSFaceIDUsageDescription" = "SimpleX 使用Face ID进行本地身份验证"; +/* Privacy - Local Network Usage Description */ +"NSLocalNetworkUsageDescription" = "SimpleX 使用本地网络访问,允许通过同一网络上的桌面应用程序使用用户聊天配置文件。"; + /* Privacy - Microphone Usage Description */ "NSMicrophoneUsageDescription" = "SimpleX 需要麦克风访问权限才能进行音频和视频通话,以及录制语音消息。"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 0c4e78a269..595545213a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -224,7 +224,7 @@ Jak používat servery Vaše servery ICE Konfigurace serverů ICE - Nastavení sítě + Pokročilé nastavení Použít proxy server SOCKS\? Použít přímé připojení k internetu\? Ne @@ -434,7 +434,7 @@ Upravit obrázek Smazat obrázek chyba volání - Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli. + Servery může provozovat kdokoli. Vytvořte si svůj profil Vytvořte si soukromé připojení Videohovor šifrovaný e2e @@ -799,7 +799,7 @@ pozval %1$s připojen změnil roli %s na %s - změnil svou roli na %s + změnil vaši roli na %s odstraněn %1$s odstranil vás skupina odstraněna @@ -1861,4 +1861,5 @@ Volání zakázáno! Nelze zavolat člena skupiny Archivované kontakty + Archivujte kontakty pro pozdější chatování. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 897ea64be8..e7b327c4ff 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -277,11 +277,11 @@ Inkognito akzeptieren Ablehnen - Chatinhalte löschen? + Chat-Inhalte entfernen? Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht. - Löschen - Chatinhalte löschen - Chatinhalte löschen + Entfernen + Chat-Inhalte entfernen + Chat-Inhalte entfernen Löschen Löschen Als gelesen markieren @@ -753,7 +753,7 @@ Mitgliedereinladungen überspringen Kontakte auswählen Kontakt geprüft - Löschen + Entfernen %d Kontakt(e) ausgewählt Keine Kontakte ausgewählt Kontakt kann nicht eingeladen werden! @@ -765,7 +765,7 @@ Gruppe löschen Gruppe löschen? Die Gruppe wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden! - Die Gruppe wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! + Die Gruppe wird nur bei Ihnen gelöscht. Dies kann nicht rückgängig gemacht werden! Gruppe verlassen Gruppenprofil bearbeiten Gruppen-Link @@ -1397,7 +1397,7 @@ Wir haben das zweite Häkchen vermisst! ✅ Reparatur der Verschlüsselung nach Wiedereinspielen von Backups. Ein paar weitere Dinge - Auch wenn sie im Chat deaktiviert sind. + Auch wenn sie in den Unterhaltungen deaktiviert sind. - stabilere Zustellung von Nachrichten. \n- ein bisschen verbesserte Gruppen. \n- und mehr! @@ -1630,7 +1630,7 @@ Zum Scannen tippen Behalten Zum Link einfügen tippen - Suchen oder fügen Sie den SimpleX-Link ein + Suchen oder SimpleX-Link einfügen Der Chat wurde gestoppt. Wenn diese Datenbank bereits auf einem anderen Gerät von Ihnen verwendet wurde, sollten Sie diese dorthin zurück übertragen, bevor Sie den Chat starten. Chat starten? Interne Fehler anzeigen @@ -1673,7 +1673,7 @@ Mit verschlüsselten Dateien und Medien. Private Notizen Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! - Private Notizen löschen? + Private Notizen entfernen? %s wurde blockiert %s wurde freigegeben Sie haben %s blockiert @@ -2081,16 +2081,16 @@ Verbinden Nachricht Öffnen - Unterhaltung gelöscht! - Nur die Unterhaltung löschen + Chat-Inhalte gelöscht! + Nur die Chat-Inhalte löschen Suchen Video - Sie können in der Chatliste weiterhin die Unterhaltung mit %1$s einsehen. + Sie können in der Chat-Liste weiterhin die Unterhaltung mit %1$s einsehen. Link einfügen Archivierte Kontakte Keine gefilterten Kontakte Ihre Kontakte - Erreichbare Chat-Symbolleiste + Chat-Symbolleiste unten Bitten Sie Ihren Kontakt darum, Anrufe zu aktivieren. Sie müssen Ihrem Kontakt Anrufe zu Ihnen erlauben, bevor Sie ihn selbst anrufen können. Anrufe erlauben? @@ -2106,11 +2106,11 @@ Kontakt wird gelöscht. Dies kann nicht rückgängig gemacht werden! Ohne Benachrichtigung löschen Einladen - Unterhaltung behalten + Chat-Inhalte beibehalten Nachricht senden, um Anrufe zu aktivieren. Sie können aus den archivierten Kontakten heraus Nachrichten an %1$s versenden. Einstellungen - Die Nachrichten werden für alle Mitglieder gelöscht. + Die Nachrichten werden für alle Gruppenmitglieder gelöscht. Die Nachrichten werden für alle Mitglieder als moderiert markiert. %d Nachrichten der Mitglieder löschen? Nachricht @@ -2120,7 +2120,7 @@ Auswählen Einladen TCP-Verbindung - Schriftgröße erhöhen. + Schriftgröße anpassen. Neue Chat-Erfahrung 🎉 Die App automatisch aktualisieren Verbindungs- und Server-Status. @@ -2136,8 +2136,8 @@ Kontakte für spätere Chats archivieren. Ihre IP-Adresse und Verbindungen werden geschützt. Löschen Sie bis zu 20 Nachrichten auf einmal. - Erreichbare Chat-Symbolleiste - Die App mit einer Hand nutzen. + Chat-Symbolleiste unten + Die App mit einer Hand bedienen. Schneller mit Ihren Freunden verbinden. Chat-Datenbank wurde exportiert Weiter diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index db06d74378..3ec6bc021b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -15,10 +15,10 @@ 30 másodperc Egyszer használatos hivatkozás %1$s szeretne kapcsolatba lépni önnel ezen keresztül: - A SimpleX Chat-ről + A SimpleX Chatről 1 nap Címváltoztatás megszakítása - A SimpleX-ről + A SimpleXről Kiemelés fogadott hívás Hozzáférés a kiszolgálókhoz SOCKS proxy segítségével a %d porton? A proxyt el kell indítani, mielőtt engedélyezné ezt az opciót. @@ -26,25 +26,25 @@ Elfogadás gombra fent, majd: Elfogadás inkognítóban - Kapcsolódási kérelem elfogadása? + Ismerőskérelem elfogadása? Elfogadás Elfogadás Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősök számára. További kiemelés - hiba a hívásban + híváshiba Csoporttagok letiltása Hitelesítés Egy üres csevegési profil jön létre a megadott névvel, és az alkalmazás a szokásos módon megnyílik. %s visszavonva Előre beállított kiszolgálók hozzáadása A hívások kezdeményezése le van tiltva ebben a csevegésben. - Külön TCP kapcsolat (és SOCKS bejelentkezési adatok) lesz használva minden ismerős és csoporttag számára. -\nFigyelem: ha sok ismerőse van, az akkumulátor- és adathasználat jelentősen megnövekedhet és néhány kapcsolódási kísérlet sikertelen lehet. - hivatkozás előnézet visszavonása + Külön TCP kapcsolat (és SOCKS bejelentkezési adatok) lesz használva minden ismerős és csoporttag számára. \u0020 +\nFigyelem: ha sok ismerőse van, az akkumulátor- és az adathasználat jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. + hivatkozás előnézetének visszavonása az alkalmazásban minden csevegési profiljához .]]> Mindkét fél küldhet eltűnő üzeneteket. Az Android Keystore-t a jelmondat biztonságos tárolására használják - lehetővé teszi az értesítési szolgáltatás működését. - Hibás az üzenet ellenőrzőösszege + Hibás az üzenet hasító értéke Háttér Tudnivaló: az üzenet- és fájl átjátszók SOCKS proxy által vannak kapcsolatban. A hívások és URL hivatkozás előnézetek közvetlen kapcsolatot használnak.]]> Alkalmazásadatok biztonsági mentése @@ -114,7 +114,7 @@ hanghívás félkövér Az alkalmazás jelkód helyettesítésre kerül egy önmegsemmisítő jelkóddal. - Arab, bulgár, finn, héber, thai és ukrán - köszönet a felhasználóknak és a Weblate-nek! + Arab, bulgár, finn, héber, thai és ukrán - köszönet a felhasználóknak és a Weblate-nek. Hangüzenetek engedélyezése? Mindig használjon átjátszó kiszolgálót mindig @@ -124,9 +124,9 @@ Élő csevegési üzenet visszavonása Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) Hang- és videóhívások - hibás az üzenet ellenőrzőösszege + hibás az üzenet hasító értéke Mindig fut - Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat megváltoztatás után - lehetővé téve az értesítések fogadását. + Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat megváltoztatás után - lehetővé teszi az értesítések fogadását. Minden alkalmazásadat törölve. Legjobb akkumulátoridő. Csak akkor kap értesítéseket, amikor az alkalmazás meg van nyitva. (NINCS háttérszolgáltatás.)]]> Megjelenés @@ -137,7 +137,7 @@ szerző Az elküldött üzenetek végleges törlése engedélyezve van az ismerősei számára. (24 óra) Mégse - Az alkalmazás csak akkor tud értesítéseket fogadni, amikor meg van nyitva. A háttérszolgáltatás nem indul el. + Az alkalmazás csak akkor tud értesítéseket fogadni, amikor meg van nyitva. A háttérszolgáltatás nem indul el Jobb üzenetek A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra. Engedélyezés @@ -147,7 +147,7 @@ Alkalmazás jelkód Felkérték a kép fogadására Kamera - A Keystore-hoz nem sikerül hozzáférni az adatbázis jelszó mentése végett + Nem érhető el a Keystore az adatbázis jelszavának mentéséhez hívás folyamatban Képek automatikus elfogadása A hívások kezdeményezése engedélyezve van az ismerősei számára. @@ -183,7 +183,7 @@ kapcsolódott Kapcsolódás kapcsolódott - Csatlakoztatott telefon + Társított mobil eszköz kapcsolódva Szerepkör megváltoztatása Kapcsolódva @@ -202,7 +202,7 @@ Az ismerős és az összes üzenet törlésre kerül - ez a művelet nem vonható vissza! Az ismerősei törlésre jelölhetnek üzeneteket; ön majd meg tudja nézni azokat. Kapcsolódás egyszer használatos hivatkozással? - Kapcsolódás egy hivatkozás / QR-kód által + Kapcsolódás egy hivatkozáson vagy QR-kódon keresztül Kapcsolódási hiba (AUTH) Ismerős neve Kapcsolódik a kapcsolattartási címen keresztül? @@ -210,7 +210,7 @@ Másolás Folytatás Kapcsolódás egy hivatkozáson keresztül? - Létező ismerős + Az ismerős már létezik Fő verzió: v%s Ismerős ellenőrizve Kapcsolódás saját magához? @@ -229,7 +229,7 @@ az ismerősnek nincs e2e titkosítása Ismerős engedélyezi Ismerős elrejtve: - Kapcsolódás számítógéphez + Társítás számítógéppel Környezeti ikon Kapcsolódás egy hivatkozáson keresztül Ismerősök @@ -271,7 +271,7 @@ kapcsolódás… Csevegési profil törlése egyedi - hívás kapcsolódik… + kapcsolódási hívás… Téma személyre szabása Jelenleg támogatott legnagyobb fájl méret: %1$s. Fájl törlése @@ -325,14 +325,14 @@ kapcsolódás… Csevegési adatbázis törölve kapcsolódás (bejelentve) - Csoportos hivatkozás létrehozása + Csoporthivatkozás létrehozása Csevegési konzol Fájlok törlése minden csevegési profilból Várólista törlése Ismerős törlése Létrehozva ekkor: %1$s cím megváltoztatása… - Csatlakoztatva a mobilhoz + Társítva a mobil eszközzel Jelenlegi jelmondat… Fájl kiválasztás Kép törlése @@ -346,7 +346,7 @@ Fájl összehasonlítás Csevegések Üzenet törlése? - Függő kapcsolatfelvételi kérések törlése? + Függőben lévő ismerőskérelem törlése? Adatbázis titkosítva! Üzenetek kiürítése? Adatbázis visszafejlesztése @@ -358,7 +358,7 @@ Adatbázis ID Adatbázis ID: %d Adatbázis azonosítók és átviteli izolációs beállítások. - Az adatbázis titkosítás jelmondata megváltoztatásra és mentésre kerül a Keystore-ban. + Az adatbázis-titkosítási jelmondat megváltoztatásra és mentésre kerül a Keystore-ban. Az adatbázis titkosításra kerül és a jelmondat eltárolásra a beállításokban. Kiszolgáló törlése A készüléken nincs beállítva a képernyőzár. A SimpleX zár ki van kapcsolva. @@ -381,7 +381,7 @@ %d hónap Cím törlése? Üzenet kézbesítési jelentések letiltása? - Az adatbázis jelmondata eltér a Keystore-ban lévőtől. + Az adatbázis jelmondat eltér a Keystore-ban lévőtől. Közvetlen üzenetek E-mail Letiltás mindenki számára @@ -415,7 +415,7 @@ nap %d nap Csevegési archívum törlése? - Duplikált megjelenítési név! + Duplikált megjelenített név! Letiltás (felülírások megtartásával) Adatbázis fejlesztése %d üzenet letiltva @@ -430,7 +430,7 @@ Minden fájl törlése Az adatbázis titkosításra kerül. Adatbázis jelmondat és -exportálás - Az adatbázis titkosításra kerül és a jelmondat eltárolásra a Keystore-ban. + Az adatbázis titkosításra kerül és a jelmondat a Keystore-ban lesz tárolva. Automatikus üzenet törlés engedélyezése? Törlés az adatbázis verziója újabb, mint az alkalmazásé, visszafelé átköltöztetés nem lehetséges a következőhöz: %s @@ -450,7 +450,7 @@ Kép szerkesztése Értesítések letiltása Eszközök - Látható helyi hálózaton + Látható a helyi hálózaton Ne engedélyezze Archívum törlése Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben. @@ -483,8 +483,8 @@ Hiba a hálózat konfigurációjának frissítésekor TCP életben tartása Kamera váltás - Üdv! -\nCsatlakozzon hozzám SimpleX Chat-en keresztül: %s + Üdvözlöm! +\nCsatlakozzon hozzám a SimpleX Chaten keresztül: %s A megjelenített név nem tartalmazhat szóközöket. Csoport Üdvözlő üzenet megadása… (opcionális) @@ -499,7 +499,7 @@ A csevegések betöltése sikertelen A csoport már létezik! Francia kezelőfelület - Csoport hivatkozások + Csoporthivatkozások Végre, megvannak! 🚀 Hiba a csevegés elindításakor A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon. @@ -515,7 +515,7 @@ Kedvenc Csoport moderáció Fájl - Csoport hivatkozás + Csoporthivatkozás titkosítás újraegyeztetés szükséges %s számára Hiba a profil váltásakor! Kísérleti funkciók @@ -536,7 +536,7 @@ Teljesen decentralizált - kizárólag tagok számára látható. Fájl: %s Hívás befejezése - Hiba a csoport hivatkozásának törlésekor + Hiba a csoporthivatkozás törlésekor Fájl elmentve Kapcsolat javítása? Fájlok és médiatartalom @@ -552,8 +552,8 @@ A csevegés betöltése sikertelen Kiszolgáló megadása kézzel A fájl akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! - Hiba a csoport hivatkozásának létrehozásakor - A Galériából + Hiba a csoporthivatkozás létrehozásakor + A galériából Engedélyezés (csoport felülírások megtartásával) Hiba az ismerős törlésekor A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra) @@ -562,12 +562,12 @@ A csoport tagjai küldhetnek eltűnő üzeneteket. Kapcsolat javítása Hiba a profil létrehozásakor! - Hiba a tag(-ok) hozzáadásakor + Hiba a tag(ok) hozzáadásakor Fájl A csoport tagjai küldhetnek fájlokat és médiatartalmakat. Törlés ennyi idő után Hiba a beállítás megváltoztatásakor - Hiba a csoport hivatkozás frissítésekor + Hiba a csoporthivatkozás frissítésekor a csoport törölve csoportprofil frissítve Hiba a függőben lévő ismerős kapcsolatának törlésekor @@ -593,7 +593,7 @@ Hiba a cím megváltoztatásának megszakításakor Hiba a fájl fogadásakor titkosítás rendben - Hiba az ismerős kérelem törlésekor + Hiba az ismerőskérelem törlésekor Üzenet kézbesítési jelentéseket engedélyezése csoportok számára? Ismerős általi javítás nem támogatott Fájl nem található @@ -604,7 +604,7 @@ Tovább csökkentett akkumulátor használat Hiba a csevegés megállításakor titkosítás rendben %s számára - Csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza! + A csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza! Titkosítás javítása az adatmentések helyreállítása után. Hiba a csevegési adatbázis törlésekor Teljes hivatkozás @@ -623,7 +623,7 @@ titkosítás újraegyeztetés szükséges Rejtett csevegési profilok Fájlok és média - A kép mentve a Galériába + A kép mentve a „Galériába” Elrejt Azonnal A fájlok- és a médiatartalom küldése le van tiltva! @@ -648,7 +648,7 @@ Figyelmen kívül hagyás Kép elküldve Rejtett - Házigazda + Kiszolgáló Kezdeti szerepkör érvénytelen csevegés óra @@ -678,12 +678,12 @@ Megerősítés esetén az üzenetküldő kiszolgálók látni fogják az IP-címét és a szolgáltatóját – azt, hogy mely kiszolgálókhoz kapcsolódik. A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését. QR kód beolvasásával.]]> - A kapott SimpleX Chat meghívó hivatkozását megnyithatja böngészőjében: + A kapott SimpleX Chat meghívó hivatkozását megnyithatja a böngészőjében: Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot: Megtalált számítógép Számítógépek A markdown használata - Csevegő profil létrehozása + Csevegési profil létrehozása Levélszemét elleni védelem Mobilok leválasztása Különböző nevek, avatarok és átviteli izoláció. @@ -691,7 +691,7 @@ Szerepkör kiválasztásának bővítése A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! meghíva - Érvénytelen kapcsolati hivatkozás + Érvénytelen kapcsolattartási hivatkozás Némítás nincsenek részletek Nem fogadott hívás @@ -699,13 +699,13 @@ Az üzenet törlésre kerül - ez a művelet nem vonható vissza! Markdown segítség új üzenet - Régi adatbázis archívum + Régi adatbázis-archívum Haladó beállítások Nincs kézbesítési információ moderált A tag eltávolítása a csoportból - ez a művelet nem vonható vissza! Győződjön meg róla, hogy az XFTP kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. - Nem kerültek ismerősök kiválasztásra + Nincs kiválasztva ismerős Nincsenek fogadott, vagy küldött fájlok Megnyitás mobil alkalmazásban, majd koppintson a Kapcsolódás gombra az alkalmazásban.]]> Markdown az üzenetekben @@ -721,7 +721,7 @@ Helyi név Hálózat és kiszolgálók Értesítés előnézet - Társítsa össze a mobil és az asztali alkalmazásokat! 🔗 + Társítsa össze a mobil és asztali alkalmazásokat! 🔗 közvetett (%1$s) Hamarosan további fejlesztések érkeznek! Az üzenetreakciók küldése le van tiltva ebben a csevegésben. @@ -749,12 +749,12 @@ Onion kiszolgálók nem lesznek használva. perc Tudjon meg többet - Új kapcsolattartási kérelem + Új ismerőskérelem Csatlakozás a csoporthoz - Összekapcsolt számítógép beállítások - meghíva az ön csoport hivatkozásán keresztül + Társított számítógép beállítások + meghíva az ön csoporthivatkozásán keresztül elhagyta a csoportot - Összekapcsolt számítógépek + Társított számítógépek Nincs alkalmazás jelkód Némítás, ha inaktív! A meghívó lejárt! @@ -779,7 +779,7 @@ Olasz kezelőfelület Nincsenek háttérhívások Üzenetek - Összekapcsolt mobil eszközök + Társított mobil eszközök Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. Az üzenet törlésre lesz jelölve. A címzett(ek) képes(ek) lesz(nek) felfedni ezt az üzenetet. Elhagyás @@ -800,12 +800,12 @@ Tegye priváttá a profilját! Üzenetkézbesítési hiba Több csevegőprofil - töröltnek jelölve + törlésre jelölve Némítás - Egy mobil összekapcsolása + Egy mobil eszköz társítása Értesítési szolgáltatás Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését. - 2 rétegű végponttól-végpontig titkosítással küldött üzeneteket.]]> + 2 rétegű végpontok közötti titkosítással küldött üzeneteket.]]> Érvénytelen átköltöztetési visszaigazolás Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. Nincsenek előzmények @@ -823,26 +823,26 @@ ajánlott %s Csoport elhagyása Minden %s által írt üzenet megjelenik! - Ha a SimpleX Chat-nek nincs felhasználói azonosítója, hogyan lehet mégis üzeneteket küldeni?]]> + Ha a SimpleX Chatnek nincs felhasználói azonosítója, hogyan lehet mégis üzeneteket küldeni?]]> Ez akkor fordulhat elő, ha: \n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak. \n2. Az üzenet visszafejtése sikertelen volt, mert ön, vagy az ismerőse régebbi adatbázis biztonsági mentést használt. \n3. A kapcsolat sérült. megfigyelő - inkognitó a csoportos hivatkozáson keresztül + inkognitó a csoporthivatkozáson keresztül Onion kiszolgálók használata, ha azok rendelkezésre állnak. Ismerősök meghívása Menük és figyelmeztetések Tagok meghívása csatlakozás mint %s - Nincs kiválasztott csevegés + Nincs kiválasztva csevegés Csak helyi profiladatok - inkognitó az egyszer használatos hivatkozáson keresztül + inkognitó egy egyszer használatos hivatkozáson keresztül Moderálva lett ekkor: %s Egyszer használatos meghívó hivatkozás Érvénytelen név! - Beszélgessünk a SimpleX Chat-ben - Moderálva lett ekkor: + Beszélgessünk a SimpleX Chatben + Moderálva ekkor: Élő üzenetek Hitelesítés Üzenetkézbesítési bizonylatok! @@ -864,7 +864,7 @@ tag Privát kapcsolat létrehozása moderálva lett %s által - Győződjön meg arról, hogy a fájl helyes YAML-szintaxist tartalmaz. Exportálja a témát, hogy legyen egy példa a téma fájl szerkezetére. + Győződjön meg arról, hogy a fájl helyes YAML-szintaxist tartalmaz. Exportálja a témát, hogy legyen egy példa a témafájl szerkezetére. dőlt Érvénytelen fájl elérési útvonal Csatlakozik a csoporthoz? @@ -904,13 +904,13 @@ Előnézet megjelenítése várakozás a visszaigazolásra… Fájl megállítása - csoport hivatkozáson keresztül + a csoporthivatkozáson keresztül PING időköze Eltűnő üzenet küldése Önmegsemmisítési jelkód Mentés és csoportprofil frissítése Adatvédelem - Az ön SimpleX címe + Profil SimpleX címe Jelentse a fejlesztőknek. Ön dönti el, hogy kivel beszélget. Az eltűnő üzenetek küldése le van tiltva. @@ -946,7 +946,7 @@ (beolvasás, vagy beillesztés a vágólapról) Videóra várakozás Válasz - Ez az egyszer használatos hivatkozása! + Ez az ön egyszer használatos hivatkozása! SimpleX Chat hívások Új inkognító profil használata Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel. @@ -964,10 +964,10 @@ Biztonsági kód beolvasása az ismerősének alkalmazásából. Lépjen kapcsolatba a csoport adminnal. Videó bekapcsolva - Profil neve: + Profilnév: Beillesztés Köszönjük, hogy telepítette a SimpleX Chatet! - Csillagozás a GitHub-on + Csillagozás a GitHubon Eltávolítás Keresés Titkosítás újraegyeztetése? @@ -1001,7 +1001,7 @@ Véletlen Megosztás az ismerősökkel ön - Nincsenek csevegési üzenetek + Nincsenek csevegései Küldés %s másodperc %s: %s @@ -1020,7 +1020,7 @@ Profiljelszó Téma Jelmondat eltávolítása a beállításokból? - SimpleX csoport hivatkozás + SimpleX csoporthivatkozás Képre várakozás Önmegsemmisítés várakozás a válaszra… @@ -1041,11 +1041,11 @@ Véletlenszerű jelmondat használata egyenrangú CSEVEGÉSI SZOLGÁLTATÁS INDÍTÁSA - Fogadott hivatkozás beillesztése + Kapott hivatkozás beillesztése Kiszolgálók mentése? A SimpleX Chat biztonsága a Trail of Bits által lett auditálva. frissítette a csoport profilját - TÁMOGASSA A SIMPLEX CHATET + SIMPLEX CHAT TÁMOGATÁSA SimpleX Chat szolgáltatás Nem lehet üzeneteket küldeni! %s ellenőrzött @@ -1122,13 +1122,13 @@ SMP kiszolgálók Az üzenet kézbesítési jelentések le vannak tiltva Adatbázis mappa megnyitása - egyszer használatos hivatkozáson keresztül + egy egyszer használatos hivatkozáson keresztül Csoportbeállítások megadása ezen keresztül: %1$s igen Hangüzenet - Használat számítógépről - ÖN + Társítás számítógéppel + PROFIL port %d Kapcsolódás hivatkozáson keresztül Cím megosztása @@ -1144,7 +1144,7 @@ eltávolítottak SimpleX cím Megjelenítés: - fogadott válasz… + válasz fogadása… Adatbázismentés visszaállítása? Üzenetek fogadása… %s és %s kapcsolódott @@ -1169,7 +1169,7 @@ Az üzenetreakciók küldése le van tiltva. Rendszer olvasatlan - Függő + Függőben Üdvözöljük %1$s! Jelmondat eltávolítása a Keystrore-ból? Feloldás @@ -1185,10 +1185,10 @@ Kép/videó megoszása… ön: %1$s Beállítások - Színek alaphelyzetbe állítása + Színek visszaállítása Mentés Váltás - A kapott hivatkozás beillesztése az ismerősökhöz történő kapcsolódáshoz… + A kapott hivatkozás beillesztése az ismerőshöz való kapcsolódáshoz… Beolvasás Port megnyitása a tűzfalon indítás… @@ -1254,7 +1254,7 @@ Fogadva ekkor: %s SimpleX zár Mentés és csoporttagok értesítése - Alaphelyzetbe állítás + Visszaállítás Csak az ismerőse tud üzenetreakciókat küldeni. Hangüzenetek elhagyta a csoportot @@ -1287,9 +1287,9 @@ Videók és fájlok 1Gb méretig TCP kapcsolat időtúllépés A(z) %1$s nevű profiljának SimpleX címe megosztásra fog kerülni. - Ön már kapcsolódott ehhez: %1$s. + Ön már kapcsolódva van ehhez: %1$s. Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! -\nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek! +\nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek.
    Ötletek és javaslatok Figyelmeztetés: néhány adat elveszhet! Koppintson az új csevegés indításához @@ -1303,23 +1303,23 @@ cím megváltoztatva nála: %s fájlok fogadása egyelőre még nem támogatott Csoportprofil mentése - Alaphelyzetbe állítás - Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, lehet hogy ez egy hiba – jelentse a problémát. -\nA kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. + Visszaállítás alaphelyzetbe + Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát. +\nA kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. videóhívás (nem e2e titkosított) Alkalmazás új kapcsolatokhoz Az új üzenetek rendszeresen letöltésre kerülnek az alkalmazás által – naponta néhány százalékot használ az akkumulátorból. Az alkalmazás nem használ push értesítéseket – az eszközről származó adatok nem kerülnek elküldésre a kiszolgálóknak. Számítógép címének beillesztése kapcsolattartási cím-hivatkozáson keresztül - SimpleX háttérszolgáltatást használja - az akkumulátor néhány százalékát használja naponta.]]> + SimpleX háttérszolgáltatást használja - az akkumulátornak csak néhány százalékát használja naponta.]]> Az ismerősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. -\nVisszavonhatja ezt a kapcsolatfelvételt és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). - A jelszó nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonsági mentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. +\nVisszavonhatja ezt az ismerőskérelmet és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). + A jelszó nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonságimentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. Az ismerősei továbbra is kapcsolódva maradnak. A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát Az adatbázis nem működik megfelelően. Koppintson további információért A fájl küldése leállt. - Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál. + Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. Nem lehetett ellenőrizni; próbálja meg újra. Az üzenet minden tag számára moderáltként lesz megjelölve. Értesítések fogadásához adja meg az adatbázis jelmondatát @@ -1327,8 +1327,8 @@ Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítés szükséges. Az üzenet minden tag számára törlésre kerül. A videó nem dekódolható. Próbálja ki egy másik videóval, vagy lépjen kapcsolatba a fejlesztőkkel. - Ez a szöveg a beállítások között érhető el - Profilja elküldésre kerül ismerőse számára, akitől ezt a hivatkozást kapta. + Ez a szöveg a „Beállításokban” érhető el + Profilja elküldésre kerül az ismerőse számára, akitől ezt a hivatkozást kapta. Az alkalmazás 1 perc után bezárható a háttérben. meghívást kapott a csoportba engedélyezze a SimpleX háttérben történő futását a következő párbeszédpanelen. Ellenkező esetben az értesítések letiltásra kerülnek.]]> @@ -1341,22 +1341,22 @@ Hálózati kapcsolat ellenőrzése a következővel: %1$s, és próbálja újra. A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be. Az alkalmazás összeomlott - Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg ismerősét, hogy küldjön egy másikat. + Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg az ismerősét, hogy küldjön egy másikat. A kép nem dekódolható. Próbálja meg egy másik képpel, vagy lépjen kapcsolatba a fejlesztőkkel. Érvénytelen fájl elérési útvonalat osztott meg. Jelentse a problémát az alkalmazás fejlesztőinek. Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet. - Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál (hiba: %1$s). + Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál (hiba: %1$s). A fájl fogadása leállt. Ne felejtse el, vagy tárolja biztonságosan – az elveszett jelszót nem lehet visszaállítani! A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését. egyszer használatos hivatkozást osztott meg inkognitóban - Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál. + Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. Később engedélyezheti a Beállításokban Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! különböző átköltöztetés az alkalmazásban/adatbázisban: %s / %s %1$s.]]> Profil felfedése - Ez a hivatkozás nem érvényes kapcsolati hivatkozás! + Ez nem egy érvényes kapcsolattartási hivatkozás! A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerősétől. Ez a beállítás a jelenlegi csevegési profilban lévő üzenetekre érvényes @@ -1367,7 +1367,7 @@ Ismerőse a jelenleg megengedett maximális méretű (%1$s) fájlnál nagyobbat küldött. Az ismerősei és az üzenetek (kézbesítés után) nem kerülnek tárolásra a SimpleX kiszolgálókon. Üzenetek formázása a szövegbe szúrt speciális karakterekkel: - Megnyitás alkalmazásban gombra.]]> + Megnyitás az alkalmazásban gombra.]]> Csevegési profilja elküldésre kerül \naz ismerőse számára Egy olyan ismerősét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban @@ -1379,10 +1379,10 @@ A hangüzenetek küldése le van tiltva ebben a csoportban. Alkalmazás akkumulátor használata / Korlátlan módot az alkalmazás beállításaiban.]]> Biztonságos kvantumrezisztens protokollon keresztül. - - hangüzenetek 5 percig. -\n- egyedi eltűnési időhatár. -\n- előzmény szerkesztése. - Használat számítógépről menüt a mobil alkalmazásban és olvassa be a QR-kódot.]]> + - 5 perc hosszúságú hangüzenetek. +\n- egyedi üzenet-eltűnési időkorlát. +\n- előzmények szerkesztése. + Társítás számítógéppel menüt a mobil alkalmazásban és olvassa be a QR-kódot.]]> %s ekkor: %s Akkor lesz kapcsolódva, amikor az ismerősének az eszköze online lesz, várjon, vagy ellenőrizze később! Kéretlen üzenetek elrejtése. @@ -1411,7 +1411,7 @@ cím megváltoztatva Ismerősei engedélyezhetik a teljes üzenet törlést. A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra. - Ha engedélyezni szeretné, hogy egy mobilalkalmazás csatlakozzon a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, ha engedélyezte azt + Ha engedélyezni szeretné a mobilalkalmazás társítását a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, ha engedélyezte azt Profilja, ismerősei és az elküldött üzenetei az eszközön kerülnek tárolásra. Alkalmazás akkumulátor használata / Korlátlan módot az alkalmazás beállításaiban.]]> Ez a karakterlánc nem egy meghívó hivatkozás! @@ -1419,7 +1419,7 @@ A kapcsolódás már folyamatban van ezen az egyszer használatos hivatkozáson keresztül! Nem veszíti el az ismerőseit, ha később törli a címét. A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. - kapcsolatba akar lépni veled! + kapcsolatba akar lépni önnel! saját szerepköre erre változott: %s A csevegési szolgáltatás elindítható a Beállítások / Adatbázis menüben vagy az alkalmazás újraindításával. Kód ellenőrzése a mobilon @@ -1431,18 +1431,18 @@ Inkognító mód kapcsolódáskor. Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait. Csatlakozott ehhez a csoporthoz - %1$s csoporthoz!]]> + %1$s csoporthoz!]]> A hangüzenetek küldése le van tiltva ebben a csevegésben. Ön irányítja csevegését! Kód ellenőrzése a számítógépen Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak. - A kapcsolódási kérelem elküldésre kerül ezen csoporttag számára + A kapcsolódási kérelem elküldésre kerül ezen csoporttag számára. Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. Már kért egy kapcsolódási kérelmet ezen a címen keresztül! Megoszthatja ezt a SimpleX címet az ismerőseivel, hogy kapcsolatba léphessenek vele: %s. Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat. Megjelenítendő üzenet beállítása az új tagok számára! - Köszönet a felhasználóknak - hozzájárulás a Weblaten! + Köszönet a felhasználóknak - hozzájárulás a Weblate-en! A kézbesítési jelentés küldése minden ismerős számára engedélyezésre kerül. Protokoll időkorlát KB-onként Az adatbázis jelmondatának megváltoztatására tett kísérlet nem fejeződött be. @@ -1453,7 +1453,7 @@ Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak. Kézbesítési jelentések engedélyezve vannak %d ismerősnél Küldés ezen keresztül: - Köszönet a felhasználóknak - hozzájárulás a Weblaten! + Köszönet a felhasználóknak - hozzájárulás a Weblate-en! A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára. Bluetooth támogatás és további fejlesztések. Ez a funkció még nem támogatott. Próbálja meg a következő kiadásban. @@ -1467,13 +1467,13 @@ Jelmondat beállítása az exportáláshoz Kézbesítési jelentések le vannak tiltva a(z) %d csoportban Néhány nem végzetes hiba történt az importálás közben: - Köszönet a felhasználóknak - hozzájárulás a Weblaten! + Köszönet a felhasználóknak - hozzájárulás a Weblate-en! Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címet. Rendszerhitelesítés helyetti beállítás. A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be. A csevegés megállítása a csevegő adatbázis exportálásához, importálásához, vagy törléséhez. A csevegés megállítása alatt nem tud üzeneteket fogadni és küldeni. Jelmondat mentése a Keystore-ba - Köszönet a felhasználóknak - hozzájárulás a Weblaten! + Köszönet a felhasználóknak - hozzájárulás a Weblate-en! Jelmondat mentése a beállításokban Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem kerülnek elküldésre. A második jelölés, amit kihagytunk! ✅ @@ -1484,18 +1484,18 @@ Kézbesítési jelentések engedélyezve vannak a(z) %d csoportban A szerepkör meg fog változni erre: „%s”. A csoportban mindenki értesítve lesz. Profil és kiszolgálókapcsolatok - Egy üzenetküldő- és alkalmazásplatform, amely védi az ön adatait és biztonságát. + Egy üzenetküldő- és alkalmazásplatform, amely védi az adatait és biztonságát. A profil aktiválásához koppintson az ikonra. Kézbesítési jelentések le vannak tiltva %d ismerősnél Munkamenet kód - Köszönet a felhasználóknak - hozzájárulás a Weblaten! + Köszönet a felhasználóknak - hozzájárulás a Weblate-en! Kis csoportok (max. 20 tag) - Az ön által elfogadott kapcsolat vissza lesz vonva! + Az ön által elfogadott kérelem vissza lesz vonva! Élő üzenet küldése - a címzett(ek) számára frissül, ahogy beírja A KÉZBESÍTÉSI JELENTÉSEKET A KÖVETKEZŐ CÍMRE KELL KÜLDENI A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel). \nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. - Az eszköz neve megosztásra kerül a csatlakoztatott mobil klienssel. + Az eszköz neve megosztásra kerül a társított mobil klienssel. A címzettek a beírás közben látják a frissítéseket. Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni. A jelmondat a beállítások között egyszerű szövegként kerül tárolásra, miután megváltoztatta vagy újraindította az alkalmazást. @@ -1510,15 +1510,15 @@ Használati útmutatóban olvasható.]]> A jelmondat a beállításokban egyszerű szövegként van tárolva. Konzol megjelenítése új ablakban - Az előző üzenet ellenőrzőösszege különbözik. + Az előző üzenet hasító értéke különbözik. Ezek a beállítások a jelenlegi profiljára vonatkoznak - Várjon, amíg a fájl betöltődik a csatolt mobilról + Várjon, amíg a fájl betöltődik a társított mobilról GitHub tárolónkban.]]> - hiba a tartalom megjelenítése közben + hiba a tartalom megjelenítésekor hiba az üzenet megjelenítésekor - Láthatóvá teheti SimpleX-beli ismerősei számára a Beállításokban. + Láthatóvá teheti a SimpleXbeli ismerősei számára a „Beállításokban”. Legfeljebb az utolsó 100 üzenet kerül elküldésre az új tagok számára. - A beolvasott kód nem egy SimpleX hivatkozás QR-kód. + A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozás. A beillesztett szöveg nem egy SimpleX hivatkozás. A meghívó hivatkozását újra megtekintheti a kapcsolat részleteinél. Csevegés indítása? @@ -1567,9 +1567,9 @@ \n%s %s mobil eszköz verziója nem támogatott. Győződjön meg arról, hogy mindkét eszközön ugyanazt a verziót használja]]> %s mobil eszközzel]]> - Érvénytelen megjelenítendő felhaszálónév! - Ez a megjelenített felhasználónév érvénytelen. Válasszon egy másik nevet. - %s mobil eszközzel, a(z) %s probléma miatt]]> + Érvénytelen megjelenítendő név! + Ez a megjelenített név érvénytelen. Válasszon egy másik nevet. + %s mobil eszközzel, a(z) %s probléma miatt]]> %s probléma miatt megszakadt a kapcsolat %s mobil eszköz nem található]]> %s mobil eszközzel rossz állapotban van]]> @@ -1581,7 +1581,7 @@ Fejlesztői beállítások A funkció végrehajtása túl sokáig tart: %1$d másodperc: %2$s %s mobil eszköz elfoglalt]]> - Már nem tag - %1$s + %1$s (már nem tag) ismeretlen státusz %1$s megváltoztatta a nevét erre: %2$s törölt kapcsolattartási cím @@ -1612,13 +1612,13 @@ letiltva letiltva az admin által Letiltva az admin által - letiltotta %s-t + letiltotta őt: %s Letiltás mindenki számára Mindenki számára letiltja ezt a tagot? %d üzenet letiltva az admin által Letiltás feloldása mindenki számára Mindenki számára feloldja a tag letiltását? - ön letiltotta %s-t + ön letiltotta őt: %s Hiba a tag mindenki számára való letiltása közben Az üzenet túl nagy Az üdvözlő üzenet túl hosszú @@ -1642,7 +1642,7 @@ Átköltöztetés visszavonása A csevegés átköltöztetve! Ellenőrizze az internetkapcsolatot, és próbálja újra - Archív hivatkozás létrehozása + Archívum hivatkozás létrehozása Adatbázis törlése erről az eszközről Sikertelen letöltés Archívum letöltése @@ -1671,7 +1671,7 @@ Ellenőrizze, hogy a hálózati beállítások megfelelőek-e ehhez az eszközhöz. A folytatáshoz a csevegést meg kell szakítani. Csevegés megállítása folyamatban - Vagy a fájl hivítkozásának biztonságos megosztása + Vagy ossza meg biztonságosan ezt a fájlhivatkozást Csevegés indítása Nem szabad ugyanazt az adatbázist használni egyszerre két eszközön.]]> Erősítse meg, hogy emlékszik az adatbázis jelmondatára az átköltöztetéshez. @@ -1726,7 +1726,7 @@ tulajdonosok adminok minden tag - SimpleX hivatkozás + SimpleX hivatkozások A hangüzenetek küldése le van tiltva A SimpleX hivatkozások küldése le van tiltva ebben a csoportban. A SimpleX hivatkozások küldése le van tiltva @@ -1840,14 +1840,14 @@ Privát üzenet útválasztás 🚀 Fájlok biztonságos fogadása Csökkentett akkumulátor-használattal. - Hiba a WebView inicializálásában. Frissítse rendszerét az új verzióra. Kérjük, lépjen kapcsolatba a fejlesztőkkel. + Hiba a WebView inicializálásában. Frissítse rendszerét az új verzióra. Lépjen kapcsolatba a fejlesztőkkel. \nHiba: %s Felhasználó által létrehozott téma visszaállítása Üzenet várakoztatási információ nincs Kézbesítési hibák felderítése - Kiszolgáló várakoztatási infó: %1$s -\nUtoljára kézbesített üzenet: %2$s + kiszolgáló várakoztatási infó: %1$s +\nutoljára kézbesített üzenet: %2$s Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött. Ideiglenes fájlhiba Üzenetállapot @@ -1858,7 +1858,7 @@ Fájlállapot Fájlállapot: %s Másolási hiba - Ezt a hivatkozást egy másik mobilleszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén. + Ezt a hivatkozást egy másik mobileszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén. Ellenőrizze, hogy a mobil és az asztali számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint az asztali számítógép tűzfalában engedélyezve van-e a kapcsolat. \nMinden további problémát osszon meg a fejlesztőkkel. Nem lehet üzenetet küldeni @@ -1870,13 +1870,13 @@ Az üzenet később is kézbesíthető, ha a tag aktívvá válik. Még nincs közvetlen kapcsolat, az üzenetet az admin továbbítja. Hivatkozás beolvasása / beillesztése - Beállított SMP-kiszolgálók + Konfigurált SMP-kiszolgálók Egyéb SMP-kiszolgálók Egyéb XFTP-kiszolgálók letiltva inaktív Nagyítás - információk a kiszolgálókról + Információk a kiszolgálókról Kapcsolódás Hibák Függőben @@ -1892,7 +1892,7 @@ Visszaállítás Minden statisztika visszaállítása Minden statisztika visszaállítása? - A kiszolgálók statisztikái visszaállnak - ez nem vonható vissza! + A kiszolgálók statisztikái visszaállnak - ez a művelet nem vonható vissza! Részletes statisztikák Letöltve lejárt @@ -1930,7 +1930,7 @@ Feltöltött fájltöredékek Elkészült Kapcsolódott kiszolgálók - Beállított XFTP-kiszolgálók + Konfigurált XFTP-kiszolgálók Kapcsolódva Jelenlegi profil Részletek @@ -2023,7 +2023,7 @@ Engedélyeznie kell a hívásokat az ismerőse számára, hogy fel tudják hívni egymást. A(z) %1$s nevű ismerősével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában. Üzenet - Kiválaszt + Kiválasztás Az üzenetek minden tag számára moderáltként lesznek megjelölve. Nincs kiválasztva semmi Az üzenetek törlésre lesznek jelölve. A címzett(ek) képes(ek) lesz(nek) felfedni ezt az üzenetet. @@ -2032,7 +2032,7 @@ Az üzenetek minden tag számára törlésre kerülnek. Csevegési adatbázis exportálva Kapcsolatok- és kiszolgálók állapotának megjelenítése. - Kapcsolódjon gyorsabban az ismerőseihez + Kapcsolódjon gyorsabban az ismerőseihez. Folytatás Ellenőrízze a hálózatát Média- és fájlkiszolgálók @@ -2052,11 +2052,11 @@ Csevegőlista átváltása: Ezt a „Megjelenés” menüben módosíthatja. Új médiabeállítások - Lejátszás a csevegési listából - Elhomályosítás a jobb adatvédelemért + Lejátszás a csevegési listából. + Elhomályosítás a jobb adatvédelemért. Automatikus frissítés Létrehozás - Új verziók letöltése a GitHub-ról + Új verziók letöltése a GitHubról Betűméret növelése. Meghívás Új csevegési élmény 🎉 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index b6d6770250..9f6fa799b7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -2,16 +2,16 @@ %1$s ANGGOTA Alamat - %1$d pesan dihapus admin %2$s + %1$d pesan dimoderasi oleh %2$s 1 menit 30 detik 5 menit Semua pesan akan dihapus - ini tidak bisa dikembalikan! Pesan akan HANYA dihapus untukmu. - Terima permintaan relasi? + Terima permintaan koneksi? Tentang alamat SimpleX Tambah kontak Tentang SimpleX - %1$d gagal mendekripsi pesan. + %1$d pesan gagal terdekripsi. Tolong laporkan hal ini ke pengembang. 1 bulan 1 minggu @@ -19,7 +19,7 @@ %1$s.]]> Panggilan suara Batal - izinkan pesan suara? + Izinkan pesan suara? Terima Versi aplikasi Versi aplikasi: v%s @@ -30,13 +30,13 @@ Tambah profil Corak Tambahan sekunder - Motif aplikasi + Tema aplikasi selalu - diizinkan mengirim pesan suara. + Izinkan mengirim pesan suara. semua anggota Panggilan suara dan video - Hal-hal lain - Sudah menghubungi! + Beberapa hal lainnya + Sudah terhubungkan! Sudah bergabung dengan grup! %1$s ingin menghubungimu lewat Batalkan penggantian alamat? @@ -54,7 +54,7 @@ tebal menelepon… Bluetooth - Panggilan ditutup + Panggilan diakhiri Ubah Peringatan: arsip akan dihapus.]]> Buat grup: untuk membuat grup baru.]]> @@ -112,4 +112,154 @@ Tampilan macet Anda membagikan lokasi file yang tidak valid. Laporkan masalah ini ke pengembang aplikasi. error + Tuatan 1 kali pakai + %1$d pesan yang terlewati + %1$d pesan yang dilewati + %s tidak didukung. Harap pastikan kamu menggunakan versi yang sama pada kedua perangkat.]]> + %s sedang sibuk]]> + %s tidak terhubung]]> + %s tidak aktif]]> + %s tidak di temukan]]> + Terima penyamaran + panggilan diterima + 6 bahasa antarmuka baru + %s tidak terhubung]]> + Tidak ada kode sandi aplikasi + Belum ada koneksi langsung, pesan diteruskan oleh admin. + Tidak ada obrolan yang difilter + Tidak ada yang dipilih + Hanya 10 gambar dapat dikirim pada saat bersamaan + Notifikasi + Hanya menghapus percakapan + Koneksi jaringan yang lebih handal. + Pengalaman obrolan yang baru 🎉 + Pilihan media baru + Izinkan panggilan? + Pesan baru + Hanya kamu yang dapat mengirim pesan menghilang. + Tidak ada pengidentifikasi pengguna. + Hanya pemilik grup yang dapat mengubah preferensi grup. + dan %d peristiwa lainnya + Peran baru anggota + Tidak ada kontak di pilih + Tidak ada kontak untuk ditambahkan + Tidak ada teks + Semua pesan baru dari %s akan disembunyikan! + tidak ada + Status jaringan + Seluruh obrolan dan pesan akan dihapus - ini tidak bisa dibatalkan! + Tidak ada perangkat terkoneksi + Tidak ada panggilan latar + Pratinjau notifikasi + Layanan notifikasi + Selalu aktif + Permintaan kontak baru + Pesan baru + Kemungkinan besar kontak ini telah menghapus koneksi dengan kamu. + Masalah jaringan - pesan kadaluwarsa setelah beberapa kali mencoba mengirim. + Tidak ada obrolan dipilih + Hanya pemilik grup yang dapat mengaktifkan file dan media. + (hanya disimpan oleh anggota grup) + Lagi + Tautan undangan satu kali + Obrolan baru + Jaringan & server + Pengaturan tingkat lanjut + Host Onion akan diperlukan untuk koneksi. +\nHarap diperhatikan: Anda tidak akan dapat terhubung ke server tanpa alamat .onion. + Host Onion tidak akan digunakan. + Selalu gunakan perutean pribadi. + Pemberitahuan akan berhenti bekerja sampai kamu meluncurkan ulang aplikasi + Semua kontak kamu akan tetap terhubung. Pembaruan profil akan dikirim ke kontak kamu. + Tidak ada enkripsi ujung-ujung + Kode sandi baru + Mati + Seluruh data aplikasi dihapus. + Arsip database baru + Arsip database lama + tidak pernah + Tidak ada file yang diterima atau dikirim + Menyetujui enkripsi… + Bisukan ketika tidak aktif! + Seluruh mode warna + mati` + tidak + on + mati + Izinkan kontak kamu menghapus pesan terkirim secara permanen. (24 jam) + Izinkan penghapusan pesan yang tidak dapat diubah hanya jika kontak kamu mengizinkannya. (24 jam) + Izinkan kontak kamu mengirim pesan suara. + Hanya kamu yang dapat menghapus pesan secara permanen (kontak kamu dapat menandainya untuk dihapus). (24 jam) + Hanya kontak kamu yang dapat menghapus pesan secara permanen (kamu dapat menandainya untuk dihapus). (24 jam) + Hanya kamu yang dapat menambahkan reaksi pesan. + Hanya kamu yang dapat melakukan panggilan. + Hanya kontak kamu yang dapat menambahkan reaksi pesan. + Izinkan pengiriman pesan langsung ke anggota. + Izinkan untuk mengirim pesan menghilang. + ditawarkan %s: %2s + Beberapa profil obrolan + Lebih banyak peningkatan akan segera hadir! + Tidak ada admin yang dapat: +\n- menghapus pesan anggota. +\n- menonaktifkan anggota (peran “pengamat”) + Lebih banyak peningkatan akan segera hadir! + - pengiriman pesan yang lebih stabil. +\n- group yang sedikit lebih baik. +\n- dan lainnya! + Aplikasi desktop baru! + Manajemen jaringan + bulan + Semua kontak, percakapan, dan file kamu akan dienkripsi dengan aman dan diunggah dalam beberapa bagian ke relay XFTP yang telah dikonfigurasi. + Tidak ada koneksi jaringan + Bisu + Koneksi jaringan + TIdak pernah + Mati + Hanya 10 video dapat dikirim pada saat bersamaan + enkripsi ujung-ke-ujung 2 lapis.]]> + Hanya pemilik grup yang dapat mengaktifkan pesan suara. + Seluruh pesan akan dihapus - ini tidak bisa dibatalkan! + Izinkan turun versi + Tidak ada informasi pengiriman + Boleh + OK + Tidak ada rincian + Tautan undangan satu kali + Host Onion akan di pakai jika tersedia. + Selalu gunakan relay + Nama tampilan baru: + Frasa sandi baru + Pemberitahuan akan dikirim hanya sampai saat aplikasi berhenti! + Pengaturan tingkat lanjut + Izinkan reaksi pesan hanya jika kontak Anda mengizinkannya. + Izinkan pesan suara hanya jika kontak kamu mengizinkannya. + Izinkan kontak kamu menambahkan reaksi pesan. + Izinkan kontak kamu untuk menghubungimu. + Izinkan kontak kamu untuk mengirim pesan menghilang. + Izinkan panggilan hanya jika kontak kamu mengizinkannya. + Izinkan pesan menghilang hanya jika kontak kamu mengizinkannya. + Izinkan reaksi pesan. + Izinkan untuk menghapus pesan terkirim secara permanen. (24 jam) + Bisu + Tidak ada riwayat + Tema obrolan yang baru + Semua profil + Izinkan untuk mengirim tautan SimpleX. + Semua data akan terhapus jika ini dimasukkan. + Tidak kompatibel! + Perangkat seluler baru + Hanya satu perangkat yang dapat bekerja pada saat bersamaan + Tidak + Tidak ada kontak yang di filter + pengamat + menyetujui enkripsi untuk %s… + Semua anggota grup akan tetap terhubung. + Tidak ada info, coba muat ulang + Tidak + Baru di %s + ditawarkan %s + Hanya kamu yang dapat mengirim pesan suara. + Hanya kontak kamu yang dapat melakukan panggilan. + Izinkan untuk mengirim file dan media. + Semua kontak kamu akan tetap terhubung. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 3c639cf777..92055493fb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -74,7 +74,7 @@ Verifique o endereço do servidor e tente novamente. para cada perfil de bate-papo que você tiver no aplicativo.]]> Melhor para bateria. Você receberá notificações apenas quando o aplicativo estiver em execução (SEM o serviço em segundo plano).]]> - Consome mais bateria! O serviço em segundo plano está sempre em execução - as notificações são exibidas assim que as mensagens estiverem disponíveis.]]> + Consome mais bateria! O aplicativo em segundo plano está sempre em execução - as notificações são exibidas instantaneamente.]]> BATE-PAPOS ÍCONE DO APLICATIVO BANCO DE DADOS DE BATE-PAPO @@ -91,7 +91,7 @@ O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis. Uma conexão TCP separada (e credencial SOCKS) será usada para cada contato e membro do grupo. \nAtenção: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar. - Bom para bateria. O serviço em segundo plano procura por mensagens a cada 10 minutos. Você pode perder chamadas ou mensagens urgentes.]]> + Bom para bateria. O aplicativo procura por mensagens a cada 10 minutos. Você pode perder chamadas ou mensagens urgentes.]]> chamda encerrada %1$s Converse com os desenvolvedores Criar link de grupo @@ -256,12 +256,12 @@ DISPOSITIVO Ferramentas de desenvolvedor conectando (introduzido) - Realçe + Tonalidade Erro ao remover membro Erro ao alterar cargo direto erro - Falha ao carregar o bate-papo + Falha ao carregar a conversa Erro ao atualizar a configuração de conexão Erro ao enviar mensagem Erro ao adicionar membro(s) @@ -317,8 +317,8 @@ Expandir seleção de cargo Erro ao salvar o perfil do grupo Mensagens diretas - habilitado - habilitado para contato + ativado + ativado para contato ativado para você %dm %d min @@ -379,7 +379,7 @@ Nome completo do grupo: Links de grupo Privacidade e segurança aprimoradas - Falha ao carregar o bate-papo + Falha ao carregar as conversas Arquivo: %s Arquivo salvo Os membros do grupo podem enviar mensagens de voz. @@ -403,7 +403,7 @@ Código de segurança incorreto! Instale o SimpleX para terminal Como funciona - Imune a spam e abuso + Imune a spam Vire a câmera Desligar Modo anônimo @@ -551,7 +551,7 @@ formato de mensagem inválido AO VIVO moderado por %s - chat inválido + conversa inválida Abrir o link no navegador pode reduzir a privacidade e a segurança da conexão. Links SimpleX não confiáveis ficarão vermelhos. Link de conexão inválido Certifique-se de que os endereços do servidor SMP estejam no formato correto, separados por linhas e não estejam duplicados. @@ -562,7 +562,7 @@ Executa quando o aplicativo está aberto enviado o envio falhou - Bate-papos + Conversas Colar Link de convite de uso único Enviar perguntas e idéias @@ -764,7 +764,7 @@ Privacidade redefinida Notificações privadas Fazer uma conexão privada - Pessoas podem se conectar com você somente via links compartilhados. + Você decide quem pode se conectar. Pode acontecer quando: \n1. As mensagens expiraram no remetente após 2 dias ou no servidor após 30 dias. \n2. A descriptografia da mensagem falhou porque você ou seu contato usou o backup do banco de dados antigo. @@ -785,7 +785,7 @@ Gravar mensagem de voz Certifique-se de que os endereços do servidor WebRTC ICE estão em formato correto, separados por linha e não estejam duplicados. Conexão e servidores - Configurações de conexão + Configurações avançadas sem detalhes Link de convite de uso único Seu endereço de servidor @@ -803,7 +803,7 @@ Não Usar conexão direta com a internet\? Ocultar: - Seu perfil é guardado no seu diapositivo e é compartilhado somente com seus contatos. Servidores SimpleX não podem ver seu perfil. + Seu perfil é guardado no seu dispositivo e é compartilhado somente com seus contatos. Servidores SimpleX não podem ver seu perfil. Salvar preferências\? Salvar e notificar membros do grupo Erro ao salvar a senha do usuário @@ -857,7 +857,8 @@ Para começar um novo bate-papo Ligar Bem-vindo(a)! - A próxima geração de mensageiros privados + A próxima geração +\nde mensageiros privados PROXY SOCKS A tentativa de alterar a senha do banco de dados não foi concluída. Pare o bate-papo para exportar, importar ou excluir o banco de dados do chat. Você não poderá receber e enviar mensagens enquanto o chat estiver interrompido. @@ -883,7 +884,7 @@ Inicia periodicamente envio não autorizado Toque para iniciar um novo bate-papo - Você não tem bate-papos + Você não tem conversas aguardando resposta… Seu banco de dados de bate-papo atual será EXCLUÍDO e SUBSTITUÍDO pelo importado. \nEsta ação não pode ser desfeita - seu perfil, contatos, mensagens e arquivos serão perdidos de forma irreversível. @@ -902,12 +903,12 @@ Atualizar O app busca novas mensagens periodicamente – ele usa alguns por cento da bateria por dia. O aplicativo não usa notificações por push – os dados do seu dispositivo não são enviados para os servidores. Para receber notificações, por favor, digite a senha do banco de dados - Serviço SimpleX + Serviço de Chat SimpleX Mostrar prévia Mostrar contato e mensagem Mostrar somente contato Compartilhar - Parar bate-papo + Parar conversa Desbloquear A mensagem será excluída para todos os membros. A mensagem será marcada como moderada para todos os membros. @@ -925,7 +926,7 @@ simplexmq: v%s (%2s) Isolamento de transporte Você controla sua conversa! - A 1ª plataforma sem nenhum identificador de usuário – privada por design. + Sem identificadores de usuário. Alto-falante ligado Vídeo desativado Alto-falante desligado @@ -976,7 +977,7 @@ iniciando… aguardando confirmação… Não armazenamos nenhum dos seus contatos ou mensagens (uma vez entregues) nos servidores. - Mensagens omitidas + Mensagens ignoradas Toque para ativar o perfil. Mostrar perfil de chat Mostrar perfil @@ -994,7 +995,7 @@ imagem de pré-visualização do link Para proteger suas informações, ative o bloqueio SimpleX. \nVocê será solicitado a completar a autenticação antes que este recurso seja ativado. - Protocolo de código aberto – qualquer um pode hospedar os servidores. + Qualquer um pode hospedar os servidores. Claro O contato permite ativado @@ -1109,7 +1110,7 @@ Revogar Sobre o endereço SimpleX Secundária adicional - Realçe adicional + Tonalidade adicional Link de uso único Adicione o endereço ao seu perfil, para que seus contatos possam compartilhá-lo com outras pessoas. A atualização do perfil será enviada aos seus contatos. Crie um endereço para permitir que as pessoas se conectem com você. @@ -1139,7 +1140,7 @@ Você não perderá seus contatos se, posteriormente, excluir seu endereço. Endereço SimpleX Quando as pessoas solicitam uma conexão, você pode aceitá-la ou rejeitá-la. - CORES DO TEMA + CORES DA INTERFACE compartilhar com os contatos A atualização do perfil será enviada aos seus contatos. Salvar configurações\? @@ -1238,7 +1239,7 @@ Alterar senha de auto-destruição Se você digitar sua senha de auto-destruição ao abrir o aplicativo: sem texto - Alguns erros não-fatais ocurreram durante importação - pode ver o console de Chat para mais detalhes. + Alguns erros não fatais ocorreram durante importação: Pesquisar Desativado Arquivos e mídia @@ -1281,7 +1282,7 @@ Desligar Corrigir conexão Corrigir conexão\? - Sem bate-papo filtrados + Sem conversas filtradas Renegociar Desfavoritar Renegociar a criptografia\? @@ -1335,7 +1336,7 @@ Enviar recibos de entrega serão habilitados para todos os contatos em todos os perfis visíveis. %s e %s conectados Conectar diretamente\? - Nenhum bate-papo selecionado + Nenhuma conversa selecionada Rascunho de mensagem desativado SimpleX não pode ser executado em segundo plano. Você receberá as notificações somente quando o aplicativo estiver em execução. @@ -1562,7 +1563,7 @@ Erro de renegociação de criptografia Erro ao abrir o navegador Erro ao enviar o convite - Carregando bate-papos… + Carregando conversas… %s foi desconectado]]> %s foi desconectado]]> Apenas um dispositivo pode funcionar ao mesmo tempo @@ -1659,4 +1660,412 @@ Servidores XFTP configurados Verifique sua conexão de internet e tente novamente Verificar atualizações + Modo de cor + Apagar banco de dados desse dispositivo + O endereço do servidor de destino de %1$s é incompatível com o as configurações %2$s do servidor de encaminhamento. + Capacidade excedida - o destinatário não recebeu as mensagens enviadas anteriormente. + Erro do servidor de destino: %1$s + Inativo + Migre de outro dispositivono novo dispositivo e escaneie o QR code.]]> + Confirme se você se lembra da senha do banco de dados para migrá-lo. + Borrar conteúdo + Borrar para melhor privacidade. + Confirmar exclusão do contato? + conectar + Apagar sem notificar + Tonalidade adicional 2 + Confirmar configurações de rede + Todos os modos de cor + Modo escuro + Não é possível enviar mensagem + Cores do chat + Criar + Confirmar upload + administradores + O contato foi apagado. + Permitir chamadas? + Não é possível chamar o contato + Conectando ao contato, por favor aguarde ou volte depois! + Chamadas proibidas! + Não é possível chamar membro do grupo + Não é possível mandar mensagem para o membro do grupo + Controle sua rede + Apague até 20 mensagens por vez. + Arquivar contatos para conversar depois. + Conecte aos seus amigos mais rapidamente. + Escuro + Confirmar arquivos de servidores desconhecidos. + Copiar erro + Conversa migrada! + chamar + O contato será apagado - essa ação não pode ser desfeita! + Beta + A atualização do aplicativo foi baixada + Tema da conversa + Entrega de depuração + Cores do modo escuro + Criando link de arquivo + Permitir downgrade + Todos os usuários + Conectado + Conectando + Perfil atual + Servidores conectados + Conexões ativas + tentativas + Reconhecido + Erros conhecidos + Conexões + Criado + erros de decriptação + Apagado + Erros de exclusão + Pedaços excluídos + Pedaços baixados + Pedaços carregados + Apagar %d mensagens dos membros? + Contato apagado! + Conversa apagada! + Contatos arquivados + Banco de dados da conversa exportado + Continuar + Conexão e status dos servidores. + Repetir download + Recebendo simultaneidade + Redefinir para o tema do usuário + IU Persa + Aviso de entrega de mensagem + Erro: %1$s + Chave incorreta ou conexão desconhecida - provavelmente esta conexão foi excluída. + Essa conversa é protegida por criptografia ponta a ponta + Repetir upload + Erro de conexão ao servidor de encaminhamento %1$s. Por favor tente mais tarde. + A versão do servidor de encaminhamento é incompatível com as configurações de rede: %1$s. + O servidor de encaminhamento %1$s falhou ao se conectar ao servidor de destino %2$s. Por favor tente mais tarde. + O endereço do servidor de encaminhamento é incompatível com as configurações de rede: %1$s. + A versão do servidor de destino de %1$s é incompatível com o servidor de encaminhamento %2$s. + Problemas de rede - a mensagem expirou após muitas tentativas de envio. + Servidor de encaminhamento: %1$s +\nErro: %2$s + Destinatário(s) não podem ver de onde essa mensagem veio. + A mensagem poderá ser entregue mais tarde se o membro se tornar ativo. + Outros servidores SMP + Para proteger seu endereço IP, roteamento privado usa seus servidores SMP para entregar mensagens. + Pular essa versão + Baixar %s (%s) + Download da atualização cancelado + Mostrar lista de conversas em nova janela + Tamanho da fonte + Fundo do papel de parede + Tonalidade do papel de parede + Remover imagem + Zoom + Definir tema padrão + Proibido enviar links SimpleX + Erro ao carregar o arquivo + Falha ao carregar + Carregando arquivo + Estatísticas detalhadas + Erro no servidor de arquivo: %1$s + Salvo de + Baixar + Encaminhar + Status de arquivo: %s + Convidar + Aumentar tamanho da fonte. + Aprimorar aplicativo automaticamente + Redefinir todas as estatísticas? + As estatísticas dos servidores serão redefinidas - isso não poderá ser desfeito! + Link inválido + Por favor cheque se o link SimpleX está correto + criptografia quantum resistant e2e com perfeito sigilo direto, repúdio e recuperação de vazamento.]]> + Essa conversa é protegida por criptografia quantum resistant ponta a ponta + Por favor tente mais tarde. + Selecionado %d + SimpleX links não permitidos + Arquivos e mídia não permitidos + Mensagem + Erro de arquivo temporário + mensagem + pesquisar + vídeo + Roteamento privado + NÃO use roteamento privado. + Abrir configurações + Fone de ouvido + Erro ao iniciar o WebView. Atualize seu sistema para a nova versão. Por favor contate os desenvolvedores. +\nErro: %s + Desativado + Forte + Ativar em conversas diretas (BETA)! + Finalizar migração em outro dispositivo. + Claro + A origem da mensagem permanece privada. + Migrar aqui + Migrando + Nova experiência de conversa 🎉 + Novas opções de mídia + Ou cole o link do arquivo + Reproduzir da lista de conversa. + Redefinir todas as estatísticas + Aviso: iniciar conversa em múltiplos dispositivos não é suportado e pode causar falhas na entrega de mensagens + Internet cabeada + não deve usar a mesma base de dados em dois dispositivos.]]> + Membros do grupo podem enviar link SimpleX + Importando arquivo + Modo claro + Ativado para + Ao conectar em chamadas de áudio de vídeo. + Migrar dispositivo + Erro ao salvar configurações + Arquivo exportado não existe + %s carregados + Para continuar, a conversa precisa ser interrompida. + Outro + Sem conexão de rede + As preferências de conversa selecionadas proíbem essa mensagem. + Erro de arquivo + Redefinir cor + Sons de chamada + Formato das imagens de perfil + IU Lituana + Roteamento de mensagem privada 🚀 + Novos temas de conversa + Com uso de bateria reduzida. + Falha no download + Falha na importação + Baixando arquivo + Repetir importação + Você pode tentar novamente. + Erro ao exportar banco de dados de conversa + Erro ao verificar a palavra-chave: + salvo + salvo de %s + Encaminhado de + Mensagens de voz não permitidas + Nova mensagem + Colar link + Links SimpleX + Encaminhar e salvar mensagens + Suave + Médio + Você precisa permitir seu contato ligue para poder ligar para ele. + Redefinir para o tema do aplicativo + Resposta recebida + Boa tarde! + Bom dia! + Baixe novas versões no GitHub. + encaminhado + O arquivo foi deletado ou o link está inválido + Abrir tela de migração + Grupos seguros + Preparando upload + Conexão de rede + Servidores desconhecidos! + Sem Tor ou VPN, seu endereço de IP ficará visível para esses relays XFTP +\n%1$s. + Erro ao exibir notificação, contate os desenvolvedores. + Salvo + Encaminhado + Servidores desconhecidos + Desprotegido + Nunca + Modo de roteamento de mensagens + Conceder permissões + Alto falante + Headphones + Sem Tor ou VPN, seu endereço de IP ficará visível para servidores de arquivo. + ARQUIVOS + Fotos de perfil + ROTEAMENTO DE MENSAGEM PRIVADA + criptografia padrão ponta a ponta + proprietários + Migrar para outro dispositivo + Verificar palavra-passe + WiFi + Alternar lista de conversa: + Você pode mudar isso em configurações de Aparência. + desativado + nenhum + informações da fila do servidor: %1$s +\n +\núltima mensagem recebida: %2$s + Por favor peça para seu contato ativar as chamadas. + Enviar mensagem para ativar chamadas. + Salvar e reconectar + Conexão TCP + Barra de ferramentas de conversa acessível + Isso protege seu endereço de IP e conexões. + Use o aplicativo com uma mão. + Arquivos carregados + Baixado + Erro ao redefinir estatísticas + Redefinir + Status de arquivo + Informações da fila de mensagens + Sistema + Repetir + Escala + Preencher + Ajustar + Links SimpleX são proibidos neste grupo. + Migrar para outro dispositivo via QR code. + Chamadas picture-in-picture + Use o aplicativo enquanto está em chamada. + Será ativado em conversas diretas! + Receber arquivos de forma segura + Gerenciamento de rede + Faça suas conversas terem uma aparência diferente! + Conexão de rede mais confiável. + Preparando download + %s baixados + Colar link de arquivo + Insira a palavra-chave + Erro ao baixar o arquivo + Ou de forma segura compartilhe esse link de arquivo + Verifique a palavra-passe do banco de dados + criptografia ponta-a-ponta com perfeito sigilo direto, repúdio e recuperação de vazamento.]]> + Arquivo não encontrado - provavelmente o arquivo foi excluído ou cancelado. + Chave incorreta ou arquivo de pedaço de endereço - provavelmente o arquivo foi excluído. + Mande mensagens diretamente quando o seu endereço de IP está protegido e o servidor de destino não suporta roteamento privado. + Use roteamento privado em servidores desconhecidos. + Escanear / Colar link + Baixando detalhes de link + Finalizar migração + Criptografia Quantum resistant + Migração concluída + Proteja seu endereço de IP dos retransmissores de mensagem escolhidos por seus contatos. +\nAtive nas configurações *Redes e servidores* . + Iniciar conversa + Configurações + abrir + Manter conversa + Apenas excluir conversa + Outros servidores XFTP + Use roteamento privado em servidores desconhecidos quando o endereço de IP não está protegido. + Sim + Instalar atualização + Atualização disponível: %s + Abrir local do arquivo + Desativar + Microfone + Conceder nas configurações + Encontre essa permissão nas configurações do Android e conceda-a manualmente. + O aplicativo irá perguntar para confirmar os downloads de servidores de arquivo desconhecidos (exceto .onion ou quando o proxy SOCKS estiver habilitado). + Tema de perfil + Defina uma palavra-chave + criptografia quantum resistant e2e + Convidar + Status da mensagem + Entrega de mensagens aprimorada + Quadrado, circulo, ou qualquer coisa entre eles. + Por favor verifique se o celular e o computador estão conectados na mesma rede local e o firewall do computador permite a conexão. +\nPor favor compartilhe qualquer outro problema com os desenvolvedores. + Esse link foi usado em outros dispositivo móvel, por favor crie um novo link no computador. + Erro ao excluir banco de dados + Por favor confirme que as configurações de rede estão corretas para este dispositivo. + Parando conversa + Você pode tentar novamente. + A versão do servidor é incompatível com seu aplicativo: %1$s. + Erro de roteamento privado + O endereço do servidor é incompatível com as configurações de rede: %1$s. + Servidor de encaminhamento: %1$s +\nErro no servidor de destino: %2$s + Endereço do servidor é incompatível com as configurações de rede. + A versão do servidor é incompatível com as configurações de rede. + Encaminhar mensagem… + Quando IP oculto + Proteger endereço IP + Enviar resposta + Arquivos + Não + Baixando atualização do aplicativo, não feche o aplicativo + Conceder permissão para fazer chamadas + Link inválido + Detalhes + Erros + Mensagens recebidas + Mensagens enviadas + Sem informação, tente recarregar + Informação dos servidores + Mostrando informação para + Estatísticas + Sessões de transporte + Recepção de mensagem + Pendente + Começando de %s. +\nTodos os dados são privados do seu dispositivo. + Total + Servidores proxiados + Servidores conectados anteriormente + Reconecte todos os servidores conectados para forçar entrega de mensagem. Isso usa tráfego adicional. + Reconectar servidor? + Reconectar servidores? + Reconectar servidor para forçar entrega de mensagem. Isso usa tráfego adicional. + Você não está conectado nesses servidores. Roteamento privado é usado para entregar mensagens para eles. + Erro + Erro ao reconectar servidor + Erro ao reconectar servidores + Reconectar todos os servidores + Reconectar + Enviar diretamente + Enviar mensagens + Enviar total + Enviar via proxy + Servidor SMP + Mensagens recebidas + Total recebido + Receber erros + Começando de %s. + Servidor XFTP + Seguro + Enviar erros + Inscrito + duplicatas + expirada + outro + Erros de inscrição + outros erros + Proxied + Inscrições ignoradas + Erros de download + Arquivos baixados + Endereço do servidor + Tamanho + Erros de upload + Abrir configurações de servidor + Selecione + As mensagens serão excluídas para todos os membros. + As mensagens serão marcadas como moderadas para todos os membros. + Mensagem encaminhada + Ainda não há conexão direta, a mensagem é encaminhada pelo administrador. + Membro inativo + Nada selecionado + Mensagens serão marcadas para exclusão. O(s) destinatário(s) poderá(ão) revelar essas mensagens. + Você ainda pode ver a conversa com %1$s na lista de conversas. + Nenhum contato filtrado + Seus contatos + NÃO envie mensagens diretamente, mesmo que o seu servidor ou o servidor de destino não suporte roteamento privado. + Mande mensagens diretamente quando o seu servidor ou o servidor de destino não suporta roteamento privado. + Retorno de roteamento de mensagens + Mostrar status da mensagem + Migrar de outro dispositivo + Status da mensagem: %s + Carregado + Servidores de mensagem + Servidores de mídia e arquivo + Mostrar porcentagem + Proxy SOCKS + Você pode salvar o arquivo exportado. + Você pode migrar o banco de dados exportado. + Alguns arquivos não foram exportados + Você pode enviar mensagens para %1$s de Contatos arquivados. + Redefinir todas as dicas + Desativado + Estável + Instalado com sucesso + Por favor reinicie o aplicativo. + Me lembre mais tarde + Para ser notificado sobre os novos lançamentos, habilite a checagem periódica de versões Estáveis e Beta. + Barra de ferramentas de conversa acessível \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index cd34f2efff..b16c96b636 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -724,4 +724,61 @@ Lỗi kết nối lại máy chủ Lỗi Lỗi khôi phục thống kê + Lỗi cập nhật liên kết nhóm + lỗi hiển thị tin nhắn + lỗi hiển thị nội dung + Lỗi lưu máy chủ ICE + Lỗi lưu máy chủ XFTP + Lỗi lưu máy chủ SMP + Lỗi gửi tin nhắn + Lỗi khởi động ứng dụng + Lỗi dừng ứng dụng + Lỗi hiển thị thông báo, liên hệ với nhà phát triển. + Lỗi lưu mật khẩu người dùng + Lỗi lưu cài đặt + Lỗi gửi lời mời + Lỗi chuyển đổi hồ sơ! + Lỗi đồng bộ kết nối + Lỗi cài đặt địa chỉ + Dù đã tắt trong cuộc trò chuyện. + Lỗi cập nhật cấu hình mạng + Lỗi cập nhật quyền riêng tư người dùng + Mở rộng chọn quyền hạn + THỬ NGHIỆM + Mở rộng + Thoát mà không lưu + đã hết hạn + Lỗi xác thực mật khẩu: + Quá trình thực hiện chức năng mất quá nhiều thời gian: %1$dgiây: %2$s + Tính năng thử nghiệm + Xuất cơ sở dữ liệu + Lỗi tải lên kho lưu trữ + Tập tin đã xuất không tồn tại + TẬP TIN + Không thể tải tin nhắn + Không tìm thấy tập tin - có thể tập tin đã bị xóa và hủy bỏ. + Lỗi tập tin + Xuất chủ đề + Không thể tải tin nhắn + Tập tin + Nhanh chóng và không cần phải đợi người gửi hoạt động! + Không tìm thấy tập tin + Tập tin: %s + Tham gia nhanh chóng hơn và xử lý tin nhắn ổn định hơn. + Tập tin + Yêu thích + Tập tin + Lỗi máy chủ tệp: %1$s + Trạng thái tệp + Tệp và phương tiện truyền thông không được cho phép + Tệp và phương tiện truyền thông bị cấm trong nhóm này. + Tệp sẽ bị xóa khỏi máy chủ. + Tệp & phương tiện truyền thông + Tệp và phương tiện truyền thông bị cấm! + Tệp sẽ được nhận khi liên hệ của bạn hoàn tất quá trình tải lên. + Tệp và phương tiện truyền thông + Tệp đã bị xóa hoặc liên kết không hợp lệ + Tệp đã được lưu + Tệp sẽ được nhận khi liên hệ của bạn hoạt động, vui lòng chờ hoặc kiểm tra lại sau! + Trạng thái tệp: %s \ No newline at end of file From ea320f531fbbd09097ed7fd931e3d2c26946684a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 16 Sep 2024 16:53:11 +0100 Subject: [PATCH 124/440] Translated using Weblate (Spanish) (#4891) 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/es/ Co-authored-by: Eraorahan --- website/langs/es.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/langs/es.json b/website/langs/es.json index 443f04b2ac..989d89557c 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -29,7 +29,7 @@ "home": "Inicio", "simplex-explained-tab-3-p-1": "Para cada cola los servidores disponen de credenciales separadas y anónimas, por lo que desconocen a qué usuarios pertenecen.", "hero-p-1": "Las demás aplicaciones usan ID de usuario: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.
    SimpleX no los tiene, ni siquiera números aleatorios.
    Esto mejora radicalmente su privacidad.", - "hero-2-header-desc": "El video muestra cómo se conecta con sus amistades a través del código QR de un solo uso, en persona o a través de videollamada. También puede conectarse compartiendo un enlace de invitación.", + "hero-2-header-desc": "El vídeo muestra cómo se conecta con sus amistades a través del código QR de un solo uso, en persona o a través de videollamada. También puede conectarse compartiendo un enlace de invitación.", "feature-7-title": "Almacenamiento portable y cifrado — podrá transferir su perfil a otro dispositivo", "simplex-private-card-4-point-2": "Para usar SimpleX a través de Tor, instala la aplicación Orbot y activa el proxy SOCKS5 (o VPN en iOS).", "simplex-private-card-3-point-1": "Para las conexiones cliente servidor se usan exclusivamente el protocolo TLS 1.2/1.3 con algoritmos robustos.", @@ -52,7 +52,7 @@ "feature-8-title": "Modo incógnito —
    exclusivo de SimpleX Chat", "simplex-private-1-title": "Doble capa de
    cifrado de extremo a extremo", "simplex-private-2-title": "Capa de cifrado
    adicional en el servidor", - "simplex-private-3-title": "Transporte TLS
    seguro y auténticado", + "simplex-private-3-title": "Transporte TLS
    seguro y autenticado", "simplex-private-4-title": "Acceso opcional
    a través de Tor", "simplex-private-7-title": "Verificación de la
    integridad del mensaje", "feature-4-title": "Mensajes de voz cifrados E2E", @@ -252,7 +252,7 @@ "hero-overlay-card-3-p-1": "Trail of Bits es una consultora de seguridad y tecnología líder cuyos clientes incluyen grandes tecnológicas, agencias gubernamentales e importantes proyectos de blockchain.", "docs-dropdown-9": "Descargas", "please-enable-javascript": "Habilita JavaScript para ver el código QR.", - "please-use-link-in-mobile-app": "Usa el enlace en la apliación móvil,", + "please-use-link-in-mobile-app": "Usa el enlace en la apliación móvil", "docs-dropdown-10": "Transparencia", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Seguridad" From 40e93cc61ebd802f0ecba3612a0bd8964a84e496 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 16 Sep 2024 18:05:09 +0100 Subject: [PATCH 125/440] core: reduce max message sizes (#4882) * core: reduce max message sizes * reduce * comment --- src/Simplex/Chat/Protocol.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 6d4b2eda68..f827568fa7 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -540,21 +540,21 @@ data ExtMsgContent = ExtMsgContent {content :: MsgContent, file :: Maybe FileInv $(JQ.deriveJSON defaultJSON ''QuotedMsg) -- this limit reserves space for metadata in forwarded messages --- 15780 (limit used for fileChunkSize) - 161 (x.grp.msg.forward overhead) = 15619, round to 15610 +-- 15780 (limit used for fileChunkSize) - 161 (x.grp.msg.forward overhead) = 15619, - 16 for block encryption ("rounded" to 15602) maxEncodedMsgLength :: Int -maxEncodedMsgLength = 15610 +maxEncodedMsgLength = 15602 -- maxEncodedMsgLength - 2222, see e2eEncUserMsgLength in agent maxCompressedMsgLength :: Int -maxCompressedMsgLength = 13388 +maxCompressedMsgLength = 13380 -- maxEncodedMsgLength - delta between MSG and INFO + 100 (returned for forward overhead) -- delta between MSG and INFO = e2eEncUserMsgLength (no PQ) - e2eEncConnInfoLength (no PQ) = 1008 maxEncodedInfoLength :: Int -maxEncodedInfoLength = 14702 +maxEncodedInfoLength = 14694 maxCompressedInfoLength :: Int -maxCompressedInfoLength = 10976 -- maxEncodedInfoLength - 3726, see e2eEncConnInfoLength in agent +maxCompressedInfoLength = 10968 -- maxEncodedInfoLength - 3726, see e2eEncConnInfoLength in agent data EncodedChatMessage = ECMEncoded ByteString | ECMLarge From 05aab35a1f96ce5b0ca64d543a427ef27930bf44 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 16 Sep 2024 18:22:50 +0100 Subject: [PATCH 126/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5d777dd158..dd97170700 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -167,11 +167,6 @@ 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; - 64A208622C8F2CCC00AE9D01 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */; }; - 64A208632C8F2CCC00AE9D01 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085E2C8F2CCB00AE9D01 /* libffi.a */; }; - 64A208642C8F2CCC00AE9D01 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */; }; - 64A208652C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */; }; - 64A208662C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; }; @@ -222,6 +217,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; + E55128D32C989E13001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128CE2C989E12001D165C /* libgmpxx.a */; }; + E55128D42C989E13001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128CF2C989E12001D165C /* libffi.a */; }; + E55128D52C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */; }; + E55128D62C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */; }; + E55128D72C989E13001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D22C989E13001D165C /* libgmp.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -508,11 +508,6 @@ 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; - 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 64A2085E2C8F2CCB00AE9D01 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a"; sourceTree = ""; }; - 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a"; sourceTree = ""; }; 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; }; 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; }; 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = ""; }; @@ -561,6 +556,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; + E55128CE2C989E12001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E55128CF2C989E12001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a"; sourceTree = ""; }; + E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a"; sourceTree = ""; }; + E55128D22C989E13001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -651,13 +651,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 64A208662C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a in Frameworks */, - 64A208622C8F2CCC00AE9D01 /* libgmpxx.a in Frameworks */, - 64A208652C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a in Frameworks */, + E55128D32C989E13001D165C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + E55128D72C989E13001D165C /* libgmp.a in Frameworks */, + E55128D62C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 64A208632C8F2CCC00AE9D01 /* libffi.a in Frameworks */, - 64A208642C8F2CCC00AE9D01 /* libgmp.a in Frameworks */, + E55128D42C989E13001D165C /* libffi.a in Frameworks */, + E55128D52C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -735,11 +735,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 64A2085E2C8F2CCB00AE9D01 /* libffi.a */, - 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */, - 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */, - 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */, - 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */, + E55128CF2C989E12001D165C /* libffi.a */, + E55128D22C989E13001D165C /* libgmp.a */, + E55128CE2C989E12001D165C /* libgmpxx.a */, + E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */, + E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */, ); path = Libraries; sourceTree = ""; From af993529f907337eb313d270743c4c6539d93741 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 17 Sep 2024 12:23:50 +0100 Subject: [PATCH 127/440] core: migrate SOCKS proxy settings (#4894) --- src/Simplex/Chat/AppSettings.hs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Simplex/Chat/AppSettings.hs b/src/Simplex/Chat/AppSettings.hs index 6d23a19ba1..863302fad2 100644 --- a/src/Simplex/Chat/AppSettings.hs +++ b/src/Simplex/Chat/AppSettings.hs @@ -28,6 +28,7 @@ data LockScreenCalls = LSCDisable | LSCShow | LSCAccept deriving (Show) data AppSettings = AppSettings { appPlatform :: Maybe AppPlatform, networkConfig :: Maybe NetworkConfig, + networkProxy :: Maybe NetworkProxy, privacyEncryptLocalFiles :: Maybe Bool, privacyAskToApproveRelays :: Maybe Bool, privacyAcceptImages :: Maybe Bool, @@ -57,11 +58,24 @@ data AppSettings = AppSettings } deriving (Show) +data NetworkProxy = NetworkProxy + { host :: Text, + port :: Int, + auth :: NetworkProxyAuth, + username :: Text, + password :: Text + } + deriving (Show) + +data NetworkProxyAuth = NPAUsername | NPAIsolate + deriving (Show) + defaultAppSettings :: AppSettings defaultAppSettings = AppSettings { appPlatform = Nothing, networkConfig = Just defaultNetworkConfig, + networkProxy = Nothing, privacyEncryptLocalFiles = Just True, privacyAskToApproveRelays = Just True, privacyAcceptImages = Just True, @@ -95,6 +109,7 @@ defaultParseAppSettings = AppSettings { appPlatform = Nothing, networkConfig = Nothing, + networkProxy = Nothing, privacyEncryptLocalFiles = Nothing, privacyAskToApproveRelays = Nothing, privacyAcceptImages = Nothing, @@ -128,6 +143,7 @@ combineAppSettings platformDefaults storedSettings = AppSettings { appPlatform = p appPlatform, networkConfig = p networkConfig, + networkProxy = p networkProxy, privacyEncryptLocalFiles = p privacyEncryptLocalFiles, privacyAskToApproveRelays = p privacyAskToApproveRelays, privacyAcceptImages = p privacyAcceptImages, @@ -167,12 +183,17 @@ $(JQ.deriveJSON (enumJSON $ dropPrefix "NPM") ''NotificationPreviewMode) $(JQ.deriveJSON (enumJSON $ dropPrefix "LSC") ''LockScreenCalls) +$(JQ.deriveJSON (enumJSON $ dropPrefix "NPA") ''NetworkProxyAuth) + +$(JQ.deriveJSON defaultJSON ''NetworkProxy) + $(JQ.deriveToJSON defaultJSON ''AppSettings) instance FromJSON AppSettings where parseJSON (J.Object v) = do appPlatform <- p "appPlatform" networkConfig <- p "networkConfig" + networkProxy <- p "networkProxy" privacyEncryptLocalFiles <- p "privacyEncryptLocalFiles" privacyAskToApproveRelays <- p "privacyAskToApproveRelays" privacyAcceptImages <- p "privacyAcceptImages" @@ -203,6 +224,7 @@ instance FromJSON AppSettings where AppSettings { appPlatform, networkConfig, + networkProxy, privacyEncryptLocalFiles, privacyAskToApproveRelays, privacyAcceptImages, From c13c7baaaf44f18c891b3597c4fe479998359462 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:07:29 +0000 Subject: [PATCH 128/440] desktop: less distance from edge to call icon (#4898) --- .../chat/simplex/common/views/chatlist/ChatListView.desktop.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt index 9e4eeb0c96..ef5eef3fdc 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt @@ -35,7 +35,7 @@ actual fun ActiveCallInteractiveArea(call: Call) { ) { Box( Modifier - .padding(end = 71.dp, bottom = 92.dp) + .padding(end = 15.dp, bottom = 92.dp) .size(67.dp) .combinedClickable(onClick = { val chat = chatModel.getChat(call.contact.id) From 665d9dcd007b4e7bcdd5ad089ffc875c68746f60 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 17 Sep 2024 17:34:24 +0100 Subject: [PATCH 129/440] ios: SOCKS proxy UI (#4893) * ios: SOCKS proxy UI * update network config * proxy * adapt * move, dont default to localhost:9050 * move socks proxy to defaults * sock proxy preference * rename * rename * fix * fix --------- Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../Views/ChatList/ServersSummaryView.swift | 24 +++-- .../Views/Migration/MigrateFromDevice.swift | 6 ++ .../AdvancedNetworkSettings.swift | 97 +++++++++++++++++-- .../Views/UserSettings/AppSettings.swift | 11 ++- .../Views/UserSettings/SettingsView.swift | 3 + apps/ios/SimpleXChat/APITypes.swift | 66 ++++++++++++- apps/ios/SimpleXChat/AppGroup.swift | 7 +- 7 files changed, 197 insertions(+), 17 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 477a78e36d..22ea78f27b 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -407,12 +407,18 @@ struct ServersSummaryView: View { struct SubscriptionStatusIndicatorView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let onionHosts = networkUseOnionHostsGroupDefault.get() - let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) + let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage( + online: m.networkInfo.online, + usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, + subs: subs, + hasSess: hasSess, + primaryColor: theme.colors.primary + ) if #available(iOS 16.0, *) { Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue) .foregroundColor(color) @@ -425,26 +431,32 @@ struct SubscriptionStatusIndicatorView: View { struct SubscriptionStatusPercentageView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let onionHosts = networkUseOnionHostsGroupDefault.get() - let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) + let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage( + online: m.networkInfo.online, + usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, + subs: subs, + hasSess: hasSess, + primaryColor: theme.colors.primary + ) Text(verbatim: "\(Int(floor(statusPercent * 100)))%") .foregroundColor(.secondary) .font(.caption) } } -func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ hasSess: Bool) -> (Color, Double, Double, Double) { +func subscriptionStatusColorAndPercentage(online: Bool, usesProxy: Bool, subs: SMPServerSubs, hasSess: Bool, primaryColor: Color) -> (Color, Double, Double, Double) { func roundedToQuarter(_ n: Double) -> Double { n >= 1 ? 1 : n <= 0 ? 0 : (n * 4).rounded() / 4 } - let activeColor: Color = onionHosts == .require ? .indigo : .accentColor + let activeColor: Color = usesProxy ? .indigo : primaryColor let noConnColorAndPercent: (Color, Double, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1, 0) let activeSubsRounded = roundedToQuarter(subs.shareOfActive) diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 73e5b97057..829cea0165 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -529,9 +529,15 @@ struct MigrateFromDevice: View { } case let .sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs): let cfg = getNetCfg() + let proxy: NetworkProxy? = if cfg.socksProxy == nil { + nil + } else { + networkProxyDefault.get() + } let data = MigrationFileLinkData.init( networkConfig: MigrationFileLinkData.NetworkConfig( socksProxy: cfg.socksProxy, + networkProxy: proxy, hostMode: cfg.hostMode, requiredHostMode: cfg.requiredHostMode ) diff --git a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift index 99c0a588eb..9884c6e877 100644 --- a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift @@ -36,6 +36,10 @@ struct AdvancedNetworkSettings: View { @State private var showSettingsAlert: NetworkSettingsAlert? @State private var onionHosts: OnionHosts = .no @State private var showSaveDialog = false + @State private var netProxy = networkProxyDefault.get() + @State private var currentNetProxy = networkProxyDefault.get() + @State private var useNetProxy = false + @State private var netProxyAuth = false var body: some View { VStack { @@ -102,6 +106,76 @@ struct AdvancedNetworkSettings: View { .foregroundColor(theme.colors.secondary) } + Section { + Toggle("Use SOCKS proxy", isOn: $useNetProxy) + Group { + TextField("IP address", text: $netProxy.host) + TextField( + "Port", + text: Binding( + get: { netProxy.port > 0 ? "\(netProxy.port)" : "" }, + set: { s in + netProxy.port = if let port = Int(s), port > 0 { + port + } else { + 0 + } + } + ) + ) + Toggle("Proxy requires password", isOn: $netProxyAuth) + if netProxyAuth { + TextField("Username", text: $netProxy.username) + PassphraseField( + key: $netProxy.password, + placeholder: "Password", + valid: NetworkProxy.validCredential(netProxy.password) + ) + } + } + .if(!useNetProxy) { $0.foregroundColor(theme.colors.secondary) } + .disabled(!useNetProxy) + } header: { + HStack { + Text("SOCKS proxy").foregroundColor(theme.colors.secondary) + if useNetProxy && !netProxy.valid { + Spacer() + Image(systemName: "exclamationmark.circle.fill").foregroundColor(.red) + } + } + } footer: { + if netProxyAuth { + Text("Your credentials may be sent unencrypted.") + .foregroundColor(theme.colors.secondary) + } else { + Text("Do not use credentials with proxy.") + .foregroundColor(theme.colors.secondary) + } + } + .onChange(of: useNetProxy) { useNetProxy in + netCfg.socksProxy = useNetProxy && currentNetProxy.valid + ? currentNetProxy.toProxyString() + : nil + netProxy = currentNetProxy + netProxyAuth = netProxy.username != "" || netProxy.password != "" + } + .onChange(of: netProxyAuth) { netProxyAuth in + if netProxyAuth { + netProxy.auth = currentNetProxy.auth + netProxy.username = currentNetProxy.username + netProxy.password = currentNetProxy.password + } else { + netProxy.auth = .username + netProxy.username = "" + netProxy.password = "" + } + } + .onChange(of: netProxy) { netProxy in + netCfg.socksProxy = useNetProxy && netProxy.valid + ? netProxy.toProxyString() + : nil + } + Section { Picker("Use .onion hosts", selection: $onionHosts) { ForEach(OnionHosts.values, id: \.self) { Text($0.text) } @@ -156,19 +230,19 @@ struct AdvancedNetworkSettings: View { Section { Button("Reset to defaults") { - updateNetCfgView(NetCfg.defaults) + updateNetCfgView(NetCfg.defaults, NetworkProxy.def) } .disabled(netCfg == NetCfg.defaults) Button("Set timeouts for proxy/VPN") { - updateNetCfgView(netCfg.withProxyTimeouts) + updateNetCfgView(netCfg.withProxyTimeouts, netProxy) } .disabled(netCfg.hasProxyTimeouts) Button("Save and reconnect") { showSettingsAlert = .update } - .disabled(netCfg == currentNetCfg) + .disabled(netCfg == currentNetCfg || (useNetProxy && !netProxy.valid)) } } } @@ -182,7 +256,8 @@ struct AdvancedNetworkSettings: View { if cfgLoaded { return } cfgLoaded = true currentNetCfg = getNetCfg() - updateNetCfgView(currentNetCfg) + currentNetProxy = networkProxyDefault.get() + updateNetCfgView(currentNetCfg, currentNetProxy) } .alert(item: $showSettingsAlert) { a in switch a { @@ -206,7 +281,7 @@ struct AdvancedNetworkSettings: View { if netCfg == currentNetCfg { dismiss() cfgLoaded = false - } else { + } else if !useNetProxy || netProxy.valid { showSaveDialog = true } }) @@ -221,18 +296,26 @@ struct AdvancedNetworkSettings: View { } } - private func updateNetCfgView(_ cfg: NetCfg) { + private func updateNetCfgView(_ cfg: NetCfg, _ proxy: NetworkProxy) { netCfg = cfg + netProxy = proxy onionHosts = OnionHosts(netCfg: netCfg) enableKeepAlive = netCfg.enableKeepAlive keepAliveOpts = netCfg.tcpKeepAlive ?? KeepAliveOpts.defaults + useNetProxy = netCfg.socksProxy != nil + netProxyAuth = switch netProxy.auth { + case .username: netProxy.username != "" || netProxy.password != "" + case .isolate: false + } } private func saveNetCfg() -> Bool { do { try setNetworkConfig(netCfg) currentNetCfg = netCfg - setNetCfg(netCfg) + setNetCfg(netCfg, networkProxy: useNetProxy ? netProxy : nil) + currentNetProxy = netProxy + networkProxyDefault.set(netProxy) return true } catch let error { let err = responseError(error) diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index bd829552f4..19260ce573 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -19,9 +19,15 @@ extension AppSettings { val.hostMode = .publicHost val.requiredHostMode = true } - val.socksProxy = nil - setNetCfg(val) + if val.socksProxy != nil { + val.socksProxy = networkProxy?.toProxyString() + setNetCfg(val, networkProxy: networkProxy) + } else { + val.socksProxy = nil + setNetCfg(val, networkProxy: nil) + } } + if let val = networkProxy { networkProxyDefault.set(val) } if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) } if let val = privacyAskToApproveRelays { privacyAskToApproveRelaysGroupDefault.set(val) } if let val = privacyAcceptImages { @@ -63,6 +69,7 @@ extension AppSettings { let def = UserDefaults.standard var c = AppSettings.defaults c.networkConfig = getNetCfg() + c.networkProxy = networkProxyDefault.get() c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get() c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get() c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get() diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 463ac4ae07..f1140575b7 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -75,6 +75,8 @@ let DEFAULT_SYSTEM_DARK_THEME = "systemDarkTheme" let DEFAULT_CURRENT_THEME_IDS = "currentThemeIds" let DEFAULT_THEME_OVERRIDES = "themeOverrides" +let DEFAULT_NETWORK_PROXY = "networkProxy" + let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen" let defaultChatItemRoundness: Double = 0.75 @@ -251,6 +253,7 @@ public class CodableDefault { } } +let networkProxyDefault: CodableDefault = CodableDefault(defaults: UserDefaults.standard, forKey: DEFAULT_NETWORK_PROXY, withDefault: NetworkProxy.def) struct SettingsView: View { @Environment(\.colorScheme) var colorScheme diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 7f030cb838..3cc2202bff 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -8,6 +8,7 @@ import Foundation import SwiftUI +import Network public let jsonDecoder = getJSONDecoder() public let jsonEncoder = getJSONEncoder() @@ -1497,6 +1498,63 @@ public struct KeepAliveOpts: Codable, Equatable { public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) } +public struct NetworkProxy: Equatable, Codable { + public var host: String = "" + public var port: Int = 0 + public var auth: NetworkProxyAuth = .username + public var username: String = "" + public var password: String = "" + + public static var def: NetworkProxy { + NetworkProxy() + } + + public var valid: Bool { + let hostOk = switch NWEndpoint.Host(host) { + case .ipv4: true + case .ipv6: true + default: false + } + return hostOk && + port > 0 && port <= 65535 && + NetworkProxy.validCredential(username) && NetworkProxy.validCredential(password) + } + + public static func validCredential(_ s: String) -> Bool { + !s.contains(":") && !s.contains("@") + } + + public func toProxyString() -> String? { + if !valid { return nil } + var res = "" + switch auth { + case .username: + let usernameTrimmed = username.trimmingCharacters(in: .whitespaces) + let passwordTrimmed = password.trimmingCharacters(in: .whitespaces) + if usernameTrimmed != "" || passwordTrimmed != "" { + res += usernameTrimmed + ":" + passwordTrimmed + "@" + } else { + res += "@" + } + case .isolate: () + } + if host != "" { + if host.contains(":") { + res += "[\(host.trimmingCharacters(in: [" ", "[", "]"]))]" + } else { + res += host.trimmingCharacters(in: .whitespaces) + } + } + res += ":\(port)" + return res + } +} + +public enum NetworkProxyAuth: String, Codable { + case username + case isolate +} + public enum NetworkStatus: Decodable, Equatable { case unknown case connected @@ -2120,11 +2178,13 @@ public struct MigrationFileLinkData: Codable { public struct NetworkConfig: Codable { let socksProxy: String? + let networkProxy: NetworkProxy? let hostMode: HostMode? let requiredHostMode: Bool? - public init(socksProxy: String?, hostMode: HostMode?, requiredHostMode: Bool?) { + public init(socksProxy: String?, networkProxy: NetworkProxy?, hostMode: HostMode?, requiredHostMode: Bool?) { self.socksProxy = socksProxy + self.networkProxy = networkProxy self.hostMode = hostMode self.requiredHostMode = requiredHostMode } @@ -2133,6 +2193,7 @@ public struct MigrationFileLinkData: Codable { return if let hostMode, let requiredHostMode { NetworkConfig( socksProxy: nil, + networkProxy: nil, hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode, requiredHostMode: requiredHostMode ) @@ -2152,6 +2213,7 @@ public struct MigrationFileLinkData: Codable { public struct AppSettings: Codable, Equatable { public var networkConfig: NetCfg? = nil + public var networkProxy: NetworkProxy? = nil public var privacyEncryptLocalFiles: Bool? = nil public var privacyAskToApproveRelays: Bool? = nil public var privacyAcceptImages: Bool? = nil @@ -2183,6 +2245,7 @@ public struct AppSettings: Codable, Equatable { var empty = AppSettings() let def = AppSettings.defaults if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } + if networkProxy != def.networkProxy { empty.networkProxy = networkProxy } if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } @@ -2215,6 +2278,7 @@ public struct AppSettings: Codable, Equatable { public static var defaults: AppSettings { AppSettings ( networkConfig: NetCfg.defaults, + networkProxy: NetworkProxy.def, privacyEncryptLocalFiles: true, privacyAskToApproveRelays: true, privacyAcceptImages: true, diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index bd38f3568c..455607ddea 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -35,6 +35,7 @@ public let GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS = "privacyAskToApproveRel // replaces DEFAULT_PROFILE_IMAGE_CORNER_RADIUS public let GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius" let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount" +public let GROUP_DEFAULT_NETWORK_SOCKS_PROXY = "networkSocksProxy" let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE = "networkSMPProxyMode" @@ -327,6 +328,7 @@ public class Default { } public func getNetCfg() -> NetCfg { + let socksProxy = groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) let onionHosts = networkUseOnionHostsGroupDefault.get() let (hostMode, requiredHostMode) = onionHosts.hostMode let sessionMode = networkSessionModeGroupDefault.get() @@ -349,6 +351,7 @@ public func getNetCfg() -> NetCfg { tcpKeepAlive = nil } return NetCfg( + socksProxy: socksProxy, hostMode: hostMode, requiredHostMode: requiredHostMode, sessionMode: sessionMode, @@ -365,11 +368,13 @@ public func getNetCfg() -> NetCfg { ) } -public func setNetCfg(_ cfg: NetCfg) { +public func setNetCfg(_ cfg: NetCfg, networkProxy: NetworkProxy?) { networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg)) networkSessionModeGroupDefault.set(cfg.sessionMode) networkSMPProxyModeGroupDefault.set(cfg.smpProxyMode) networkSMPProxyFallbackGroupDefault.set(cfg.smpProxyFallback) + let socksProxy = networkProxy?.toProxyString() + groupDefaults.set(socksProxy, forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) From 17b55c51c5f3f128b49811ad2d20777b02ab6fa2 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:50:26 +0400 Subject: [PATCH 130/440] core: update statuses of all batched messages on SENT, RCVD (#4888) * core: update statuses of all batched messages on SENT, RCVD * wip * update all * refactor --- src/Simplex/Chat.hs | 119 ++++++++++++++++------------- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Store/Messages.hs | 33 ++++---- src/Simplex/Chat/View.hs | 9 ++- 4 files changed, 90 insertions(+), 73 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 9157ac7509..d5f06a326f 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -4545,10 +4545,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void $ continueSending connEntity conn sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId - ci_ <- withStore $ \db -> do - ci_ <- updateDirectItemStatus' db ct conn msgId (CISSndSent SSPComplete) - forM ci_ $ \ci -> liftIO $ setDirectSndChatItemViaProxy db user ct ci (isJust proxy) - forM_ ci_ $ \ci -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + cis <- withStore $ \db -> do + cis <- updateDirectItemsStatus' db ct conn msgId (CISSndSent SSPComplete) + liftIO $ forM cis $ \ci -> setDirectSndChatItemViaProxy db user ct ci (isJust proxy) + let acis = map ctItem cis + unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + where + ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) SWITCH qd phase cStats -> do toView $ CRContactSwitch user ct (SwitchProgress qd phase cStats) when (phase `elem` [SPStarted, SPCompleted]) $ case qd of @@ -4604,7 +4607,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processConnMERR connEntity conn err MERRS msgIds err -> do -- error cannot be AUTH error here - updateDirectItemsStatus ct conn (L.toList msgIds) (CISSndError $ agentSndError err) + updateDirectItemsStatusMsgs ct conn (L.toList msgIds) (CISSndError $ agentSndError err) toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) ERR err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) @@ -4958,7 +4961,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = continued <- continueSending connEntity conn sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId - updateGroupItemStatus gInfo m conn msgId GSSSent (Just $ isJust proxy) + updateGroupItemsStatus gInfo m conn msgId GSSSent (Just $ isJust proxy) when continued $ sendPendingGroupMessages user m conn SWITCH qd phase cStats -> do toView $ CRGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats) @@ -5002,10 +5005,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = continued <- continueSending connEntity conn when continued $ sendPendingGroupMessages user m conn MWARN msgId err -> do - withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (GSSWarning $ agentSndError err) + withStore' $ \db -> updateGroupItemsErrorStatus db msgId (groupMemberId' m) (GSSWarning $ agentSndError err) processConnMWARN connEntity conn err MERR msgId err -> do - withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (GSSError $ agentSndError err) + withStore' $ \db -> updateGroupItemsErrorStatus db msgId (groupMemberId' m) (GSSError $ agentSndError err) -- group errors are silenced to reduce load on UI event log -- toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err @@ -5013,7 +5016,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let newStatus = GSSError $ agentSndError err -- error cannot be AUTH error here withStore' $ \db -> forM_ msgIds $ \msgId -> - updateGroupItemErrorStatus db msgId (groupMemberId' m) newStatus `catchAll_` pure () + updateGroupItemsErrorStatus db msgId (groupMemberId' m) newStatus `catchAll_` pure () toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) ERR err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) @@ -5021,10 +5024,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO add debugging output _ -> pure () where - updateGroupItemErrorStatus :: DB.Connection -> AgentMsgId -> GroupMemberId -> GroupSndStatus -> IO () - updateGroupItemErrorStatus db msgId groupMemberId newStatus = do - chatItemId_ <- getChatItemIdByAgentMsgId db connId msgId - forM_ chatItemId_ $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus + updateGroupItemsErrorStatus :: DB.Connection -> AgentMsgId -> GroupMemberId -> GroupSndStatus -> IO () + updateGroupItemsErrorStatus db msgId groupMemberId newStatus = do + itemIds <- getChatItemIdsByAgentMsgId db connId msgId + forM_ itemIds $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32) agentMsgDecryptError = \case @@ -6696,43 +6699,42 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus updateDirectItemStatus ct conn agentMsgId $ CISSndRcvd msgRcptStatus SSPComplete - -- TODO [batch send] update status of all messages in batch - -- - this is for when we implement identifying inactive connections - -- - regular messages sent in batch would all be marked as delivered by a single receipt - -- - repeat for directMsgReceived if same logic is applied to direct messages - -- - getChatItemIdByAgentMsgId to return [ChatItemId] groupMsgReceived :: GroupInfo -> GroupMember -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> CM () groupMsgReceived gInfo m conn@Connection {connId} msgMeta msgRcpts = do checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus - updateGroupItemStatus gInfo m conn agentMsgId (GSSRcvd msgRcptStatus) Nothing + updateGroupItemsStatus gInfo m conn agentMsgId (GSSRcvd msgRcptStatus) Nothing - updateDirectItemsStatus :: Contact -> Connection -> [AgentMsgId] -> CIStatus 'MDSnd -> CM () - updateDirectItemsStatus ct conn msgIds newStatus = do - cis_ <- withStore' $ \db -> forM msgIds $ \msgId -> runExceptT $ updateDirectItemStatus' db ct conn msgId newStatus - -- only send the last expired item event to view - case reverse $ catMaybes $ rights cis_ of - ci : _ -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) - _ -> pure () + -- Searches chat items for many agent message IDs and updates their status + updateDirectItemsStatusMsgs :: Contact -> Connection -> [AgentMsgId] -> CIStatus 'MDSnd -> CM () + updateDirectItemsStatusMsgs ct conn msgIds newStatus = do + cis <- withStore' $ \db -> forM msgIds $ \msgId -> runExceptT $ updateDirectItemsStatus' db ct conn msgId newStatus + let acis = map ctItem $ concat $ rights cis + unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + where + ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) updateDirectItemStatus :: Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> CM () updateDirectItemStatus ct conn msgId newStatus = do - ci_ <- withStore $ \db -> updateDirectItemStatus' db ct conn msgId newStatus - forM_ ci_ $ \ci -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + cis <- withStore $ \db -> updateDirectItemsStatus' db ct conn msgId newStatus + let acis = map ctItem cis + unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + where + ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) - updateDirectItemStatus' :: DB.Connection -> Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> ExceptT StoreError IO (Maybe (ChatItem 'CTDirect 'MDSnd)) - updateDirectItemStatus' db ct@Contact {contactId} Connection {connId} msgId newStatus = - liftIO (getDirectChatItemByAgentMsgId db user contactId connId msgId) >>= \case - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ _}}) -> pure Nothing - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) - | itemStatus == newStatus -> pure Nothing - | otherwise -> Just <$> updateDirectChatItemStatus db user ct itemId newStatus - _ -> pure Nothing - - updateGroupMemSndStatus :: ChatItemId -> GroupMemberId -> GroupSndStatus -> CM Bool - updateGroupMemSndStatus itemId groupMemberId newStatus = - withStore' $ \db -> updateGroupMemSndStatus' db itemId groupMemberId newStatus + updateDirectItemsStatus' :: DB.Connection -> Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> ExceptT StoreError IO [ChatItem 'CTDirect 'MDSnd] + updateDirectItemsStatus' db ct@Contact {contactId} Connection {connId} msgId newStatus = do + items <- liftIO $ getDirectChatItemsByAgentMsgId db user contactId connId msgId + catMaybes <$> mapM updateItem items + where + updateItem :: CChatItem 'CTDirect -> ExceptT StoreError IO (Maybe (ChatItem 'CTDirect 'MDSnd)) + updateItem = \case + (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ _}}) -> pure Nothing + (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) + | itemStatus == newStatus -> pure Nothing + | otherwise -> Just <$> updateDirectChatItemStatus db user ct itemId newStatus + _ -> pure Nothing updateGroupMemSndStatus' :: DB.Connection -> ChatItemId -> GroupMemberId -> GroupSndStatus -> IO Bool updateGroupMemSndStatus' db itemId groupMemberId newStatus = @@ -6743,20 +6745,29 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise -> updateGroupSndStatus db itemId groupMemberId newStatus $> True _ -> pure False - updateGroupItemStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> GroupSndStatus -> Maybe Bool -> CM () - updateGroupItemStatus gInfo@GroupInfo {groupId} GroupMember {groupMemberId} Connection {connId} msgId newMemStatus viaProxy_ = - withStore' (\db -> getGroupChatItemByAgentMsgId db user groupId connId msgId) >>= \case - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ SSPComplete}}) -> pure () - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) -> do - forM_ viaProxy_ $ \viaProxy -> withStore' $ \db -> setGroupSndViaProxy db itemId groupMemberId viaProxy - memStatusChanged <- updateGroupMemSndStatus itemId groupMemberId newMemStatus - when memStatusChanged $ do - memStatusCounts <- withStore' (`getGroupSndStatusCounts` itemId) - let newStatus = membersGroupItemStatus memStatusCounts - when (newStatus /= itemStatus) $ do - chatItem <- withStore $ \db -> updateGroupChatItemStatus db user gInfo itemId newStatus - toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem) - _ -> pure () + updateGroupItemsStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> GroupSndStatus -> Maybe Bool -> CM () + updateGroupItemsStatus gInfo@GroupInfo {groupId} GroupMember {groupMemberId} Connection {connId} msgId newMemStatus viaProxy_ = do + items <- withStore' (\db -> getGroupChatItemsByAgentMsgId db user groupId connId msgId) + cis <- catMaybes <$> withStore (\db -> mapM (updateItem db) items) + let acis = map gItem cis + unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + where + gItem = AChatItem SCTGroup SMDSnd (GroupChat gInfo) + updateItem :: DB.Connection -> CChatItem 'CTGroup -> ExceptT StoreError IO (Maybe (ChatItem 'CTGroup 'MDSnd)) + updateItem db = \case + (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ SSPComplete}}) -> pure Nothing + (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) -> do + forM_ viaProxy_ $ \viaProxy -> liftIO $ setGroupSndViaProxy db itemId groupMemberId viaProxy + memStatusChanged <- liftIO $ updateGroupMemSndStatus' db itemId groupMemberId newMemStatus + if memStatusChanged + then do + memStatusCounts <- liftIO $ getGroupSndStatusCounts db itemId + let newStatus = membersGroupItemStatus memStatusCounts + if newStatus /= itemStatus + then Just <$> updateGroupChatItemStatus db user gInfo itemId newStatus + else pure Nothing + else pure Nothing + _ -> pure Nothing createContactPQSndItem :: User -> Contact -> Connection -> PQEncryption -> CM (Contact, Connection) createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' = diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 1e00172cea..c80dc320f1 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -600,7 +600,7 @@ data ChatResponse | CRGroupMemberCode {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionCode :: Text} | CRConnectionVerified {user :: User, verified :: Bool, expectedCode :: Text} | CRNewChatItems {user :: User, chatItems :: [AChatItem]} - | CRChatItemStatusUpdated {user :: User, chatItem :: AChatItem} + | CRChatItemsStatusesUpdated {user :: User, chatItems :: [AChatItem]} | CRChatItemUpdated {user :: User, chatItem :: AChatItem} | CRChatItemNotChanged {user :: User, chatItem :: AChatItem} | CRChatItemReaction {user :: User, added :: Bool, reaction :: ACIReaction} diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index f6f9588f66..e4715c7b12 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -75,16 +75,16 @@ module Simplex.Chat.Store.Messages getGroupCIReactions, getGroupReactions, setGroupReaction, - getChatItemIdByAgentMsgId, + getChatItemIdsByAgentMsgId, getDirectChatItem, getDirectCIWithReactions, getDirectChatItemBySharedMsgId, - getDirectChatItemByAgentMsgId, + getDirectChatItemsByAgentMsgId, getGroupChatItem, getGroupCIWithReactions, getGroupChatItemBySharedMsgId, getGroupMemberCIBySharedMsgId, - getGroupChatItemByAgentMsgId, + getGroupChatItemsByAgentMsgId, getGroupMemberChatItemLast, getLocalChatItem, updateLocalChatItem', @@ -1624,19 +1624,18 @@ getAllChatItems db vr user@User {userId} pagination search_ = do |] (userId, search, beforeTs, beforeTs, beforeId, count) -getChatItemIdByAgentMsgId :: DB.Connection -> Int64 -> AgentMsgId -> IO (Maybe ChatItemId) -getChatItemIdByAgentMsgId db connId msgId = - fmap join . maybeFirstRow fromOnly $ - DB.query +getChatItemIdsByAgentMsgId :: DB.Connection -> Int64 -> AgentMsgId -> IO [ChatItemId] +getChatItemIdsByAgentMsgId db connId msgId = + map fromOnly + <$> DB.query db [sql| SELECT chat_item_id FROM chat_item_messages - WHERE message_id = ( + WHERE message_id IN ( SELECT message_id FROM msg_deliveries WHERE connection_id = ? AND agent_msg_id = ? - LIMIT 1 ) |] (connId, msgId) @@ -1780,10 +1779,10 @@ getDirectChatItemBySharedMsgId db user@User {userId} contactId sharedMsgId = do itemId <- getDirectChatItemIdBySharedMsgId_ db userId contactId sharedMsgId getDirectChatItem db user contactId itemId -getDirectChatItemByAgentMsgId :: DB.Connection -> User -> ContactId -> Int64 -> AgentMsgId -> IO (Maybe (CChatItem 'CTDirect)) -getDirectChatItemByAgentMsgId db user contactId connId msgId = do - itemId_ <- getChatItemIdByAgentMsgId db connId msgId - maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getDirectChatItem db user contactId) itemId_ +getDirectChatItemsByAgentMsgId :: DB.Connection -> User -> ContactId -> Int64 -> AgentMsgId -> IO [CChatItem 'CTDirect] +getDirectChatItemsByAgentMsgId db user contactId connId msgId = do + itemIds <- getChatItemIdsByAgentMsgId db connId msgId + catMaybes <$> mapM (fmap eitherToMaybe . runExceptT . getDirectChatItem db user contactId) itemIds getDirectChatItemIdBySharedMsgId_ :: DB.Connection -> UserId -> Int64 -> SharedMsgId -> ExceptT StoreError IO Int64 getDirectChatItemIdBySharedMsgId_ db userId contactId sharedMsgId = @@ -2036,10 +2035,10 @@ getGroupMemberCIBySharedMsgId db user@User {userId} groupId memberId sharedMsgId (GCUserMember, userId, groupId, memberId, sharedMsgId) getGroupChatItem db user groupId itemId -getGroupChatItemByAgentMsgId :: DB.Connection -> User -> GroupId -> Int64 -> AgentMsgId -> IO (Maybe (CChatItem 'CTGroup)) -getGroupChatItemByAgentMsgId db user groupId connId msgId = do - itemId_ <- getChatItemIdByAgentMsgId db connId msgId - maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupChatItem db user groupId) itemId_ +getGroupChatItemsByAgentMsgId :: DB.Connection -> User -> GroupId -> Int64 -> AgentMsgId -> IO [CChatItem 'CTGroup] +getGroupChatItemsByAgentMsgId db user groupId connId msgId = do + itemIds <- getChatItemIdsByAgentMsgId db connId msgId + catMaybes <$> mapM (fmap eitherToMaybe . runExceptT . getGroupChatItem db user groupId) itemIds getGroupChatItem :: DB.Connection -> User -> Int64 -> ChatItemId -> ExceptT StoreError IO (CChatItem 'CTGroup) getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index cb686ef2b0..166e12b2c2 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -134,7 +134,14 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId] - CRChatItemStatusUpdated u ci -> ttyUser u $ viewChatItemStatusUpdated ci ts tz testView showReceipts + CRChatItemsStatusesUpdated u chatItems + | length chatItems <= 20 -> + concatMap + (\ci -> ttyUser u $ viewChatItemStatusUpdated ci ts tz testView showReceipts) + chatItems + | testView && showReceipts -> + ttyUser u [sShow (length chatItems) <> " message statuses updated"] + | otherwise -> [] CRChatItemUpdated u (AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewItemUpdate chat item liveItems ts tz CRChatItemNotChanged u ci -> ttyUser u $ viewItemNotChanged ci CRChatItemsDeleted u deletions byUser timed -> case deletions of From 0d9ef4e56740e5e7405a0e93a50ffed4501f6057 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:51:18 +0400 Subject: [PATCH 131/440] ui: support CRChatItemsStatusesUpdated response (#4890) * ui: support new CRChatItemsStatusesUpdated api * ios * refactor --- apps/ios/Shared/Model/SimpleXAPI.swift | 36 ++++++++++--------- .../Shared/Views/Call/ActiveCallView.swift | 10 +++--- apps/ios/SimpleX SE/ShareModel.swift | 3 +- apps/ios/SimpleXChat/APITypes.swift | 8 +++-- apps/ios/SimpleXChat/ChatTypes.swift | 14 ++++++++ .../chat/simplex/common/model/SimpleXAPI.kt | 21 +++++------ .../simplex/common/views/call/CallView.kt | 9 ++--- .../simplex/common/views/chat/ChatView.kt | 4 +-- 8 files changed, 64 insertions(+), 41 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 7312b42b1b..6bbacea245 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1821,23 +1821,25 @@ func processReceivedMsg(_ res: ChatResponse) async { NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) } } - case let .chatItemStatusUpdated(user, aChatItem): - let cInfo = aChatItem.chatInfo - let cItem = aChatItem.chatItem - if !cItem.isDeletedContent && active(user) { - await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) } - } - if let endTask = m.messageDelivery[cItem.id] { - switch cItem.meta.itemStatus { - case .sndNew: () - case .sndSent: endTask() - case .sndRcvd: endTask() - case .sndErrorAuth: endTask() - case .sndError: endTask() - case .sndWarning: endTask() - case .rcvNew: () - case .rcvRead: () - case .invalid: () + case let .chatItemsStatusesUpdated(user, chatItems): + for chatItem in chatItems { + let cInfo = chatItem.chatInfo + let cItem = chatItem.chatItem + if !cItem.isDeletedContent && active(user) { + await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) } + } + if let endTask = m.messageDelivery[cItem.id] { + switch cItem.meta.itemStatus { + case .sndNew: () + case .sndSent: endTask() + case .sndRcvd: endTask() + case .sndErrorAuth: endTask() + case .sndError: endTask() + case .sndWarning: endTask() + case .rcvNew: () + case .rcvRead: () + case .invalid: () + } } } case let .chatItemUpdated(user, aChatItem): diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index d238c2dbae..049ee95a09 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -214,10 +214,12 @@ struct ActiveCallView: View { ChatReceiver.shared.messagesChannel = nil return } - if case let .chatItemStatusUpdated(_, msg) = msg, - msg.chatInfo.id == call.contact.id, - case .sndCall = msg.chatItem.content, - case .sndRcvd = msg.chatItem.meta.itemStatus { + if case let .chatItemsStatusesUpdated(_, chatItems) = msg, + chatItems.contains(where: { ci in + ci.chatInfo.id == call.contact.id && + ci.chatItem.content.isSndCall && + ci.chatItem.meta.itemStatus.isSndRcvd + }) { CallSoundsPlayer.shared.startInCallSound() ChatReceiver.shared.messagesChannel = nil } diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index e73aeee13c..807f2c9903 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -320,7 +320,8 @@ class ShareModel: ObservableObject { } await ch.completeFile() if await !ch.isRunning { break } - case let .chatItemStatusUpdated(_, ci): + case let .chatItemsStatusesUpdated(_, chatItems): + guard let ci = chatItems.last else { continue } guard isMessage(for: ci) else { continue } if let (title, message) = ci.chatItem.meta.itemStatus.statusInfo { // `title` and `message` already localized and interpolated diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3cc2202bff..c210431d12 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -601,7 +601,7 @@ public enum ChatResponse: Decodable, Error { case groupEmpty(user: UserRef, groupInfo: GroupInfo) case userContactLinkSubscribed case newChatItems(user: UserRef, chatItems: [AChatItem]) - case chatItemStatusUpdated(user: UserRef, chatItem: AChatItem) + case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) case chatItemUpdated(user: UserRef, chatItem: AChatItem) case chatItemNotChanged(user: UserRef, chatItem: AChatItem) case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) @@ -772,7 +772,7 @@ public enum ChatResponse: Decodable, Error { case .groupEmpty: return "groupEmpty" case .userContactLinkSubscribed: return "userContactLinkSubscribed" case .newChatItems: return "newChatItems" - case .chatItemStatusUpdated: return "chatItemStatusUpdated" + case .chatItemsStatusesUpdated: return "chatItemsStatusesUpdated" case .chatItemUpdated: return "chatItemUpdated" case .chatItemNotChanged: return "chatItemNotChanged" case .chatItemReaction: return "chatItemReaction" @@ -943,7 +943,9 @@ public enum ChatResponse: Decodable, Error { case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) - case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemsStatusesUpdated(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 84bf445601..6b03833d08 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2856,6 +2856,13 @@ public enum CIStatus: Decodable, Hashable { ) } } + + public var isSndRcvd: Bool { + switch self { + case .sndRcvd: return true + default: return false + } + } } public enum SndError: Decodable, Hashable { @@ -3152,6 +3159,13 @@ public enum CIContent: Decodable, ItemContent, Hashable { default: return false } } + + public var isSndCall: Bool { + switch self { + case .sndCall: return true + default: return false + } + } } public enum MsgDecryptError: String, Decodable, Hashable { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 452e8a704b..de31229697 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -2186,15 +2186,16 @@ object ChatController { } } } - is CR.ChatItemStatusUpdated -> { - val cInfo = r.chatItem.chatInfo - val cItem = r.chatItem.chatItem - if (!cItem.isDeletedContent && active(r.user)) { - withChats { - updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) + is CR.ChatItemsStatusesUpdated -> + r.chatItems.forEach { chatItem -> + val cInfo = chatItem.chatInfo + val cItem = chatItem.chatItem + if (!cItem.isDeletedContent && active(r.user)) { + withChats { + updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) + } } } - } is CR.ChatItemUpdated -> chatItemSimpleUpdate(rhId, r.user, r.chatItem) is CR.ChatItemReaction -> { @@ -4831,7 +4832,7 @@ sealed class CR { @Serializable @SerialName("groupEmpty") class GroupEmpty(val user: UserRef, val group: GroupInfo): CR() @Serializable @SerialName("userContactLinkSubscribed") class UserContactLinkSubscribed: CR() @Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List): CR() - @Serializable @SerialName("chatItemStatusUpdated") class ChatItemStatusUpdated(val user: UserRef, val chatItem: AChatItem): CR() + @Serializable @SerialName("chatItemsStatusesUpdated") class ChatItemsStatusesUpdated(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemReaction") class ChatItemReaction(val user: UserRef, val added: Boolean, val reaction: ACIReaction): CR() @@ -5008,7 +5009,7 @@ sealed class CR { is GroupEmpty -> "groupEmpty" is UserContactLinkSubscribed -> "userContactLinkSubscribed" is NewChatItems -> "newChatItems" - is ChatItemStatusUpdated -> "chatItemStatusUpdated" + is ChatItemsStatusesUpdated -> "chatItemsStatusesUpdated" is ChatItemUpdated -> "chatItemUpdated" is ChatItemNotChanged -> "chatItemNotChanged" is ChatItemReaction -> "chatItemReaction" @@ -5177,7 +5178,7 @@ sealed class CR { is GroupEmpty -> withUser(user, json.encodeToString(group)) is UserContactLinkSubscribed -> noDetails() is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) - is ChatItemStatusUpdated -> withUser(user, json.encodeToString(chatItem)) + is ChatItemsStatusesUpdated -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) is ChatItemReaction -> withUser(user, "added: $added\n${json.encodeToString(reaction)}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt index e4a6691d49..ae020ea3a1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt @@ -15,10 +15,11 @@ fun activeCallWaitDeliveryReceipt(scope: CoroutineScope) = scope.launch(Dispatch if (call == null || call.callState > CallState.InvitationSent) break val msg = apiResp.resp if (apiResp.remoteHostId == call.remoteHostId && - msg is CR.ChatItemStatusUpdated && - msg.chatItem.chatInfo.id == call.contact.id && - msg.chatItem.chatItem.content is CIContent.SndCall && - msg.chatItem.chatItem.meta.itemStatus is CIStatus.SndRcvd) { + msg is CR.ChatItemsStatusesUpdated && + msg.chatItems.any { + it.chatInfo.id == call.contact.id && it.chatItem.content is CIContent.SndCall && it.chatItem.meta.itemStatus is CIStatus.SndRcvd + } + ) { CallSoundsPlayer.startInCallSound(scope) break } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 511230cc83..2bef0bdca7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -444,8 +444,8 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - for (apiResp in controller.messagesChannel) { val msg = apiResp.resp if (apiResp.remoteHostId == chatRh && - msg is CR.ChatItemStatusUpdated && - msg.chatItem.chatItem.id == cItem.id + msg is CR.ChatItemsStatusesUpdated && + msg.chatItems.any { it.chatItem.id == cItem.id } ) { ciInfo = loadChatItemInfo() ?: return@withContext initialCiInfo = ciInfo From 69bbe0ae916d02039c0e0f7ae1c5903ebd0bd534 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 17 Sep 2024 20:09:50 +0000 Subject: [PATCH 132/440] android, desktop: proxy configuration includes credentials (#4892) * android, desktop: proxy configuration includes credentials * migration * changes for disabled socks * migration * port * new logic * migration * check validity of fields * validity of host * import changes proxy just in case * send port always * non-nullable * Revert "send port always" This reverts commit 14dd066d80f4c6c6b1061e8e6142bf18f83b97bb. * string --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/SimpleXAPI.kt | 91 ++++-- .../views/migration/MigrateFromDevice.kt | 16 +- .../common/views/migration/MigrateToDevice.kt | 177 ++++++----- .../usersettings/AdvancedNetworkSettings.kt | 32 +- .../views/usersettings/NetworkAndServers.kt | 285 ++++++++++++------ .../commonMain/resources/MR/base/strings.xml | 10 + 6 files changed, 377 insertions(+), 234 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index de31229697..906ca826ca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -129,7 +129,22 @@ class AppPreferences { val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false) val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false) val networkShowSubscriptionPercentage = mkBoolPreference(SHARED_PREFS_NETWORK_SHOW_SUBSCRIPTION_PERCENTAGE, false) - val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050") + private val _networkProxy = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, json.encodeToString(NetworkProxy())) + val networkProxy: SharedPreference = SharedPreference( + get = fun(): NetworkProxy { + val value = _networkProxy.get() ?: return NetworkProxy() + return try { + if (value.startsWith("{")) { + json.decodeFromString(value) + } else { + NetworkProxy(host = value.substringBefore(":").ifBlank { "localhost" }, port = value.substringAfter(":").toIntOrNull() ?: 9050) + } + } catch (e: Throwable) { + NetworkProxy() + } + }, + set = fun(proxy: NetworkProxy) { _networkProxy.set(json.encodeToString(proxy)) } + ) private val _networkSessionMode = mkStrPreference(SHARED_PREFS_NETWORK_SESSION_MODE, TransportSessionMode.default.name) val networkSessionMode: SharedPreference = SharedPreference( get = fun(): TransportSessionMode { @@ -531,7 +546,7 @@ object ChatController { suspend fun startChatWithTemporaryDatabase(ctrl: ChatCtrl, netCfg: NetCfg): User? { Log.d(TAG, "startChatWithTemporaryDatabase") val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl) - if (!apiSetNetworkConfig(netCfg, ctrl)) { + if (!apiSetNetworkConfig(netCfg, ctrl = ctrl)) { Log.e(TAG, "Error setting network config, stopping migration") return null } @@ -976,16 +991,18 @@ object ChatController { throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}") } - suspend fun apiSetNetworkConfig(cfg: NetCfg, ctrl: ChatCtrl? = null): Boolean { + suspend fun apiSetNetworkConfig(cfg: NetCfg, showAlertOnError: Boolean = true, ctrl: ChatCtrl? = null): Boolean { val r = sendCmd(null, CC.APISetNetworkConfig(cfg), ctrl) return when (r) { is CR.CmdOk -> true else -> { Log.e(TAG, "apiSetNetworkConfig bad response: ${r.responseType} ${r.details}") - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.error_setting_network_config), - "${r.responseType}: ${r.details}" - ) + if (showAlertOnError) { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.error_setting_network_config), + "${r.responseType}: ${r.details}" + ) + } false } } @@ -2777,13 +2794,9 @@ object ChatController { fun getNetCfg(): NetCfg { val useSocksProxy = appPrefs.networkUseSocksProxy.get() - val proxyHostPort = appPrefs.networkProxyHostPort.get() + val networkProxy = appPrefs.networkProxy.get() val socksProxy = if (useSocksProxy) { - if (proxyHostPort?.startsWith("localhost:") == true) { - proxyHostPort.removePrefix("localhost") - } else { - proxyHostPort ?: ":9050" - } + networkProxy.toProxyString() } else { null } @@ -2825,7 +2838,7 @@ object ChatController { } /** - * [AppPreferences.networkProxyHostPort] is not changed here, use appPrefs to set it + * [AppPreferences.networkProxy] is not changed here, use appPrefs to set it * */ fun setNetCfg(cfg: NetCfg) { appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy) @@ -3569,13 +3582,8 @@ data class NetCfg( val useSocksProxy: Boolean get() = socksProxy != null val enableKeepAlive: Boolean get() = tcpKeepAlive != null - fun withHostPort(hostPort: String?, default: String? = ":9050"): NetCfg { - val socksProxy = if (hostPort?.startsWith("localhost:") == true) { - hostPort.removePrefix("localhost") - } else { - hostPort ?: default - } - return copy(socksProxy = socksProxy) + fun withProxy(proxy: NetworkProxy?, default: String? = ":9050"): NetCfg { + return copy(socksProxy = proxy?.toProxyString() ?: default) } companion object { @@ -3617,6 +3625,39 @@ data class NetCfg( } } +@Serializable +data class NetworkProxy( + val username: String = "", + val password: String = "", + val auth: NetworkProxyAuth = NetworkProxyAuth.ISOLATE, + val host: String = "localhost", + val port: Int = 9050 +) { + fun toProxyString(): String { + var res = "" + if (auth == NetworkProxyAuth.USERNAME && (username.isNotBlank() || password.isNotBlank())) { + res += username.trim() + ":" + password.trim() + "@" + } else if (auth == NetworkProxyAuth.USERNAME) { + res += "@" + } + if (host != "localhost") { + res += if (host.contains(':')) "[${host.trim(' ', '[', ']')}]" else host.trim() + } + if (port != 9050 || res.isEmpty()) { + res += ":$port" + } + return res + } +} + +@Serializable +enum class NetworkProxyAuth { + @SerialName("isolate") + ISOLATE, + @SerialName("username") + USERNAME, +} + enum class OnionHosts { NEVER, PREFER, REQUIRED } @@ -6184,6 +6225,7 @@ enum class NotificationsMode() { @Serializable data class AppSettings( var networkConfig: NetCfg? = null, + var networkProxy: NetworkProxy? = null, var privacyEncryptLocalFiles: Boolean? = null, var privacyAskToApproveRelays: Boolean? = null, var privacyAcceptImages: Boolean? = null, @@ -6215,6 +6257,7 @@ data class AppSettings( val empty = AppSettings() val def = defaults if (networkConfig != def.networkConfig) { empty.networkConfig = networkConfig } + if (networkProxy != def.networkProxy) { empty.networkProxy = networkProxy } if (privacyEncryptLocalFiles != def.privacyEncryptLocalFiles) { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } if (privacyAskToApproveRelays != def.privacyAskToApproveRelays) { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } if (privacyAcceptImages != def.privacyAcceptImages) { empty.privacyAcceptImages = privacyAcceptImages } @@ -6252,8 +6295,12 @@ data class AppSettings( if (net.hostMode == HostMode.Onion) { net = net.copy(hostMode = HostMode.Public, requiredHostMode = true) } + if (net.socksProxy != null) { + net = net.copy(socksProxy = networkProxy?.toProxyString()) + } setNetCfg(net) } + networkProxy?.let { def.networkProxy.set(it) } privacyEncryptLocalFiles?.let { def.privacyEncryptLocalFiles.set(it) } privacyAskToApproveRelays?.let { def.privacyAskToApproveRelays.set(it) } privacyAcceptImages?.let { def.privacyAcceptImages.set(it) } @@ -6286,6 +6333,7 @@ data class AppSettings( val defaults: AppSettings get() = AppSettings( networkConfig = NetCfg.defaults, + networkProxy = null, privacyEncryptLocalFiles = true, privacyAskToApproveRelays = true, privacyAcceptImages = true, @@ -6319,6 +6367,7 @@ data class AppSettings( val def = appPreferences return defaults.copy( networkConfig = getNetCfg(), + networkProxy = def.networkProxy.get(), privacyEncryptLocalFiles = def.privacyEncryptLocalFiles.get(), privacyAskToApproveRelays = def.privacyAskToApproveRelays.get(), privacyAcceptImages = def.privacyAcceptImages.get(), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index a71503e315..4cc7899cc8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -4,7 +4,6 @@ import SectionBottomSpacer import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -17,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase @@ -38,7 +38,6 @@ import kotlinx.serialization.* import java.io.File import java.net.URLEncoder import kotlin.math.max -import kotlin.math.sqrt @Serializable data class MigrationFileLinkData( @@ -46,16 +45,20 @@ data class MigrationFileLinkData( ) { @Serializable data class NetworkConfig( - val socksProxy: String?, + // Legacy. Remove in 2025 + @SerialName("socksProxy") + val legacySocksProxy: String?, + val networkProxy: NetworkProxy?, val hostMode: HostMode?, val requiredHostMode: Boolean? ) { - fun hasOnionConfigured(): Boolean = socksProxy != null || hostMode == HostMode.Onion + fun hasProxyConfigured(): Boolean = networkProxy != null || legacySocksProxy != null || hostMode == HostMode.Onion fun transformToPlatformSupported(): NetworkConfig { return if (hostMode != null && requiredHostMode != null) { NetworkConfig( - socksProxy = if (hostMode == HostMode.Onion) socksProxy ?: NetCfg.proxyDefaults.socksProxy else socksProxy, + legacySocksProxy = if (hostMode == HostMode.Onion) legacySocksProxy ?: NetCfg.proxyDefaults.socksProxy else legacySocksProxy, + networkProxy = if (hostMode == HostMode.Onion) networkProxy ?: NetworkProxy() else networkProxy, hostMode = if (hostMode == HostMode.Onion) HostMode.OnionViaSocks else hostMode, requiredHostMode = requiredHostMode ) @@ -570,7 +573,8 @@ private fun MutableState.startUploading( val cfg = getNetCfg() val data = MigrationFileLinkData( networkConfig = MigrationFileLinkData.NetworkConfig( - socksProxy = cfg.socksProxy, + legacySocksProxy = null, + networkProxy = if (appPrefs.networkUseSocksProxy.get()) appPrefs.networkProxy.get() else null, hostMode = cfg.hostMode, requiredHostMode = cfg.requiredHostMode ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 8312c213ec..415f5cdd57 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -5,16 +5,15 @@ import SectionItemView import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import chat.simplex.common.model.* import chat.simplex.common.model.AppPreferences.Companion.SHARED_PREFS_MIGRATION_TO_STAGE +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatCtrl @@ -41,10 +40,10 @@ import kotlin.math.max @Serializable sealed class MigrationToDeviceState { - @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState() - @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState() - @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState() - @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg): MigrationToDeviceState() + @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val networkProxy: NetworkProxy?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState() + @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() + @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() + @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() companion object { // Here we check whether it's needed to show migration process after app restart or not @@ -66,10 +65,10 @@ sealed class MigrationToDeviceState { null } else { val archivePath = File(getMigrationTempFilesDirectory(), state.archiveName) - MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg) + MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg, state.networkProxy) } } - is Passphrase -> MigrationToState.Passphrase("", state.netCfg) + is Passphrase -> MigrationToState.Passphrase("", state.netCfg, state.networkProxy) } if (initial == null) { settings.remove(SHARED_PREFS_MIGRATION_TO_STAGE) @@ -91,16 +90,24 @@ sealed class MigrationToDeviceState { @Serializable sealed class MigrationToState { @Serializable object PasteOrScanLink: MigrationToState() - @Serializable data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToState() - @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val ctrl: ChatCtrl?): MigrationToState() - @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState() - @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState() + @Serializable data class Onion( + val link: String, + // Legacy, remove in 2025 + @SerialName("socksProxy") + val legacySocksProxy: String?, + val networkProxy: NetworkProxy?, + val hostMode: HostMode, + val requiredHostMode: Boolean + ): MigrationToState() + @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?, val ctrl: ChatCtrl?): MigrationToState() + @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() } private var MutableState.state: MigrationToState? @@ -175,16 +182,16 @@ private fun ModalData.SectionByState( when (val s = migrationState.value) { null -> {} is MigrationToState.PasteOrScanLink -> migrationState.PasteOrScanLinkView() - is MigrationToState.Onion -> OnionView(s.link, s.socksProxy, s.hostMode, s.requiredHostMode, migrationState) - is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg) - is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg) + is MigrationToState.Onion -> OnionView(s.link, s.legacySocksProxy, s.networkProxy, s.hostMode, s.requiredHostMode, migrationState) + is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg, s.networkProxy) + is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg, s.networkProxy) is MigrationToState.DownloadProgress -> DownloadProgressView(s.downloadedBytes, totalBytes = s.totalBytes) - is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg) - is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg) - is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg) - is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg) - is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg) - is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, close) + is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg, s.networkProxy) + is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg, s.networkProxy) + is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, s.networkProxy, close) } } @@ -216,21 +223,24 @@ private fun MutableState.PasteLinkView() { } @Composable -private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) { +private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, linkNetworkProxy: NetworkProxy?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) { val onionHosts = remember { stateGetOrPut("onionHosts") { - getNetCfg().copy(socksProxy = socksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts + getNetCfg().copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts } } - val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { socksProxy != null } } + val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { linkNetworkProxy != null || legacyLinkSocksProxy != null } } val sessionMode = remember { stateGetOrPut("sessionMode") { TransportSessionMode.User} } - val networkProxyHostPort = remember { stateGetOrPut("networkHostProxyPort") { - var proxy = (socksProxy ?: chatModel.controller.appPrefs.networkProxyHostPort.get()) - if (proxy?.startsWith(":") == true) proxy = "localhost$proxy" - proxy - } + val networkProxy = remember { stateGetOrPut("networkProxy") { + linkNetworkProxy + ?: if (legacyLinkSocksProxy != null) { + NetworkProxy(host = legacyLinkSocksProxy.substringBefore(":").ifBlank { "localhost" }, port = legacyLinkSocksProxy.substringAfter(":").toIntOrNull() ?: 9050) + } else { + appPrefs.networkProxy.get() + } + } } val netCfg = rememberSaveable(stateSaver = serializableSaver()) { - mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = socksProxy, sessionMode = sessionMode.value)) + mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, sessionMode = sessionMode.value)) } SectionView(stringResource(MR.strings.migrate_to_device_confirm_network_settings).uppercase()) { @@ -241,12 +251,12 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos click = { val updated = netCfg.value .withOnionHosts(onionHosts.value) - .withHostPort(if (networkUseSocksProxy.value) networkProxyHostPort.value else null, null) + .withProxy(if (networkUseSocksProxy.value) networkProxy.value else null, null) .copy( sessionMode = sessionMode.value ) withBGApi { - state.value = MigrationToState.DatabaseInit(link, updated) + state.value = MigrationToState.DatabaseInit(link, updated, if (networkUseSocksProxy.value) networkProxy.value else null) } } ){} @@ -255,8 +265,8 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos SectionSpacer() - val networkProxyHostPortPref = SharedPreference(get = { networkProxyHostPort.value }, set = { - networkProxyHostPort.value = it + val networkProxyPref = SharedPreference(get = { networkProxy.value }, set = { + networkProxy.value = it }) SectionView(stringResource(MR.strings.network_settings_title).uppercase()) { OnionRelatedLayout( @@ -264,13 +274,10 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos networkUseSocksProxy, onionHosts, sessionMode, - networkProxyHostPortPref, + networkProxyPref, toggleSocksProxy = { enable -> networkUseSocksProxy.value = enable }, - useOnion = { - onionHosts.value = it - }, updateSessionMode = { sessionMode.value = it } @@ -279,13 +286,13 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos } @Composable -private fun MutableState.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg) { +private fun MutableState.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg, networkProxy: NetworkProxy?) { Box { SectionView(stringResource(MR.strings.migrate_to_device_database_init).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - prepareDatabase(link, tempDatabaseFile, netCfg) + prepareDatabase(link, tempDatabaseFile, netCfg, networkProxy) } } @@ -297,14 +304,15 @@ private fun MutableState.LinkDownloadingView( archivePath: String, tempDatabaseFile: File, chatReceiver: MutableState, - netCfg: NetCfg + netCfg: NetCfg, + networkProxy: NetworkProxy? ) { Box { SectionView(stringResource(MR.strings.migrate_to_device_downloading_details).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg) + startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg, networkProxy) } } @@ -319,14 +327,14 @@ private fun DownloadProgressView(downloadedBytes: Long, totalBytes: Long) { } @Composable -private fun MutableState.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg) { +private fun MutableState.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { SectionView(stringResource(MR.strings.migrate_to_device_download_failed).uppercase()) { SettingsActionItemWithContent( icon = painterResource(MR.images.ic_download), text = stringResource(MR.strings.migrate_to_device_repeat_download), textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.DatabaseInit(link, netCfg) + state = MigrationToState.DatabaseInit(link, netCfg, networkProxy) } ) {} SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again)) @@ -339,25 +347,25 @@ private fun MutableState.DownloadFailedView(link: String, cha } @Composable -private fun MutableState.ArchiveImportView(archivePath: String, netCfg: NetCfg) { +private fun MutableState.ArchiveImportView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { Box { SectionView(stringResource(MR.strings.migrate_to_device_importing_archive).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - importArchive(archivePath, netCfg) + importArchive(archivePath, netCfg, networkProxy) } } @Composable -private fun MutableState.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg) { +private fun MutableState.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { SectionView(stringResource(MR.strings.migrate_to_device_import_failed).uppercase()) { SettingsActionItemWithContent( icon = painterResource(MR.images.ic_download), text = stringResource(MR.strings.migrate_to_device_repeat_import), textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.ArchiveImport(archivePath, netCfg) + state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy) } ) {} SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again)) @@ -365,7 +373,7 @@ private fun MutableState.ArchiveImportFailedView(archivePath: } @Composable -private fun MutableState.PassphraseEnteringView(currentKey: String, netCfg: NetCfg) { +private fun MutableState.PassphraseEnteringView(currentKey: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { val currentKey = rememberSaveable { mutableStateOf(currentKey) } val verifyingPassphrase = rememberSaveable { mutableStateOf(false) } val useKeychain = rememberSaveable { mutableStateOf(appPreferences.storeDBPassphrase.get()) } @@ -395,9 +403,9 @@ private fun MutableState.PassphraseEnteringView(currentKey: S val (status, _) = chatInitTemporaryDatabase(dbAbsolutePrefixPath, key = currentKey.value, confirmation = MigrationConfirmation.YesUp) val success = status == DBMigrationResult.OK || status == DBMigrationResult.InvalidConfirmation if (success) { - state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg) + state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg, networkProxy) } else if (status is DBMigrationResult.ErrorMigration) { - state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg) + state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg, networkProxy) } else { showErrorOnMigrationIfNeeded(status) } @@ -414,7 +422,7 @@ private fun MutableState.PassphraseEnteringView(currentKey: S } @Composable -private fun MutableState.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg) { +private fun MutableState.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?) { data class Tuple4(val a: A, val b: B, val c: C, val d: D) val (header: String, button: String?, footer: String, confirmation: MigrationConfirmation?) = when (status) { is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) { @@ -449,7 +457,7 @@ private fun MutableState.MigrationConfirmationView(status: DB text = button, textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg) + state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg, networkProxy) } ) {} } @@ -458,13 +466,13 @@ private fun MutableState.MigrationConfirmationView(status: DB } @Composable -private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) { +private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) { Box { SectionView(stringResource(MR.strings.migrate_to_device_migrating).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - startChat(passphrase, confirmation, useKeychain, netCfg, close) + startChat(passphrase, confirmation, useKeychain, netCfg, networkProxy, close) } } @@ -476,19 +484,21 @@ private fun ProgressView() { private suspend fun MutableState.checkUserLink(link: String) { if (strHasSimplexFileLink(link.trim())) { val data = MigrationFileLinkData.readFromLink(link) - val hasOnionConfigured = data?.networkConfig?.hasOnionConfigured() ?: false + val hasProxyConfigured = data?.networkConfig?.hasProxyConfigured() ?: false val networkConfig = data?.networkConfig?.transformToPlatformSupported() // If any of iOS or Android had onion enabled, show onion screen - if (hasOnionConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) { - state = MigrationToState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode) - MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode)) + if (hasProxyConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) { + state = MigrationToState.Onion(link.trim(), networkConfig.legacySocksProxy, networkConfig.networkProxy, networkConfig.hostMode, networkConfig.requiredHostMode) + MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.legacySocksProxy, networkConfig.networkProxy, networkConfig.hostMode, networkConfig.requiredHostMode)) } else { val current = getNetCfg() state = MigrationToState.DatabaseInit(link.trim(), current.copy( - socksProxy = networkConfig?.socksProxy, + socksProxy = null, hostMode = networkConfig?.hostMode ?: current.hostMode, requiredHostMode = networkConfig?.requiredHostMode ?: current.requiredHostMode - )) + ), + networkProxy = null + ) } } else { AlertManager.shared.showAlertMsg( @@ -502,6 +512,7 @@ private fun MutableState.prepareDatabase( link: String, tempDatabaseFile: File, netCfg: NetCfg, + networkProxy: NetworkProxy? ) { withLongRunningApi { val ctrlAndUser = initTemporaryDatabase(tempDatabaseFile, netCfg) @@ -513,7 +524,7 @@ private fun MutableState.prepareDatabase( } val (ctrl, user) = ctrlAndUser - state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg) + state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg, networkProxy) } } @@ -526,13 +537,14 @@ private fun MutableState.startDownloading( link: String, archivePath: String, netCfg: NetCfg, + networkProxy: NetworkProxy? ) { withBGApi { chatReceiver.value = MigrationToChatReceiver(ctrl, tempDatabaseFile) { msg -> when (msg) { is CR.RcvFileProgressXFTP -> { - state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, ctrl) - MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg)) + state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, networkProxy, ctrl) + MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg, networkProxy)) } is CR.RcvStandaloneFileComplete -> { delay(500) @@ -540,8 +552,8 @@ private fun MutableState.startDownloading( if (state == null) { MigrationToDeviceState.save(null) } else { - state = MigrationToState.ArchiveImport(archivePath, netCfg) - MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg)) + state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy) + MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg, networkProxy)) } } is CR.RcvFileError -> { @@ -549,7 +561,7 @@ private fun MutableState.startDownloading( generalGetString(MR.strings.migrate_to_device_download_failed), generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) ) - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) } is CR.ChatRespError -> { if (msg.chatError is ChatError.ChatErrorChat && msg.chatError.errorType is ChatErrorType.NoRcvFileUser) { @@ -557,7 +569,7 @@ private fun MutableState.startDownloading( generalGetString(MR.strings.migrate_to_device_download_failed), generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) ) - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) } else { Log.d(TAG, "unsupported error: ${msg.responseType}, ${json.encodeToString(msg.chatError)}") } @@ -569,7 +581,7 @@ private fun MutableState.startDownloading( val (res, error) = controller.downloadStandaloneFile(user, link, CryptoFile.plain(File(archivePath).path), ctrl) if (res == null) { - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg( generalGetString(MR.strings.migrate_to_device_error_downloading_archive), error @@ -578,7 +590,7 @@ private fun MutableState.startDownloading( } } -private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg) { +private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { withLongRunningApi { try { if (ChatController.ctrl == null || ChatController.ctrl == -1L) { @@ -592,14 +604,14 @@ private fun MutableState.importArchive(archivePath: String, n if (archiveErrors.isNotEmpty()) { showArchiveImportedWithErrorsAlert(archiveErrors) } - state = MigrationToState.Passphrase("", netCfg) - MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg)) + state = MigrationToState.Passphrase("", netCfg, networkProxy) + MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg, networkProxy)) } catch (e: Exception) { - state = MigrationToState.ArchiveImportFailed(archivePath, netCfg) + state = MigrationToState.ArchiveImportFailed(archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_importing_database), e.stackTraceToString()) } } catch (e: Exception) { - state = MigrationToState.ArchiveImportFailed(archivePath, netCfg) + state = MigrationToState.ArchiveImportFailed(archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_deleting_database), e.stackTraceToString()) } } @@ -609,7 +621,7 @@ private suspend fun stopArchiveDownloading(fileId: Long, ctrl: ChatCtrl) { controller.apiCancelFile(null, fileId, ctrl) } -private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) { +private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) { if (useKeychain) { ksDatabasePassword.set(passphrase) } else { @@ -621,7 +633,8 @@ private fun startChat(passphrase: String, confirmation: MigrationConfirmation, u try { initChatController(useKey = passphrase, confirmMigrations = confirmation) { CompletableDeferred(false) } val appSettings = controller.apiGetAppSettings(AppSettings.current.prepareForExport()).copy( - networkConfig = netCfg + networkConfig = netCfg, + networkProxy = networkProxy ) finishMigration(appSettings, close) } catch (e: Exception) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index 3c8ab2b70a..6dc0f74df3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -1,10 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionItemWithValue +import SectionTextFooter import SectionView import SectionViewSelectableCards import androidx.compose.desktop.ui.tooling.preview.Preview @@ -40,12 +40,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U val currentCfg = remember { stateGetOrPut("currentCfg") { controller.getNetCfg() } } val currentCfgVal = currentCfg.value // used only on initialization - val onionHosts = remember { mutableStateOf(currentCfgVal.onionHosts) } val sessionMode = remember { mutableStateOf(currentCfgVal.sessionMode) } val smpProxyMode = remember { mutableStateOf(currentCfgVal.smpProxyMode) } val smpProxyFallback = remember { mutableStateOf(currentCfgVal.smpProxyFallback) } - val networkUseSocksProxy: MutableState = remember { mutableStateOf(currentCfgVal.useSocksProxy) } val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) } val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) } val networkTCPTimeoutPerKb = remember { mutableStateOf(currentCfgVal.tcpTimeoutPerKb) } @@ -90,11 +88,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U tcpKeepAlive = tcpKeepAlive, smpPingInterval = networkSMPPingInterval.value, smpPingCount = networkSMPPingCount.value - ).withOnionHosts(onionHosts.value) + ).withOnionHosts(currentCfg.value.onionHosts) } fun updateView(cfg: NetCfg) { - onionHosts.value = cfg.onionHosts sessionMode.value = cfg.sessionMode smpProxyMode.value = cfg.smpProxyMode smpProxyFallback.value = cfg.smpProxyFallback @@ -148,10 +145,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U ) { AdvancedNetworkSettingsLayout( currentRemoteHost = currentRemoteHost, - networkUseSocksProxy = networkUseSocksProxy, developerTools = developerTools, - onionHosts = onionHosts, - useOnion = { onionHosts.value = it; currentCfg.value = currentCfg.value.withOnionHosts(it) }, sessionMode = sessionMode, smpProxyMode = smpProxyMode, smpProxyFallback = smpProxyFallback, @@ -183,10 +177,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U @Composable fun AdvancedNetworkSettingsLayout( currentRemoteHost: RemoteHostInfo?, - networkUseSocksProxy: State, developerTools: Boolean, - onionHosts: MutableState, - useOnion: (OnionHosts) -> Unit, sessionMode: MutableState, smpProxyMode: MutableState, smpProxyFallback: MutableState, @@ -223,21 +214,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { derivedStateOf { smpProxyMode.value != SMPProxyMode.Never } }) SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy) } - SectionCustomFooter { - Text(stringResource(MR.strings.private_routing_explanation)) - } - SectionDividerSpaced(maxTopPadding = true) - } - - if (currentRemoteHost == null && networkUseSocksProxy.value) { - SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) { - UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) - SectionCustomFooter { - Column { - Text(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported)) - } - } - } + SectionTextFooter(stringResource(MR.strings.private_routing_explanation)) SectionDividerSpaced(maxTopPadding = true) } @@ -562,7 +539,6 @@ fun PreviewAdvancedNetworkSettingsLayout() { SimpleXTheme { AdvancedNetworkSettingsLayout( currentRemoteHost = null, - networkUseSocksProxy = remember { mutableStateOf(false) }, developerTools = false, sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, @@ -577,8 +553,6 @@ fun PreviewAdvancedNetworkSettingsLayout() { networkTCPKeepIdle = remember { mutableStateOf(10) }, networkTCPKeepIntvl = remember { mutableStateOf(10) }, networkTCPKeepCnt = remember { mutableStateOf(10) }, - onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, - useOnion = {}, updateSessionMode = {}, updateSMPProxyMode = {}, updateSMPProxyFallback = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 5bcb0a545d..5272353c20 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -1,10 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionItemWithValue +import SectionTextFooter import SectionView import SectionViewSelectable import TextIconSpaced @@ -37,10 +37,11 @@ fun NetworkAndServersView() { val netCfg = remember { chatModel.controller.getNetCfg() } val networkUseSocksProxy: MutableState = remember { mutableStateOf(netCfg.useSocksProxy) } - val proxyPort = remember { derivedStateOf { chatModel.controller.appPrefs.networkProxyHostPort.state.value?.split(":")?.lastOrNull()?.toIntOrNull() ?: 9050 } } + val proxyPort = remember { derivedStateOf { appPrefs.networkProxy.state.value.port } } NetworkAndServersLayout( currentRemoteHost = currentRemoteHost, networkUseSocksProxy = networkUseSocksProxy, + onionHosts = remember { mutableStateOf(netCfg.onionHosts) }, toggleSocksProxy = { enable -> val def = NetCfg.defaults val proxyDef = NetCfg.proxyDefaults @@ -51,7 +52,7 @@ fun NetworkAndServersView() { confirmText = generalGetString(MR.strings.confirm_verb), onConfirm = { withBGApi { - var conf = controller.getNetCfg().withHostPort(controller.appPrefs.networkProxyHostPort.get()) + var conf = controller.getNetCfg().withProxy(controller.appPrefs.networkProxy.get()) if (conf.tcpConnectTimeout == def.tcpConnectTimeout) { conf = conf.copy(tcpConnectTimeout = proxyDef.tcpConnectTimeout) } @@ -104,6 +105,7 @@ fun NetworkAndServersView() { @Composable fun NetworkAndServersLayout( currentRemoteHost: RemoteHostInfo?, networkUseSocksProxy: MutableState, + onionHosts: MutableState, toggleSocksProxy: (Boolean) -> Unit, ) { val m = chatModel @@ -120,14 +122,10 @@ fun NetworkAndServersView() { if (currentRemoteHost == null) { UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) - SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxyHostPort, false, it) }}) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxy, onionHosts, sessionMode = appPrefs.networkSessionMode.get(), false, it) }}) SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showCustomModal { AdvancedNetworkSettingsView(showModal, it) } }) if (networkUseSocksProxy.value) { - SectionCustomFooter { - Column { - Text(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) - } - } + SectionTextFooter(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) SectionDividerSpaced(maxTopPadding = true) } else { SectionDividerSpaced() @@ -158,16 +156,14 @@ fun NetworkAndServersView() { networkUseSocksProxy: MutableState, onionHosts: MutableState, sessionMode: MutableState, - networkProxyHostPort: SharedPreference, + networkProxy: SharedPreference, toggleSocksProxy: (Boolean) -> Unit, - useOnion: (OnionHosts) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, ) { val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.fullscreen.showModal(content = it) } val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.fullscreen.showCustomModal { close -> it(close) }} UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) - SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxyHostPort, true, it) } }) - UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxy, onionHosts, sessionMode.value, true, it) } }) if (developerTools) { SessionModePicker(sessionMode, showModal, updateSessionMode) } @@ -205,46 +201,98 @@ fun UseSocksProxySwitch( @Composable fun SocksProxySettings( networkUseSocksProxy: Boolean, - networkProxyHostPort: SharedPreference = appPrefs.networkProxyHostPort, + networkProxy: SharedPreference, + onionHosts: MutableState, + sessionMode: TransportSessionMode, migration: Boolean, close: () -> Unit ) { - val defaultHostPort = remember { "localhost:9050" } - val hostPortSaved by remember { networkProxyHostPort.state } + val networkProxySaved by remember { networkProxy.state } + val onionHostsSaved = remember { mutableStateOf(onionHosts.value) } + + val usernameUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(networkProxySaved.username)) + } + val passwordUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(networkProxySaved.password)) + } val hostUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.firstOrNull() ?: "localhost")) + mutableStateOf(TextFieldValue(networkProxySaved.host)) } val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.lastOrNull() ?: "9050")) + mutableStateOf(TextFieldValue(networkProxySaved.port.toString())) } - val save = { - val oldValue = networkProxyHostPort.get() - networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) - if (networkUseSocksProxy && !migration) { - withBGApi { - if (!controller.apiSetNetworkConfig(controller.getNetCfg())) { - networkProxyHostPort.set(oldValue) - } + val proxyAuthRandomUnsaved = rememberSaveable { mutableStateOf(networkProxySaved.auth == NetworkProxyAuth.ISOLATE) } + LaunchedEffect(proxyAuthRandomUnsaved.value) { + if (!proxyAuthRandomUnsaved.value && onionHosts.value != OnionHosts.NEVER) { + onionHosts.value = OnionHosts.NEVER + } + } + val proxyAuthModeUnsaved = remember(proxyAuthRandomUnsaved.value, usernameUnsaved.value.text, passwordUnsaved.value.text) { + derivedStateOf { + if (proxyAuthRandomUnsaved.value) { + NetworkProxyAuth.ISOLATE + } else { + NetworkProxyAuth.USERNAME } } } - val saveAndClose = { - val oldValue = networkProxyHostPort.get() - networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) + + val save: (Boolean) -> Unit = { closeOnSuccess -> + val oldValue = networkProxy.get() + usernameUnsaved.value = usernameUnsaved.value.copy(if (proxyAuthModeUnsaved.value == NetworkProxyAuth.USERNAME) usernameUnsaved.value.text.trim() else "") + passwordUnsaved.value = passwordUnsaved.value.copy(if (proxyAuthModeUnsaved.value == NetworkProxyAuth.USERNAME) passwordUnsaved.value.text.trim() else "") + hostUnsaved.value = hostUnsaved.value.copy(hostUnsaved.value.text.trim()) + portUnsaved.value = portUnsaved.value.copy(portUnsaved.value.text.trim()) + + networkProxy.set( + NetworkProxy( + username = usernameUnsaved.value.text, + password = passwordUnsaved.value.text, + host = hostUnsaved.value.text, + port = portUnsaved.value.text.toIntOrNull() ?: 9050, + auth = proxyAuthModeUnsaved.value + ) + ) + val oldCfg = controller.getNetCfg() + val cfg = oldCfg.withOnionHosts(onionHosts.value) + val oldOnionHosts = onionHostsSaved.value + onionHostsSaved.value = onionHosts.value + + if (!migration) { + controller.setNetCfg(cfg) + } if (networkUseSocksProxy && !migration) { withBGApi { - if (controller.apiSetNetworkConfig(controller.getNetCfg())) { - close() + if (controller.apiSetNetworkConfig(cfg, showAlertOnError = false)) { + onionHosts.value = cfg.onionHosts + onionHostsSaved.value = onionHosts.value + if (closeOnSuccess) { + close() + } } else { - networkProxyHostPort.set(oldValue) + controller.setNetCfg(oldCfg) + networkProxy.set(oldValue) + onionHostsSaved.value = oldOnionHosts + showWrongProxyConfigAlert() } } } } - val saveDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text) || - remember { derivedStateOf { !validHost(hostUnsaved.value.text) } }.value || - remember { derivedStateOf { !validPort(portUnsaved.value.text) } }.value - val resetDisabled = hostUnsaved.value.text + ":" + portUnsaved.value.text == defaultHostPort + val saveDisabled = + ( + networkProxySaved.username == usernameUnsaved.value.text.trim() && + networkProxySaved.password == passwordUnsaved.value.text.trim() && + networkProxySaved.host == hostUnsaved.value.text.trim() && + networkProxySaved.port.toString() == portUnsaved.value.text.trim() && + networkProxySaved.auth == proxyAuthModeUnsaved.value && + onionHosts.value == onionHostsSaved.value + ) || + !validCredential(usernameUnsaved.value.text) || + !validCredential(passwordUnsaved.value.text) || + !validHost(hostUnsaved.value.text) || + !validPort(portUnsaved.value.text) + val resetDisabled = hostUnsaved.value.text.trim() == "localhost" && portUnsaved.value.text.trim() == "9050" && proxyAuthRandomUnsaved.value && onionHosts.value == NetCfg.defaults.onionHosts ModalView( close = { if (saveDisabled) { @@ -252,7 +300,7 @@ fun SocksProxySettings( } else { showUnsavedSocksHostPortAlert( confirmText = generalGetString(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), - save = saveAndClose, + save = { save(true) }, close = close ) } @@ -263,38 +311,78 @@ fun SocksProxySettings( .fillMaxWidth() ) { AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) - SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { - DefaultConfigurableTextField( - hostUnsaved, - stringResource(MR.strings.host_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validHost, - keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), - keyboardType = KeyboardType.Text, - ) - DefaultConfigurableTextField( - portUnsaved, - stringResource(MR.strings.port_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validPort, - keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }), - keyboardType = KeyboardType.Number, - ) + SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + DefaultConfigurableTextField( + hostUnsaved, + stringResource(MR.strings.host_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validHost, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Text, + ) + DefaultConfigurableTextField( + portUnsaved, + stringResource(MR.strings.port_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validPort, + keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save(false) }), + keyboardType = KeyboardType.Number, + ) + } + + UseOnionHosts(onionHosts, rememberUpdatedState(networkUseSocksProxy && proxyAuthRandomUnsaved.value)) { + onionHosts.value = it + } + SectionTextFooter(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported)) } - SectionDividerSpaced(maxBottomPadding = false) + SectionDividerSpaced(maxTopPadding = true) + + SectionView(stringResource(MR.strings.network_proxy_auth).uppercase()) { + PreferenceToggle( + stringResource(MR.strings.network_proxy_random_credentials), + checked = proxyAuthRandomUnsaved.value, + onChange = { proxyAuthRandomUnsaved.value = it } + ) + if (!proxyAuthRandomUnsaved.value) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + DefaultConfigurableTextField( + usernameUnsaved, + stringResource(MR.strings.network_proxy_username), + modifier = Modifier.fillMaxWidth(), + isValid = ::validCredential, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Text, + ) + DefaultConfigurableTextField( + passwordUnsaved, + stringResource(MR.strings.network_proxy_password), + modifier = Modifier.fillMaxWidth(), + isValid = ::validCredential, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Password, + ) + } + } + SectionTextFooter(proxyAuthFooter(usernameUnsaved.value.text, passwordUnsaved.value.text, proxyAuthModeUnsaved.value, sessionMode)) + } + + SectionDividerSpaced(maxBottomPadding = false, maxTopPadding = true) SectionView { SectionItemView({ - val newHost = defaultHostPort.split(":").first() - val newPort = defaultHostPort.split(":").last() - hostUnsaved.value = hostUnsaved.value.copy(newHost, TextRange(newHost.length)) - portUnsaved.value = portUnsaved.value.copy(newPort, TextRange(newPort.length)) + hostUnsaved.value = hostUnsaved.value.copy("localhost", TextRange(9)) + portUnsaved.value = portUnsaved.value.copy("9050", TextRange(4)) + usernameUnsaved.value = TextFieldValue() + passwordUnsaved.value = TextFieldValue() + proxyAuthRandomUnsaved.value = true + onionHosts.value = NetCfg.defaults.onionHosts }, disabled = resetDisabled) { Text(stringResource(MR.strings.network_options_reset_to_defaults), color = if (resetDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } SectionItemView( - click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save() } else save() }, + click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save(false) } else save(false) }, disabled = saveDisabled ) { Text(stringResource(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) @@ -305,6 +393,12 @@ fun SocksProxySettings( } } +private fun proxyAuthFooter(username: String, password: String, auth: NetworkProxyAuth, sessionMode: TransportSessionMode): String = when { + auth == NetworkProxyAuth.ISOLATE -> generalGetString(if (sessionMode == TransportSessionMode.User) MR.strings.network_proxy_auth_mode_isolate_by_auth_user else MR.strings.network_proxy_auth_mode_isolate_by_auth_entity) + username.isBlank() && password.isBlank() -> generalGetString(MR.strings.network_proxy_auth_mode_no_auth) + else -> generalGetString(MR.strings.network_proxy_auth_mode_username_password) +} + private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit, close: () -> Unit) { AlertManager.shared.showAlertDialogStacked( title = generalGetString(MR.strings.update_network_settings_question), @@ -319,7 +413,6 @@ private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit, fun UseOnionHosts( onionHosts: MutableState, enabled: State, - showModal: (@Composable ModalData.() -> Unit) -> Unit, useOnion: (OnionHosts) -> Unit, ) { val values = remember { @@ -331,36 +424,29 @@ fun UseOnionHosts( } } } - val onSelected = { - showModal { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { - AppBarTitle(stringResource(MR.strings.network_use_onion_hosts)) - SectionViewSelectable(null, onionHosts, values, useOnion) - } - } - } - if (enabled.value) { - SectionItemWithValue( - generalGetString(MR.strings.network_use_onion_hosts), - onionHosts, - values, - icon = painterResource(MR.images.ic_security), - enabled = enabled, - onSelected = onSelected - ) - } else { - // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before - SectionItemWithValue( - generalGetString(MR.strings.network_use_onion_hosts), - remember { mutableStateOf(OnionHosts.NEVER) }, - listOf(ValueTitleDesc(OnionHosts.NEVER, generalGetString(MR.strings.network_use_onion_hosts_no), AnnotatedString(generalGetString(MR.strings.network_use_onion_hosts_no_desc)))), - icon = painterResource(MR.images.ic_security), - enabled = enabled, - onSelected = {} - ) + Column { + if (enabled.value) { + ExposedDropDownSettingRow( + generalGetString(MR.strings.network_use_onion_hosts), + values.map { it.value to it.title }, + onionHosts, + icon = painterResource(MR.images.ic_security), + enabled = enabled, + onSelected = useOnion + ) + } else { + // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before + ExposedDropDownSettingRow( + generalGetString(MR.strings.network_use_onion_hosts), + listOf(OnionHosts.NEVER to generalGetString(MR.strings.network_use_onion_hosts_no)), + remember { mutableStateOf(OnionHosts.NEVER) }, + icon = painterResource(MR.images.ic_security), + enabled = enabled, + onSelected = {} + ) + } + SectionTextFooter(values.first { it.value == onionHosts.value }.description) } } @@ -398,12 +484,8 @@ fun SessionModePicker( ) } -// https://stackoverflow.com/a/106223 -private fun validHost(s: String): Boolean { - val validIp = Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") - val validHostname = Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])[.])*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$"); - return s.matches(validIp) || s.matches(validHostname) -} +private fun validHost(s: String): Boolean = + !s.contains('@') // https://ihateregex.io/expr/port/ fun validPort(s: String): Boolean { @@ -411,6 +493,16 @@ fun validPort(s: String): Boolean { return s.isNotBlank() && s.matches(validPort) } +private fun validCredential(s: String): Boolean = + !s.contains(':') && !s.contains('@') + +fun showWrongProxyConfigAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.network_proxy_incorrect_config_title), + text = generalGetString(MR.strings.network_proxy_incorrect_config_desc), + ) +} + fun showUpdateNetworkSettingsDialog( title: String, startsWith: String = "", @@ -435,6 +527,7 @@ fun PreviewNetworkAndServersLayout() { NetworkAndServersLayout( currentRemoteHost = null, networkUseSocksProxy = remember { mutableStateOf(true) }, + onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, toggleSocksProxy = {}, ) } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index f3e491245b..55cd86e035 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -769,7 +769,17 @@ SOCKS proxy SOCKS proxy settings Use SOCKS proxy + Proxy authentication + Use random credentials + Use different proxy credentials for each profile. + Use different proxy credentials for each connection. + Do not use credentials with proxy. + Your credentials may be sent unencrypted. + Username + Password port %d + Error saving proxy + Make sure proxy configuration is correct. Host Port Use SOCKS proxy? From 3a61290730d46c9a217335cd67f5547a38df2c1f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 17 Sep 2024 22:30:24 +0100 Subject: [PATCH 133/440] core: 6.1.0.1 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index 3d5422612a..4aecc29a97 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.0 +version: 6.1.0.1 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index b58285a349..2a80cb4ea6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.0.0 +version: 6.1.0.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From c13e8e4037c817598393059f60bea5d91cd29088 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 18 Sep 2024 07:28:00 +0100 Subject: [PATCH 134/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index dd97170700..3374b5773a 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -217,11 +217,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E55128D32C989E13001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128CE2C989E12001D165C /* libgmpxx.a */; }; - E55128D42C989E13001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128CF2C989E12001D165C /* libffi.a */; }; - E55128D52C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */; }; - E55128D62C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */; }; - E55128D72C989E13001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D22C989E13001D165C /* libgmp.a */; }; + E55128DD2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */; }; + E55128DE2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */; }; + E55128DF2C9AA96B001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DA2C9AA96B001D165C /* libffi.a */; }; + E55128E02C9AA96B001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DB2C9AA96B001D165C /* libgmpxx.a */; }; + E55128E12C9AA96B001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DC2C9AA96B001D165C /* libgmp.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -556,11 +556,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E55128CE2C989E12001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E55128CF2C989E12001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a"; sourceTree = ""; }; - E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a"; sourceTree = ""; }; - E55128D22C989E13001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a"; sourceTree = ""; }; + E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a"; sourceTree = ""; }; + E55128DA2C9AA96B001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E55128DB2C9AA96B001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E55128DC2C9AA96B001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -651,14 +651,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E55128D32C989E13001D165C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E55128D72C989E13001D165C /* libgmp.a in Frameworks */, - E55128D62C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a in Frameworks */, + E55128DF2C9AA96B001D165C /* libffi.a in Frameworks */, + E55128E12C9AA96B001D165C /* libgmp.a in Frameworks */, + E55128DE2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a in Frameworks */, + E55128DD2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E55128D42C989E13001D165C /* libffi.a in Frameworks */, - E55128D52C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, + E55128E02C9AA96B001D165C /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -735,11 +735,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E55128CF2C989E12001D165C /* libffi.a */, - E55128D22C989E13001D165C /* libgmp.a */, - E55128CE2C989E12001D165C /* libgmpxx.a */, - E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */, - E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */, + E55128DA2C9AA96B001D165C /* libffi.a */, + E55128DC2C9AA96B001D165C /* libgmp.a */, + E55128DB2C9AA96B001D165C /* libgmpxx.a */, + E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */, + E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */, ); path = Libraries; sourceTree = ""; From 17a0f3a2105d0a29502f0774eee4ca07a30481be Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 18 Sep 2024 08:56:43 +0100 Subject: [PATCH 135/440] core: 6.1.0.2, update min versions for remote access to 6.1.0.2 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.yaml b/package.yaml index 4aecc29a97..ab4c9790ef 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.1 +version: 6.1.0.2 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 2a80cb4ea6..51602084f4 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.0.1 +version: 6.1.0.2 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 527b87c010..76d3754f17 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -72,11 +72,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 1, 0, 0] +minRemoteCtrlVersion = AppVersion [6, 1, 0, 2] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 1, 0, 0] +minRemoteHostVersion = AppVersion [6, 1, 0, 2] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 0a4667304c7192f125933d2071d79dd342f966c8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 18 Sep 2024 10:18:06 +0100 Subject: [PATCH 136/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3374b5773a..519c825f04 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -217,11 +217,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E55128DD2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */; }; - E55128DE2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */; }; - E55128DF2C9AA96B001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DA2C9AA96B001D165C /* libffi.a */; }; - E55128E02C9AA96B001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DB2C9AA96B001D165C /* libgmpxx.a */; }; - E55128E12C9AA96B001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DC2C9AA96B001D165C /* libgmp.a */; }; + E55128E72C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */; }; + E55128E82C9AD063001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E32C9AD063001D165C /* libgmp.a */; }; + E55128E92C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */; }; + E55128EA2C9AD063001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E52C9AD063001D165C /* libgmpxx.a */; }; + E55128EB2C9AD063001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E62C9AD063001D165C /* libffi.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -556,11 +556,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a"; sourceTree = ""; }; - E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a"; sourceTree = ""; }; - E55128DA2C9AA96B001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E55128DB2C9AA96B001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E55128DC2C9AA96B001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a"; sourceTree = ""; }; + E55128E32C9AD063001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a"; sourceTree = ""; }; + E55128E52C9AD063001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E55128E62C9AD063001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -651,14 +651,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E55128E72C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a in Frameworks */, + E55128E82C9AD063001D165C /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E55128DF2C9AA96B001D165C /* libffi.a in Frameworks */, - E55128E12C9AA96B001D165C /* libgmp.a in Frameworks */, - E55128DE2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a in Frameworks */, - E55128DD2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a in Frameworks */, + E55128EB2C9AD063001D165C /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E55128E92C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E55128E02C9AA96B001D165C /* libgmpxx.a in Frameworks */, + E55128EA2C9AD063001D165C /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -735,11 +735,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E55128DA2C9AA96B001D165C /* libffi.a */, - E55128DC2C9AA96B001D165C /* libgmp.a */, - E55128DB2C9AA96B001D165C /* libgmpxx.a */, - E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */, - E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */, + E55128E62C9AD063001D165C /* libffi.a */, + E55128E32C9AD063001D165C /* libgmp.a */, + E55128E52C9AD063001D165C /* libgmpxx.a */, + E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */, + E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */, ); path = Libraries; sourceTree = ""; From 529921e16ad93489890af7519b47bdc92d8e0613 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 18 Sep 2024 11:37:32 +0100 Subject: [PATCH 137/440] 6.1-beta.0: ios 237, android 239, desktop 66 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 519c825f04..b0b838eef8 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1891,7 +1891,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1916,7 +1916,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1940,7 +1940,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1965,7 +1965,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1981,11 +1981,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2001,11 +2001,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2026,7 +2026,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2041,7 +2041,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2063,7 +2063,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2078,7 +2078,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2100,7 +2100,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2126,7 +2126,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2151,7 +2151,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2177,7 +2177,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2202,7 +2202,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2217,7 +2217,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2236,7 +2236,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2251,7 +2251,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index ac2fce0e12..6123ce156f 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.0.4 -android.version_code=237 +android.version_name=6.1-beta.0 +android.version_code=239 -desktop.version_name=6.0.4 -desktop.version_code=65 +desktop.version_name=6.1-beta.0 +desktop.version_code=66 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From d47ff3597d2c828b1d2d6c66eae1ea167de50ee5 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 18 Sep 2024 16:16:32 +0100 Subject: [PATCH 138/440] android, desktop: reset incognito on profile picker (#4903) --- .../kotlin/chat/simplex/common/views/newchat/NewChatView.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index a05de0e8b3..0e7a01ddab 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -308,6 +308,7 @@ fun ActiveProfilePicker( switchingProfile.value = true withApi { try { + appPreferences.incognito.set(false) var updatedConn: PendingContactConnection? = null; if (contactConnection != null) { @@ -361,6 +362,7 @@ fun ActiveProfilePicker( switchingProfile.value = true withApi { try { + appPreferences.incognito.set(true) val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) if (conn != null) { withChats { From acc9be1a5b6c9cd617b7ff3c2ee8b35bae51fd10 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 19 Sep 2024 06:57:37 +0000 Subject: [PATCH 139/440] android, desktop: compose 1.6.11 (#4902) --- apps/multiplatform/gradle.properties | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 6123ce156f..11d7b5e7fb 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -21,8 +21,6 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 @@ -34,4 +32,4 @@ desktop.version_code=66 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 -compose.version=1.6.1 +compose.version=1.6.11 From 255538e5d73405bbc880bbdfb7ad19ee803f1b30 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 19 Sep 2024 10:04:19 +0300 Subject: [PATCH 140/440] ios: bulk forward (#4857) * ios: forward multiple messages * ios: batch previews, when sending media messsages (#4861) --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Arturs Krumins Co-authored-by: Diogo Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 155 ++++++++++++------ .../Views/Chat/ChatItemForwardingView.swift | 21 ++- apps/ios/Shared/Views/Chat/ChatView.swift | 145 ++++++++++++++-- .../Chat/ComposeMessage/ComposeView.swift | 144 ++++++++-------- .../Chat/ComposeMessage/ContextItemView.swift | 46 ++++-- .../Chat/ComposeMessage/SendMessageView.swift | 1 + .../Chat/SelectableChatItemToolbars.swift | 23 ++- .../ios/Shared/Views/Helpers/ShareSheet.swift | 18 +- apps/ios/SimpleXChat/APITypes.swift | 15 +- 9 files changed, 404 insertions(+), 164 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 6bbacea245..fab3e10990 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -357,6 +357,12 @@ func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws - throw r } +func apiPlanForwardChatItems(type: ChatType, id: Int64, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) { + let r = await chatSendCmd(.apiPlanForwardChatItems(toChatType: type, toChatId: id, itemIds: itemIds)) + if case let .forwardPlan(_, chatItemIds, forwardConfimation) = r { return (chatItemIds, forwardConfimation) } + throw r +} + func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? { let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemIds: itemIds, ttl: ttl) return await processSendMessageCmd(toChatType: toChatType, cmd: cmd) @@ -1039,77 +1045,122 @@ func standaloneFileInfo(url: String, ctrl: chat_ctrl? = nil) async -> MigrationF } func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async { - if let chatItem = await apiReceiveFile( - fileId: fileId, - userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(), - encrypted: privacyEncryptLocalFilesGroupDefault.get(), + await receiveFiles( + user: user, + fileIds: [fileId], + userApprovedRelays: userApprovedRelays, auto: auto - ) { - await chatItemSimpleUpdate(user, chatItem) - } + ) } -func apiReceiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? { - let r = await chatSendCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) - let am = AlertManager.shared - if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } - if case .rcvFileAcceptedSndCancelled = r { - logger.debug("apiReceiveFile error: sender cancelled file transfer") - if !auto { - am.showAlertMsg( - title: "Cannot receive file", - message: "Sender cancelled file transfer." +func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool = false, auto: Bool = false) async { + var fileIdsToApprove = [Int64]() + var srvsToApprove = Set() + var otherFileErrs = [ChatResponse]() + + for fileId in fileIds { + let r = await chatSendCmd( + .receiveFile( + fileId: fileId, + userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(), + encrypted: privacyEncryptLocalFilesGroupDefault.get(), + inline: nil ) + ) + switch r { + case let .rcvFileAccepted(_, chatItem): + await chatItemSimpleUpdate(user, chatItem) + default: + if let chatError = chatError(r) { + switch chatError { + case let .fileNotApproved(fileId, unknownServers): + fileIdsToApprove.append(fileId) + srvsToApprove.formUnion(unknownServers) + default: + otherFileErrs.append(r) + } + } } - } else if let networkErrorAlert = networkErrorAlert(r) { - logger.error("apiReceiveFile network error: \(String(describing: r))") - if !auto { - am.showAlert(networkErrorAlert) + } + + if !auto { + let otherErrsStr = if otherFileErrs.isEmpty { + "" + } else if otherFileErrs.count == 1 { + "\(otherFileErrs[0])" + } else if otherFileErrs.count == 2 { + "\(otherFileErrs[0])\n\(otherFileErrs[1])" + } else { + "\(otherFileErrs[0])\n\(otherFileErrs[1])\nand \(otherFileErrs.count - 2) other error(s)" } - } else { - switch chatError(r) { - case .fileCancelled: - logger.debug("apiReceiveFile ignoring fileCancelled error") - case .fileAlreadyReceiving: - logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error") - case let .fileNotApproved(fileId, unknownServers): - logger.debug("apiReceiveFile fileNotApproved error") - if !auto { - let srvs = unknownServers.map { s in + + // If there are not approved files, alert is shown the same way both in case of singular and plural files reception + if !fileIdsToApprove.isEmpty { + let srvs = srvsToApprove + .map { s in if let srv = parseServerAddress(s), !srv.hostnames.isEmpty { srv.hostnames[0] } else { serverHost(s) } } - am.showAlert(Alert( - title: Text("Unknown servers!"), - message: Text("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs.sorted().joined(separator: ", "))."), - primaryButton: .default( - Text("Download"), - action: { - Task { - logger.debug("apiReceiveFile fileNotApproved alert - in Task") - if let user = ChatModel.shared.currentUser { - await receiveFile(user: user, fileId: fileId, userApprovedRelays: true) - } + .sorted() + .joined(separator: ", ") + let fIds = fileIdsToApprove + await MainActor.run { + showAlert( + title: NSLocalizedString("Unknown servers!", comment: "alert title"), + message: ( + NSLocalizedString("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs).", comment: "alert message") + + (otherErrsStr != "" ? "\n\n" + NSLocalizedString("Other file errors:\n\(otherErrsStr)", comment: "alert message") : "") + ), + buttonTitle: NSLocalizedString("Download", comment: "alert button"), + buttonAction: { + Task { + logger.debug("apiReceiveFile fileNotApproved alert - in Task") + if let user = ChatModel.shared.currentUser { + await receiveFiles(user: user, fileIds: fIds, userApprovedRelays: true) } } - ), - secondaryButton: .cancel() - )) + }, + cancelButton: true + ) } - default: - logger.error("apiReceiveFile error: \(String(describing: r))") - if !auto { - am.showAlertMsg( - title: "Error receiving file", - message: "Error: \(responseError(r))" + } else if otherFileErrs.count == 1 { // If there is a single other error, we differentiate on it + let errorResponse = otherFileErrs.first! + switch errorResponse { + case let .rcvFileAcceptedSndCancelled(_, rcvFileTransfer): + logger.debug("receiveFiles error: sender cancelled file transfer \(rcvFileTransfer.fileId)") + await MainActor.run { + showAlert( + NSLocalizedString("Cannot receive file", comment: "alert title"), + message: NSLocalizedString("Sender cancelled file transfer.", comment: "alert message") + ) + } + default: + if let chatError = chatError(errorResponse) { + switch chatError { + case .fileCancelled, .fileAlreadyReceiving: + logger.debug("receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") + default: + await MainActor.run { + showAlert( + NSLocalizedString("Error receiving file", comment: "alert title"), + message: responseError(errorResponse) + ) + } + } + } + } + } else if otherFileErrs.count > 1 { // If there are multiple other errors, we show general alert + await MainActor.run { + showAlert( + NSLocalizedString("Error receiving file", comment: "alert title"), + message: NSLocalizedString("File errors:\n\(otherErrsStr)", comment: "alert message") ) } } } - return nil } func cancelFile(user: User, fileId: Int64) async { diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 32993d1a76..79ede14be5 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -14,7 +14,7 @@ struct ChatItemForwardingView: View { @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss - var ci: ChatItem + var chatItems: [ChatItem] var fromChatInfo: ChatInfo @Binding var composeState: ComposeState @@ -73,11 +73,14 @@ struct ChatItemForwardingView: View { } @ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View { - let prohibited = chat.prohibitedByPref( - hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text), - isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false, - isVoice: ci.content.msgContent?.isVoice ?? false - ) + let prohibited = chatItems.map { ci in + chat.prohibitedByPref( + hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text), + isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false, + isVoice: ci.content.msgContent?.isVoice ?? false + ) + }.contains(true) + Button { if prohibited { alert = SomeAlert( @@ -93,10 +96,10 @@ struct ChatItemForwardingView: View { composeState = ComposeState( message: composeState.message, preview: composeState.linkPreview != nil ? composeState.preview : .noPreview, - contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo) + contextItem: .forwardingItems(chatItems: chatItems, fromChatInfo: fromChatInfo) ) } else { - composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo) + composeState = ComposeState.init(forwardingItems: chatItems, fromChatInfo: fromChatInfo) ItemsModel.shared.loadOpenChat(chat.id) } } @@ -123,7 +126,7 @@ struct ChatItemForwardingView: View { #Preview { ChatItemForwardingView( - ci: ChatItem.getSample(1, .directSnd, .now, "hello"), + chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")], fromChatInfo: .direct(contact: Contact.sampleData), composeState: Binding.constant(ComposeState(message: "hello")) ).environmentObject(CurrentColors.toAppTheme()) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 5c11cdc3df..e7359587df 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -42,6 +42,7 @@ struct ChatView: View { @State private var showGroupLinkSheet: Bool = false @State private var groupLink: String? @State private var groupLinkMemberRole: GroupMemberRole = .member + @State private var forwardedChatItems: [ChatItem] = [] @State private var selectedChatItems: Set? = nil @State private var showDeleteSelectedMessages: Bool = false @State private var allowToDeleteSelectedMessagesForAll: Bool = false @@ -98,7 +99,8 @@ struct ChatView: View { if case let .group(groupInfo) = chat.chatInfo { showModerateSelectedMessagesAlert(groupInfo) } - } + }, + forwardItems: forwardSelectedMessages ) } } @@ -135,6 +137,22 @@ struct ChatView: View { } } } + .sheet(isPresented: Binding( + get: { !forwardedChatItems.isEmpty }, + set: { isPresented in + if !isPresented { + forwardedChatItems = [] + selectedChatItems = nil + } + } + )) { + if #available(iOS 16.0, *) { + ChatItemForwardingView(chatItems: forwardedChatItems, fromChatInfo: chat.chatInfo, composeState: $composeState) + .presentationDetents([.fraction(0.8)]) + } else { + ChatItemForwardingView(chatItems: forwardedChatItems, fromChatInfo: chat.chatInfo, composeState: $composeState) + } + } .onAppear { selectedChatItems = nil initChatView() @@ -411,7 +429,8 @@ struct ChatView: View { composeState: $composeState, selectedMember: $selectedMember, revealedChatItem: $revealedChatItem, - selectedChatItems: $selectedChatItems + selectedChatItems: $selectedChatItems, + forwardedChatItems: $forwardedChatItems ) .id(ci.id) // Required to trigger `onAppear` on iOS15 } loadPage: { @@ -701,6 +720,116 @@ struct ChatView: View { } } + private func forwardSelectedMessages() { + Task { + do { + if let selectedChatItems { + let (validItems, confirmation) = try await apiPlanForwardChatItems( + type: chat.chatInfo.chatType, + id: chat.chatInfo.apiId, + itemIds: Array(selectedChatItems) + ) + if let confirmation { + if validItems.count > 0 { + showAlert( + String.localizedStringWithFormat( + NSLocalizedString("Forward %d message(s)?", comment: "alert title"), + validItems.count + ), + message: forwardConfirmationText(confirmation) + "\n" + + NSLocalizedString("Forward messages without files?", comment: "alert message") + ) { + switch confirmation { + case let .filesNotAccepted(fileIds): + [forwardAction(validItems), downloadAction(fileIds), cancelAlertAction] + default: + [forwardAction(validItems), cancelAlertAction] + } + } + } else { + showAlert( + NSLocalizedString("Nothing to forward!", comment: "alert title"), + message: forwardConfirmationText(confirmation) + ) { + switch confirmation { + case let .filesNotAccepted(fileIds): + [downloadAction(fileIds), cancelAlertAction] + default: + [okAlertAction] + } + } + } + } else { + await openForwardingSheet(validItems) + } + } + } catch { + logger.error("Plan forward chat items failed: \(error.localizedDescription)") + } + } + + func forwardConfirmationText(_ fc: ForwardConfirmation) -> String { + switch fc { + case let .filesNotAccepted(fileIds): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) were not downloaded.", comment: "forward confirmation reason"), + fileIds.count + ) + case let .filesInProgress(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) are still being downloaded.", comment: "forward confirmation reason"), + filesCount + ) + case let .filesMissing(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) were deleted.", comment: "forward confirmation reason"), + filesCount + ) + case let .filesFailed(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) failed to download.", comment: "forward confirmation reason"), + filesCount + ) + } + } + + func forwardAction(_ items: [Int64]) -> UIAlertAction { + UIAlertAction( + title: NSLocalizedString("Forward messages", comment: "alert action"), + style: .default, + handler: { _ in Task { await openForwardingSheet(items) } } + ) + } + + func downloadAction(_ fileIds: [Int64]) -> UIAlertAction { + UIAlertAction( + title: NSLocalizedString("Download files", comment: "alert action"), + style: .default, + handler: { _ in + Task { + if let user = ChatModel.shared.currentUser { + await receiveFiles(user: user, fileIds: fileIds) + } + } + } + ) + } + + func openForwardingSheet(_ items: [Int64]) async { + let im = ItemsModel.shared + var items = Set(items) + var fci = [ChatItem]() + for reversedChatItem in im.reversedChatItems { + if items.contains(reversedChatItem.id) { + items.remove(reversedChatItem.id) + fci.insert(reversedChatItem, at: 0) + } + if items.isEmpty { break } + } + await MainActor.run { forwardedChatItems = fci } + } + } + private func loadChatItems(_ cInfo: ChatInfo) { Task { if loadingItems || firstPage { return } @@ -762,10 +891,10 @@ struct ChatView: View { @State private var showDeleteMessages = false @State private var showChatItemInfoSheet: Bool = false @State private var chatItemInfo: ChatItemInfo? - @State private var showForwardingSheet: Bool = false @State private var msgWidth: CGFloat = 0 @Binding var selectedChatItems: Set? + @Binding var forwardedChatItems: [ChatItem] @State private var allowMenu: Bool = true @State private var markedRead = false @@ -1079,14 +1208,6 @@ struct ChatView: View { }) { ChatItemInfoView(ci: ci, chatItemInfo: $chatItemInfo) } - .sheet(isPresented: $showForwardingSheet) { - if #available(iOS 16.0, *) { - ChatItemForwardingView(ci: ci, fromChatInfo: chat.chatInfo, composeState: $composeState) - .presentationDetents([.fraction(0.8)]) - } else { - ChatItemForwardingView(ci: ci, fromChatInfo: chat.chatInfo, composeState: $composeState) - } - } } private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool { @@ -1227,7 +1348,7 @@ struct ChatView: View { var forwardButton: Button { Button { - showForwardingSheet = true + forwardedChatItems = [chatItem] } label: { Label( NSLocalizedString("Forward", comment: "chat item action"), diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 99ab778a0e..8cd851446f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -23,7 +23,7 @@ enum ComposeContextItem { case noContextItem case quotedItem(chatItem: ChatItem) case editingItem(chatItem: ChatItem) - case forwardingItem(chatItem: ChatItem, fromChatInfo: ChatInfo) + case forwardingItems(chatItems: [ChatItem], fromChatInfo: ChatInfo) } enum VoiceMessageRecordingState { @@ -73,10 +73,10 @@ struct ComposeState { } } - init(forwardingItem: ChatItem, fromChatInfo: ChatInfo) { + init(forwardingItems: [ChatItem], fromChatInfo: ChatInfo) { self.message = "" self.preview = .noPreview - self.contextItem = .forwardingItem(chatItem: forwardingItem, fromChatInfo: fromChatInfo) + self.contextItem = .forwardingItems(chatItems: forwardingItems, fromChatInfo: fromChatInfo) self.voiceMessageRecordingState = .noRecording } @@ -112,7 +112,7 @@ struct ComposeState { var forwarding: Bool { switch contextItem { - case .forwardingItem: return true + case .forwardingItems: return true default: return false } } @@ -167,6 +167,13 @@ struct ComposeState { } } + var manyMediaPreviews: Bool { + switch preview { + case let .mediaPreviews(mediaPreviews): return mediaPreviews.count > 1 + default: return false + } + } + var attachmentDisabled: Bool { if editing || forwarding || liveMessage != nil || inProgress { return true } switch preview { @@ -687,7 +694,7 @@ struct ComposeView: View { case let .quotedItem(chatItem: quotedItem): ContextItemView( chat: chat, - contextItem: quotedItem, + contextItems: [quotedItem], contextIcon: "arrowshape.turn.up.left", cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) } ) @@ -695,18 +702,17 @@ struct ComposeView: View { case let .editingItem(chatItem: editingItem): ContextItemView( chat: chat, - contextItem: editingItem, + contextItems: [editingItem], contextIcon: "pencil", cancelContextItem: { clearState() } ) Divider() - case let .forwardingItem(chatItem: forwardedItem, _): + case let .forwardingItems(chatItems, _): ContextItemView( chat: chat, - contextItem: forwardedItem, + contextItems: chatItems, contextIcon: "arrowshape.turn.up.forward", - cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) }, - showSender: false + cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) } ) Divider() } @@ -730,10 +736,11 @@ struct ComposeView: View { } if chat.chatInfo.contact?.nextSendGrpInv ?? false { await sendMemberContactInvitation() - } else if case let .forwardingItem(ci, fromChatInfo) = composeState.contextItem { - sent = await forwardItem(ci, fromChatInfo, ttl) + } else if case let .forwardingItems(chatItems, fromChatInfo) = composeState.contextItem { + // Composed text is send as a reply to the last forwarded item + sent = await forwardItems(chatItems, fromChatInfo, ttl).last if !composeState.message.isEmpty { - sent = await send(checkLinkPreview(), quoted: sent?.id, live: false, ttl: ttl) + _ = await send(checkLinkPreview(), quoted: sent?.id, live: false, ttl: ttl) } } else if case let .editingItem(ci) = composeState.contextItem { sent = await updateMessage(ci, live: live) @@ -750,27 +757,28 @@ struct ComposeView: View { sent = await send(.text(msgText), quoted: quoted, live: live, ttl: ttl) case .linkPreview: sent = await send(checkLinkPreview(), quoted: quoted, live: live, ttl: ttl) - case let .mediaPreviews(mediaPreviews: media): - // TODO batch send: batch media previews + case let .mediaPreviews(media): let last = media.count - 1 + var msgs: [ComposedMessage] = [] if last >= 0 { for i in 0.. 0 { + // Sleep to allow `progressByTimeout` update be rendered + try? await Task.sleep(nanoseconds: 100_000000) + } + if let (fileSource, msgContent) = mediaContent(media[i], text: "") { + msgs.append(ComposedMessage(fileSource: fileSource, msgContent: msgContent)) } - _ = try? await Task.sleep(nanoseconds: 100_000000) } - if case (_, .video(_, _, _)) = media[last] { - sent = await sendVideo(media[last], text: msgText, quoted: quoted, live: live, ttl: ttl) - } else { - sent = await sendImage(media[last], text: msgText, quoted: quoted, live: live, ttl: ttl) + if let (fileSource, msgContent) = mediaContent(media[last], text: msgText) { + msgs.append(ComposedMessage(fileSource: fileSource, quotedItemId: quoted, msgContent: msgContent)) } } - if sent == nil { - sent = await send(.text(msgText), quoted: quoted, live: live, ttl: ttl) + if msgs.isEmpty { + msgs = [ComposedMessage(quotedItemId: quoted, msgContent: .text(msgText))] } + sent = await send(msgs, live: live, ttl: ttl).last + case let .voicePreview(recordingFileName, duration): stopPlayback.toggle() let file = voiceCryptoFile(recordingFileName) @@ -792,6 +800,20 @@ struct ComposeView: View { } return sent + func mediaContent(_ media: (String, UploadContent?), text: String) -> (CryptoFile?, MsgContent)? { + let (previewImage, uploadContent) = media + return switch uploadContent { + case let .simpleImage(image): + (saveImage(image), .image(text: text, image: previewImage)) + case let .animatedImage(image): + (saveAnimImage(image), .image(text: text, image: previewImage)) + case let .video(_, url, duration): + (moveTempFileFromURL(url), .video(text: text, image: previewImage, duration: duration)) + case .none: + nil + } + } + func sending() async { await MainActor.run { composeState.inProgress = true } } @@ -855,23 +877,6 @@ struct ComposeView: View { } } - func sendImage(_ imageData: (String, UploadContent?), text: String = "", quoted: Int64? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { - let (image, data) = imageData - if let data = data, let savedFile = saveAnyImage(data) { - return await send(.image(text: text, image: image), quoted: quoted, file: savedFile, live: live, ttl: ttl) - } - return nil - } - - func sendVideo(_ imageData: (String, UploadContent?), text: String = "", quoted: Int64? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { - let (image, data) = imageData - if case let .video(_, url, duration) = data, let savedFile = moveTempFileFromURL(url) { - ChatModel.shared.filesToDelete.remove(url) - return await send(.video(text: text, image: image, duration: duration), quoted: quoted, file: savedFile, live: live, ttl: ttl) - } - return nil - } - func voiceCryptoFile(_ fileName: String) -> CryptoFile? { if !privacyEncryptLocalFilesGroupDefault.get() { return CryptoFile.plain(fileName) @@ -888,17 +893,22 @@ struct ComposeView: View { } func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { + await send( + [ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc)], + live: live, + ttl: ttl + ).first + } + + func send(_ msgs: [ComposedMessage], live: Bool, ttl: Int?) async -> [ChatItem] { if let chatItems = chat.chatInfo.chatType == .local - ? await apiCreateChatItems( - noteFolderId: chat.chatInfo.apiId, - composedMessages: [ComposedMessage(fileSource: file, msgContent: mc)] - ) + ? await apiCreateChatItems(noteFolderId: chat.chatInfo.apiId, composedMessages: msgs) : await apiSendMessages( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, live: live, ttl: ttl, - composedMessages: [ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc)] + composedMessages: msgs ) { await MainActor.run { chatModel.removeLiveDummy(animated: false) @@ -906,33 +916,43 @@ struct ComposeView: View { chatModel.addChatItem(chat.chatInfo, chatItem) } } - // UI only supports sending one item at a time - return chatItems.first + return chatItems } - if let file = file { - removeFile(file.filePath) + for msg in msgs { + if let file = msg.fileSource { + removeFile(file.filePath) + } } - return nil + return [] } - func forwardItem(_ forwardedItem: ChatItem, _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> ChatItem? { + func forwardItems(_ forwardedItems: [ChatItem], _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> [ChatItem] { if let chatItems = await apiForwardChatItems( toChatType: chat.chatInfo.chatType, toChatId: chat.chatInfo.apiId, fromChatType: fromChatInfo.chatType, fromChatId: fromChatInfo.apiId, - itemIds: [forwardedItem.id], + itemIds: forwardedItems.map { $0.id }, ttl: ttl ) { await MainActor.run { for chatItem in chatItems { chatModel.addChatItem(chat.chatInfo, chatItem) } + if forwardedItems.count != chatItems.count { + showAlert( + String.localizedStringWithFormat( + NSLocalizedString("%d messages not forwarded", comment: "alert title"), + forwardedItems.count - chatItems.count + ), + message: NSLocalizedString("Messages were deleted after you selected them.", comment: "alert message") + ) + } } - // TODO batch send: forward multiple messages - return chatItems.first + return chatItems + } else { + return [] } - return nil } func checkLinkPreview() -> MsgContent { @@ -949,14 +969,6 @@ struct ComposeView: View { return .text(msgText) } } - - func saveAnyImage(_ img: UploadContent) -> CryptoFile? { - switch img { - case let .simpleImage(image): return saveImage(image) - case let .animatedImage(image): return saveAnimImage(image) - default: return nil - } - } } private func startVoiceMessageRecording() async { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index 6245bbe21f..8b988f5624 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -12,7 +12,7 @@ import SimpleXChat struct ContextItemView: View { @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat - let contextItem: ChatItem + let contextItems: [ChatItem] let contextIcon: String let cancelContextItem: () -> Void var showSender: Bool = true @@ -24,13 +24,22 @@ struct ContextItemView: View { .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) .foregroundColor(theme.colors.secondary) - if showSender, let sender = contextItem.memberDisplayName { - VStack(alignment: .leading, spacing: 4) { - Text(sender).font(.caption).foregroundColor(theme.colors.secondary) - msgContentView(lines: 2) - } + if let singleItem = contextItems.first, contextItems.count == 1 { + if showSender, let sender = singleItem.memberDisplayName { + VStack(alignment: .leading, spacing: 4) { + Text(sender).font(.caption).foregroundColor(theme.colors.secondary) + msgContentView(lines: 2, contextItem: singleItem) + } + } else { + msgContentView(lines: 3, contextItem: singleItem) + } } else { - msgContentView(lines: 3) + Text( + chat.chatInfo.chatType == .local + ? "Saving \(contextItems.count) messages" + : "Forwarding \(contextItems.count) messages" + ) + .italic() } Spacer() Button { @@ -45,23 +54,32 @@ struct ContextItemView: View { .padding(12) .frame(minHeight: 54) .frame(maxWidth: .infinity) - .background(chatItemFrameColor(contextItem, theme)) + .background(background) } - private func msgContentView(lines: Int) -> some View { - contextMsgPreview() + private var background: Color { + contextItems.first + .map { chatItemFrameColor($0, theme) } + ?? Color(uiColor: .tertiarySystemBackground) + } + + private func msgContentView(lines: Int, contextItem: ChatItem) -> some View { + contextMsgPreview(contextItem) .multilineTextAlignment(isRightToLeft(contextItem.text) ? .trailing : .leading) .lineLimit(lines) } - private func contextMsgPreview() -> Text { + private func contextMsgPreview(_ contextItem: ChatItem) -> Text { return attachment() + messageText(contextItem.text, contextItem.formattedText, nil, preview: true, showSecrets: false, secondaryColor: theme.colors.secondary) func attachment() -> Text { + let isFileLoaded = if let fileSource = getLoadedFileSource(contextItem.file) { + FileManager.default.fileExists(atPath: getAppFilePath(fileSource.filePath).path) + } else { false } switch contextItem.content.msgContent { - case .file: return image("doc.fill") + case .file: return isFileLoaded ? image("doc.fill") : Text("") case .image: return image("photo") - case .voice: return image("play.fill") + case .voice: return isFileLoaded ? image("play.fill") : Text("") default: return Text("") } } @@ -75,6 +93,6 @@ struct ContextItemView: View { struct ContextItemView_Previews: PreviewProvider { static var previews: some View { let contextItem: ChatItem = ChatItem.getSample(1, .directSnd, .now, "hello") - return ContextItemView(chat: Chat.sampleData, contextItem: contextItem, contextIcon: "pencil.circle", cancelContextItem: {}) + return ContextItemView(chat: Chat.sampleData, contextItems: [contextItem], contextIcon: "pencil.circle", cancelContextItem: {}) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index 9ad6e986bd..50ec8f28c1 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -227,6 +227,7 @@ struct SendMessageView: View { !composeState.editing { if case .noContextItem = composeState.contextItem, !composeState.voicePreview, + !composeState.manyMediaPreviews, let send = sendLiveMessage, let update = updateLiveMessage { Button { diff --git a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift index 87bc73a60e..746c423b8f 100644 --- a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift +++ b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift @@ -32,12 +32,15 @@ struct SelectedItemsBottomToolbar: View { var deleteItems: (Bool) -> Void var moderateItems: () -> Void //var shareItems: () -> Void + var forwardItems: () -> Void @State var deleteEnabled: Bool = false @State var deleteForEveryoneEnabled: Bool = false @State var canModerate: Bool = false @State var moderateEnabled: Bool = false + @State var forwardEnabled: Bool = false + @State var allButtonsDisabled = false var body: some View { @@ -50,6 +53,7 @@ struct SelectedItemsBottomToolbar: View { } label: { Image(systemName: "trash") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) .foregroundColor(!deleteEnabled || allButtonsDisabled ? theme.colors.secondary: .red) } @@ -61,24 +65,24 @@ struct SelectedItemsBottomToolbar: View { } label: { Image(systemName: "flag") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) .foregroundColor(!moderateEnabled || allButtonsDisabled ? theme.colors.secondary : .red) } .disabled(!moderateEnabled || allButtonsDisabled) .opacity(canModerate ? 1 : 0) - Spacer() Button { - //shareItems() + forwardItems() } label: { - Image(systemName: "square.and.arrow.up") + Image(systemName: "arrowshape.turn.up.forward") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(allButtonsDisabled ? theme.colors.secondary : theme.colors.primary) + .foregroundColor(!forwardEnabled || allButtonsDisabled ? theme.colors.secondary : theme.colors.primary) } - .disabled(allButtonsDisabled) - .opacity(0) + .disabled(!forwardEnabled || allButtonsDisabled) } .frame(maxHeight: .infinity) .padding([.leading, .trailing], 12) @@ -106,15 +110,16 @@ struct SelectedItemsBottomToolbar: View { if let selected = selectedItems { let me: Bool let onlyOwnGroupItems: Bool - (deleteEnabled, deleteForEveryoneEnabled, me, onlyOwnGroupItems, selectedChatItems) = chatItems.reduce((true, true, true, true, [])) { (r, ci) in + (deleteEnabled, deleteForEveryoneEnabled, me, onlyOwnGroupItems, forwardEnabled, selectedChatItems) = chatItems.reduce((true, true, true, true, true, [])) { (r, ci) in if selected.contains(ci.id) { - var (de, dee, me, onlyOwnGroupItems, sel) = r + var (de, dee, me, onlyOwnGroupItems, fe, sel) = r de = de && ci.canBeDeletedForSelf dee = dee && ci.meta.deletable && !ci.localNote onlyOwnGroupItems = onlyOwnGroupItems && ci.chatDir == .groupSnd me = me && ci.content.msgContent != nil && ci.memberToModerate(chatInfo) != nil + fe = fe && ci.content.msgContent != nil && ci.meta.itemDeleted == nil && !ci.isLiveDummy 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) + return (de, dee, me, onlyOwnGroupItems, fe, sel) } else { return r } diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 7b80dd1544..b8de0e4ceb 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -47,8 +47,24 @@ func showAlert( buttonAction() }) if cancelButton { - alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel)) + alert.addAction(cancelAlertAction) } topController.present(alert, animated: true) } } + +func showAlert( + _ title: String, + message: String? = nil, + actions: () -> [UIAlertAction] = { [okAlertAction] } +) { + if let topController = getTopViewController() { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + for action in actions() { alert.addAction(action) } + topController.present(alert, animated: true) + } +} + +let okAlertAction = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default) + +let cancelAlertAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index c210431d12..b0cd62a867 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -49,6 +49,7 @@ public enum ChatCommand { case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) + case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64]) case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) case apiGetNtfToken case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) @@ -204,6 +205,7 @@ public enum ChatCommand { case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" + case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): let ttlStr = ttl != nil ? "\(ttl!)" : "default" return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" @@ -359,6 +361,7 @@ public enum ChatCommand { case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" case .apiChatItemReaction: return "apiChatItemReaction" + case .apiPlanForwardChatItems: return "apiPlanForwardChatItems" case .apiForwardChatItems: return "apiForwardChatItems" case .apiGetNtfToken: return "apiGetNtfToken" case .apiRegisterToken: return "apiRegisterToken" @@ -601,6 +604,7 @@ public enum ChatResponse: Decodable, Error { case groupEmpty(user: UserRef, groupInfo: GroupInfo) case userContactLinkSubscribed case newChatItems(user: UserRef, chatItems: [AChatItem]) + case forwardPlan(user: UserRef, chatItemIds: [Int64], forwardConfirmation: ForwardConfirmation?) case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) case chatItemUpdated(user: UserRef, chatItem: AChatItem) case chatItemNotChanged(user: UserRef, chatItem: AChatItem) @@ -772,6 +776,7 @@ public enum ChatResponse: Decodable, Error { case .groupEmpty: return "groupEmpty" case .userContactLinkSubscribed: return "userContactLinkSubscribed" case .newChatItems: return "newChatItems" + case .forwardPlan: return "forwardPlan" case .chatItemsStatusesUpdated: return "chatItemsStatusesUpdated" case .chatItemUpdated: return "chatItemUpdated" case .chatItemNotChanged: return "chatItemNotChanged" @@ -943,6 +948,7 @@ public enum ChatResponse: Decodable, Error { case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) + case let .forwardPlan(u, chatItemIds, forwardConfirmation): return withUser(u, "items: \(chatItemIds) forwardConfirmation: \(String(describing: forwardConfirmation))") case let .chatItemsStatusesUpdated(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) @@ -1134,7 +1140,7 @@ public enum ChatPagination { public struct ComposedMessage: Encodable { public var fileSource: CryptoFile? var quotedItemId: Int64? - var msgContent: MsgContent + public var msgContent: MsgContent public init(fileSource: CryptoFile? = nil, quotedItemId: Int64? = nil, msgContent: MsgContent) { self.fileSource = fileSource @@ -1595,6 +1601,13 @@ public enum NetworkStatus: Decodable, Equatable { } } +public enum ForwardConfirmation: Decodable, Hashable { + case filesNotAccepted(fileIds: [Int64]) + case filesInProgress(filesCount: Int) + case filesMissing(filesCount: Int) + case filesFailed(filesCount: Int) +} + public struct ConnNetworkStatus: Decodable { public var agentConnId: String public var networkStatus: NetworkStatus From 10ded1530cf9396feddddde7ee3fabf14ca84a89 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:14:50 +0400 Subject: [PATCH 141/440] desktop: cleanup current invitation when closing new chat sheet by pressing on center modal (#4904) --- .../kotlin/chat/simplex/common/views/newchat/NewChatView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 0e7a01ddab..8e6af4080d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -68,7 +68,7 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC * Otherwise, it will be called here AFTER [AddContactLearnMore] is launched and will clear the value too soon. * It will be dropped automatically when connection established or when user goes away from this screen. **/ - if (chatModel.showingInvitation.value != null && ModalManager.start.openModalCount() == 1) { + if (chatModel.showingInvitation.value != null && ModalManager.start.openModalCount() <= 1) { val conn = contactConnection.value if (chatModel.showingInvitation.value?.connChatUsed == false && conn != null) { AlertManager.shared.showAlertDialog( From c5813b348978c06a2a24c4bb8bc377f262cc91fa Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 19 Sep 2024 08:36:54 +0000 Subject: [PATCH 142/440] android, desktop: catch URI creation errors (#4901) * android, desktop: catch URI creation errors * showing alert when pasting an incorrect link * moved from Uri to String while processing SimpleX links --- .../src/main/java/chat/simplex/app/MainActivity.kt | 9 +++------ .../kotlin/chat/simplex/common/model/ChatModel.kt | 2 +- .../common/views/chat/group/GroupMemberInfoView.kt | 3 +-- .../simplex/common/views/chatlist/ChatListView.kt | 4 ++-- .../chat/simplex/common/views/helpers/Utils.kt | 7 +++---- .../chat/simplex/common/views/newchat/ConnectPlan.kt | 12 ++++++------ .../simplex/common/views/newchat/NewChatSheet.kt | 2 +- .../chat/simplex/common/views/newchat/NewChatView.kt | 2 +- .../simplex/common/views/call/CallView.desktop.kt | 2 +- 9 files changed, 19 insertions(+), 24 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index c63b6cb497..f29c0c3387 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -150,12 +150,9 @@ fun processIntent(intent: Intent?) { "android.intent.action.VIEW" -> { val uri = intent.data if (uri != null) { - val transformedUri = uri.toURIOrNull() - if (transformedUri != null) { - chatModel.appOpenUrl.value = null to transformedUri - } else { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_parsing_uri_title), generalGetString(MR.strings.error_parsing_uri_desc)) - } + chatModel.appOpenUrl.value = null to uri.toString() + } else { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_parsing_uri_title), generalGetString(MR.strings.error_parsing_uri_desc)) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 5671322d17..ac3397ce97 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -82,7 +82,7 @@ object ChatModel { val desktopOnboardingRandomPassword = mutableStateOf(false) // set when app is opened via contact or invitation URI (rhId, uri) - val appOpenUrl = mutableStateOf?>(null) + val appOpenUrl = mutableStateOf?>(null) // Needed to check for bottom nav bar and to apply or not navigation bar color on Android val newChatSheetVisible = mutableStateOf(false) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index f814fb2eb8..9981d70a52 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -665,9 +665,8 @@ private fun updateMemberRoleDialog( fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) { try { - val uri = URI(connReqUri) withBGApi { - planAndConnect(rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) + planAndConnect(rhId, connReqUri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 54c67674ad..50949b0b16 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -478,7 +478,7 @@ private fun ToggleFilterEnabledButton() { @Composable expect fun ActiveCallInteractiveArea(call: Call) -fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) { +fun connectIfOpenedViaUri(rhId: Long?, uri: String, chatModel: ChatModel) { Log.d(TAG, "connectIfOpenedViaUri: opened via link") if (chatModel.currentUser.value == null) { chatModel.appOpenUrl.value = rhId to uri @@ -566,7 +566,7 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState< withBGApi { planAndConnect( chatModel.remoteHostId(), - URI.create(link), + link, incognito = null, filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id }, filterKnownGroup = { searchChatFilteredBySimplexLink.value = it.id }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 7b504116cc..39611361e3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -480,12 +480,11 @@ inline fun serializableSaver(): Saver = Saver( ) fun UriHandler.openVerifiedSimplexUri(uri: String) { - val URI = try { URI.create(uri) } catch (e: Exception) { null } - if (URI != null) { - connectIfOpenedViaUri(chatModel.remoteHostId(), URI, ChatModel) - } + connectIfOpenedViaUri(chatModel.remoteHostId(), uri, ChatModel) } +fun uriCreateOrNull(uri: String) = try { URI.create(uri) } catch (e: Exception) { null } + fun UriHandler.openUriCatching(uri: String) { try { openUri(uri) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index e49fbcf1e6..6c18e47df3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -20,7 +20,7 @@ enum class ConnectionLinkType { suspend fun planAndConnect( rhId: Long?, - uri: URI, + uri: String, incognito: Boolean?, close: (() -> Unit)?, cleanup: (() -> Unit)? = null, @@ -29,7 +29,7 @@ suspend fun planAndConnect( ) { val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri.toString()) if (connectionPlan != null) { - val link = strHasSingleSimplexLink(uri.toString().trim()) + val link = strHasSingleSimplexLink(uri.trim()) val linkText = if (link?.format is Format.SimplexLink) "

    ${link.simplexLinkText(link.format.linkType, link.format.smpHosts)}" else @@ -323,13 +323,13 @@ suspend fun planAndConnect( suspend fun connectViaUri( chatModel: ChatModel, rhId: Long?, - uri: URI, + uri: String, incognito: Boolean, connectionPlan: ConnectionPlan?, close: (() -> Unit)?, cleanup: (() -> Unit)?, ) { - val pcc = chatModel.controller.apiConnect(rhId, incognito, uri.toString()) + val pcc = chatModel.controller.apiConnect(rhId, incognito, uri) val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION if (pcc != null) { withChats { @@ -361,7 +361,7 @@ fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType fun askCurrentOrIncognitoProfileAlert( chatModel: ChatModel, rhId: Long?, - uri: URI, + uri: String, connectionPlan: ConnectionPlan?, close: (() -> Unit)?, title: String, @@ -417,7 +417,7 @@ fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, co fun ownGroupLinkConfirmConnect( chatModel: ChatModel, rhId: Long?, - uri: URI, + uri: String, linkText: String, incognito: Boolean?, connectionPlan: ConnectionPlan?, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 2a6c8838e4..1a3ea10806 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -482,7 +482,7 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState< withBGApi { planAndConnect( chatModel.remoteHostId(), - URI.create(link), + link, incognito = null, filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id }, close = close, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 8e6af4080d..3ac8cdee64 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -655,7 +655,7 @@ private suspend fun verify(rhId: Long?, text: String?, close: () -> Unit): Boole private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanup: (() -> Unit)? = null) { planAndConnect( rhId, - URI.create(link), + link, close = close, cleanup = cleanup, incognito = null diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt index d6331616cc..8d40ab4c0a 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt @@ -204,7 +204,7 @@ fun startServer(onResponse: (WVAPIMessage) -> Unit): NanoWSD { return when { session.headers["upgrade"] == "websocket" -> super.handle(session) session.uri.contains("/simplex/call/") -> resourcesToResponse("/desktop/call.html") - else -> resourcesToResponse(URI.create(session.uri).path) + else -> resourcesToResponse(uriCreateOrNull(session.uri)?.path ?: return newFixedLengthResponse("Error parsing URL")) } } } From f7cb6f279640654f6b21186490c0880c7e6ca74f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 19 Sep 2024 14:10:07 +0100 Subject: [PATCH 143/440] Revert "android, desktop: compose 1.6.11 (#4902)" This reverts commit acc9be1a5b6c9cd617b7ff3c2ee8b35bae51fd10. --- apps/multiplatform/gradle.properties | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 11d7b5e7fb..6123ce156f 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -21,6 +21,8 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 @@ -32,4 +34,4 @@ desktop.version_code=66 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 -compose.version=1.6.11 +compose.version=1.6.1 From d69e222b7b27351ad5f5f1d6355507c6b642996b Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 19 Sep 2024 15:39:14 +0100 Subject: [PATCH 144/440] android, desktop: bulk forward (#4868) --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/ChatModel.kt | 8 + .../chat/simplex/common/model/SimpleXAPI.kt | 202 +++++++++++++----- .../simplex/common/views/chat/ChatView.kt | 166 +++++++++++++- .../simplex/common/views/chat/ComposeView.kt | 70 +++--- .../common/views/chat/ContextItemView.kt | 66 +++--- .../views/chat/SelectableChatItemToolbars.kt | 29 ++- .../common/views/chatlist/ShareListView.kt | 16 +- .../simplex/common/views/helpers/Enums.kt | 5 +- .../commonMain/resources/MR/base/strings.xml | 16 ++ 9 files changed, 450 insertions(+), 128 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index ac3397ce97..4fcda25855 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1404,6 +1404,14 @@ class Group ( var members: List ) +@Serializable +sealed class ForwardConfirmation { + @Serializable @SerialName("filesNotAccepted") data class FilesNotAccepted(val fileIds: List) : ForwardConfirmation() + @Serializable @SerialName("filesInProgress") data class FilesInProgress(val filesCount: Int) : ForwardConfirmation() + @Serializable @SerialName("filesMissing") data class FilesMissing(val filesCount: Int) : ForwardConfirmation() + @Serializable @SerialName("filesFailed") data class FilesFailed(val filesCount: Int) : ForwardConfirmation() +} + @Serializable data class GroupInfo ( val groupId: Long, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 906ca826ca..7bf97a6e37 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1,18 +1,19 @@ package chat.simplex.common.model import SectionItemView -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.layout.* +import androidx.compose.material.* import chat.simplex.common.views.helpers.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.setNetCfg import chat.simplex.common.model.ChatModel.changingActiveUserMutex @@ -905,7 +906,15 @@ object ChatController { return processSendMessageCmd(rh, cmd)?.map { it.chatItem } } - + suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, chatItemIds: List): CR.ForwardPlan? { + return when (val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, chatItemIds))) { + is CR.ForwardPlan -> r + else -> { + apiErrorAlert("apiPlanForwardChatItems", generalGetString(MR.strings.error_forwarding_messages), r) + null + } + } + } suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, mc: MsgContent, live: Boolean = false): AChatItem? { val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, itemId, mc, live)) @@ -1541,50 +1550,132 @@ object ChatController { } } - suspend fun apiReceiveFile(rh: Long?, fileId: Long, userApprovedRelays: Boolean, encrypted: Boolean, inline: Boolean? = null, auto: Boolean = false): AChatItem? { - // -1 here is to override default behavior of providing current remote host id because file can be asked by local device while remote is connected - val r = sendCmd(rh, CC.ReceiveFile(fileId, userApprovedRelays = userApprovedRelays, encrypt = encrypted, inline = inline)) - return when (r) { - is CR.RcvFileAccepted -> r.chatItem - is CR.RcvFileAcceptedSndCancelled -> { - Log.d(TAG, "apiReceiveFile error: sender cancelled file transfer") - if (!auto) { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.cannot_receive_file), - generalGetString(MR.strings.sender_cancelled_file_transfer) - ) - } - null - } + suspend fun receiveFiles(rhId: Long?, user: UserLike, fileIds: List, userApprovedRelays: Boolean = false, auto: Boolean = false) { + val fileIdsToApprove = mutableListOf() + val srvsToApprove = mutableSetOf() + val otherFileErrs = mutableListOf() - else -> { - if (!(networkErrorAlert(r))) { - val maybeChatError = chatError(r) - if (maybeChatError is ChatErrorType.FileCancelled || maybeChatError is ChatErrorType.FileAlreadyReceiving) { - Log.d(TAG, "apiReceiveFile ignoring FileCancelled or FileAlreadyReceiving error") - } else if (maybeChatError is ChatErrorType.FileNotApproved) { - Log.d(TAG, "apiReceiveFile FileNotApproved error") - if (!auto) { - val srvs = maybeChatError.unknownServers.map{ serverHostname(it) } - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.file_not_approved_title), - text = generalGetString(MR.strings.file_not_approved_descr).format(srvs.sorted().joinToString(separator = ", ")), - confirmText = generalGetString(MR.strings.download_file), - onConfirm = { - val user = chatModel.currentUser.value - if (user != null) { - withBGApi { chatModel.controller.receiveFile(rh, user, fileId, userApprovedRelays = true) } - } - }, - ) - } - } else if (!auto) { - apiErrorAlert("apiReceiveFile", generalGetString(MR.strings.error_receiving_file), r) - } + for (fileId in fileIds) { + val r = sendCmd( + rhId, CC.ReceiveFile( + fileId, + userApprovedRelays = userApprovedRelays || !appPrefs.privacyAskToApproveRelays.get(), + encrypt = appPrefs.privacyEncryptLocalFiles.get(), + inline = null + ) + ) + if (r is CR.RcvFileAccepted) { + chatItemSimpleUpdate(rhId, user, r.chatItem) + } else { + val maybeChatError = chatError(r) + if (maybeChatError is ChatErrorType.FileNotApproved) { + fileIdsToApprove.add(maybeChatError.fileId) + srvsToApprove.addAll(maybeChatError.unknownServers.map { serverHostname(it) }) + } else { + otherFileErrs.add(r) } - null } } + + if (!auto) { + // If there are not approved files, alert is shown the same way both in case of singular and plural files reception + if (fileIdsToApprove.isNotEmpty()) { + showFilesToApproveAlert( + srvsToApprove = srvsToApprove, + otherFileErrs = otherFileErrs, + approveFiles = { + withBGApi { + receiveFiles( + rhId = rhId, + user = user, + fileIds = fileIdsToApprove, + userApprovedRelays = true + ) + } + } + ) + } else if (otherFileErrs.size == 1) { // If there is a single other error, we differentiate on it + when (val errCR = otherFileErrs.first()) { + is CR.RcvFileAcceptedSndCancelled -> { + Log.d(TAG, "receiveFiles error: sender cancelled file transfer") + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.cannot_receive_file), + generalGetString(MR.strings.sender_cancelled_file_transfer) + ) + } + else -> { + val maybeChatError = chatError(errCR) + if (maybeChatError is ChatErrorType.FileCancelled || maybeChatError is ChatErrorType.FileAlreadyReceiving) { + Log.d(TAG, "receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") + } else { + apiErrorAlert("receiveFiles", generalGetString(MR.strings.error_receiving_file), errCR) + } + } + } + } else if (otherFileErrs.size > 1) { // If there are multiple other errors, we show general alert + val errsStr = otherFileErrs.map { json.encodeToString(it) }.joinToString(separator = "\n") + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.error_receiving_file), + text = String.format(generalGetString(MR.strings.n_file_errors), otherFileErrs.size, errsStr), + shareText = true + ) + } + } + } + + private fun showFilesToApproveAlert( + srvsToApprove: Set, + otherFileErrs: List, + approveFiles: (() -> Unit) + ) { + val srvsToApproveStr = srvsToApprove.sorted().joinToString(separator = ", ") + val alertText = + generalGetString(MR.strings.file_not_approved_descr).format(srvsToApproveStr) + + (if (otherFileErrs.isNotEmpty()) "\n" + generalGetString(MR.strings.n_other_file_errors).format(otherFileErrs.size) else "") + + AlertManager.shared.showAlertDialogButtonsColumn(generalGetString(MR.strings.file_not_approved_title), alertText, belowTextContent = { + if (otherFileErrs.isNotEmpty()) { + val clipboard = LocalClipboardManager.current + SimpleButtonFrame(click = { + clipboard.setText(AnnotatedString(otherFileErrs.map { json.encodeToString(it) }.joinToString(separator = "\n"))) + }) { + Icon( + painterResource(MR.images.ic_content_copy), + contentDescription = null, + tint = MaterialTheme.colors.primary, + modifier = Modifier.padding(end = 8.dp) + ) + Text(generalGetString(MR.strings.copy_error), color = MaterialTheme.colors.primary) + } + } + }) { + Row( + Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), + horizontalArrangement = Arrangement.SpaceBetween + ) { + val focusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + // Wait before focusing to prevent auto-confirming if a user used Enter key on hardware keyboard + delay(200) + focusRequester.requestFocus() + } + TextButton(onClick = AlertManager.shared::hideAlert) { Text(generalGetString(MR.strings.cancel_verb)) } + TextButton(onClick = { + approveFiles.invoke() + AlertManager.shared.hideAlert() + }, Modifier.focusRequester(focusRequester)) { Text(generalGetString(MR.strings.download_file)) } + } + } + } + + suspend fun receiveFile(rhId: Long?, user: UserLike, fileId: Long, userApprovedRelays: Boolean = false, auto: Boolean = false) { + receiveFiles( + rhId = rhId, + user = user, + fileIds = listOf(fileId), + userApprovedRelays = userApprovedRelays, + auto = auto + ) } suspend fun cancelFile(rh: Long?, user: User, fileId: Long) { @@ -2689,19 +2780,6 @@ object ChatController { } } - suspend fun receiveFile(rhId: Long?, user: UserLike, fileId: Long, userApprovedRelays: Boolean = false, auto: Boolean = false) { - val chatItem = apiReceiveFile( - rhId, - fileId, - userApprovedRelays = userApprovedRelays || !appPrefs.privacyAskToApproveRelays.get(), - encrypted = appPrefs.privacyEncryptLocalFiles.get(), - auto = auto - ) - if (chatItem != null) { - chatItemSimpleUpdate(rhId, user, chatItem) - } - } - suspend fun leaveGroup(rh: Long?, groupId: Long) { val groupInfo = apiLeaveGroup(rh, groupId) if (groupInfo != null) { @@ -2914,6 +2992,7 @@ sealed class CC { class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List, val mode: CIDeleteMode): CC() class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List): CC() class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC() + class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val chatItemIds: List): CC() class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List, val ttl: Int?): CC() class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC() class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC() @@ -3072,6 +3151,9 @@ sealed class CC { val ttlStr = if (ttl != null) "$ttl" else "default" "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}" } + is ApiPlanForwardChatItems -> { + "/_forward plan ${chatRef(fromChatType, fromChatId)} ${chatItemIds.joinToString(",")}" + } is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}" is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}" is ApiJoinGroup -> "/_join #$groupId" @@ -3216,6 +3298,7 @@ sealed class CC { is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem" is ApiChatItemReaction -> "apiChatItemReaction" is ApiForwardChatItems -> "apiForwardChatItems" + is ApiPlanForwardChatItems -> "apiPlanForwardChatItems" is ApiNewGroup -> "apiNewGroup" is ApiAddMember -> "apiAddMember" is ApiJoinGroup -> "apiJoinGroup" @@ -4878,6 +4961,7 @@ sealed class CR { @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemReaction") class ChatItemReaction(val user: UserRef, val added: Boolean, val reaction: ACIReaction): CR() @Serializable @SerialName("chatItemsDeleted") class ChatItemsDeleted(val user: UserRef, val chatItemDeletions: List, val byUser: Boolean): CR() + @Serializable @SerialName("forwardPlan") class ForwardPlan(val user: UserRef, val itemsCount: Int, val chatItemIds: List, val forwardConfirmation: ForwardConfirmation? = null): CR() // group events @Serializable @SerialName("groupCreated") class GroupCreated(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("sentGroupInvitation") class SentGroupInvitation(val user: UserRef, val groupInfo: GroupInfo, val contact: Contact, val member: GroupMember): CR() @@ -5055,6 +5139,7 @@ sealed class CR { is ChatItemNotChanged -> "chatItemNotChanged" is ChatItemReaction -> "chatItemReaction" is ChatItemsDeleted -> "chatItemsDeleted" + is ForwardPlan -> "forwardPlan" is GroupCreated -> "groupCreated" is SentGroupInvitation -> "sentGroupInvitation" is UserAcceptedGroupSent -> "userAcceptedGroupSent" @@ -5224,6 +5309,7 @@ sealed class CR { is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) is ChatItemReaction -> withUser(user, "added: $added\n${json.encodeToString(reaction)}") is ChatItemsDeleted -> withUser(user, "${chatItemDeletions.map { (deletedChatItem, toChatItem) -> "deletedChatItem: ${json.encodeToString(deletedChatItem)}\ntoChatItem: ${json.encodeToString(toChatItem)}" }} \nbyUser: $byUser") + is ForwardPlan -> withUser(user, "itemsCount: $itemsCount\nchatItemIds: ${json.encodeToString(chatItemIds)}\nforwardConfirmation: ${json.encodeToString(forwardConfirmation)}") is GroupCreated -> withUser(user, json.encodeToString(groupInfo)) is SentGroupInvitation -> withUser(user, "groupInfo: $groupInfo\ncontact: $contact\nmember: $member") is UserAcceptedGroupSent -> json.encodeToString(groupInfo) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 2bef0bdca7..835f884f98 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.* +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs @@ -172,7 +173,30 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - }) } } - } + }, + forwardItems = { + val itemIds = selectedChatItems.value + + if (itemIds != null) { + withBGApi { + val chatItemIds = itemIds.toList() + val forwardPlan = controller.apiPlanForwardChatItems( + rh = chatRh, + fromChatType = chatInfo.chatType, + fromChatId = chatInfo.apiId, + chatItemIds = chatItemIds + ) + + if (forwardPlan != null) { + if (forwardPlan.chatItemIds.count() < chatItemIds.count() || forwardPlan.forwardConfirmation != null) { + handleForwardConfirmation(chatRh, forwardPlan, chatInfo) + } else { + forwardContent(forwardPlan.chatItemIds, chatInfo) + } + } + } + } + }, ) } }, @@ -347,9 +371,9 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - openDirectChat(chatRh, contactId, chatModel) } }, - forwardItem = { cItem, cInfo -> + forwardItem = { cInfo, cItem -> chatModel.chatId.value = null - chatModel.sharedContent.value = SharedContent.Forward(cInfo, cItem) + chatModel.sharedContent.value = SharedContent.Forward(listOf(cItem), cInfo) }, updateContactStats = { contact -> withBGApi { @@ -1416,6 +1440,65 @@ private fun TopEndFloatingButton( } } +@Composable +private fun DownloadFilesButton( + forwardConfirmation: ForwardConfirmation.FilesNotAccepted, + rhId: Long?, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding +) { + val user = chatModel.currentUser.value + + if (user != null) { + TextButton( + contentPadding = contentPadding, + modifier = modifier, + onClick = { + AlertManager.shared.hideAlert() + + withBGApi { + controller.receiveFiles( + rhId = rhId, + fileIds = forwardConfirmation.fileIds, + user = user + ) + } + } + ) { + Text(stringResource(MR.strings.forward_files_not_accepted_receive_files), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } +} + +@Composable +private fun ForwardButton( + forwardPlan: CR.ForwardPlan, + chatInfo: ChatInfo, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding +) { + TextButton( + onClick = { + forwardContent(forwardPlan.chatItemIds, chatInfo) + AlertManager.shared.hideAlert() + }, + modifier = modifier, + contentPadding = contentPadding + ) { + Text(stringResource(MR.strings.forward_chat_item), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } +} + +@Composable +private fun ButtonRow(horizontalArrangement: Arrangement.Horizontal, content: @Composable() (RowScope.() -> Unit)) { + Row( + Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), + horizontalArrangement = horizontalArrangement + ) { + content() + } +} + val chatViewScrollState = MutableStateFlow(false) fun addGroupMembers(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: (() -> Unit)? = null) { @@ -1712,6 +1795,83 @@ private fun ViewConfiguration.bigTouchSlop(slop: Float = 50f) = object: ViewConf override val touchSlop: Float get() = slop } +private fun forwardContent(chatItemsIds: List, chatInfo: ChatInfo) { + chatModel.chatId.value = null + chatModel.sharedContent.value = SharedContent.Forward( + chatModel.chatItems.value.filter { chatItemsIds.contains(it.id) }, + chatInfo + ) +} + +private fun forwardConfirmationAlertDescription(forwardConfirmation: ForwardConfirmation): String { + return when (forwardConfirmation) { + is ForwardConfirmation.FilesNotAccepted -> String.format(generalGetString(MR.strings.forward_files_not_accepted_desc), forwardConfirmation.fileIds.count()) + is ForwardConfirmation.FilesInProgress -> String.format(generalGetString(MR.strings.forward_files_in_progress_desc), forwardConfirmation.filesCount) + is ForwardConfirmation.FilesFailed -> String.format(generalGetString(MR.strings.forward_files_failed_to_receive_desc), forwardConfirmation.filesCount) + is ForwardConfirmation.FilesMissing -> String.format(generalGetString(MR.strings.forward_files_missing_desc), forwardConfirmation.filesCount) + } +} + +private fun handleForwardConfirmation( + rhId: Long?, + forwardPlan: CR.ForwardPlan, + chatInfo: ChatInfo +) { + var alertDescription = if (forwardPlan.forwardConfirmation != null) forwardConfirmationAlertDescription(forwardPlan.forwardConfirmation) else "" + + if (forwardPlan.chatItemIds.isNotEmpty()) { + alertDescription += "\n${generalGetString(MR.strings.forward_alert_forward_messages_without_files)}" + } + + AlertManager.shared.showAlertDialogButtonsColumn( + title = if (forwardPlan.chatItemIds.isNotEmpty()) + String.format(generalGetString(MR.strings.forward_alert_title_messages_to_forward), forwardPlan.chatItemIds.count()) else + generalGetString(MR.strings.forward_alert_title_nothing_to_forward), + text = alertDescription, + buttons = { + if (forwardPlan.chatItemIds.isNotEmpty()) { + when (val confirmation = forwardPlan.forwardConfirmation) { + is ForwardConfirmation.FilesNotAccepted -> { + val fillMaxWidthModifier = Modifier.fillMaxWidth() + val contentPadding = PaddingValues(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL) + Column { + ForwardButton(forwardPlan, chatInfo, fillMaxWidthModifier, contentPadding) + DownloadFilesButton(confirmation, rhId, fillMaxWidthModifier, contentPadding) + TextButton(onClick = { AlertManager.shared.hideAlert() }, modifier = fillMaxWidthModifier, contentPadding = contentPadding) { + Text(stringResource(MR.strings.cancel_verb), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + } + else -> { + ButtonRow(Arrangement.SpaceBetween) { + TextButton(onClick = { AlertManager.shared.hideAlert() }) { + Text(stringResource(MR.strings.cancel_verb), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + ForwardButton(forwardPlan, chatInfo) + } + } + } + } else { + when (val confirmation = forwardPlan.forwardConfirmation) { + is ForwardConfirmation.FilesNotAccepted -> { + ButtonRow(Arrangement.SpaceBetween) { + TextButton(onClick = { AlertManager.shared.hideAlert() }) { + Text(stringResource(MR.strings.cancel_verb), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + DownloadFilesButton(confirmation, rhId) + } + } + else -> ButtonRow(Arrangement.Center) { + TextButton(onClick = { AlertManager.shared.hideAlert() }) { + Text(stringResource(MR.strings.ok), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + } + } + } + ) +} + @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 821a449509..cad18af9bb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -13,7 +13,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontStyle import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -49,7 +48,7 @@ sealed class ComposeContextItem { @Serializable object NoContextItem: ComposeContextItem() @Serializable class QuotedItem(val chatItem: ChatItem): ComposeContextItem() @Serializable class EditingItem(val chatItem: ChatItem): ComposeContextItem() - @Serializable class ForwardingItem(val chatItem: ChatItem, val fromChatInfo: ChatInfo): ComposeContextItem() + @Serializable class ForwardingItems(val chatItems: List, val fromChatInfo: ChatInfo): ComposeContextItem() } @Serializable @@ -85,7 +84,7 @@ data class ComposeState( } val forwarding: Boolean get() = when (contextItem) { - is ComposeContextItem.ForwardingItem -> true + is ComposeContextItem.ForwardingItems -> true else -> false } val sendEnabled: () -> Boolean @@ -407,33 +406,41 @@ fun ComposeView( return null } - suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? { + suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): List? { val cInfo = chat.chatInfo val cs = composeState.value - var sent: ChatItem? + var sent: List? val msgText = text ?: cs.message fun sending() { composeState.value = composeState.value.copy(inProgress = true) } - suspend fun forwardItem(rhId: Long?, forwardedItem: ChatItem, fromChatInfo: ChatInfo, ttl: Int?): ChatItem? { + suspend fun forwardItem(rhId: Long?, forwardedItem: List, fromChatInfo: ChatInfo, ttl: Int?): List? { val chatItems = controller.apiForwardChatItems( rh = rhId, toChatType = chat.chatInfo.chatType, toChatId = chat.chatInfo.apiId, fromChatType = fromChatInfo.chatType, fromChatId = fromChatInfo.apiId, - itemIds = listOf(forwardedItem.id), + itemIds = forwardedItem.map { it.id }, ttl = ttl ) + chatItems?.forEach { chatItem -> withChats { addChatItem(rhId, chat.chatInfo, chatItem) } } - // TODO batch send: forward multiple messages - return chatItems?.firstOrNull() + + if (chatItems != null && chatItems.count() < forwardedItem.count()) { + AlertManager.shared.showAlertMsg( + title = String.format(generalGetString(MR.strings.forward_files_messages_deleted_after_selection_title), forwardedItem.count() - chatItems.count()), + text = generalGetString(MR.strings.forward_files_messages_deleted_after_selection_desc) + ) + } + + return chatItems } fun checkLinkPreview(): MsgContent { @@ -506,16 +513,25 @@ fun ComposeView( if (chat.nextSendGrpInv) { sendMemberContactInvitation() sent = null - } else if (cs.contextItem is ComposeContextItem.ForwardingItem) { - sent = forwardItem(chat.remoteHostId, cs.contextItem.chatItem, cs.contextItem.fromChatInfo, ttl = ttl) + } else if (cs.contextItem is ComposeContextItem.ForwardingItems) { + sent = forwardItem(chat.remoteHostId, cs.contextItem.chatItems, cs.contextItem.fromChatInfo, ttl = ttl) if (cs.message.isNotEmpty()) { - sent = send(chat, checkLinkPreview(), quoted = sent?.id, live = false, ttl = ttl) + sent?.mapIndexed { index, message -> + if (index == sent!!.lastIndex) { + send(chat, checkLinkPreview(), quoted = message.id, live = false, ttl = ttl) + } else { + message + } + } } - } else if (cs.contextItem is ComposeContextItem.EditingItem) { + } + else if (cs.contextItem is ComposeContextItem.EditingItem) { val ei = cs.contextItem.chatItem - sent = updateMessage(ei, chat, live) + val updatedMessage = updateMessage(ei, chat, live) + sent = if (updatedMessage != null) listOf(updatedMessage) else null } else if (liveMessage != null && liveMessage.sent) { - sent = updateMessage(liveMessage.chatItem, chat, live) + val updatedMessage = updateMessage(liveMessage.chatItem, chat, live) + sent = if (updatedMessage != null) listOf(updatedMessage) else null } else { val msgs: ArrayList = ArrayList() val files: ArrayList = ArrayList() @@ -608,21 +624,23 @@ fun ComposeView( localPath = file.filePath ) } - sent = send(chat, content, if (index == 0) quotedItemId else null, file, + val sendResult = send(chat, content, if (index == 0) quotedItemId else null, file, live = if (content !is MsgContent.MCVoice && index == msgs.lastIndex) live else false, ttl = ttl ) + sent = if (sendResult != null) listOf(sendResult) else null } if (sent == null && (cs.preview is ComposePreview.MediaPreview || cs.preview is ComposePreview.FilePreview || cs.preview is ComposePreview.VoicePreview) ) { - sent = send(chat, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) + val sendResult = send(chat, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) + sent = if (sendResult != null) listOf(sendResult) else null } } val wasForwarding = cs.forwarding - val forwardingFromChatId = (cs.contextItem as? ComposeContextItem.ForwardingItem)?.fromChatInfo?.id + val forwardingFromChatId = (cs.contextItem as? ComposeContextItem.ForwardingItems)?.fromChatInfo?.id clearState(live) val draft = chatModel.draft.value if (wasForwarding && chatModel.draftChatId.value == chat.chatInfo.id && forwardingFromChatId != chat.chatInfo.id && draft != null) { @@ -724,8 +742,8 @@ fun ComposeView( val typedMsg = cs.message if ((cs.sendEnabled() || cs.contextItem is ComposeContextItem.QuotedItem) && (cs.liveMessage == null || !cs.liveMessage.sent)) { val ci = sendMessageAsync(typedMsg, live = true, ttl = null) - if (ci != null) { - composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = typedMsg, sent = true)) + if (!ci.isNullOrEmpty()) { + composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci.last(), typedMsg = typedMsg, sentMsg = typedMsg, sent = true)) } } else if (cs.liveMessage == null) { val cItem = chatModel.addLiveDummy(chat.chatInfo) @@ -745,8 +763,8 @@ fun ComposeView( val sentMsg = liveMessageToSend(liveMessage, typedMsg) if (sentMsg != null) { val ci = sendMessageAsync(sentMsg, live = true, ttl = null) - if (ci != null) { - composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true)) + if (!ci.isNullOrEmpty()) { + composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci.last(), typedMsg = typedMsg, sentMsg = sentMsg, sent = true)) } } else if (liveMessage.typedMsg != typedMsg) { composeState.value = composeState.value.copy(liveMessage = liveMessage.copy(typedMsg = typedMsg)) @@ -805,13 +823,13 @@ fun ComposeView( fun contextItemView() { when (val contextItem = composeState.value.contextItem) { ComposeContextItem.NoContextItem -> {} - is ComposeContextItem.QuotedItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_reply)) { + is ComposeContextItem.QuotedItem -> ContextItemView(listOf(contextItem.chatItem), painterResource(MR.images.ic_reply), chatType = chat.chatInfo.chatType) { composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem) } - is ComposeContextItem.EditingItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_edit_filled)) { + is ComposeContextItem.EditingItem -> ContextItemView(listOf(contextItem.chatItem), painterResource(MR.images.ic_edit_filled), chatType = chat.chatInfo.chatType) { clearState() } - is ComposeContextItem.ForwardingItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_forward), showSender = false) { + is ComposeContextItem.ForwardingItems -> ContextItemView(contextItem.chatItems, painterResource(MR.images.ic_forward), showSender = false, chatType = chat.chatInfo.chatType) { composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem) } } @@ -834,7 +852,7 @@ fun ComposeView( is SharedContent.Media -> composeState.processPickedMedia(shared.uris, shared.text) is SharedContent.File -> composeState.processPickedFile(shared.uri, shared.text) is SharedContent.Forward -> composeState.value = composeState.value.copy( - contextItem = ComposeContextItem.ForwardingItem(shared.chatItem, shared.fromChatInfo), + contextItem = ComposeContextItem.ForwardingItems(shared.chatItems, shared.fromChatInfo), preview = if (composeState.value.preview is ComposePreview.CLinkPreview) composeState.value.preview else ComposePreview.NoPreview ) null -> {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt index 0c4efa7d0d..5850f0b7ec 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt @@ -13,28 +13,31 @@ import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.runtime.* import androidx.compose.ui.text.* +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.* import chat.simplex.common.model.* +import chat.simplex.common.platform.getLoadedFilePath +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import kotlinx.datetime.Clock @Composable fun ContextItemView( - contextItem: ChatItem, + contextItems: List, contextIcon: Painter, showSender: Boolean = true, - cancelContextItem: () -> Unit + chatType: ChatType, + cancelContextItem: () -> Unit, ) { - val sent = contextItem.chatDir.sent val sentColor = MaterialTheme.appColors.sentMessage val receivedColor = MaterialTheme.appColors.receivedMessage @Composable - fun MessageText(attachment: ImageResource?, lines: Int) { + fun MessageText(contextItem: ChatItem, attachment: ImageResource?, lines: Int) { val inlineContent: Pair Unit, Map>? = if (attachment != null) { remember(contextItem.id) { val inlineContentBuilder: AnnotatedString.Builder.() -> Unit = { @@ -62,19 +65,24 @@ fun ContextItemView( ) } - fun attachment(): ImageResource? = - when (contextItem.content.msgContent) { - is MsgContent.MCFile -> MR.images.ic_draft_filled + fun attachment(contextItem: ChatItem): ImageResource? { + val fileIsLoaded = getLoadedFilePath(contextItem.file) != null + + return when (contextItem.content.msgContent) { + is MsgContent.MCFile -> if (fileIsLoaded) MR.images.ic_draft_filled else null is MsgContent.MCImage -> MR.images.ic_image - is MsgContent.MCVoice -> MR.images.ic_play_arrow_filled + is MsgContent.MCVoice -> if (fileIsLoaded) MR.images.ic_play_arrow_filled else null else -> null } + } @Composable - fun ContextMsgPreview(lines: Int) { - MessageText(remember(contextItem.id) { attachment() }, lines) + fun ContextMsgPreview(contextItem: ChatItem, lines: Int) { + MessageText(contextItem, remember(contextItem.id) { attachment(contextItem) }, lines) } + val sent = contextItems[0].chatDir.sent + Row( Modifier .padding(top = 8.dp) @@ -97,20 +105,27 @@ fun ContextItemView( contentDescription = stringResource(MR.strings.icon_descr_context), tint = MaterialTheme.colors.secondary, ) - val sender = contextItem.memberDisplayName - if (showSender && sender != null) { - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - sender, - style = TextStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary) - ) - ContextMsgPreview(lines = 2) + + if (contextItems.count() == 1) { + val contextItem = contextItems[0] + val sender = contextItem.memberDisplayName + + if (showSender && sender != null) { + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + sender, + style = TextStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary) + ) + ContextMsgPreview(contextItem, lines = 2) + } + } else { + ContextMsgPreview(contextItem, lines = 3) } - } else { - ContextMsgPreview(lines = 3) + } else if (contextItems.isNotEmpty()) { + Text(String.format(generalGetString(if (chatType == ChatType.Local) MR.strings.compose_save_messages_n else MR.strings.compose_forward_messages_n), contextItems.count()), fontStyle = FontStyle.Italic) } } IconButton(onClick = cancelContextItem) { @@ -129,8 +144,9 @@ fun ContextItemView( fun PreviewContextItemView() { SimpleXTheme { ContextItemView( - contextItem = ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), "hello"), - contextIcon = painterResource(MR.images.ic_edit_filled) + contextItems = listOf(ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), "hello")), + contextIcon = painterResource(MR.images.ic_edit_filled), + chatType = ChatType.Direct ) {} } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt index 2aebe07306..d12e7ac090 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -51,17 +52,29 @@ fun SelectedItemsBottomToolbar( selectedChatItems: MutableState?>, deleteItems: (Boolean) -> Unit, // Boolean - delete for everyone is possible moderateItems: () -> Unit, -// shareItems: () -> Unit, + forwardItems: () -> Unit, ) { val deleteEnabled = remember { mutableStateOf(false) } val deleteForEveryoneEnabled = remember { mutableStateOf(false) } val canModerate = remember { mutableStateOf(false) } val moderateEnabled = remember { mutableStateOf(false) } + val forwardEnabled = remember { mutableStateOf(false) } val allButtonsDisabled = remember { mutableStateOf(false) } Box { // It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}) - Row(Modifier.matchParentSize().background(MaterialTheme.colors.background), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { + Row( + Modifier + .matchParentSize() + .background(MaterialTheme.colors.background) + .pointerInput(Unit) { + detectGesture { + true + } + }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { IconButton({ deleteItems(deleteForEveryoneEnabled.value) }, enabled = deleteEnabled.value && !allButtonsDisabled.value) { Icon( painterResource(MR.images.ic_delete), @@ -80,18 +93,18 @@ fun SelectedItemsBottomToolbar( ) } - IconButton({ /*shareItems()*/ }, Modifier.alpha(0f), enabled = false/*!allButtonsDisabled.value*/) { + IconButton({ forwardItems() }, enabled = forwardEnabled.value && !allButtonsDisabled.value) { Icon( - painterResource(MR.images.ic_share), + painterResource(MR.images.ic_forward), null, Modifier.size(22.dp), - tint = if (allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + tint = if (!forwardEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } } } LaunchedEffect(chatInfo, chatItems, selectedChatItems.value) { - recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, allButtonsDisabled) + recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, allButtonsDisabled) } } @@ -102,6 +115,7 @@ private fun recheckItems(chatInfo: ChatInfo, deleteForEveryoneEnabled: MutableState, canModerate: MutableState, moderateEnabled: MutableState, + forwardEnabled: MutableState, allButtonsDisabled: MutableState ) { val count = selectedChatItems.value?.size ?: 0 @@ -112,6 +126,7 @@ private fun recheckItems(chatInfo: ChatInfo, var rDeleteForEveryoneEnabled = true var rModerateEnabled = true var rOnlyOwnGroupItems = true + var rForwardEnabled = true val rSelectedChatItems = mutableSetOf() for (ci in chatItems) { if (selected.contains(ci.id)) { @@ -119,6 +134,7 @@ private fun recheckItems(chatInfo: ChatInfo, rDeleteForEveryoneEnabled = rDeleteForEveryoneEnabled && ci.meta.deletable && !ci.localNote rOnlyOwnGroupItems = rOnlyOwnGroupItems && ci.chatDir is CIDirection.GroupSnd rModerateEnabled = rModerateEnabled && ci.content.msgContent != null && ci.memberToModerate(chatInfo) != null + rForwardEnabled = rForwardEnabled && ci.content.msgContent != null && ci.meta.itemDeleted == null && !ci.isLiveDummy rSelectedChatItems.add(ci.id) // we are collecting new selected items here to account for any changes in chat items list } } @@ -126,6 +142,7 @@ private fun recheckItems(chatInfo: ChatInfo, deleteEnabled.value = rDeleteEnabled deleteForEveryoneEnabled.value = rDeleteForEveryoneEnabled moderateEnabled.value = rModerateEnabled + forwardEnabled.value = rForwardEnabled selectedChatItems.value = rSelectedChatItems } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index b4d0b05584..769a0b83f6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -58,11 +58,13 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) { hasSimplexLink = hasSimplexLink(sharedContent.text) } is SharedContent.Forward -> { - val mc = sharedContent.chatItem.content.msgContent - if (mc != null) { - isMediaOrFileAttachment = mc.isMediaOrFileAttachment - isVoice = mc.isVoice - hasSimplexLink = hasSimplexLink(mc.text) + sharedContent.chatItems.forEach { ci -> + val mc = ci.content.msgContent + if (mc != null) { + isMediaOrFileAttachment = isMediaOrFileAttachment || mc.isMediaOrFileAttachment + isVoice = isVoice || mc.isVoice + hasSimplexLink = hasSimplexLink || hasSimplexLink(mc.text) + } } } null -> {} @@ -175,11 +177,11 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal title = { Row(verticalAlignment = Alignment.CenterVertically) { Text( - when (chatModel.sharedContent.value) { + when (val v = chatModel.sharedContent.value) { is SharedContent.Text -> stringResource(MR.strings.share_message) is SharedContent.Media -> stringResource(MR.strings.share_image) is SharedContent.File -> stringResource(MR.strings.share_file) - is SharedContent.Forward -> stringResource(MR.strings.forward_message) + is SharedContent.Forward -> if (v.chatItems.size > 1) stringResource(MR.strings.forward_multiple) else stringResource(MR.strings.forward_message) null -> stringResource(MR.strings.share_message) }, color = MaterialTheme.colors.onBackground, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt index ee4638445b..30811d5c94 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt @@ -2,8 +2,7 @@ package chat.simplex.common.views.helpers import androidx.compose.runtime.saveable.Saver -import chat.simplex.common.model.ChatInfo -import chat.simplex.common.model.ChatItem +import chat.simplex.common.model.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.serialization.* import kotlinx.serialization.descriptors.* @@ -15,7 +14,7 @@ sealed class SharedContent { data class Text(val text: String): SharedContent() data class Media(val text: String, val uris: List): SharedContent() data class File(val text: String, val uri: URI): SharedContent() - data class Forward(val chatItem: ChatItem, val fromChatInfo: ChatInfo): SharedContent() + data class Forward(val chatItems: List, val fromChatInfo: ChatInfo): SharedContent() } enum class AnimatedViewState { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 55cd86e035..08a16075a4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -125,6 +125,7 @@ Destination server version of %1$s is incompatible with forwarding server %2$s. Please try later. Error sending message + Error forwarding messages Error creating message Error loading details Error adding member(s) @@ -133,7 +134,9 @@ Sender cancelled file transfer. Unknown servers! Without Tor or VPN, your IP address will be visible to these XFTP relays:\n%1$s. + %1$d other file error(s). Error receiving file + %1$d file error(s):\n%2$s Error creating address Contact already exists You are already connected to %1$s. @@ -378,12 +381,23 @@ No selected chat Nothing selected Selected %d + Forward %1$s message(s)? + Nothing to forward! + Forward messages without files? + Messages were deleted after you selected them. + %1$d file(s) were not downloaded. + %1$d file(s) are still being downloaded. + %1$d file(s) failed to download. + %1$d file(s) were deleted. + Download + %1$s messages not forwarded Share message… Share media… Share file… Forward message… + Forward messages… Cannot send message Selected chat preferences prohibit this message. @@ -405,6 +419,8 @@ Files and media prohibited! Only group owners can enable files and media. Send direct message to connect + Forwarding %1$s messages + Saving %1$s messages SimpleX links not allowed Files and media not allowed Voice messages not allowed From 25fb099c4484601dc788012db49296d1e7ed462d Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:00:08 +0000 Subject: [PATCH 145/440] android, desktop: fix always connecting state in call (#4911) --- .../src/commonMain/resources/assets/www/call.js | 14 ++++++++++++-- packages/simplex-chat-webrtc/src/call.ts | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js index 571c494c7c..68b2f1bf54 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js @@ -34,6 +34,9 @@ var useWorker = false; var isDesktop = false; var localizedState = ""; var localizedDescription = ""; +// When one side of a call sends candidates tot fast (until local & remote descriptions are set), that candidates +// will be stored here and then set when the call will be ready to process them +let afterCallInitializedCandidates = []; const processCommand = (function () { const defaultIceServers = [ { urls: ["stuns:stun.simplex.im:443"] }, @@ -234,6 +237,8 @@ const processCommand = (function () { const pc = activeCall.connection; const offer = await pc.createOffer(); await pc.setLocalDescription(offer); + addIceCandidates(pc, afterCallInitializedCandidates); + afterCallInitializedCandidates = []; // for debugging, returning the command for callee to use // resp = { // type: "offer", @@ -272,6 +277,8 @@ const processCommand = (function () { const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); addIceCandidates(pc, remoteIceCandidates); + addIceCandidates(pc, afterCallInitializedCandidates); + afterCallInitializedCandidates = []; // same as command for caller to use resp = { type: "answer", @@ -297,17 +304,20 @@ const processCommand = (function () { // console.log("answer remoteIceCandidates", JSON.stringify(remoteIceCandidates)) await pc.setRemoteDescription(new RTCSessionDescription(answer)); addIceCandidates(pc, remoteIceCandidates); + addIceCandidates(pc, afterCallInitializedCandidates); + afterCallInitializedCandidates = []; resp = { type: "ok" }; } break; case "ice": + const remoteIceCandidates = parse(command.iceCandidates); if (pc) { - const remoteIceCandidates = parse(command.iceCandidates); addIceCandidates(pc, remoteIceCandidates); resp = { type: "ok" }; } else { - resp = { type: "error", message: "ice: call not started" }; + afterCallInitializedCandidates.push(...remoteIceCandidates); + resp = { type: "error", message: "ice: call not started yet, will add candidates later" }; } break; case "media": diff --git a/packages/simplex-chat-webrtc/src/call.ts b/packages/simplex-chat-webrtc/src/call.ts index 19682249e9..2e9a58750c 100644 --- a/packages/simplex-chat-webrtc/src/call.ts +++ b/packages/simplex-chat-webrtc/src/call.ts @@ -219,6 +219,9 @@ var useWorker = false var isDesktop = false var localizedState = "" var localizedDescription = "" +// When one side of a call sends candidates tot fast (until local & remote descriptions are set), that candidates +// will be stored here and then set when the call will be ready to process them +let afterCallInitializedCandidates: RTCIceCandidateInit[] = [] const processCommand = (function () { type RTCRtpSenderWithEncryption = RTCRtpSender & { @@ -445,6 +448,8 @@ const processCommand = (function () { const pc = activeCall.connection const offer = await pc.createOffer() await pc.setLocalDescription(offer) + addIceCandidates(pc, afterCallInitializedCandidates) + afterCallInitializedCandidates = [] // for debugging, returning the command for callee to use // resp = { // type: "offer", @@ -481,6 +486,8 @@ const processCommand = (function () { const answer = await pc.createAnswer() await pc.setLocalDescription(answer) addIceCandidates(pc, remoteIceCandidates) + addIceCandidates(pc, afterCallInitializedCandidates) + afterCallInitializedCandidates = [] // same as command for caller to use resp = { type: "answer", @@ -503,16 +510,19 @@ const processCommand = (function () { // console.log("answer remoteIceCandidates", JSON.stringify(remoteIceCandidates)) await pc.setRemoteDescription(new RTCSessionDescription(answer)) addIceCandidates(pc, remoteIceCandidates) + addIceCandidates(pc, afterCallInitializedCandidates) + afterCallInitializedCandidates = [] resp = {type: "ok"} } break case "ice": + const remoteIceCandidates: RTCIceCandidateInit[] = parse(command.iceCandidates) if (pc) { - const remoteIceCandidates: RTCIceCandidateInit[] = parse(command.iceCandidates) addIceCandidates(pc, remoteIceCandidates) resp = {type: "ok"} } else { - resp = {type: "error", message: "ice: call not started"} + afterCallInitializedCandidates.push(...remoteIceCandidates) + resp = {type: "error", message: "ice: call not started yet, will add candidates later"} } break case "media": From 5ca27f63e68edb861822183d7e180e1f75b172ae Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:27:14 +0400 Subject: [PATCH 146/440] core: send errors processing (#4910) * core: send errors processing * test --- src/Simplex/Chat.hs | 20 ++++++++++++++++---- tests/ChatTests/Direct.hs | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d5f06a326f..7ca2f4b948 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2945,8 +2945,8 @@ processChatCommand' vr = \case msgs_ <- sendDirectContactMessages user ct $ L.map XMsgNew msgContainers let itemsData = prepareSndItemsData msgs_ cmrs ciFiles_ quotedItems_ when (length itemsData /= length cmrs) $ logError "sendContactContentMessages: cmrs and itemsData length mismatch" - (errs, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live - unless (null errs) $ toView $ CRChatErrors (Just user) errs + r@(_, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live + processSendErrs user r forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> forM_ cis $ \ci -> startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) deleteAt @@ -3010,8 +3010,8 @@ processChatCommand' vr = \case cis_ <- saveSndChatItems user (CDGroupSnd gInfo) itemsData timed_ live when (length itemsData /= length cmrs) $ logError "sendGroupContentMessages: cmrs and cis_ length mismatch" createMemberSndStatuses cis_ msgs_ gsr - let (errs, cis) = partitionEithers cis_ - unless (null errs) $ toView $ CRChatErrors (Just user) errs + let r@(_, cis) = partitionEithers cis_ + processSendErrs user r forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> forM_ cis $ \ci -> startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) deleteAt @@ -3103,6 +3103,18 @@ processChatCommand' vr = \case | (msg_, (ComposedMessage {msgContent}, itemForwarded), f, q) <- zipWith4 (,,,) msgs_ (L.toList cmrs') (L.toList ciFiles_) (L.toList quotedItems_) ] + processSendErrs :: User -> ([ChatError], [ChatItem c d]) -> CM () + processSendErrs user = \case + -- no errors + ([], _) -> pure () + -- at least one item is successfully created + (errs, _ci : _) -> toView $ CRChatErrors (Just user) errs + -- single error + ([err], []) -> throwError err + -- multiple errors + (errs@(err : _), []) -> do + toView $ CRChatErrors (Just user) errs + throwError err getCommandDirectChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (Contact, [CChatItem 'CTDirect]) getCommandDirectChatItems user ctId itemIds = do ct <- withFastStore $ \db -> getContact db vr user ctId diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 97a9d89200..c47cf975a1 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -2547,7 +2547,7 @@ setupDesynchronizedRatchet tmp alice = do (bob "/tail @alice 1" bob <# "alice> decryption error, possibly due to the device change (header, 3 messages)" - bob `send` "@alice 1" + bob ##> "@alice 1" bob <## "error: command is prohibited, sendMessagesB: send prohibited" (alice Date: Fri, 20 Sep 2024 09:45:30 +0100 Subject: [PATCH 147/440] core: 6.1.0.3 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index ab4c9790ef..4e4ba3a549 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.2 +version: 6.1.0.3 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 51602084f4..bf42c5117e 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.0.2 +version: 6.1.0.3 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 68e570656dfae448ba6c1ebcd6ce2ddd9575751f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 20 Sep 2024 12:34:38 +0100 Subject: [PATCH 148/440] ui: translations (#4915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ios: update core library * Translated using Weblate (Portuguese) Currently translated at 46.2% (944 of 2041 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% (2041 of 2041 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% (2041 of 2041 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.2% (1787 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 99.2% (1787 of 1800 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% (2041 of 2041 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2041 of 2041 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 99.9% (1799 of 1800 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% (2041 of 2041 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Japanese) Currently translated at 88.6% (1809 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Vietnamese) Currently translated at 38.5% (786 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 38.8% (793 of 2041 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 99.9% (2040 of 2041 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% (2051 of 2051 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2051 of 2051 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% (2051 of 2051 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% (1800 of 1800 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% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Japanese) Currently translated at 90.0% (1847 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 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 39.4% (810 of 2051 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% (2051 of 2051 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% (2051 of 2051 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% (2067 of 2067 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% (1800 of 1800 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 40.0% (827 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Italian) Currently translated at 100.0% (2067 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Arabic) Currently translated at 99.3% (2054 of 2067 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% (2067 of 2067 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * ios: update core library * Translated using Weblate (Portuguese) Currently translated at 46.2% (944 of 2041 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% (2041 of 2041 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% (2041 of 2041 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.2% (1787 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 99.2% (1787 of 1800 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% (2041 of 2041 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2041 of 2041 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 99.9% (1799 of 1800 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% (2041 of 2041 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Japanese) Currently translated at 88.6% (1809 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Vietnamese) Currently translated at 38.5% (786 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 38.8% (793 of 2041 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 99.9% (2040 of 2041 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% (2051 of 2051 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2051 of 2051 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% (2051 of 2051 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% (1800 of 1800 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% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Japanese) Currently translated at 90.0% (1847 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 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 39.4% (810 of 2051 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% (2051 of 2051 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% (2051 of 2051 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% (2067 of 2067 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% (1800 of 1800 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 40.0% (827 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Italian) Currently translated at 100.0% (2067 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Arabic) Currently translated at 99.3% (2054 of 2067 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% (2067 of 2067 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% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * process localizations * fix interpolation --------- Co-authored-by: Antonio Oliveira Co-authored-by: summoner001 Co-authored-by: Ghost of Sparta Co-authored-by: Random Co-authored-by: M1K4 Co-authored-by: Bezruchenko Simon Co-authored-by: Miyu Sakatsuki Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: mlanp Co-authored-by: 大王叫我来巡山 Co-authored-by: jonnysemon --- apps/ios/Shared/Model/SimpleXAPI.swift | 6 +- .../bg.xcloc/Localized Contents/bg.xliff | 113 ++++- .../cs.xcloc/Localized Contents/cs.xliff | 113 ++++- .../de.xcloc/Localized Contents/de.xliff | 133 +++++- .../en.xcloc/Localized Contents/en.xliff | 139 +++++- .../es.xcloc/Localized Contents/es.xliff | 113 ++++- .../fi.xcloc/Localized Contents/fi.xliff | 113 ++++- .../fr.xcloc/Localized Contents/fr.xliff | 113 ++++- .../hu.xcloc/Localized Contents/hu.xliff | 437 +++++++++++------- .../it.xcloc/Localized Contents/it.xliff | 133 +++++- .../ja.xcloc/Localized Contents/ja.xliff | 113 ++++- .../nl.xcloc/Localized Contents/nl.xliff | 180 ++++++-- .../pl.xcloc/Localized Contents/pl.xliff | 113 ++++- .../ru.xcloc/Localized Contents/ru.xliff | 113 ++++- .../th.xcloc/Localized Contents/th.xliff | 113 ++++- .../tr.xcloc/Localized Contents/tr.xliff | 113 ++++- .../uk.xcloc/Localized Contents/uk.xliff | 113 ++++- .../Localized Contents/zh-Hans.xliff | 113 ++++- .../SimpleX SE/hu.lproj/Localizable.strings | 4 +- .../SimpleX SE/nl.lproj/Localizable.strings | 2 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 8 + apps/ios/bg.lproj/Localizable.strings | 11 +- apps/ios/cs.lproj/Localizable.strings | 8 +- apps/ios/de.lproj/Localizable.strings | 75 ++- apps/ios/es.lproj/Localizable.strings | 15 +- apps/ios/fi.lproj/Localizable.strings | 8 +- apps/ios/fr.lproj/Localizable.strings | 15 +- apps/ios/hu.lproj/Localizable.strings | 371 ++++++++------- apps/ios/it.lproj/Localizable.strings | 75 ++- apps/ios/ja.lproj/Localizable.strings | 8 +- apps/ios/nl.lproj/Localizable.strings | 118 +++-- apps/ios/pl.lproj/Localizable.strings | 15 +- apps/ios/ru.lproj/Localizable.strings | 15 +- apps/ios/th.lproj/Localizable.strings | 8 +- apps/ios/tr.lproj/Localizable.strings | 15 +- apps/ios/uk.lproj/Localizable.strings | 15 +- apps/ios/zh-Hans.lproj/Localizable.strings | 17 +- .../commonMain/resources/MR/ar/strings.xml | 33 +- .../commonMain/resources/MR/de/strings.xml | 21 +- .../commonMain/resources/MR/hu/strings.xml | 408 ++++++++-------- .../commonMain/resources/MR/it/strings.xml | 36 ++ .../commonMain/resources/MR/ja/strings.xml | 53 ++- .../commonMain/resources/MR/nl/strings.xml | 77 +-- .../commonMain/resources/MR/pt/strings.xml | 19 + .../commonMain/resources/MR/uk/strings.xml | 21 +- .../commonMain/resources/MR/vi/strings.xml | 48 ++ .../resources/MR/zh-rCN/strings.xml | 27 +- 47 files changed, 3126 insertions(+), 804 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index fab3e10990..ea48d5d7b3 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1111,8 +1111,8 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool showAlert( title: NSLocalizedString("Unknown servers!", comment: "alert title"), message: ( - NSLocalizedString("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs).", comment: "alert message") + - (otherErrsStr != "" ? "\n\n" + NSLocalizedString("Other file errors:\n\(otherErrsStr)", comment: "alert message") : "") + String.localizedStringWithFormat(NSLocalizedString("Without Tor or VPN, your IP address will be visible to these XFTP relays: %@.", comment: "alert message"), srvs) + + (otherErrsStr != "" ? "\n\n" + String.localizedStringWithFormat(NSLocalizedString("Other file errors:\n%@", comment: "alert message"), otherErrsStr) : "") ), buttonTitle: NSLocalizedString("Download", comment: "alert button"), buttonAction: { @@ -1156,7 +1156,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool await MainActor.run { showAlert( NSLocalizedString("Error receiving file", comment: "alert title"), - message: NSLocalizedString("File errors:\n\(otherErrsStr)", comment: "alert message") + message: String.localizedStringWithFormat(NSLocalizedString("File errors:\n%@", comment: "alert message"), otherErrsStr) ) } } diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index f55c87b1b8..cbd0c2742b 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -161,11 +161,31 @@ %d дни time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d часа time interval + + %d messages not forwarded + alert title + %d min %d мин. @@ -1194,7 +1214,7 @@ Cannot receive file Файлът не може да бъде получен - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2350,6 +2370,10 @@ This is your own one-time link! Не изпращай история на нови членове. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Не създавай адрес @@ -2373,7 +2397,8 @@ This is your own one-time link! Download Изтегли - chat item action + alert button + chat item action Download errors @@ -2389,6 +2414,10 @@ This is your own one-time link! Свали файл server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2809,7 +2838,7 @@ This is your own one-time link! Error receiving file Грешка при получаване на файл - No comment provided by engineer. + alert title Error reconnecting server @@ -3035,6 +3064,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3165,11 +3199,23 @@ This is your own one-time link! Препрати chat item action + + Forward %d message(s)? + alert title + Forward and save messages Препращане и запазване на съобщения No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Препратено @@ -3180,6 +3226,10 @@ This is your own one-time link! Препратено от No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3465,6 +3515,10 @@ Error: %2$@ ICE сървъри (по един на ред) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Ако не можете да се срещнете лично, покажете QR код във видеоразговора или споделете линка. @@ -4146,6 +4200,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Съобщенията, файловете и разговорите са защитени чрез **криптиране от край до край** с перфектна секретност при препращане, правдоподобно опровержение и възстановяване при взлом. @@ -4435,6 +4493,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Известия @@ -4467,7 +4529,7 @@ This is your link for group %@! Ok Ок - No comment provided by engineer. + alert button Old database @@ -4655,6 +4717,11 @@ Requires compatible VPN. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING бройка @@ -4690,6 +4757,10 @@ Requires compatible VPN. Кодът за достъп е зададен! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Парола за показване @@ -4834,6 +4905,10 @@ Error: %@ Полски интерфейс No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен @@ -5010,6 +5085,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push известия @@ -5398,6 +5477,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5506,6 +5589,10 @@ Enable in *Network & servers* settings. Запазено съобщение message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5698,7 +5785,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Подателят отмени прехвърлянето на файла. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6738,7 +6825,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6848,6 +6935,10 @@ To connect, please ask your contact to create another connection link and check Използвай .onion хостове No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Използвай сървърите на SimpleX Chat? @@ -6919,6 +7010,10 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Използват се сървърите на SimpleX Chat. @@ -7148,7 +7243,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7547,6 +7642,10 @@ Repeat connection request? Вашите контакти ще останат свързани. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Вашата текуща чат база данни ще бъде ИЗТРИТА и ЗАМЕНЕНА с импортираната. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 3b29a1e51f..f53628f35f 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -158,11 +158,31 @@ %d dní time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d hodin time interval + + %d messages not forwarded + alert title + %d min %d minuty @@ -1153,7 +1173,7 @@ Cannot receive file Nelze přijmout soubor - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2271,6 +2291,10 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Nevytvářet adresu @@ -2293,7 +2317,8 @@ This is your own one-time link! Download - chat item action + alert button + chat item action Download errors @@ -2308,6 +2333,10 @@ This is your own one-time link! Stáhnout soubor server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2713,7 +2742,7 @@ This is your own one-time link! Error receiving file Chyba při příjmu souboru - No comment provided by engineer. + alert title Error reconnecting server @@ -2932,6 +2961,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3058,10 +3092,22 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded No comment provided by engineer. @@ -3070,6 +3116,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3348,6 +3398,10 @@ Error: %2$@ Servery ICE (jeden na řádek) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Pokud se nemůžete setkat osobně, zobrazte QR kód ve videohovoru nebo sdílejte odkaz. @@ -4002,6 +4056,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4276,6 +4334,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Oznámení @@ -4307,7 +4369,7 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4487,6 +4549,11 @@ Vyžaduje povolení sítě VPN. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Počet PING @@ -4522,6 +4589,10 @@ Vyžaduje povolení sítě VPN. Heslo nastaveno! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Heslo k zobrazení @@ -4658,6 +4729,10 @@ Error: %@ Polské rozhraní No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Je možné, že otisk certifikátu v adrese serveru je nesprávný @@ -4831,6 +4906,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Nabízená oznámení @@ -5208,6 +5287,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5312,6 +5395,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5500,7 +5587,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Odesílatel zrušil přenos souboru. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6511,7 +6598,7 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6616,6 +6703,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Použít hostitele .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Používat servery SimpleX Chat? @@ -6684,6 +6775,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Používat servery SimpleX Chat. @@ -6896,7 +6991,7 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7278,6 +7373,10 @@ Repeat connection request? Vaše kontakty zůstanou připojeny. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Vaše aktuální chat databáze bude ODSTRANĚNA a NAHRAZENA importovanou. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 884c322dae..a126212534 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -161,11 +162,31 @@ %d Tage time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d Stunden time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1026,6 +1047,7 @@ Auto-accept settings + Einstellungen automatisch akzeptieren alert title @@ -1221,7 +1243,7 @@ Cannot receive file Datei kann nicht empfangen werden - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1351,6 +1373,7 @@ Chat preferences were changed. + Die Chat-Präferenzen wurden geändert. alert message @@ -1754,6 +1777,7 @@ Das ist Ihr eigener Einmal-Link! Corner + Ecke No comment provided by engineer. @@ -2425,6 +2449,10 @@ Das ist Ihr eigener Einmal-Link! Den Nachrichtenverlauf nicht an neue Mitglieder senden. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Keine Adresse erstellt @@ -2448,7 +2476,8 @@ Das ist Ihr eigener Einmal-Link! Download Herunterladen - chat item action + alert button + chat item action Download errors @@ -2465,6 +2494,10 @@ Das ist Ihr eigener Einmal-Link! Datei herunterladen server test step + + Download files + alert action + Downloaded Heruntergeladen @@ -2742,6 +2775,7 @@ Das ist Ihr eigener Einmal-Link! Error changing connection profile + Fehler beim Wechseln des Verbindungs-Profils No comment provided by engineer. @@ -2756,6 +2790,7 @@ Das ist Ihr eigener Einmal-Link! Error changing to incognito! + Fehler beim Wechseln zum Inkognito-Profil! No comment provided by engineer. @@ -2880,6 +2915,7 @@ Das ist Ihr eigener Einmal-Link! Error migrating settings + Fehler beim Migrieren der Einstellungen No comment provided by engineer. @@ -2890,7 +2926,7 @@ Das ist Ihr eigener Einmal-Link! Error receiving file Fehler beim Empfangen der Datei - No comment provided by engineer. + alert title Error reconnecting server @@ -2984,6 +3020,7 @@ Das ist Ihr eigener Einmal-Link! Error switching profile + Fehler beim Wechseln des Profils No comment provided by engineer. @@ -3122,6 +3159,11 @@ Das ist Ihr eigener Einmal-Link! Datei-Fehler No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Datei nicht gefunden - höchstwahrscheinlich wurde die Datei gelöscht oder der Transfer abgebrochen. @@ -3257,11 +3299,23 @@ Das ist Ihr eigener Einmal-Link! Weiterleiten chat item action + + Forward %d message(s)? + alert title + Forward and save messages Nachrichten weiterleiten und speichern No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Weitergeleitet @@ -3272,6 +3326,10 @@ Das ist Ihr eigener Einmal-Link! Weitergeleitet aus No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es später erneut. @@ -3566,6 +3624,10 @@ Fehler: %2$@ ICE-Server (einer pro Zeile) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Falls Sie sich nicht persönlich treffen können, zeigen Sie den QR-Code in einem Videoanruf oder teilen Sie den Link. @@ -4213,6 +4275,7 @@ Das ist Ihr Link für die Gruppe %@! Message shape + Nachrichten-Form No comment provided by engineer. @@ -4265,6 +4328,10 @@ Das ist Ihr Link für die Gruppe %@! Gesendete Nachrichten No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. @@ -4560,6 +4627,10 @@ Das ist Ihr Link für die Gruppe %@! Nichts ausgewählt No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Benachrichtigungen @@ -4592,7 +4663,7 @@ Das ist Ihr Link für die Gruppe %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4854,11 @@ Dies erfordert die Aktivierung eines VPNs. Andere %@ Server No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING-Zähler @@ -4818,6 +4894,10 @@ Dies erfordert die Aktivierung eines VPNs. Zugangscode eingestellt! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Passwort anzeigen @@ -4967,6 +5047,10 @@ Fehler: %@ Polnische Bedienoberfläche No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig @@ -5154,6 +5238,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Proxy-Server No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push-Benachrichtigungen @@ -5377,6 +5465,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Remove archive? + Archiv entfernen? No comment provided by engineer. @@ -5559,6 +5648,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SMP-Server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Dateien sicher empfangen @@ -5647,6 +5740,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save your profile? + Ihr Profil speichern? alert title @@ -5669,6 +5763,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Gespeicherte Nachricht message info title + + Saving %lld messages + No comment provided by engineer. + Scale Skalieren @@ -5751,6 +5849,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Select chat profile + Chat-Profil auswählen No comment provided by engineer. @@ -5871,7 +5970,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Sender cancelled file transfer. Der Absender hat die Dateiübertragung abgebrochen. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6090,6 +6189,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Settings were changed. + Die Einstellungen wurden geändert. alert message @@ -6129,6 +6229,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Share profile + Profil teilen No comment provided by engineer. @@ -6298,6 +6399,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Some app settings were not migrated. + Einige App-Einstellungen wurden nicht migriert. No comment provided by engineer. @@ -6477,6 +6579,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Tail + Sprechblase No comment provided by engineer. @@ -6673,6 +6776,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The uploaded database archive will be permanently removed from the servers. + Das hochgeladene Datenbank-Archiv wird dauerhaft von den Servern entfernt. No comment provided by engineer. @@ -6955,7 +7059,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Unknown servers! Unbekannte Server! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7173,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Verwende .onion-Hosts No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Verwenden Sie SimpleX-Chat-Server? @@ -7144,6 +7252,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Benutzer-Auswahl No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Verwendung von SimpleX-Chat-Servern. @@ -7377,7 +7489,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7760,6 +7872,7 @@ Verbindungsanfrage wiederholen? Your chat preferences + Ihre Chat-Präferenzen alert title @@ -7769,6 +7882,7 @@ Verbindungsanfrage wiederholen? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Ihre Verbindung wurde auf %@ verschoben. Während Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf. No comment provided by engineer. @@ -7786,6 +7900,10 @@ Verbindungsanfrage wiederholen? Ihre Kontakte bleiben weiterhin verbunden. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Ihre aktuelle Chat-Datenbank wird GELÖSCHT und durch die Importierte ERSETZT. @@ -7823,6 +7941,7 @@ Verbindungsanfrage wiederholen? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet. alert message diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 6eb935222f..e98bd7b784 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -162,11 +162,36 @@ %d days time interval + + %d file(s) are still being downloaded. + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d hours time interval + + %d messages not forwarded + %d messages not forwarded + alert title + %d min %d min @@ -1223,7 +1248,7 @@ Cannot receive file Cannot receive file - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2429,6 +2454,11 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Don't create address @@ -2452,7 +2482,8 @@ This is your own one-time link! Download Download - chat item action + alert button + chat item action Download errors @@ -2469,6 +2500,11 @@ This is your own one-time link! Download file server test step + + Download files + Download files + alert action + Downloaded Downloaded @@ -2897,7 +2933,7 @@ This is your own one-time link! Error receiving file Error receiving file - No comment provided by engineer. + alert title Error reconnecting server @@ -3130,6 +3166,13 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. File not found - most likely file was deleted or cancelled. @@ -3265,11 +3308,26 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + Forward %d message(s)? + alert title + Forward and save messages Forward and save messages No comment provided by engineer. + + Forward messages + Forward messages + alert action + + + Forward messages without files? + Forward messages without files? + alert message + Forwarded Forwarded @@ -3280,6 +3338,11 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Forwarding server %@ failed to connect to destination server %@. Please try later. @@ -3574,6 +3637,11 @@ Error: %2$@ ICE servers (one per line) No comment provided by engineer. + + IP address + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. If you can't meet in person, show QR code in a video call, or share the link. @@ -4274,6 +4342,11 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. @@ -4569,6 +4642,11 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + Nothing to forward! + alert title + Notifications Notifications @@ -4601,7 +4679,7 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4792,6 +4870,13 @@ Requires compatible VPN. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + Other file errors: +%@ + alert message + PING count PING count @@ -4827,6 +4912,11 @@ Requires compatible VPN. Passcode set! No comment provided by engineer. + + Password + Password + No comment provided by engineer. + Password to show Password to show @@ -4976,6 +5066,11 @@ Error: %@ Polish interface No comment provided by engineer. + + Port + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Possibly, certificate fingerprint in server address is incorrect @@ -5163,6 +5258,11 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + Proxy requires password + No comment provided by engineer. + Push notifications Push notifications @@ -5569,6 +5669,11 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + SOCKS proxy + No comment provided by engineer. + Safely receive files Safely receive files @@ -5680,6 +5785,11 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + Saving %lld messages + No comment provided by engineer. + Scale Scale @@ -5883,7 +5993,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Sender cancelled file transfer. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6972,7 +7082,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7086,6 +7196,11 @@ To connect, please ask your contact to create another connection link and check Use .onion hosts No comment provided by engineer. + + Use SOCKS proxy + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Use SimpleX Chat servers? @@ -7161,6 +7276,11 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + Username + No comment provided by engineer. + Using SimpleX Chat servers. Using SimpleX Chat servers. @@ -7394,7 +7514,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7805,6 +7925,11 @@ Repeat connection request? Your contacts will remain connected. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Your current chat database will be DELETED and REPLACED with the imported one. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index a10a1594de..22251b7443 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -161,11 +161,31 @@ %d días time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d horas time interval + + %d messages not forwarded + alert title + %d min %d minutos @@ -1221,7 +1241,7 @@ Cannot receive file No se puede recibir el archivo - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ This is your own one-time link! No se envía el historial a los miembros nuevos. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address No crear dirección SimpleX @@ -2448,7 +2472,8 @@ This is your own one-time link! Download Descargar - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ This is your own one-time link! Descargar archivo server test step + + Download files + alert action + Downloaded Descargado @@ -2890,7 +2919,7 @@ This is your own one-time link! Error receiving file Error al recibir archivo - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ This is your own one-time link! Error de archivo No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Archivo no encontrado, probablemente haya sido borrado o cancelado. @@ -3257,11 +3291,23 @@ This is your own one-time link! Reenviar chat item action + + Forward %d message(s)? + alert title + Forward and save messages Reenviar y guardar mensajes No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Reenviado @@ -3272,6 +3318,10 @@ This is your own one-time link! Reenviado por No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. El servidor de reenvío %@ no ha podido conectarse al servidor de destino %@. Por favor, intentalo más tarde. @@ -3566,6 +3616,10 @@ Error: %2$@ Servidores ICE (uno por línea) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Si no puedes reunirte en persona, muestra el código QR por videollamada o comparte el enlace. @@ -4265,6 +4319,10 @@ This is your link for group %@! Mensajes enviados No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Los mensajes, archivos y llamadas están protegidos mediante **cifrado de extremo a extremo** con secreto perfecto hacía adelante, repudio y recuperación tras ataque. @@ -4560,6 +4618,10 @@ This is your link for group %@! Nada seleccionado No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Notificaciones @@ -4592,7 +4654,7 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Requiere activación de la VPN. Otros servidores %@ No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Contador PING @@ -4818,6 +4885,10 @@ Requiere activación de la VPN. ¡Código de acceso guardado! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Contraseña para hacerlo visible @@ -4967,6 +5038,10 @@ Error: %@ Interfaz en polaco No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta @@ -5154,6 +5229,10 @@ Actívalo en ajustes de *Servidores y Redes*. Servidores con proxy No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Notificaciones automáticas @@ -5559,6 +5638,10 @@ Actívalo en ajustes de *Servidores y Redes*. Servidor SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Recibe archivos de forma segura @@ -5669,6 +5752,10 @@ Actívalo en ajustes de *Servidores y Redes*. Mensaje guardado message info title + + Saving %lld messages + No comment provided by engineer. + Scale Escala @@ -5871,7 +5958,7 @@ Actívalo en ajustes de *Servidores y Redes*. Sender cancelled file transfer. El remitente ha cancelado la transferencia de archivos. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ Se te pedirá que completes la autenticación antes de activar esta función.
    Unknown servers! ¡Servidores desconocidos! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar hosts .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? ¿Usar servidores SimpleX Chat? @@ -7144,6 +7235,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Selección de usuarios No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Usar servidores SimpleX Chat. @@ -7377,7 +7472,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Sin Tor o VPN, tu dirección IP será visible para estos servidores XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Repeat connection request? Tus contactos permanecerán conectados. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. La base de datos actual será ELIMINADA y SUSTITUIDA por la importada. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index cf2ea3c36d..3035f3e8f7 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -156,11 +156,31 @@ %d päivää time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d tuntia time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1146,7 +1166,7 @@ Cannot receive file Tiedostoa ei voi vastaanottaa - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2264,6 +2284,10 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Älä luo osoitetta @@ -2286,7 +2310,8 @@ This is your own one-time link! Download - chat item action + alert button + chat item action Download errors @@ -2301,6 +2326,10 @@ This is your own one-time link! Lataa tiedosto server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2704,7 +2733,7 @@ This is your own one-time link! Error receiving file Virhe tiedoston vastaanottamisessa - No comment provided by engineer. + alert title Error reconnecting server @@ -2922,6 +2951,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3048,10 +3082,22 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded No comment provided by engineer. @@ -3060,6 +3106,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3338,6 +3388,10 @@ Error: %2$@ ICE-palvelimet (yksi per rivi) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Jos et voi tavata henkilökohtaisesti, näytä QR-koodi videopuhelussa tai jaa linkki. @@ -3992,6 +4046,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4265,6 +4323,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Ilmoitukset @@ -4296,7 +4358,7 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4475,6 +4537,11 @@ Edellyttää VPN:n sallimista. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING-määrä @@ -4510,6 +4577,10 @@ Edellyttää VPN:n sallimista. Pääsykoodi asetettu! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Salasana näytettäväksi @@ -4646,6 +4717,10 @@ Error: %@ Puolalainen käyttöliittymä No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Palvelimen osoitteen varmenteen sormenjälki on mahdollisesti virheellinen @@ -4819,6 +4894,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push-ilmoitukset @@ -5196,6 +5275,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5300,6 +5383,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5487,7 +5574,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Lähettäjä peruutti tiedoston siirron. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6496,7 +6583,7 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6601,6 +6688,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Käytä .onion-isäntiä No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Käytä SimpleX Chat palvelimia? @@ -6669,6 +6760,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Käyttää SimpleX Chat -palvelimia. @@ -6881,7 +6976,7 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7263,6 +7358,10 @@ Repeat connection request? Kontaktisi pysyvät yhdistettyinä. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Nykyinen keskustelut-tietokantasi poistetaan ja korvataan tuodulla tietokannalla. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 547d0f3674..2d86fbe760 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -161,11 +161,31 @@ %d jours time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d heures time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1221,7 +1241,7 @@ Cannot receive file Impossible de recevoir le fichier - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ Il s'agit de votre propre lien unique ! Ne pas envoyer d'historique aux nouveaux membres. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Ne pas créer d'adresse @@ -2448,7 +2472,8 @@ Il s'agit de votre propre lien unique ! Download Télécharger - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ Il s'agit de votre propre lien unique ! Télécharger le fichier server test step + + Download files + alert action + Downloaded Téléchargé @@ -2890,7 +2919,7 @@ Il s'agit de votre propre lien unique ! Error receiving file Erreur lors de la réception du fichier - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ Il s'agit de votre propre lien unique ! Erreur de fichier No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Fichier introuvable - le fichier a probablement été supprimé ou annulé. @@ -3257,11 +3291,23 @@ Il s'agit de votre propre lien unique ! Transférer chat item action + + Forward %d message(s)? + alert title + Forward and save messages Transférer et sauvegarder des messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Transféré @@ -3272,6 +3318,10 @@ Il s'agit de votre propre lien unique ! Transféré depuis No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Le serveur de redirection %@ n'a pas réussi à se connecter au serveur de destination %@. Veuillez réessayer plus tard. @@ -3566,6 +3616,10 @@ Erreur : %2$@ Serveurs ICE (un par ligne) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Si vous ne pouvez pas vous rencontrer en personne, montrez le code QR lors d'un appel vidéo ou partagez le lien. @@ -4265,6 +4319,10 @@ Voici votre lien pour le groupe %@ ! Messages envoyés No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction. @@ -4560,6 +4618,10 @@ Voici votre lien pour le groupe %@ ! Aucune sélection No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Notifications @@ -4592,7 +4654,7 @@ Voici votre lien pour le groupe %@ ! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Nécessite l'activation d'un VPN. Autres serveurs %@ No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Nombre de PING @@ -4818,6 +4885,10 @@ Nécessite l'activation d'un VPN. Code d'accès défini ! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Mot de passe à entrer @@ -4967,6 +5038,10 @@ Erreur : %@ Interface en polonais No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte @@ -5154,6 +5229,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Serveurs routés via des proxy No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Notifications push @@ -5559,6 +5638,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Serveur SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Réception de fichiers en toute sécurité @@ -5669,6 +5752,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Message enregistré message info title + + Saving %lld messages + No comment provided by engineer. + Scale Échelle @@ -5871,7 +5958,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Sender cancelled file transfer. L'expéditeur a annulé le transfert de fichiers. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Unknown servers! Serveurs inconnus ! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser les hôtes .onions No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Utiliser les serveurs SimpleX Chat ? @@ -7144,6 +7235,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Sélection de l'utilisateur No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Vous utilisez les serveurs SimpleX. @@ -7377,7 +7472,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Sans Tor ni VPN, votre adresse IP sera visible par ces relais XFTP : %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Répéter la demande de connexion ? Vos contacts resteront connectés. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Votre base de données de chat actuelle va être SUPPRIMEE et REMPLACEE par celle importée. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 063dd3d14a..2f79096b14 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -161,11 +162,31 @@ %d nap time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d óra time interval + + %d messages not forwarded + alert title + %d min %d perc @@ -328,12 +349,12 @@ **Add contact**: to create a new invitation link, or connect via a link you received. - **Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz. + **Ismerős hozzáadása**: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz. No comment provided by engineer. **Add new contact**: to create your one-time QR Code or link for your contact. - **Új ismerős hozzáadása**: egyszer használatos QR-kód vagy hivatkozás létrehozása az ismerőse számára. + **Új ismerős hozzáadása**: egyszer használható QR-kód vagy hivatkozás létrehozása az ismerőse számára. No comment provided by engineer. @@ -343,7 +364,7 @@ **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. - **Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van. + **Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van. No comment provided by engineer. @@ -353,12 +374,12 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. - **Megjegyzés**: ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja az ismerőseitől érkező üzenetek visszafejtését. + **Figyelem:** ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja az ismerőseitől érkező üzenetek visszafejtését. No comment provided by engineer. **Please note**: you will NOT be able to recover or change passphrase if you lose it. - **Figyelem**: NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt. + **Figyelem:** NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt. No comment provided by engineer. @@ -373,7 +394,7 @@ **Warning**: the archive will be removed. - **Figyelem**: az archívum törlésre kerül. + **Figyelem**: az archívum eltávolításra kerül. No comment provided by engineer. @@ -522,8 +543,8 @@ A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. - A rendszer külön TCP-kapcsolatot fog használni **minden ismerőshöz és csoporttaghoz**. -**Figyelem**: sok kapcsolódás esetén, az akkumulátor- és adatforgalom fogyasztás jelentősen megnőhet, és egyes kapcsolatok meghiúsulhatnak. + **Minden egyes kapcsolathoz és csoporttaghoz** külön TCP-kapcsolat lesz használva. +**Figyelem:** ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. No comment provided by engineer. @@ -543,17 +564,17 @@ About SimpleX - A SimpleX-ről + A SimpleXről No comment provided by engineer. About SimpleX Chat - A SimpleX Chat-ről + A SimpleX Chatről No comment provided by engineer. About SimpleX address - A SimpleX címről + A SimpleX-címről No comment provided by engineer. @@ -570,12 +591,12 @@ Accept connection request? - Ismerőskérelem elfogadása? + Kapcsolatkérés elfogadása? No comment provided by engineer. Accept contact request from %@? - Elfogadja %@ kapcsolat kérését? + Elfogadja %@ kapcsolatkérését? notification body @@ -636,7 +657,7 @@ Add welcome message - Üdvözlő üzenet hozzáadása + Üdvözlőüzenet hozzáadása No comment provided by engineer. @@ -751,7 +772,7 @@ Allow calls only if your contact allows them. - A hívások kezdeményezése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + A hívások kezdeményezése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. No comment provided by engineer. @@ -761,7 +782,7 @@ Allow disappearing messages only if your contact allows it to you. - Az eltűnő üzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi az ön számára. + Az eltűnő üzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi az ön számára. No comment provided by engineer. @@ -771,12 +792,12 @@ Allow irreversible message deletion only if your contact allows it to you. (24 hours) - Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) + Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) No comment provided by engineer. Allow message reactions only if your contact allows them. - Az üzenetreakciók küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + Az üzenetreakciók küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. No comment provided by engineer. @@ -806,12 +827,12 @@ Allow to send SimpleX links. - A SimpleX hivatkozások küldése engedélyezve van. + A SimpleX-hivatkozások küldése engedélyezve van. No comment provided by engineer. Allow to send files and media. - Fájlok és médiatartalom küldésének engedélyezése. + Fájlok és médiatartalmak küldésének engedélyezése. No comment provided by engineer. @@ -821,7 +842,7 @@ Allow voice messages only if your contact allows them. - A hangüzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + A hangüzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. No comment provided by engineer. @@ -1016,7 +1037,7 @@ Auto-accept contact requests - Kapcsolódási kérelmek automatikus elfogadása + Kapcsolatkérések automatikus elfogadása No comment provided by engineer. @@ -1026,6 +1047,7 @@ Auto-accept settings + Beállítások automatikus elfogadása alert title @@ -1221,7 +1243,7 @@ Cannot receive file Nem lehet fogadni a fájlt - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1351,6 +1373,7 @@ Chat preferences were changed. + A csevegési beállítások megváltoztak. alert message @@ -1547,14 +1570,14 @@ Connect to yourself? This is your own SimpleX address! Kapcsolódás saját magához? -Ez az ön SimpleX címe! +Ez az ön SimpleX-címe! No comment provided by engineer. Connect to yourself? This is your own one-time link! Kapcsolódás saját magához? -Ez az ön egyszer használatos hivatkozása! +Ez az ön egyszer használható hivatkozása! No comment provided by engineer. @@ -1569,7 +1592,7 @@ Ez az ön egyszer használatos hivatkozása! Connect via one-time link - Kapcsolódás egyszer használatos hivatkozáson keresztül + Kapcsolódás egyszer használható hivatkozáson keresztül No comment provided by engineer. @@ -1609,7 +1632,7 @@ Ez az ön egyszer használatos hivatkozása! Connecting to server… (error: %@) - Kapcsolódás a kiszolgálóhoz... (hiba: %@) + Kapcsolódás a kiszolgálóhoz… (hiba: %@) No comment provided by engineer. @@ -1649,7 +1672,7 @@ Ez az ön egyszer használatos hivatkozása! Connection request sent! - Kapcsolódási kérés elküldve! + Kapcsolatkérés elküldve! No comment provided by engineer. @@ -1754,6 +1777,7 @@ Ez az ön egyszer használatos hivatkozása! Corner + Sarok No comment provided by engineer. @@ -1768,7 +1792,7 @@ Ez az ön egyszer használatos hivatkozása! Create SimpleX address - SimpleX cím létrehozása + SimpleX-cím létrehozása No comment provided by engineer. @@ -2237,7 +2261,7 @@ Ez az ön egyszer használatos hivatkozása! Delivery receipts! - Üzenet kézbesítési jelentések! + Kézbesítési jelentések! No comment provided by engineer. @@ -2307,12 +2331,12 @@ Ez az ön egyszer használatos hivatkozása! Device authentication is disabled. Turning off SimpleX Lock. - A készüléken nincs beállítva a képernyőzár. A SimpleX zár ki van kapcsolva. + A készüléken nincs beállítva a képernyőzár. A SimpleX-zár ki van kapcsolva. No comment provided by engineer. Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication. - A készüléken nincs beállítva a képernyőzár. A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén. + A készüléken nincs beállítva a képernyőzár. A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén. No comment provided by engineer. @@ -2337,7 +2361,7 @@ Ez az ön egyszer használatos hivatkozása! Disable SimpleX Lock - SimpleX zár kikapcsolása + SimpleX-zár kikapcsolása authentication reason @@ -2425,6 +2449,10 @@ Ez az ön egyszer használatos hivatkozása! Az előzmények ne kerüljenek elküldésre az új tagok számára. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Ne hozzon létre címet @@ -2448,7 +2476,8 @@ Ez az ön egyszer használatos hivatkozása! Download Letöltés - chat item action + alert button + chat item action Download errors @@ -2465,6 +2494,10 @@ Ez az ön egyszer használatos hivatkozása! Fájl letöltése server test step + + Download files + alert action + Downloaded Letöltve @@ -2517,7 +2550,7 @@ Ez az ön egyszer használatos hivatkozása! Enable SimpleX Lock - SimpleX zár bekapcsolása + SimpleX-zár bekapcsolása authentication reason @@ -2602,7 +2635,7 @@ Ez az ön egyszer használatos hivatkozása! Encrypt stored files & media - Tárolt fájlok és médiatartalmak titkosítása + A tárolt fájlok- és a médiatartalmak titkosítása No comment provided by engineer. @@ -2662,7 +2695,7 @@ Ez az ön egyszer használatos hivatkozása! Enter correct passphrase. - Helyes jelmondat bevitele. + Adja meg a helyes jelmondatot. No comment provided by engineer. @@ -2697,12 +2730,12 @@ Ez az ön egyszer használatos hivatkozása! Enter welcome message… - Üdvözlő üzenet megadása… + Üdvözlőüzenet megadása… placeholder Enter welcome message… (optional) - Üdvözlő üzenet megadása… (opcionális) + Üdvözlőüzenet megadása… (opcionális) placeholder @@ -2722,7 +2755,7 @@ Ez az ön egyszer használatos hivatkozása! Error accepting contact request - Hiba történt a kapcsolatfelvételi kérelem elfogadásakor + Hiba történt a kapcsolatkérés elfogadásakor No comment provided by engineer. @@ -2742,6 +2775,7 @@ Ez az ön egyszer használatos hivatkozása! Error changing connection profile + Hiba a kapcsolati profilra való váltás közben No comment provided by engineer. @@ -2756,6 +2790,7 @@ Ez az ön egyszer használatos hivatkozása! Error changing to incognito! + Hiba az inkognitó-profilra való váltás közben! No comment provided by engineer. @@ -2880,6 +2915,7 @@ Ez az ön egyszer használatos hivatkozása! Error migrating settings + Hiba a beallítások átköltöztetése közben No comment provided by engineer. @@ -2890,7 +2926,7 @@ Ez az ön egyszer használatos hivatkozása! Error receiving file Hiba a fájl fogadásakor - No comment provided by engineer. + alert title Error reconnecting server @@ -2969,7 +3005,7 @@ Ez az ön egyszer használatos hivatkozása! Error setting delivery receipts! - Hiba történt a kézbesítési igazolások beállításakor! + Hiba történt a kézbesítési jelentések beállításakor! No comment provided by engineer. @@ -2984,6 +3020,7 @@ Ez az ön egyszer használatos hivatkozása! Error switching profile + Hiba a profilváltás közben No comment provided by engineer. @@ -3109,7 +3146,7 @@ Ez az ön egyszer használatos hivatkozása! Faster joining and more reliable messages. - Gyorsabb csatlakozás és megbízhatóbb üzenet kézbesítés. + Gyorsabb csatlakozás és megbízhatóbb üzenetkézbesítés. No comment provided by engineer. @@ -3122,6 +3159,11 @@ Ez az ön egyszer használatos hivatkozása! Fájlhiba No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. A fájl nem található - valószínűleg a fájlt törölték vagy visszavonták. @@ -3169,27 +3211,27 @@ Ez az ön egyszer használatos hivatkozása! Files & media - Fájlok és média + Fájlok és médiatartalmak No comment provided by engineer. Files and media - Fájlok és médiatartalom + Fájlok és médiatartalmak chat feature Files and media are prohibited in this group. - A fájlok- és a médiatartalom küldése le van tiltva ebben a csoportban. + A fájlok- és a médiatartalmak le vannak tiltva ebben a csoportban. No comment provided by engineer. Files and media not allowed - Fájlok és média tartalom küldése le van tiltva + A fájlok- és médiatartalmak nincsenek engedélyezve No comment provided by engineer. Files and media prohibited! - A fájlok- és a médiatartalom küldése le van tiltva! + A fájlok- és a médiatartalmak küldése le van tiltva! No comment provided by engineer. @@ -3257,11 +3299,23 @@ Ez az ön egyszer használatos hivatkozása! Továbbítás chat item action + + Forward %d message(s)? + alert title + Forward and save messages Üzenetek továbbítása és mentése No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Továbbított @@ -3272,6 +3326,10 @@ Ez az ön egyszer használatos hivatkozása! Továbbítva innen: No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. A(z) %@ továbbító kiszolgáló nem tudott csatlakozni a(z) %@ célkiszolgálóhoz. Próbálja meg később. @@ -3323,7 +3381,7 @@ Hiba: %2$@ Fully decentralized – visible only to members. - Teljesen decentralizált - kizárólag tagok számára látható. + Teljesen decentralizált - csak a tagok számára látható. No comment provided by engineer. @@ -3333,7 +3391,7 @@ Hiba: %2$@ Further reduced battery usage - Tovább csökkentett akkumulátor használat + Tovább csökkentett akkumulátor-használat No comment provided by engineer. @@ -3383,17 +3441,17 @@ Hiba: %2$@ Group invitation - Csoportos meghívó + Csoportmeghívó No comment provided by engineer. Group invitation expired - A csoport meghívó lejárt + A csoportmeghívó lejárt No comment provided by engineer. Group invitation is no longer valid, it was removed by sender. - A csoport meghívó már nem érvényes, a küldője törölte. + A csoportmeghívó már nem érvényes, a küldője eltávolította. No comment provided by engineer. @@ -3418,7 +3476,7 @@ Hiba: %2$@ Group members can send SimpleX links. - A csoport tagjai küldhetnek SimpleX hivatkozásokat. + A csoport tagjai küldhetnek SimpleX-hivatkozásokat. No comment provided by engineer. @@ -3453,7 +3511,7 @@ Hiba: %2$@ Group preferences - Csoport beállítások + Csoportbeállítások No comment provided by engineer. @@ -3468,7 +3526,7 @@ Hiba: %2$@ Group welcome message - Csoport üdvözlő üzenete + A csoport üdvözlőüzenete No comment provided by engineer. @@ -3566,6 +3624,10 @@ Hiba: %2$@ ICE-kiszolgálók (soronként egy) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás közben, vagy ossza meg a hivatkozást. @@ -3573,7 +3635,7 @@ Hiba: %2$@ If you enter this passcode when opening the app, all app data will be irreversibly removed! - Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen törlődik! + Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen eltávolításra kerül! No comment provided by engineer. @@ -3643,7 +3705,7 @@ Hiba: %2$@ Improved message delivery - Továbbfejlesztett üzenetküldés + Továbbfejlesztett üzenetkézbesítés No comment provided by engineer. @@ -3683,7 +3745,7 @@ Hiba: %2$@ Incognito mode - Inkognitó mód + Inkognitómód No comment provided by engineer. @@ -4073,7 +4135,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Győződjön meg arról, hogy a %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@). + Győződjön meg arról, hogy a(z) %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@). No comment provided by engineer. @@ -4158,12 +4220,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message delivery receipts! - Üzenet kézbesítési jelentések! + Üzenetkézbesítési jelentések! No comment provided by engineer. Message delivery warning - Üzenet kézbesítési figyelmeztetés + Üzenetkézbesítési figyelmeztetés item status text @@ -4203,7 +4265,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message reception - Üzenet kézbesítési jelentés + Üzenetjelentés No comment provided by engineer. @@ -4213,6 +4275,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message shape + Üzenetbuborék formája No comment provided by engineer. @@ -4265,6 +4328,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Elküldött üzenetek No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi. @@ -4322,7 +4389,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat). - Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre az aktuális adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat). + Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat). No comment provided by engineer. @@ -4432,7 +4499,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! New contact request - Új ismerőskérelem + Új kapcsolatkérés notification @@ -4560,6 +4627,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Nincs kiválasztva semmi No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Értesítések @@ -4592,7 +4663,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Ok Rendben - No comment provided by engineer. + alert button Old database @@ -4606,7 +4677,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! One-time invitation link - Egyszer használatos meghívó hivatkozás + Egyszer használható meghívó-hivatkozás No comment provided by engineer. @@ -4735,7 +4806,7 @@ VPN engedélyezése szükséges. Open server settings - Kiszolgáló beállításainak megnyitása + Kiszolgáló-beállítások megnyitása No comment provided by engineer. @@ -4783,6 +4854,11 @@ VPN engedélyezése szükséges. További %@ kiszolgálók No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING számláló @@ -4818,6 +4894,10 @@ VPN engedélyezése szükséges. A jelkód beállítva! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Jelszó megjelenítése @@ -4825,7 +4905,7 @@ VPN engedélyezése szükséges. Past member %@ - %@ (már nem tag) + (már nem tag) %@ past/unknown group member @@ -4929,7 +5009,7 @@ Hiba: %@ Please enter correct current passphrase. - Adja meg a helyes aktuális jelmondatát. + Adja meg a helyes, jelenlegi jelmondatát. No comment provided by engineer. @@ -4967,6 +5047,10 @@ Hiba: %@ Lengyel kezelőfelület No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen @@ -5009,7 +5093,7 @@ Hiba: %@ Private filenames - Privát fájl nevek + Privát fájlnevek No comment provided by engineer. @@ -5089,7 +5173,7 @@ Hiba: %@ Prohibit sending SimpleX links. - A SimpleX hivatkozások küldése le van tiltva. + A SimpleX-hivatkozások küldése le van tiltva. No comment provided by engineer. @@ -5104,7 +5188,7 @@ Hiba: %@ Prohibit sending files and media. - Fájlok- és a médiatartalom küldés letiltása. + Fájlok- és a médiatartalmak küldésének letiltása. No comment provided by engineer. @@ -5154,6 +5238,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Proxyzott kiszolgálók No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push értesítések @@ -5221,7 +5309,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Receipts are disabled - Üzenet kézbesítési jelentés letiltva + A kézbesítési jelentések le vannak tiltva No comment provided by engineer. @@ -5241,7 +5329,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Received file event - Fogadott fájl esemény + Fogadott fájlesemény notification @@ -5301,7 +5389,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reconnect all connected servers to force message delivery. It uses additional traffic. - Újrakapcsolódás az összes kiszolgálóhoz az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ. + Az összes kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ. No comment provided by engineer. @@ -5316,7 +5404,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reconnect server to force message delivery. It uses additional traffic. - A kiszolgálóhoz való újrakapcsolódás az üzenet kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ. + A kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ. No comment provided by engineer. @@ -5341,7 +5429,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reduced battery usage - Csökkentett akkumulátorhasználat + Csökkentett akkumulátor-használat No comment provided by engineer. @@ -5357,7 +5445,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reject contact request - Kapcsolatfelvételi kérelem elutasítása + Kapcsolatkérés elutasítása No comment provided by engineer. @@ -5377,6 +5465,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Remove archive? + Archívum eltávolítása? No comment provided by engineer. @@ -5416,7 +5505,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Repeat connection request? - Kapcsolódási kérés megismétlése? + Kapcsolatkérés megismétlése? No comment provided by engineer. @@ -5431,7 +5520,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Repeat join request? - Csatlakozási kérés megismétlése? + Csatlakozáskérés megismétlése? No comment provided by engineer. @@ -5559,6 +5648,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SMP-kiszolgáló No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Fájlok biztonságos fogadása @@ -5607,12 +5700,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Save group profile - Csoportprofil elmentése + Csoportprofil mentése No comment provided by engineer. Save passphrase and open chat - Jelmondat elmentése és csevegés megnyitása + Jelmondat mentése és a csevegés megnyitása No comment provided by engineer. @@ -5642,11 +5735,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Save welcome message? - Üdvözlőszöveg mentése? + Üdvözlőüzenet mentése? No comment provided by engineer. Save your profile? + Profil mentése? alert title @@ -5669,6 +5763,10 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Mentett üzenet message info title + + Saving %lld messages + No comment provided by engineer. + Scale Méretezés @@ -5711,12 +5809,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Search bar accepts invitation links. - A keresősáv elfogadja a meghívó hivatkozásokat. + A keresősáv elfogadja a meghívó-hivatkozásokat. No comment provided by engineer. Search or paste SimpleX link - Keresés, vagy SimpleX hivatkozás beillesztése + Keresés, vagy SimpleX-hivatkozás beillesztése No comment provided by engineer. @@ -5746,16 +5844,17 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Select - Választás + Kijelölés chat item action Select chat profile + Csevegési profil kiválasztása No comment provided by engineer. Selected %lld - %lld kiválasztva + %lld kijelölve No comment provided by engineer. @@ -5855,7 +5954,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Send receipts - Üzenet kézbesítési jelentések + Kézbesítési jelentések küldése No comment provided by engineer. @@ -5871,11 +5970,11 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Sender cancelled file transfer. A fájl küldője visszavonta az átvitelt. - No comment provided by engineer. + alert message Sender may have deleted the connection request. - A küldő törölhette a kapcsolódási kérelmet. + A küldő törölhette a kapcsolatkérést. No comment provided by engineer. @@ -5895,22 +5994,22 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Sending receipts is disabled for %lld contacts - A kézbesítési jelentések küldése le van tiltva %lld ismerősnél + A kézbesítési jelentések le vannak tiltva %lld ismerősnél No comment provided by engineer. Sending receipts is disabled for %lld groups - A kézbesítési jelentések küldése le van tiltva %lld csoportban + A kézbesítési jelentések le vannak tiltva %lld csoportban No comment provided by engineer. Sending receipts is enabled for %lld contacts - A kézbesítési jelentések küldése engedélyezve van %lld ismerős számára + A kézbesítési jelentések engedélyezve vannak %lld ismerősnél No comment provided by engineer. Sending receipts is enabled for %lld groups - A kézbesítési jelentések küldése engedélyezve van %lld csoportban + A kézbesítési jelentések engedélyezve vannak %lld csoportban No comment provided by engineer. @@ -5935,7 +6034,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Sent file event - Elküldött fájl esemény + Elküldött fájlesemény notification @@ -6090,6 +6189,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Settings were changed. + A beállítások megváltoztak. alert message @@ -6104,7 +6204,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Share 1-time link - Egyszer használatos hivatkozás megosztása + Egyszer használható hivatkozás megosztása No comment provided by engineer. @@ -6129,11 +6229,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Share profile + Profil megosztása No comment provided by engineer. Share this 1-time invite link - Egyszer használatos meghívó hivatkozás megosztása + Egyszer használható meghívó-hivatkozás megosztása No comment provided by engineer. @@ -6198,7 +6299,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX Address - SimpleX cím + SimpleX-cím No comment provided by engineer. @@ -6208,7 +6309,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX Lock - SimpleX zár + SimpleX-zár No comment provided by engineer. @@ -6218,17 +6319,17 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX Lock not enabled! - A SimpleX zár nincs bekapcsolva! + A SimpleX-zár nincs bekapcsolva! No comment provided by engineer. SimpleX Lock turned on - SimpleX zár bekapcsolva + SimpleX-zár bekapcsolva No comment provided by engineer. SimpleX address - SimpleX cím + SimpleX-cím No comment provided by engineer. @@ -6248,22 +6349,22 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX links - SimpleX hivatkozások + SimpleX-hivatkozások chat feature SimpleX links are prohibited in this group. - A SimpleX hivatkozások küldése le van tiltva ebben a csoportban. + A SimpleX-hivatkozások küldése le van tiltva ebben a csoportban. No comment provided by engineer. SimpleX links not allowed - A SimpleX hivatkozások küldése le van tiltva + A SimpleX-hivatkozások küldése le van tiltva No comment provided by engineer. SimpleX one-time invitation - SimpleX egyszer használatos meghívó + Egyszer használható SimpleX-meghívó simplex link type @@ -6298,6 +6399,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Some app settings were not migrated. + Egyes alkalmazásbeállítások nem lettek átköltöztetve. No comment provided by engineer. @@ -6387,12 +6489,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Stop receiving file? - Fájl fogadás megállítása? + Fájlfogadás megállítása? No comment provided by engineer. Stop sending file? - Fájl küldés megállítása? + Fájlküldés megállítása? No comment provided by engineer. @@ -6477,6 +6579,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Tail + Farok No comment provided by engineer. @@ -6521,7 +6624,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Temporary file error - Ideiglenes fájlhiba + Ideiglenesfájl-hiba No comment provided by engineer. @@ -6573,7 +6676,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The app can notify you when you receive messages or contact requests - please open settings to enable. - Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatfelvételi kéréseket kap – beállítások megnyitása az engedélyezéshez. + Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – beállítások megnyitása az engedélyezéshez. No comment provided by engineer. @@ -6668,11 +6771,12 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The text you pasted is not a SimpleX link. - A beillesztett szöveg nem egy SimpleX hivatkozás. + A beillesztett szöveg nem egy SimpleX-hivatkozás. No comment provided by engineer. The uploaded database archive will be permanently removed from the servers. + A feltöltött adatbázis-archívum véglegesen eltávolításra kerül a kiszolgálókról. No comment provided by engineer. @@ -6692,7 +6796,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak. + Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalmakkal együtt törlésre kerül. Az alacsony felbontású képek viszont megmaradnak. No comment provided by engineer. @@ -6737,12 +6841,12 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This is your own SimpleX address! - Ez az ön SimpleX címe! + Ez az ön SimpleX-címe! No comment provided by engineer. This is your own one-time link! - Ez az ön egyszer használatos hivatkozása! + Ez az ön egyszer használható hivatkozása! No comment provided by engineer. @@ -6798,7 +6902,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. - A biztonsága érdekében kapcsolja be a SimpleX zár funkciót. + A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót. A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén. No comment provided by engineer. @@ -6829,7 +6933,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Toggle incognito when connecting. - Inkognitó mód kapcsolódáskor. + Inkognitómód kapcsolódáskor. No comment provided by engineer. @@ -6955,7 +7059,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unknown servers! Ismeretlen kiszolgálók! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,9 +7173,13 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Tor .onion kiszolgálók használata No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? - SimpleX Chat kiszolgálók használata? + SimpleX Chat-kiszolgálók használata? No comment provided by engineer. @@ -7144,9 +7252,13 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Felhasználó kiválasztása No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. - SimpleX Chat kiszolgálók használatban. + SimpleX Chat-kiszolgálók használatban. No comment provided by engineer. @@ -7256,22 +7368,22 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Waiting for desktop... - Várakozás az asztali kliensre... + Várakozás az asztali kliensre… No comment provided by engineer. Waiting for file - Fájlra várakozás + Várakozás a fájlra No comment provided by engineer. Waiting for image - Képre várakozás + Várakozás a képre No comment provided by engineer. Waiting for video - Videóra várakozás + Várakozás a videóra No comment provided by engineer. @@ -7306,12 +7418,12 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Welcome message - Üdvözlő üzenet + Üdvözlőüzenet No comment provided by engineer. Welcome message is too long - Az üdvözlő üzenet túl hosszú + Az üdvözlőüzenet túl hosszú No comment provided by engineer. @@ -7331,12 +7443,12 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc When people request to connect, you can accept or reject it. - Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat. + Amikor az emberek kapcsolatot kérnek, ön elfogadhatja vagy elutasíthatja azokat. No comment provided by engineer. When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. - Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. + Inkognitó-profil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. No comment provided by engineer. @@ -7356,17 +7468,17 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc With encrypted files and media. - Titkosított fájlokkal és médiatartalommal. + Titkosított fájlokkal és médiatartalmakkal. No comment provided by engineer. With optional welcome message. - Opcionális üdvözlő üzenettel. + Opcionális üdvözlőüzenettel. No comment provided by engineer. With reduced battery usage. - Csökkentett akkumulátorhasználattal. + Csökkentett akkumulátor-használattal. No comment provided by engineer. @@ -7376,8 +7488,8 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - Tor vagy VPN nélkül az IP-címe látható lesz ezen XFTP átjátszók számára: %@. - No comment provided by engineer. + A megosztást az Adatvédelem és biztonság / SimpleX zár menüben engedélyezheti. + alert message Wrong database passphrase @@ -7441,7 +7553,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc You are already connecting via this one-time link! - A kapcsolódás már folyamatban van ezen az egyszer használatos hivatkozáson keresztül! + A kapcsolódás már folyamatban van ezen az egyszer használható hivatkozáson keresztül! No comment provided by engineer. @@ -7451,7 +7563,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc You are already joining the group %@. - A csatlakozás már folyamatban van a(z) %@ csoporthoz. + A csatlakozás már folyamatban van a(z) %@ nevű csoporthoz. No comment provided by engineer. @@ -7468,7 +7580,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc You are already joining the group! Repeat join request? Csatlakozás folyamatban! -Csatlakozási kérés megismétlése? +Csatlakozáskérés megismétlése? No comment provided by engineer. @@ -7568,7 +7680,7 @@ Csatlakozási kérés megismétlése? You can turn on SimpleX Lock via Settings. - A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be. + A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be. No comment provided by engineer. @@ -7578,7 +7690,7 @@ Csatlakozási kérés megismétlése? You can view invitation link again in connection details. - A meghívó hivatkozást újra megtekintheti a kapcsolat részleteinél. + A meghívó-hivatkozást újra megtekintheti a kapcsolat részleteinél. No comment provided by engineer. @@ -7598,14 +7710,14 @@ Csatlakozási kérés megismétlése? You have already requested connection via this address! - Már kért egy kapcsolódási kérelmet ezen a címen keresztül! + Már küldött egy kapcsolatkérést ezen a címen keresztül! No comment provided by engineer. You have already requested connection! Repeat connection request? - Már kért egy kapcsolódási kérelmet! -Kapcsolódási kérés megismétlése? + Már küldött egy kapcsolódási kérelmet! +Kapcsolatkérés megismétlése? No comment provided by engineer. @@ -7655,12 +7767,12 @@ Kapcsolódási kérés megismétlése? You rejected group invitation - Csoport meghívó elutasítva + Csoportmeghívó elutasítva No comment provided by engineer. You sent group invitation - Csoport meghívó elküldve + Csoportmeghívó elküldve No comment provided by engineer. @@ -7675,7 +7787,7 @@ Kapcsolódási kérés megismétlése? You will be connected when your connection request is accepted, please wait or check later! - Akkor lesz kapcsolódva, ha a kapcsolódási kérelme elfogadásra kerül, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, ha a kapcsolatkérése elfogadásra kerül, várjon, vagy ellenőrizze később! No comment provided by engineer. @@ -7735,7 +7847,7 @@ Kapcsolódási kérés megismétlése? Your SimpleX address - Profil SimpleX címe + Profil SimpleX-címe No comment provided by engineer. @@ -7760,6 +7872,7 @@ Kapcsolódási kérés megismétlése? Your chat preferences + Csevegési beállítások alert title @@ -7769,6 +7882,7 @@ Kapcsolódási kérés megismétlése? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + A kapcsolata át lett helyezve ide: %@, de egy váratlan hiba történt a profilra való átirányítás közben. No comment provided by engineer. @@ -7786,6 +7900,10 @@ Kapcsolódási kérés megismétlése? Az ismerősei továbbra is kapcsolódva maradnak. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. A jelenlegi csevegési adatbázis TÖRLŐDNI FOG, és a HELYÉRE az importált adatbázis kerül. @@ -7818,11 +7936,12 @@ Kapcsolódási kérés megismétlése? Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. - Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra. A SimpleX kiszolgálók nem látjhatják profilját. + Profilja az eszközön van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX-kiszolgálók nem láthatják a profilját. No comment provided by engineer. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + A profilja megváltozott. Ha elmenti, a frissített profil elküldésre kerül az összes ismerősének. alert message @@ -7897,7 +8016,7 @@ Kapcsolódási kérés megismétlése? agreeing encryption for %@… - titkosítás jóváhagyása %@ számára… + titkosítás elfogadása %@ számára… chat item text @@ -8027,7 +8146,7 @@ Kapcsolódási kérés megismétlése? connect to SimpleX Chat developers. - Kapcsolódás a SimpleX Chat fejlesztőkhöz. + kapcsolódás a SimpleX Chat fejlesztőkhöz. No comment provided by engineer. @@ -8062,7 +8181,7 @@ Kapcsolódási kérés megismétlése? connecting (introduction invitation) - kapcsolódás (bemutatkozó meghívó) + kapcsolódás (bemutatkozó-meghívó) No comment provided by engineer. @@ -8202,7 +8321,7 @@ Kapcsolódási kérés megismétlése? encryption agreed - titkosítás egyeztetve + titkosítás elfogadva chat item text @@ -8312,7 +8431,7 @@ Kapcsolódási kérés megismétlése? incognito via one-time link - inkognitó egy egyszer használatos hivatkozáson keresztül + inkognitó egy egyszer használható hivatkozáson keresztül chat list item description @@ -8549,17 +8668,17 @@ Kapcsolódási kérés megismétlése? removed contact address - törölt kapcsolattartási cím + eltávolította a kapcsolattartási címet profile update event chat item removed profile picture - törölte a profilképét + eltávolította a profilképét profile update event chat item removed you - eltávolítottak + eltávolította önt rcv group event chat item @@ -8703,7 +8822,7 @@ utoljára fogadott üzenet: %2$@ via one-time link - egy egyszer használatos hivatkozáson keresztül + egyszer használható hivatkozáson keresztül chat list item description @@ -8803,12 +8922,12 @@ utoljára fogadott üzenet: %2$@ you shared one-time link - egyszer használatos hivatkozást osztott meg + egyszer használható hivatkozást osztott meg chat list item description you shared one-time link incognito - egyszer használatos hivatkozást osztott meg inkognitóban + egyszer használható hivatkozást osztott meg inkognitóban chat list item description @@ -8946,7 +9065,7 @@ utoljára fogadott üzenet: %2$@ Currently maximum supported file size is %@. - Jelenleg a maximális támogatott fájlméret %@. + Jelenleg a maximálisan támogatott fájlméret: %@. No comment provided by engineer. @@ -9096,7 +9215,7 @@ utoljára fogadott üzenet: %2$@ You can allow sharing in Privacy & Security / SimpleX Lock settings. - A megosztást az Adatvédelem és biztonság / SimpleX zár menüben engedélyezheti. + A megosztást az Adatvédelem és biztonság / SimpleX-zár menüben engedélyezheti. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index a5e013fec2..38e3fee0de 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -161,11 +162,31 @@ %d giorni time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d ore time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1026,6 +1047,7 @@ Auto-accept settings + Accetta automaticamente le impostazioni alert title @@ -1221,7 +1243,7 @@ Cannot receive file Impossibile ricevere il file - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1351,6 +1373,7 @@ Chat preferences were changed. + Le preferenze della chat sono state cambiate. alert message @@ -1754,6 +1777,7 @@ Questo è il tuo link una tantum! Corner + Angolo No comment provided by engineer. @@ -2425,6 +2449,10 @@ Questo è il tuo link una tantum! Non inviare la cronologia ai nuovi membri. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Non creare un indirizzo @@ -2448,7 +2476,8 @@ Questo è il tuo link una tantum! Download Scarica - chat item action + alert button + chat item action Download errors @@ -2465,6 +2494,10 @@ Questo è il tuo link una tantum! Scarica file server test step + + Download files + alert action + Downloaded Scaricato @@ -2742,6 +2775,7 @@ Questo è il tuo link una tantum! Error changing connection profile + Errore nel cambio di profilo di connessione No comment provided by engineer. @@ -2756,6 +2790,7 @@ Questo è il tuo link una tantum! Error changing to incognito! + Errore nel passaggio a incognito! No comment provided by engineer. @@ -2880,6 +2915,7 @@ Questo è il tuo link una tantum! Error migrating settings + Errore nella migrazione delle impostazioni No comment provided by engineer. @@ -2890,7 +2926,7 @@ Questo è il tuo link una tantum! Error receiving file Errore nella ricezione del file - No comment provided by engineer. + alert title Error reconnecting server @@ -2984,6 +3020,7 @@ Questo è il tuo link una tantum! Error switching profile + Errore nel cambio di profilo No comment provided by engineer. @@ -3122,6 +3159,11 @@ Questo è il tuo link una tantum! Errore del file No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. File non trovato - probabilmente è stato eliminato o annullato. @@ -3257,11 +3299,23 @@ Questo è il tuo link una tantum! Inoltra chat item action + + Forward %d message(s)? + alert title + Forward and save messages Inoltra e salva i messaggi No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Inoltrato @@ -3272,6 +3326,10 @@ Questo è il tuo link una tantum! Inoltrato da No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Il server di inoltro %@ non è riuscito a connettersi al server di destinazione %@. Riprova più tardi. @@ -3566,6 +3624,10 @@ Errore: %2$@ Server ICE (uno per riga) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Se non potete incontrarvi di persona, mostra il codice QR in una videochiamata o condividi il link. @@ -4213,6 +4275,7 @@ Questo è il tuo link per il gruppo %@! Message shape + Forma del messaggio No comment provided by engineer. @@ -4265,6 +4328,10 @@ Questo è il tuo link per il gruppo %@! Messaggi inviati No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione. @@ -4560,6 +4627,10 @@ Questo è il tuo link per il gruppo %@! Nessuna selezione No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Notifiche @@ -4592,7 +4663,7 @@ Questo è il tuo link per il gruppo %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4854,11 @@ Richiede l'attivazione della VPN. Altri %@ server No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Conteggio PING @@ -4818,6 +4894,10 @@ Richiede l'attivazione della VPN. Codice di accesso impostato! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Password per mostrare @@ -4967,6 +5047,10 @@ Errore: %@ Interfaccia polacca No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata @@ -5154,6 +5238,10 @@ Attivalo nelle impostazioni *Rete e server*. Server via proxy No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Notifiche push @@ -5377,6 +5465,7 @@ Attivalo nelle impostazioni *Rete e server*. Remove archive? + Rimuovere l'archivio? No comment provided by engineer. @@ -5559,6 +5648,10 @@ Attivalo nelle impostazioni *Rete e server*. Server SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Ricevi i file in sicurezza @@ -5647,6 +5740,7 @@ Attivalo nelle impostazioni *Rete e server*. Save your profile? + Salvare il profilo? alert title @@ -5669,6 +5763,10 @@ Attivalo nelle impostazioni *Rete e server*. Messaggio salvato message info title + + Saving %lld messages + No comment provided by engineer. + Scale Scala @@ -5751,6 +5849,7 @@ Attivalo nelle impostazioni *Rete e server*. Select chat profile + Seleziona il profilo di chat No comment provided by engineer. @@ -5871,7 +5970,7 @@ Attivalo nelle impostazioni *Rete e server*. Sender cancelled file transfer. Il mittente ha annullato il trasferimento del file. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6090,6 +6189,7 @@ Attivalo nelle impostazioni *Rete e server*. Settings were changed. + Le impostazioni sono state cambiate. alert message @@ -6129,6 +6229,7 @@ Attivalo nelle impostazioni *Rete e server*. Share profile + Condividi il profilo No comment provided by engineer. @@ -6298,6 +6399,7 @@ Attivalo nelle impostazioni *Rete e server*. Some app settings were not migrated. + Alcune impostazioni dell'app non sono state migrate. No comment provided by engineer. @@ -6477,6 +6579,7 @@ Attivalo nelle impostazioni *Rete e server*. Tail + Coda No comment provided by engineer. @@ -6673,6 +6776,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. The uploaded database archive will be permanently removed from the servers. + L'archivio del database caricato verrà rimosso definitivamente dai server. No comment provided by engineer. @@ -6955,7 +7059,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Unknown servers! Server sconosciuti! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7173,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa gli host .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Usare i server di SimpleX Chat? @@ -7144,6 +7252,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Selezione utente No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Utilizzo dei server SimpleX Chat. @@ -7377,7 +7489,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Senza Tor o VPN, il tuo indirizzo IP sarà visibile a questi relay XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7760,6 +7872,7 @@ Ripetere la richiesta di connessione? Your chat preferences + Le tue preferenze della chat alert title @@ -7769,6 +7882,7 @@ Ripetere la richiesta di connessione? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + La tua connessione è stata spostata a %@, ma si è verificato un errore imprevisto durante il reindirizzamento al profilo. No comment provided by engineer. @@ -7786,6 +7900,10 @@ Ripetere la richiesta di connessione? I tuoi contatti resteranno connessi. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Il tuo attuale database della chat verrà ELIMINATO e SOSTITUITO con quello importato. @@ -7823,6 +7941,7 @@ Ripetere la richiesta di connessione? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti. alert message diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 3edfb59c57..ded20013bb 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -161,11 +161,31 @@ %d 日 time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d 時 time interval + + %d messages not forwarded + alert title + %d min %d 分 @@ -1170,7 +1190,7 @@ Cannot receive file ファイル受信ができません - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2288,6 +2308,10 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address アドレスを作成しないでください @@ -2310,7 +2334,8 @@ This is your own one-time link! Download - chat item action + alert button + chat item action Download errors @@ -2325,6 +2350,10 @@ This is your own one-time link! ファイルをダウンロード server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2729,7 +2758,7 @@ This is your own one-time link! Error receiving file ファイル受信にエラー発生 - No comment provided by engineer. + alert title Error reconnecting server @@ -2947,6 +2976,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3073,10 +3107,22 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded No comment provided by engineer. @@ -3085,6 +3131,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3363,6 +3413,10 @@ Error: %2$@ ICEサーバ (1行に1サーバ) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. 直接会えない場合は、ビデオ通話で QR コードを表示するか、リンクを共有してください。 @@ -4016,6 +4070,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4290,6 +4348,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications 通知 @@ -4321,7 +4383,7 @@ This is your link for group %@! Ok OK - No comment provided by engineer. + alert button Old database @@ -4501,6 +4563,11 @@ VPN を有効にする必要があります。 Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING回数 @@ -4536,6 +4603,10 @@ VPN を有効にする必要があります。 パスコードを設定しました! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show パスワードを表示する @@ -4672,6 +4743,10 @@ Error: %@ ポーランド語UI No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect サーバアドレスの証明証IDが正しくないかもしれません @@ -4845,6 +4920,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications プッシュ通知 @@ -5221,6 +5300,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5325,6 +5408,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5511,7 +5598,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. 送信者がファイル転送をキャンセルしました。 - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6514,7 +6601,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6619,6 +6706,10 @@ To connect, please ask your contact to create another connection link and check .onionホストを使う No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX チャット サーバーを使用しますか? @@ -6687,6 +6778,10 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. SimpleX チャット サーバーを使用する。 @@ -6899,7 +6994,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7281,6 +7376,10 @@ Repeat connection request? 連絡先は接続されたままになります。 No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. 現在のチャット データベースは削除され、インポートされたデータベースに置き換えられます。 diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 61fdcc1258..f61ce8924f 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -161,11 +162,31 @@ %d dagen time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d uren time interval + + %d messages not forwarded + alert title + %d min %d min @@ -516,7 +537,7 @@ A separate TCP connection will be used **for each chat profile you have in the app**. - Er wordt een aparte TCP-verbinding gebruikt **voor elk chat profiel dat je in de app hebt**. + Er wordt een aparte TCP-verbinding gebruikt **voor elk chatprofiel dat je in de app hebt**. No comment provided by engineer. @@ -1026,6 +1047,7 @@ Auto-accept settings + Instellingen automatisch accepteren alert title @@ -1150,7 +1172,7 @@ By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). - Via chat profiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). + Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. @@ -1221,7 +1243,7 @@ Cannot receive file Kan bestand niet ontvangen - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1351,6 +1373,7 @@ Chat preferences were changed. + Chatvoorkeuren zijn gewijzigd. alert message @@ -1360,7 +1383,7 @@ Chats - Gesprekken + Chats No comment provided by engineer. @@ -1754,6 +1777,7 @@ Dit is uw eigen eenmalige link! Corner + Hoek No comment provided by engineer. @@ -1967,7 +1991,7 @@ Dit is uw eigen eenmalige link! Database passphrase is required to open chat. - Database wachtwoord is vereist om je gesprekken te openen. + Database wachtwoord is vereist om je chats te openen. No comment provided by engineer. @@ -2062,12 +2086,12 @@ Dit is uw eigen eenmalige link! Delete chat profile - Chat profiel verwijderen + Chatprofiel verwijderen No comment provided by engineer. Delete chat profile? - Chat profiel verwijderen? + Chatprofiel verwijderen? No comment provided by engineer. @@ -2107,7 +2131,7 @@ Dit is uw eigen eenmalige link! Delete files for all chat profiles - Verwijder bestanden voor alle chat profielen + Verwijder bestanden voor alle chatprofielen No comment provided by engineer. @@ -2425,6 +2449,10 @@ Dit is uw eigen eenmalige link! Stuur geen geschiedenis naar nieuwe leden. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Maak geen adres aan @@ -2448,7 +2476,8 @@ Dit is uw eigen eenmalige link! Download Downloaden - chat item action + alert button + chat item action Download errors @@ -2465,6 +2494,10 @@ Dit is uw eigen eenmalige link! Download bestand server test step + + Download files + alert action + Downloaded Gedownload @@ -2742,6 +2775,7 @@ Dit is uw eigen eenmalige link! Error changing connection profile + Fout bij wijzigen van verbindingsprofiel No comment provided by engineer. @@ -2756,6 +2790,7 @@ Dit is uw eigen eenmalige link! Error changing to incognito! + Fout bij het overschakelen naar incognito! No comment provided by engineer. @@ -2880,6 +2915,7 @@ Dit is uw eigen eenmalige link! Error migrating settings + Fout bij migreren van instellingen No comment provided by engineer. @@ -2890,7 +2926,7 @@ Dit is uw eigen eenmalige link! Error receiving file Fout bij ontvangen van bestand - No comment provided by engineer. + alert title Error reconnecting server @@ -2984,6 +3020,7 @@ Dit is uw eigen eenmalige link! Error switching profile + Fout bij wisselen van profiel No comment provided by engineer. @@ -3122,6 +3159,11 @@ Dit is uw eigen eenmalige link! Bestandsfout No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Bestand niet gevonden - hoogstwaarschijnlijk is het bestand verwijderd of geannuleerd. @@ -3214,7 +3256,7 @@ Dit is uw eigen eenmalige link! Find chats faster - Vind gesprekken sneller + Vind chats sneller No comment provided by engineer. @@ -3257,11 +3299,23 @@ Dit is uw eigen eenmalige link! Doorsturen chat item action + + Forward %d message(s)? + alert title + Forward and save messages Berichten doorsturen en opslaan No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Doorgestuurd @@ -3272,6 +3326,10 @@ Dit is uw eigen eenmalige link! Doorgestuurd vanuit No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. De doorstuurserver %@ kon geen verbinding maken met de bestemmingsserver %@. Probeer het later opnieuw. @@ -3493,7 +3551,7 @@ Fout: %2$@ Hidden chat profiles - Verborgen chat profielen + Verborgen chatprofielen No comment provided by engineer. @@ -3566,6 +3624,10 @@ Fout: %2$@ ICE servers (één per lijn) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Als je elkaar niet persoonlijk kunt ontmoeten, laat dan de QR-code zien in een videogesprek of deel de link. @@ -3845,7 +3907,7 @@ Fout: %2$@ It allows having many anonymous connections without any shared data between them in a single chat profile. - Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel. + Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel. No comment provided by engineer. @@ -4213,6 +4275,7 @@ Dit is jouw link voor groep %@! Message shape + Berichtvorm No comment provided by engineer. @@ -4265,6 +4328,10 @@ Dit is jouw link voor groep %@! Berichten verzonden No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Berichten, bestanden en oproepen worden beschermd door **end-to-end codering** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. @@ -4367,7 +4434,7 @@ Dit is jouw link voor groep %@! Multiple chat profiles - Meerdere chat profielen + Meerdere chatprofielen No comment provided by engineer. @@ -4517,7 +4584,7 @@ Dit is jouw link voor groep %@! No filtered chats - Geen gefilterde gesprekken + Geen gefilterde chats No comment provided by engineer. @@ -4560,6 +4627,10 @@ Dit is jouw link voor groep %@! Niets geselecteerd No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Meldingen @@ -4592,7 +4663,7 @@ Dit is jouw link voor groep %@! Ok OK - No comment provided by engineer. + alert button Old database @@ -4783,6 +4854,11 @@ Vereist het inschakelen van VPN. Andere %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING count @@ -4818,6 +4894,10 @@ Vereist het inschakelen van VPN. Toegangscode ingesteld! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Wachtwoord om weer te geven @@ -4875,7 +4955,7 @@ Vereist het inschakelen van VPN. Play from the chat list. - Afspelen via de gesprekken lijst. + Afspelen via de chat lijst. No comment provided by engineer. @@ -4954,7 +5034,7 @@ Fout: %@ Please store passphrase securely, you will NOT be able to access chat if you lose it. - Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken. + Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de chats. No comment provided by engineer. @@ -4967,6 +5047,10 @@ Fout: %@ Poolse interface No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Mogelijk is de certificaat vingerafdruk in het server adres onjuist @@ -5131,7 +5215,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Protect your chat profiles with a password! - Bescherm je chat profielen met een wachtwoord! + Bescherm je chatprofielen met een wachtwoord! No comment provided by engineer. @@ -5154,6 +5238,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push meldingen @@ -5377,6 +5465,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Remove archive? + Archief verwijderen? No comment provided by engineer. @@ -5491,7 +5580,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Restart the app to create a new chat profile - Start de app opnieuw om een nieuw chat profiel aan te maken + Start de app opnieuw om een nieuw chatprofiel aan te maken No comment provided by engineer. @@ -5559,6 +5648,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Veilig bestanden ontvangen @@ -5612,7 +5705,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save passphrase and open chat - Bewaar het wachtwoord en open je gesprekken + Bewaar het wachtwoord en open je chats No comment provided by engineer. @@ -5632,7 +5725,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save servers - Bewaar servers + Servers opslaan No comment provided by engineer. @@ -5647,6 +5740,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save your profile? + Uw profiel opslaan? alert title @@ -5669,6 +5763,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Opgeslagen bericht message info title + + Saving %lld messages + No comment provided by engineer. + Scale Schaal @@ -5751,6 +5849,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Select chat profile + Selecteer chatprofiel No comment provided by engineer. @@ -5871,7 +5970,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Sender cancelled file transfer. Afzender heeft bestandsoverdracht geannuleerd. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6090,6 +6189,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Settings were changed. + Instellingen zijn gewijzigd. alert message @@ -6129,6 +6229,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Share profile + Profiel delen No comment provided by engineer. @@ -6298,6 +6399,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Some app settings were not migrated. + Sommige app-instellingen zijn niet gemigreerd. No comment provided by engineer. @@ -6663,7 +6765,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The servers for new connections of your current chat profile **%@**. - De servers voor nieuwe verbindingen van uw huidige chat profiel **%@**. + De servers voor nieuwe verbindingen van uw huidige chatprofiel **%@**. No comment provided by engineer. @@ -6673,6 +6775,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The uploaded database archive will be permanently removed from the servers. + Het geüploade databasearchief wordt permanent van de servers verwijderd. No comment provided by engineer. @@ -6752,7 +6855,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. This setting applies to messages in your current chat profile **%@**. - Deze instelling is van toepassing op berichten in je huidige chat profiel **%@**. + Deze instelling is van toepassing op berichten in je huidige chatprofiel **%@**. No comment provided by engineer. @@ -6809,7 +6912,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page. - Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chat profielen**. + Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chatprofielen**. No comment provided by engineer. @@ -6924,7 +7027,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Unhide chat profile - Chat profiel zichtbaar maken + Chatprofiel zichtbaar maken No comment provided by engineer. @@ -6955,7 +7058,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Unknown servers! Onbekende servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7172,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik .onion-hosts No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX Chat servers gebruiken? @@ -7144,6 +7251,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruikersselectie No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. SimpleX Chat servers gebruiken. @@ -7377,7 +7488,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Zonder Tor of VPN zal uw IP-adres zichtbaar zijn voor deze XFTP-relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7426,7 +7537,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak You already have a chat profile with the same display name. Please choose another name. - Je hebt al een chat profiel met dezelfde weergave naam. Kies een andere naam. + Je hebt al een chatprofiel met dezelfde weergave naam. Kies een andere naam. No comment provided by engineer. @@ -7760,6 +7871,7 @@ Verbindingsverzoek herhalen? Your chat preferences + Uw chat voorkeuren alert title @@ -7769,6 +7881,7 @@ Verbindingsverzoek herhalen? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Uw verbinding is verplaatst naar %@, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel. No comment provided by engineer. @@ -7786,6 +7899,10 @@ Verbindingsverzoek herhalen? Uw contacten blijven verbonden. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Uw huidige chat database wordt VERWIJDERD en VERVANGEN door de geïmporteerde. @@ -7823,6 +7940,7 @@ Verbindingsverzoek herhalen? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden. alert message @@ -8971,7 +9089,7 @@ laatst ontvangen bericht: %2$@ Database passphrase is required to open chat. - Database wachtwoord is vereist om je gesprekken te openen. + Database wachtwoord is vereist om je chats te openen. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 87f19d69a3..63239de085 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -161,11 +161,31 @@ %d dni time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d godzin time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1221,7 +1241,7 @@ Cannot receive file Nie można odebrać pliku - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ To jest twój jednorazowy link! Nie wysyłaj historii do nowych członków. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Nie twórz adresu @@ -2448,7 +2472,8 @@ To jest twój jednorazowy link! Download Pobierz - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ To jest twój jednorazowy link! Pobierz plik server test step + + Download files + alert action + Downloaded Pobrane @@ -2890,7 +2919,7 @@ To jest twój jednorazowy link! Error receiving file Błąd odbioru pliku - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ To jest twój jednorazowy link! Błąd pliku No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Nie odnaleziono pliku - najprawdopodobniej plik został usunięty lub anulowany. @@ -3257,11 +3291,23 @@ To jest twój jednorazowy link! Przekaż dalej chat item action + + Forward %d message(s)? + alert title + Forward and save messages Przesyłaj dalej i zapisuj wiadomości No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Przekazane dalej @@ -3272,6 +3318,10 @@ To jest twój jednorazowy link! Przekazane dalej od No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później. @@ -3566,6 +3616,10 @@ Błąd: %2$@ Serwery ICE (po jednym na linię) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Jeśli nie możesz spotkać się osobiście, pokaż kod QR w rozmowie wideo lub udostępnij link. @@ -4265,6 +4319,10 @@ To jest twój link do grupy %@! Wysłane wiadomości No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Wiadomości, pliki i połączenia są chronione przez **szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. @@ -4560,6 +4618,10 @@ To jest twój link do grupy %@! Nic nie jest zaznaczone No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Powiadomienia @@ -4592,7 +4654,7 @@ To jest twój link do grupy %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Wymaga włączenia VPN. Inne %@ serwery No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Liczba PINGÓW @@ -4818,6 +4885,10 @@ Wymaga włączenia VPN. Pin ustawiony! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Hasło do wyświetlenia @@ -4967,6 +5038,10 @@ Błąd: %@ Polski interfejs No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy @@ -5154,6 +5229,10 @@ Włącz w ustawianiach *Sieć i serwery* . Serwery trasowane przez proxy No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Powiadomienia push @@ -5559,6 +5638,10 @@ Włącz w ustawianiach *Sieć i serwery* . Serwer SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Bezpiecznie otrzymuj pliki @@ -5669,6 +5752,10 @@ Włącz w ustawianiach *Sieć i serwery* . Zachowano wiadomość message info title + + Saving %lld messages + No comment provided by engineer. + Scale Skaluj @@ -5871,7 +5958,7 @@ Włącz w ustawianiach *Sieć i serwery* . Sender cancelled file transfer. Nadawca anulował transfer pliku. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania. Unknown servers! Nieznane serwery! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj hostów .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Użyć serwerów SimpleX Chat? @@ -7144,6 +7235,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wybór użytkownika No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Używanie serwerów SimpleX Chat. @@ -7377,7 +7472,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Bez Tor lub VPN, Twój adres IP będzie widoczny dla tych przekaźników XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Powtórzyć prośbę połączenia? Twoje kontakty pozostaną połączone. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Twoja obecna baza danych czatu zostanie usunięta i zastąpiona zaimportowaną. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index f2e278c9a4..73b6459780 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -161,11 +161,31 @@ %d дней time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d ч. time interval + + %d messages not forwarded + alert title + %d min %d мин @@ -1221,7 +1241,7 @@ Cannot receive file Невозможно получить файл - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ This is your own one-time link! Не отправлять историю новым членам. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Не создавать адрес @@ -2448,7 +2472,8 @@ This is your own one-time link! Download Загрузить - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ This is your own one-time link! Загрузка файла server test step + + Download files + alert action + Downloaded Принято @@ -2890,7 +2919,7 @@ This is your own one-time link! Error receiving file Ошибка при получении файла - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ This is your own one-time link! Ошибка файла No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Файл не найден - скорее всего, файл был удален или отменен. @@ -3257,11 +3291,23 @@ This is your own one-time link! Переслать chat item action + + Forward %d message(s)? + alert title + Forward and save messages Переслать и сохранить сообщение No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Переслано @@ -3272,6 +3318,10 @@ This is your own one-time link! Переслано из No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Пересылающий сервер %@ не смог подключиться к серверу назначения %@. Попробуйте позже. @@ -3566,6 +3616,10 @@ Error: %2$@ ICE серверы (один на строке) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Если Вы не можете встретиться лично, покажите QR-код во время видеозвонка или поделитесь ссылкой. @@ -4265,6 +4319,10 @@ This is your link for group %@! Сообщений отправлено No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. @@ -4560,6 +4618,10 @@ This is your link for group %@! Ничего не выбрано No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Уведомления @@ -4592,7 +4654,7 @@ This is your link for group %@! Ok Ок - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Requires compatible VPN. Другие %@ серверы No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Количество PING @@ -4818,6 +4885,10 @@ Requires compatible VPN. Код доступа установлен! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Пароль чтобы раскрыть @@ -4967,6 +5038,10 @@ Error: %@ Польский интерфейс No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Возможно, хэш сертификата в адресе сервера неверный @@ -5154,6 +5229,10 @@ Enable in *Network & servers* settings. Проксированные серверы No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Доставка уведомлений @@ -5559,6 +5638,10 @@ Enable in *Network & servers* settings. SMP сервер No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Получайте файлы безопасно @@ -5669,6 +5752,10 @@ Enable in *Network & servers* settings. Сохраненное сообщение message info title + + Saving %lld messages + No comment provided by engineer. + Scale Масштаб @@ -5871,7 +5958,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Отправитель отменил передачу файла. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! Неизвестные серверы! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ To connect, please ask your contact to create another connection link and check Использовать .onion хосты No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Использовать серверы предосталенные SimpleX Chat? @@ -7144,6 +7235,10 @@ To connect, please ask your contact to create another connection link and check Выбор пользователя No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Используются серверы, предоставленные SimpleX Chat. @@ -7377,7 +7472,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Repeat connection request? Ваши контакты сохранятся. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Текущие данные Вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 870c01af8f..195ded259c 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -151,11 +151,31 @@ %d วัน time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d ชั่วโมง time interval + + %d messages not forwarded + alert title + %d min %d นาที @@ -1138,7 +1158,7 @@ Cannot receive file ไม่สามารถรับไฟล์ได้ - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2251,6 +2271,10 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address อย่าสร้างที่อยู่ @@ -2273,7 +2297,8 @@ This is your own one-time link! Download - chat item action + alert button + chat item action Download errors @@ -2288,6 +2313,10 @@ This is your own one-time link! ดาวน์โหลดไฟล์ server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2689,7 +2718,7 @@ This is your own one-time link! Error receiving file เกิดข้อผิดพลาดในการรับไฟล์ - No comment provided by engineer. + alert title Error reconnecting server @@ -2907,6 +2936,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3033,10 +3067,22 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded No comment provided by engineer. @@ -3045,6 +3091,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3323,6 +3373,10 @@ Error: %2$@ เซิร์ฟเวอร์ ICE (หนึ่งเครื่องต่อสาย) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. หากคุณไม่สามารถพบกันในชีวิตจริงได้ ให้แสดงคิวอาร์โค้ดในวิดีโอคอล หรือแชร์ลิงก์ @@ -3975,6 +4029,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4246,6 +4304,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications การแจ้งเตือน @@ -4277,7 +4339,7 @@ This is your link for group %@! Ok ตกลง - No comment provided by engineer. + alert button Old database @@ -4454,6 +4516,11 @@ Requires compatible VPN. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count จํานวน PING @@ -4489,6 +4556,10 @@ Requires compatible VPN. ตั้งรหัสผ่านเรียบร้อยแล้ว! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show รหัสผ่านที่จะแสดง @@ -4625,6 +4696,10 @@ Error: %@ อินเตอร์เฟซภาษาโปแลนด์ No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect อาจเป็นไปได้ว่าลายนิ้วมือของ certificate ในที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง @@ -4798,6 +4873,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications การแจ้งเตือนแบบทันที @@ -5173,6 +5252,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5277,6 +5360,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5464,7 +5551,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. ผู้ส่งยกเลิกการโอนไฟล์ - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6468,7 +6555,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6573,6 +6660,10 @@ To connect, please ask your contact to create another connection link and check ใช้โฮสต์ .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? ใช้เซิร์ฟเวอร์ SimpleX Chat ไหม? @@ -6639,6 +6730,10 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. กำลังใช้เซิร์ฟเวอร์ SimpleX Chat อยู่ @@ -6851,7 +6946,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7232,6 +7327,10 @@ Repeat connection request? ผู้ติดต่อของคุณจะยังคงเชื่อมต่ออยู่ No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. ฐานข้อมูลแชทปัจจุบันของคุณจะถูกลบและแทนที่ด้วยฐานข้อมูลที่นำเข้า diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 3e1199666f..e65fab3814 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -161,11 +161,31 @@ %d gün time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d saat time interval + + %d messages not forwarded + alert title + %d min %d dakika @@ -1196,7 +1216,7 @@ Cannot receive file Dosya alınamıyor - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2358,6 +2378,10 @@ Bu senin kendi tek kullanımlık bağlantın! Yeni üyelere geçmişi gönderme. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Adres oluşturma @@ -2381,7 +2405,8 @@ Bu senin kendi tek kullanımlık bağlantın! Download İndir - chat item action + alert button + chat item action Download errors @@ -2397,6 +2422,10 @@ Bu senin kendi tek kullanımlık bağlantın! Dosya indir server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2817,7 +2846,7 @@ Bu senin kendi tek kullanımlık bağlantın! Error receiving file Dosya alınırken sorun oluştu - No comment provided by engineer. + alert title Error reconnecting server @@ -3043,6 +3072,11 @@ Bu senin kendi tek kullanımlık bağlantın! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3174,11 +3208,23 @@ Bu senin kendi tek kullanımlık bağlantın! İlet chat item action + + Forward %d message(s)? + alert title + Forward and save messages Mesajları ilet ve kaydet No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded İletildi @@ -3189,6 +3235,10 @@ Bu senin kendi tek kullanımlık bağlantın! Şuradan iletildi No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3478,6 +3528,10 @@ Hata: %2$@ ICE sunucuları (her satıra bir tane) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Eğer onunla buluşamıyorsan görüntülü aramada QR kod göster veya bağlantığı paylaş. @@ -4161,6 +4215,10 @@ Bu senin grup için bağlantın %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Mesajlar, dosyalar ve aramalar **uçtan uca şifreleme** ile mükemmel ileri gizlilik, inkar ve izinsiz giriş kurtarma ile korunur. @@ -4451,6 +4509,10 @@ Bu senin grup için bağlantın %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Bildirimler @@ -4483,7 +4545,7 @@ Bu senin grup için bağlantın %@! Ok Tamam - No comment provided by engineer. + alert button Old database @@ -4671,6 +4733,11 @@ VPN'nin etkinleştirilmesi gerekir. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING sayısı @@ -4706,6 +4773,10 @@ VPN'nin etkinleştirilmesi gerekir. Şifre ayarlandı! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Gösterilecek şifre @@ -4850,6 +4921,10 @@ Hata: %@ Lehçe arayüz No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil @@ -5032,6 +5107,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Anında bildirimler @@ -5420,6 +5499,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Dosyaları güvenle alın @@ -5529,6 +5612,10 @@ Enable in *Network & servers* settings. Kaydedilmiş mesaj message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5723,7 +5810,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Gönderici dosya gönderimini iptal etti. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6770,7 +6857,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Unknown servers! Bilinmeyen sunucular! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6880,6 +6967,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste .onion ana bilgisayarlarını kullan No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX Chat sunucuları kullanılsın mı? @@ -6953,6 +7044,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. SimpleX Chat sunucuları kullanılıyor. @@ -7184,7 +7279,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Tor veya VPN olmadan, IP adresiniz bu XFTP aktarıcıları tarafından görülebilir: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7584,6 +7679,10 @@ Bağlantı isteği tekrarlansın mı? Kişileriniz bağlı kalacaktır. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Mevcut sohbet veritabanınız SİLİNECEK ve içe aktarılan veritabanıyla DEĞİŞTİRİLECEKTİR. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index d371b29109..b2dcccd08d 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -161,11 +161,31 @@ %d днів time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d годин time interval + + %d messages not forwarded + alert title + %d min %d хв @@ -1221,7 +1241,7 @@ Cannot receive file Не вдається отримати файл - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ This is your own one-time link! Не надсилайте історію новим користувачам. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Не створювати адресу @@ -2448,7 +2472,8 @@ This is your own one-time link! Download Завантажити - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ This is your own one-time link! Завантажити файл server test step + + Download files + alert action + Downloaded Завантажено @@ -2890,7 +2919,7 @@ This is your own one-time link! Error receiving file Помилка отримання файлу - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ This is your own one-time link! Помилка файлу No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Файл не знайдено - найімовірніше, файл було видалено або скасовано. @@ -3257,11 +3291,23 @@ This is your own one-time link! Пересилання chat item action + + Forward %d message(s)? + alert title + Forward and save messages Пересилання та збереження повідомлень No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Переслано @@ -3272,6 +3318,10 @@ This is your own one-time link! Переслано з No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Серверу переадресації %@ не вдалося з'єднатися з сервером призначення %@. Спробуйте пізніше. @@ -3566,6 +3616,10 @@ Error: %2$@ Сервери ICE (по одному на лінію) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Якщо ви не можете зустрітися особисто, покажіть QR-код у відеодзвінку або поділіться посиланням. @@ -4265,6 +4319,10 @@ This is your link for group %@! Надіслані повідомлення No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Повідомлення, файли та дзвінки захищені **наскрізним шифруванням** з ідеальною секретністю переадресації, відмовою та відновленням після злому. @@ -4560,6 +4618,10 @@ This is your link for group %@! Нічого не вибрано No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Сповіщення @@ -4592,7 +4654,7 @@ This is your link for group %@! Ok Гаразд - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Requires compatible VPN. Інші сервери %@ No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Кількість PING @@ -4818,6 +4885,10 @@ Requires compatible VPN. Пароль встановлено! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Показати пароль @@ -4967,6 +5038,10 @@ Error: %@ Польський інтерфейс No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Можливо, в адресі сервера неправильно вказано відбиток сертифіката @@ -5154,6 +5229,10 @@ Enable in *Network & servers* settings. Проксі-сервери No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push-повідомлення @@ -5559,6 +5638,10 @@ Enable in *Network & servers* settings. Сервер SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Безпечне отримання файлів @@ -5669,6 +5752,10 @@ Enable in *Network & servers* settings. Збережене повідомлення message info title + + Saving %lld messages + No comment provided by engineer. + Scale Масштаб @@ -5871,7 +5958,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Відправник скасував передачу файлу. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! Невідомі сервери! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ To connect, please ask your contact to create another connection link and check Використовуйте хости .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Використовувати сервери SimpleX Chat? @@ -7144,6 +7235,10 @@ To connect, please ask your contact to create another connection link and check Вибір користувача No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Використання серверів SimpleX Chat. @@ -7377,7 +7472,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Без Tor або VPN ваша IP-адреса буде видимою для цих XFTP-ретрансляторів: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Repeat connection request? Ваші контакти залишаться на зв'язку. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Ваша поточна база даних чату буде ВИДАЛЕНА і ЗАМІНЕНА імпортованою. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index b7abefd465..4123cda02c 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -161,11 +161,31 @@ %d 天 time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d 小时 time interval + + %d messages not forwarded + alert title + %d min %d 分钟 @@ -1221,7 +1241,7 @@ Cannot receive file 无法接收文件 - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ This is your own one-time link! 不给新成员发送历史消息。 No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address 不创建地址 @@ -2448,7 +2472,8 @@ This is your own one-time link! Download 下载 - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ This is your own one-time link! 下载文件 server test step + + Download files + alert action + Downloaded 已下载 @@ -2890,7 +2919,7 @@ This is your own one-time link! Error receiving file 接收文件错误 - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ This is your own one-time link! 文件错误 No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. 找不到文件 - 很可能文件已被删除或取消。 @@ -3257,11 +3291,23 @@ This is your own one-time link! 转发 chat item action + + Forward %d message(s)? + alert title + Forward and save messages 转发并保存消息 No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded 已转发 @@ -3272,6 +3318,10 @@ This is your own one-time link! 转发自 No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. 转发服务器 %@ 无法连接到目标服务器 %@。请稍后尝试。 @@ -3566,6 +3616,10 @@ Error: %2$@ ICE 服务器(每行一个) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. 如果您不能亲自见面,可以在视频通话中展示二维码,或分享链接。 @@ -4265,6 +4319,10 @@ This is your link for group %@! 已发送的消息 No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. 消息、文件和通话受到 **端到端加密** 的保护,具有完全正向保密、否认和闯入恢复。 @@ -4560,6 +4618,10 @@ This is your link for group %@! 未选中任何内容 No comment provided by engineer. + + Nothing to forward! + alert title + Notifications 通知 @@ -4592,7 +4654,7 @@ This is your link for group %@! Ok 好的 - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Requires compatible VPN. 其他 %@ 服务器 No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING 次数 @@ -4818,6 +4885,10 @@ Requires compatible VPN. 密码已设置! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show 显示密码 @@ -4967,6 +5038,10 @@ Error: %@ 波兰语界面 No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect 服务器地址中的证书指纹可能不正确 @@ -5154,6 +5229,10 @@ Enable in *Network & servers* settings. 代理服务器 No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications 推送通知 @@ -5559,6 +5638,10 @@ Enable in *Network & servers* settings. SMP 服务器 No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files 安全接收文件 @@ -5669,6 +5752,10 @@ Enable in *Network & servers* settings. 已保存的消息 message info title + + Saving %lld messages + No comment provided by engineer. + Scale 规模 @@ -5871,7 +5958,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. 发送人已取消文件传输。 - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! 未知服务器! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ To connect, please ask your contact to create another connection link and check 使用 .onion 主机 No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? 使用 SimpleX Chat 服务器? @@ -7144,6 +7235,10 @@ To connect, please ask your contact to create another connection link and check 用户选择 No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. 使用 SimpleX Chat 服务器。 @@ -7377,7 +7472,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. 如果没有 Tor 或 VPN,您的 IP 地址将对以下 XFTP 中继可见:%@。 - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Repeat connection request? 与您的联系人保持连接。 No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. 您当前的聊天数据库将被删除并替换为导入的数据库。 diff --git a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings index 8ce4317a9b..13638c4186 100644 --- a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings @@ -17,7 +17,7 @@ "Comment" = "Hozzászólás"; /* No comment provided by engineer. */ -"Currently maximum supported file size is %@." = "Jelenleg a maximális támogatott fájlméret %@."; +"Currently maximum supported file size is %@." = "Jelenleg a maximálisan támogatott fájlméret: %@."; /* No comment provided by engineer. */ "Database downgrade required" = "Adatbázis visszafejlesztése szükséges"; @@ -107,5 +107,5 @@ "Wrong database passphrase" = "Hibás adatbázis jelmondat"; /* No comment provided by engineer. */ -"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "A megosztást az Adatvédelem és biztonság / SimpleX zár menüben engedélyezheti."; +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "A megosztást az Adatvédelem és biztonság / SimpleX-zár menüben engedélyezheti."; diff --git a/apps/ios/SimpleX SE/nl.lproj/Localizable.strings b/apps/ios/SimpleX SE/nl.lproj/Localizable.strings index 8412c88ea6..e5d2487b54 100644 --- a/apps/ios/SimpleX SE/nl.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/nl.lproj/Localizable.strings @@ -32,7 +32,7 @@ "Database passphrase is different from saved in the keychain." = "Het wachtwoord van de database verschilt van het wachtwoord die in de keychain is opgeslagen."; /* No comment provided by engineer. */ -"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je gesprekken te openen."; +"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je chats te openen."; /* No comment provided by engineer. */ "Database upgrade required" = "Database upgrade vereist"; diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b0b838eef8..9b96b3dee6 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -2117,6 +2117,10 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Libraries", + ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", @@ -2168,6 +2172,10 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Libraries", + ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index b044c8bc1e..1468292b1e 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -709,7 +709,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Няма достъп до Keychain за запазване на паролата за базата данни"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Файлът не може да бъде получен"; /* No comment provided by engineer. */ @@ -1395,7 +1395,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Понижи версията и отвори чата"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Изтегли"; /* No comment provided by engineer. */ @@ -1683,7 +1684,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Грешка при отваряне на чата"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Грешка при получаване на файл"; /* No comment provided by engineer. */ @@ -2685,7 +2686,7 @@ /* feature offered item */ "offered %@: %@" = "предлага %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ок"; /* No comment provided by engineer. */ @@ -3365,7 +3366,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Изпращане до последните 100 съобщения на нови членове."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Подателят отмени прехвърлянето на файла."; /* No comment provided by engineer. */ diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index f46da2d120..e6d2018699 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -571,7 +571,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Nelze získat přístup ke klíčence pro uložení hesla databáze"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Nelze přijmout soubor"; /* No comment provided by engineer. */ @@ -1380,7 +1380,7 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "Chyba načítání %@ serverů"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Chyba při příjmu souboru"; /* No comment provided by engineer. */ @@ -2187,7 +2187,7 @@ /* feature offered item */ "offered %@: %@" = "nabídl %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -2735,7 +2735,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Odeslat je z galerie nebo vlastní klávesnice."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Odesílatel zrušil přenos souboru."; /* No comment provided by engineer. */ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index b69b9fa370..93e80091f7 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ und %lld Mitglieder"; @@ -649,6 +652,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Bilder automatisch akzeptieren"; +/* alert title */ +"Auto-accept settings" = "Einstellungen automatisch akzeptieren"; + /* No comment provided by engineer. */ "Back" = "Zurück"; @@ -796,7 +802,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Die Nachricht kann nicht weitergeleitet werden"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Datei kann nicht empfangen werden"; /* snd error text */ @@ -890,6 +896,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Chat-Präferenzen"; +/* alert message */ +"Chat preferences were changed." = "Die Chat-Präferenzen wurden geändert."; + /* No comment provided by engineer. */ "Chat theme" = "Chat-Design"; @@ -1178,6 +1187,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Core Version: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Ecke"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Richtiger Name für %@?"; @@ -1629,7 +1641,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Datenbank herabstufen und den Chat öffnen"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Herunterladen"; /* No comment provided by engineer. */ @@ -1857,12 +1870,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Fehler beim Wechseln der Empfängeradresse"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Fehler beim Wechseln des Verbindungs-Profils"; + /* No comment provided by engineer. */ "Error changing role" = "Fehler beim Ändern der Rolle"; /* No comment provided by engineer. */ "Error changing setting" = "Fehler beim Ändern der Einstellung"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Fehler beim Wechseln zum Inkognito-Profil!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut."; @@ -1936,9 +1955,12 @@ "Error loading %@ servers" = "Fehler beim Laden von %@ Servern"; /* No comment provided by engineer. */ -"Error opening chat" = "Fehler beim Öffnen des Chats"; +"Error migrating settings" = "Fehler beim Migrieren der Einstellungen"; /* No comment provided by engineer. */ +"Error opening chat" = "Fehler beim Öffnen des Chats"; + +/* alert title */ "Error receiving file" = "Fehler beim Empfangen der Datei"; /* No comment provided by engineer. */ @@ -1995,6 +2017,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Fehler beim Beenden des Chats"; +/* No comment provided by engineer. */ +"Error switching profile" = "Fehler beim Wechseln des Profils"; + /* No comment provided by engineer. */ "Error switching profile!" = "Fehler beim Umschalten des Profils!"; @@ -2815,6 +2840,9 @@ /* No comment provided by engineer. */ "Message servers" = "Nachrichten-Server"; +/* No comment provided by engineer. */ +"Message shape" = "Nachrichten-Form"; + /* No comment provided by engineer. */ "Message source remains private." = "Die Nachrichtenquelle bleibt privat."; @@ -3081,7 +3109,7 @@ /* feature offered item */ "offered %@: %@" = "angeboten %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3580,6 +3608,9 @@ /* No comment provided by engineer. */ "Remove" = "Entfernen"; +/* No comment provided by engineer. */ +"Remove archive?" = "Archiv entfernen?"; + /* No comment provided by engineer. */ "Remove image" = "Bild entfernen"; @@ -3752,6 +3783,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Begrüßungsmeldung speichern?"; +/* alert title */ +"Save your profile?" = "Ihr Profil speichern?"; + /* No comment provided by engineer. */ "saved" = "abgespeichert"; @@ -3833,6 +3867,9 @@ /* chat item action */ "Select" = "Auswählen"; +/* No comment provided by engineer. */ +"Select chat profile" = "Chat-Profil auswählen"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld ausgewählt"; @@ -3905,7 +3942,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Der Absender hat die Dateiübertragung abgebrochen."; /* No comment provided by engineer. */ @@ -4046,6 +4083,9 @@ /* No comment provided by engineer. */ "Settings" = "Einstellungen"; +/* alert message */ +"Settings were changed." = "Die Einstellungen wurden geändert."; + /* No comment provided by engineer. */ "Shape profile images" = "Form der Profil-Bilder"; @@ -4067,6 +4107,9 @@ /* No comment provided by engineer. */ "Share link" = "Link teilen"; +/* No comment provided by engineer. */ +"Share profile" = "Profil teilen"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Teilen Sie diesen Einmal-Einladungslink"; @@ -4169,6 +4212,9 @@ /* blur media */ "Soft" = "Weich"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Einige App-Einstellungen wurden nicht migriert."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Einzelne Datei(en) wurde(n) nicht exportiert:"; @@ -4268,6 +4314,9 @@ /* No comment provided by engineer. */ "System authentication" = "System-Authentifizierung"; +/* No comment provided by engineer. */ +"Tail" = "Sprechblase"; + /* No comment provided by engineer. */ "Take picture" = "Machen Sie ein Foto"; @@ -4397,6 +4446,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Der von Ihnen eingefügte Text ist kein SimpleX-Link."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Das hochgeladene Datenbank-Archiv wird dauerhaft von den Servern entfernt."; + /* No comment provided by engineer. */ "Themes" = "Design"; @@ -4574,7 +4626,7 @@ /* No comment provided by engineer. */ "unknown servers" = "Unbekannte Relais"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Unbekannte Server!"; /* No comment provided by engineer. */ @@ -4880,7 +4932,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für Datei-Server sichtbar sein."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: %@."; /* No comment provided by engineer. */ @@ -5141,9 +5193,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen."; +/* alert title */ +"Your chat preferences" = "Ihre Chat-Präferenzen"; + /* No comment provided by engineer. */ "Your chat profiles" = "Ihre Chat-Profile"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Ihre Verbindung wurde auf %@ verschoben. Während Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@)."; @@ -5177,6 +5235,9 @@ /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index ed5efe14ab..debac7711e 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "No se puede reenviar el mensaje"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "No se puede recibir el archivo"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Degradar y abrir Chat"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Descargar"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Error al abrir chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Error al recibir archivo"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "ofrecido %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Se envían hasta 100 mensajes más recientes a los miembros nuevos."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "El remitente ha cancelado la transferencia de archivos."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "con servidores desconocidos"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "¡Servidores desconocidos!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Sin Tor o VPN, tu dirección IP será visible para los servidores de archivos."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Sin Tor o VPN, tu dirección IP será visible para estos servidores XFTP: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index ee7056709e..2bf17ae40a 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -556,7 +556,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Ei pääsyä avainnippuun tietokannan salasanan tallentamiseksi"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Tiedostoa ei voi vastaanottaa"; /* No comment provided by engineer. */ @@ -1356,7 +1356,7 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "Virhe %@-palvelimien lataamisessa"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Virhe tiedoston vastaanottamisessa"; /* No comment provided by engineer. */ @@ -2160,7 +2160,7 @@ /* feature offered item */ "offered %@: %@" = "tarjottu %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -2699,7 +2699,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Lähetä ne galleriasta tai mukautetuista näppäimistöistä."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Lähettäjä peruutti tiedoston siirron."; /* No comment provided by engineer. */ diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index e1c4c20461..0f2c4dfbc5 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Impossible de transférer le message"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Impossible de recevoir le fichier"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Rétrograder et ouvrir le chat"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Télécharger"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Erreur lors de l'ouverture du chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Erreur lors de la réception du fichier"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "propose %1$@ : %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Envoi des 100 derniers messages aux nouveaux membres."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "L'expéditeur a annulé le transfert de fichiers."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "relais inconnus"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Serveurs inconnus !"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Sans Tor ou un VPN, votre adresse IP sera visible par les serveurs de fichiers."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Sans Tor ni VPN, votre adresse IP sera visible par ces relais XFTP : %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 54c0b6bd50..2c8d1ebebd 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -65,10 +65,10 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; +"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása**: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; /* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása**: egyszer használatos QR-kód vagy hivatkozás létrehozása az ismerőse számára."; +"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása**: egyszer használható QR-kód vagy hivatkozás létrehozása az ismerőse számára."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Csoport létrehozása**: új csoport létrehozásához."; @@ -80,16 +80,16 @@ "**e2e encrypted** video call" = "**e2e titkosított** videóhívás"; /* No comment provided by engineer. */ -"**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." = "**Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van."; +"**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." = "**Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van."; /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Legprivátabb**: ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; /* No comment provided by engineer. */ -"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Megjegyzés**: ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja az ismerőseitől érkező üzenetek visszafejtését."; +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Figyelem:** ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja az ismerőseitől érkező üzenetek visszafejtését."; /* No comment provided by engineer. */ -"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Figyelem**: NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt."; +"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Figyelem:** NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt."; /* No comment provided by engineer. */ "**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; @@ -98,7 +98,7 @@ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés**: Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges."; /* No comment provided by engineer. */ -"**Warning**: the archive will be removed." = "**Figyelem**: az archívum törlésre kerül."; +"**Warning**: the archive will be removed." = "**Figyelem**: az archívum eltávolításra kerül."; /* No comment provided by engineer. */ "*bold*" = "\\*félkövér*"; @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ kapcsolódni szeretne!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ és további %lld tag"; @@ -311,7 +314,7 @@ "A separate TCP connection will be used **for each chat profile you have in the app**." = "A rendszer külön TCP-kapcsolatot fog használni **az alkalmazásban található minden csevegési profilhoz**."; /* No comment provided by engineer. */ -"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." = "A rendszer külön TCP-kapcsolatot fog használni **minden ismerőshöz és csoporttaghoz**.\n**Figyelem**: sok kapcsolódás esetén, az akkumulátor- és adatforgalom fogyasztás jelentősen megnőhet, és egyes kapcsolatok meghiúsulhatnak."; +"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." = "**Minden egyes kapcsolathoz és csoporttaghoz** külön TCP-kapcsolat lesz használva.\n**Figyelem:** ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet."; /* No comment provided by engineer. */ "Abort" = "Megszakítás"; @@ -323,13 +326,13 @@ "Abort changing address?" = "Címváltoztatás megszakítása??"; /* No comment provided by engineer. */ -"About SimpleX" = "A SimpleX-ről"; +"About SimpleX" = "A SimpleXről"; /* No comment provided by engineer. */ -"About SimpleX address" = "A SimpleX címről"; +"About SimpleX address" = "A SimpleX-címről"; /* No comment provided by engineer. */ -"About SimpleX Chat" = "A SimpleX Chat-ről"; +"About SimpleX Chat" = "A SimpleX Chatről"; /* No comment provided by engineer. */ "above, then choose:" = "gombra fent, majd válassza ki:"; @@ -343,10 +346,10 @@ "Accept" = "Elfogadás"; /* No comment provided by engineer. */ -"Accept connection request?" = "Ismerőskérelem elfogadása?"; +"Accept connection request?" = "Kapcsolatkérés elfogadása?"; /* notification body */ -"Accept contact request from %@?" = "Elfogadja %@ kapcsolat kérését?"; +"Accept contact request from %@?" = "Elfogadja %@ kapcsolatkérését?"; /* accept contact request via notification swipe action */ @@ -386,7 +389,7 @@ "Add to another device" = "Hozzáadás egy másik eszközhöz"; /* No comment provided by engineer. */ -"Add welcome message" = "Üdvözlő üzenet hozzáadása"; +"Add welcome message" = "Üdvözlőüzenet hozzáadása"; /* No comment provided by engineer. */ "Additional accent" = "További kiemelés"; @@ -422,7 +425,7 @@ "Advanced settings" = "Haladó beállítások"; /* chat item text */ -"agreeing encryption for %@…" = "titkosítás jóváhagyása %@ számára…"; +"agreeing encryption for %@…" = "titkosítás elfogadása %@ számára…"; /* chat item text */ "agreeing encryption…" = "titkosítás elfogadása…"; @@ -470,22 +473,22 @@ "Allow" = "Engedélyezés"; /* No comment provided by engineer. */ -"Allow calls only if your contact allows them." = "A hívások kezdeményezése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi."; +"Allow calls only if your contact allows them." = "A hívások kezdeményezése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi."; /* No comment provided by engineer. */ "Allow calls?" = "Hívások engedélyezése?"; /* No comment provided by engineer. */ -"Allow disappearing messages only if your contact allows it to you." = "Az eltűnő üzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi az ön számára."; +"Allow disappearing messages only if your contact allows it to you." = "Az eltűnő üzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi az ön számára."; /* No comment provided by engineer. */ "Allow downgrade" = "Visszafejlesztés engedélyezése"; /* No comment provided by engineer. */ -"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra)"; +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra)"; /* No comment provided by engineer. */ -"Allow message reactions only if your contact allows them." = "Az üzenetreakciók küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi."; +"Allow message reactions only if your contact allows them." = "Az üzenetreakciók küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi."; /* No comment provided by engineer. */ "Allow message reactions." = "Üzenetreakciók engedélyezése."; @@ -503,16 +506,16 @@ "Allow to irreversibly delete sent messages. (24 hours)" = "Elküldött üzenetek végleges törlésének engedélyezése. (24 óra)"; /* No comment provided by engineer. */ -"Allow to send files and media." = "Fájlok és médiatartalom küldésének engedélyezése."; +"Allow to send files and media." = "Fájlok és médiatartalmak küldésének engedélyezése."; /* No comment provided by engineer. */ -"Allow to send SimpleX links." = "A SimpleX hivatkozások küldése engedélyezve van."; +"Allow to send SimpleX links." = "A SimpleX-hivatkozások küldése engedélyezve van."; /* No comment provided by engineer. */ "Allow to send voice messages." = "Hangüzenetek küldésének engedélyezése."; /* No comment provided by engineer. */ -"Allow voice messages only if your contact allows them." = "A hangüzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi."; +"Allow voice messages only if your contact allows them." = "A hangüzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi."; /* No comment provided by engineer. */ "Allow voice messages?" = "Hangüzenetek engedélyezése?"; @@ -644,11 +647,14 @@ "Auto-accept" = "Automatikus elfogadás"; /* No comment provided by engineer. */ -"Auto-accept contact requests" = "Kapcsolódási kérelmek automatikus elfogadása"; +"Auto-accept contact requests" = "Kapcsolatkérések automatikus elfogadása"; /* No comment provided by engineer. */ "Auto-accept images" = "Képek automatikus elfogadása"; +/* alert title */ +"Auto-accept settings" = "Beállítások automatikus elfogadása"; + /* No comment provided by engineer. */ "Back" = "Vissza"; @@ -796,7 +802,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Nem lehet továbbítani az üzenetet"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Nem lehet fogadni a fájlt"; /* snd error text */ @@ -890,6 +896,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Csevegési beállítások"; +/* alert message */ +"Chat preferences were changed." = "A csevegési beállítások megváltoztak."; + /* No comment provided by engineer. */ "Chat theme" = "Csevegés témája"; @@ -1005,7 +1014,7 @@ "Connect to desktop" = "Társítás számítógéppel"; /* No comment provided by engineer. */ -"connect to SimpleX Chat developers." = "Kapcsolódás a SimpleX Chat fejlesztőkhöz."; +"connect to SimpleX Chat developers." = "kapcsolódás a SimpleX Chat fejlesztőkhöz."; /* No comment provided by engineer. */ "Connect to your friends faster." = "Kapcsolódjon gyorsabban az ismerőseihez."; @@ -1014,10 +1023,10 @@ "Connect to yourself?" = "Kapcsolódás saját magához?"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódás saját magához?\nEz az ön egyszer használatos hivatkozása!"; +"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódás saját magához?\nEz az ön egyszer használható hivatkozása!"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz az ön SimpleX címe!"; +"Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz az ön SimpleX-címe!"; /* No comment provided by engineer. */ "Connect via contact address" = "Kapcsolódás a kapcsolattartási címen keresztül"; @@ -1026,7 +1035,7 @@ "Connect via link" = "Kapcsolódás egy hivatkozáson keresztül"; /* No comment provided by engineer. */ -"Connect via one-time link" = "Kapcsolódás egyszer használatos hivatkozáson keresztül"; +"Connect via one-time link" = "Kapcsolódás egyszer használható hivatkozáson keresztül"; /* No comment provided by engineer. */ "Connect with %@" = "Kapcsolódás ezzel: %@"; @@ -1065,7 +1074,7 @@ "connecting (introduced)" = "kapcsolódás (bejelentve)"; /* No comment provided by engineer. */ -"connecting (introduction invitation)" = "kapcsolódás (bemutatkozó meghívó)"; +"connecting (introduction invitation)" = "kapcsolódás (bemutatkozó-meghívó)"; /* call status */ "connecting call" = "kapcsolódási hívás…"; @@ -1074,7 +1083,7 @@ "Connecting server…" = "Kapcsolódás a kiszolgálóhoz…"; /* No comment provided by engineer. */ -"Connecting server… (error: %@)" = "Kapcsolódás a kiszolgálóhoz... (hiba: %@)"; +"Connecting server… (error: %@)" = "Kapcsolódás a kiszolgálóhoz… (hiba: %@)"; /* No comment provided by engineer. */ "Connecting to contact, please wait or check later!" = "Kapcsolódás az ismerőshöz, várjon vagy ellenőrizze később!"; @@ -1104,7 +1113,7 @@ "Connection notifications" = "Kapcsolódási értesítések"; /* No comment provided by engineer. */ -"Connection request sent!" = "Kapcsolódási kérés elküldve!"; +"Connection request sent!" = "Kapcsolatkérés elküldve!"; /* No comment provided by engineer. */ "Connection terminated" = "Kapcsolat megszakítva"; @@ -1178,6 +1187,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Alapverziószám: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Sarok"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Név javítása erre: %@?"; @@ -1215,7 +1227,7 @@ "Create secret group" = "Titkos csoport létrehozása"; /* No comment provided by engineer. */ -"Create SimpleX address" = "SimpleX cím létrehozása"; +"Create SimpleX address" = "SimpleX-cím létrehozása"; /* No comment provided by engineer. */ "Create your profile" = "Saját profil létrehozása"; @@ -1495,7 +1507,7 @@ "Delivery receipts are disabled!" = "A kézbesítési jelentések ki vannak kapcsolva!"; /* No comment provided by engineer. */ -"Delivery receipts!" = "Üzenet kézbesítési jelentések!"; +"Delivery receipts!" = "Kézbesítési jelentések!"; /* No comment provided by engineer. */ "Description" = "Leírás"; @@ -1537,10 +1549,10 @@ "Device" = "Eszköz"; /* No comment provided by engineer. */ -"Device authentication is disabled. Turning off SimpleX Lock." = "A készüléken nincs beállítva a képernyőzár. A SimpleX zár ki van kapcsolva."; +"Device authentication is disabled. Turning off SimpleX Lock." = "A készüléken nincs beállítva a képernyőzár. A SimpleX-zár ki van kapcsolva."; /* No comment provided by engineer. */ -"Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication." = "A készüléken nincs beállítva a képernyőzár. A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén."; +"Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication." = "A készüléken nincs beállítva a képernyőzár. A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén."; /* No comment provided by engineer. */ "different migration in the app/database: %@ / %@" = "különböző átköltöztetések az alkalmazásban/adatbázisban: %@ / %@"; @@ -1564,7 +1576,7 @@ "Disable for all" = "Letiltás mindenki számára"; /* authentication reason */ -"Disable SimpleX Lock" = "SimpleX zár kikapcsolása"; +"Disable SimpleX Lock" = "SimpleX-zár kikapcsolása"; /* No comment provided by engineer. */ "disabled" = "letiltva"; @@ -1629,7 +1641,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Visszafejlesztés és a csevegés megnyitása"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Letöltés"; /* No comment provided by engineer. */ @@ -1711,7 +1724,7 @@ "Enable self-destruct passcode" = "Önmegsemmisítő jelkód engedélyezése"; /* authentication reason */ -"Enable SimpleX Lock" = "SimpleX zár bekapcsolása"; +"Enable SimpleX Lock" = "SimpleX-zár bekapcsolása"; /* No comment provided by engineer. */ "Enable TCP keep-alive" = "TCP életben tartása"; @@ -1741,7 +1754,7 @@ "Encrypt local files" = "Helyi fájlok titkosítása"; /* No comment provided by engineer. */ -"Encrypt stored files & media" = "Tárolt fájlok és médiatartalmak titkosítása"; +"Encrypt stored files & media" = "A tárolt fájlok- és a médiatartalmak titkosítása"; /* No comment provided by engineer. */ "Encrypted database" = "Titkosított adatbázis"; @@ -1768,7 +1781,7 @@ "Encrypted message: unexpected error" = "Titkosított üzenet: váratlan hiba"; /* chat item text */ -"encryption agreed" = "titkosítás egyeztetve"; +"encryption agreed" = "titkosítás elfogadva"; /* chat item text */ "encryption agreed for %@" = "titkosítás elfogadva %@ számára"; @@ -1804,7 +1817,7 @@ "ended call %@" = "%@ hívása befejeződött"; /* No comment provided by engineer. */ -"Enter correct passphrase." = "Helyes jelmondat bevitele."; +"Enter correct passphrase." = "Adja meg a helyes jelmondatot."; /* No comment provided by engineer. */ "Enter group name…" = "Csoportnév megadása…"; @@ -1828,10 +1841,10 @@ "Enter this device name…" = "Eszköznév megadása…"; /* placeholder */ -"Enter welcome message…" = "Üdvözlő üzenet megadása…"; +"Enter welcome message…" = "Üdvözlőüzenet megadása…"; /* placeholder */ -"Enter welcome message… (optional)" = "Üdvözlő üzenet megadása… (opcionális)"; +"Enter welcome message… (optional)" = "Üdvözlőüzenet megadása… (opcionális)"; /* No comment provided by engineer. */ "Enter your name…" = "Adjon meg egy nevet…"; @@ -1846,7 +1859,7 @@ "Error aborting address change" = "Hiba a cím megváltoztatásának megszakításakor"; /* No comment provided by engineer. */ -"Error accepting contact request" = "Hiba történt a kapcsolatfelvételi kérelem elfogadásakor"; +"Error accepting contact request" = "Hiba történt a kapcsolatkérés elfogadásakor"; /* No comment provided by engineer. */ "Error accessing database file" = "Hiba az adatbázisfájl elérésekor"; @@ -1857,12 +1870,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Hiba a cím megváltoztatásakor"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Hiba a kapcsolati profilra való váltás közben"; + /* No comment provided by engineer. */ "Error changing role" = "Hiba a szerepkör megváltoztatásakor"; /* No comment provided by engineer. */ "Error changing setting" = "Hiba a beállítás megváltoztatásakor"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Hiba az inkognitó-profilra való váltás közben!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Hiba a(z) %@ továbbító kiszolgálóhoz való kapcsolódáskor. Próbálja meg később."; @@ -1936,9 +1955,12 @@ "Error loading %@ servers" = "Hiba a %@ kiszolgálók betöltésekor"; /* No comment provided by engineer. */ -"Error opening chat" = "Hiba a csevegés megnyitásakor"; +"Error migrating settings" = "Hiba a beallítások átköltöztetése közben"; /* No comment provided by engineer. */ +"Error opening chat" = "Hiba a csevegés megnyitásakor"; + +/* alert title */ "Error receiving file" = "Hiba a fájl fogadásakor"; /* No comment provided by engineer. */ @@ -1987,7 +2009,7 @@ "Error sending message" = "Hiba az üzenet küldésekor"; /* No comment provided by engineer. */ -"Error setting delivery receipts!" = "Hiba történt a kézbesítési igazolások beállításakor!"; +"Error setting delivery receipts!" = "Hiba történt a kézbesítési jelentések beállításakor!"; /* No comment provided by engineer. */ "Error starting chat" = "Hiba a csevegés elindításakor"; @@ -1995,6 +2017,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Hiba a csevegés megállításakor"; +/* No comment provided by engineer. */ +"Error switching profile" = "Hiba a profilváltás közben"; + /* No comment provided by engineer. */ "Error switching profile!" = "Hiba a profil váltásakor!"; @@ -2075,7 +2100,7 @@ "Fast and no wait until the sender is online!" = "Gyors és nem kell várni, amíg a feladó online lesz!"; /* No comment provided by engineer. */ -"Faster joining and more reliable messages." = "Gyorsabb csatlakozás és megbízhatóbb üzenet kézbesítés."; +"Faster joining and more reliable messages." = "Gyorsabb csatlakozás és megbízhatóbb üzenetkézbesítés."; /* swipe action */ "Favorite" = "Kedvenc"; @@ -2111,19 +2136,19 @@ "Files" = "Fájlok"; /* No comment provided by engineer. */ -"Files & media" = "Fájlok és média"; +"Files & media" = "Fájlok és médiatartalmak"; /* chat feature */ -"Files and media" = "Fájlok és médiatartalom"; +"Files and media" = "Fájlok és médiatartalmak"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "A fájlok- és a médiatartalom küldése le van tiltva ebben a csoportban."; +"Files and media are prohibited in this group." = "A fájlok- és a médiatartalmak le vannak tiltva ebben a csoportban."; /* No comment provided by engineer. */ -"Files and media not allowed" = "Fájlok és média tartalom küldése le van tiltva"; +"Files and media not allowed" = "A fájlok- és médiatartalmak nincsenek engedélyezve"; /* No comment provided by engineer. */ -"Files and media prohibited!" = "A fájlok- és a médiatartalom küldése le van tiltva!"; +"Files and media prohibited!" = "A fájlok- és a médiatartalmak küldése le van tiltva!"; /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Olvasatlan és kedvenc csevegésekre való szűrés."; @@ -2204,13 +2229,13 @@ "Full name (optional)" = "Teljes név (opcionális)"; /* No comment provided by engineer. */ -"Fully decentralized – visible only to members." = "Teljesen decentralizált - kizárólag tagok számára látható."; +"Fully decentralized – visible only to members." = "Teljesen decentralizált - csak a tagok számára látható."; /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Teljesen újra implementálva - háttérben történő működés!"; /* No comment provided by engineer. */ -"Further reduced battery usage" = "Tovább csökkentett akkumulátor használat"; +"Further reduced battery usage" = "Tovább csökkentett akkumulátor-használat"; /* No comment provided by engineer. */ "GIFs and stickers" = "GIF-ek és matricák"; @@ -2243,13 +2268,13 @@ "Group image" = "Csoportkép"; /* No comment provided by engineer. */ -"Group invitation" = "Csoportos meghívó"; +"Group invitation" = "Csoportmeghívó"; /* No comment provided by engineer. */ -"Group invitation expired" = "A csoport meghívó lejárt"; +"Group invitation expired" = "A csoportmeghívó lejárt"; /* No comment provided by engineer. */ -"Group invitation is no longer valid, it was removed by sender." = "A csoport meghívó már nem érvényes, a küldője törölte."; +"Group invitation is no longer valid, it was removed by sender." = "A csoportmeghívó már nem érvényes, a küldője eltávolította."; /* No comment provided by engineer. */ "Group link" = "Csoporthivatkozás"; @@ -2273,7 +2298,7 @@ "Group members can send files and media." = "A csoport tagjai küldhetnek fájlokat és médiatartalmakat."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "A csoport tagjai küldhetnek SimpleX hivatkozásokat."; +"Group members can send SimpleX links." = "A csoport tagjai küldhetnek SimpleX-hivatkozásokat."; /* No comment provided by engineer. */ "Group members can send voice messages." = "A csoport tagjai küldhetnek hangüzeneteket."; @@ -2285,7 +2310,7 @@ "Group moderation" = "Csoport moderáció"; /* No comment provided by engineer. */ -"Group preferences" = "Csoport beállítások"; +"Group preferences" = "Csoportbeállítások"; /* No comment provided by engineer. */ "Group profile" = "Csoportprofil"; @@ -2297,7 +2322,7 @@ "group profile updated" = "csoportprofil frissítve"; /* No comment provided by engineer. */ -"Group welcome message" = "Csoport üdvözlő üzenete"; +"Group welcome message" = "A csoport üdvözlőüzenete"; /* No comment provided by engineer. */ "Group will be deleted for all members - this cannot be undone!" = "A csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza!"; @@ -2363,7 +2388,7 @@ "If you can't meet in person, show QR code in a video call, or share the link." = "Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás közben, vagy ossza meg a hivatkozást."; /* No comment provided by engineer. */ -"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen törlődik!"; +"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen eltávolításra kerül!"; /* No comment provided by engineer. */ "If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:"; @@ -2405,7 +2430,7 @@ "Importing archive" = "Archívum importálása"; /* No comment provided by engineer. */ -"Improved message delivery" = "Továbbfejlesztett üzenetküldés"; +"Improved message delivery" = "Továbbfejlesztett üzenetkézbesítés"; /* No comment provided by engineer. */ "Improved privacy and security" = "Fejlesztett adatvédelem és biztonság"; @@ -2432,7 +2457,7 @@ "Incognito groups" = "Inkognitó csoportok"; /* No comment provided by engineer. */ -"Incognito mode" = "Inkognitó mód"; +"Incognito mode" = "Inkognitómód"; /* No comment provided by engineer. */ "Incognito mode protects your privacy by using a new random profile for each contact." = "Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ."; @@ -2444,7 +2469,7 @@ "incognito via group link" = "inkognitó a csoporthivatkozáson keresztül"; /* chat list item description */ -"incognito via one-time link" = "inkognitó egy egyszer használatos hivatkozáson keresztül"; +"incognito via one-time link" = "inkognitó egy egyszer használható hivatkozáson keresztül"; /* notification */ "Incoming audio call" = "Bejövő hanghívás"; @@ -2714,7 +2739,7 @@ "Make profile private!" = "Tegye priváttá a profilját!"; /* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjön meg arról, hogy a %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@)."; +"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjön meg arról, hogy a(z) %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@)."; /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva."; @@ -2780,10 +2805,10 @@ "Message delivery error" = "Üzenetkézbesítési hiba"; /* No comment provided by engineer. */ -"Message delivery receipts!" = "Üzenet kézbesítési jelentések!"; +"Message delivery receipts!" = "Üzenetkézbesítési jelentések!"; /* item status text */ -"Message delivery warning" = "Üzenet kézbesítési figyelmeztetés"; +"Message delivery warning" = "Üzenetkézbesítési figyelmeztetés"; /* No comment provided by engineer. */ "Message draft" = "Üzenetvázlat"; @@ -2810,11 +2835,14 @@ "message received" = "üzenet érkezett"; /* No comment provided by engineer. */ -"Message reception" = "Üzenet kézbesítési jelentés"; +"Message reception" = "Üzenetjelentés"; /* No comment provided by engineer. */ "Message servers" = "Üzenetkiszolgálók"; +/* No comment provided by engineer. */ +"Message shape" = "Üzenetbuborék formája"; + /* No comment provided by engineer. */ "Message source remains private." = "Az üzenet forrása titokban marad."; @@ -2879,7 +2907,7 @@ "Migration error:" = "Átköltöztetés hiba:"; /* No comment provided by engineer. */ -"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre az aktuális adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat)."; +"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat)."; /* No comment provided by engineer. */ "Migration is completed" = "Az átköltöztetés befejeződött"; @@ -2963,7 +2991,7 @@ "New chat experience 🎉" = "Új csevegési élmény 🎉"; /* notification */ -"New contact request" = "Új ismerőskérelem"; +"New contact request" = "Új kapcsolatkérés"; /* notification */ "New contact:" = "Új kapcsolat:"; @@ -3081,7 +3109,7 @@ /* feature offered item */ "offered %@: %@" = "ajánlotta %1$@: %2$@-kor"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Rendben"; /* No comment provided by engineer. */ @@ -3097,7 +3125,7 @@ "on" = "bekapcsolva"; /* No comment provided by engineer. */ -"One-time invitation link" = "Egyszer használatos meghívó hivatkozás"; +"One-time invitation link" = "Egyszer használható meghívó-hivatkozás"; /* No comment provided by engineer. */ "Onion hosts will be **required** for connection.\nRequires compatible VPN." = "A kapcsolódáshoz Onion kiszolgálókra lesz szükség.\nVPN engedélyezése szükséges."; @@ -3169,7 +3197,7 @@ "Open migration to another device" = "Átköltöztetés megkezdése egy másik eszközre"; /* No comment provided by engineer. */ -"Open server settings" = "Kiszolgáló beállításainak megnyitása"; +"Open server settings" = "Kiszolgáló-beállítások megnyitása"; /* No comment provided by engineer. */ "Open Settings" = "Beállítások megnyitása"; @@ -3232,7 +3260,7 @@ "Password to show" = "Jelszó megjelenítése"; /* past/unknown group member */ -"Past member %@" = "%@ (már nem tag)"; +"Past member %@" = "(már nem tag) %@"; /* No comment provided by engineer. */ "Paste desktop address" = "Számítógép címének beillesztése"; @@ -3301,7 +3329,7 @@ "Please contact group admin." = "Lépjen kapcsolatba a csoport adminnal."; /* No comment provided by engineer. */ -"Please enter correct current passphrase." = "Adja meg a helyes aktuális jelmondatát."; +"Please enter correct current passphrase." = "Adja meg a helyes, jelenlegi jelmondatát."; /* No comment provided by engineer. */ "Please enter the previous password after restoring database backup. This action can not be undone." = "Előző jelszó megadása az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem vonható vissza."; @@ -3349,7 +3377,7 @@ "Privacy redefined" = "Adatvédelem újraértelmezve"; /* No comment provided by engineer. */ -"Private filenames" = "Privát fájl nevek"; +"Private filenames" = "Privát fájlnevek"; /* No comment provided by engineer. */ "Private message routing" = "Privát üzenet útválasztás"; @@ -3403,10 +3431,10 @@ "Prohibit sending disappearing messages." = "Az eltűnő üzenetek küldése le van tiltva."; /* No comment provided by engineer. */ -"Prohibit sending files and media." = "Fájlok- és a médiatartalom küldés letiltása."; +"Prohibit sending files and media." = "Fájlok- és a médiatartalmak küldésének letiltása."; /* No comment provided by engineer. */ -"Prohibit sending SimpleX links." = "A SimpleX hivatkozások küldése le van tiltva."; +"Prohibit sending SimpleX links." = "A SimpleX-hivatkozások küldése le van tiltva."; /* No comment provided by engineer. */ "Prohibit sending voice messages." = "A hangüzenetek küldése le van tiltva."; @@ -3478,7 +3506,7 @@ "Read more in our GitHub repository." = "További információ a GitHub tárolónkban."; /* No comment provided by engineer. */ -"Receipts are disabled" = "Üzenet kézbesítési jelentés letiltva"; +"Receipts are disabled" = "A kézbesítési jelentések le vannak tiltva"; /* No comment provided by engineer. */ "Receive errors" = "Üzenetfogadási hibák"; @@ -3496,7 +3524,7 @@ "received confirmation…" = "visszaigazolás fogadása…"; /* notification */ -"Received file event" = "Fogadott fájl esemény"; +"Received file event" = "Fogadott fájlesemény"; /* message info title */ "Received message" = "Fogadott üzenet"; @@ -3532,7 +3560,7 @@ "Reconnect" = "Újrakapcsolás"; /* No comment provided by engineer. */ -"Reconnect all connected servers to force message delivery. It uses additional traffic." = "Újrakapcsolódás az összes kiszolgálóhoz az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ."; +"Reconnect all connected servers to force message delivery. It uses additional traffic." = "Az összes kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ."; /* No comment provided by engineer. */ "Reconnect all servers" = "Újrakapcsolódás minden kiszolgálóhoz"; @@ -3541,7 +3569,7 @@ "Reconnect all servers?" = "Újrakapcsolódás minden kiszolgálóhoz?"; /* No comment provided by engineer. */ -"Reconnect server to force message delivery. It uses additional traffic." = "A kiszolgálóhoz való újrakapcsolódás az üzenet kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ."; +"Reconnect server to force message delivery. It uses additional traffic." = "A kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ."; /* No comment provided by engineer. */ "Reconnect server?" = "Újrakapcsolódás a kiszolgálóhoz?"; @@ -3556,7 +3584,7 @@ "Record updated at: %@" = "A bejegyzés frissítve: %@"; /* No comment provided by engineer. */ -"Reduced battery usage" = "Csökkentett akkumulátorhasználat"; +"Reduced battery usage" = "Csökkentett akkumulátor-használat"; /* reject incoming call via notification swipe action */ @@ -3566,7 +3594,7 @@ "Reject (sender NOT notified)" = "Elutasítás (a feladó NEM kap értesítést)"; /* No comment provided by engineer. */ -"Reject contact request" = "Kapcsolatfelvételi kérelem elutasítása"; +"Reject contact request" = "Kapcsolatkérés elutasítása"; /* call status */ "rejected call" = "elutasított hívás"; @@ -3580,6 +3608,9 @@ /* No comment provided by engineer. */ "Remove" = "Eltávolítás"; +/* No comment provided by engineer. */ +"Remove archive?" = "Archívum eltávolítása?"; + /* No comment provided by engineer. */ "Remove image" = "Kép eltávolítása"; @@ -3599,13 +3630,13 @@ "removed %@" = "eltávolította őt: %@"; /* profile update event chat item */ -"removed contact address" = "törölt kapcsolattartási cím"; +"removed contact address" = "eltávolította a kapcsolattartási címet"; /* profile update event chat item */ -"removed profile picture" = "törölte a profilképét"; +"removed profile picture" = "eltávolította a profilképét"; /* rcv group event chat item */ -"removed you" = "eltávolítottak"; +"removed you" = "eltávolította önt"; /* No comment provided by engineer. */ "Renegotiate" = "Újraegyzetetés"; @@ -3617,7 +3648,7 @@ "Renegotiate encryption?" = "Titkosítás újraegyeztetése?"; /* No comment provided by engineer. */ -"Repeat connection request?" = "Kapcsolódási kérés megismétlése?"; +"Repeat connection request?" = "Kapcsolatkérés megismétlése?"; /* No comment provided by engineer. */ "Repeat download" = "Letöltés ismét"; @@ -3626,7 +3657,7 @@ "Repeat import" = "Importálás ismét"; /* No comment provided by engineer. */ -"Repeat join request?" = "Csatlakozási kérés megismétlése?"; +"Repeat join request?" = "Csatlakozáskérés megismétlése?"; /* No comment provided by engineer. */ "Repeat upload" = "Feltöltés ismét"; @@ -3729,10 +3760,10 @@ "Save archive" = "Archívum mentése"; /* No comment provided by engineer. */ -"Save group profile" = "Csoportprofil elmentése"; +"Save group profile" = "Csoportprofil mentése"; /* No comment provided by engineer. */ -"Save passphrase and open chat" = "Jelmondat elmentése és csevegés megnyitása"; +"Save passphrase and open chat" = "Jelmondat mentése és a csevegés megnyitása"; /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Jelmondat mentése a kulcstartóba"; @@ -3750,7 +3781,10 @@ "Save servers?" = "Kiszolgálók mentése?"; /* No comment provided by engineer. */ -"Save welcome message?" = "Üdvözlőszöveg mentése?"; +"Save welcome message?" = "Üdvözlőüzenet mentése?"; + +/* alert title */ +"Save your profile?" = "Profil mentése?"; /* No comment provided by engineer. */ "saved" = "mentett"; @@ -3798,10 +3832,10 @@ "Search" = "Keresés"; /* No comment provided by engineer. */ -"Search bar accepts invitation links." = "A keresősáv elfogadja a meghívó hivatkozásokat."; +"Search bar accepts invitation links." = "A keresősáv elfogadja a meghívó-hivatkozásokat."; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Keresés, vagy SimpleX hivatkozás beillesztése"; +"Search or paste SimpleX link" = "Keresés, vagy SimpleX-hivatkozás beillesztése"; /* network option */ "sec" = "mp"; @@ -3831,10 +3865,13 @@ "security code changed" = "a biztonsági kód megváltozott"; /* chat item action */ -"Select" = "Választás"; +"Select" = "Kijelölés"; /* No comment provided by engineer. */ -"Selected %lld" = "%lld kiválasztva"; +"Select chat profile" = "Csevegési profil kiválasztása"; + +/* No comment provided by engineer. */ +"Selected %lld" = "%lld kijelölve"; /* No comment provided by engineer. */ "Selected chat preferences prohibit this message." = "A kiválasztott csevegési beállítások tiltják ezt az üzenetet."; @@ -3897,7 +3934,7 @@ "Send questions and ideas" = "Ötletek és kérdések beküldése"; /* No comment provided by engineer. */ -"Send receipts" = "Üzenet kézbesítési jelentések"; +"Send receipts" = "Kézbesítési jelentések küldése"; /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Küldje el őket galériából vagy egyedi billentyűzetekről."; @@ -3905,11 +3942,11 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Az utolsó 100 üzenet elküldése az új tagoknak."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "A fájl küldője visszavonta az átvitelt."; /* No comment provided by engineer. */ -"Sender may have deleted the connection request." = "A küldő törölhette a kapcsolódási kérelmet."; +"Sender may have deleted the connection request." = "A küldő törölhette a kapcsolatkérést."; /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára."; @@ -3921,16 +3958,16 @@ "Sending file will be stopped." = "A fájl küldése leállt."; /* No comment provided by engineer. */ -"Sending receipts is disabled for %lld contacts" = "A kézbesítési jelentések küldése le van tiltva %lld ismerősnél"; +"Sending receipts is disabled for %lld contacts" = "A kézbesítési jelentések le vannak tiltva %lld ismerősnél"; /* No comment provided by engineer. */ -"Sending receipts is disabled for %lld groups" = "A kézbesítési jelentések küldése le van tiltva %lld csoportban"; +"Sending receipts is disabled for %lld groups" = "A kézbesítési jelentések le vannak tiltva %lld csoportban"; /* No comment provided by engineer. */ -"Sending receipts is enabled for %lld contacts" = "A kézbesítési jelentések küldése engedélyezve van %lld ismerős számára"; +"Sending receipts is enabled for %lld contacts" = "A kézbesítési jelentések engedélyezve vannak %lld ismerősnél"; /* No comment provided by engineer. */ -"Sending receipts is enabled for %lld groups" = "A kézbesítési jelentések küldése engedélyezve van %lld csoportban"; +"Sending receipts is enabled for %lld groups" = "A kézbesítési jelentések engedélyezve vannak %lld csoportban"; /* No comment provided by engineer. */ "Sending via" = "Küldés ezen keresztül"; @@ -3945,7 +3982,7 @@ "Sent directly" = "Közvetlenül küldött"; /* notification */ -"Sent file event" = "Elküldött fájl esemény"; +"Sent file event" = "Elküldött fájlesemény"; /* message info title */ "Sent message" = "Elküldött üzenet"; @@ -4046,6 +4083,9 @@ /* No comment provided by engineer. */ "Settings" = "Beállítások"; +/* alert message */ +"Settings were changed." = "A beállítások megváltoztak."; + /* No comment provided by engineer. */ "Shape profile images" = "Profilkép alakzat"; @@ -4053,7 +4093,7 @@ "Share" = "Megosztás"; /* No comment provided by engineer. */ -"Share 1-time link" = "Egyszer használatos hivatkozás megosztása"; +"Share 1-time link" = "Egyszer használható hivatkozás megosztása"; /* No comment provided by engineer. */ "Share address" = "Cím megosztása"; @@ -4068,7 +4108,10 @@ "Share link" = "Hivatkozás megosztása"; /* No comment provided by engineer. */ -"Share this 1-time invite link" = "Egyszer használatos meghívó hivatkozás megosztása"; +"Share profile" = "Profil megosztása"; + +/* No comment provided by engineer. */ +"Share this 1-time invite link" = "Egyszer használható meghívó-hivatkozás megosztása"; /* No comment provided by engineer. */ "Share to SimpleX" = "Megosztás a SimpleX-ben"; @@ -4107,10 +4150,10 @@ "SimpleX" = "SimpleX"; /* No comment provided by engineer. */ -"SimpleX address" = "SimpleX cím"; +"SimpleX address" = "SimpleX-cím"; /* No comment provided by engineer. */ -"SimpleX Address" = "SimpleX cím"; +"SimpleX Address" = "SimpleX-cím"; /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett auditálva."; @@ -4125,28 +4168,28 @@ "SimpleX group link" = "SimpleX csoporthivatkozás"; /* chat feature */ -"SimpleX links" = "SimpleX hivatkozások"; +"SimpleX links" = "SimpleX-hivatkozások"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "A SimpleX hivatkozások küldése le van tiltva ebben a csoportban."; +"SimpleX links are prohibited in this group." = "A SimpleX-hivatkozások küldése le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ -"SimpleX links not allowed" = "A SimpleX hivatkozások küldése le van tiltva"; +"SimpleX links not allowed" = "A SimpleX-hivatkozások küldése le van tiltva"; /* No comment provided by engineer. */ -"SimpleX Lock" = "SimpleX zár"; +"SimpleX Lock" = "SimpleX-zár"; /* No comment provided by engineer. */ "SimpleX Lock mode" = "Zárolási mód"; /* No comment provided by engineer. */ -"SimpleX Lock not enabled!" = "A SimpleX zár nincs bekapcsolva!"; +"SimpleX Lock not enabled!" = "A SimpleX-zár nincs bekapcsolva!"; /* No comment provided by engineer. */ -"SimpleX Lock turned on" = "SimpleX zár bekapcsolva"; +"SimpleX Lock turned on" = "SimpleX-zár bekapcsolva"; /* simplex link type */ -"SimpleX one-time invitation" = "SimpleX egyszer használatos meghívó"; +"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó"; /* No comment provided by engineer. */ "Simplified incognito mode" = "Egyszerűsített inkognító mód"; @@ -4169,6 +4212,9 @@ /* blur media */ "Soft" = "Enyhe"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Egyes alkalmazásbeállítások nem lettek átköltöztetve."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Néhány fájl nem került exportálásra:"; @@ -4224,10 +4270,10 @@ "Stop file" = "Fájl megállítása"; /* No comment provided by engineer. */ -"Stop receiving file?" = "Fájl fogadás megállítása?"; +"Stop receiving file?" = "Fájlfogadás megállítása?"; /* No comment provided by engineer. */ -"Stop sending file?" = "Fájl küldés megállítása?"; +"Stop sending file?" = "Fájlküldés megállítása?"; /* No comment provided by engineer. */ "Stop sharing" = "Megosztás megállítása"; @@ -4268,6 +4314,9 @@ /* No comment provided by engineer. */ "System authentication" = "Rendszerhitelesítés"; +/* No comment provided by engineer. */ +"Tail" = "Farok"; + /* No comment provided by engineer. */ "Take picture" = "Kép készítése"; @@ -4308,7 +4357,7 @@ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; /* No comment provided by engineer. */ -"Temporary file error" = "Ideiglenes fájlhiba"; +"Temporary file error" = "Ideiglenesfájl-hiba"; /* server test failure */ "Test failed at step %@." = "A teszt sikertelen volt a(z) %@ lépésnél."; @@ -4335,7 +4384,7 @@ "The 1st platform without any user identifiers – private by design." = "Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre."; /* No comment provided by engineer. */ -"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatfelvételi kéréseket kap – beállítások megnyitása az engedélyezéshez."; +"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – beállítások megnyitása az engedélyezéshez."; /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról (kivéve .onion) történő letöltések megerősítését."; @@ -4395,7 +4444,10 @@ "The servers for new connections of your current chat profile **%@**." = "Jelenlegi profil új ismerőseinek kiszolgálói **%@**."; /* No comment provided by engineer. */ -"The text you pasted is not a SimpleX link." = "A beillesztett szöveg nem egy SimpleX hivatkozás."; +"The text you pasted is not a SimpleX link." = "A beillesztett szöveg nem egy SimpleX-hivatkozás."; + +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "A feltöltött adatbázis-archívum véglegesen eltávolításra kerül a kiszolgálókról."; /* No comment provided by engineer. */ "Themes" = "Témák"; @@ -4407,7 +4459,7 @@ "They can be overridden in contact and group settings." = "Ezek felülbírálhatóak az ismerős- és csoportbeállításokban."; /* No comment provided by engineer. */ -"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak."; +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalmakkal együtt törlésre kerül. Az alacsony felbontású képek viszont megmaradnak."; /* No comment provided by engineer. */ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Ez a művelet nem vonható vissza - a kiválasztottnál korábban küldött és fogadott üzenetek törlésre kerülnek. Ez több percet is igénybe vehet."; @@ -4437,10 +4489,10 @@ "This group no longer exists." = "Ez a csoport már nem létezik."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Ez az ön egyszer használatos hivatkozása!"; +"This is your own one-time link!" = "Ez az ön egyszer használható hivatkozása!"; /* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Ez az ön SimpleX címe!"; +"This is your own SimpleX address!" = "Ez az ön SimpleX-címe!"; /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozást egy másik mobileszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén."; @@ -4470,7 +4522,7 @@ "To protect timezone, image/voice files use UTC." = "Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak."; /* No comment provided by engineer. */ -"To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "A biztonsága érdekében kapcsolja be a SimpleX zár funkciót.\nA funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén."; +"To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót.\nA funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén."; /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-cím védelmének érdekében a privát útválasztás az SMP kiszolgálókat használja az üzenetek kézbesítéséhez."; @@ -4491,7 +4543,7 @@ "Toggle chat list:" = "Csevegőlista átváltása:"; /* No comment provided by engineer. */ -"Toggle incognito when connecting." = "Inkognitó mód kapcsolódáskor."; +"Toggle incognito when connecting." = "Inkognitómód kapcsolódáskor."; /* No comment provided by engineer. */ "Toolbar opacity" = "Eszköztár átlátszatlansága"; @@ -4574,7 +4626,7 @@ /* No comment provided by engineer. */ "unknown servers" = "ismeretlen átjátszók"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Ismeretlen kiszolgálók!"; /* No comment provided by engineer. */ @@ -4689,7 +4741,7 @@ "Use server" = "Kiszolgáló használata"; /* No comment provided by engineer. */ -"Use SimpleX Chat servers?" = "SimpleX Chat kiszolgálók használata?"; +"Use SimpleX Chat servers?" = "SimpleX Chat-kiszolgálók használata?"; /* No comment provided by engineer. */ "Use the app while in the call." = "Használja az alkalmazást hívás közben."; @@ -4704,7 +4756,7 @@ "User selection" = "Felhasználó kiválasztása"; /* No comment provided by engineer. */ -"Using SimpleX Chat servers." = "SimpleX Chat kiszolgálók használatban."; +"Using SimpleX Chat servers." = "SimpleX Chat-kiszolgálók használatban."; /* No comment provided by engineer. */ "v%@" = "v%@"; @@ -4743,7 +4795,7 @@ "via group link" = "a csoporthivatkozáson keresztül"; /* chat list item description */ -"via one-time link" = "egy egyszer használatos hivatkozáson keresztül"; +"via one-time link" = "egyszer használható hivatkozáson keresztül"; /* No comment provided by engineer. */ "via relay" = "átjátszón keresztül"; @@ -4800,16 +4852,16 @@ "waiting for confirmation…" = "várakozás a visszaigazolásra…"; /* No comment provided by engineer. */ -"Waiting for desktop..." = "Várakozás az asztali kliensre..."; +"Waiting for desktop..." = "Várakozás az asztali kliensre…"; /* No comment provided by engineer. */ -"Waiting for file" = "Fájlra várakozás"; +"Waiting for file" = "Várakozás a fájlra"; /* No comment provided by engineer. */ -"Waiting for image" = "Képre várakozás"; +"Waiting for image" = "Várakozás a képre"; /* No comment provided by engineer. */ -"Waiting for video" = "Videóra várakozás"; +"Waiting for video" = "Várakozás a videóra"; /* No comment provided by engineer. */ "Wallpaper accent" = "Háttérkép kiemelés"; @@ -4836,10 +4888,10 @@ "Welcome %@!" = "Üdvözöllek %@!"; /* No comment provided by engineer. */ -"Welcome message" = "Üdvözlő üzenet"; +"Welcome message" = "Üdvözlőüzenet"; /* No comment provided by engineer. */ -"Welcome message is too long" = "Az üdvözlő üzenet túl hosszú"; +"Welcome message is too long" = "Az üdvözlőüzenet túl hosszú"; /* No comment provided by engineer. */ "What's new" = "Milyen újdonságok vannak"; @@ -4854,10 +4906,10 @@ "when IP hidden" = "ha az IP-cím rejtett"; /* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat."; +"When people request to connect, you can accept or reject it." = "Amikor az emberek kapcsolatot kérnek, ön elfogadhatja vagy elutasíthatja azokat."; /* No comment provided by engineer. */ -"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; +"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitó-profil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; /* No comment provided by engineer. */ "WiFi" = "Wi-Fi"; @@ -4869,19 +4921,19 @@ "Wired ethernet" = "Vezetékes Ethernet"; /* No comment provided by engineer. */ -"With encrypted files and media." = "Titkosított fájlokkal és médiatartalommal."; +"With encrypted files and media." = "Titkosított fájlokkal és médiatartalmakkal."; /* No comment provided by engineer. */ -"With optional welcome message." = "Opcionális üdvözlő üzenettel."; +"With optional welcome message." = "Opcionális üdvözlőüzenettel."; /* No comment provided by engineer. */ -"With reduced battery usage." = "Csökkentett akkumulátorhasználattal."; +"With reduced battery usage." = "Csökkentett akkumulátor-használattal."; /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nélkül az IP-címe látható lesz a fájlkiszolgálók számára."; -/* No comment provided by engineer. */ -"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nélkül az IP-címe látható lesz ezen XFTP átjátszók számára: %@."; +/* alert message */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "A megosztást az Adatvédelem és biztonság / SimpleX zár menüben engedélyezheti."; /* No comment provided by engineer. */ "Wrong database passphrase" = "Hibás adatbázis jelmondat"; @@ -4926,13 +4978,13 @@ "You are already connecting to %@." = "Már folyamatban van a kapcsolódás ehhez: %@."; /* No comment provided by engineer. */ -"You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használatos hivatkozáson keresztül!"; +"You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használható hivatkozáson keresztül!"; /* No comment provided by engineer. */ "You are already in group %@." = "Már a(z) %@ csoport tagja."; /* No comment provided by engineer. */ -"You are already joining the group %@." = "A csatlakozás már folyamatban van a(z) %@ csoporthoz."; +"You are already joining the group %@." = "A csatlakozás már folyamatban van a(z) %@ nevű csoporthoz."; /* No comment provided by engineer. */ "You are already joining the group via this link!" = "A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül!"; @@ -4941,7 +4993,7 @@ "You are already joining the group via this link." = "A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül."; /* No comment provided by engineer. */ -"You are already joining the group!\nRepeat join request?" = "Csatlakozás folyamatban!\nCsatlakozási kérés megismétlése?"; +"You are already joining the group!\nRepeat join request?" = "Csatlakozás folyamatban!\nCsatlakozáskérés megismétlése?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál."; @@ -5010,13 +5062,13 @@ "You can still view conversation with %@ in the list of chats." = "A(z) %@ nevű ismerősével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában."; /* No comment provided by engineer. */ -"You can turn on SimpleX Lock via Settings." = "A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be."; +"You can turn on SimpleX Lock via Settings." = "A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be."; /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Üzenetek formázása a szövegbe szúrt speciális karakterekkel:"; /* No comment provided by engineer. */ -"You can view invitation link again in connection details." = "A meghívó hivatkozást újra megtekintheti a kapcsolat részleteinél."; +"You can view invitation link again in connection details." = "A meghívó-hivatkozást újra megtekintheti a kapcsolat részleteinél."; /* No comment provided by engineer. */ "You can't send messages!" = "Nem lehet üzeneteket küldeni!"; @@ -5040,10 +5092,10 @@ "You could not be verified; please try again." = "Nem lehetett ellenőrizni; próbálja meg újra."; /* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Már kért egy kapcsolódási kérelmet ezen a címen keresztül!"; +"You have already requested connection via this address!" = "Már küldött egy kapcsolatkérést ezen a címen keresztül!"; /* No comment provided by engineer. */ -"You have already requested connection!\nRepeat connection request?" = "Már kért egy kapcsolódási kérelmet!\nKapcsolódási kérés megismétlése?"; +"You have already requested connection!\nRepeat connection request?" = "Már küldött egy kapcsolódási kérelmet!\nKapcsolatkérés megismétlése?"; /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra."; @@ -5076,19 +5128,19 @@ "You need to allow your contact to send voice messages to be able to send them." = "Hangüzeneteket küldéséhez engedélyeznie kell azok küldését az ismerősök számára."; /* No comment provided by engineer. */ -"You rejected group invitation" = "Csoport meghívó elutasítva"; +"You rejected group invitation" = "Csoportmeghívó elutasítva"; /* snd group event chat item */ "you removed %@" = "eltávolította őt: %@"; /* No comment provided by engineer. */ -"You sent group invitation" = "Csoport meghívó elküldve"; +"You sent group invitation" = "Csoportmeghívó elküldve"; /* chat list item description */ -"you shared one-time link" = "egyszer használatos hivatkozást osztott meg"; +"you shared one-time link" = "egyszer használható hivatkozást osztott meg"; /* chat list item description */ -"you shared one-time link incognito" = "egyszer használatos hivatkozást osztott meg inkognitóban"; +"you shared one-time link incognito" = "egyszer használható hivatkozást osztott meg inkognitóban"; /* snd group event chat item */ "you unblocked %@" = "ön feloldotta %@ letiltását"; @@ -5100,7 +5152,7 @@ "You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoporthivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; /* No comment provided by engineer. */ -"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolódási kérelme elfogadásra kerül, várjon, vagy ellenőrizze később!"; +"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolatkérése elfogadásra kerül, várjon, vagy ellenőrizze később!"; /* No comment provided by engineer. */ "You will be connected when your contact's device is online, please wait or check later!" = "Akkor le kapcsolódva, amikor az ismerőse eszköze online lesz, várjon, vagy ellenőrizze később!"; @@ -5141,9 +5193,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "A csevegési adatbázis nincs titkosítva – adjon meg egy jelmondatot a titkosításhoz."; +/* alert title */ +"Your chat preferences" = "Csevegési beállítások"; + /* No comment provided by engineer. */ "Your chat profiles" = "Csevegési profilok"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "A kapcsolata át lett helyezve ide: %@, de egy váratlan hiba történt a profilra való átirányítás közben."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ismerőse olyan fájlt küldött, amely meghaladja a jelenleg támogatott maximális méretet (%@)."; @@ -5175,7 +5233,10 @@ "Your profile **%@** will be shared." = "A(z) **%@** nevű profilja megosztásra fog kerülni."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra. A SimpleX kiszolgálók nem látjhatják profilját."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profilja az eszközön van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX-kiszolgálók nem láthatják a profilját."; + +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja megváltozott. Ha elmenti, a frissített profil elküldésre kerül az összes ismerősének."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profilja, ismerősei és az elküldött üzenetei az eszközön kerülnek tárolásra."; @@ -5193,7 +5254,7 @@ "Your settings" = "Beállítások"; /* No comment provided by engineer. */ -"Your SimpleX address" = "Profil SimpleX címe"; +"Your SimpleX address" = "Profil SimpleX-címe"; /* No comment provided by engineer. */ "Your SMP servers" = "SMP kiszolgálók"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index c60c1537a6..27e7ab5fc5 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ si vuole connettere!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ e %lld membri"; @@ -649,6 +652,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Auto-accetta immagini"; +/* alert title */ +"Auto-accept settings" = "Accetta automaticamente le impostazioni"; + /* No comment provided by engineer. */ "Back" = "Indietro"; @@ -796,7 +802,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Impossibile inoltrare il messaggio"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Impossibile ricevere il file"; /* snd error text */ @@ -890,6 +896,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Preferenze della chat"; +/* alert message */ +"Chat preferences were changed." = "Le preferenze della chat sono state cambiate."; + /* No comment provided by engineer. */ "Chat theme" = "Tema della chat"; @@ -1178,6 +1187,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Versione core: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Angolo"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Correggere il nome a %@?"; @@ -1629,7 +1641,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Esegui downgrade e apri chat"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Scarica"; /* No comment provided by engineer. */ @@ -1857,12 +1870,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Errore nella modifica dell'indirizzo"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Errore nel cambio di profilo di connessione"; + /* No comment provided by engineer. */ "Error changing role" = "Errore nel cambio di ruolo"; /* No comment provided by engineer. */ "Error changing setting" = "Errore nella modifica dell'impostazione"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Errore nel passaggio a incognito!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Errore di connessione al server di inoltro %@. Riprova più tardi."; @@ -1936,9 +1955,12 @@ "Error loading %@ servers" = "Errore nel caricamento dei server %@"; /* No comment provided by engineer. */ -"Error opening chat" = "Errore di apertura della chat"; +"Error migrating settings" = "Errore nella migrazione delle impostazioni"; /* No comment provided by engineer. */ +"Error opening chat" = "Errore di apertura della chat"; + +/* alert title */ "Error receiving file" = "Errore nella ricezione del file"; /* No comment provided by engineer. */ @@ -1995,6 +2017,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Errore nell'interruzione della chat"; +/* No comment provided by engineer. */ +"Error switching profile" = "Errore nel cambio di profilo"; + /* No comment provided by engineer. */ "Error switching profile!" = "Errore nel cambio di profilo!"; @@ -2815,6 +2840,9 @@ /* No comment provided by engineer. */ "Message servers" = "Server dei messaggi"; +/* No comment provided by engineer. */ +"Message shape" = "Forma del messaggio"; + /* No comment provided by engineer. */ "Message source remains private." = "La fonte del messaggio resta privata."; @@ -3081,7 +3109,7 @@ /* feature offered item */ "offered %@: %@" = "offerto %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3580,6 +3608,9 @@ /* No comment provided by engineer. */ "Remove" = "Rimuovi"; +/* No comment provided by engineer. */ +"Remove archive?" = "Rimuovere l'archivio?"; + /* No comment provided by engineer. */ "Remove image" = "Rimuovi immagine"; @@ -3752,6 +3783,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Salvare il messaggio di benvenuto?"; +/* alert title */ +"Save your profile?" = "Salvare il profilo?"; + /* No comment provided by engineer. */ "saved" = "salvato"; @@ -3833,6 +3867,9 @@ /* chat item action */ "Select" = "Seleziona"; +/* No comment provided by engineer. */ +"Select chat profile" = "Seleziona il profilo di chat"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld selezionato"; @@ -3905,7 +3942,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Invia fino a 100 ultimi messaggi ai nuovi membri."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Il mittente ha annullato il trasferimento del file."; /* No comment provided by engineer. */ @@ -4046,6 +4083,9 @@ /* No comment provided by engineer. */ "Settings" = "Impostazioni"; +/* alert message */ +"Settings were changed." = "Le impostazioni sono state cambiate."; + /* No comment provided by engineer. */ "Shape profile images" = "Forma delle immagini del profilo"; @@ -4067,6 +4107,9 @@ /* No comment provided by engineer. */ "Share link" = "Condividi link"; +/* No comment provided by engineer. */ +"Share profile" = "Condividi il profilo"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Condividi questo link di invito una tantum"; @@ -4169,6 +4212,9 @@ /* blur media */ "Soft" = "Leggera"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Alcune impostazioni dell'app non sono state migrate."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Alcuni file non sono stati esportati:"; @@ -4268,6 +4314,9 @@ /* No comment provided by engineer. */ "System authentication" = "Autenticazione di sistema"; +/* No comment provided by engineer. */ +"Tail" = "Coda"; + /* No comment provided by engineer. */ "Take picture" = "Scatta foto"; @@ -4397,6 +4446,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Il testo che hai incollato non è un link SimpleX."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "L'archivio del database caricato verrà rimosso definitivamente dai server."; + /* No comment provided by engineer. */ "Themes" = "Temi"; @@ -4574,7 +4626,7 @@ /* No comment provided by engineer. */ "unknown servers" = "relay sconosciuti"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Server sconosciuti!"; /* No comment provided by engineer. */ @@ -4880,7 +4932,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Senza Tor o VPN, il tuo indirizzo IP sarà visibile ai server di file."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Senza Tor o VPN, il tuo indirizzo IP sarà visibile a questi relay XFTP: %@."; /* No comment provided by engineer. */ @@ -5141,9 +5193,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Il tuo database della chat non è crittografato: imposta la password per crittografarlo."; +/* alert title */ +"Your chat preferences" = "Le tue preferenze della chat"; + /* No comment provided by engineer. */ "Your chat profiles" = "I tuoi profili di chat"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "La tua connessione è stata spostata a %@, ma si è verificato un errore imprevisto durante il reindirizzamento al profilo."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Il tuo contatto ha inviato un file più grande della dimensione massima attualmente supportata (%@)."; @@ -5177,6 +5235,9 @@ /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 8a6c48532c..8420879ce4 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -628,7 +628,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "データベースのパスワードを保存するためのキーチェーンにアクセスできません"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "ファイル受信ができません"; /* No comment provided by engineer. */ @@ -1431,7 +1431,7 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "%@ サーバーのロード中にエラーが発生"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "ファイル受信にエラー発生"; /* No comment provided by engineer. */ @@ -2235,7 +2235,7 @@ /* feature offered item */ "offered %@: %@" = "提供された %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "OK"; /* No comment provided by engineer. */ @@ -2771,7 +2771,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "ギャラリーまたはカスタム キーボードから送信します。"; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "送信者がファイル転送をキャンセルしました。"; /* No comment provided by engineer. */ diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 6c566fa7da..af66433f46 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ wil verbinding maken!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ en %lld leden"; @@ -308,7 +311,7 @@ "A new random profile will be shared." = "Een nieuw willekeurig profiel wordt gedeeld."; /* No comment provided by engineer. */ -"A separate TCP connection will be used **for each chat profile you have in the app**." = "Er wordt een aparte TCP-verbinding gebruikt **voor elk chat profiel dat je in de app hebt**."; +"A separate TCP connection will be used **for each chat profile you have in the app**." = "Er wordt een aparte TCP-verbinding gebruikt **voor elk chatprofiel dat je in de app hebt**."; /* No comment provided by engineer. */ "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." = "Er wordt een aparte TCP-verbinding gebruikt **voor elk contact en groepslid**.\n**Let op**: als u veel verbindingen heeft, kan uw batterij- en verkeersverbruik aanzienlijk hoger zijn en kunnen sommige verbindingen uitvallen."; @@ -649,6 +652,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Afbeeldingen automatisch accepteren"; +/* alert title */ +"Auto-accept settings" = "Instellingen automatisch accepteren"; + /* No comment provided by engineer. */ "Back" = "Terug"; @@ -740,7 +746,7 @@ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgaars, Fins, Thais en Oekraïens - dankzij de gebruikers en [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; /* No comment provided by engineer. */ -"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Via chat profiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; /* No comment provided by engineer. */ "call" = "bellen"; @@ -796,7 +802,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Kan bericht niet doorsturen"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Kan bestand niet ontvangen"; /* snd error text */ @@ -890,11 +896,14 @@ /* No comment provided by engineer. */ "Chat preferences" = "Gesprek voorkeuren"; +/* alert message */ +"Chat preferences were changed." = "Chatvoorkeuren zijn gewijzigd."; + /* No comment provided by engineer. */ "Chat theme" = "Chat thema"; /* No comment provided by engineer. */ -"Chats" = "Gesprekken"; +"Chats" = "Chats"; /* No comment provided by engineer. */ "Check server address and try again." = "Controleer het server adres en probeer het opnieuw."; @@ -1178,6 +1187,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Core versie: v% @"; +/* No comment provided by engineer. */ +"Corner" = "Hoek"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Juiste naam voor %@?"; @@ -1308,7 +1320,7 @@ "Database passphrase is different from saved in the keychain." = "Het wachtwoord van de database verschilt van het wachtwoord dat is opgeslagen in de keychain."; /* No comment provided by engineer. */ -"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je gesprekken te openen."; +"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je chats te openen."; /* No comment provided by engineer. */ "Database upgrade" = "Database upgrade"; @@ -1381,10 +1393,10 @@ "Delete chat archive?" = "Chat archief verwijderen?"; /* No comment provided by engineer. */ -"Delete chat profile" = "Chat profiel verwijderen"; +"Delete chat profile" = "Chatprofiel verwijderen"; /* No comment provided by engineer. */ -"Delete chat profile?" = "Chat profiel verwijderen?"; +"Delete chat profile?" = "Chatprofiel verwijderen?"; /* No comment provided by engineer. */ "Delete connection" = "Verbinding verwijderen"; @@ -1408,7 +1420,7 @@ "Delete files and media?" = "Bestanden en media verwijderen?"; /* No comment provided by engineer. */ -"Delete files for all chat profiles" = "Verwijder bestanden voor alle chat profielen"; +"Delete files for all chat profiles" = "Verwijder bestanden voor alle chatprofielen"; /* chat feature */ "Delete for everyone" = "Verwijderen voor iedereen"; @@ -1629,7 +1641,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Downgraden en chat openen"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Downloaden"; /* No comment provided by engineer. */ @@ -1857,12 +1870,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Fout bij wijzigen van adres"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Fout bij wijzigen van verbindingsprofiel"; + /* No comment provided by engineer. */ "Error changing role" = "Fout bij wisselen van rol"; /* No comment provided by engineer. */ "Error changing setting" = "Fout bij wijzigen van instelling"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Fout bij het overschakelen naar incognito!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Fout bij het verbinden met doorstuurserver %@. Probeer het later opnieuw."; @@ -1936,9 +1955,12 @@ "Error loading %@ servers" = "Fout bij het laden van %@ servers"; /* No comment provided by engineer. */ -"Error opening chat" = "Fout bij het openen van de chat"; +"Error migrating settings" = "Fout bij migreren van instellingen"; /* No comment provided by engineer. */ +"Error opening chat" = "Fout bij het openen van de chat"; + +/* alert title */ "Error receiving file" = "Fout bij ontvangen van bestand"; /* No comment provided by engineer. */ @@ -1995,6 +2017,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Fout bij het stoppen van de chat"; +/* No comment provided by engineer. */ +"Error switching profile" = "Fout bij wisselen van profiel"; + /* No comment provided by engineer. */ "Error switching profile!" = "Fout bij wisselen van profiel!"; @@ -2138,7 +2163,7 @@ "Finally, we have them! 🚀" = "Eindelijk, we hebben ze! 🚀"; /* No comment provided by engineer. */ -"Find chats faster" = "Vind gesprekken sneller"; +"Find chats faster" = "Vind chats sneller"; /* No comment provided by engineer. */ "Fix" = "Herstel"; @@ -2312,7 +2337,7 @@ "Hidden" = "Verborgen"; /* No comment provided by engineer. */ -"Hidden chat profiles" = "Verborgen chat profielen"; +"Hidden chat profiles" = "Verborgen chatprofielen"; /* No comment provided by engineer. */ "Hidden profile password" = "Verborgen profiel wachtwoord"; @@ -2573,7 +2598,7 @@ "Irreversible message deletion is prohibited in this group." = "Het onomkeerbaar verwijderen van berichten is verboden in deze groep."; /* No comment provided by engineer. */ -"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel."; +"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel."; /* No comment provided by engineer. */ "It can happen when you or your connection used the old database backup." = "Het kan gebeuren wanneer u of de ander een oude database back-up gebruikt."; @@ -2815,6 +2840,9 @@ /* No comment provided by engineer. */ "Message servers" = "Berichtservers"; +/* No comment provided by engineer. */ +"Message shape" = "Berichtvorm"; + /* No comment provided by engineer. */ "Message source remains private." = "Berichtbron blijft privé."; @@ -2921,7 +2949,7 @@ "Most likely this connection is deleted." = "Hoogstwaarschijnlijk is deze verbinding verwijderd."; /* No comment provided by engineer. */ -"Multiple chat profiles" = "Meerdere chat profielen"; +"Multiple chat profiles" = "Meerdere chatprofielen"; /* No comment provided by engineer. */ "mute" = "dempen"; @@ -3026,7 +3054,7 @@ "no e2e encryption" = "geen e2e versleuteling"; /* No comment provided by engineer. */ -"No filtered chats" = "Geen gefilterde gesprekken"; +"No filtered chats" = "Geen gefilterde chats"; /* No comment provided by engineer. */ "No group!" = "Groep niet gevonden!"; @@ -3081,7 +3109,7 @@ /* feature offered item */ "offered %@: %@" = "voorgesteld %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "OK"; /* No comment provided by engineer. */ @@ -3271,7 +3299,7 @@ "PING interval" = "PING interval"; /* No comment provided by engineer. */ -"Play from the chat list." = "Afspelen via de gesprekken lijst."; +"Play from the chat list." = "Afspelen via de chat lijst."; /* No comment provided by engineer. */ "Please ask your contact to enable calls." = "Vraag uw contactpersoon om oproepen in te schakelen."; @@ -3316,7 +3344,7 @@ "Please restart the app and migrate the database to enable push notifications." = "Start de app opnieuw en migreer de database om push meldingen in te schakelen."; /* No comment provided by engineer. */ -"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken."; +"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de chats."; /* No comment provided by engineer. */ "Please store passphrase securely, you will NOT be able to change it if you lose it." = "Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u het kwijtraakt."; @@ -3418,7 +3446,7 @@ "Protect IP address" = "Bescherm het IP-adres"; /* No comment provided by engineer. */ -"Protect your chat profiles with a password!" = "Bescherm je chat profielen met een wachtwoord!"; +"Protect your chat profiles with a password!" = "Bescherm je chatprofielen met een wachtwoord!"; /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Bescherm uw IP-adres tegen de berichtenrelais die door uw contacten zijn gekozen.\nSchakel dit in in *Netwerk en servers*-instellingen."; @@ -3580,6 +3608,9 @@ /* No comment provided by engineer. */ "Remove" = "Verwijderen"; +/* No comment provided by engineer. */ +"Remove archive?" = "Archief verwijderen?"; + /* No comment provided by engineer. */ "Remove image" = "Verwijder afbeelding"; @@ -3662,7 +3693,7 @@ "Reset to user theme" = "Terugzetten naar gebruikersthema"; /* No comment provided by engineer. */ -"Restart the app to create a new chat profile" = "Start de app opnieuw om een nieuw chat profiel aan te maken"; +"Restart the app to create a new chat profile" = "Start de app opnieuw om een nieuw chatprofiel aan te maken"; /* No comment provided by engineer. */ "Restart the app to use imported chat database" = "Start de app opnieuw om de geïmporteerde chat database te gebruiken"; @@ -3732,7 +3763,7 @@ "Save group profile" = "Groep profiel opslaan"; /* No comment provided by engineer. */ -"Save passphrase and open chat" = "Bewaar het wachtwoord en open je gesprekken"; +"Save passphrase and open chat" = "Bewaar het wachtwoord en open je chats"; /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Sla het wachtwoord op in de Keychain"; @@ -3744,7 +3775,7 @@ "Save profile password" = "Bewaar profiel wachtwoord"; /* No comment provided by engineer. */ -"Save servers" = "Bewaar servers"; +"Save servers" = "Servers opslaan"; /* No comment provided by engineer. */ "Save servers?" = "Servers opslaan?"; @@ -3752,6 +3783,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Welkom bericht opslaan?"; +/* alert title */ +"Save your profile?" = "Uw profiel opslaan?"; + /* No comment provided by engineer. */ "saved" = "opgeslagen"; @@ -3833,6 +3867,9 @@ /* chat item action */ "Select" = "Selecteer"; +/* No comment provided by engineer. */ +"Select chat profile" = "Selecteer chatprofiel"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld geselecteerd"; @@ -3905,7 +3942,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Stuur tot 100 laatste berichten naar nieuwe leden."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Afzender heeft bestandsoverdracht geannuleerd."; /* No comment provided by engineer. */ @@ -4046,6 +4083,9 @@ /* No comment provided by engineer. */ "Settings" = "Instellingen"; +/* alert message */ +"Settings were changed." = "Instellingen zijn gewijzigd."; + /* No comment provided by engineer. */ "Shape profile images" = "Vorm profiel afbeeldingen"; @@ -4067,6 +4107,9 @@ /* No comment provided by engineer. */ "Share link" = "Deel link"; +/* No comment provided by engineer. */ +"Share profile" = "Profiel delen"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Deel deze eenmalige uitnodigingslink"; @@ -4169,6 +4212,9 @@ /* blur media */ "Soft" = "Soft"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Sommige app-instellingen zijn niet gemigreerd."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Sommige bestanden zijn niet geëxporteerd:"; @@ -4392,11 +4438,14 @@ "The sender will NOT be notified" = "De afzender wordt NIET op de hoogte gebracht"; /* No comment provided by engineer. */ -"The servers for new connections of your current chat profile **%@**." = "De servers voor nieuwe verbindingen van uw huidige chat profiel **%@**."; +"The servers for new connections of your current chat profile **%@**." = "De servers voor nieuwe verbindingen van uw huidige chatprofiel **%@**."; /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "De tekst die u hebt geplakt is geen SimpleX link."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Het geüploade databasearchief wordt permanent van de servers verwijderd."; + /* No comment provided by engineer. */ "Themes" = "Thema's"; @@ -4446,7 +4495,7 @@ "This link was used with another mobile device, please create a new link on the desktop." = "Deze link is gebruikt met een ander mobiel apparaat. Maak een nieuwe link op de desktop."; /* No comment provided by engineer. */ -"This setting applies to messages in your current chat profile **%@**." = "Deze instelling is van toepassing op berichten in je huidige chat profiel **%@**."; +"This setting applies to messages in your current chat profile **%@**." = "Deze instelling is van toepassing op berichten in je huidige chatprofiel **%@**."; /* No comment provided by engineer. */ "Title" = "Titel"; @@ -4479,7 +4528,7 @@ "To record voice message please grant permission to use Microphone." = "Geef toestemming om de microfoon te gebruiken om een spraakbericht op te nemen."; /* No comment provided by engineer. */ -"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chat profielen**."; +"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chatprofielen**."; /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Om directe push meldingen te ondersteunen, moet de chat database worden gemigreerd."; @@ -4551,7 +4600,7 @@ "Unhide" = "zichtbaar maken"; /* No comment provided by engineer. */ -"Unhide chat profile" = "Chat profiel zichtbaar maken"; +"Unhide chat profile" = "Chatprofiel zichtbaar maken"; /* No comment provided by engineer. */ "Unhide profile" = "Profiel zichtbaar maken"; @@ -4574,7 +4623,7 @@ /* No comment provided by engineer. */ "unknown servers" = "onbekende relays"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Onbekende servers!"; /* No comment provided by engineer. */ @@ -4880,7 +4929,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Zonder Tor of VPN is uw IP-adres zichtbaar voor bestandsservers."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Zonder Tor of VPN zal uw IP-adres zichtbaar zijn voor deze XFTP-relays: %@."; /* No comment provided by engineer. */ @@ -4917,7 +4966,7 @@ "You allow" = "Jij staat toe"; /* No comment provided by engineer. */ -"You already have a chat profile with the same display name. Please choose another name." = "Je hebt al een chat profiel met dezelfde weergave naam. Kies een andere naam."; +"You already have a chat profile with the same display name. Please choose another name." = "Je hebt al een chatprofiel met dezelfde weergave naam. Kies een andere naam."; /* No comment provided by engineer. */ "You are already connected to %@." = "U bent al verbonden met %@."; @@ -5141,9 +5190,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Uw chat database is niet versleuteld, stel een wachtwoord in om deze te versleutelen."; +/* alert title */ +"Your chat preferences" = "Uw chat voorkeuren"; + /* No comment provided by engineer. */ "Your chat profiles" = "Uw chat profielen"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Uw verbinding is verplaatst naar %@, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Uw contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%@)."; @@ -5177,6 +5232,9 @@ /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 7def1c7e02..11e6227596 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Nie można przekazać wiadomości"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Nie można odebrać pliku"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Obniż wersję i otwórz czat"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Pobierz"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Błąd otwierania czatu"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Błąd odbioru pliku"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "zaoferował %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Wysyłaj do 100 ostatnich wiadomości do nowych członków."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Nadawca anulował transfer pliku."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "nieznane przekaźniki"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Nieznane serwery!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Bez Tor lub VPN, Twój adres IP będzie widoczny do serwerów plików."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Bez Tor lub VPN, Twój adres IP będzie widoczny dla tych przekaźników XFTP: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 038041d0e6..ec024ed12a 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Невозможно переслать сообщение"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Невозможно получить файл"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Откатить версию и открыть чат"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Загрузить"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Ошибка доступа к чату"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Ошибка при получении файла"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "предложил(a) %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ок"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Отправить до 100 последних сообщений новым членам."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Отправитель отменил передачу файла."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "неизвестные серверы"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Неизвестные серверы!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Без Тора или ВПН, Ваш IP адрес будет доступен серверам файлов."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 994a10c9db..8813113fce 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -532,7 +532,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "ไม่สามารถเข้าถึง keychain เพื่อบันทึกรหัสผ่านฐานข้อมูล"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "ไม่สามารถรับไฟล์ได้"; /* No comment provided by engineer. */ @@ -1308,7 +1308,7 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "โหลดเซิร์ฟเวอร์ %@ ผิดพลาด"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "เกิดข้อผิดพลาดในการรับไฟล์"; /* No comment provided by engineer. */ @@ -2097,7 +2097,7 @@ /* feature offered item */ "offered %@: %@" = "เสนอแล้ว %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "ตกลง"; /* No comment provided by engineer. */ @@ -2630,7 +2630,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "ส่งจากแกลเลอรีหรือแป้นพิมพ์แบบกำหนดเอง"; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "ผู้ส่งยกเลิกการโอนไฟล์"; /* No comment provided by engineer. */ diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index f39140340b..387d72d0cf 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -715,7 +715,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Veritabanı şifresini kaydetmek için Anahtar Zinciri'ne erişilemiyor"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Dosya alınamıyor"; /* snd error text */ @@ -1419,7 +1419,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Sürüm düşür ve sohbeti aç"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "İndir"; /* No comment provided by engineer. */ @@ -1707,7 +1708,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Sohbeti açarken sorun oluştu"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Dosya alınırken sorun oluştu"; /* No comment provided by engineer. */ @@ -2727,7 +2728,7 @@ /* feature offered item */ "offered %@: %@" = "%1$@: %2$@ teklif etti"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Tamam"; /* No comment provided by engineer. */ @@ -3431,7 +3432,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Yeni üyelere 100 adete kadar son mesajları gönderin."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Gönderici dosya gönderimini iptal etti."; /* No comment provided by engineer. */ @@ -3986,7 +3987,7 @@ /* No comment provided by engineer. */ "unknown servers" = "bilinmeyen yönlendiriciler"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Bilinmeyen sunucular!"; /* No comment provided by engineer. */ @@ -4262,7 +4263,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Tor veya VPN olmadan, IP adresiniz dosya sunucularına görülebilir."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor veya VPN olmadan, IP adresiniz bu XFTP aktarıcıları tarafından görülebilir: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 4218065a2e..478f03860c 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Неможливо переслати повідомлення"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Не вдається отримати файл"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Пониження та відкритий чат"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Завантажити"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Помилка відкриття чату"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Помилка отримання файлу"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "запропонував %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Гаразд"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Надішліть до 100 останніх повідомлень новим користувачам."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Відправник скасував передачу файлу."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "невідомі реле"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Невідомі сервери!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Без Tor або VPN ваша IP-адреса буде видимою для файлових серверів."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Без Tor або VPN ваша IP-адреса буде видимою для цих XFTP-ретрансляторів: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 0f78665b83..ca2d0b5134 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "无法转发消息"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "无法接收文件"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "降级并打开聊天"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "下载"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "打开聊天时出错"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "接收文件错误"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "已提供 %1$@:%2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "好的"; /* No comment provided by engineer. */ @@ -3520,7 +3521,7 @@ "Receiving via" = "接收通过"; /* No comment provided by engineer. */ -"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "最近的历史记录和改进的 [目录机器人](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; +"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "最近的历史记录和改进的 [目录机器人](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; /* No comment provided by engineer. */ "Recipient(s) can't see who this message is from." = "收件人看不到这条消息来自何人。"; @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "给新成员发送最多 100 条历史消息。"; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "发送人已取消文件传输。"; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "未知服务器"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "未知服务器!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "如果没有 Tor 或 VPN,您的 IP 地址将对文件服务器可见。"; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "如果没有 Tor 或 VPN,您的 IP 地址将对以下 XFTP 中继可见:%@。"; /* No comment provided by engineer. */ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 8f4b0158a7..405cc945cc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -31,7 +31,7 @@ أضِف خوادم محدّدة مسبقًا أضِف إلى جهاز آخر سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! - الوصول إلى الخوادم عبر بروكسي SOCKS على المنفذ %d؟ يجب بدء تشغيل الوكيل قبل تمكين هذا الخيار. + الوصول إلى الخوادم عبر وكيل SOCKS على المنفذ %d؟ يجب بدء تشغيل الوكيل قبل تمكين هذا الخيار. أضِف خادم إعدادات الشبكة المتقدمة سيبقى جميع أعضاء المجموعة على اتصال. @@ -50,7 +50,7 @@ السماح بالمكالمات فقط إذا سمحت جهة اتصالك بذلك. اسمح بردود الفعل على الرسائل فقط إذا سمحت جهة اتصالك بذلك. يتم استخدام Android Keystore لتخزين عبارة المرور بشكل آمن - فهو يسمح لخدمة الإشعارات بالعمل. - يتم إنشاء ملف تعريف دردشة فارغ بالاسم المقدم ، ويفتح التطبيق كالمعتاد. + يتم إنشاء ملف تعريف دردشة فارغ بالاسم المقدم، ويفتح التطبيق كالمعتاد. أجب الاتصال دائِماً السماح بإرسال رسائل تختفي. @@ -141,7 +141,7 @@ لا يمكن تهيئة قاعدة البيانات إرفاق طلب لاستلام الصورة - نسخة التطبيق: v%s + إصدار التطبيق: v%s قبول تلقائي المكالمات لا يمكن دعوة جهات الاتصال! @@ -1768,9 +1768,9 @@ تم تجاوز السعة - لم يتلق المُستلم الرسائل المُرسلة مسبقًا. خطأ في خادم الوجهة: %1$s خطأ: %1$s - خادم إعادة التوجيه: %1$s + خادم التحويل: %1$s \nخطأ في الخادم الوجهة: %2$s - خادم إعادة التوجيه: %1$s + خادم التحويل: %1$s \nخطأ: %2$s تحذير تسليم الرسالة مشكلات الشبكة - انتهت صلاحية الرسالة بعد عِدة محاولات لإرسالها. @@ -2067,4 +2067,27 @@ صفّر كافة التلميحات يُرجى التأكد من أن رابط SimpleX صحيح. الرابط غير صالح + %1$d خطأ في الملف: +\n%2$s + فشل تنزيل %1$d ملف/ات. + لم يتم تنزيل %1$d ملف/ات. + نزّل + شارك ملف التعريف + استخدم بيانات اعتماد الوكيل المختلفة لكل اتصال. + اسم المستخدم + قد يتم إرسال بيانات الاعتماد الخاصة بك غير مُعمَّاة. + خطأ في حفظ الوكيل + إزالة الأرشيف؟ + وضع النظام + سيتم إزالة أرشيف قاعدة البيانات المرفوعة نهائيًا من الخوادم. + استخدم بيانات اعتماد الوكيل المختلفة لكل ملف تعريف. + استخدم بيانات اعتماد عشوائية + قاعدة بيانات الدردشة + حُذف %1$d ملف/ات. + لا يزال يتم تنزيل %1$d ملفًا. + لا تستخدم بيانات الاعتماد مع الوكيل. + خطأ في تحويل الرسائل + خطأ في تبديل الملف الشخصي + حدد ملف تعريف الدردشة + لقد تم نقل اتصالك إلى %s ولكن حدث خطأ غير متوقع أثناء إعادة توجيهك إلى الملف الشخصي. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index e7b327c4ff..8aa9c97dae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -1061,7 +1061,7 @@ Bluetooth-Unterstützung und weitere Verbesserungen. Administratoren können nun \n- Nachrichten von Gruppenmitgliedern löschen -\n- Gruppenmitglieder deaktivieren („Beobachter“-Rolle) +\n- Gruppenmitglieder deaktivieren (Beobachter-Rolle) Gruppen-Begrüßungsmeldung Weiter reduzierter Batterieverbrauch Weitere Verbesserungen sind bald verfügbar! @@ -2151,4 +2151,23 @@ Neue Nachricht Bitte überprüfen Sie, ob der SimpleX-Link korrekt ist. Ungültiger Link + CHAT-DATENBANK + Fehler beim Wechseln des Profils + Die Nachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden! + Profil teilen + System-Modus + Das hochgeladene Datenbank-Archiv wird dauerhaft von den Servern entfernt. + Chat-Profil auswählen + Archiv entfernen? + Ihre Anmeldeinformationen können unverschlüsselt versendet werden. + Verwenden Sie keine Anmeldeinformationen mit einem Proxy. + Ihre Verbindung wurde auf %s verschoben, aber während der Weiterleitung auf das Profil trat ein unerwarteter Fehler auf. + Stellen Sie sicher, dass die Proxy-Konfiguration richtig ist. + Fehler beim Speichern des Proxys + Passwort + Proxy-Authentifizierung + Verwenden Sie für jede Verbindung unterschiedliche Proxy-Anmeldeinformationen. + Verwenden Sie für jedes Profil unterschiedliche Proxy-Anmeldeinformationen. + Verwenden Sie zufällige Anmeldeinformationen + Benutzername \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 3ec6bc021b..2ee1c89791 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -9,11 +9,11 @@ 6 új kezelőfelületi nyelv 5 perc 1 perc - A SimpleX címről + A SimpleX-címről Címváltoztatás megszakítása? Megszakítás 30 másodperc - Egyszer használatos hivatkozás + Egyszer használható hivatkozás %1$s szeretne kapcsolatba lépni önnel ezen keresztül: A SimpleX Chatről 1 nap @@ -26,7 +26,7 @@ Elfogadás gombra fent, majd: Elfogadás inkognítóban - Ismerőskérelem elfogadása? + Kapcsolatkérés elfogadása? Elfogadás Elfogadás Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősök számára. @@ -38,23 +38,23 @@ %s visszavonva Előre beállított kiszolgálók hozzáadása A hívások kezdeményezése le van tiltva ebben a csevegésben. - Külön TCP kapcsolat (és SOCKS bejelentkezési adatok) lesz használva minden ismerős és csoporttag számára. \u0020 -\nFigyelem: ha sok ismerőse van, az akkumulátor- és az adathasználat jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. + Minden egyes kapcsolathoz és csoporttaghoz külön TCP-kapcsolat (és SOCKS hitelesítő adat) lesz használva. +\nFigyelem: ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. hivatkozás előnézetének visszavonása - az alkalmazásban minden csevegési profiljához .]]> + Minden egyes kapcsolathoz és csoporttaghoz külön TCP-kapcsolat (és SOCKS hitelesítő adat) lesz használva.]]> Mindkét fél küldhet eltűnő üzeneteket. Az Android Keystore-t a jelmondat biztonságos tárolására használják - lehetővé teszi az értesítési szolgáltatás működését. Hibás az üzenet hasító értéke Háttér - Tudnivaló: az üzenet- és fájl átjátszók SOCKS proxy által vannak kapcsolatban. A hívások és URL hivatkozás előnézetek közvetlen kapcsolatot használnak.]]> + Figyelem: az üzenet- és fájl átjátszók SOCKS proxy által vannak kapcsolatban. A hívások és URL hivatkozás előnézetek közvetlen kapcsolatot használnak.]]> Alkalmazásadatok biztonsági mentése Az adatbázis inicializálása sikertelen Ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél. A csevegési profil által (alap beállítás), vagy a kapcsolat által (BÉTA). Egy új véletlenszerű profil lesz megosztva. - A hangüzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + A hangüzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Az alkalmazás build száma: %s - Hang-/videóhívások + Hang- és videóhívások Speciális hálózati beállítások A hangüzenetek küldése engedélyezve van az ismerősei számára. Hang- és videóhívások @@ -65,8 +65,8 @@ Nem lehet fogadni a fájlt Hitelesítés elérhetetlen Alkalmazás verzió - Üdvözlő üzenet hozzáadása - a(z) %s titkosításának elfogadása… + Üdvözlőüzenet hozzáadása + titkosítás elfogadása %s számára… " \nElérhető a v5.1-ben" Mindkét fél véglegesen törölheti az elküldött üzeneteket. (24 óra) @@ -84,7 +84,7 @@ Téves üzenet ID Az üzenetreakciók küldése engedélyezve van az ismerősei számára. A hangüzenetek küldése engedélyezve van. - Az üzenetreakciók küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + Az üzenetreakciók küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Vissza Kikapcsolható a beállításokban – az értesítések továbbra is megjelenítésre kerülnek amíg az alkalmazás fut.]]> Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz. @@ -92,13 +92,13 @@ titkosítás elfogadása… Ismerősök meghívása le van tiltva! téves üzenet ID - Kapcsolódási kérelmek automatikus elfogadása - Figyelem: NEM fogja tudni helyreállítani, vagy megváltoztatni a jelmondatot abban az esetben, ha elveszíti.]]> + Kapcsolatkérések automatikus elfogadása + Figyelem: NEM fogja tudni helyreállítani, vagy megváltoztatni a jelmondatot abban az esetben, ha elveszíti.]]> hívás… További másodlagos Hozzáadás egy másik eszközhöz Az üzenetreakciók küldése engedélyezve van. - Fájl előnézet visszavonása + Fájlelőnézet visszavonása Minden csoporttag kapcsolatban marad. Több akkumulátort használ! Az alkalmazás mindig fut a háttérben - az értesítések azonnal megjelennek.]]> Letiltás @@ -109,7 +109,7 @@ Letiltás Még néhány dolog Hitelesítés visszavonva - A fájlok- és a médiatartalom küldése engedélyezve van. + A fájlok- és a médiatartalmak küldése engedélyezve van. Minden csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza! hanghívás félkövér @@ -122,7 +122,7 @@ Engedélyezés Minden ismerősével kapcsolatban marad. Élő csevegési üzenet visszavonása - Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) + Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) Hang- és videóhívások hibás az üzenet hasító értéke Mindig fut @@ -130,7 +130,7 @@ Minden alkalmazásadat törölve. Legjobb akkumulátoridő. Csak akkor kap értesítéseket, amikor az alkalmazás meg van nyitva. (NINCS háttérszolgáltatás.)]]> Megjelenés - Az akkumulátor optimalizálása aktív, mely kikapcsolja a háttérszolgáltatást és az új üzenetek rendszeres kérését. A beállításokon keresztül újra engedélyezhetők. + Az akkumulátor-optimalizálás aktív, ez kikapcsolja a háttérszolgáltatást és az új üzenetek rendszeres lekérdezését. A beállításokban újraengedélyezheti. Biztosan letiltja? %1$s hívása befejeződött Jó akkumulátoridő. Az alkalmazás 10 percenként ellenőrzi az új üzeneteket. Előfordulhat, hogy hívásokról, vagy a sürgős üzenetekről marad le.]]> @@ -154,7 +154,7 @@ ALKALMAZÁS IKON Kiszolgáló hozzáadása QR-kód beolvasásával. Az eltűnő üzenetek küldése engedélyezve van. - Az eltűnő üzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + Az eltűnő üzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Hang kikapcsolva A közvetlen üzenetek küldése a tagok között engedélyezve van. Alkalmazás @@ -164,7 +164,7 @@ Sikertelen hitelesítés Minden %s által írt új üzenet elrejtésre kerül! Alkalmazás verzió: v%s - A hívások kezdeményezése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + A hívások kezdeményezése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Kiszolgáló hozzáadása Hang bekapcsolva hanghívás (nem e2e titkosított) @@ -187,7 +187,7 @@ kapcsolódva Szerepkör megváltoztatása Kapcsolódva - Belépési adatok megerősítése + Hitelesítő adatok megerősítése Megváltoztatja a fogadó címet? cím megváltoztatva Önmegsemmisítő mód megváltoztatása @@ -201,7 +201,7 @@ Csoport létrehozása véletlenszerű profillal. Az ismerős és az összes üzenet törlésre kerül - ez a művelet nem vonható vissza! Az ismerősei törlésre jelölhetnek üzeneteket; ön majd meg tudja nézni azokat. - Kapcsolódás egyszer használatos hivatkozással? + Kapcsolódás egyszer használható hivatkozással? Kapcsolódás egy hivatkozáson vagy QR-kódon keresztül Kapcsolódási hiba (AUTH) Ismerős neve @@ -215,7 +215,7 @@ Ismerős ellenőrizve Kapcsolódás saját magához? Kimásolva a vágólapra - Kapcsolódási kérés elküldve! + Kapcsolatkérés elküldve! Kapcsolódás a számítógéphez Kapcsolat Név helyesbítése erre: %s? @@ -228,7 +228,7 @@ kapcsolat létrehozva az ismerősnek nincs e2e titkosítása Ismerős engedélyezi - Ismerős elrejtve: + Rejtett név: Társítás számítógéppel Környezeti ikon Kapcsolódás egy hivatkozáson keresztül @@ -240,11 +240,11 @@ \n- gyorsabb és stabilabb. Hozzájárulás kapcsolódás (bemutatkozó meghívó) - SimpleX cím létrehozása + SimpleX-cím létrehozása törölt ismerős Csoporttag üzenetének törlése? A csevegés fut - Egyszer használatos meghívó hivatkozás létrehozása + Egyszer használható meghívó-hivatkozás létrehozása Törlés Új üzenetek ellenőrzése 10 percenként, legfeljebb 1 percen keresztül Adatbázis törlése @@ -304,7 +304,7 @@ cím megváltoztatása… kapcsolódás… Hívás kapcsolása - Fájlok és a médiatartalmak törlése? + A fájlok- és a médiatartalmak törlése? befejezett CSEVEGÉSI ADATBÁZIS Önmegsemmisító jelkód megváltoztatása @@ -334,7 +334,7 @@ cím megváltoztatása… Társítva a mobil eszközzel Jelenlegi jelmondat… - Fájl kiválasztás + Fájl kiválasztása Kép törlése Fájl létrehozása Tikos csoport létrehozása @@ -343,7 +343,7 @@ Kiürítés Cím létrehozása, hogy az emberek kapcsolatba léphessenek önnel. Biztonsági kódok összehasonlítása az ismerőseiével. - Fájl összehasonlítás + Fájl-összehasonlítás Csevegések Üzenet törlése? Függőben lévő ismerőskérelem törlése? @@ -361,7 +361,7 @@ Az adatbázis-titkosítási jelmondat megváltoztatásra és mentésre kerül a Keystore-ban. Az adatbázis titkosításra kerül és a jelmondat eltárolásra a beállításokban. Kiszolgáló törlése - A készüléken nincs beállítva a képernyőzár. A SimpleX zár ki van kapcsolva. + A készüléken nincs beállítva a képernyőzár. A SimpleX-zár ki van kapcsolva. Letiltás Letiltás minden csoport számára Engedélyezés minden csoport számára @@ -372,7 +372,7 @@ Számítógép címe %dmp Kézbesítési jelentések! - A készüléken nincs beállítva a képernyőzár. A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén. + A készüléken nincs beállítva a képernyőzár. A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén. Titkosítás visszafejtési hiba Eltűnik ekkor: %s szerkesztve @@ -380,7 +380,7 @@ %d óra %d hónap Cím törlése? - Üzenet kézbesítési jelentések letiltása? + Kézbesítési jelentések letiltása? Az adatbázis jelmondat eltér a Keystore-ban lévőtől. Közvetlen üzenetek E-mail @@ -393,7 +393,7 @@ %dó %dhét Felfedezés helyi hálózaton keresztül - Helyi csoportok felfedezése és csatlakozás + Csoportok felfedezése és csatlakozás %1$d üzenet moderálva lett %2$s által Eltűnő üzenet Ne hozzon létre címet @@ -411,7 +411,7 @@ A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban. %d perc Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Exportálás előtt változtassa meg a jelmondatot. - Üzenet kézbesítés jelentéseket letiltása a csoportok számára? + Kézbesítés jelentések letiltása a csoportok számára? nap %d nap Csevegési archívum törlése? @@ -472,9 +472,9 @@ Hiba az adatbázis titkosításakor Hiba a csoport törlésekor Kilépés mentés nélkül - Tárolt fájlok és médiatartalmak titkosítása + A tárolt fájlok- és a médiatartalmak titkosítása Hiba a cím beállításakor - A csoport meghívó lejárt + A csoportmeghívó lejárt Hiba az ICE kiszolgálók mentésekor Hiba Hiba @@ -487,11 +487,11 @@ \nCsatlakozzon hozzám a SimpleX Chaten keresztül: %s
    A megjelenített név nem tartalmazhat szóközöket. Csoport - Üdvözlő üzenet megadása… (opcionális) + Üdvözlőüzenet megadása… (opcionális) Hiba a csevegési adatbázis exportálásakor Hiba a fájl mentésekor Helyi fájlok titkosítása - titkosítás egyeztetve %s számára + titkosítás elfogadva %s számára %d üzenet megjelölve törlésre titkosítás újra egyeztetése engedélyezve Önmegsemmisítés engedélyezése @@ -520,31 +520,31 @@ Hiba a profil váltásakor! Kísérleti funkciók Engedélyezés (felülírások megtartásával) - Helyes jelmondat bevitele. + Adja meg a helyes jelmondatot. A csoport törlésre kerül az ön számára - ez a művelet nem vonható vissza! Adatbázis titkosítása? A zárolási képernyőn megjelenő hívások engedélyezése a Beállításokban. - titkosítás egyeztetve - Üzenet kézbesítési jelentések engedélyezése? + titkosítás elfogadva + Kézbesítési jelentések engedélyezése? Hiba a csoportprofil mentésekor hiba A fájl törölve lesz a kiszolgálóról. Akkor is, ha le van tiltva a beszélgetésben. - Gyorsabb csatlakozás és megbízhatóbb üzenet kézbesítés. + Gyorsabb csatlakozás és megbízhatóbb üzenetkézbesítés. Zárolás engedélyezése SEGÍTSÉG - Teljesen decentralizált - kizárólag tagok számára látható. + Teljesen decentralizált - csak a tagok számára látható. Fájl: %s Hívás befejezése Hiba a csoporthivatkozás törlésekor Fájl elmentve Kapcsolat javítása? - Fájlok és médiatartalom + Fájlok és médiatartalmak KONZOLHOZ Sikertelen titkosítás-újraegyeztetés. Hiba a felhasználói profil törlésekor Csoporttag általi javítás nem támogatott - Üdvözlő üzenet megadása… + Üdvözlőüzenet megadása… Titkosított adatbázis Jelszó megadása a keresőben A fájl akkor érkezik meg, amikor a küldője befejezte annak feltöltését. @@ -577,7 +577,7 @@ A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket. Hiba a tag eltávolításakor befejeződött - Csoport üdvözlő üzenete + A csoport üdvözlőüzenete Csoport neve: Hiba a meghívó küldésekor Adjon meg egy nevet: @@ -585,7 +585,7 @@ Téma exportálása Eszköznév megadása… Hiba - A csoport meghívó már nem érvényes, a küldője törölte. + A csoportmeghívó már nem érvényes, a küldője eltávolította. A csoport teljes neve: segítség Önmegsemmisítő jelkód engedélyezése @@ -593,15 +593,15 @@ Hiba a cím megváltoztatásának megszakításakor Hiba a fájl fogadásakor titkosítás rendben - Hiba az ismerőskérelem törlésekor - Üzenet kézbesítési jelentéseket engedélyezése csoportok számára? + Hiba a kapcsolatkérés törlésekor + Kézbesítési jelentések engedélyezése a csoportok számára? Ismerős általi javítás nem támogatott Fájl nem található Kapcsolat bontása A csoport tagjai üzenetreakciókat adhatnak hozzá. Adatbázis exportálása Teljes név: - Tovább csökkentett akkumulátor használat + Tovább csökkentett akkumulátor-használat Hiba a csevegés megállításakor titkosítás rendben %s számára A csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza! @@ -610,29 +610,29 @@ Teljes hivatkozás Hiba a cím megváltoztatásakor A csoport tagjai küldhetnek hangüzeneteket. - Csoport beállítások + Csoportbeállítások Hiba: %s Eltűnő üzenetek - SimpleX zár bekapcsolása - Hiba a kapcsolat szinkronizálása közben + SimpleX-zár bekapcsolása + Hiba a kapcsolat szinkronizálásakor Hiba a cím létrehozásakor engedélyezve Hiba a részletek betöltésekor - Hiba történt a kapcsolatfelvételi kérelem elfogadásakor + Hiba történt a kapcsolatkérés elfogadásakor titkosítás újraegyeztetése engedélyezett %s számára titkosítás újraegyeztetés szükséges Rejtett csevegési profilok - Fájlok és média + Fájlok és médiatartalmak A kép mentve a „Galériába” Elrejt Azonnal - A fájlok- és a médiatartalom küldése le van tiltva! + A fájlok- és a médiatartalmak küldése le van tiltva! Profil elrejtése Kiszolgálók használata Csevegési üzenetek gyorsabb megtalálása Téma importálása Hiba a téma importálásakor - Ismerős és üzenet elrejtése + Ismerős nevének és az üzenet tartalmának elrejtése Nem kompatibilis adatbázis verzió Hogyan működik a SimpleX Nem kompatibilis verzió @@ -661,24 +661,24 @@ Adatbázis importálása Importálás Azonnali értesítések - Inkognitó mód + Inkognitómód Csevegési adatbázis importálása? Az azonnali értesítések le vannak tiltva! Azonnali értesítések! Kép - A fájlok- és a médiatartalom küldése le van tiltva ebben a csoportban. + A fájlok- és a médiatartalmak le vannak tiltva ebben a csoportban. Hogyan működik Elrejt: Hiba az ismerőssel történő kapcsolat létrehozásában ICE-kiszolgálók (soronként egy) - beolvashatja a QR-kódot a videohívásban, vagy az ismerőse megoszthat egy meghívó hivatkozást.]]> - Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen törlődik! + beolvashatja a QR-kódot a videohívásban, vagy az ismerőse megoszthat egy meghívó-hivatkozást.]]> + Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen eltávolításra kerül! Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás közben, vagy ossza meg a hivatkozást. mutassa meg a QR-kódot a videohívásban, vagy ossza meg a hivatkozást.]]> Megerősítés esetén az üzenetküldő kiszolgálók látni fogják az IP-címét és a szolgáltatóját – azt, hogy mely kiszolgálókhoz kapcsolódik. A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését. QR kód beolvasásával.]]> - A kapott SimpleX Chat meghívó hivatkozását megnyithatja a böngészőjében: + A kapott SimpleX Chat-meghívó-hivatkozását megnyithatja a böngészőjében: Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot: Megtalált számítógép Számítógépek @@ -688,7 +688,7 @@ Mobilok leválasztása Különböző nevek, avatarok és átviteli izoláció. Elutasítás esetén a feladó NEM kap értesítést. - Szerepkör kiválasztásának bővítése + Szerepkörválasztás bővítése A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! meghíva Érvénytelen kapcsolattartási hivatkozás @@ -704,7 +704,7 @@ Nincs kézbesítési információ moderált A tag eltávolítása a csoportból - ez a művelet nem vonható vissza! - Győződjön meg róla, hogy az XFTP kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. + Győződjön meg arról, hogy az XFTP kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. Nincs kiválasztva ismerős Nincsenek fogadott, vagy küldött fájlok Megnyitás mobil alkalmazásban, majd koppintson a Kapcsolódás gombra az alkalmazásban.]]> @@ -720,7 +720,7 @@ Nagy fájl! Helyi név Hálózat és kiszolgálók - Értesítés előnézet + Értesítés előnézete Társítsa össze a mobil és asztali alkalmazásokat! 🔗 közvetett (%1$s) Hamarosan további fejlesztések érkeznek! @@ -730,7 +730,7 @@ Új asztali alkalmazás! Most már az adminok is: \n- törölhetik a tagok üzeneteit. -\n- letilthatnak tagokat („megfigyelő” szerepkör) +\n- letilthatnak tagokat (megfigyelő szerepkör)
    meghívta őt: %1$s Az üzenetreakciók küldése le van tiltva ebben a csoportban. Nem @@ -749,7 +749,7 @@ Onion kiszolgálók nem lesznek használva. perc Tudjon meg többet - Új ismerőskérelem + Új kapcsolatkérés Csatlakozás a csoporthoz Társított számítógép beállítások meghíva az ön csoporthivatkozásán keresztül @@ -792,7 +792,7 @@ Nincs hozzáadandó ismerős Üzenetvázlat meghívta, hogy csatlakozzon - Egyszer használatos meghívó hivatkozás + Egyszer használható meghívó-hivatkozás Értesítések Egyszerre csak 10 kép küldhető el ajánlott %s: %2s @@ -837,15 +837,15 @@ csatlakozás mint %s Nincs kiválasztva csevegés Csak helyi profiladatok - inkognitó egy egyszer használatos hivatkozáson keresztül + inkognitó egy egyszer használható hivatkozáson keresztül Moderálva lett ekkor: %s - Egyszer használatos meghívó hivatkozás + Egyszer használható meghívó-hivatkozás Érvénytelen név! Beszélgessünk a SimpleX Chatben Moderálva ekkor: Élő üzenetek Hitelesítés - Üzenetkézbesítési bizonylatok! + Üzenetkézbesítési jelentések! hivatkozás előnézeti képe Csoport elhagyása? nem @@ -876,7 +876,7 @@ Bejövő hanghívás Kulcstartó hiba Csatlakozik a csoporthoz? - Az inkognitó mód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ. + Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ. - stabilabb üzenetkézbesítés. \n- valamivel jobb csoportok. \n- és még sok más! @@ -890,10 +890,10 @@ elutasított hívás Rendszeres fogadott, tiltott - Kapcsolódási kérés megismétlése? + Kapcsolatkérés megismétlése? Véglegesen csak ön törölhet üzeneteket (ismerőse csak törlésre jelölheti őket ). (24 óra) Szerepkör - SimpleX kapcsolattartási cím + SimpleX-kapcsolattartási-cím Megállítás Előre beállított kiszolgáló Új csevegés kezdése @@ -901,7 +901,7 @@ Megnyitás Protokoll időtúllépés titkos - Előnézet megjelenítése + Üzenetek előnézetének megjelenítése várakozás a visszaigazolásra… Fájl megállítása a csoporthivatkozáson keresztül @@ -910,7 +910,7 @@ Önmegsemmisítési jelkód Mentés és csoportprofil frissítése Adatvédelem - Profil SimpleX címe + Profil SimpleX-címe Jelentse a fejlesztőknek. Ön dönti el, hogy kivel beszélget. Az eltűnő üzenetek küldése le van tiltva. @@ -925,17 +925,17 @@ Csak ön tud hívásokat indítani. Biztonságos várólista Értékelje az alkalmazást - Egyszer használatos hivatkozás megosztása + Egyszer használható hivatkozás megosztása Hiba az adatbázis visszaállításakor %s és %s Engedélyezve - Csökkentett akkumulátorhasználat - Mentés és ismerősök értesítése + Csökkentett akkumulátor-használat + Mentés és az ismerősök értesítése Előnézet Csevegés használata Megosztás Fogadott üzenet - Üdvözlő üzenet + Üdvözlőüzenet %s, %s és további %d tag kapcsolódott Csak az ismerőse tud hívást indítani. TÉMÁK @@ -944,16 +944,16 @@ Üdvözöljük! Önmegsemmisítési jelkód (beolvasás, vagy beillesztés a vágólapról) - Videóra várakozás + Várakozás a videóra Válasz - Ez az ön egyszer használatos hivatkozása! + Ez az ön egyszer használható hivatkozása! SimpleX Chat hívások Új inkognító profil használata Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel. SimpleX Hivatkozás előnézete a biztonsági kód megváltozott - Kizárólag ismerős megjelenítése + Csak az ismerős nevének megjelenítése Hangszóró bekapcsolva Importált csevegési adatbázis használatához indítsa újra az alkalmazást. jogosulatlan küldés @@ -978,15 +978,15 @@ Adatbázismentés visszaállítása Visszavon Ismerős felkérése, hogy engedélyezze a hangüzenetek küldését. - egyszer használatos hivatkozást osztott meg - A hivatkozás megnyitása a böngészőben gyengítheti az adatvédelmet és a biztonságot. A megbízhatatlan SimpleX hivatkozások pirossal vannak kiemelve. + egyszer használható hivatkozást osztott meg + A hivatkozás megnyitása a böngészőben gyengítheti az adatvédelmet és a biztonságot. A megbízhatatlan SimpleX-hivatkozások pirossal vannak kiemelve. ICE kiszolgálók Kapcsolat létrehozása Elutasítás - Ismerős és üzenet megjelenítése + Ismerős nevének és az üzenet tartalmának megjelenítése BEÁLLÍTÁSOK Felhasználói fiók jelszavának mentése - Fájl küldés megállítása? + Fájlküldés megállítása? Számítógép leválasztása? A hangüzenetek le vannak tilva! Közvetlen üzenet küldése a kapcsolódáshoz @@ -1010,7 +1010,7 @@ Archívum mentése %s, %s és %d tag Csevegési szolgáltatás megállítása - SimpleX hivatkozások + SimpleX-hivatkozások Az elküldött üzenetek törlésre kerülnek a beállított idő után. Némítás feloldása Elküldve ekkor: %s @@ -1020,8 +1020,8 @@ Profiljelszó Téma Jelmondat eltávolítása a beállításokból? - SimpleX csoporthivatkozás - Képre várakozás + SimpleX-csoporthivatkozás + Várakozás a képre Önmegsemmisítés várakozás a válaszra… Ismerős nevének beállítása… @@ -1035,7 +1035,7 @@ Rendszer Elküldés Biztonsági kód - Adja meg a helyes aktuális jelmondatát. + Adja meg a helyes, jelenlegi jelmondatát. Az elküldött üzenetek végleges törlése le van tiltva. Az üzenetreakciók küldése le van tiltva. Véletlenszerű jelmondat használata @@ -1057,11 +1057,11 @@ Ismerősök kiválasztása ismeretlen üzenet formátum Kiszolgálók mentése - Üdvözlő üzenet + Üdvözlőüzenet mp A profilfrissítés elküldésre került az ismerősök számára. Egyszerűsített inkognító mód - Üdvözlőszöveg mentése? + Üdvözlőüzenet mentése? Új csevegési fiók létrehozásához indítsa újra az alkalmazást. Engedély megtagadva! Főggőben lévő hívás @@ -1082,7 +1082,7 @@ Adatbázis jelmondat beállítása Elküldött üzenet Időszakosan indul - Ez az ön SimpleX címe! + Ez az ön SimpleX-címe! eltávolítva Megosztás SimpleX csapat @@ -1091,7 +1091,7 @@ tulajdonos Bekapcsolás %s, %s és %s kapcsolódott - SimpleX egyszer használatos meghívó + Egyszer használható SimpleX-meghívó Hívások nem sikerült elküldeni KEZELŐFELÜLET SZÍNEI @@ -1104,10 +1104,10 @@ Videó Automatikus elfogadási beállítások mentése Újraegyzetetés - Videóra várakozás + Várakozás a videóra XFTP kiszolgálók Videó kikapcsolva - Privát fájl nevek + Privát fájlnevek Beállítások mentése? Jelkód Ismeretlen hiba @@ -1117,12 +1117,12 @@ Adatbázis jelmondat beállítása Biztonsági kód megtekintése Tag feloldása? - A küldő törölhette a kapcsolódási kérelmet. + A küldő törölhette a kapcsolatkérést. Hibás adatbázis jelmondat SMP kiszolgálók - Az üzenet kézbesítési jelentések le vannak tiltva + A kézbesítési jelentések le vannak tiltva Adatbázis mappa megnyitása - egy egyszer használatos hivatkozáson keresztül + egyszer használható hivatkozáson keresztül Csoportbeállítások megadása ezen keresztül: %1$s igen @@ -1136,13 +1136,13 @@ Megállítás Címmegosztás megállítása? Csevegés profilok megnyitása - Csatlakozási kérés megismétlése? - Képre várakozás + Csatlakozáskérés megismétlése? + Várakozás a képre Hangüzenetek Biztosan eltávolítja? Biztonsági kód ellenőrzése - eltávolítottak - SimpleX cím + eltávolította önt + SimpleX-cím Megjelenítés: válasz fogadása… Adatbázismentés visszaállítása? @@ -1161,7 +1161,7 @@ A jelkód megváltozott! Akkor fut, amikor az alkalmazás meg van nyitva Ez a QR-kód nem egy hivatkozás! - Fájlra várakozás + Várakozás a fájlra simplexmq: v%s (%2s) Szétkapcsolás Véletlenszerű profil @@ -1182,7 +1182,7 @@ A hangüzenetek küldése le van tiltva. Ismerős nevének beállítása Csak ön tud eltűnő üzeneteket küldeni. - Kép/videó megoszása… + Média megosztása… ön: %1$s Beállítások Színek visszaállítása @@ -1202,8 +1202,8 @@ QR-kód megjelenítése videóhívás Kedvenc törlése - Üzenet kézbesítési jelentések küldése - SimpleX cím + Kézbesítési jelentések küldése + SimpleX-cím Koppintson a Mentés és ismerős értesítése Elutasított hívás @@ -1216,13 +1216,13 @@ Zárolási mód Fájl visszavonása XFTP kiszolgálók - A fájlok- és a médiatartalom küldése le van tiltva. + A fájlok- és a médiatartalmak küldése le van tiltva. Fájl megosztása… Mentés átjátszón keresztül Megosztás megállítása eltávolította őt: %1$s - Jelmondat mentése és csevegés megnyitása + Jelmondat mentése és a csevegés megnyitása Beállítások mentése? Nincsenek felhasználói azonosítók. A közvetlen üzenetek küldése a tagok között le van tiltva. @@ -1245,28 +1245,28 @@ Feloldás Némítás feloldása SimpleX Chat megnyitása a hívás fogadásához - Fájl fogadás megállítása? + Fájlfogadás megállítása? - opcionális értesítés a törölt ismerősök számára \n- profil nevek szóközökkel \n- és továbbiak! Lengyel kezelőfelület Kiszolgáló használata Fogadva ekkor: %s - SimpleX zár + SimpleX-zár Mentés és csoporttagok értesítése Visszaállítás Csak az ismerőse tud üzenetreakciókat küldeni. Hangüzenetek elhagyta a csoportot Hangüzenet rögzítése - SimpleX zár bekapcsolva + SimpleX-zár bekapcsolva közvetlen üzenet küldése Beolvasás mobilról Kapcsolatok ellenőrzése Üzenet megosztása… másodperc - A SimpleX zár nincs bekapcsolva! - SimpleX zár + A SimpleX-zár nincs bekapcsolva! + SimpleX-zár Beállítások Csevegési adatbázis eltávolította őt: %1$s @@ -1281,12 +1281,12 @@ Fogadott üzenet Csak az ismerőse tudja az üzeneteket véglegesen törölni (ön csak törlésre jelölheti meg azokat). (24 óra) Az önmegsemmisítési jelkód megváltozott! - SimpleX Chat kiszolgálók használatban. - SimpleX Chat kiszolgálók használata? + SimpleX Chat-kiszolgálók használatban. + SimpleX Chat-kiszolgálók használata? Csevegési profil felfedése Videók és fájlok 1Gb méretig TCP kapcsolat időtúllépés - A(z) %1$s nevű profiljának SimpleX címe megosztásra fog kerülni. + A(z) %1$s nevű profiljának SimpleX-címe megosztásra fog kerülni. Ön már kapcsolódva van ehhez: %1$s. Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! \nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek. @@ -1313,7 +1313,7 @@ kapcsolattartási cím-hivatkozáson keresztül SimpleX háttérszolgáltatást használja - az akkumulátornak csak néhány százalékát használja naponta.]]> Az ismerősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. -\nVisszavonhatja ezt az ismerőskérelmet és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). +\nVisszavonhatja ezt az ismerőskérelmet és eltávolíthatja az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). A jelszó nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonságimentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. Az ismerősei továbbra is kapcsolódva maradnak. A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát @@ -1335,11 +1335,11 @@ A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát Kapcsolódni fog a csoport összes tagjához. Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen - A biztonsága érdekében kapcsolja be a SimpleX zár funkciót. + A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót. \nA funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén. A videó akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! Hálózati kapcsolat ellenőrzése a következővel: %1$s, és próbálja újra. - A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be. + A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be. Az alkalmazás összeomlott Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg az ismerősét, hogy küldjön egy másikat. A kép nem dekódolható. Próbálja meg egy másik képpel, vagy lépjen kapcsolatba a fejlesztőkkel. @@ -1349,7 +1349,7 @@ A fájl fogadása leállt. Ne felejtse el, vagy tárolja biztonságosan – az elveszett jelszót nem lehet visszaállítani! A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését. - egyszer használatos hivatkozást osztott meg inkognitóban + egyszer használható hivatkozást osztott meg inkognitóban Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. Később engedélyezheti a Beállításokban Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! @@ -1365,19 +1365,19 @@ A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül. Meghívást kapott a csoportba Ismerőse a jelenleg megengedett maximális méretű (%1$s) fájlnál nagyobbat küldött. - Az ismerősei és az üzenetek (kézbesítés után) nem kerülnek tárolásra a SimpleX kiszolgálókon. + Az ismerősei és az üzenetek (kézbesítés után) nem kerülnek tárolásra a SimpleX-kiszolgálókon. Üzenetek formázása a szövegbe szúrt speciális karakterekkel: Megnyitás az alkalmazásban gombra.]]> Csevegési profilja elküldésre kerül \naz ismerőse számára - Egy olyan ismerősét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban - %1$s csoporthoz.]]> + Egy olyan ismerősét próbálja meghívni, akivel inkognitó-profilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban + %1$s nevű csoporthoz.]]> Amikor az alkalmazás fut Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva Kapcsolat izolációs mód - Akkor lesz kapcsolódva, ha a kapcsolódási kérelme elfogadásra kerül, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, ha a kapcsolatkérése elfogadásra kerül, várjon, vagy ellenőrizze később! A hangüzenetek küldése le van tiltva ebben a csoportban. - Alkalmazás akkumulátor használata / Korlátlan módot az alkalmazás beállításaiban.]]> + Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Biztonságos kvantumrezisztens protokollon keresztül. - 5 perc hosszúságú hangüzenetek. \n- egyedi üzenet-eltűnési időkorlát. @@ -1389,12 +1389,12 @@ Használja az .onion kiszolgálókat NEM értékre, ha a SOCKS proxy nem támogatja őket.]]> Megoszthatja a címét egy hivatkozásként vagy egy QR-kódként – így bárki kapcsolódhat önhöz. Létrehozás később - Profilja az eszközön van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX kiszolgálók nem láthatják a profilját. + Profilja az eszközön van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX-kiszolgálók nem láthatják a profilját. %s szerepkörét megváltoztatta erre: %s - Csoport meghívó elutasítva + Csoportmeghívó elutasítva Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX üzenetsorokhoz rendel azonosítókat, minden egyes ismerőshöz egy különbözőt. (megosztás egy ismerőssel) - Csoport meghívó elküldve + Csoportmeghívó elküldve Kapcsolat izolációs mód frissítése? Kapcsolat izolációs mód Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak. @@ -1413,10 +1413,10 @@ A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra. Ha engedélyezni szeretné a mobilalkalmazás társítását a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, ha engedélyezte azt Profilja, ismerősei és az elküldött üzenetei az eszközön kerülnek tárolásra. - Alkalmazás akkumulátor használata / Korlátlan módot az alkalmazás beállításaiban.]]> - Ez a karakterlánc nem egy meghívó hivatkozás! + Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> + Ez a karakterlánc nem egy meghívó-hivatkozás! Új csevegés kezdése - A kapcsolódás már folyamatban van ezen az egyszer használatos hivatkozáson keresztül! + A kapcsolódás már folyamatban van ezen az egyszer használható hivatkozáson keresztül! Nem veszíti el az ismerőseit, ha később törli a címét. A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. kapcsolatba akar lépni önnel! @@ -1425,7 +1425,7 @@ Kód ellenőrzése a mobilon Csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz. a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet az újdonságokról.]]> - Opcionális üdvözlő üzenettel. + Opcionális üdvözlőüzenettel. Ismeretlen adatbázis hiba: %s Elrejtheti vagy lenémíthatja a felhasználó profiljait - koppintson (vagy asztali alkalmazásban kattintson) hosszan a profilra a felugró menühöz. Inkognító mód kapcsolódáskor. @@ -1436,11 +1436,11 @@ Ön irányítja csevegését! Kód ellenőrzése a számítógépen Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak. - A kapcsolódási kérelem elküldésre kerül ezen csoporttag számára. - Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. - Már kért egy kapcsolódási kérelmet ezen a címen keresztül! - Megoszthatja ezt a SimpleX címet az ismerőseivel, hogy kapcsolatba léphessenek vele: %s. - Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat. + A kapcsolatkérés elküldésre kerül ezen csoporttag számára. + Inkognitó-profil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. + Már küldött egy kapcsolatkérést ezen a címen keresztül! + Megoszthatja ezt a SimpleX-címet az ismerőseivel, hogy kapcsolatba léphessenek vele: %s. + Amikor az emberek kapcsolatot kérnek, ön elfogadhatja vagy elutasíthatja azokat. Megjelenítendő üzenet beállítása az új tagok számára! Köszönet a felhasználóknak - hozzájárulás a Weblate-en! A kézbesítési jelentés küldése minden ismerős számára engedélyezésre kerül. @@ -1450,8 +1450,8 @@ Profilja csak az ismerőseivel kerül megosztásra. Néhány kiszolgáló megbukott a teszten: Koppintson a csatlakozáshoz - Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak. - Kézbesítési jelentések engedélyezve vannak %d ismerősnél + Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalmakkal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak. + A kézbesítési jelentések engedélyezve vannak %d ismerősnél Küldés ezen keresztül: Köszönet a felhasználóknak - hozzájárulás a Weblate-en! A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára. @@ -1465,8 +1465,8 @@ \nEz később megváltoztatható. Koppintson az inkognitóban való kapcsolódáshoz Jelmondat beállítása az exportáláshoz - Kézbesítési jelentések le vannak tiltva a(z) %d csoportban - Néhány nem végzetes hiba történt az importálás közben: + A kézbesítési jelentések le vannak tiltva %d csoportban + Néhány nem végzetes hiba történt az importáláskor: Köszönet a felhasználóknak - hozzájárulás a Weblate-en! Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címet. Rendszerhitelesítés helyetti beállítás. @@ -1481,12 +1481,12 @@ További információ a GitHub tárolónkban. Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. A mentett WebRTC ICE-kiszolgálók eltávolításra kerülnek. - Kézbesítési jelentések engedélyezve vannak a(z) %d csoportban - A szerepkör meg fog változni erre: „%s”. A csoportban mindenki értesítve lesz. + A kézbesítési jelentések engedélyezve vannak %d csoportban + A szerepkör meg fog változni erre: %s. A csoportban mindenki értesítve lesz. Profil és kiszolgálókapcsolatok Egy üzenetküldő- és alkalmazásplatform, amely védi az adatait és biztonságát. A profil aktiválásához koppintson az ikonra. - Kézbesítési jelentések le vannak tiltva %d ismerősnél + A kézbesítési jelentések le vannak tiltva %d ismerősnél Munkamenet kód Köszönet a felhasználóknak - hozzájárulás a Weblate-en! Kis csoportok (max. 20 tag) @@ -1496,13 +1496,13 @@ A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel). \nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Az eszköz neve megosztásra kerül a társított mobil klienssel. - A címzettek a beírás közben látják a frissítéseket. + A címzettek a beírás közben látják a szövegváltozásokat. Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni. - A jelmondat a beállítások között egyszerű szövegként kerül tárolásra, miután megváltoztatta vagy újraindította az alkalmazást. + A jelmondat a beállításokban egyszerű szövegként kerül tárolásra, miután megváltoztatta vagy újraindította az alkalmazást. A jelenlegi csevegési profilhoz tartozó új kapcsolatok kiszolgálói Fogadás ezen keresztül: Tárolja el biztonságosan jelmondát, mert ha elveszti azt, akkor NEM férhet hozzá a csevegéshez. - A tag szerepköre erre fog változni: „%s”. A tag új meghívót fog kapni. + A szerepkör meg fog változni erre: %s. A tag új meghívót fog kapni. profilkép helyőrző A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet! Ez a művelet nem vonható vissza - profiljai, ismerősei, üzenetei és fájljai véglegesen törlésre kerülnek. @@ -1519,15 +1519,15 @@ Láthatóvá teheti a SimpleXbeli ismerősei számára a „Beállításokban”. Legfeljebb az utolsó 100 üzenet kerül elküldésre az új tagok számára. A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozás. - A beillesztett szöveg nem egy SimpleX hivatkozás. - A meghívó hivatkozását újra megtekintheti a kapcsolat részleteinél. + A beillesztett szöveg nem egy SimpleX-hivatkozás. + A meghívó-hivatkozását újra megtekintheti a kapcsolat részleteinél. Csevegés indítása? Látható előzmények Alkalmazás jelkód Ismerős hozzáadása Koppintson a beolvasáshoz Koppintson a hivatkozás beillesztéséhez - Ismerős hozzáadása: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.]]> + Ismerős hozzáadása: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.]]> Csoport létrehozása: új csoport létrehozásához.]]> A csevegés leállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés megkezdése előtt. Az előzmények nem kerülnek elküldésre az új tagok számára. @@ -1538,14 +1538,14 @@ Vagy mutassa meg ezt a kódot Kamera hozzáférés engedélyezése Fel nem használt meghívó megtartása? - Egyszer használatos meghívó hivatkozás megosztása + Egyszer használható meghívó-hivatkozás megosztása Új csevegés Csevegések betöltése… Hivatkozás létrehozása… Vagy QR-kód beolvasása Érvénytelen QR-kód Megtartás - Keresés, vagy SimpleX hivatkozás beillesztése + Keresés, vagy SimpleX-hivatkozás beillesztése Belső hibák megjelenítése Kritikus hiba Belső hiba @@ -1554,7 +1554,7 @@ Számítógép elfoglalt Számítógép inaktív Csevegés újraindítása - Időtúllépés a számítógépről való kapcsolódás közben + Időtúllépés a számítógéphez való csatlakozáskor Számítógép kliens kapcsolata megszakadt A kapcsolat megszakadt A kapcsolat megszakadt @@ -1573,7 +1573,7 @@ %s probléma miatt megszakadt a kapcsolat %s mobil eszköz nem található]]> %s mobil eszközzel rossz állapotban van]]> - %s mobil eszközhöz való csatlakozás közben]]> + %s mobil eszközhöz való csatlakozáskor]]> ismeretlen Lassú funkció Lassú API-hívások megjelenítése @@ -1581,11 +1581,11 @@ Fejlesztői beállítások A funkció végrehajtása túl sokáig tart: %1$d másodperc: %2$s %s mobil eszköz elfoglalt]]> - %1$s (már nem tag) + (Már nem tag) %1$s ismeretlen státusz %1$s megváltoztatta a nevét erre: %2$s - törölt kapcsolattartási cím - törölte a profilképét + eltávolította a kapcsolattartási címet + eltávolította a profilképét új kapcsolattartási cím beállítása új profilképet állított be frissített profil @@ -1598,15 +1598,15 @@ Mentett üzenet Megosztva ekkor: %s Minden üzenet törlésre kerül – ez a művelet nem vonható vissza! - Továbbfejlesztett üzenetküldés + Továbbfejlesztett üzenetkézbesítés Csatlakozás csoportos beszélgetésekhez Hivatkozás beillesztése a kapcsolódáshoz! Privát jegyzetek - A keresősáv elfogadja a meghívó hivatkozásokat. - Titkosított fájlokkal és médiatartalommal. - Csökkentett akkumulátorhasználattal. + A keresősáv elfogadja a meghívó-hivatkozásokat. + Titkosított fájlokkal és médiatartalmakkal. + Csökkentett akkumulátor-használattal. Magyar és török felhasználói felület - A közelmúlt eseményei és továbbfejlesztett jegyzék bot. + A közelmúlt eseményei és továbbfejlesztett könyvtárbot. feloldotta %s letiltását ön feloldotta %s letiltását letiltva @@ -1619,15 +1619,15 @@ Letiltás feloldása mindenki számára Mindenki számára feloldja a tag letiltását? ön letiltotta őt: %s - Hiba a tag mindenki számára való letiltása közben + Hiba a tag mindenki számára való letiltásakor Az üzenet túl nagy - Az üdvözlő üzenet túl hosszú + Az üdvözlőüzenet túl hosszú Az adatbázis átköltöztetése folyamatban van. \nEz eltarthat néhány percig. Hanghívás A hívás befejeződött Videóhívás - Hiba a böngésző megnyitása közben + Hiba a böngésző megnyitásakor A hívásokhoz egy alapértelmezett webböngésző szükséges. Állítson be egy alapértelmezett webböngészőt az eszközön, és osszon meg további információkat a SimpleX Chat fejlesztőivel. Hálózati beállítások megerősítése Hiba a csevegési adatbázis exportálásakor @@ -1683,7 +1683,7 @@ %s feltöltve Sikertelen feltöltés Archívum feltöltése - Figyelmeztetés: a csevegés elindítása egyszerre több eszközön nem támogatott, továbbá üzenetkézbesítési hibákat okozhat + Figyelmeztetés: a csevegés elindítása egyszerre több eszközön nem támogatott, és üzenetkézbesítési hibákat okozhat Importálás ismét szabványos végpontok közötti titkosítás Átköltöztetés ide @@ -1696,7 +1696,7 @@ Átköltöztetés befejezve Átköltöztetés egy másik eszközre QR-kód használatával. Átköltöztetés - Megjegyzés: ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja az ismerőseitől érkező üzenetek visszafejtését.]]> + Figyelem: ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja az ismerőseitől érkező üzenetek visszafejtését.]]> Megpróbálhatja még egyszer. Hibás hivatkozás végpontok közötti kvantumrezisztens titkosítás @@ -1721,16 +1721,16 @@ További Wi-Fi továbbított - A SimpleX hivatkozások küldése le van tiltva - A csoport tagjai küldhetnek SimpleX hivatkozásokat. + A SimpleX-hivatkozások küldése le van tiltva + A csoport tagjai küldhetnek SimpleX-hivatkozásokat. tulajdonosok adminok minden tag - SimpleX hivatkozások + SimpleX-hivatkozások A hangüzenetek küldése le van tiltva - A SimpleX hivatkozások küldése le van tiltva ebben a csoportban. - A SimpleX hivatkozások küldése le van tiltva - Fájlok és média tartalom küldése le van tiltva + A SimpleX-hivatkozások küldése le van tiltva ebben a csoportban. + A SimpleX-hivatkozások küldése le van tiltva + A fájlok- és médiatartalmak nincsenek engedélyezve A SimpleX hivatkozások küldése engedélyezve van. Számukra engedélyezve: mentett @@ -1769,7 +1769,7 @@ \nCélkiszolgáló hiba: %2$s Hiba: %1$s Kapacitás túllépés - a címzett nem kapta meg a korábban elküldött üzeneteket. - Üzenet kézbesítési figyelmeztetés + Üzenetkézbesítési figyelmeztetés A kiszolgáló címe nem kompatibilis a hálózati beállításokkal. Soha Ismeretlen kiszolgálók @@ -1849,12 +1849,12 @@ kiszolgáló várakoztatási infó: %1$s \nutoljára kézbesített üzenet: %2$s Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött. - Ideiglenes fájlhiba + Ideiglenesfájl-hiba Üzenetállapot Üzenetállapot: %s Fájlhiba A fájl nem található - valószínűleg a fájlt törölték vagy visszavonták. - Fájlkiszolgáló hiba: %1$s + Fájlkiszolgáló-hiba: %1$s Fájlállapot Fájlállapot: %s Másolási hiba @@ -1919,7 +1919,7 @@ Feltöltött fájlok Letöltött fájltöredékek Letöltött fájlok - Kiszolgáló beállításainak megnyitása + Kiszolgáló-beállítások megnyitása Kiszolgáló címe Feltöltési hibák Nyugtázva @@ -1946,8 +1946,8 @@ Korábban kapcsolódott kiszolgálók Privát útválasztási hiba Fogadott üzenetek - Az összes kiszolgálóhoz való újrakapcsolás az üzenetek kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ. - A kiszolgálóhoz való újrakapcsolódás az üzenet kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ. + Az összes kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ. + A kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ. Elküldött üzenetek Munkamenetek átvitele Összesen @@ -2022,13 +2022,13 @@ Üzenet küldése a hívások engedélyezéséhez. Engedélyeznie kell a hívásokat az ismerőse számára, hogy fel tudják hívni egymást. A(z) %1$s nevű ismerősével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában. - Üzenet - Kiválasztás + Üzenet… + Kijelölés Az üzenetek minden tag számára moderáltként lesznek megjelölve. Nincs kiválasztva semmi Az üzenetek törlésre lesznek jelölve. A címzett(ek) képes(ek) lesz(nek) felfedni ezt az üzenetet. Törli a tagok %d üzenetét? - %d kiválasztva + %d kijelölve Az üzenetek minden tag számára törlésre kerülnek. Csevegési adatbázis exportálva Kapcsolatok- és kiszolgálók állapotának megjelenítése. @@ -2056,11 +2056,47 @@ Elhomályosítás a jobb adatvédelemért. Automatikus frissítés Létrehozás - Új verziók letöltése a GitHubról + Új verziók letöltése a GitHubról. Betűméret növelése. Meghívás Új csevegési élmény 🎉 Új üzenet Érvénytelen hivatkozás - Ellenőrizze, hogy a SimpleX hivatkozás helyes-e. + Ellenőrizze, hogy a SimpleX-hivatkozás helyes-e. + Hiba a profilváltáskor + A kapcsolata át lett helyezve ide: %s, de egy váratlan hiba történt a profilra való átirányításkor. + Az üzenetek törlésre kerülnek - ez a művelet nem vonható vissza! + Archívum eltávolítása? + A feltöltött adatbázis-archívum véglegesen eltávolításra kerül a kiszolgálókról. + CSEVEGÉSI ADATBÁZIS + Profil megosztása + Rendszerbeállítások használata + Csevegési profil kiválasztása + Ne használja a hitelesítő adatokat proxyval. + Különböző proxy hitelesítő adatok használata minden egyes profilhoz. + Különböző proxy hitelesítő adatok használata minden egyes kapcsolathoz. + Jelszó + Felhasználónév + A hitelesítőadatai titkosítatlanul is elküldhetőek. + Hiba a proxy mentésekor + Győződjön meg arról, hogy a proxy konfigurációja helyes. + Proxyhitelesítés + Véletlenszerű hitelesítő adatok használata + %1$d egyéb fájlhiba. + Nincs mit továbbítani! + %1$d fájl letöltése még folyamatban van. + %1$d fájlt nem sikerült letölteni. + %1$d fájl nem lett letöltve. + Letöltés + %1$d fájl törölve lett. + %1$s üzenet továbbítása + Üzenetek továbbítása… + %1$d fájlhiba: +\n%2$s + %1$s üzenet nem lett továbbítva + %1$s üzenet továbbítása? + Üzenetek továbbítása fájlok nélkül? + Az üzeneteket törölték miután kiválasztotta őket. + %1$s üzenet mentése + Hiba az üzenetek továbbításakor \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 1bc9f6283f..088a233f6e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2069,4 +2069,40 @@ Nuovo messaggio Link non valido Controlla che il link SimpleX sia corretto. + Errore nel cambio di profilo + Seleziona il profilo di chat + Condividi il profilo + DATABASE DELLA CHAT + Modalità di sistema + Rimuovere l\'archivio? + I messaggi verranno eliminati. Non è reversibile! + L\'archivio del database caricato verrà rimosso definitivamente dai server. + La tua connessione è stata spostata a %s, ma si è verificato un errore imprevisto durante il reindirizzamento al profilo. + Non usare credenziali con proxy. + Assicurati che la configurazione del proxy sia corretta. + Autenticazione del proxy + Usa diverse credenziali del proxy per ogni connessione. + Usa diverse credenziali del proxy per ogni profilo. + Usa credenziali casuali + Le credenziali potrebbero essere inviate in chiaro. + Errore di salvataggio del proxy + Password + Nome utente + %1$d file è/sono ancora in scaricamento. + %1$s messaggi non inoltrati + %1$d altro/i errore/i di file. + Errore nell\'inoltro dei messaggi + %1$d errore/i di file: +\n%2$s + Inoltrare %1$s messaggio/i? + Inoltrare i messaggi senza file? + I messaggi sono stati eliminati dopo che li hai selezionati. + Niente da inoltrare! + %1$d file ha/hanno fallito lo scaricamento. + %1$d file è/sono stato/i eliminato/i. + %1$d file non è/sono stato/i scaricato/i. + Scarica + Inoltro di %1$s messaggi + Inoltra messaggi… + Salvataggio di %1$s messaggi \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 38df6a8b16..8ca7e6fddb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -312,7 +312,7 @@ 自分のサーバの使い方 ICEサーバ (1行に1サーバ) ネットワークとサーバ - ネットワーク設定 + 高度な設定 アドレスを削除 保存せずに閉じる 表示の名前には空白が使用できません。 @@ -535,7 +535,7 @@ 色付き 応答 分散型 - スパムや悪質送信を完全防止 + スパム耐性 即時 定期的 通話は既に終了してます! @@ -1837,4 +1837,53 @@ SMPサーバーの構成 接続中 XFTPサーバーの構成 + チャトリスト切り替え + 連絡先 + メッセージサーバ + メディア&ファイルサーバ + チャットツールバーを近づける + 招待 + 作成 + メッセージ + チャットツールバーを近づける + QRスキャン / リンクの貼り付け + 片手でアプリを利用できます + 招待 + リンクの貼り付け + 無効 + 現在のプロフィール + 全てのプロフィール + 通話 + 連絡先の削除を確認しますか? + 接続 + 連絡先が削除されます - この操作は取り消せません! + プロフィールの切り替えエラー + メディアのぼかし + チャットデータベース + 続ける + 連絡先の削除完了! + 詳細 + 非アクティブ + 無効 + プロキシの保存エラー + 通話を許可しますか? + 連絡先が削除されました。 + 無効 + 一度に最大20件のメッセージを削除できます。 + サーバに接続中 + エラー + サーバーへの再接続エラー + エラー + ファイル + 復号化エラー + 削除エラー + 重複 + 期限切れ + 統計の詳細 + プロキシで認証情報を使用しないでください。 + サーバーへの再接続エラー + 統計のリセットエラー + メッセージを送信することができません + 連絡先と通話することができません + 接続待ち \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 9a8810de84..1ff95a0ac5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -72,7 +72,7 @@ Over SimpleX Over SimpleX Chat hier boven, dan: - Alle gesprekken en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! + Alle chats en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! Alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd. Sta verdwijnende berichten alleen toe als uw contact dit toestaat. Sta spraak berichten alleen toe als uw contact ze toestaat. @@ -90,7 +90,7 @@ APP ICON App versie App versie: v%s - voor elk chat profiel dat je in de app hebt .]]> + voor elk chatprofiel dat je in de app hebt .]]> audio oproep (niet e2e versleuteld) Achtergrondservice is altijd actief, meldingen worden weergegeven zodra de berichten beschikbaar zijn. Oproep beëindigd @@ -120,9 +120,9 @@ Gesprek archief Wachtwoord database wijzigen\? Chat is gestopt - Gesprek voorkeuren - Chat profiel - GESPREKKEN + Chat voorkeuren + Chatprofiel + CHATS Praat met de ontwikkelaars Controleer het server adres en probeer het opnieuw. Bestand @@ -180,7 +180,7 @@ "De database wordt versleuteld en het wachtwoord wordt opgeslagen in de Keychain." Het wachtwoord voor database versleuteling wordt bijgewerkt. Database fout - Database wachtwoord is vereist om je gesprekken te openen. + Database wachtwoord is vereist om je chats te openen. Contact bestaat al Oproep verbinden Maak link @@ -233,7 +233,7 @@ Chat archief verwijderen\? Archief verwijderen Verwijder contact\? - Chat profiel verwijderen\? + Chatprofiel verwijderen? Verwijderen voor iedereen Link verwijderen direct @@ -250,7 +250,7 @@ Verwijder bericht\? Verwijder berichten Wachtrij verwijderen - Verwijder bestanden voor alle chat profielen + Verwijder bestanden voor alle chatprofielen Verwijder voor mij Groep verwijderen Link verwijderen\? @@ -284,7 +284,7 @@ %dmth %d uren %dh - Chat profiel verwijderen\? + Chatprofiel verwijderen? Chat profiel verwijderen voor verwijderd Beschrijving @@ -347,7 +347,7 @@ Groepsleden kunnen verzonden berichten onomkeerbaar verwijderen. (24 uur) Groepsleden kunnen directe berichten sturen Groepsleden kunnen spraak berichten verzenden. - Per chat profiel (standaard) of per verbinding (BETA). + Per chatprofiel (standaard) of per verbinding (BETA). Verschillende namen, avatars en transportisolatie. Franse interface Fout bij opslaan van groep profiel @@ -392,7 +392,7 @@ Fout bij opslaan van SMP servers Fout bij updaten van netwerk configuratie Kan het gesprek niet laden - Kan de gesprekken niet laden + Kan de chats niet laden Volledige link dubbel bericht Ongeldige verbinding link @@ -456,10 +456,10 @@ Groep verlaten\? Nieuwe leden rol Geen contacten om toe te voegen - Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel. + Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel. Licht nee - Meerdere chat profielen + Meerdere chatprofielen Italiaanse interface Concept bericht Meer verbeteringen volgen snel! @@ -590,7 +590,7 @@ Alleen uw contact kan spraak berichten verzenden. Verbied het onomkeerbaar verwijderen van berichten. voorgesteld %s - Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken. + Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de chats. Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u deze kwijtraakt. Chat openen Voer het vorige wachtwoord in na het herstellen van de database back-up. Deze actie kan niet ongedaan gemaakt worden. @@ -620,12 +620,12 @@ ontvangt, uw contacten de servers die u gebruikt om ze berichten te sturen.]]> Video aan Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. - Deze instelling is van toepassing op berichten in uw huidige chat profiel + Deze instelling is van toepassing op berichten in uw huidige chatprofiel Bewaar archief bijgewerkt groep profiel verwijderd - Uw chat profiel wordt verzonden naar de groepsleden - Je hebt al een chat profiel met dezelfde weergave naam. Kies een andere naam. + Uw chatprofiel wordt verzonden naar de groepsleden + Je hebt al een chatprofiel met dezelfde weergave naam. Kies een andere naam. U bent al verbonden met %1$s. Test mislukt bij stap %s. Veilige wachtrij @@ -650,8 +650,8 @@ Deze tekst is beschikbaar in instellingen Welkom! je bent uitgenodigd voor de groep - Je hebt geen gesprekken - Gesprekken + Je hebt geen chats + Chats Deel bestand… Afbeelding delen… Wachten op afbeelding @@ -680,7 +680,7 @@ SimpleX Adres Toon QR-code SimpleX-Logo - Je chat profiel wordt verzonden naar uw contact + Je chatprofiel wordt verzonden naar uw contact Je wordt verbonden met de groep wanneer het apparaat van de groep host online is, even geduld a.u.b. of controleer het later! U wordt verbonden wanneer uw verbindingsverzoek wordt geaccepteerd, even geduld a.u.b. of controleer later! Je wordt verbonden wanneer het apparaat van je contact online is, even geduld a.u.b. of controleer het later! @@ -696,7 +696,7 @@ Stuur ons een e-mail SimpleX Vergrendelen SMP servers - Bewaar servers + Servers opslaan Servertest mislukt! Sommige servers hebben de test niet doorstaan: Servers testen @@ -747,7 +747,7 @@ CHAT UITVOEREN Uw chat database Wachtwoord instellen om te exporteren - Start de app opnieuw om een nieuw chat profiel aan te maken. + Start de app opnieuw om een nieuw chatprofiel aan te maken. U mag ALLEEN de meest recente versie van uw chat-database op één apparaat gebruiken, anders ontvangt u mogelijk geen berichten meer van sommige contacten. Start de app opnieuw om de geïmporteerde chat database te gebruiken. Stop de chat om database acties mogelijk te maken. @@ -842,7 +842,7 @@ Camera Gebruik voor nieuwe verbindingen Star on GitHub - De servers voor nieuwe verbindingen van je huidige chat profiel + De servers voor nieuwe verbindingen van je huidige chatprofiel Uw SMP servers Opgeslagen WebRTC ICE servers worden verwijderd. Uw ICE servers @@ -865,7 +865,7 @@ Uw contacten kunnen volledige verwijdering van berichten toestaan. U moet elke keer dat de app start het wachtwoord invoeren, deze wordt niet op het apparaat opgeslagen. Verkeerd wachtwoord voor de database - Bewaar het wachtwoord en open je gesprekken + Bewaar het wachtwoord en open je chats De poging om het wachtwoord van de database te wijzigen is niet voltooid. Database back-up terugzetten Database back-up terugzetten\? @@ -963,7 +963,7 @@ Fout bij updaten van gebruikers privacy Verder verminderd batterij verbruik Groep welkom bericht - Verborgen chat profielen + Verborgen chatprofielen Profiel verbergen Verbergen Verborgen profiel wachtwoord @@ -974,7 +974,7 @@ Nu kunnen beheerders: \n- berichten van leden verwijderen. \n- schakel leden uit ("waarnemer" rol) - Bescherm je chat profielen met een wachtwoord! + Bescherm je chatprofielen met een wachtwoord! Wachtwoord om weer te geven Groep profiel opslaan en bijwerken Servers opslaan\? @@ -988,7 +988,7 @@ zichtbaar maken Dempen opheffen Welkom bericht - Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoekveld in op de pagina Uw chat profielen. + Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoekveld in op de pagina Uw chatprofielen. Welkom bericht U ontvangt nog steeds oproepen en meldingen van gedempte profielen wanneer deze actief zijn. Database downgraden @@ -1011,9 +1011,9 @@ EXPERIMENTEEL Verwijder profiel Profiel wachtwoord - Chat profiel zichtbaar maken + Chatprofiel zichtbaar maken Profiel zichtbaar maken - Chat profiel verwijderen\? + Chatprofiel verwijderen? Gevraagd om de video te ontvangen Er kunnen slechts 10 video\'s tegelijk worden verzonden Te veel video\'s! @@ -1107,7 +1107,7 @@ Poolse interface Dank aan de gebruikers – draag bij via Weblate! Video\'s en bestanden tot 1 GB - Chat profielen openen + Open chatprofielen Over SimpleX adres Kom meer te weten Om verbinding te maken, kan uw contact de QR-code scannen of de link in de app gebruiken. @@ -1249,7 +1249,7 @@ Adres wijziging wordt afgebroken. Het oude ontvangstadres wordt gebruikt. Annuleer het wijzigen van het adres Afbreken - Geen gefilterde gesprekken + Geen gefilterde chats Alleen groep eigenaren kunnen bestanden en media inschakelen. Bestanden en media zijn verboden in deze groep. Favoriet @@ -1303,7 +1303,7 @@ Ontvangst bevestiging verzenden De tweede vink die we gemist hebben! ✅ Filter ongelezen en favoriete chats. - Vind gesprekken sneller + Vind chats sneller Repareer versleuteling na het herstellen van back-ups. Behoud uw verbindingen Eén bericht laten verdwijnen @@ -1538,7 +1538,7 @@ Zichtbare geschiedenis App toegangscode Nieuw gesprek - Gesprekken laden… + Chats laden… Link maken… Of scan de QR-code Ongeldige QR-code @@ -2057,7 +2057,7 @@ U kunt dit wijzigen in de instellingen onder uiterlijk Creëren Vervagen voor betere privacy. - Afspelen via de gesprekken lijst. + Afspelen via de chatlijst. Download nieuwe versies van GitHub. Vergroot het lettertype. App automatisch upgraden @@ -2067,4 +2067,13 @@ Nieuw bericht Ongeldige link Controleer of de SimpleX-link correct is. + Fout bij wisselen van profiel + Selecteer chatprofiel + Profiel delen + Uw verbinding is verplaatst naar %s, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel. + CHAT DATABASE + Systeemmodus + Archief verwijderen? + Berichten worden verwijderd. Dit kan niet ongedaan worden gemaakt! + Het geüploade databasearchief wordt permanent van de servers verwijderd. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index b38348b30f..db175452a2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -949,4 +949,23 @@ Detalhes Você pode partilhar o seu endereço com os seus contactos para permitir que se conectem com %s. Você pode partilhar o seu endereço como uma ligação ou código QR - qualquer pessoa pode conectar-se a si. + Permitir + Todas as novas mensagens de %s serão ocultadas! + Somente você pode fazer ligações. + Todos os modos de cores + administradores + "aceitando criptografia para %s…" + Adicionar contato + Permitir downgrade + Todas as mensagens serão deletadas - isso não poderá ser desfeito! + Mais algumas coisas + Permitir envio de arquivos e mídias. + Administradores podem bloquear um membro para todos. + Aceitando criptografia + Configurações avançadas + todos os membros + Erros de reconhecimento + Mudança de endereço será cancelada. Antigo endereço de recebimento será usado. + Permitir ligações? + Conexões ativas \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index f6b3b12431..fd3001cd0d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -776,7 +776,7 @@ Дякуємо користувачам – приєднуйтеся через Weblate! Тепер адміністратори можуть: \n- видаляти повідомлення учасників. -\n- вимикати учасників (роль спостерігач) +\n- вимикати учасників (роль спостерігача). Встановіть повідомлення, яке показується новим учасникам! Додатково зменшено використання батареї Більше поліпшень незабаром! @@ -2067,4 +2067,23 @@ Скинути всі підказки Доступно оновлення: %s Завантаження оновлення скасовано + БАЗА ДАНИХ ЧАТУ + Вибрати профіль чату + Помилка при зміні профілю + Повідомлення будуть видалені — це не можна скасувати! + Видалити архів? + Поділитися профілем + Завантажений архів бази даних буде остаточно видалено з серверів. + Ваше з\'єднання було перенесено на %s, але виникла несподівана помилка під час перенаправлення на профіль. + Режим системи + Не використовуйте облікові дані з проксі. + Аутентифікація проксі + Використовуйте різні облікові дані проксі для кожного з\'єднання. + Використовувати випадкові облікові дані + Ваші облікові дані можуть бути надіслані в незашифрованому вигляді. + Помилка під час збереження проксі + Переконайтеся, що конфігурація проксі правильна. + Пароль + Ім\'я користувача + Використовуйте різні облікові дані проксі для кожного профілю. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index b16c96b636..a82160667b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -781,4 +781,52 @@ Tệp đã được lưu Tệp sẽ được nhận khi liên hệ của bạn hoạt động, vui lòng chờ hoặc kiểm tra lại sau! Trạng thái tệp: %s + Lấp đầy + CƠ SỞ DỮ LIỆU SIMPLEX CHAT + Lỗi chuyển đổi hồ sơ + Lọc các cuộc hội thoại chưa đọc và các cuộc hội thoại yêu thích. + Cuối cùng, chúng ta đã có chúng! 🚀 + Hoàn tất quá trình di chuyển ở thiết bị khác. + Hoàn tất quá trình di chuyển + Tìm các cuộc trò chuyện nhanh hơn + Tìm kiếm quyền này trong phần cài đặt Android và cấp quyền theo cách thủ công. + Sửa + Kích thước phù hợp + Chuyển tiếp + Đã được chuyển tiếp từ + đã được chuyển tiếp + Máy chủ chuyển tiếp: %1$s +\nLỗi máy chủ đích: %2$s + Máy chủ chuyển tiếp: %1$s +\nLỗi: %2$s + Chuyển tiếp và lưu tin nhắn + Chức năng sửa không hỗ trợ bởi thành viên nhóm + Cho tất cả mọi người + Đã được chuyển tiếp + Không sử dụng thông tin đăng nhập với proxy. + Sửa kết nối + Sửa kết nối? + Chức năng sửa không hỗ trợ bởi liên hệ + Sửa mã hóa sau khi hồi phục dữ liệu dự phòng. + Lỗi lưu proxy + Đổi máy ảnh + Kích thước font + %1$d lỗi tệp khác. + Lỗi chuyển tiếp tin nhắn + %1$d tệp tải không thành công. + %1$d tệp đã bị xóa. + %1$d tệp đã không được tải xuống. + Tải xuống + Chuyển tiếp %1$s tin nhắn? + Chuyển tiếp tin nhắn… + %1$d lỗi tệp: +\n%2$s + %1$d tệp đang được tải xuống. + %1$s tin nhắn không được chuyển tiếp + Đang chuyển tiếp %1$s tin nhắn + Máy chủ chuyển tiếp %1$s không thể kết nối tới máy chủ đích %2$s. Vui lòng thử lại sau. + Địa chỉ máy chủ chuyển tiếp không tương thích với cài đặt mạng: %1$s. + Phiên bản máy chủ chuyển tiếp không tương thích với cài đặt mạng: %1$s. + CHO CONSOLE + Chuyển tiếp tin nhắn… \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 469ce9b5b1..43becf32f4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -502,7 +502,7 @@ 显示 您只能在一台设备上使用最新版本的聊天数据库,否则您可能会停止接收来自某些联系人的消息。 新密码…… - 该角色将更改为“%s”。群组中每个人都会收到通知。 + 该角色将更改为 %s。群组中每个人都会收到通知。 SimpleX 锁定 定期通知 定期启动 @@ -733,7 +733,7 @@ 图像无法解码。 请尝试不同的图像或联系开发者。 主题 此操作无法撤消——所有接收和发送的文件和媒体都将被删除。 低分辨率图片将保留。 - 角色将更改为“%s”。 该成员将收到新的邀请。 + 角色将更改为%s。 该成员将收到新的邀请。 此操作无法撤消——早于所选的发送和接收的消息将被删除。 这可能需要几分钟时间。 此二维码不是链接! 接收地址将变更到不同的服务器。地址更改将在发件人上线后完成。 @@ -946,7 +946,7 @@ 将为所有成员删除该消息。 该消息将对所有成员标记为已被管理员移除。 删除成员消息? - 观察者 + 观察员 您是观察者 更新群组链接错误 您无法发送消息! @@ -968,7 +968,7 @@ 更多改进即将推出! 现在管理员可以: \n- 删除成员的消息。 -\n- 禁用成员(“观察员”角色) +\n- 禁用成员(观察员角色) 使用密码保护您的聊天资料! 确认密码 更新用户隐私错误 @@ -2068,4 +2068,23 @@ 新消息 请检查 Simple X 链接是否正确。 无效链接 + 聊天数据库 + 系统模式 + 上传的数据库存档将永久性从服务器被删除。 + 确保代理配置正确 + 消息将被删除 - 此操作无法撤销! + 你的连接被移动到 %s,但在将你重定向到配置文件时发生了意料之外的错误。 + 代理不使用身份验证凭据 + 切换配置文件出错 + 代理身份验证 + 删除存档? + 选择聊天配置文件 + 保存代理出错 + 密码 + 每个连接使用不同的代理身份验证凭据。 + 每个配置文件使用不同的代理身份验证。 + 你的凭据可能以未经加密的方式被发送。 + 使用随机凭据 + 用户名 + 分享配置文件 \ No newline at end of file From 630fea42c375799cef14d0f9dc14f8a3c2a612e2 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 20 Sep 2024 12:47:40 +0100 Subject: [PATCH 149/440] website: translations (#4916) * ios: update core library * Translated using Weblate (Hungarian) 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/hu/ * Revert "ios: update core library" This reverts commit 211f6c51f23f9178038d1a3d5085235ad6857ce4. --------- Co-authored-by: summoner001 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 8 ----- website/langs/hu.json | 38 +++++++++++----------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 9b96b3dee6..b0b838eef8 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -2117,10 +2117,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Libraries", - ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", @@ -2172,10 +2168,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Libraries", - ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", diff --git a/website/langs/hu.json b/website/langs/hu.json index 7b6b856065..3ac0a77fc0 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -4,10 +4,10 @@ "reference": "Referencia", "blog": "Blog", "features": "Funkciók", - "why-simplex": "Miért válassza a SimpleX-t", - "simplex-privacy": "SimpleX adatvédelem", - "simplex-network": "SimpleX hálózat", - "simplex-explained": "Simplex bemutatása", + "why-simplex": "Miért válassza a SimpleXet", + "simplex-privacy": "SimpleX-adatvédelem", + "simplex-network": "SimpleX-hálózat", + "simplex-explained": "A Simplex bemutatása", "simplex-explained-tab-1-text": "1. Felhasználói élmény", "simplex-explained-tab-2-text": "2. Hogyan működik", "simplex-explained-tab-3-text": "3. Mit látnak a kiszolgálók", @@ -42,7 +42,7 @@ "feature-5-title": "Eltűnő üzenetek", "feature-6-title": "E2E-titkosított
    hang- és videohívások", "feature-7-title": "Hordozható titkosított alkalmazás-adattárolás — profil áthelyezése egy másik eszközre", - "feature-8-title": "Az inkognitó mód —
    egyedülálló a SimpleX Chat-ben", + "feature-8-title": "Az inkognitó mód —
    egyedülálló a SimpleX Chatben", "simplex-network-overlay-1-title": "Összehasonlítás más P2P üzenetküldő protokollokkal", "simplex-private-1-title": "2 rétegű végpontok közötti titkosítás", "simplex-private-2-title": "További rétege a
    kiszolgáló titkosítás", @@ -83,9 +83,9 @@ "simplex-unique-2-overlay-1-title": "A legjobb védelem a spam és a visszaélések ellen", "simplex-unique-3-title": "Az ön adatai fölött csak ön rendelkezik", "simplex-unique-3-overlay-1-title": "Az ön adatai fölött csak ön rendelkezik", - "simplex-unique-4-title": "Öné a SimpleX hálózat", - "simplex-unique-4-overlay-1-title": "Teljesen decentralizált — a SimpleX hálózat a felhasználóké", - "hero-overlay-card-1-p-1": "Sok felhasználó kérdezte: ha a SimpleX-nek nincsenek felhasználói azonosítói, honnan tudja, hová kell eljuttatni az üzeneteket?", + "simplex-unique-4-title": "Öné a SimpleX-hálózat", + "simplex-unique-4-overlay-1-title": "Teljesen decentralizált — a SimpleX-hálózat a felhasználóké", + "hero-overlay-card-1-p-1": "Sok felhasználó kérdezte: ha a SimpleXnek nincsenek felhasználói azonosítói, honnan tudja, hogy hová kell eljuttatni az üzeneteket?", "hero-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez az összes többi platform által használt felhasználói azonosítók helyett a SimpleX az üzenetek várakoztatásához ideiglenes, névtelen, páros azonosítókat használ, külön-külön minden egyes kapcsolathoz — nincsenek hosszú távú azonosítók.", "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz Tor hálózaton keresztül is kapcsolódhat.", "hero-overlay-card-1-p-5": "Csak a kliensek tárolják a felhasználói profilokat, kapcsolatokat és csoportokat; az üzenetek küldése 2 rétegű végpontok közötti titkosítással történik.", @@ -95,16 +95,16 @@ "hero-overlay-card-2-p-3": "Még a Tor v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezen támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitó módot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések bizonyítása.", "hero-overlay-card-3-p-1": "Trail of Bits egy vezető biztonsági és technológiai tanácsadó cég, amelynek ügyfelei közé tartoznak a nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.", - "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX platform kriptográfiai és hálózati komponenseit.", + "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX-platform kriptográfiai és hálózati komponenseit.", "simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a DHT valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet O(log N) csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.", "simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenesen sem, és csak ideiglenes páros azonosítókat használ, ami jobb névtelenséget és metaadatvédelmet biztosít.", "simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a MITM-támadás problémát, és a legtöbb létező implementáció nem használ sávon kívüli üzeneteket a kezdeti kulcscseréhez. A SimpleX a kezdeti kulcscseréhez sávon kívüli üzeneteket, vagy bizonyos esetekben már meglévő biztonságos és megbízható kapcsolatokat használ.", - "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hálózat sebezhető Sybil támadással, mert minden egyes csomópont felderíthető, és a hálózat egészként működik. A támadások enyhítésére szolgáló ismert intézkedés lehet egy központi kiszolgáló (pl.: tracker), vagy egy drága tanúsítvány. A SimpleX hálózat nem ismeri fel a kiszolgálókat, töredezett és több elszigetelt alhálózatként működik, ami lehetetlenné teszi az egész hálózatra kiterjedő támadásokat.", - "simplex-network-overlay-card-1-li-6": "A P2P-hálózatok sebezhetőek lehetnek a DRDoS-támadással szemben, amikor a kliensek képesek a forgalmat újraközvetíteni és felerősíteni, ami az egész hálózatra kiterjedő szolgáltatásmegtagadást eredményez. A SimpleX kliensek csak az ismert kapcsolatból származó forgalmat továbbítják, és a támadó nem használhatja őket arra, hogy az egész hálózatban felerősítse a forgalmat.", + "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hálózat sebezhető Sybil támadással, mert minden egyes csomópont felderíthető, és a hálózat egészként működik. A támadások enyhítésére szolgáló ismert intézkedés lehet egy központi kiszolgáló (pl.: tracker), vagy egy drága tanúsítvány. A SimpleX-hálózat nem ismeri fel a kiszolgálókat, töredezett és több elszigetelt alhálózatként működik, ami lehetetlenné teszi az egész hálózatra kiterjedő támadásokat.", + "simplex-network-overlay-card-1-li-6": "A P2P-hálózatok sebezhetőek lehetnek a DRDoS-támadással szemben, amikor a kliensek képesek a forgalmat újraközvetíteni és felerősíteni, ami az egész hálózatra kiterjedő szolgáltatásmegtagadást eredményez. A SimpleX-kliensek csak az ismert kapcsolatból származó forgalmat továbbítják, és a támadó nem használhatja őket arra, hogy az egész hálózatban felerősítse a forgalmat.", "privacy-matters-overlay-card-1-p-1": "Sok nagyvállalat arra használja fel az önnel kapcsolatban álló személyek adatait, hogy megbecsülje az ön jövedelmét, hogy olyan termékeket adjon el önnek, amelyekre valójában nincs is szüksége, és hogy meghatározza az árakat.", "privacy-matters-overlay-card-1-p-2": "Az online kiskereskedők tudják, hogy az alacsonyabb jövedelműek nagyobb valószínűséggel vásárolnak azonnal, ezért magasabb árakat számíthatnak fel, vagy eltörölhetik a kedvezményeket.", "privacy-matters-overlay-card-1-p-3": "Egyes pénzügyi és biztosítótársaságok szociális grafikonokat használnak a kamatlábak és a díjak meghatározásához. Ez gyakran arra készteti az alacsonyabb jövedelmű embereket, hogy többet fizessenek — ez az úgynevezett „szegénységi prémium”.", - "privacy-matters-overlay-card-1-p-4": "A SimpleX platform minden alternatívánál jobban védi a kapcsolatainak adatait, teljes mértékben megakadályozva, hogy a ismeretségi-hálója bármilyen vállalat vagy szervezet számára elérhetővé váljon. Még ha az emberek a SimpleX Chat által biztosított kiszolgálókat is használják, sem a felhasználók számát, sem a kapcsolataikat nem ismerjük.", + "privacy-matters-overlay-card-1-p-4": "A SimpleX-platform minden alternatívánál jobban védi a kapcsolatainak adatait, teljes mértékben megakadályozva, hogy a ismeretségi-hálója bármilyen vállalat vagy szervezet számára elérhetővé váljon. Még ha az emberek a SimpleX Chat által biztosított kiszolgálókat is használják, sem a felhasználók számát, sem a kapcsolataikat nem ismerjük.", "privacy-matters-overlay-card-2-p-1": "Nem is olyan régen megfigyelhettük, hogy a nagy választásokat manipulálta egy neves tanácsadó cég, amely az ismeretségi-háló segítségével eltorzította a valós világról alkotott képünket, és manipulálta a szavazatainkat.", "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektív legyen és független döntéseket tudjon hozni, az információs terét is kézben kell tartania. Ez csak akkor lehetséges, ha privát kommunikációs platformot használ, amely nem fér hozzá az ismeretségi-hálójához.", "privacy-matters-overlay-card-2-p-3": "A SimpleX az első olyan platform, amely eleve nem rendelkezik felhasználói azonosítókkal, így jobban védi az ismeretségi-hálóját, mint bármely ismert alternatíva.", @@ -114,16 +114,16 @@ "privacy-matters-overlay-card-3-p-4": "Nem elég, ha csak egy végpontok között titkosított üzenetküldőt használunk, mindannyiunknak olyan üzenetküldőket kell használnunk, amelyek védik személyes ismerőseink magánéletét — akikkel kapcsolatban állunk.", "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő platformoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — nem tudjuk, hogy hányan használják a SimpleX-kiszolgálóinkat.", "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX az egyirányú üzenet várakoztatást használ páronkénti névtelen címekkel, külön a fogadott és külön az elküldött üzenetek számára, általában különböző kiszolgálókon keresztül. A SimpleX használata olyan, mintha minden egyes kapcsolatnak más-más “eldobható” e-mail címe vagy telefonja lenne és nem kell ezeket gondosan kezelni.", - "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX platform kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Tor-on keresztül kapcsolódik a SimpleX kiszolgálókhoz.", - "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX platformon, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", + "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX platform kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgálókhoz.", + "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX-platformon, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", "simplex-unique-overlay-card-2-p-2": "Még az opcionális felhasználói cím esetében is, bár spam kapcsolatfelvételi kérések küldésére használható, megváltoztathatja vagy teljesen törölheti azt anélkül, hogy elveszítené a meglévő kapcsolatait.", "simplex-unique-overlay-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban, amely exportálható és átvihető bármely más támogatott eszközre.", - "simplex-unique-overlay-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX átjátszó-kiszolgálókon tartózkodnak, amíg be nem érkeznek a címzetthez, majd véglegesen törlődnek onnan.", - "simplex-unique-overlay-card-3-p-3": "A föderált hálózatok kiszolgálóitól (e-mail, XMPP vagy Matrix) eltérően a SimpleX kiszolgálók nem tárolják a felhasználói fiókokat, csak továbbítják az üzeneteket, így védve mindkét fél magánéletét.", + "simplex-unique-overlay-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX-átjátszókiszolgálókon tartózkodnak, amíg be nem érkeznek a címzetthez, majd véglegesen törlődnek onnan.", + "simplex-unique-overlay-card-3-p-3": "A föderált hálózatok kiszolgálóitól (e-mail, XMPP vagy Matrix) eltérően a SimpleX-kiszolgálók nem tárolják a felhasználói fiókokat, csak továbbítják az üzeneteket, így védve mindkét fél magánéletét.", "simplex-unique-overlay-card-3-p-4": "A küldött és a fogadott kiszolgálóforgalom között nincsenek közös azonosítók vagy titkosított szövegek — ha bárki megfigyeli, nem tudja könnyen megállapítani, hogy ki kivel kommunikál, még akkor sem, ha a TLS-t kompromittálják.", - "simplex-unique-overlay-card-4-p-1": "Használhatja a SimpleX-et saját kiszolgálóival, és továbbra is kommunikálhat azokkal, akik az általunk biztosított, előre konfigurált kiszolgálókat használják.", + "simplex-unique-overlay-card-4-p-1": "Használhatja a SimpleXet a saját kiszolgálóival, és továbbra is kommunikálhat azokkal, akik az általunk biztosított, előre konfigurált kiszolgálókat használják.", "simplex-unique-overlay-card-4-p-2": "A SimpleX platform nyitott protokollt használ és SDK-t biztosít a chatbotok létrehozásához, lehetővé téve olyan szolgáltatások megvalósítását, amelyekkel a felhasználók a SimpleX Chat alkalmazásokon keresztül léphetnek kapcsolatba — mi már nagyon várjuk, hogy milyen SimpleX szolgáltatásokat készítenek a lelkes közreműködők.", - "simplex-unique-overlay-card-4-p-3": "Ha a SimpleX platformra való fejlesztést fontolgatja, például a SimpleX-alkalmazások felhasználóinak szánt chatbotot, vagy a SimpleX Chat Jegyzék bot integrálását más mobilalkalmazásba, lépjen velünk kapcsolatba, ha bármilyen tanácsot vagy támogatást szeretne kapni.", + "simplex-unique-overlay-card-4-p-3": "Ha a SimpleX-platformra való fejlesztést fontolgatja, például a SimpleX-alkalmazások felhasználóinak szánt chatbotot, vagy a SimpleX Chat-könvtárbot integrálását más mobilalkalmazásba, lépjen velünk kapcsolatba, ha bármilyen tanácsot vagy támogatást szeretne kapni.", "simplex-unique-card-1-p-1": "A SimpleX védi az ön profiljához tartozó kapcsolatait és metaadatait, elrejtve azokat a SimpleX platform kiszolgálói és a megfigyelők elől.", "simplex-unique-card-1-p-2": "Minden más létező üzenetküldő platformtól eltérően a SimpleX nem rendelkezik a felhasználókhoz rendelt azonosítókkal — még véletlenszerű számokkal sem.", "simplex-unique-card-2-p-1": "Mivel a SimpleX platformon nincs azonosítója vagy állandó címe, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", @@ -224,7 +224,7 @@ "contact-hero-header": "Kapott egy címet a SimpleX Chat-en való kapcsolódáshoz", "invitation-hero-header": "Kapott egy egyszer használatos hivatkozást a SimpleX Chat-en való kapcsolódáshoz", "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független - a szabványos webes protokollokon, pl. WebSockets-en keresztül is működik.", - "simplex-private-card-4-point-2": "A SimpleX Tor-on keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxy-t (vagy a VPN-t az iOS-ban).", + "simplex-private-card-4-point-2": "A SimpleX Toron keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", "simplex-private-card-5-point-1": "A SimpleX minden titkosítási réteghez tartalomkitöltést használ, hogy meghiúsítsa az üzenetméret ellen irányuló támadásokat.", "simplex-private-card-5-point-2": "A kiszolgálók és a hálózatot megfigyelők számára a különböző méretű üzenetek egyformának tűnnek.", "privacy-matters-1-title": "Hirdetés és árdiszkrimináció", From efb7fc6c3be230389cc988bafbf2f70eb9d4b497 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 20 Sep 2024 15:01:47 +0100 Subject: [PATCH 150/440] 6.1-beta.1: ios 238, android 240, desktop 67 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 60 +++++++++++----------- apps/multiplatform/gradle.properties | 8 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b0b838eef8..a6bfd9d80e 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -217,11 +217,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E55128E72C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */; }; - E55128E82C9AD063001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E32C9AD063001D165C /* libgmp.a */; }; - E55128E92C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */; }; - E55128EA2C9AD063001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E52C9AD063001D165C /* libgmpxx.a */; }; - E55128EB2C9AD063001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E62C9AD063001D165C /* libffi.a */; }; + E55128F12C9DA948001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128EC2C9DA948001D165C /* libffi.a */; }; + E55128F22C9DA948001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128ED2C9DA948001D165C /* libgmpxx.a */; }; + E55128F32C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128EE2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a */; }; + E55128F42C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128EF2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a */; }; + E55128F52C9DA948001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128F02C9DA948001D165C /* libgmp.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -556,11 +556,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a"; sourceTree = ""; }; - E55128E32C9AD063001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a"; sourceTree = ""; }; - E55128E52C9AD063001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E55128E62C9AD063001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E55128EC2C9DA948001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E55128ED2C9DA948001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E55128EE2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a"; sourceTree = ""; }; + E55128EF2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a"; sourceTree = ""; }; + E55128F02C9DA948001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -651,14 +651,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E55128E72C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a in Frameworks */, - E55128E82C9AD063001D165C /* libgmp.a in Frameworks */, + E55128F32C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a in Frameworks */, + E55128F42C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a in Frameworks */, + E55128F52C9DA948001D165C /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E55128EB2C9AD063001D165C /* libffi.a in Frameworks */, + E55128F22C9DA948001D165C /* libgmpxx.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E55128E92C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E55128EA2C9AD063001D165C /* libgmpxx.a in Frameworks */, + E55128F12C9DA948001D165C /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -735,11 +735,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E55128E62C9AD063001D165C /* libffi.a */, - E55128E32C9AD063001D165C /* libgmp.a */, - E55128E52C9AD063001D165C /* libgmpxx.a */, - E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */, - E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */, + E55128EC2C9DA948001D165C /* libffi.a */, + E55128F02C9DA948001D165C /* libgmp.a */, + E55128ED2C9DA948001D165C /* libgmpxx.a */, + E55128EE2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a */, + E55128EF2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a */, ); path = Libraries; sourceTree = ""; @@ -1891,7 +1891,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1940,7 +1940,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1981,7 +1981,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2001,7 +2001,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2026,7 +2026,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2063,7 +2063,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2100,7 +2100,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2151,7 +2151,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2202,7 +2202,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2236,7 +2236,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 6123ce156f..f07eea9ec7 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.1-beta.0 -android.version_code=239 +android.version_name=6.1-beta.1 +android.version_code=240 -desktop.version_name=6.1-beta.0 -desktop.version_code=66 +desktop.version_name=6.1-beta.1 +desktop.version_code=67 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 8a70bad9afba8adc415080c75efc3a2dc7189135 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 20 Sep 2024 21:13:27 +0400 Subject: [PATCH 151/440] core: process ERRS event (#4896) * core: process ERRS event * refactor * update --------- Co-authored-by: Evgeny Poberezkin --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 7fb9f6d353..f2a652d721 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 309ef3766cc6b69e0c3aa0c140faab25383b732a + tag: bef11e4cbe0a3776f0910375f2adb60399043835 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 52dd21e8cd..730a0f9501 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."309ef3766cc6b69e0c3aa0c140faab25383b732a" = "1ch03kizvsq3m5jwravyil529mc0lcfwj43czb1nhykbg8yb3cjv"; + "https://github.com/simplex-chat/simplexmq.git"."bef11e4cbe0a3776f0910375f2adb60399043835" = "195hir9crv51iyli8yjk8sivk7wagxxjfmhzq6ahydvbkrpd3258"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 7ca2f4b948..d42603adfa 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -4093,6 +4093,7 @@ processAgentMessageNoConn = \case UP srv conns -> serverEvent srv conns NSConnected CRContactsSubscribed SUSPENDED -> toView CRChatSuspended DEL_USER agentUserId -> toView $ CRAgentUserDeleted agentUserId + ERRS cErrs -> errsEvent cErrs where hostEvent :: ChatResponse -> CM () hostEvent = whenM (asks $ hostEvents . config) . toView @@ -4105,6 +4106,16 @@ processAgentMessageNoConn = \case notifyCLI = do cs <- withStore' (`getConnectionsContacts` conns) toView $ event srv cs + errsEvent :: [(ConnId, AgentErrorType)] -> CM () + errsEvent cErrs = do + vr <- chatVersionRange + errs <- lift $ rights <$> withStoreBatch' (\db -> map (getChatErr vr db) cErrs) + toView $ CRChatErrors Nothing errs + where + getChatErr :: VersionRangeChat -> DB.Connection -> (ConnId, AgentErrorType) -> IO ChatError + getChatErr vr db (connId, err) = + let acId = AgentConnId connId + in ChatErrorAgent err <$> (getUserByAConnId db acId $>>= \user -> eitherToMaybe <$> runExceptT (getConnectionEntity db vr user acId)) processAgentMsgSndFile :: ACorrId -> SndFileId -> AEvent 'AESndFile -> CM () processAgentMsgSndFile _corrId aFileId msg = do From 33e12e35a0d0b0e40d3204b21e6a87c039bc8e10 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 20 Sep 2024 21:19:06 +0100 Subject: [PATCH 152/440] ios: use translation in dropdown --- apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index 70c33329b1..ab3388bfce 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -808,7 +808,7 @@ struct ThemeDestinationPicker: View { @Binding var customizeThemeIsOpen: Bool var body: some View { - let values = [(nil, "All profiles")] + m.users.filter { $0.user.activeUser }.map { ($0.user.userId, $0.user.chatViewName)} + let values = [(nil, NSLocalizedString("All profiles", comment: "profile dropdown"))] + m.users.filter { $0.user.activeUser }.map { ($0.user.userId, $0.user.chatViewName)} if values.contains(where: { (userId, text) in userId == themeUserDestination?.0 }) { Picker("Apply to", selection: $themeUserDest) { From c849f5356d5f22a8c79434379118d20a86900133 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 21 Sep 2024 13:07:27 +0100 Subject: [PATCH 153/440] core: save the time user profile was opened at to order in ui (#4920) * core: save the time user profile was opened at to order in ui * replace timestamp with order --- apps/ios/SimpleXChat/ChatTypes.swift | 1 + .../chat/simplex/common/model/ChatModel.kt | 1 + simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 3 +-- src/Simplex/Chat/Core.hs | 11 +++----- .../Chat/Migrations/M20240920_user_order.hs | 18 +++++++++++++ src/Simplex/Chat/Migrations/chat_schema.sql | 3 ++- src/Simplex/Chat/Store/Migrations.hs | 4 ++- src/Simplex/Chat/Store/Profiles.hs | 26 +++++++++++-------- src/Simplex/Chat/Store/Shared.hs | 8 +++--- src/Simplex/Chat/Types.hs | 1 + tests/MobileTests.hs | 10 +++---- 12 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20240920_user_order.hs diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 6b03833d08..272e487214 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -17,6 +17,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { public var profile: LocalProfile public var fullPreferences: FullPreferences public var activeUser: Bool + public var activeOrder: Int64 public var displayName: String { get { profile.displayName } } public var fullName: String { get { profile.fullName } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 4fcda25855..9f52694f91 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -806,6 +806,7 @@ data class User( val profile: LocalProfile, val fullPreferences: FullChatPreferences, override val activeUser: Boolean, + val activeOrder: Long, override val showNtfs: Boolean, val sendRcptsContacts: Boolean, val sendRcptsSmallGroups: Boolean, diff --git a/simplex-chat.cabal b/simplex-chat.cabal index bf42c5117e..a6c96ecc91 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -147,6 +147,7 @@ library Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays Simplex.Chat.Migrations.M20240528_quota_err_counter Simplex.Chat.Migrations.M20240827_calls_uuid + Simplex.Chat.Migrations.M20240920_user_order Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d42603adfa..8e8ab5eb38 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -593,8 +593,7 @@ processChatCommand' vr = \case user_ <- chatReadVar currentUser user' <- privateGetUser userId' validateUserPassword_ user_ user' viewPwd_ - withFastStore' (`setActiveUser` userId') - let user'' = user' {activeUser = True} + user'' <- withFastStore' (`setActiveUser` user') chatWriteVar currentUser $ Just user'' pure $ CRActiveUser user'' SetActiveUser uName viewPwd_ -> do diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index a07968654d..ad2f1367da 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -74,9 +74,7 @@ getSelectActiveUser st = do selectUser :: [User] -> IO (Maybe User) selectUser = \case [] -> pure Nothing - [user@User {userId}] -> do - withTransaction st (`setActiveUser` userId) - pure $ Just user + [user] -> Just <$> withTransaction st (`setActiveUser` user) users -> do putStrLn "Select user profile:" forM_ (zip [1 :: Int ..] users) $ \(n, user) -> putStrLn $ show n <> ": " <> userStr user @@ -88,10 +86,9 @@ getSelectActiveUser st = do Nothing -> putStrLn "not a number" >> loop Just n | n <= 0 || n > length users -> putStrLn "invalid user number" >> loop - | otherwise -> do - let user@User {userId} = users !! (n - 1) - withTransaction st (`setActiveUser` userId) - pure $ Just user + | otherwise -> + let user = users !! (n - 1) + in Just <$> withTransaction st (`setActiveUser` user) createActiveUser :: ChatController -> IO User createActiveUser cc = do diff --git a/src/Simplex/Chat/Migrations/M20240920_user_order.hs b/src/Simplex/Chat/Migrations/M20240920_user_order.hs new file mode 100644 index 0000000000..29fd1532f2 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240920_user_order.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240920_user_order where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240920_user_order :: Query +m20240920_user_order = + [sql| +ALTER TABLE users ADD COLUMN active_order INTEGER NOT NULL DEFAULT 0; +|] + +down_m20240920_user_order :: Query +down_m20240920_user_order = + [sql| +ALTER TABLE users DROP COLUMN active_order; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 25cf886384..214c0e72cc 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -35,7 +35,8 @@ CREATE TABLE users( send_rcpts_contacts INTEGER NOT NULL DEFAULT 0, send_rcpts_small_groups INTEGER NOT NULL DEFAULT 0, user_member_profile_updated_at TEXT, - ui_themes TEXT, -- 1 for active user + ui_themes TEXT, + active_order INTEGER NOT NULL DEFAULT 0, -- 1 for active user FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE RESTRICT diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index be3f4027ca..a09baee272 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -111,6 +111,7 @@ import Simplex.Chat.Migrations.M20240510_chat_items_via_proxy import Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays import Simplex.Chat.Migrations.M20240528_quota_err_counter import Simplex.Chat.Migrations.M20240827_calls_uuid +import Simplex.Chat.Migrations.M20240920_user_order import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -221,7 +222,8 @@ schemaMigrations = ("20240510_chat_items_via_proxy", m20240510_chat_items_via_proxy, Just down_m20240510_chat_items_via_proxy), ("20240515_rcv_files_user_approved_relays", m20240515_rcv_files_user_approved_relays, Just down_m20240515_rcv_files_user_approved_relays), ("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter), - ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid) + ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid), + ("20240920_user_order", m20240920_user_order, Just down_m20240920_user_order) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index a29460d5b1..fb9774a54e 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -19,7 +19,6 @@ module Simplex.Chat.Store.Profiles getUsersInfo, getUsers, setActiveUser, - getSetActiveUser, getUser, getUserIdByName, getUserByAConnId, @@ -106,10 +105,11 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, let showNtfs = True sendRcptsContacts = True sendRcptsSmallGroups = True + order <- getNextActiveOrder db DB.execute db - "INSERT INTO users (agent_user_id, local_display_name, active_user, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,0,?,?,?,?,?)" - (auId, displayName, activeUser, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, currentTs, currentTs) + "INSERT INTO users (agent_user_id, local_display_name, active_user, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,?,0,?,?,?,?,?)" + (auId, displayName, activeUser, order, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, currentTs, currentTs) userId <- insertedRowId db DB.execute db @@ -126,7 +126,7 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, (profileId, displayName, userId, True, currentTs, currentTs, currentTs) contactId <- insertedRowId db DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId) - pure $ toUser $ (userId, auId, contactId, profileId, activeUser, displayName, fullName, image, Nothing, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing) + pure $ toUser $ (userId, auId, contactId, profileId, activeUser, order, displayName, fullName, image, Nothing, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing) getUsersInfo :: DB.Connection -> IO [UserInfo] getUsersInfo db = getUsers db >>= mapM getUserInfo @@ -161,15 +161,19 @@ getUsers :: DB.Connection -> IO [User] getUsers db = map toUser <$> DB.query_ db userQuery -setActiveUser :: DB.Connection -> UserId -> IO () -setActiveUser db userId = do +setActiveUser :: DB.Connection -> User -> IO User +setActiveUser db user@User {userId} = do DB.execute_ db "UPDATE users SET active_user = 0" - DB.execute db "UPDATE users SET active_user = 1 WHERE user_id = ?" (Only userId) + activeOrder <- getNextActiveOrder db + DB.execute db "UPDATE users SET active_user = 1, active_order = ? WHERE user_id = ?" (activeOrder, userId) + pure user {activeUser = True, activeOrder} -getSetActiveUser :: DB.Connection -> UserId -> ExceptT StoreError IO User -getSetActiveUser db userId = do - liftIO $ setActiveUser db userId - getUser db userId +getNextActiveOrder :: DB.Connection -> IO Int64 +getNextActiveOrder db = do + order <- fromMaybe 0 . join <$> maybeFirstRow fromOnly (DB.query_ db "SELECT max(active_order) FROM users") + if order == maxBound + then 0 <$ DB.execute db "UPDATE users SET active_order = active_order - ?" (Only (maxBound :: Int64)) + else pure $ order + 1 getUser :: DB.Connection -> UserId -> ExceptT StoreError IO User getUser db userId = diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index a4c2d1da39..ba41cc47be 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -422,16 +422,16 @@ toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userCo userQuery :: Query userQuery = [sql| - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id |] -toUser :: (UserId, UserId, ContactId, ProfileId, Bool, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (Bool, Bool, Bool, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User -toUser ((userId, auId, userContactId, profileId, activeUser, displayName, fullName, image, contactLink, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = - User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes} +toUser :: (UserId, UserId, ContactId, ProfileId, Bool, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (Bool, Bool, Bool, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User +toUser ((userId, auId, userContactId, profileId, activeUser, activeOrder, displayName, fullName, image, contactLink, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = + User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, activeOrder, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes} where profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences = userPreferences, localAlias = ""} fullPreferences = mergePreferences Nothing userPreferences diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index fe40fcfcee..71fa1d98b9 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -113,6 +113,7 @@ data User = User profile :: LocalProfile, fullPreferences :: FullPreferences, activeUser :: Bool, + activeOrder :: Int64, viewPwdHash :: Maybe UserPwdHash, showNtfs :: Bool, sendRcptsContacts :: Bool, diff --git a/tests/MobileTests.hs b/tests/MobileTests.hs index cd4b3980eb..638d3d8078 100644 --- a/tests/MobileTests.hs +++ b/tests/MobileTests.hs @@ -94,10 +94,10 @@ activeUserExists = #endif activeUserExistsSwift :: LB.ByteString -activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}" +activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}" activeUserExistsTagged :: LB.ByteString -activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" +activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" activeUser :: LB.ByteString activeUser = @@ -108,10 +108,10 @@ activeUser = #endif activeUserSwift :: LB.ByteString -activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" +activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" activeUserTagged :: LB.ByteString -activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" +activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" chatStarted :: LB.ByteString chatStarted = @@ -184,7 +184,7 @@ pendingSubSummaryTagged :: LB.ByteString pendingSubSummaryTagged = "{\"resp\":{\"type\":\"pendingSubSummary\",\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}" userJSON :: LB.ByteString -userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" +userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" parsedMarkdown :: LB.ByteString parsedMarkdown = From 560b52167329f8064abfc95b213f8ae6cfe0931c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 21 Sep 2024 19:12:53 +0100 Subject: [PATCH 154/440] ios: scrolling user profiles (#4909) * ios: scrolling user profiles --------- Co-authored-by: Levitating Pineapple --- .../Shared/Views/ChatList/UserPicker.swift | 227 +++++++----------- .../Views/Helpers/StickyScrollView.swift | 52 ++++ .../Views/UserSettings/SettingsView.swift | 18 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + apps/ios/SimpleXChat/ChatTypes.swift | 1 + .../chat/simplex/common/model/ChatModel.kt | 1 + 6 files changed, 159 insertions(+), 144 deletions(-) create mode 100644 apps/ios/Shared/Views/Helpers/StickyScrollView.swift diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index efe54cb036..b3baa62f01 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -14,11 +14,20 @@ struct UserPicker: View { @Environment(\.colorScheme) private var colorScheme: ColorScheme @Environment(\.dismiss) private var dismiss: DismissAction @Binding var activeSheet: UserPickerSheet? + @State private var currentUser: Int64? @State private var switchingProfile = false + @State private var frameWidth: CGFloat = 0 + + // Inset grouped list dimensions + private let imageSize: CGFloat = 44 + private let rowPadding: CGFloat = 16 + private let sectionSpacing: CGFloat = 35 + private var sectionHorizontalPadding: CGFloat { frameWidth > 375 ? 20 : 16 } + private let sectionShape = RoundedRectangle(cornerRadius: 10, style: .continuous) var body: some View { if #available(iOS 16.0, *) { - let v = viewBody.presentationDetents([.height(420)]) + let v = viewBody.presentationDetents([.height(400)]) if #available(iOS 16.4, *) { v.scrollBounceBehavior(.basedOnSize) } else { @@ -28,88 +37,80 @@ struct UserPicker: View { viewBody } } - + + @ViewBuilder private var viewBody: some View { - let otherUsers = m.users.filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId } - return List { - Section(header: Text("You").foregroundColor(theme.colors.secondary)) { - if let user = m.currentUser { - openSheetOnTap(label: { - ZStack { - let v = ProfilePreview(profileOf: user) - .foregroundColor(.primary) - .padding(.leading, -8) - if #available(iOS 16.0, *) { - v + let otherUsers: [UserInfo] = m.users + .filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId } + .sorted(using: KeyPathComparator(\.user.activeOrder, order: .reverse)) + let sectionWidth = max(frameWidth - sectionHorizontalPadding * 2, 0) + let currentUserWidth = max(frameWidth - sectionHorizontalPadding - rowPadding * 2 - 14 - imageSize, 0) + VStack(spacing: 0) { + if let user = m.currentUser { + StickyScrollView { + HStack(spacing: rowPadding) { + HStack { + ProfileImage(imageStr: user.image, size: imageSize, color: Color(uiColor: .tertiarySystemGroupedBackground)) + .padding(.trailing, 6) + profileName(user).lineLimit(1) + } + .padding(rowPadding) + .frame(width: otherUsers.isEmpty ? sectionWidth : currentUserWidth, alignment: .leading) + .background(Color(.secondarySystemGroupedBackground)) + .clipShape(sectionShape) + .onTapGesture { activeSheet = .currentProfile } + ForEach(otherUsers) { u in + userView(u, size: imageSize) + .frame(maxWidth: sectionWidth * 0.618) + .fixedSize() + } + } + .padding(.horizontal, sectionHorizontalPadding) + } + .frame(height: 2 * rowPadding + imageSize) + .padding(.top, sectionSpacing) + .overlay(DetermineWidth()) + .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } + } + List { + Section { + openSheetOnTap("qrcode", title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", sheet: .address) + openSheetOnTap("switch.2", title: "Chat preferences", sheet: .chatPreferences) + openSheetOnTap("person.crop.rectangle.stack", title: "Your chat profiles", sheet: .chatProfiles) + openSheetOnTap("desktopcomputer", title: "Use from desktop", sheet: .useFromDesktop) + + ZStack(alignment: .trailing) { + openSheetOnTap("gearshape", title: "Settings", sheet: .settings) + Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill") + .resizable() + .symbolRenderingMode(.monochrome) + .foregroundColor(theme.colors.secondary) + .frame(maxWidth: 20, maxHeight: 20) + .onTapGesture { + if (colorScheme == .light) { + ThemeManager.applyTheme(systemDarkThemeDefault.get()) } else { - v.padding(.vertical, 4) + ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName) } } - }) { - activeSheet = .currentProfile - } - - openSheetOnTap(title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", icon: "qrcode") { - activeSheet = .address - } - - openSheetOnTap(title: "Chat preferences", icon: "switch.2") { - activeSheet = .chatPreferences - } - } - } - - Section { - if otherUsers.isEmpty { - openSheetOnTap(title: "Your chat profiles", icon: "person.crop.rectangle.stack") { - activeSheet = .chatProfiles - } - } else { - let v = userPickerRow(otherUsers, size: 44) - .padding(.leading, -11) - if #available(iOS 16.0, *) { - v - } else { - v.padding(.vertical, 4) - } - } - - openSheetOnTap(title: "Use from desktop", icon: "desktopcomputer") { - activeSheet = .useFromDesktop - } - - ZStack(alignment: .trailing) { - openSheetOnTap(title: "Settings", icon: "gearshape") { - activeSheet = .settings - } - Label {} icon: { - Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill") - .resizable() - .symbolRenderingMode(.monochrome) - .foregroundColor(theme.colors.secondary) - .frame(maxWidth: 20, maxHeight: 20) - } - .onTapGesture { - if (colorScheme == .light) { - ThemeManager.applyTheme(systemDarkThemeDefault.get()) - } else { - ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName) + .onLongPressGesture { + ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) } } - .onLongPressGesture { - ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) - } } } } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .onAppear { // This check prevents the call of listUsers after the app is suspended, and the database is closed. if case .active = scenePhase { + currentUser = m.currentUser?.userId Task { do { let users = try await listUsersAsync() - await MainActor.run { m.users = users } + await MainActor.run { + m.users = users + currentUser = m.currentUser?.userId + } } catch { logger.error("Error loading users \(responseError(error))") } @@ -119,71 +120,34 @@ struct UserPicker: View { .modifier(ThemedBackground(grouped: true)) .disabled(switchingProfile) } - - private func userPickerRow(_ users: [UserInfo], size: CGFloat) -> some View { - HStack(spacing: 6) { - let s = ScrollView(.horizontal) { - HStack(spacing: 27) { - ForEach(users) { u in - if !u.user.hidden && u.user.userId != m.currentUser?.userId { - userView(u, size: size) - } - } - } - .padding(.leading, 4) - .padding(.trailing, 22) - } - ZStack(alignment: .trailing) { - if #available(iOS 16.0, *) { - s.scrollIndicators(.hidden) - } else { - s - } - LinearGradient( - colors: [.clear, .black], - startPoint: .leading, - endPoint: .trailing - ) - .frame(width: size, height: size + 3) - .blendMode(.destinationOut) - .allowsHitTesting(false) - } - .compositingGroup() - .padding(.top, -3) // to fit unread badge - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(theme.colors.secondary) - .padding(.trailing, 4) - .onTapGesture { - activeSheet = .chatProfiles - } - } - } private func userView(_ u: UserInfo, size: CGFloat) -> some View { - ZStack(alignment: .topTrailing) { - ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) - .padding([.top, .trailing], 3) - if (u.unreadCount > 0) { - unreadBadge(u) + HStack { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) + if (u.unreadCount > 0) { + unreadBadge(u).offset(x: 4, y: -4) + } } + .padding(.trailing, 6) + Text(u.user.displayName).font(.title2).lineLimit(1) } - .frame(width: size) + .padding(rowPadding) + .background(Color(.secondarySystemGroupedBackground)) + .clipShape(sectionShape) .onTapGesture { switchingProfile = true + dismiss() Task { do { try await changeActiveUserAsync_(u.user.userId, viewPwd: nil) - await MainActor.run { - switchingProfile = false - dismiss() - } + await MainActor.run { switchingProfile = false } } catch { await MainActor.run { switchingProfile = false - AlertManager.shared.showAlertMsg( - title: "Error switching profile!", - message: "Error: \(responseError(error))" + showAlert( + NSLocalizedString("Error switching profile!", comment: "alertTitle"), + message: String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "alert message"), responseError(error)) ) } } @@ -191,21 +155,14 @@ struct UserPicker: View { } } - private func openSheetOnTap(title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View { - openSheetOnTap(label: { - ZStack(alignment: .leading) { - Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center) - .symbolRenderingMode(.monochrome) - .foregroundColor(theme.colors.secondary) - Text(title) - .foregroundColor(.primary) - .padding(.leading, 36) + private func openSheetOnTap(_ icon: String, title: LocalizedStringKey, sheet: UserPickerSheet) -> some View { + Button { + activeSheet = sheet + } label: { + settingsRow(icon, color: theme.colors.secondary) { + Text(title).foregroundColor(.primary) } - }, action: action) - } - - private func openSheetOnTap(label: () -> V, action: @escaping () -> Void) -> some View { - Button(action: action, label: label) + } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) } diff --git a/apps/ios/Shared/Views/Helpers/StickyScrollView.swift b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift new file mode 100644 index 0000000000..0ba539772f --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift @@ -0,0 +1,52 @@ +// +// StickyScrollView.swift +// SimpleX (iOS) +// +// Created by user on 20/09/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct StickyScrollView: UIViewRepresentable { + @ViewBuilder let content: () -> Content + + func makeUIView(context: Context) -> UIScrollView { + let hc = context.coordinator.hostingController + hc.view.backgroundColor = .clear + let sv = UIScrollView() + sv.showsHorizontalScrollIndicator = false + sv.addSubview(hc.view) + sv.delegate = context.coordinator + return sv + } + + func updateUIView(_ scrollView: UIScrollView, context: Context) { + let hc = context.coordinator.hostingController + hc.rootView = content() + hc.view.frame.size = hc.view.intrinsicContentSize + scrollView.contentSize = hc.view.intrinsicContentSize + } + + func makeCoordinator() -> Coordinator { + Coordinator(content: content()) + } + + class Coordinator: NSObject, UIScrollViewDelegate { + let hostingController: UIHostingController + + init(content: Content) { + self.hostingController = UIHostingController(rootView: content) + } + + func scrollViewWillEndDragging( + _ scrollView: UIScrollView, + withVelocity velocity: CGPoint, + targetContentOffset: UnsafeMutablePointer + ) { + if targetContentOffset.pointee.x < 64 { + targetContentOffset.pointee.x = 0 + } + } + } +} diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index f1140575b7..e018b181e6 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -507,18 +507,18 @@ struct ProfilePreview: View { HStack { ProfileImage(imageStr: profileOf.image, size: 44, color: color) .padding(.trailing, 6) - profileName().lineLimit(1) + profileName(profileOf).lineLimit(1) } } - - private func profileName() -> Text { - var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2) - if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { - t = t + Text(" (" + profileOf.fullName + ")") +} + +func profileName(_ profileOf: NamedChat) -> Text { + var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2) + if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { + t = t + Text(" (" + profileOf.fullName + ")") // .font(.callout) - } - return t - } + } + return t } struct SettingsView_Previews: PreviewProvider { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index a6bfd9d80e..a73b3febcd 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -204,6 +204,7 @@ CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; }; CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; }; CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; }; + CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */; }; CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; }; CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; }; CEE723B12C3BD3D70009AE93 /* SimpleX SE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -543,6 +544,7 @@ CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = ""; }; CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = ""; }; + CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = ""; }; CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = ""; }; CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; @@ -796,6 +798,7 @@ CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */, CE7548092C622630009579B7 /* SwipeLabel.swift */, CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */, + CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */, ); path = Helpers; sourceTree = ""; @@ -1496,6 +1499,7 @@ 5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */, 5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */, 5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */, + CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */, 5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */, 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */, 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */, diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 272e487214..0f319f2f9d 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -50,6 +50,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { profile: LocalProfile.sampleData, fullPreferences: FullPreferences.sampleData, activeUser: true, + activeOrder: 0, showNtfs: true, sendRcptsContacts: true, sendRcptsSmallGroups: false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 9f52694f91..8d942222c1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -834,6 +834,7 @@ data class User( profile = LocalProfile.sampleData, fullPreferences = FullChatPreferences.sampleData, activeUser = true, + activeOrder = 0, showNtfs = true, sendRcptsContacts = true, sendRcptsSmallGroups = false, From e79fa136a4ad19b715adb6359dbaaa0654c18d7d Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Sat, 21 Sep 2024 21:26:42 +0300 Subject: [PATCH 155/440] ios: fix keyboard loosing focus when forward search results are empty (#4895) --- apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 79ede14be5..587957cd5d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -19,7 +19,6 @@ struct ChatItemForwardingView: View { @Binding var composeState: ComposeState @State private var searchText: String = "" - @FocusState private var searchFocused @State private var alert: SomeAlert? private let chatsToForwardTo = filterChatsToForwardTo(chats: ChatModel.shared.chats) @@ -46,8 +45,6 @@ struct ChatItemForwardingView: View { VStack(alignment: .leading) { if !chatsToForwardTo.isEmpty { List { - searchFieldView(text: $searchText, focussed: $searchFocused, theme.colors.onBackground, theme.colors.secondary) - .padding(.leading, 2) let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { foundChat($0, s) } ForEach(chats) { chat in @@ -55,6 +52,7 @@ struct ChatItemForwardingView: View { .disabled(chatModel.deletedChats.contains(chat.chatInfo.id)) } } + .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always)) .modifier(ThemedBackground(grouped: true)) } else { ZStack { From 8a906485d1e28f9b39ef5f4c7d2c75f0773c538e Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Sat, 21 Sep 2024 23:33:18 +0300 Subject: [PATCH 156/440] ios: display year in chat for previous years (#4919) * ios: display year in chat for previous years * fix chat time, show past years in the list * style --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Views/Chat/ChatView.swift | 6 +++++- apps/ios/Shared/Views/ChatList/ChatPreviewView.swift | 7 ++++++- apps/ios/SimpleXChat/ChatTypes.swift | 9 ++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e7359587df..7f6b61c1ea 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -622,7 +622,11 @@ struct ChatView: View { Text(String.localizedStringWithFormat( NSLocalizedString("%@, %@", comment: "format for date separator in chat"), date.formatted(.dateTime.weekday(.abbreviated)), - date.formatted(.dateTime.day().month(.abbreviated)) + date.formatted( + Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) + ? .dateTime.day().month(.abbreviated) + : .dateTime.day().month(.abbreviated).year() + ) )) .font(.callout) .fontWeight(.medium) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index cf9977860d..d721d546c1 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -35,11 +35,16 @@ struct ChatPreviewView: View { } .padding(.leading, 4) + let chatTs = if let cItem { + cItem.meta.itemTs + } else { + chat.chatInfo.chatTs + } VStack(spacing: 0) { HStack(alignment: .top) { chatPreviewTitle() Spacer() - (cItem?.timestampText ?? formatTimestampText(chat.chatInfo.chatTs)) + (formatTimestampText(chatTs)) .font(.subheadline) .frame(minWidth: 60, alignment: .trailing) .foregroundColor(theme.colors.secondary) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 0f319f2f9d..0777503650 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2765,9 +2765,16 @@ public struct CITimed: Decodable, Hashable { let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) +let msgDateYearFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits).year(.twoDigits) public func formatTimestampText(_ date: Date) -> Text { - Text(verbatim: date.formatted(recent(date) ? msgTimeFormat : msgDateFormat)) + Text(verbatim: date.formatted( + recent(date) + ? msgTimeFormat + : Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) + ? msgDateFormat + : msgDateYearFormat + )) } public func formatTimestampMeta(_ date: Date) -> String { From 55d180466a4d6720184a2419014db88887c8b933 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Sat, 21 Sep 2024 20:34:13 +0000 Subject: [PATCH 157/440] docs: iOS notifications in FAQ (#4879) * docs: iOS notifications in FAQ * Update FAQ.md --- docs/FAQ.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index d8a8d5938f..932a4c33ee 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -148,14 +148,20 @@ Check battery settings for the app - it should be set to Unrestricted. For some devices, there may be additional options to prevent the app from being killed - e.g., on Xiaomi you need to enable Auto Start setting for the app. Please consult https://dontkillmyapp.com site for any additional settings for your device. -**iOS notifications failed to initialize correctly** +**Why my notifications aren't working on iOS** Check the color of the bolt icon next to Notifications in app settings - it should be green. If it's not, please open notifications, disable them (choose Off / Local), and then enable again - you should do it when you have Internet connection. +Check if your push server has been restarted at time of the issue (Notifications -> Push server) at https://status.simplex.chat if it has been restarted, you may not receive notifications from that time. + +If device was offline, you may need to open the app to start receiving notifications. + If the above didn't help, the reason could be that iOS failed to issue notification token - we have seen this issue several times. In this case, restarting the whole device should help. +In some cases notifications may still not work, iOS notifications are hard to do right in a decentralized app, we will be improving them soon to be more reliable. + **Messaging server or notification server is under maintenance** Please check the current status of preset servers at [https://status.simplex.chat](https://status.simplex.chat). You can also connect to status bot via QR code on that page - it will send the updates when the server is offline for maintenance, and also when the new versions of the app are released. From d5507f2fa35d7414178f68992e11767716d55ef8 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 23 Sep 2024 14:57:59 +0700 Subject: [PATCH 158/440] android, desktop: member name position depends on length (#4918) * android, desktop: member name position depends on length * maxWidth limit * fix * optimization * paddings --------- Co-authored-by: Evgeny Poberezkin --- .../simplex/common/views/chat/ChatView.kt | 117 ++++++++++-------- .../common/views/chat/item/ChatItemView.kt | 3 +- .../common/views/chat/item/FramedItemView.kt | 53 ++++++-- 3 files changed, 112 insertions(+), 61 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 835f884f98..18deb48597 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.* +import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -1033,25 +1034,6 @@ fun BoxWithConstraintsScope.ChatItemsList( // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view LocalViewConfiguration provides LocalViewConfiguration.current.bigTouchSlop() ) { - val dismissState = rememberDismissState(initialValue = DismissValue.Default) { - if (it == DismissValue.DismissedToStart) { - scope.launch { - if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local) { - if (composeState.value.editing) { - composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) - } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { - composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) - } - } - } - } - false - } - val swipeableModifier = SwipeToDismissModifier( - state = dismissState, - directions = setOf(DismissDirection.EndToStart), - swipeDistance = with(LocalDensity.current) { 30.dp.toPx() }, - ) val provider = { providerForGallery(i, chatModel.chatItems.value, cItem.id) { indexInReversed -> scope.launch { @@ -1066,16 +1048,35 @@ fun BoxWithConstraintsScope.ChatItemsList( val revealed = remember { mutableStateOf(false) } @Composable - fun ChatItemViewShortHand(cItem: ChatItem, range: IntRange?) { + fun ChatItemViewShortHand(cItem: ChatItem, range: IntRange?, fillMaxWidth: Boolean = true) { tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy) + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy) } } @Composable fun ChatItemView(cItem: ChatItem, range: IntRange?, prevItem: ChatItem?) { + val dismissState = rememberDismissState(initialValue = DismissValue.Default) { + if (it == DismissValue.DismissedToStart) { + scope.launch { + if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local) { + if (composeState.value.editing) { + composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) + } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { + composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) + } + } + } + } + false + } + val swipeableModifier = SwipeToDismissModifier( + state = dismissState, + directions = setOf(DismissDirection.EndToStart), + swipeDistance = with(LocalDensity.current) { 30.dp.toPx() }, + ) val sent = cItem.chatDir.sent Box(Modifier.padding(bottom = 4.dp)) { val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null && cItem.meta.itemForwarded == null @@ -1095,43 +1096,61 @@ fun BoxWithConstraintsScope.ChatItemsList( Column( Modifier .padding(top = 8.dp) - .padding(start = 8.dp, end = if (voiceWithTransparentBack) 12.dp else 66.dp), + .padding(start = 8.dp, end = if (voiceWithTransparentBack) 12.dp else 66.dp) + .fillMaxWidth() + .then(swipeableModifier), verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.Start ) { - if (cItem.content.showMemberName) { - val memberNameStyle = SpanStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary) - val memberNameString = if (memCount == 1 && member.memberRole > GroupMemberRole.Member) { - buildAnnotatedString { - withStyle(memberNameStyle.copy(fontWeight = FontWeight.Medium)) { append(member.memberRole.text) } - append(" ") - withStyle(memberNameStyle) { append(memberNames(member, prevMember, memCount)) } - } - } else { - buildAnnotatedString { - withStyle(memberNameStyle) { append(memberNames(member, prevMember, memCount)) } + @Composable + fun MemberNameAndRole() { + Row(Modifier.padding(bottom = 2.dp).graphicsLayer { translationX = selectionOffset.toPx() }, horizontalArrangement = Arrangement.SpaceBetween) { + Text( + memberNames(member, prevMember, memCount), + Modifier + .padding(start = MEMBER_IMAGE_SIZE + DEFAULT_PADDING_HALF) + .weight(1f, false), + fontSize = 13.5.sp, + color = MaterialTheme.colors.secondary, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + if (memCount == 1 && member.memberRole > GroupMemberRole.Member) { + Text( + member.memberRole.text, + Modifier.padding(start = DEFAULT_PADDING_HALF * 1.5f, end = DEFAULT_PADDING_HALF), + fontSize = 13.5.sp, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.secondary, + maxLines = 1 + ) } } - Text( - memberNameString, - Modifier.padding(start = MEMBER_IMAGE_SIZE + 10.dp), - maxLines = 2 - ) } - Box(contentAlignment = Alignment.CenterStart) { - androidx.compose.animation.AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { - SelectedChatItem(Modifier, cItem.id, selectedChatItems) - } - Row( - swipeableOrSelectionModifier, - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { - MemberImage(member) + + @Composable + fun Item() { + Box(Modifier.layoutId(CHAT_BUBBLE_LAYOUT_ID), contentAlignment = Alignment.CenterStart) { + androidx.compose.animation.AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { + SelectedChatItem(Modifier, cItem.id, selectedChatItems) + } + Row(Modifier.graphicsLayer { translationX = selectionOffset.toPx() }, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { + MemberImage(member) + } + ChatItemViewShortHand(cItem, range, false) } - ChatItemViewShortHand(cItem, range) } } + if (cItem.content.showMemberName) { + DependentLayout(Modifier, CHAT_BUBBLE_LAYOUT_ID) { + MemberNameAndRole() + Item() + } + } else { + Item() + } } } else { Box(contentAlignment = Alignment.CenterStart) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 516e47e7ed..df30e85161 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -51,6 +51,7 @@ fun ChatItemView( revealed: MutableState, range: IntRange?, selectedChatItems: MutableState?>, + fillMaxWidth: Boolean = true, selectChatItem: () -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit, deleteMessages: (List) -> Unit, @@ -83,7 +84,7 @@ fun ChatItemView( val live = composeState.value.liveMessage != null Box( - modifier = Modifier.fillMaxWidth(), + modifier = if (fillMaxWidth) Modifier.fillMaxWidth() else Modifier, contentAlignment = alignment, ) { val info = cItem.meta.itemStatus.statusInto diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index 8a579d5289..ddcf7c340b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -304,6 +304,7 @@ fun CIMarkdownText( } const val CHAT_IMAGE_LAYOUT_ID = "chatImage" +const val CHAT_BUBBLE_LAYOUT_ID = "chatBubble" /** * Equal to [androidx.compose.ui.unit.Constraints.MaxFocusMask], which is 0x3FFFF - 1 * Other values make a crash `java.lang.IllegalArgumentException: Can't represent a width of 123456 and height of 9909 in Constraints` @@ -311,23 +312,23 @@ const val CHAT_IMAGE_LAYOUT_ID = "chatImage" * */ const val MAX_SAFE_WIDTH = 0x3FFFF - 1 +/** + * Limiting max value for height + width in order to not crash the app, see [androidx.compose.ui.unit.Constraints.createConstraints] + * */ +private fun maxSafeHeight(width: Int) = when { // width bits + height bits should be <= 31 + width < 0x1FFF /*MaxNonFocusMask*/ -> 0x3FFFF - 1 /* MaxFocusMask */ // 13 bits width + 18 bits height + width < 0x7FFF /*MinNonFocusMask*/ -> 0xFFFF - 1 /* MinFocusMask */ // 15 bits width + 16 bits height + width < 0xFFFF /*MinFocusMask*/ -> 0x7FFF - 1 /* MinFocusMask */ // 16 bits width + 15 bits height + width < 0x3FFFF /*MaxFocusMask*/ -> 0x1FFF - 1 /* MaxNonFocusMask */ // 18 bits width + 13 bits height + else -> 0x1FFF // shouldn't happen since width is limited already +} + @Composable fun PriorityLayout( modifier: Modifier = Modifier, priorityLayoutId: String, content: @Composable () -> Unit ) { - /** - * Limiting max value for height + width in order to not crash the app, see [androidx.compose.ui.unit.Constraints.createConstraints] - * */ - fun maxSafeHeight(width: Int) = when { // width bits + height bits should be <= 31 - width < 0x1FFF /*MaxNonFocusMask*/ -> 0x3FFFF - 1 /* MaxFocusMask */ // 13 bits width + 18 bits height - width < 0x7FFF /*MinNonFocusMask*/ -> 0xFFFF - 1 /* MinFocusMask */ // 15 bits width + 16 bits height - width < 0xFFFF /*MinFocusMask*/ -> 0x7FFF - 1 /* MinFocusMask */ // 16 bits width + 15 bits height - width < 0x3FFFF /*MaxFocusMask*/ -> 0x1FFF - 1 /* MaxNonFocusMask */ // 18 bits width + 13 bits height - else -> 0x1FFF // shouldn't happen since width is limited already - } - Layout( content = content, modifier = modifier @@ -352,6 +353,36 @@ fun PriorityLayout( } } } + +@Composable +fun DependentLayout( + modifier: Modifier = Modifier, + mainLayoutId: String, + content: @Composable () -> Unit +) { + Layout( + content = content, + modifier = modifier + ) { measureable, constraints -> + // Find important element which should tell what min width it needs to draw itself. + // Expecting only one such element. Can be less than one but not more + val mainPlaceable = measureable.firstOrNull { it.layoutId == mainLayoutId }?.measure(constraints) + val placeables: List = measureable.map { + if (it.layoutId == mainLayoutId) + mainPlaceable!! + else + it.measure(constraints.copy(minWidth = mainPlaceable?.width ?: 0, maxWidth = min(MAX_SAFE_WIDTH, constraints.maxWidth))) } + val width = mainPlaceable?.measuredWidth ?: min(MAX_SAFE_WIDTH, placeables.maxOf { it.width }) + val height = minOf(maxSafeHeight(width), placeables.sumOf { it.height }) + layout(width, height) { + var y = 0 + placeables.forEach { + it.place(0, y) + y += it.measuredHeight + } + } + } +} /* class EditedProvider: PreviewParameterProvider { From 981cbb8bf935a1e2803dc000d0a0f18d499d7cf3 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 24 Aug 2024 14:00:56 +0000 Subject: [PATCH 159/440] android: target API level 34 (Android 14) (#4697) --- apps/multiplatform/android/build.gradle.kts | 22 +++++----- .../android/src/main/AndroidManifest.xml | 14 +++++- .../main/java/chat/simplex/app/CallService.kt | 44 ++++++++++++++++--- .../java/chat/simplex/app/MainActivity.kt | 2 +- .../main/java/chat/simplex/app/SimplexApp.kt | 5 ++- .../java/chat/simplex/app/SimplexService.kt | 36 ++++++++++++--- .../simplex/app/model/NtfManager.android.kt | 4 +- .../simplex/app/views/call/CallActivity.kt | 10 ++--- .../android/src/main/res/values/colors.xml | 1 - apps/multiplatform/common/build.gradle.kts | 16 +++---- .../res/drawable/edit_text_cursor.xml | 2 +- 11 files changed, 112 insertions(+), 44 deletions(-) diff --git a/apps/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts index 5c2c786a21..250616ea5c 100644 --- a/apps/multiplatform/android/build.gradle.kts +++ b/apps/multiplatform/android/build.gradle.kts @@ -15,7 +15,7 @@ android { namespace = "chat.simplex.app" minSdk = 26 //noinspection OldTargetApi - targetSdk = 33 + targetSdk = 34 // !!! // skip version code after release to F-Droid, as it uses two version codes versionCode = (extra["android.version_code"] as String).toInt() @@ -126,29 +126,29 @@ android { dependencies { implementation(project(":common")) - implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.core:core-ktx:1.13.1") //implementation("androidx.compose.ui:ui:${rootProject.extra["compose.version"] as String}") //implementation("androidx.compose.material:material:$compose_version") //implementation("androidx.compose.ui:ui-tooling-preview:$compose_version") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") - implementation("androidx.lifecycle:lifecycle-process:2.7.0") - implementation("androidx.activity:activity-compose:1.8.2") - val workVersion = "2.9.0" + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4") + implementation("androidx.lifecycle:lifecycle-process:2.8.4") + implementation("androidx.activity:activity-compose:1.9.1") + val workVersion = "2.9.1" implementation("androidx.work:work-runtime-ktx:$workVersion") implementation("androidx.work:work-multiprocess:$workVersion") - implementation("com.jakewharton:process-phoenix:2.2.0") + implementation("com.jakewharton:process-phoenix:3.0.0") //Camera Permission - implementation("com.google.accompanist:accompanist-permissions:0.23.0") + implementation("com.google.accompanist:accompanist-permissions:0.34.0") //implementation("androidx.compose.material:material-icons-extended:$compose_version") //implementation("androidx.compose.ui:ui-util:$compose_version") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") //androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version") debugImplementation("androidx.compose.ui:ui-tooling:1.6.4") } diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index 073f1bf8c8..deb5d83e5f 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -21,6 +21,12 @@ + + + + + + + android:stopWithTask="false" + android:foregroundServiceType="remoteMessaging" + /> @@ -141,7 +149,9 @@ android:name=".CallService" android:enabled="true" android:exported="false" - android:stopWithTask="false"/> + android:stopWithTask="false" + android:foregroundServiceType="mediaPlayback|microphone|camera|remoteMessaging" + /> = 34) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING + } else { + 0 + } + } else if (Build.VERSION.SDK_INT >= 30) { + if (call.supportsVideo()) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA + } else { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE + } + } else if (Build.VERSION.SDK_INT >= 29) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + } else { + 0 + } } private fun createNotificationChannel(): NotificationManager? { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index 5a69d282b4..c63b6cb497 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -54,7 +54,7 @@ class MainActivity: FragmentActivity() { SimplexApp.context.schedulePeriodicWakeUp() } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) processIntent(intent) processExternalIntent(intent) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 4d3b390189..1ce30f6435 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -119,7 +119,10 @@ class SimplexApp: Application(), LifecycleEventObserver { * */ if (chatModel.chatRunning.value != false && chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && - appPrefs.notificationsMode.get() == NotificationsMode.SERVICE + appPrefs.notificationsMode.get() == NotificationsMode.SERVICE && + // New installation passes all checks above and tries to start the service which is not needed at all + // because preferred notification type is not yet chosen. So, check that the user has initialized db already + appPrefs.newDatabaseInitialized.get() ) { SimplexService.start() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index 7fc1bd151c..ce3f0825b8 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.* import android.content.* import android.content.pm.PackageManager +import android.content.pm.ServiceInfo import android.net.Uri import android.os.* import android.os.SystemClock @@ -15,8 +16,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat import androidx.core.content.ContextCompat import androidx.work.* +import chat.simplex.app.model.NtfManager import chat.simplex.common.AppLock import chat.simplex.common.helpers.requiresIgnoringBattery import chat.simplex.common.model.ChatController @@ -52,18 +55,15 @@ class SimplexService: Service() { } else { Log.d(TAG, "null intent. Probably restarted by the system.") } - startForeground(SIMPLEX_SERVICE_ID, serviceNotification) + ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType()) return START_STICKY // to restart if killed } override fun onCreate() { super.onCreate() Log.d(TAG, "Simplex service created") - val title = generalGetString(MR.strings.simplex_service_notification_title) - val text = generalGetString(MR.strings.simplex_service_notification_text) - notificationManager = createNotificationChannel() - serviceNotification = createNotification(title, text) - startForeground(SIMPLEX_SERVICE_ID, serviceNotification) + createNotificationIfNeeded() + ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType()) /** * The reason [stopAfterStart] exists is because when the service is not called [startForeground] yet, and * we call [stopSelf] on the same service, [ForegroundServiceDidNotStartInTimeException] will be thrown. @@ -103,6 +103,26 @@ class SimplexService: Service() { super.onDestroy() } + private fun createNotificationIfNeeded(): Notification { + val ntf = serviceNotification + if (ntf != null) return ntf + + val title = generalGetString(MR.strings.simplex_service_notification_title) + val text = generalGetString(MR.strings.simplex_service_notification_text) + notificationManager = createNotificationChannel() + val newNtf = createNotification(title, text) + serviceNotification = newNtf + return newNtf + } + + private fun foregroundServiceType(): Int { + return if (Build.VERSION.SDK_INT >= 34) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING + } else { + 0 + } + } + private fun startService() { Log.d(TAG, "SimplexService startService") if (wakeLock != null || isCheckingNewMessages) return @@ -292,6 +312,10 @@ class SimplexService: Service() { } private suspend fun serviceAction(action: Action) { + if (!NtfManager.areNotificationsEnabledInSystem()) { + Log.d(TAG, "SimplexService serviceAction: ${action.name}. Notifications are not enabled in OS yet, not starting service") + return + } Log.d(TAG, "SimplexService serviceAction: ${action.name}") withContext(Dispatchers.IO) { Intent(androidAppContext, SimplexService::class.java).also { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt index 417a81a953..cf19589d4a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt @@ -53,7 +53,7 @@ object NtfManager { private val msgNtfTimeoutMs = 30000L init { - if (manager.areNotificationsEnabled()) createNtfChannelsMaybeShowAlert() + if (areNotificationsEnabledInSystem()) createNtfChannelsMaybeShowAlert() } private fun callNotificationChannel(channelId: String, channelName: String): NotificationChannel { @@ -287,6 +287,8 @@ object NtfManager { } } + fun areNotificationsEnabledInSystem() = manager.areNotificationsEnabled() + /** * This function creates notifications channels. On Android 13+ calling it for the first time will trigger system alert, * The alert asks a user to allow or disallow to show notifications for the app. That's why it should be called only when the user diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index 323eb4417b..a9697069c0 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -120,6 +120,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { return grantedAudio && grantedCamera } + @Deprecated("Was deprecated in OS") override fun onBackPressed() { if (isOnLockScreenNow()) { super.onBackPressed() @@ -139,6 +140,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { } override fun onUserLeaveHint() { + super.onUserLeaveHint() // On Android 12+ PiP is enabled automatically when a user hides the app if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) { enterPictureInPictureMode() @@ -248,6 +250,9 @@ fun CallActivityView() { ) if (permissionsState.allPermissionsGranted) { ActiveCallView() + LaunchedEffect(Unit) { + activity.startServiceAndBind() + } } else { CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) { withBGApi { chatModel.callManager.endCall(call) } @@ -285,11 +290,6 @@ fun CallActivityView() { AlertManager.shared.showInView() } } - LaunchedEffect(call == null) { - if (call != null) { - activity.startServiceAndBind() - } - } LaunchedEffect(invitation, call, switchingCall, showCallView) { if (!switchingCall && invitation == null && (!showCallView || call == null)) { Log.d(TAG, "CallActivityView: finishing activity") diff --git a/apps/multiplatform/android/src/main/res/values/colors.xml b/apps/multiplatform/android/src/main/res/values/colors.xml index e1a994e57f..1833a6d9a3 100644 --- a/apps/multiplatform/android/src/main/res/values/colors.xml +++ b/apps/multiplatform/android/src/main/res/values/colors.xml @@ -2,6 +2,5 @@ #FF000000 #FFFFFFFF - #8b8786 #121212 \ No newline at end of file diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index dd9c7ab161..1670672753 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -61,8 +61,8 @@ kotlin { val androidMain by getting { kotlin.srcDir("build/generated/moko/androidMain/src") dependencies { - implementation("androidx.activity:activity-compose:1.8.2") - val workVersion = "2.9.0" + implementation("androidx.activity:activity-compose:1.9.1") + val workVersion = "2.9.1" implementation("androidx.work:work-runtime-ktx:$workVersion") implementation("com.google.accompanist:accompanist-insets:0.30.1") @@ -78,22 +78,22 @@ kotlin { //Camera Permission implementation("com.google.accompanist:accompanist-permissions:0.34.0") - implementation("androidx.webkit:webkit:1.10.0") + implementation("androidx.webkit:webkit:1.11.0") // GIFs support implementation("io.coil-kt:coil-compose:2.6.0") implementation("io.coil-kt:coil-gif:2.6.0") - implementation("com.jakewharton:process-phoenix:2.2.0") + implementation("com.jakewharton:process-phoenix:3.0.0") - val cameraXVersion = "1.3.2" + val cameraXVersion = "1.3.4" implementation("androidx.camera:camera-core:${cameraXVersion}") implementation("androidx.camera:camera-camera2:${cameraXVersion}") implementation("androidx.camera:camera-lifecycle:${cameraXVersion}") implementation("androidx.camera:camera-view:${cameraXVersion}") // Calls lifecycle listener - implementation("androidx.lifecycle:lifecycle-process:2.4.1") + implementation("androidx.lifecycle:lifecycle-process:2.8.4") } } val desktopMain by getting { @@ -119,8 +119,8 @@ android { defaultConfig { minSdk = 26 } - testOptions.targetSdk = 33 - lint.targetSdk = 33 + testOptions.targetSdk = 34 + lint.targetSdk = 34 val isAndroid = gradle.startParameter.taskNames.find { val lower = it.lowercase() lower.contains("release") || lower.startsWith("assemble") || lower.startsWith("install") diff --git a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml index 683c3a4dd4..948ae4d4bf 100644 --- a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml +++ b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml @@ -1,5 +1,5 @@ - + From 1f226dda64ee3146e0b992fee2d0ee08df7b2947 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:25:14 +0700 Subject: [PATCH 160/440] android: fix status bar color after hiding call (#4928) * android: fix status bar color after hiding call * dark status bar in call --- .../main/java/chat/simplex/app/SimplexApp.kt | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 6adaa1d4e0..40e8ffa9bc 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -287,13 +287,23 @@ class SimplexApp: Application(), LifecycleEventObserver { // Blend status bar color to the animated color val colors = CurrentColors.value.colors val baseBackgroundColor = if (toolbarOnTop) colors.background.mixWith(colors.onBackground, 0.97f) else colors.background - window.statusBarColor = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb() - val navBar = navBarColor.toArgb() + var statusBar = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb() + var statusBarLight = isLight + // SimplexGreen while in call + if (window.statusBarColor == SimplexGreen.toArgb()) { + statusBarColorAfterCall.intValue = statusBar + statusBar = SimplexGreen.toArgb() + statusBarLight = false + } + window.statusBarColor = statusBar + val navBar = navBarColor.toArgb() + if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) { + windowInsetController?.isAppearanceLightStatusBars = statusBarLight + } if (window.navigationBarColor != navBar) { window.navigationBarColor = navBar } - if (windowInsetController?.isAppearanceLightNavigationBars != isLight) { windowInsetController?.isAppearanceLightNavigationBars = isLight } @@ -313,11 +323,13 @@ class SimplexApp: Application(), LifecycleEventObserver { backgroundColor } }).toArgb() + var statusBarLight = isLight // SimplexGreen while in call if (window.statusBarColor == SimplexGreen.toArgb()) { statusBarColorAfterCall.intValue = statusBar statusBar = SimplexGreen.toArgb() + statusBarLight = false } val navBar = (if (hasBottom && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { backgroundColor.mixWith(CurrentColors.value.colors.onBackground, 0.97f) @@ -327,8 +339,8 @@ class SimplexApp: Application(), LifecycleEventObserver { if (window.statusBarColor != statusBar) { window.statusBarColor = statusBar } - if (windowInsetController?.isAppearanceLightStatusBars != isLight) { - windowInsetController?.isAppearanceLightStatusBars = isLight + if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) { + windowInsetController?.isAppearanceLightStatusBars = statusBarLight } if (window.navigationBarColor != navBar) { window.navigationBarColor = navBar From 0f301adc57c199a7d68961d8643fde9e8f5de2a7 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 24 Sep 2024 09:25:41 +0100 Subject: [PATCH 161/440] core: xrcp encryption with forward secrecy (#4926) * core: xrcp encryption with forward secrecy (tests intermittently fail) * track and correlate keys * simplify * refactor * remove comment --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Remote.hs | 49 +++++++++-------- src/Simplex/Chat/Remote/Protocol.hs | 79 +++++++++++++++------------- src/Simplex/Chat/Remote/Transport.hs | 23 ++++---- src/Simplex/Chat/Remote/Types.hs | 54 +++++++++++++++---- 6 files changed, 127 insertions(+), 82 deletions(-) diff --git a/cabal.project b/cabal.project index f2a652d721..96208d329a 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: bef11e4cbe0a3776f0910375f2adb60399043835 + tag: 04da670d00e904149040b136ebdea112de71e218 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 730a0f9501..d9d2d27013 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."bef11e4cbe0a3776f0910375f2adb60399043835" = "195hir9crv51iyli8yjk8sivk7wagxxjfmhzq6ahydvbkrpd3258"; + "https://github.com/simplex-chat/simplexmq.git"."04da670d00e904149040b136ebdea112de71e218" = "0kbpd56b3b8dy72xicfdqmiywxabq000125523dgmn3a5pjagc08"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 76d3754f17..0d39951b90 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -54,6 +54,7 @@ import Simplex.Chat.Util (encryptFile, liftIOEither) import Simplex.FileTransfer.Description (FileDigest (..)) import Simplex.Messaging.Agent import Simplex.Messaging.Agent.Protocol (AgentErrorType (RCP)) +import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String (StrEncoding (..)) @@ -497,31 +498,32 @@ handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQue handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do logDebug "handleRemoteCommand" liftIO (tryRemoteError' parseRequest) >>= \case - Right (getNext, rc) -> do + Right (rfKN, getNext, rc) -> do chatReadVar' currentUser >>= \case Nothing -> replyError $ ChatError CENoActiveUser - Just user -> processCommand user getNext rc `catchChatError'` replyError + Just user -> processCommand user rfKN getNext rc `catchChatError'` replyError Left e -> reply $ RRProtocolError e where - parseRequest :: ExceptT RemoteProtocolError IO (GetChunk, RemoteCommand) + parseRequest :: ExceptT RemoteProtocolError IO (C.SbKeyNonce, GetChunk, RemoteCommand) parseRequest = do - (header, getNext) <- parseDecryptHTTP2Body encryption request reqBody - (getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header) + (rfKN, header, getNext) <- parseDecryptHTTP2Body encryption request reqBody + (rfKN,getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header) replyError = reply . RRChatResponse . CRChatCmdError Nothing - processCommand :: User -> GetChunk -> RemoteCommand -> CM () - processCommand user getNext = \case + processCommand :: User -> C.SbKeyNonce -> GetChunk -> RemoteCommand -> CM () + processCommand user rfKN getNext = \case RCSend {command} -> lift $ handleSend execChatCommand command >>= reply RCRecv {wait = time} -> lift $ liftIO (handleRecv time remoteOutputQ) >>= reply - RCStoreFile {fileName, fileSize, fileDigest} -> lift $ handleStoreFile encryption fileName fileSize fileDigest getNext >>= reply - RCGetFile {file} -> handleGetFile encryption user file replyWith + RCStoreFile {fileName, fileSize, fileDigest} -> lift $ handleStoreFile rfKN fileName fileSize fileDigest getNext >>= reply + RCGetFile {file} -> handleGetFile user file replyWith reply :: RemoteResponse -> CM' () - reply = (`replyWith` \_ -> pure ()) + reply = (`replyWith` \_ _ -> pure ()) replyWith :: Respond - replyWith rr attach = - liftIO (tryRemoteError' . encryptEncodeHTTP2Body encryption $ J.encode rr) >>= \case + replyWith rr attach = do + (corrId, cmdKN, sfKN) <- atomically $ getRemoteSndKeys encryption + liftIO (tryRemoteError' . encryptEncodeHTTP2Body corrId cmdKN encryption $ J.encode rr) >>= \case Right resp -> liftIO . sendResponse . responseStreaming N.status200 [] $ \send flush -> do send resp - attach send + attach sfKN send flush Left e -> toView' . CRChatError Nothing . ChatErrorRemoteCtrl $ RCEProtocolError e @@ -532,7 +534,7 @@ type GetChunk = Int -> IO ByteString type SendChunk = Builder -> IO () -type Respond = RemoteResponse -> (SendChunk -> IO ()) -> CM' () +type Respond = RemoteResponse -> (C.SbKeyNonce -> SendChunk -> IO ()) -> CM' () liftRC :: ExceptT RemoteProtocolError IO a -> CM a liftRC = liftError (ChatErrorRemoteCtrl . RCEProtocolError) @@ -559,8 +561,8 @@ handleRecv time events = do -- TODO this command could remember stored files and return IDs to allow removing files that are not needed. -- Also, there should be some process removing unused files uploaded to remote host (possibly, all unused files). -handleStoreFile :: RemoteCrypto -> FilePath -> Word32 -> FileDigest -> GetChunk -> CM' RemoteResponse -handleStoreFile encryption fileName fileSize fileDigest getChunk = +handleStoreFile :: C.SbKeyNonce -> FilePath -> Word32 -> FileDigest -> GetChunk -> CM' RemoteResponse +handleStoreFile rfKN fileName fileSize fileDigest getChunk = either RRProtocolError RRFileStored <$> (chatReadVar' filesFolder >>= storeFile) where storeFile :: Maybe FilePath -> CM' (Either RemoteProtocolError FilePath) @@ -570,11 +572,11 @@ handleStoreFile encryption fileName fileSize fileDigest getChunk = storeFileTo :: FilePath -> CM' (Either RemoteProtocolError FilePath) storeFileTo dir = liftIO . tryRemoteError' $ do filePath <- liftIO $ dir `uniqueCombine` fileName - receiveEncryptedFile encryption getChunk fileSize fileDigest filePath + receiveEncryptedFile rfKN getChunk fileSize fileDigest filePath pure filePath -handleGetFile :: RemoteCrypto -> User -> RemoteFile -> Respond -> CM () -handleGetFile encryption User {userId} RemoteFile {userId = commandUserId, fileId, sent, fileSource = cf'@CryptoFile {filePath}} reply = do +handleGetFile :: User -> RemoteFile -> Respond -> CM () +handleGetFile User {userId} RemoteFile {userId = commandUserId, fileId, sent, fileSource = cf'@CryptoFile {filePath}} reply = do logDebug $ "GetFile: " <> tshow filePath unless (userId == commandUserId) $ throwChatError $ CEDifferentActiveUser {commandUserId, activeUserId = userId} path <- maybe filePath ( filePath) <$> chatReadVar filesFolder @@ -582,11 +584,12 @@ handleGetFile encryption User {userId} RemoteFile {userId = commandUserId, fileI cf <- getLocalCryptoFile db commandUserId fileId sent unless (cf == cf') $ throwError $ SEFileNotFound fileId liftRC (tryRemoteError $ getFileInfo path) >>= \case - Left e -> lift $ reply (RRProtocolError e) $ \_ -> pure () + Left e -> lift $ reply (RRProtocolError e) $ \_ _ -> pure () Right (fileSize, fileDigest) -> - ExceptT . withFile path ReadMode $ \h -> runExceptT $ do - encFile <- liftRC $ prepareEncryptedFile encryption (h, fileSize) - lift $ reply RRFile {fileSize, fileDigest} $ sendEncryptedFile encFile + lift . withFile path ReadMode $ \h -> do + reply RRFile {fileSize, fileDigest} $ \sfKN send -> void . runExceptT $ do + encFile <- prepareEncryptedFile sfKN (h, fileSize) + liftIO $ sendEncryptedFile encFile send listRemoteCtrls :: CM [RemoteCtrlInfo] listRemoteCtrls = do diff --git a/src/Simplex/Chat/Remote/Protocol.hs b/src/Simplex/Chat/Remote/Protocol.hs index fe07a940ae..00fc56f897 100644 --- a/src/Simplex/Chat/Remote/Protocol.hs +++ b/src/Simplex/Chat/Remote/Protocol.hs @@ -43,6 +43,8 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..)) import Simplex.Messaging.Crypto.Lazy (LazyByteString) import Simplex.Messaging.Encoding import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON, pattern SingleFieldJSONTag, pattern TaggedObjectJSONData, pattern TaggedObjectJSONTag) +import qualified Simplex.Messaging.TMap as TM +import Simplex.Messaging.Transport (TSbChainKeys) import Simplex.Messaging.Transport.Buffer (getBuffered) import Simplex.Messaging.Transport.HTTP2 (HTTP2Body (..), HTTP2BodyChunk, getBodyChunk) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, HTTP2Response (..), closeHTTP2Client, sendRequestDirect) @@ -77,11 +79,9 @@ $(deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse) mkRemoteHostClient :: HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> CM RemoteHostClient mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles} = do - drg <- asks random - counter <- newTVarIO 1 - let HostSessKeys {hybridKey, idPrivKey, sessPrivKey} = sessionKeys + let HostSessKeys {chainKeys, idPrivKey, sessPrivKey} = sessionKeys signatures = RSSign {idPrivKey, sessPrivKey} - encryption = RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} + encryption <- liftIO $ mkRemoteCrypto sessionCode chainKeys signatures pure RemoteHostClient { hostEncoding = encoding, @@ -93,11 +93,16 @@ mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {enc } mkCtrlRemoteCrypto :: CtrlSessKeys -> SessionCode -> CM RemoteCrypto -mkCtrlRemoteCrypto CtrlSessKeys {hybridKey, idPubKey, sessPubKey} sessionCode = do - drg <- asks random - counter <- newTVarIO 1 +mkCtrlRemoteCrypto CtrlSessKeys {chainKeys, idPubKey, sessPubKey} sessionCode = let signatures = RSVerify {idPubKey, sessPubKey} - pure RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} + in liftIO $ mkRemoteCrypto sessionCode chainKeys signatures + +mkRemoteCrypto :: SessionCode -> TSbChainKeys -> RemoteSignatures -> IO RemoteCrypto +mkRemoteCrypto sessionCode chainKeys signatures = do + sndCounter <- newTVarIO 0 + rcvCounter <- newTVarIO 0 + skippedKeys <- liftIO TM.emptyIO + pure RemoteCrypto {sessionCode, sndCounter, rcvCounter, chainKeys, skippedKeys, signatures} closeRemoteHostClient :: RemoteHostClient -> IO () closeRemoteHostClient RemoteHostClient {httpClient} = closeHTTP2Client httpClient @@ -125,26 +130,30 @@ remoteStoreFile c localPath fileName = do r -> badResponse r remoteGetFile :: RemoteHostClient -> FilePath -> RemoteFile -> ExceptT RemoteProtocolError IO () -remoteGetFile c@RemoteHostClient {encryption} destDir rf@RemoteFile {fileSource = CryptoFile {filePath}} = +remoteGetFile c destDir rf@RemoteFile {fileSource = CryptoFile {filePath}} = sendRemoteCommand c Nothing RCGetFile {file = rf} >>= \case - (getChunk, RRFile {fileSize, fileDigest}) -> do + (rfKN, getChunk, RRFile {fileSize, fileDigest}) -> do -- TODO we could optimize by checking size and hash before receiving the file let localPath = destDir takeFileName filePath - receiveEncryptedFile encryption getChunk fileSize fileDigest localPath - (_, r) -> badResponse r + receiveEncryptedFile rfKN getChunk fileSize fileDigest localPath + (_, _, r) -> badResponse r -- TODO validate there is no attachment in response sendRemoteCommand' :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO RemoteResponse -sendRemoteCommand' c attachment_ rc = snd <$> sendRemoteCommand c attachment_ rc +sendRemoteCommand' c attachment_ rc = do + (_, _, r) <- sendRemoteCommand c attachment_ rc + pure r -sendRemoteCommand :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO (Int -> IO ByteString, RemoteResponse) +sendRemoteCommand :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO (C.SbKeyNonce, Int -> IO ByteString, RemoteResponse) sendRemoteCommand RemoteHostClient {httpClient, hostEncoding, encryption} file_ cmd = do - encFile_ <- mapM (prepareEncryptedFile encryption) file_ - req <- httpRequest encFile_ <$> encryptEncodeHTTP2Body encryption (J.encode cmd) + (corrId, cmdKN, sfKN) <- atomically $ getRemoteSndKeys encryption + encCmd <- encryptEncodeHTTP2Body corrId cmdKN encryption $ J.encode cmd + encFile_ <- mapM (prepareEncryptedFile sfKN) file_ + let req = httpRequest encFile_ encCmd HTTP2Response {response, respBody} <- liftError' (RPEHTTP2 . tshow) $ sendRequestDirect httpClient req Nothing - (header, getNext) <- parseDecryptHTTP2Body encryption response respBody + (rfKN, header, getNext) <- parseDecryptHTTP2Body encryption response respBody rr <- liftEitherWith (RPEInvalidJSON . fromString) $ J.eitherDecode header >>= JT.parseEither J.parseJSON . convertJSON hostEncoding localEncoding - pure (getNext, rr) + pure (rfKN, getNext, rr) where httpRequest encFile_ cmdBld = H.requestStreaming N.methodPost "/" mempty $ \send flush -> do send cmdBld @@ -213,13 +222,11 @@ pattern OwsfTag = (SingleFieldJSONTag, J.Bool True) -- See https://github.com/simplex-chat/simplexmq/blob/master/rfcs/2023-10-25-remote-control.md for encoding -encryptEncodeHTTP2Body :: RemoteCrypto -> LazyByteString -> ExceptT RemoteProtocolError IO Builder -encryptEncodeHTTP2Body RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} s = do - corrId <- atomically $ stateTVar counter $ \c -> (c, c + 1) - let pfx = smpEncode (sessionCode, corrId) - (nonce, ct) <- liftError PRERemoteControl $ RC.rcEncryptBody drg hybridKey $ LB.fromStrict pfx <> s +encryptEncodeHTTP2Body :: Word32 -> C.SbKeyNonce -> RemoteCrypto -> LazyByteString -> ExceptT RemoteProtocolError IO Builder +encryptEncodeHTTP2Body corrId cmdKN RemoteCrypto {sessionCode, signatures} s = do + ct <- liftError PRERemoteControl $ RC.rcEncryptBody cmdKN $ LB.fromStrict (smpEncode sessionCode) <> s let ctLen = encodeWord32 (fromIntegral $ LB.length ct) - signed = LB.fromStrict (smpEncode nonce <> ctLen) <> ct + signed = LB.fromStrict (encodeWord32 corrId <> ctLen) <> ct sigs <- bodySignatures signed pure $ lazyByteString signed <> sigs where @@ -235,24 +242,25 @@ encryptEncodeHTTP2Body RemoteCrypto {drg, counter, sessionCode, hybridKey, signa sign k = C.signatureBytes . C.sign' k . BA.convert . CH.hashFinalize -- | Parse and decrypt HTTP2 request/response -parseDecryptHTTP2Body :: HTTP2BodyChunk a => RemoteCrypto -> a -> HTTP2Body -> ExceptT RemoteProtocolError IO (LazyByteString, Int -> IO ByteString) -parseDecryptHTTP2Body RemoteCrypto {hybridKey, sessionCode, signatures} hr HTTP2Body {bodyBuffer} = do - (nonce, ct) <- getBody - s <- liftError PRERemoteControl $ RC.rcDecryptBody hybridKey nonce ct - (,getNext) <$> parseBody s +parseDecryptHTTP2Body :: HTTP2BodyChunk a => RemoteCrypto -> a -> HTTP2Body -> ExceptT RemoteProtocolError IO (C.SbKeyNonce, LazyByteString, Int -> IO ByteString) +parseDecryptHTTP2Body rc@RemoteCrypto {sessionCode, signatures} hr HTTP2Body {bodyBuffer} = do + (corrId, ct) <- getBody + (cmdKN, rfKN) <- ExceptT $ atomically $ getRemoteRcvKeys rc corrId + s <- liftError PRERemoteControl $ RC.rcDecryptBody cmdKN ct + s' <- parseBody s + pure (rfKN, s', getNext) where - getBody :: ExceptT RemoteProtocolError IO (C.CbNonce, LazyByteString) + getBody :: ExceptT RemoteProtocolError IO (Word32, LazyByteString) getBody = do - nonceStr <- liftIO $ getNext 24 - nonce <- liftEitherWith RPEInvalidBody $ smpDecode nonceStr + corrIdStr <- liftIO $ getNext 4 ctLenStr <- liftIO $ getNext 4 let ctLen = decodeWord32 ctLenStr when (ctLen > fromIntegral (maxBound :: Int)) $ throwError RPEInvalidSize chunks <- liftIO $ getLazy $ fromIntegral ctLen - let hc = CH.hashUpdates (CH.hashInit @SHA512) [nonceStr, ctLenStr] + let hc = CH.hashUpdates (CH.hashInit @SHA512) [corrIdStr, ctLenStr] hc' = CH.hashUpdates hc chunks verifySignatures hc' - pure (nonce, LB.fromChunks chunks) + pure (decodeWord32 corrIdStr, LB.fromChunks chunks) getLazy :: Int -> IO [ByteString] getLazy 0 = pure [] getLazy n = do @@ -279,9 +287,8 @@ parseDecryptHTTP2Body RemoteCrypto {hybridKey, sessionCode, signatures} hr HTTP2 parseBody s = case LB.uncons s of Nothing -> throwError $ RPEInvalidBody "empty body" Just (scLen, rest) -> do - (sessCode', rest') <- takeBytes (fromIntegral scLen) rest + (sessCode', s') <- takeBytes (fromIntegral scLen) rest unless (sessCode' == sessionCode) $ throwError PRESessionCode - (_corrId, s') <- takeBytes 8 rest' pure s' where takeBytes n s' = do diff --git a/src/Simplex/Chat/Remote/Transport.hs b/src/Simplex/Chat/Remote/Transport.hs index 774aeccda2..933936aa4d 100644 --- a/src/Simplex/Chat/Remote/Transport.hs +++ b/src/Simplex/Chat/Remote/Transport.hs @@ -1,5 +1,6 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} module Simplex.Chat.Remote.Transport where @@ -20,27 +21,25 @@ import Simplex.RemoteControl.Types (RCErrorType (..)) import UnliftIO import UnliftIO.Directory (getFileSize) -type EncryptedFile = ((Handle, Word32), C.CbNonce, LC.SbState) +type EncryptedFile = ((Handle, Word32), LC.SbState) -prepareEncryptedFile :: RemoteCrypto -> (Handle, Word32) -> ExceptT RemoteProtocolError IO EncryptedFile -prepareEncryptedFile RemoteCrypto {drg, hybridKey} f = do - nonce <- atomically $ C.randomCbNonce drg - sbState <- liftEitherWith (const $ PRERemoteControl RCEEncrypt) $ LC.kcbInit hybridKey nonce - pure (f, nonce, sbState) +prepareEncryptedFile :: C.SbKeyNonce -> (Handle, Word32) -> ExceptT RemoteProtocolError IO EncryptedFile +prepareEncryptedFile (sk, nonce) f = do + sbState <- liftEitherWith (const $ PRERemoteControl RCEEncrypt) $ LC.sbInit sk nonce + pure (f, sbState) sendEncryptedFile :: EncryptedFile -> (Builder -> IO ()) -> IO () -sendEncryptedFile ((h, sz), nonce, sbState) send = do - send $ byteString $ smpEncode ('\x01', nonce, sz + fromIntegral C.authTagSize) +sendEncryptedFile ((h, sz), sbState) send = do + send $ byteString $ smpEncode ('\x01', sz + fromIntegral C.authTagSize) sendEncFile h send sbState sz -receiveEncryptedFile :: RemoteCrypto -> (Int -> IO ByteString) -> Word32 -> FileDigest -> FilePath -> ExceptT RemoteProtocolError IO () -receiveEncryptedFile RemoteCrypto {hybridKey} getChunk fileSize fileDigest toPath = do +receiveEncryptedFile :: C.SbKeyNonce -> (Int -> IO ByteString) -> Word32 -> FileDigest -> FilePath -> ExceptT RemoteProtocolError IO () +receiveEncryptedFile (sk, nonce) getChunk fileSize fileDigest toPath = do c <- liftIO $ getChunk 1 unless (c == "\x01") $ throwError RPENoFile - nonce <- liftError' RPEInvalidBody $ smpDecode <$> getChunk 24 size <- liftError' RPEInvalidBody $ smpDecode <$> getChunk 4 unless (size == fileSize + fromIntegral C.authTagSize) $ throwError RPEFileSize - sbState <- liftEitherWith (const $ PRERemoteControl RCEDecrypt) $ LC.kcbInit hybridKey nonce + sbState <- liftEitherWith (const $ PRERemoteControl RCEDecrypt) $ LC.sbInit sk nonce liftError' fErr $ withFile toPath WriteMode $ \h -> receiveSbFile getChunk h sbState fileSize digest <- liftIO $ LC.sha512Hash <$> LB.readFile toPath unless (FileDigest digest == fileDigest) $ throwError RPEFileDigest diff --git a/src/Simplex/Chat/Remote/Types.hs b/src/Simplex/Chat/Remote/Types.hs index d85dde9e87..a5396e7945 100644 --- a/src/Simplex/Chat/Remote/Types.hs +++ b/src/Simplex/Chat/Remote/Types.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} @@ -11,22 +12,22 @@ module Simplex.Chat.Remote.Types where import Control.Concurrent.Async (Async) -import Control.Concurrent.STM (TVar) +import Control.Concurrent.STM import Control.Exception (Exception) -import Crypto.Random (ChaChaDRG) +import Control.Monad (when) import qualified Data.Aeson.TH as J import Data.ByteString (ByteString) import Data.Int (Int64) import Data.Text (Text) -import Data.Word (Word16) +import Data.Word (Word16, Word32) import Simplex.Chat.Remote.AppVersion import Simplex.Chat.Types (verificationCode) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile) -import Simplex.Messaging.Crypto.SNTRUP761 (KEMHybridSecret) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) -import Simplex.Messaging.Transport (TLS (..)) +import Simplex.Messaging.Transport (TLS (..), TSbChainKeys (..)) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client) +import qualified Simplex.Messaging.TMap as TM import Simplex.RemoteControl.Client import Simplex.RemoteControl.Types @@ -40,13 +41,45 @@ data RemoteHostClient = RemoteHostClient } data RemoteCrypto = RemoteCrypto - { drg :: TVar ChaChaDRG, - counter :: TVar Int64, - sessionCode :: ByteString, - hybridKey :: KEMHybridSecret, + { sessionCode :: ByteString, + sndCounter :: TVar Word32, + rcvCounter :: TVar Word32, + chainKeys :: TSbChainKeys, + skippedKeys :: TM.TMap Word32 (C.SbKeyNonce, C.SbKeyNonce), signatures :: RemoteSignatures } +getRemoteSndKeys :: RemoteCrypto -> STM (Word32, C.SbKeyNonce, C.SbKeyNonce) +getRemoteSndKeys RemoteCrypto {sndCounter, chainKeys = TSbChainKeys {sndKey}} = do + corrId <- stateTVar sndCounter $ \c -> let !c' = c + 1 in (c', c') + cmdKN <- stateTVar sndKey C.sbcHkdf + fileKN <- stateTVar sndKey C.sbcHkdf + pure (corrId, cmdKN, fileKN) + +getRemoteRcvKeys :: RemoteCrypto -> Word32 -> STM (Either RemoteProtocolError (C.SbKeyNonce, C.SbKeyNonce)) +getRemoteRcvKeys RemoteCrypto {rcvCounter, chainKeys = TSbChainKeys {rcvKey}, skippedKeys} !corrId = + readTVar rcvCounter >>= getRcvKeys + where + getRcvKeys prevCorrId + | prevCorrId > corrId = + let err = PREEarlierId $ prevCorrId - corrId + in maybe (Left err) Right <$> TM.lookupDelete corrId skippedKeys + | prevCorrId == corrId = + pure $ Left PREDuplicateId + | prevCorrId + maxSkip < corrId = + pure $ Left $ RPEManySkippedIds (corrId - prevCorrId) + | otherwise = do -- prevCorrId < corrId + writeTVar rcvCounter corrId + skipKeys (prevCorrId + 1) + Right <$> getKeys + maxSkip = 256 + getKeys = (,) <$> stateTVar rcvKey C.sbcHkdf <*> stateTVar rcvKey C.sbcHkdf + skipKeys !cId = + when (cId < corrId) $ do + keys <- getKeys + TM.insert cId keys skippedKeys + skipKeys (cId + 1) + data RemoteSignatures = RSSign { idPrivKey :: C.PrivateKeyEd25519, @@ -110,6 +143,9 @@ data RemoteProtocolError | RPENoFile | RPEFileSize | RPEFileDigest + | RPEManySkippedIds Word32 + | PREEarlierId Word32 + | PREDuplicateId | -- | Wrong response received for the command sent RPEUnexpectedResponse {response :: Text} | -- | A file already exists in the destination position From 3685c8574378c930d87092c2c1d3400597f16213 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 24 Sep 2024 09:46:54 +0100 Subject: [PATCH 162/440] ios: reduce scroll stickiness threshold for user profiles to 32px --- apps/ios/Shared/Views/Helpers/StickyScrollView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Helpers/StickyScrollView.swift b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift index 0ba539772f..71bee4f548 100644 --- a/apps/ios/Shared/Views/Helpers/StickyScrollView.swift +++ b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift @@ -44,7 +44,7 @@ struct StickyScrollView: UIViewRepresentable { withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer ) { - if targetContentOffset.pointee.x < 64 { + if targetContentOffset.pointee.x < 32 { targetContentOffset.pointee.x = 0 } } From c67302a5bb4d220a8ac70356a895d01ebe24a5bc Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 24 Sep 2024 09:51:21 +0100 Subject: [PATCH 163/440] core: update simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 96208d329a..d5e17ea299 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 04da670d00e904149040b136ebdea112de71e218 + tag: 7dcac19a671f76dedbe769030742714783946bd3 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index d9d2d27013..91e5e8866f 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."04da670d00e904149040b136ebdea112de71e218" = "0kbpd56b3b8dy72xicfdqmiywxabq000125523dgmn3a5pjagc08"; + "https://github.com/simplex-chat/simplexmq.git"."7dcac19a671f76dedbe769030742714783946bd3" = "0c1jygir4c1s8g4hdz7b6vw69bvcrknbih9rq8y8rv3d8zl32qpq"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 4526afe7e955905fb8620b8414e8c6b90eb242d5 Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 24 Sep 2024 11:51:02 +0100 Subject: [PATCH 164/440] desktop: wrap content of remote hosts on overflow (#4923) * desktop: wrap content of remote hosts on overflow * fix long device text and align --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/views/chatlist/UserPicker.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 41fe127093..19bc2afbd5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts @@ -457,6 +458,7 @@ fun UserPickerInactiveUserBadge(userInfo: UserInfo, stopped: Boolean, size: Dp = } } +@OptIn(ExperimentalLayoutApi::class) @Composable private fun DevicePickerRow( localDeviceActive: Boolean, @@ -465,13 +467,13 @@ private fun DevicePickerRow( onRemoteHostClick: (rh: RemoteHostInfo, connecting: MutableState) -> Unit, onRemoteHostActionButtonClick: (rh: RemoteHostInfo) -> Unit, ) { - Row( + FlowRow( Modifier .fillMaxWidth() .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically + verticalArrangement = Arrangement.spacedBy(12.dp) ) { val activeHost = remoteHosts.firstOrNull { h -> h.activeHost } @@ -554,7 +556,8 @@ fun DevicePill( verticalAlignment = Alignment.CenterVertically ) { Row( - Modifier.padding(horizontal = 6.dp, vertical = 4.dp) + Modifier.padding(horizontal = 6.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically ) { Icon( icon, @@ -567,6 +570,9 @@ fun DevicePill( text, color = MaterialTheme.colors.onSurface, fontSize = 12.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = if (onActionButtonClick != null && actionButtonVisible) Modifier.widthIn(max = 300.dp * fontSizeSqrtMultiplier) else Modifier ) if (onActionButtonClick != null && actionButtonVisible) { val interactionSource = remember { MutableInteractionSource() } From 5b3aba9db2ad2c3d36a85a34af85988b0e166172 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 29 Aug 2024 13:40:55 +0100 Subject: [PATCH 165/440] ci: dont build when files in core do not change (#4797) --- .github/workflows/build.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c41fb4646a..6ad4f12ef9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,22 @@ on: branches: - master - stable - - users tags: - "v*" - "!*-fdroid" - "!*-armv7a" pull_request: + paths-ignore: + - "apps/ios" + - "apps/multiplatform" + - "blog" + - "docs" + - "fastlane" + - "images" + - "packages" + - "website" + - "README.md" + - "PRIVACY.md" jobs: prepare-release: From d6dc35738e4df8f6509b2928603e9c515ebc8a04 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 24 Sep 2024 12:42:22 +0100 Subject: [PATCH 166/440] core: 6.0.5.0 (simplexmq 6.0.5.0) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 92f5d475e9..965a1722d0 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: fa772af6c63fab8f04d9d32d8e8397d75d7d0391 + tag: 4268b90763c58358809a3ea7dd8bc7d78eeb3077 source-repository-package type: git diff --git a/package.yaml b/package.yaml index 947589acd0..8990bfc3d3 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.4.0 +version: 6.0.5.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index c3b85f9b53..d5edef4840 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."fa772af6c63fab8f04d9d32d8e8397d75d7d0391" = "07d0f89msb6p05y67q90ky9jr1rygg7v3xlkga7y255mmpjsqbip"; + "https://github.com/simplex-chat/simplexmq.git"."4268b90763c58358809a3ea7dd8bc7d78eeb3077" = "0w444jbxxi5hgipf35xniwmffnpg4qb46sz112hrwxyf5syfi2k5"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index b3cde5ae9f..aa9ab0b9b3 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.0.4.0 +version: 6.0.5.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 54ff95f3501d654703eebcd6971d6ba31837cd8d Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:44:55 +0400 Subject: [PATCH 167/440] ios: fix theme customization changing color mode (#4936) --- apps/ios/Shared/Theme/ThemeManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Theme/ThemeManager.swift b/apps/ios/Shared/Theme/ThemeManager.swift index 9d648750d1..4166619d04 100644 --- a/apps/ios/Shared/Theme/ThemeManager.swift +++ b/apps/ios/Shared/Theme/ThemeManager.swift @@ -197,7 +197,7 @@ class ThemeManager { var themeIds = currentThemeIdsDefault.get() themeIds[nonSystemThemeName] = prevValue.themeId currentThemeIdsDefault.set(themeIds) - applyTheme(nonSystemThemeName) + applyTheme(currentThemeDefault.get()) } static func copyFromSameThemeOverrides(_ type: WallpaperType?, _ lowerLevelOverride: ThemeModeOverride?, _ pref: Binding) -> Bool { From 3b88ddbd4f3facb9224f7b6e820d6d9e6388c4af Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:51:57 +0000 Subject: [PATCH 168/440] desktop: fix vlc dependency (2) (#4869) --- apps/multiplatform/common/build.gradle.kts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 1670672753..1aaa061daa 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -99,10 +99,15 @@ kotlin { val desktopMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0") - implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") + implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") { + exclude("net.java.dev.jna") + } + // For jSystemThemeDetector only + implementation("net.java.dev.jna:jna-platform:5.14.0") implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT") implementation("org.slf4j:slf4j-simple:2.0.12") implementation("uk.co.caprica:vlcj:4.8.3") + implementation("net.java.dev.jna:jna:5.14.0") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a") implementation("com.squareup.okhttp3:okhttp:4.12.0") From 93ab3076d497c0ae9b7371c4f6094987ff75df60 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 17 Sep 2024 17:34:24 +0100 Subject: [PATCH 169/440] ios: SOCKS proxy UI (#4893) * ios: SOCKS proxy UI * update network config * proxy * adapt * move, dont default to localhost:9050 * move socks proxy to defaults * sock proxy preference * rename * rename * fix * fix --------- Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../Views/ChatList/ServersSummaryView.swift | 24 +++-- .../Views/Migration/MigrateFromDevice.swift | 6 ++ .../AdvancedNetworkSettings.swift | 97 +++++++++++++++++-- .../Views/UserSettings/AppSettings.swift | 11 ++- .../Views/UserSettings/SettingsView.swift | 3 + apps/ios/SimpleXChat/APITypes.swift | 66 ++++++++++++- apps/ios/SimpleXChat/AppGroup.swift | 7 +- 7 files changed, 197 insertions(+), 17 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 477a78e36d..22ea78f27b 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -407,12 +407,18 @@ struct ServersSummaryView: View { struct SubscriptionStatusIndicatorView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let onionHosts = networkUseOnionHostsGroupDefault.get() - let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) + let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage( + online: m.networkInfo.online, + usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, + subs: subs, + hasSess: hasSess, + primaryColor: theme.colors.primary + ) if #available(iOS 16.0, *) { Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue) .foregroundColor(color) @@ -425,26 +431,32 @@ struct SubscriptionStatusIndicatorView: View { struct SubscriptionStatusPercentageView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let onionHosts = networkUseOnionHostsGroupDefault.get() - let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) + let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage( + online: m.networkInfo.online, + usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, + subs: subs, + hasSess: hasSess, + primaryColor: theme.colors.primary + ) Text(verbatim: "\(Int(floor(statusPercent * 100)))%") .foregroundColor(.secondary) .font(.caption) } } -func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ hasSess: Bool) -> (Color, Double, Double, Double) { +func subscriptionStatusColorAndPercentage(online: Bool, usesProxy: Bool, subs: SMPServerSubs, hasSess: Bool, primaryColor: Color) -> (Color, Double, Double, Double) { func roundedToQuarter(_ n: Double) -> Double { n >= 1 ? 1 : n <= 0 ? 0 : (n * 4).rounded() / 4 } - let activeColor: Color = onionHosts == .require ? .indigo : .accentColor + let activeColor: Color = usesProxy ? .indigo : primaryColor let noConnColorAndPercent: (Color, Double, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1, 0) let activeSubsRounded = roundedToQuarter(subs.shareOfActive) diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 1303a1247f..3c514d3529 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -534,9 +534,15 @@ struct MigrateFromDevice: View { } case let .sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs): let cfg = getNetCfg() + let proxy: NetworkProxy? = if cfg.socksProxy == nil { + nil + } else { + networkProxyDefault.get() + } let data = MigrationFileLinkData.init( networkConfig: MigrationFileLinkData.NetworkConfig( socksProxy: cfg.socksProxy, + networkProxy: proxy, hostMode: cfg.hostMode, requiredHostMode: cfg.requiredHostMode ) diff --git a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift index 99c0a588eb..9884c6e877 100644 --- a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift @@ -36,6 +36,10 @@ struct AdvancedNetworkSettings: View { @State private var showSettingsAlert: NetworkSettingsAlert? @State private var onionHosts: OnionHosts = .no @State private var showSaveDialog = false + @State private var netProxy = networkProxyDefault.get() + @State private var currentNetProxy = networkProxyDefault.get() + @State private var useNetProxy = false + @State private var netProxyAuth = false var body: some View { VStack { @@ -102,6 +106,76 @@ struct AdvancedNetworkSettings: View { .foregroundColor(theme.colors.secondary) } + Section { + Toggle("Use SOCKS proxy", isOn: $useNetProxy) + Group { + TextField("IP address", text: $netProxy.host) + TextField( + "Port", + text: Binding( + get: { netProxy.port > 0 ? "\(netProxy.port)" : "" }, + set: { s in + netProxy.port = if let port = Int(s), port > 0 { + port + } else { + 0 + } + } + ) + ) + Toggle("Proxy requires password", isOn: $netProxyAuth) + if netProxyAuth { + TextField("Username", text: $netProxy.username) + PassphraseField( + key: $netProxy.password, + placeholder: "Password", + valid: NetworkProxy.validCredential(netProxy.password) + ) + } + } + .if(!useNetProxy) { $0.foregroundColor(theme.colors.secondary) } + .disabled(!useNetProxy) + } header: { + HStack { + Text("SOCKS proxy").foregroundColor(theme.colors.secondary) + if useNetProxy && !netProxy.valid { + Spacer() + Image(systemName: "exclamationmark.circle.fill").foregroundColor(.red) + } + } + } footer: { + if netProxyAuth { + Text("Your credentials may be sent unencrypted.") + .foregroundColor(theme.colors.secondary) + } else { + Text("Do not use credentials with proxy.") + .foregroundColor(theme.colors.secondary) + } + } + .onChange(of: useNetProxy) { useNetProxy in + netCfg.socksProxy = useNetProxy && currentNetProxy.valid + ? currentNetProxy.toProxyString() + : nil + netProxy = currentNetProxy + netProxyAuth = netProxy.username != "" || netProxy.password != "" + } + .onChange(of: netProxyAuth) { netProxyAuth in + if netProxyAuth { + netProxy.auth = currentNetProxy.auth + netProxy.username = currentNetProxy.username + netProxy.password = currentNetProxy.password + } else { + netProxy.auth = .username + netProxy.username = "" + netProxy.password = "" + } + } + .onChange(of: netProxy) { netProxy in + netCfg.socksProxy = useNetProxy && netProxy.valid + ? netProxy.toProxyString() + : nil + } + Section { Picker("Use .onion hosts", selection: $onionHosts) { ForEach(OnionHosts.values, id: \.self) { Text($0.text) } @@ -156,19 +230,19 @@ struct AdvancedNetworkSettings: View { Section { Button("Reset to defaults") { - updateNetCfgView(NetCfg.defaults) + updateNetCfgView(NetCfg.defaults, NetworkProxy.def) } .disabled(netCfg == NetCfg.defaults) Button("Set timeouts for proxy/VPN") { - updateNetCfgView(netCfg.withProxyTimeouts) + updateNetCfgView(netCfg.withProxyTimeouts, netProxy) } .disabled(netCfg.hasProxyTimeouts) Button("Save and reconnect") { showSettingsAlert = .update } - .disabled(netCfg == currentNetCfg) + .disabled(netCfg == currentNetCfg || (useNetProxy && !netProxy.valid)) } } } @@ -182,7 +256,8 @@ struct AdvancedNetworkSettings: View { if cfgLoaded { return } cfgLoaded = true currentNetCfg = getNetCfg() - updateNetCfgView(currentNetCfg) + currentNetProxy = networkProxyDefault.get() + updateNetCfgView(currentNetCfg, currentNetProxy) } .alert(item: $showSettingsAlert) { a in switch a { @@ -206,7 +281,7 @@ struct AdvancedNetworkSettings: View { if netCfg == currentNetCfg { dismiss() cfgLoaded = false - } else { + } else if !useNetProxy || netProxy.valid { showSaveDialog = true } }) @@ -221,18 +296,26 @@ struct AdvancedNetworkSettings: View { } } - private func updateNetCfgView(_ cfg: NetCfg) { + private func updateNetCfgView(_ cfg: NetCfg, _ proxy: NetworkProxy) { netCfg = cfg + netProxy = proxy onionHosts = OnionHosts(netCfg: netCfg) enableKeepAlive = netCfg.enableKeepAlive keepAliveOpts = netCfg.tcpKeepAlive ?? KeepAliveOpts.defaults + useNetProxy = netCfg.socksProxy != nil + netProxyAuth = switch netProxy.auth { + case .username: netProxy.username != "" || netProxy.password != "" + case .isolate: false + } } private func saveNetCfg() -> Bool { do { try setNetworkConfig(netCfg) currentNetCfg = netCfg - setNetCfg(netCfg) + setNetCfg(netCfg, networkProxy: useNetProxy ? netProxy : nil) + currentNetProxy = netProxy + networkProxyDefault.set(netProxy) return true } catch let error { let err = responseError(error) diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index bd829552f4..19260ce573 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -19,9 +19,15 @@ extension AppSettings { val.hostMode = .publicHost val.requiredHostMode = true } - val.socksProxy = nil - setNetCfg(val) + if val.socksProxy != nil { + val.socksProxy = networkProxy?.toProxyString() + setNetCfg(val, networkProxy: networkProxy) + } else { + val.socksProxy = nil + setNetCfg(val, networkProxy: nil) + } } + if let val = networkProxy { networkProxyDefault.set(val) } if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) } if let val = privacyAskToApproveRelays { privacyAskToApproveRelaysGroupDefault.set(val) } if let val = privacyAcceptImages { @@ -63,6 +69,7 @@ extension AppSettings { let def = UserDefaults.standard var c = AppSettings.defaults c.networkConfig = getNetCfg() + c.networkProxy = networkProxyDefault.get() c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get() c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get() c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get() diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index a4908f628f..5fb44e1ef8 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -73,6 +73,8 @@ let DEFAULT_SYSTEM_DARK_THEME = "systemDarkTheme" let DEFAULT_CURRENT_THEME_IDS = "currentThemeIds" let DEFAULT_THEME_OVERRIDES = "themeOverrides" +let DEFAULT_NETWORK_PROXY = "networkProxy" + let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen" let appDefaults: [String: Any] = [ @@ -245,6 +247,7 @@ public class CodableDefault { } } +let networkProxyDefault: CodableDefault = CodableDefault(defaults: UserDefaults.standard, forKey: DEFAULT_NETWORK_PROXY, withDefault: NetworkProxy.def) struct SettingsView: View { @Environment(\.colorScheme) var colorScheme diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index d7fc533e91..8245230771 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -8,6 +8,7 @@ import Foundation import SwiftUI +import Network public let jsonDecoder = getJSONDecoder() public let jsonEncoder = getJSONEncoder() @@ -1476,6 +1477,63 @@ public struct KeepAliveOpts: Codable, Equatable { public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) } +public struct NetworkProxy: Equatable, Codable { + public var host: String = "" + public var port: Int = 0 + public var auth: NetworkProxyAuth = .username + public var username: String = "" + public var password: String = "" + + public static var def: NetworkProxy { + NetworkProxy() + } + + public var valid: Bool { + let hostOk = switch NWEndpoint.Host(host) { + case .ipv4: true + case .ipv6: true + default: false + } + return hostOk && + port > 0 && port <= 65535 && + NetworkProxy.validCredential(username) && NetworkProxy.validCredential(password) + } + + public static func validCredential(_ s: String) -> Bool { + !s.contains(":") && !s.contains("@") + } + + public func toProxyString() -> String? { + if !valid { return nil } + var res = "" + switch auth { + case .username: + let usernameTrimmed = username.trimmingCharacters(in: .whitespaces) + let passwordTrimmed = password.trimmingCharacters(in: .whitespaces) + if usernameTrimmed != "" || passwordTrimmed != "" { + res += usernameTrimmed + ":" + passwordTrimmed + "@" + } else { + res += "@" + } + case .isolate: () + } + if host != "" { + if host.contains(":") { + res += "[\(host.trimmingCharacters(in: [" ", "[", "]"]))]" + } else { + res += host.trimmingCharacters(in: .whitespaces) + } + } + res += ":\(port)" + return res + } +} + +public enum NetworkProxyAuth: String, Codable { + case username + case isolate +} + public enum NetworkStatus: Decodable, Equatable { case unknown case connected @@ -2099,11 +2157,13 @@ public struct MigrationFileLinkData: Codable { public struct NetworkConfig: Codable { let socksProxy: String? + let networkProxy: NetworkProxy? let hostMode: HostMode? let requiredHostMode: Bool? - public init(socksProxy: String?, hostMode: HostMode?, requiredHostMode: Bool?) { + public init(socksProxy: String?, networkProxy: NetworkProxy?, hostMode: HostMode?, requiredHostMode: Bool?) { self.socksProxy = socksProxy + self.networkProxy = networkProxy self.hostMode = hostMode self.requiredHostMode = requiredHostMode } @@ -2112,6 +2172,7 @@ public struct MigrationFileLinkData: Codable { return if let hostMode, let requiredHostMode { NetworkConfig( socksProxy: nil, + networkProxy: nil, hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode, requiredHostMode: requiredHostMode ) @@ -2131,6 +2192,7 @@ public struct MigrationFileLinkData: Codable { public struct AppSettings: Codable, Equatable { public var networkConfig: NetCfg? = nil + public var networkProxy: NetworkProxy? = nil public var privacyEncryptLocalFiles: Bool? = nil public var privacyAskToApproveRelays: Bool? = nil public var privacyAcceptImages: Bool? = nil @@ -2162,6 +2224,7 @@ public struct AppSettings: Codable, Equatable { var empty = AppSettings() let def = AppSettings.defaults if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } + if networkProxy != def.networkProxy { empty.networkProxy = networkProxy } if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } @@ -2194,6 +2257,7 @@ public struct AppSettings: Codable, Equatable { public static var defaults: AppSettings { AppSettings ( networkConfig: NetCfg.defaults, + networkProxy: NetworkProxy.def, privacyEncryptLocalFiles: true, privacyAskToApproveRelays: true, privacyAcceptImages: true, diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index bd38f3568c..455607ddea 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -35,6 +35,7 @@ public let GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS = "privacyAskToApproveRel // replaces DEFAULT_PROFILE_IMAGE_CORNER_RADIUS public let GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius" let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount" +public let GROUP_DEFAULT_NETWORK_SOCKS_PROXY = "networkSocksProxy" let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE = "networkSMPProxyMode" @@ -327,6 +328,7 @@ public class Default { } public func getNetCfg() -> NetCfg { + let socksProxy = groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) let onionHosts = networkUseOnionHostsGroupDefault.get() let (hostMode, requiredHostMode) = onionHosts.hostMode let sessionMode = networkSessionModeGroupDefault.get() @@ -349,6 +351,7 @@ public func getNetCfg() -> NetCfg { tcpKeepAlive = nil } return NetCfg( + socksProxy: socksProxy, hostMode: hostMode, requiredHostMode: requiredHostMode, sessionMode: sessionMode, @@ -365,11 +368,13 @@ public func getNetCfg() -> NetCfg { ) } -public func setNetCfg(_ cfg: NetCfg) { +public func setNetCfg(_ cfg: NetCfg, networkProxy: NetworkProxy?) { networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg)) networkSessionModeGroupDefault.set(cfg.sessionMode) networkSMPProxyModeGroupDefault.set(cfg.smpProxyMode) networkSMPProxyFallbackGroupDefault.set(cfg.smpProxyFallback) + let socksProxy = networkProxy?.toProxyString() + groupDefaults.set(socksProxy, forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) From 5261886b31250b9289fa4cbfcb836cda7e206592 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 17 Sep 2024 20:09:50 +0000 Subject: [PATCH 170/440] android, desktop: proxy configuration includes credentials (#4892) * android, desktop: proxy configuration includes credentials * migration * changes for disabled socks * migration * port * new logic * migration * check validity of fields * validity of host * import changes proxy just in case * send port always * non-nullable * Revert "send port always" This reverts commit 14dd066d80f4c6c6b1061e8e6142bf18f83b97bb. * string --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/SimpleXAPI.kt | 91 ++++-- .../views/migration/MigrateFromDevice.kt | 16 +- .../common/views/migration/MigrateToDevice.kt | 177 ++++++----- .../usersettings/AdvancedNetworkSettings.kt | 32 +- .../views/usersettings/NetworkAndServers.kt | 285 ++++++++++++------ .../commonMain/resources/MR/base/strings.xml | 10 + 6 files changed, 377 insertions(+), 234 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 4d5caea16a..9cd9376c8d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -129,7 +129,22 @@ class AppPreferences { val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false) val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false) val networkShowSubscriptionPercentage = mkBoolPreference(SHARED_PREFS_NETWORK_SHOW_SUBSCRIPTION_PERCENTAGE, false) - val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050") + private val _networkProxy = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, json.encodeToString(NetworkProxy())) + val networkProxy: SharedPreference = SharedPreference( + get = fun(): NetworkProxy { + val value = _networkProxy.get() ?: return NetworkProxy() + return try { + if (value.startsWith("{")) { + json.decodeFromString(value) + } else { + NetworkProxy(host = value.substringBefore(":").ifBlank { "localhost" }, port = value.substringAfter(":").toIntOrNull() ?: 9050) + } + } catch (e: Throwable) { + NetworkProxy() + } + }, + set = fun(proxy: NetworkProxy) { _networkProxy.set(json.encodeToString(proxy)) } + ) private val _networkSessionMode = mkStrPreference(SHARED_PREFS_NETWORK_SESSION_MODE, TransportSessionMode.default.name) val networkSessionMode: SharedPreference = SharedPreference( get = fun(): TransportSessionMode { @@ -531,7 +546,7 @@ object ChatController { suspend fun startChatWithTemporaryDatabase(ctrl: ChatCtrl, netCfg: NetCfg): User? { Log.d(TAG, "startChatWithTemporaryDatabase") val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl) - if (!apiSetNetworkConfig(netCfg, ctrl)) { + if (!apiSetNetworkConfig(netCfg, ctrl = ctrl)) { Log.e(TAG, "Error setting network config, stopping migration") return null } @@ -976,16 +991,18 @@ object ChatController { throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}") } - suspend fun apiSetNetworkConfig(cfg: NetCfg, ctrl: ChatCtrl? = null): Boolean { + suspend fun apiSetNetworkConfig(cfg: NetCfg, showAlertOnError: Boolean = true, ctrl: ChatCtrl? = null): Boolean { val r = sendCmd(null, CC.APISetNetworkConfig(cfg), ctrl) return when (r) { is CR.CmdOk -> true else -> { Log.e(TAG, "apiSetNetworkConfig bad response: ${r.responseType} ${r.details}") - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.error_setting_network_config), - "${r.responseType}: ${r.details}" - ) + if (showAlertOnError) { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.error_setting_network_config), + "${r.responseType}: ${r.details}" + ) + } false } } @@ -2745,13 +2762,9 @@ object ChatController { fun getNetCfg(): NetCfg { val useSocksProxy = appPrefs.networkUseSocksProxy.get() - val proxyHostPort = appPrefs.networkProxyHostPort.get() + val networkProxy = appPrefs.networkProxy.get() val socksProxy = if (useSocksProxy) { - if (proxyHostPort?.startsWith("localhost:") == true) { - proxyHostPort.removePrefix("localhost") - } else { - proxyHostPort ?: ":9050" - } + networkProxy.toProxyString() } else { null } @@ -2793,7 +2806,7 @@ object ChatController { } /** - * [AppPreferences.networkProxyHostPort] is not changed here, use appPrefs to set it + * [AppPreferences.networkProxy] is not changed here, use appPrefs to set it * */ fun setNetCfg(cfg: NetCfg) { appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy) @@ -3529,13 +3542,8 @@ data class NetCfg( val useSocksProxy: Boolean get() = socksProxy != null val enableKeepAlive: Boolean get() = tcpKeepAlive != null - fun withHostPort(hostPort: String?, default: String? = ":9050"): NetCfg { - val socksProxy = if (hostPort?.startsWith("localhost:") == true) { - hostPort.removePrefix("localhost") - } else { - hostPort ?: default - } - return copy(socksProxy = socksProxy) + fun withProxy(proxy: NetworkProxy?, default: String? = ":9050"): NetCfg { + return copy(socksProxy = proxy?.toProxyString() ?: default) } companion object { @@ -3577,6 +3585,39 @@ data class NetCfg( } } +@Serializable +data class NetworkProxy( + val username: String = "", + val password: String = "", + val auth: NetworkProxyAuth = NetworkProxyAuth.ISOLATE, + val host: String = "localhost", + val port: Int = 9050 +) { + fun toProxyString(): String { + var res = "" + if (auth == NetworkProxyAuth.USERNAME && (username.isNotBlank() || password.isNotBlank())) { + res += username.trim() + ":" + password.trim() + "@" + } else if (auth == NetworkProxyAuth.USERNAME) { + res += "@" + } + if (host != "localhost") { + res += if (host.contains(':')) "[${host.trim(' ', '[', ']')}]" else host.trim() + } + if (port != 9050 || res.isEmpty()) { + res += ":$port" + } + return res + } +} + +@Serializable +enum class NetworkProxyAuth { + @SerialName("isolate") + ISOLATE, + @SerialName("username") + USERNAME, +} + enum class OnionHosts { NEVER, PREFER, REQUIRED } @@ -6139,6 +6180,7 @@ enum class NotificationsMode() { @Serializable data class AppSettings( var networkConfig: NetCfg? = null, + var networkProxy: NetworkProxy? = null, var privacyEncryptLocalFiles: Boolean? = null, var privacyAskToApproveRelays: Boolean? = null, var privacyAcceptImages: Boolean? = null, @@ -6170,6 +6212,7 @@ data class AppSettings( val empty = AppSettings() val def = defaults if (networkConfig != def.networkConfig) { empty.networkConfig = networkConfig } + if (networkProxy != def.networkProxy) { empty.networkProxy = networkProxy } if (privacyEncryptLocalFiles != def.privacyEncryptLocalFiles) { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } if (privacyAskToApproveRelays != def.privacyAskToApproveRelays) { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } if (privacyAcceptImages != def.privacyAcceptImages) { empty.privacyAcceptImages = privacyAcceptImages } @@ -6207,8 +6250,12 @@ data class AppSettings( if (net.hostMode == HostMode.Onion) { net = net.copy(hostMode = HostMode.Public, requiredHostMode = true) } + if (net.socksProxy != null) { + net = net.copy(socksProxy = networkProxy?.toProxyString()) + } setNetCfg(net) } + networkProxy?.let { def.networkProxy.set(it) } privacyEncryptLocalFiles?.let { def.privacyEncryptLocalFiles.set(it) } privacyAskToApproveRelays?.let { def.privacyAskToApproveRelays.set(it) } privacyAcceptImages?.let { def.privacyAcceptImages.set(it) } @@ -6241,6 +6288,7 @@ data class AppSettings( val defaults: AppSettings get() = AppSettings( networkConfig = NetCfg.defaults, + networkProxy = null, privacyEncryptLocalFiles = true, privacyAskToApproveRelays = true, privacyAcceptImages = true, @@ -6274,6 +6322,7 @@ data class AppSettings( val def = appPreferences return defaults.copy( networkConfig = getNetCfg(), + networkProxy = def.networkProxy.get(), privacyEncryptLocalFiles = def.privacyEncryptLocalFiles.get(), privacyAskToApproveRelays = def.privacyAskToApproveRelays.get(), privacyAcceptImages = def.privacyAcceptImages.get(), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index a71503e315..4cc7899cc8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -4,7 +4,6 @@ import SectionBottomSpacer import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -17,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase @@ -38,7 +38,6 @@ import kotlinx.serialization.* import java.io.File import java.net.URLEncoder import kotlin.math.max -import kotlin.math.sqrt @Serializable data class MigrationFileLinkData( @@ -46,16 +45,20 @@ data class MigrationFileLinkData( ) { @Serializable data class NetworkConfig( - val socksProxy: String?, + // Legacy. Remove in 2025 + @SerialName("socksProxy") + val legacySocksProxy: String?, + val networkProxy: NetworkProxy?, val hostMode: HostMode?, val requiredHostMode: Boolean? ) { - fun hasOnionConfigured(): Boolean = socksProxy != null || hostMode == HostMode.Onion + fun hasProxyConfigured(): Boolean = networkProxy != null || legacySocksProxy != null || hostMode == HostMode.Onion fun transformToPlatformSupported(): NetworkConfig { return if (hostMode != null && requiredHostMode != null) { NetworkConfig( - socksProxy = if (hostMode == HostMode.Onion) socksProxy ?: NetCfg.proxyDefaults.socksProxy else socksProxy, + legacySocksProxy = if (hostMode == HostMode.Onion) legacySocksProxy ?: NetCfg.proxyDefaults.socksProxy else legacySocksProxy, + networkProxy = if (hostMode == HostMode.Onion) networkProxy ?: NetworkProxy() else networkProxy, hostMode = if (hostMode == HostMode.Onion) HostMode.OnionViaSocks else hostMode, requiredHostMode = requiredHostMode ) @@ -570,7 +573,8 @@ private fun MutableState.startUploading( val cfg = getNetCfg() val data = MigrationFileLinkData( networkConfig = MigrationFileLinkData.NetworkConfig( - socksProxy = cfg.socksProxy, + legacySocksProxy = null, + networkProxy = if (appPrefs.networkUseSocksProxy.get()) appPrefs.networkProxy.get() else null, hostMode = cfg.hostMode, requiredHostMode = cfg.requiredHostMode ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 8312c213ec..415f5cdd57 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -5,16 +5,15 @@ import SectionItemView import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import chat.simplex.common.model.* import chat.simplex.common.model.AppPreferences.Companion.SHARED_PREFS_MIGRATION_TO_STAGE +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatCtrl @@ -41,10 +40,10 @@ import kotlin.math.max @Serializable sealed class MigrationToDeviceState { - @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState() - @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState() - @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState() - @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg): MigrationToDeviceState() + @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val networkProxy: NetworkProxy?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState() + @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() + @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() + @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() companion object { // Here we check whether it's needed to show migration process after app restart or not @@ -66,10 +65,10 @@ sealed class MigrationToDeviceState { null } else { val archivePath = File(getMigrationTempFilesDirectory(), state.archiveName) - MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg) + MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg, state.networkProxy) } } - is Passphrase -> MigrationToState.Passphrase("", state.netCfg) + is Passphrase -> MigrationToState.Passphrase("", state.netCfg, state.networkProxy) } if (initial == null) { settings.remove(SHARED_PREFS_MIGRATION_TO_STAGE) @@ -91,16 +90,24 @@ sealed class MigrationToDeviceState { @Serializable sealed class MigrationToState { @Serializable object PasteOrScanLink: MigrationToState() - @Serializable data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToState() - @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val ctrl: ChatCtrl?): MigrationToState() - @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState() - @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState() + @Serializable data class Onion( + val link: String, + // Legacy, remove in 2025 + @SerialName("socksProxy") + val legacySocksProxy: String?, + val networkProxy: NetworkProxy?, + val hostMode: HostMode, + val requiredHostMode: Boolean + ): MigrationToState() + @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?, val ctrl: ChatCtrl?): MigrationToState() + @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() } private var MutableState.state: MigrationToState? @@ -175,16 +182,16 @@ private fun ModalData.SectionByState( when (val s = migrationState.value) { null -> {} is MigrationToState.PasteOrScanLink -> migrationState.PasteOrScanLinkView() - is MigrationToState.Onion -> OnionView(s.link, s.socksProxy, s.hostMode, s.requiredHostMode, migrationState) - is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg) - is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg) + is MigrationToState.Onion -> OnionView(s.link, s.legacySocksProxy, s.networkProxy, s.hostMode, s.requiredHostMode, migrationState) + is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg, s.networkProxy) + is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg, s.networkProxy) is MigrationToState.DownloadProgress -> DownloadProgressView(s.downloadedBytes, totalBytes = s.totalBytes) - is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg) - is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg) - is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg) - is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg) - is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg) - is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, close) + is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg, s.networkProxy) + is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg, s.networkProxy) + is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, s.networkProxy, close) } } @@ -216,21 +223,24 @@ private fun MutableState.PasteLinkView() { } @Composable -private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) { +private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, linkNetworkProxy: NetworkProxy?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) { val onionHosts = remember { stateGetOrPut("onionHosts") { - getNetCfg().copy(socksProxy = socksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts + getNetCfg().copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts } } - val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { socksProxy != null } } + val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { linkNetworkProxy != null || legacyLinkSocksProxy != null } } val sessionMode = remember { stateGetOrPut("sessionMode") { TransportSessionMode.User} } - val networkProxyHostPort = remember { stateGetOrPut("networkHostProxyPort") { - var proxy = (socksProxy ?: chatModel.controller.appPrefs.networkProxyHostPort.get()) - if (proxy?.startsWith(":") == true) proxy = "localhost$proxy" - proxy - } + val networkProxy = remember { stateGetOrPut("networkProxy") { + linkNetworkProxy + ?: if (legacyLinkSocksProxy != null) { + NetworkProxy(host = legacyLinkSocksProxy.substringBefore(":").ifBlank { "localhost" }, port = legacyLinkSocksProxy.substringAfter(":").toIntOrNull() ?: 9050) + } else { + appPrefs.networkProxy.get() + } + } } val netCfg = rememberSaveable(stateSaver = serializableSaver()) { - mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = socksProxy, sessionMode = sessionMode.value)) + mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, sessionMode = sessionMode.value)) } SectionView(stringResource(MR.strings.migrate_to_device_confirm_network_settings).uppercase()) { @@ -241,12 +251,12 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos click = { val updated = netCfg.value .withOnionHosts(onionHosts.value) - .withHostPort(if (networkUseSocksProxy.value) networkProxyHostPort.value else null, null) + .withProxy(if (networkUseSocksProxy.value) networkProxy.value else null, null) .copy( sessionMode = sessionMode.value ) withBGApi { - state.value = MigrationToState.DatabaseInit(link, updated) + state.value = MigrationToState.DatabaseInit(link, updated, if (networkUseSocksProxy.value) networkProxy.value else null) } } ){} @@ -255,8 +265,8 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos SectionSpacer() - val networkProxyHostPortPref = SharedPreference(get = { networkProxyHostPort.value }, set = { - networkProxyHostPort.value = it + val networkProxyPref = SharedPreference(get = { networkProxy.value }, set = { + networkProxy.value = it }) SectionView(stringResource(MR.strings.network_settings_title).uppercase()) { OnionRelatedLayout( @@ -264,13 +274,10 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos networkUseSocksProxy, onionHosts, sessionMode, - networkProxyHostPortPref, + networkProxyPref, toggleSocksProxy = { enable -> networkUseSocksProxy.value = enable }, - useOnion = { - onionHosts.value = it - }, updateSessionMode = { sessionMode.value = it } @@ -279,13 +286,13 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos } @Composable -private fun MutableState.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg) { +private fun MutableState.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg, networkProxy: NetworkProxy?) { Box { SectionView(stringResource(MR.strings.migrate_to_device_database_init).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - prepareDatabase(link, tempDatabaseFile, netCfg) + prepareDatabase(link, tempDatabaseFile, netCfg, networkProxy) } } @@ -297,14 +304,15 @@ private fun MutableState.LinkDownloadingView( archivePath: String, tempDatabaseFile: File, chatReceiver: MutableState, - netCfg: NetCfg + netCfg: NetCfg, + networkProxy: NetworkProxy? ) { Box { SectionView(stringResource(MR.strings.migrate_to_device_downloading_details).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg) + startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg, networkProxy) } } @@ -319,14 +327,14 @@ private fun DownloadProgressView(downloadedBytes: Long, totalBytes: Long) { } @Composable -private fun MutableState.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg) { +private fun MutableState.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { SectionView(stringResource(MR.strings.migrate_to_device_download_failed).uppercase()) { SettingsActionItemWithContent( icon = painterResource(MR.images.ic_download), text = stringResource(MR.strings.migrate_to_device_repeat_download), textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.DatabaseInit(link, netCfg) + state = MigrationToState.DatabaseInit(link, netCfg, networkProxy) } ) {} SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again)) @@ -339,25 +347,25 @@ private fun MutableState.DownloadFailedView(link: String, cha } @Composable -private fun MutableState.ArchiveImportView(archivePath: String, netCfg: NetCfg) { +private fun MutableState.ArchiveImportView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { Box { SectionView(stringResource(MR.strings.migrate_to_device_importing_archive).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - importArchive(archivePath, netCfg) + importArchive(archivePath, netCfg, networkProxy) } } @Composable -private fun MutableState.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg) { +private fun MutableState.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { SectionView(stringResource(MR.strings.migrate_to_device_import_failed).uppercase()) { SettingsActionItemWithContent( icon = painterResource(MR.images.ic_download), text = stringResource(MR.strings.migrate_to_device_repeat_import), textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.ArchiveImport(archivePath, netCfg) + state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy) } ) {} SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again)) @@ -365,7 +373,7 @@ private fun MutableState.ArchiveImportFailedView(archivePath: } @Composable -private fun MutableState.PassphraseEnteringView(currentKey: String, netCfg: NetCfg) { +private fun MutableState.PassphraseEnteringView(currentKey: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { val currentKey = rememberSaveable { mutableStateOf(currentKey) } val verifyingPassphrase = rememberSaveable { mutableStateOf(false) } val useKeychain = rememberSaveable { mutableStateOf(appPreferences.storeDBPassphrase.get()) } @@ -395,9 +403,9 @@ private fun MutableState.PassphraseEnteringView(currentKey: S val (status, _) = chatInitTemporaryDatabase(dbAbsolutePrefixPath, key = currentKey.value, confirmation = MigrationConfirmation.YesUp) val success = status == DBMigrationResult.OK || status == DBMigrationResult.InvalidConfirmation if (success) { - state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg) + state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg, networkProxy) } else if (status is DBMigrationResult.ErrorMigration) { - state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg) + state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg, networkProxy) } else { showErrorOnMigrationIfNeeded(status) } @@ -414,7 +422,7 @@ private fun MutableState.PassphraseEnteringView(currentKey: S } @Composable -private fun MutableState.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg) { +private fun MutableState.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?) { data class Tuple4(val a: A, val b: B, val c: C, val d: D) val (header: String, button: String?, footer: String, confirmation: MigrationConfirmation?) = when (status) { is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) { @@ -449,7 +457,7 @@ private fun MutableState.MigrationConfirmationView(status: DB text = button, textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg) + state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg, networkProxy) } ) {} } @@ -458,13 +466,13 @@ private fun MutableState.MigrationConfirmationView(status: DB } @Composable -private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) { +private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) { Box { SectionView(stringResource(MR.strings.migrate_to_device_migrating).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - startChat(passphrase, confirmation, useKeychain, netCfg, close) + startChat(passphrase, confirmation, useKeychain, netCfg, networkProxy, close) } } @@ -476,19 +484,21 @@ private fun ProgressView() { private suspend fun MutableState.checkUserLink(link: String) { if (strHasSimplexFileLink(link.trim())) { val data = MigrationFileLinkData.readFromLink(link) - val hasOnionConfigured = data?.networkConfig?.hasOnionConfigured() ?: false + val hasProxyConfigured = data?.networkConfig?.hasProxyConfigured() ?: false val networkConfig = data?.networkConfig?.transformToPlatformSupported() // If any of iOS or Android had onion enabled, show onion screen - if (hasOnionConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) { - state = MigrationToState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode) - MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode)) + if (hasProxyConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) { + state = MigrationToState.Onion(link.trim(), networkConfig.legacySocksProxy, networkConfig.networkProxy, networkConfig.hostMode, networkConfig.requiredHostMode) + MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.legacySocksProxy, networkConfig.networkProxy, networkConfig.hostMode, networkConfig.requiredHostMode)) } else { val current = getNetCfg() state = MigrationToState.DatabaseInit(link.trim(), current.copy( - socksProxy = networkConfig?.socksProxy, + socksProxy = null, hostMode = networkConfig?.hostMode ?: current.hostMode, requiredHostMode = networkConfig?.requiredHostMode ?: current.requiredHostMode - )) + ), + networkProxy = null + ) } } else { AlertManager.shared.showAlertMsg( @@ -502,6 +512,7 @@ private fun MutableState.prepareDatabase( link: String, tempDatabaseFile: File, netCfg: NetCfg, + networkProxy: NetworkProxy? ) { withLongRunningApi { val ctrlAndUser = initTemporaryDatabase(tempDatabaseFile, netCfg) @@ -513,7 +524,7 @@ private fun MutableState.prepareDatabase( } val (ctrl, user) = ctrlAndUser - state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg) + state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg, networkProxy) } } @@ -526,13 +537,14 @@ private fun MutableState.startDownloading( link: String, archivePath: String, netCfg: NetCfg, + networkProxy: NetworkProxy? ) { withBGApi { chatReceiver.value = MigrationToChatReceiver(ctrl, tempDatabaseFile) { msg -> when (msg) { is CR.RcvFileProgressXFTP -> { - state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, ctrl) - MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg)) + state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, networkProxy, ctrl) + MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg, networkProxy)) } is CR.RcvStandaloneFileComplete -> { delay(500) @@ -540,8 +552,8 @@ private fun MutableState.startDownloading( if (state == null) { MigrationToDeviceState.save(null) } else { - state = MigrationToState.ArchiveImport(archivePath, netCfg) - MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg)) + state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy) + MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg, networkProxy)) } } is CR.RcvFileError -> { @@ -549,7 +561,7 @@ private fun MutableState.startDownloading( generalGetString(MR.strings.migrate_to_device_download_failed), generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) ) - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) } is CR.ChatRespError -> { if (msg.chatError is ChatError.ChatErrorChat && msg.chatError.errorType is ChatErrorType.NoRcvFileUser) { @@ -557,7 +569,7 @@ private fun MutableState.startDownloading( generalGetString(MR.strings.migrate_to_device_download_failed), generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) ) - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) } else { Log.d(TAG, "unsupported error: ${msg.responseType}, ${json.encodeToString(msg.chatError)}") } @@ -569,7 +581,7 @@ private fun MutableState.startDownloading( val (res, error) = controller.downloadStandaloneFile(user, link, CryptoFile.plain(File(archivePath).path), ctrl) if (res == null) { - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg( generalGetString(MR.strings.migrate_to_device_error_downloading_archive), error @@ -578,7 +590,7 @@ private fun MutableState.startDownloading( } } -private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg) { +private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { withLongRunningApi { try { if (ChatController.ctrl == null || ChatController.ctrl == -1L) { @@ -592,14 +604,14 @@ private fun MutableState.importArchive(archivePath: String, n if (archiveErrors.isNotEmpty()) { showArchiveImportedWithErrorsAlert(archiveErrors) } - state = MigrationToState.Passphrase("", netCfg) - MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg)) + state = MigrationToState.Passphrase("", netCfg, networkProxy) + MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg, networkProxy)) } catch (e: Exception) { - state = MigrationToState.ArchiveImportFailed(archivePath, netCfg) + state = MigrationToState.ArchiveImportFailed(archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_importing_database), e.stackTraceToString()) } } catch (e: Exception) { - state = MigrationToState.ArchiveImportFailed(archivePath, netCfg) + state = MigrationToState.ArchiveImportFailed(archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_deleting_database), e.stackTraceToString()) } } @@ -609,7 +621,7 @@ private suspend fun stopArchiveDownloading(fileId: Long, ctrl: ChatCtrl) { controller.apiCancelFile(null, fileId, ctrl) } -private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) { +private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) { if (useKeychain) { ksDatabasePassword.set(passphrase) } else { @@ -621,7 +633,8 @@ private fun startChat(passphrase: String, confirmation: MigrationConfirmation, u try { initChatController(useKey = passphrase, confirmMigrations = confirmation) { CompletableDeferred(false) } val appSettings = controller.apiGetAppSettings(AppSettings.current.prepareForExport()).copy( - networkConfig = netCfg + networkConfig = netCfg, + networkProxy = networkProxy ) finishMigration(appSettings, close) } catch (e: Exception) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index 3c8ab2b70a..6dc0f74df3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -1,10 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionItemWithValue +import SectionTextFooter import SectionView import SectionViewSelectableCards import androidx.compose.desktop.ui.tooling.preview.Preview @@ -40,12 +40,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U val currentCfg = remember { stateGetOrPut("currentCfg") { controller.getNetCfg() } } val currentCfgVal = currentCfg.value // used only on initialization - val onionHosts = remember { mutableStateOf(currentCfgVal.onionHosts) } val sessionMode = remember { mutableStateOf(currentCfgVal.sessionMode) } val smpProxyMode = remember { mutableStateOf(currentCfgVal.smpProxyMode) } val smpProxyFallback = remember { mutableStateOf(currentCfgVal.smpProxyFallback) } - val networkUseSocksProxy: MutableState = remember { mutableStateOf(currentCfgVal.useSocksProxy) } val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) } val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) } val networkTCPTimeoutPerKb = remember { mutableStateOf(currentCfgVal.tcpTimeoutPerKb) } @@ -90,11 +88,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U tcpKeepAlive = tcpKeepAlive, smpPingInterval = networkSMPPingInterval.value, smpPingCount = networkSMPPingCount.value - ).withOnionHosts(onionHosts.value) + ).withOnionHosts(currentCfg.value.onionHosts) } fun updateView(cfg: NetCfg) { - onionHosts.value = cfg.onionHosts sessionMode.value = cfg.sessionMode smpProxyMode.value = cfg.smpProxyMode smpProxyFallback.value = cfg.smpProxyFallback @@ -148,10 +145,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U ) { AdvancedNetworkSettingsLayout( currentRemoteHost = currentRemoteHost, - networkUseSocksProxy = networkUseSocksProxy, developerTools = developerTools, - onionHosts = onionHosts, - useOnion = { onionHosts.value = it; currentCfg.value = currentCfg.value.withOnionHosts(it) }, sessionMode = sessionMode, smpProxyMode = smpProxyMode, smpProxyFallback = smpProxyFallback, @@ -183,10 +177,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U @Composable fun AdvancedNetworkSettingsLayout( currentRemoteHost: RemoteHostInfo?, - networkUseSocksProxy: State, developerTools: Boolean, - onionHosts: MutableState, - useOnion: (OnionHosts) -> Unit, sessionMode: MutableState, smpProxyMode: MutableState, smpProxyFallback: MutableState, @@ -223,21 +214,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { derivedStateOf { smpProxyMode.value != SMPProxyMode.Never } }) SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy) } - SectionCustomFooter { - Text(stringResource(MR.strings.private_routing_explanation)) - } - SectionDividerSpaced(maxTopPadding = true) - } - - if (currentRemoteHost == null && networkUseSocksProxy.value) { - SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) { - UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) - SectionCustomFooter { - Column { - Text(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported)) - } - } - } + SectionTextFooter(stringResource(MR.strings.private_routing_explanation)) SectionDividerSpaced(maxTopPadding = true) } @@ -562,7 +539,6 @@ fun PreviewAdvancedNetworkSettingsLayout() { SimpleXTheme { AdvancedNetworkSettingsLayout( currentRemoteHost = null, - networkUseSocksProxy = remember { mutableStateOf(false) }, developerTools = false, sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, @@ -577,8 +553,6 @@ fun PreviewAdvancedNetworkSettingsLayout() { networkTCPKeepIdle = remember { mutableStateOf(10) }, networkTCPKeepIntvl = remember { mutableStateOf(10) }, networkTCPKeepCnt = remember { mutableStateOf(10) }, - onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, - useOnion = {}, updateSessionMode = {}, updateSMPProxyMode = {}, updateSMPProxyFallback = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 5bcb0a545d..5272353c20 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -1,10 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionItemWithValue +import SectionTextFooter import SectionView import SectionViewSelectable import TextIconSpaced @@ -37,10 +37,11 @@ fun NetworkAndServersView() { val netCfg = remember { chatModel.controller.getNetCfg() } val networkUseSocksProxy: MutableState = remember { mutableStateOf(netCfg.useSocksProxy) } - val proxyPort = remember { derivedStateOf { chatModel.controller.appPrefs.networkProxyHostPort.state.value?.split(":")?.lastOrNull()?.toIntOrNull() ?: 9050 } } + val proxyPort = remember { derivedStateOf { appPrefs.networkProxy.state.value.port } } NetworkAndServersLayout( currentRemoteHost = currentRemoteHost, networkUseSocksProxy = networkUseSocksProxy, + onionHosts = remember { mutableStateOf(netCfg.onionHosts) }, toggleSocksProxy = { enable -> val def = NetCfg.defaults val proxyDef = NetCfg.proxyDefaults @@ -51,7 +52,7 @@ fun NetworkAndServersView() { confirmText = generalGetString(MR.strings.confirm_verb), onConfirm = { withBGApi { - var conf = controller.getNetCfg().withHostPort(controller.appPrefs.networkProxyHostPort.get()) + var conf = controller.getNetCfg().withProxy(controller.appPrefs.networkProxy.get()) if (conf.tcpConnectTimeout == def.tcpConnectTimeout) { conf = conf.copy(tcpConnectTimeout = proxyDef.tcpConnectTimeout) } @@ -104,6 +105,7 @@ fun NetworkAndServersView() { @Composable fun NetworkAndServersLayout( currentRemoteHost: RemoteHostInfo?, networkUseSocksProxy: MutableState, + onionHosts: MutableState, toggleSocksProxy: (Boolean) -> Unit, ) { val m = chatModel @@ -120,14 +122,10 @@ fun NetworkAndServersView() { if (currentRemoteHost == null) { UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) - SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxyHostPort, false, it) }}) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxy, onionHosts, sessionMode = appPrefs.networkSessionMode.get(), false, it) }}) SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showCustomModal { AdvancedNetworkSettingsView(showModal, it) } }) if (networkUseSocksProxy.value) { - SectionCustomFooter { - Column { - Text(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) - } - } + SectionTextFooter(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) SectionDividerSpaced(maxTopPadding = true) } else { SectionDividerSpaced() @@ -158,16 +156,14 @@ fun NetworkAndServersView() { networkUseSocksProxy: MutableState, onionHosts: MutableState, sessionMode: MutableState, - networkProxyHostPort: SharedPreference, + networkProxy: SharedPreference, toggleSocksProxy: (Boolean) -> Unit, - useOnion: (OnionHosts) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, ) { val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.fullscreen.showModal(content = it) } val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.fullscreen.showCustomModal { close -> it(close) }} UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) - SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxyHostPort, true, it) } }) - UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxy, onionHosts, sessionMode.value, true, it) } }) if (developerTools) { SessionModePicker(sessionMode, showModal, updateSessionMode) } @@ -205,46 +201,98 @@ fun UseSocksProxySwitch( @Composable fun SocksProxySettings( networkUseSocksProxy: Boolean, - networkProxyHostPort: SharedPreference = appPrefs.networkProxyHostPort, + networkProxy: SharedPreference, + onionHosts: MutableState, + sessionMode: TransportSessionMode, migration: Boolean, close: () -> Unit ) { - val defaultHostPort = remember { "localhost:9050" } - val hostPortSaved by remember { networkProxyHostPort.state } + val networkProxySaved by remember { networkProxy.state } + val onionHostsSaved = remember { mutableStateOf(onionHosts.value) } + + val usernameUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(networkProxySaved.username)) + } + val passwordUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(networkProxySaved.password)) + } val hostUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.firstOrNull() ?: "localhost")) + mutableStateOf(TextFieldValue(networkProxySaved.host)) } val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.lastOrNull() ?: "9050")) + mutableStateOf(TextFieldValue(networkProxySaved.port.toString())) } - val save = { - val oldValue = networkProxyHostPort.get() - networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) - if (networkUseSocksProxy && !migration) { - withBGApi { - if (!controller.apiSetNetworkConfig(controller.getNetCfg())) { - networkProxyHostPort.set(oldValue) - } + val proxyAuthRandomUnsaved = rememberSaveable { mutableStateOf(networkProxySaved.auth == NetworkProxyAuth.ISOLATE) } + LaunchedEffect(proxyAuthRandomUnsaved.value) { + if (!proxyAuthRandomUnsaved.value && onionHosts.value != OnionHosts.NEVER) { + onionHosts.value = OnionHosts.NEVER + } + } + val proxyAuthModeUnsaved = remember(proxyAuthRandomUnsaved.value, usernameUnsaved.value.text, passwordUnsaved.value.text) { + derivedStateOf { + if (proxyAuthRandomUnsaved.value) { + NetworkProxyAuth.ISOLATE + } else { + NetworkProxyAuth.USERNAME } } } - val saveAndClose = { - val oldValue = networkProxyHostPort.get() - networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) + + val save: (Boolean) -> Unit = { closeOnSuccess -> + val oldValue = networkProxy.get() + usernameUnsaved.value = usernameUnsaved.value.copy(if (proxyAuthModeUnsaved.value == NetworkProxyAuth.USERNAME) usernameUnsaved.value.text.trim() else "") + passwordUnsaved.value = passwordUnsaved.value.copy(if (proxyAuthModeUnsaved.value == NetworkProxyAuth.USERNAME) passwordUnsaved.value.text.trim() else "") + hostUnsaved.value = hostUnsaved.value.copy(hostUnsaved.value.text.trim()) + portUnsaved.value = portUnsaved.value.copy(portUnsaved.value.text.trim()) + + networkProxy.set( + NetworkProxy( + username = usernameUnsaved.value.text, + password = passwordUnsaved.value.text, + host = hostUnsaved.value.text, + port = portUnsaved.value.text.toIntOrNull() ?: 9050, + auth = proxyAuthModeUnsaved.value + ) + ) + val oldCfg = controller.getNetCfg() + val cfg = oldCfg.withOnionHosts(onionHosts.value) + val oldOnionHosts = onionHostsSaved.value + onionHostsSaved.value = onionHosts.value + + if (!migration) { + controller.setNetCfg(cfg) + } if (networkUseSocksProxy && !migration) { withBGApi { - if (controller.apiSetNetworkConfig(controller.getNetCfg())) { - close() + if (controller.apiSetNetworkConfig(cfg, showAlertOnError = false)) { + onionHosts.value = cfg.onionHosts + onionHostsSaved.value = onionHosts.value + if (closeOnSuccess) { + close() + } } else { - networkProxyHostPort.set(oldValue) + controller.setNetCfg(oldCfg) + networkProxy.set(oldValue) + onionHostsSaved.value = oldOnionHosts + showWrongProxyConfigAlert() } } } } - val saveDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text) || - remember { derivedStateOf { !validHost(hostUnsaved.value.text) } }.value || - remember { derivedStateOf { !validPort(portUnsaved.value.text) } }.value - val resetDisabled = hostUnsaved.value.text + ":" + portUnsaved.value.text == defaultHostPort + val saveDisabled = + ( + networkProxySaved.username == usernameUnsaved.value.text.trim() && + networkProxySaved.password == passwordUnsaved.value.text.trim() && + networkProxySaved.host == hostUnsaved.value.text.trim() && + networkProxySaved.port.toString() == portUnsaved.value.text.trim() && + networkProxySaved.auth == proxyAuthModeUnsaved.value && + onionHosts.value == onionHostsSaved.value + ) || + !validCredential(usernameUnsaved.value.text) || + !validCredential(passwordUnsaved.value.text) || + !validHost(hostUnsaved.value.text) || + !validPort(portUnsaved.value.text) + val resetDisabled = hostUnsaved.value.text.trim() == "localhost" && portUnsaved.value.text.trim() == "9050" && proxyAuthRandomUnsaved.value && onionHosts.value == NetCfg.defaults.onionHosts ModalView( close = { if (saveDisabled) { @@ -252,7 +300,7 @@ fun SocksProxySettings( } else { showUnsavedSocksHostPortAlert( confirmText = generalGetString(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), - save = saveAndClose, + save = { save(true) }, close = close ) } @@ -263,38 +311,78 @@ fun SocksProxySettings( .fillMaxWidth() ) { AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) - SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { - DefaultConfigurableTextField( - hostUnsaved, - stringResource(MR.strings.host_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validHost, - keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), - keyboardType = KeyboardType.Text, - ) - DefaultConfigurableTextField( - portUnsaved, - stringResource(MR.strings.port_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validPort, - keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }), - keyboardType = KeyboardType.Number, - ) + SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + DefaultConfigurableTextField( + hostUnsaved, + stringResource(MR.strings.host_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validHost, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Text, + ) + DefaultConfigurableTextField( + portUnsaved, + stringResource(MR.strings.port_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validPort, + keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save(false) }), + keyboardType = KeyboardType.Number, + ) + } + + UseOnionHosts(onionHosts, rememberUpdatedState(networkUseSocksProxy && proxyAuthRandomUnsaved.value)) { + onionHosts.value = it + } + SectionTextFooter(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported)) } - SectionDividerSpaced(maxBottomPadding = false) + SectionDividerSpaced(maxTopPadding = true) + + SectionView(stringResource(MR.strings.network_proxy_auth).uppercase()) { + PreferenceToggle( + stringResource(MR.strings.network_proxy_random_credentials), + checked = proxyAuthRandomUnsaved.value, + onChange = { proxyAuthRandomUnsaved.value = it } + ) + if (!proxyAuthRandomUnsaved.value) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + DefaultConfigurableTextField( + usernameUnsaved, + stringResource(MR.strings.network_proxy_username), + modifier = Modifier.fillMaxWidth(), + isValid = ::validCredential, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Text, + ) + DefaultConfigurableTextField( + passwordUnsaved, + stringResource(MR.strings.network_proxy_password), + modifier = Modifier.fillMaxWidth(), + isValid = ::validCredential, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Password, + ) + } + } + SectionTextFooter(proxyAuthFooter(usernameUnsaved.value.text, passwordUnsaved.value.text, proxyAuthModeUnsaved.value, sessionMode)) + } + + SectionDividerSpaced(maxBottomPadding = false, maxTopPadding = true) SectionView { SectionItemView({ - val newHost = defaultHostPort.split(":").first() - val newPort = defaultHostPort.split(":").last() - hostUnsaved.value = hostUnsaved.value.copy(newHost, TextRange(newHost.length)) - portUnsaved.value = portUnsaved.value.copy(newPort, TextRange(newPort.length)) + hostUnsaved.value = hostUnsaved.value.copy("localhost", TextRange(9)) + portUnsaved.value = portUnsaved.value.copy("9050", TextRange(4)) + usernameUnsaved.value = TextFieldValue() + passwordUnsaved.value = TextFieldValue() + proxyAuthRandomUnsaved.value = true + onionHosts.value = NetCfg.defaults.onionHosts }, disabled = resetDisabled) { Text(stringResource(MR.strings.network_options_reset_to_defaults), color = if (resetDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } SectionItemView( - click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save() } else save() }, + click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save(false) } else save(false) }, disabled = saveDisabled ) { Text(stringResource(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) @@ -305,6 +393,12 @@ fun SocksProxySettings( } } +private fun proxyAuthFooter(username: String, password: String, auth: NetworkProxyAuth, sessionMode: TransportSessionMode): String = when { + auth == NetworkProxyAuth.ISOLATE -> generalGetString(if (sessionMode == TransportSessionMode.User) MR.strings.network_proxy_auth_mode_isolate_by_auth_user else MR.strings.network_proxy_auth_mode_isolate_by_auth_entity) + username.isBlank() && password.isBlank() -> generalGetString(MR.strings.network_proxy_auth_mode_no_auth) + else -> generalGetString(MR.strings.network_proxy_auth_mode_username_password) +} + private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit, close: () -> Unit) { AlertManager.shared.showAlertDialogStacked( title = generalGetString(MR.strings.update_network_settings_question), @@ -319,7 +413,6 @@ private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit, fun UseOnionHosts( onionHosts: MutableState, enabled: State, - showModal: (@Composable ModalData.() -> Unit) -> Unit, useOnion: (OnionHosts) -> Unit, ) { val values = remember { @@ -331,36 +424,29 @@ fun UseOnionHosts( } } } - val onSelected = { - showModal { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { - AppBarTitle(stringResource(MR.strings.network_use_onion_hosts)) - SectionViewSelectable(null, onionHosts, values, useOnion) - } - } - } - if (enabled.value) { - SectionItemWithValue( - generalGetString(MR.strings.network_use_onion_hosts), - onionHosts, - values, - icon = painterResource(MR.images.ic_security), - enabled = enabled, - onSelected = onSelected - ) - } else { - // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before - SectionItemWithValue( - generalGetString(MR.strings.network_use_onion_hosts), - remember { mutableStateOf(OnionHosts.NEVER) }, - listOf(ValueTitleDesc(OnionHosts.NEVER, generalGetString(MR.strings.network_use_onion_hosts_no), AnnotatedString(generalGetString(MR.strings.network_use_onion_hosts_no_desc)))), - icon = painterResource(MR.images.ic_security), - enabled = enabled, - onSelected = {} - ) + Column { + if (enabled.value) { + ExposedDropDownSettingRow( + generalGetString(MR.strings.network_use_onion_hosts), + values.map { it.value to it.title }, + onionHosts, + icon = painterResource(MR.images.ic_security), + enabled = enabled, + onSelected = useOnion + ) + } else { + // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before + ExposedDropDownSettingRow( + generalGetString(MR.strings.network_use_onion_hosts), + listOf(OnionHosts.NEVER to generalGetString(MR.strings.network_use_onion_hosts_no)), + remember { mutableStateOf(OnionHosts.NEVER) }, + icon = painterResource(MR.images.ic_security), + enabled = enabled, + onSelected = {} + ) + } + SectionTextFooter(values.first { it.value == onionHosts.value }.description) } } @@ -398,12 +484,8 @@ fun SessionModePicker( ) } -// https://stackoverflow.com/a/106223 -private fun validHost(s: String): Boolean { - val validIp = Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") - val validHostname = Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])[.])*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$"); - return s.matches(validIp) || s.matches(validHostname) -} +private fun validHost(s: String): Boolean = + !s.contains('@') // https://ihateregex.io/expr/port/ fun validPort(s: String): Boolean { @@ -411,6 +493,16 @@ fun validPort(s: String): Boolean { return s.isNotBlank() && s.matches(validPort) } +private fun validCredential(s: String): Boolean = + !s.contains(':') && !s.contains('@') + +fun showWrongProxyConfigAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.network_proxy_incorrect_config_title), + text = generalGetString(MR.strings.network_proxy_incorrect_config_desc), + ) +} + fun showUpdateNetworkSettingsDialog( title: String, startsWith: String = "", @@ -435,6 +527,7 @@ fun PreviewNetworkAndServersLayout() { NetworkAndServersLayout( currentRemoteHost = null, networkUseSocksProxy = remember { mutableStateOf(true) }, + onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, toggleSocksProxy = {}, ) } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index b81f733cd1..7ebfb7ce54 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -765,7 +765,17 @@ SOCKS proxy SOCKS proxy settings Use SOCKS proxy + Proxy authentication + Use random credentials + Use different proxy credentials for each profile. + Use different proxy credentials for each connection. + Do not use credentials with proxy. + Your credentials may be sent unencrypted. + Username + Password port %d + Error saving proxy + Make sure proxy configuration is correct. Host Port Use SOCKS proxy? From fe0013c4a92dfb039eccacbe1ac791ee68780abf Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 24 Sep 2024 17:51:34 +0100 Subject: [PATCH 171/440] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 399d88b39f..3dcdba3865 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -216,11 +216,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84482C8220D0008C24D1 /* libffi.a */; }; - E5BD844E2C8220D0008C24D1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84492C8220D0008C24D1 /* libgmpxx.a */; }; - E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */; }; - E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */; }; - E5BD84512C8220D0008C24D1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844C2C8220D0008C24D1 /* libgmp.a */; }; + E5CC47842CA31C3A00551ACF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC477F2CA31C3900551ACF /* libgmpxx.a */; }; + E5CC47852CA31C3A00551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC47802CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a */; }; + E5CC47862CA31C3A00551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC47812CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a */; }; + E5CC47872CA31C3A00551ACF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC47822CA31C3A00551ACF /* libgmp.a */; }; + E5CC47882CA31C3A00551ACF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC47832CA31C3A00551ACF /* libffi.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -554,11 +554,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E5BD84482C8220D0008C24D1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E5BD84492C8220D0008C24D1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a"; sourceTree = ""; }; - E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a"; sourceTree = ""; }; - E5BD844C2C8220D0008C24D1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5CC477F2CA31C3900551ACF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E5CC47802CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a"; sourceTree = ""; }; + E5CC47812CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a"; sourceTree = ""; }; + E5CC47822CA31C3A00551ACF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5CC47832CA31C3A00551ACF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -649,14 +649,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E5CC47852CA31C3A00551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a in Frameworks */, + E5CC47872CA31C3A00551ACF /* libgmp.a in Frameworks */, + E5CC47842CA31C3A00551ACF /* libgmpxx.a in Frameworks */, + E5CC47882CA31C3A00551ACF /* libffi.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E5BD844E2C8220D0008C24D1 /* libgmpxx.a in Frameworks */, - E5BD84512C8220D0008C24D1 /* libgmp.a in Frameworks */, + E5CC47862CA31C3A00551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */, - E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */, - E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -733,11 +733,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E5BD84482C8220D0008C24D1 /* libffi.a */, - E5BD844C2C8220D0008C24D1 /* libgmp.a */, - E5BD84492C8220D0008C24D1 /* libgmpxx.a */, - E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */, - E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */, + E5CC47832CA31C3A00551ACF /* libffi.a */, + E5CC47822CA31C3A00551ACF /* libgmp.a */, + E5CC477F2CA31C3900551ACF /* libgmpxx.a */, + E5CC47812CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a */, + E5CC47802CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a */, ); path = Libraries; sourceTree = ""; From 2f730d54e9858452e87e641b7fd618c669da68aa Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 24 Sep 2024 21:48:30 +0100 Subject: [PATCH 172/440] 6.0.5: ios 239, android 241, desktop 68 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3dcdba3865..3f35bc3904 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1887,7 +1887,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1912,7 +1912,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1936,7 +1936,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1961,7 +1961,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1977,11 +1977,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1997,11 +1997,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2022,7 +2022,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2037,7 +2037,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2059,7 +2059,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2074,7 +2074,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2096,7 +2096,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2122,7 +2122,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2147,7 +2147,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2173,7 +2173,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2198,7 +2198,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2213,7 +2213,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2232,7 +2232,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2247,7 +2247,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.0.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index ac2fce0e12..47d9233de7 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.0.4 -android.version_code=237 +android.version_name=6.0.5 +android.version_code=241 -desktop.version_name=6.0.4 -desktop.version_code=65 +desktop.version_name=6.0.5 +desktop.version_code=68 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From e2e6935e5bc47f5660f39071eebeaefd2cb86760 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:16:32 +0400 Subject: [PATCH 173/440] core: fix reactions not being read on item updates (#4938) --- src/Simplex/Chat/Store/Messages.hs | 32 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index e4715c7b12..562a865276 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -1580,7 +1580,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do CPLast count -> liftIO $ getAllChatItemsLast_ count CPAfter afterId count -> liftIO . getAllChatItemsAfter_ afterId count . aChatItemTs =<< getAChatItem_ afterId CPBefore beforeId count -> liftIO . getAllChatItemsBefore_ beforeId count . aChatItemTs =<< getAChatItem_ beforeId - mapM (uncurry (getAChatItem db vr user) >=> liftIO . getACIReactions db) itemRefs + mapM (uncurry (getAChatItem db vr user)) itemRefs where search = fromMaybe "" search_ getAChatItem_ itemId = do @@ -2279,20 +2279,22 @@ getChatRefViaItemId db User {userId} itemId = do (_, _) -> Left $ SEBadChatItem itemId Nothing getAChatItem :: DB.Connection -> VersionRangeChat -> User -> ChatRef -> ChatItemId -> ExceptT StoreError IO AChatItem -getAChatItem db vr user chatRef itemId = case chatRef of - ChatRef CTDirect contactId -> do - ct <- getContact db vr user contactId - (CChatItem msgDir ci) <- getDirectChatItem db user contactId itemId - pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci - ChatRef CTGroup groupId -> do - gInfo <- getGroupInfo db vr user groupId - (CChatItem msgDir ci) <- getGroupChatItem db user groupId itemId - pure $ AChatItem SCTGroup msgDir (GroupChat gInfo) ci - ChatRef CTLocal folderId -> do - nf <- getNoteFolder db user folderId - CChatItem msgDir ci <- getLocalChatItem db user folderId itemId - pure $ AChatItem SCTLocal msgDir (LocalChat nf) ci - _ -> throwError $ SEChatItemNotFound itemId +getAChatItem db vr user chatRef itemId = do + aci <- case chatRef of + ChatRef CTDirect contactId -> do + ct <- getContact db vr user contactId + (CChatItem msgDir ci) <- getDirectChatItem db user contactId itemId + pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci + ChatRef CTGroup groupId -> do + gInfo <- getGroupInfo db vr user groupId + (CChatItem msgDir ci) <- getGroupChatItem db user groupId itemId + pure $ AChatItem SCTGroup msgDir (GroupChat gInfo) ci + ChatRef CTLocal folderId -> do + nf <- getNoteFolder db user folderId + CChatItem msgDir ci <- getLocalChatItem db user folderId itemId + pure $ AChatItem SCTLocal msgDir (LocalChat nf) ci + _ -> throwError $ SEChatItemNotFound itemId + liftIO $ getACIReactions db aci getAChatItemBySharedMsgId :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> SharedMsgId -> ExceptT StoreError IO AChatItem getAChatItemBySharedMsgId db user cd sharedMsgId = case cd of From 6e5eb697a2eaab9ab4f6c6bf671cb56aa9e2438e Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:33:20 +0400 Subject: [PATCH 174/440] core: use broker ts for member profile update item ts (#4940) --- src/Simplex/Chat.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 8e8ab5eb38..583f35bb2b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -4927,7 +4927,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XFile fInv -> processGroupFileInvitation' gInfo m' fInv msg brokerTs XFileCancel sharedMsgId -> xFileCancelGroup gInfo m' sharedMsgId XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInvGroup gInfo m' sharedMsgId fileConnReq_ fName - XInfo p -> xInfoMember gInfo m' p + XInfo p -> xInfoMember gInfo m' p brokerTs XGrpLinkMem p -> xGrpLinkMem gInfo m' conn' p XGrpMemNew memInfo -> xGrpMemNew gInfo m' memInfo msg brokerTs XGrpMemIntro memInfo memRestrictions_ -> xGrpMemIntro gInfo m' memInfo memRestrictions_ @@ -6058,22 +6058,22 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Profile {displayName = n, fullName = fn, image = i, contactLink = cl} = p Profile {displayName = n', fullName = fn', image = i', contactLink = cl'} = p' - xInfoMember :: GroupInfo -> GroupMember -> Profile -> CM () - xInfoMember gInfo m p' = void $ processMemberProfileUpdate gInfo m p' True + xInfoMember :: GroupInfo -> GroupMember -> Profile -> UTCTime -> CM () + xInfoMember gInfo m p' brokerTs = void $ processMemberProfileUpdate gInfo m p' True (Just brokerTs) xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> CM () xGrpLinkMem gInfo@GroupInfo {membership} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do xGrpLinkMemReceived <- withStore $ \db -> getXGrpLinkMemReceived db groupMemberId if viaGroupLink && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived then do - m' <- processMemberProfileUpdate gInfo m p' False + m' <- processMemberProfileUpdate gInfo m p' False Nothing withStore' $ \db -> setXGrpLinkMemReceived db groupMemberId True let connectedIncognito = memberIncognito membership probeMatchingMemberContact m' connectedIncognito else messageError "x.grp.link.mem error: invalid group link host profile update" - processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> CM GroupMember - processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems + processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> Maybe UTCTime -> CM GroupMember + processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems itemTs_ | redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = case memberContactId of Nothing -> do @@ -6103,7 +6103,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createProfileUpdatedItem m' = when createItems $ do let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p' - createInternalChatItem user (CDGroupRcv gInfo m') ciContent Nothing + createInternalChatItem user (CDGroupRcv gInfo m') ciContent itemTs_ createFeatureEnabledItems :: Contact -> CM () createFeatureEnabledItems ct@Contact {mergedPreferences} = @@ -6700,7 +6700,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XMsgDel sharedMsgId memId -> groupMessageDelete gInfo author sharedMsgId memId rcvMsg msgTs XMsgReact sharedMsgId (Just memId) reaction add -> groupMsgReaction gInfo author sharedMsgId memId reaction add rcvMsg msgTs XFileCancel sharedMsgId -> xFileCancelGroup gInfo author sharedMsgId - XInfo p -> xInfoMember gInfo author p + XInfo p -> xInfoMember gInfo author p msgTs XGrpMemNew memInfo -> xGrpMemNew gInfo author memInfo rcvMsg msgTs XGrpMemRole memId memRole -> xGrpMemRole gInfo author memId memRole rcvMsg msgTs XGrpMemDel memId -> xGrpMemDel gInfo author memId rcvMsg msgTs From 9199fbffd5e6608f18e0976d28c354156237598b Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 26 Sep 2024 00:26:04 +0300 Subject: [PATCH 175/440] ios: fix add members search keyboard focus (#4934) * ios: fix add members search keyboard focus * use -1 as ID --------- Co-authored-by: Evgeny Poberezkin --- .../Views/Chat/Group/AddGroupMembersView.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 189ab95494..859d2dfd27 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -105,8 +105,10 @@ struct AddGroupMembersViewCommon: View { .padding(.leading, 2) let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase let members = s == "" ? membersToAdd : membersToAdd.filter { $0.chatViewName.localizedLowercase.contains(s) } - ForEach(members) { contact in - contactCheckView(contact) + ForEach(members + [dummyContact]) { contact in + if contact.contactId != dummyContact.contactId { + contactCheckView(contact) + } } } } @@ -130,6 +132,14 @@ struct AddGroupMembersViewCommon: View { .modifier(ThemedBackground(grouped: true)) } + // Resolves keyboard losing focus bug in iOS16 and iOS17, + // when there are no items inside `ForEach(memebers)` loop + private let dummyContact: Contact = { + var dummy = Contact.sampleData + dummy.contactId = -1 + return dummy + }() + private func inviteMembersButton() -> some View { Button { inviteMembers() From 67472b6285a2aaa4c5d25d72059ffca60e41964c Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 26 Sep 2024 09:00:10 +0100 Subject: [PATCH 176/440] android, desktop: scrolling user profiles (#4939) * android, desktop: scrolling user profiles --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../views/chatlist/UserPicker.android.kt | 145 ++++---- .../common/views/chatlist/UserPicker.kt | 333 +++++++++--------- .../views/chatlist/UserPicker.desktop.kt | 55 ++- apps/multiplatform/gradle.properties | 2 +- 4 files changed, 285 insertions(+), 250 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt index 6c16a75874..b68756c669 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt @@ -5,14 +5,19 @@ import androidx.compose.foundation.* import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.DrawerDefaults.ScrimOpacity import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.User @@ -21,95 +26,101 @@ import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingStage -import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +private val USER_PICKER_IMAGE_SIZE = 44.dp +private val USER_PICKER_ROW_PADDING = 16.dp + @Composable -actual fun UserPickerInactiveUsersSection( +actual fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) { val scrollState = rememberScrollState() + val screenWidthDp = windowWidth() if (users.isNotEmpty()) { SectionItemView( - padding = PaddingValues( - start = 16.dp, - top = if (windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL else DEFAULT_PADDING_HALF, - bottom = DEFAULT_PADDING_HALF), + padding = PaddingValues(), disabled = stopped ) { Box { Row( - modifier = Modifier.padding(end = DEFAULT_PADDING + 30.dp).horizontalScroll(scrollState) + modifier = Modifier.horizontalScroll(scrollState), ) { - users.forEach { u -> - UserPickerInactiveUserBadge(u, stopped) { - onUserClicked(it) - withBGApi { - delay(500) - scrollState.scrollTo(0) + Spacer(Modifier.width(DEFAULT_PADDING)) + Row(horizontalArrangement = Arrangement.spacedBy(USER_PICKER_ROW_PADDING)) { + users.forEach { u -> + UserPickerUserBox(u, stopped, modifier = Modifier.userBoxWidth(u.user, users.size, screenWidthDp)) { + onUserClicked(it) + withBGApi { + delay(500) + scrollState.scrollTo(0) + } } } - Spacer(Modifier.width(20.dp)) - } - Spacer(Modifier.width(60.dp)) - } - Row( - horizontalArrangement = Arrangement.End, - modifier = Modifier - .fillMaxWidth() - .padding(end = DEFAULT_PADDING + 30.dp) - .height(60.dp) - ) { - Canvas(modifier = Modifier.size(60.dp)) { - drawRect( - brush = Brush.horizontalGradient( - colors = listOf( - Color.Transparent, - CurrentColors.value.colors.surface, - ) - ), - ) - } - } - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .height(60.dp) - .fillMaxWidth() - .padding(end = DEFAULT_PADDING) - ) { - IconButton( - onClick = onShowAllProfilesClicked, - enabled = !stopped - ) { - Icon( - painterResource(MR.images.ic_chevron_right), - stringResource(MR.strings.your_chat_profiles), - tint = MaterialTheme.colors.secondary, - modifier = Modifier.size(34.dp) - ) } + Spacer(Modifier.width(DEFAULT_PADDING)) } } } - } else { - UserPickerOptionRow( - painterResource(MR.images.ic_manage_accounts), - stringResource(MR.strings.your_chat_profiles), - onShowAllProfilesClicked + } +} +@Composable +fun UserPickerUserBox( + userInfo: UserInfo, + stopped: Boolean, + modifier: Modifier = Modifier, + onClick: (user: User) -> Unit, +) { + Row( + modifier = modifier + .userPickerBoxModifier() + .clickable ( + onClick = { onClick(userInfo.user) }, + enabled = !stopped + ) + .background(MaterialTheme.colors.background) + .padding(USER_PICKER_ROW_PADDING), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(USER_PICKER_ROW_PADDING) + ) { + Box { + ProfileImageForActiveCall(size = USER_PICKER_IMAGE_SIZE, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) + + if (userInfo.unreadCount > 0 && !userInfo.user.activeUser) { + unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false) + } + } + val user = userInfo.user + Text( + user.displayName, + fontWeight = if (user.activeUser) FontWeight.Bold else FontWeight.Normal, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) } } +@Composable +private fun Modifier.userPickerBoxModifier(): Modifier { + val percent = remember { appPreferences.profileImageCornerRadius.state } + val r = kotlin.math.max(0f, percent.value) + + val cornerSize = when { + r >= 50 -> 50 + r <= 0 -> 0 + else -> r.toInt() + } + + val shape = RoundedCornerShape(CornerSize(cornerSize)) + return this.clip(shape).border(1.dp, MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 1 - userPickerAlpha() - 0.02f), shape) +} + + private fun calculateFraction(pos: Float) = (pos / 1f).coerceIn(0f, 1f) @@ -150,7 +161,7 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow< isLight = colors.isLight, drawerShadingColor = shadingColor, toolbarOnTop = !appPrefs.oneHandUI.get(), - navBarColor = colors.surface + navBarColor = colors.background.mixWith(colors.onBackground, 1 - userPickerAlpha()) ) } else if (ModalManager.start.modalCount.value == 0) { platform.androidSetDrawerStatusAndNavBarColor( @@ -220,3 +231,13 @@ private fun Modifier.draggableBottomDrawerModifier( orientation = Orientation.Vertical, resistance = null ) + +private fun Modifier.userBoxWidth(user: User, totalUsers: Int, windowWidth: Dp): Modifier { + return if (totalUsers == 1) { + this.width(windowWidth - DEFAULT_PADDING * 2) + } else if (user.activeUser) { + this.width(windowWidth - DEFAULT_PADDING - (USER_PICKER_ROW_PADDING * 3) - USER_PICKER_IMAGE_SIZE) + } else { + this.widthIn(max = (windowWidth - (DEFAULT_PADDING * 2)) * 0.618f) + } +} \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 19bc2afbd5..8546dc4fb3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -1,13 +1,11 @@ package chat.simplex.common.views.chatlist import SectionItemView -import SectionView import TextIconSpaced import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -32,7 +30,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* import chat.simplex.common.views.CreateProfile import chat.simplex.common.views.localauth.VerticalDivider -import chat.simplex.common.views.newchat.* import chat.simplex.common.views.remote.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.views.usersettings.AppearanceScope.ColorModeSwitcher @@ -41,6 +38,8 @@ import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +private val USER_PICKER_SECTION_SPACING = 32.dp + @Composable fun UserPicker( chatModel: ChatModel, @@ -57,7 +56,7 @@ fun UserPicker( derivedStateOf { chatModel.users .filter { u -> u.user.activeUser || !u.user.hidden } - .sortedByDescending { it.user.activeUser } + .sortedByDescending { it.user.activeOrder } } } val remoteHosts by remember { @@ -143,10 +142,33 @@ fun UserPicker( .height(IntrinsicSize.Min) .fillMaxWidth() .then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true) else Modifier) - .background(MaterialTheme.colors.surface) - .padding(vertical = DEFAULT_PADDING), + .background(if (appPlatform.isAndroid) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) else MaterialTheme.colors.surface) + .padding(bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), pickerState = userPickerState ) { + val showCustomModal: (@Composable() (ModalData.(ChatModel, () -> Unit) -> Unit)) -> () -> Unit = { modalView -> + { + ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } + } + } + val stopped = remember { chatModel.chatRunning }.value == false + val onUserClicked: (user: User) -> Unit = { user -> + if (!user.activeUser) { + userPickerState.value = AnimatedViewState.HIDING + withBGApi { + controller.showProgressIfNeeded { + ModalManager.closeAllModalsEverywhere() + chatModel.controller.changeActiveUser(user.remoteHostId, user.userId, null) + } + } + } else { + showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }() + withBGApi { + closePicker(userPickerState) + } + } + } + @Composable fun FirstSection() { if (remoteHosts.isNotEmpty()) { @@ -170,87 +192,24 @@ fun UserPicker( } ) } - ActiveUserSection( - chatModel = chatModel, - userPickerState = userPickerState, - ) + val currentUser = remember { chatModel.currentUser }.value + if (appPlatform.isAndroid) { + Column(modifier = Modifier.padding(top = USER_PICKER_SECTION_SPACING, bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL - 3.dp)) { + UserPickerUsersSection( + users = users, + onUserClicked = onUserClicked, + stopped = stopped + ) + } + } else if (currentUser != null) { + SectionItemView({ onUserClicked(currentUser) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { + ProfilePreview(currentUser.profile, stopped = stopped) + } + } } @Composable fun SecondSection() { - GlobalSettingsSection( - chatModel = chatModel, - userPickerState = userPickerState, - setPerformLA = setPerformLA, - onUserClicked = { user -> - userPickerState.value = AnimatedViewState.HIDING - if (!user.activeUser) { - withBGApi { - controller.showProgressIfNeeded { - ModalManager.closeAllModalsEverywhere() - chatModel.controller.changeActiveUser(user.remoteHostId, user.userId, null) - } - } - } - }, - onShowAllProfilesClicked = { - doWithAuth( - generalGetString(MR.strings.auth_open_chat_profiles), - generalGetString(MR.strings.auth_log_in_using_credential) - ) { - ModalManager.start.showCustomModal { close -> - val search = rememberSaveable { mutableStateOf("") } - val profileHidden = rememberSaveable { mutableStateOf(false) } - ModalView( - { close() }, - endButtons = { - SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } - }, - content = { UserProfilesView(chatModel, search, profileHidden) }) - } - } - } - ) - } - - if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) { - Column { - FirstSection() - Divider(Modifier.padding(DEFAULT_PADDING)) - SecondSection() - } - } else { - Row { - Box(Modifier.weight(1f)) { - FirstSection() - } - VerticalDivider() - Box(Modifier.weight(1f)) { - SecondSection() - } - } - } - } -} - -@Composable -private fun ActiveUserSection( - chatModel: ChatModel, - userPickerState: MutableStateFlow, -) { - val showCustomModal: (@Composable() (ModalData.(ChatModel, () -> Unit) -> Unit)) -> () -> Unit = { modalView -> - { - ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } - } - } - val currentUser = remember { chatModel.currentUser }.value - val stopped = chatModel.chatRunning.value == false - - if (currentUser != null) { - SectionView { - SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { - ProfilePreview(currentUser.profile, stopped = stopped) - } UserPickerOptionRow( painterResource(MR.images.ic_qr_code), if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_simplex_contact_address) else generalGetString(MR.strings.create_simplex_address), @@ -266,9 +225,22 @@ private fun ActiveUserSection( }), disabled = stopped ) - } - } else { - SectionView { + if (appPlatform.isDesktop) { + Divider(Modifier.padding(DEFAULT_PADDING)) + + val inactiveUsers = users.filter { !it.user.activeUser } + + if (inactiveUsers.isNotEmpty()) { + Column(modifier = Modifier.padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)) { + UserPickerUsersSection( + users = inactiveUsers, + onUserClicked = onUserClicked, + stopped = stopped + ) + } + } + } + if (chatModel.desktopNoUserNoRemote) { UserPickerOptionRow( painterResource(MR.images.ic_manage_accounts), @@ -284,76 +256,121 @@ private fun ActiveUserSection( } } ) + } else { + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + stringResource(MR.strings.your_chat_profiles), + { + doWithAuth( + generalGetString(MR.strings.auth_open_chat_profiles), + generalGetString(MR.strings.auth_log_in_using_credential) + ) { + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + val profileHidden = rememberSaveable { mutableStateOf(false) } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { UserProfilesView(chatModel, search, profileHidden) }) + } + } + }, + disabled = stopped + ) + } + } + + if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) { + Column { + FirstSection() + SecondSection() + GlobalSettingsSection( + userPickerState = userPickerState, + setPerformLA = setPerformLA, + ) + } + } else { + Column { + FirstSection() + Row { + Box(Modifier.weight(1f)) { + Column { + SecondSection() + } + } + VerticalDivider() + Box(Modifier.weight(1f)) { + Column { + GlobalSettingsSection( + userPickerState = userPickerState, + setPerformLA = setPerformLA, + ) + } + } + } } } } } +fun userPickerAlpha(): Float { + return when (CurrentColors.value.base) { + DefaultTheme.LIGHT -> 0.05f + DefaultTheme.DARK -> 0.05f + DefaultTheme.BLACK -> 0.075f + DefaultTheme.SIMPLEX -> 0.035f + } +} + @Composable private fun GlobalSettingsSection( - chatModel: ChatModel, userPickerState: MutableStateFlow, setPerformLA: (Boolean) -> Unit, - onUserClicked: (user: User) -> Unit, - onShowAllProfilesClicked: () -> Unit ) { - val stopped = chatModel.chatRunning.value == false - val users by remember { - derivedStateOf { - chatModel.users - .filter { u -> !u.user.hidden && !u.user.activeUser } - } - } + val stopped = remember { chatModel.chatRunning }.value == false - SectionView(headerBottomPadding = if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_PADDING else 0.dp) { - UserPickerInactiveUsersSection( - users = users, - onShowAllProfilesClicked = onShowAllProfilesClicked, - onUserClicked = onUserClicked, - stopped = stopped - ) + if (appPlatform.isAndroid) { + val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - if (appPlatform.isAndroid) { - val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - - UserPickerOptionRow( - painterResource(MR.images.ic_desktop), - text, - click = { - ModalManager.start.showCustomModal { close -> - ConnectDesktopView(close) - } - } - ) - } else { - UserPickerOptionRow( - icon = painterResource(MR.images.ic_smartphone_300), - text = stringResource(if (remember { chat.simplex.common.platform.chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), - click = { - userPickerState.value = AnimatedViewState.HIDING - ModalManager.start.showModal { - ConnectMobileView() - } - }, - disabled = stopped - ) - } - - SectionItemView( + UserPickerOptionRow( + painterResource(MR.images.ic_desktop), + text, click = { - ModalManager.start.showModalCloseable { close -> - SettingsView(chatModel, setPerformLA, close) + ModalManager.start.showCustomModal { close -> + ConnectDesktopView(close) + } + } + ) + } else { + UserPickerOptionRow( + icon = painterResource(MR.images.ic_smartphone_300), + text = stringResource(if (remember { chat.simplex.common.platform.chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), + click = { + userPickerState.value = AnimatedViewState.HIDING + ModalManager.start.showModal { + ConnectMobileView() } }, - padding = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING + 2.dp) - ) { - val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_settings), text, tint = MaterialTheme.colors.secondary) - TextIconSpaced() - Text(text, color = Color.Unspecified) - Spacer(Modifier.weight(1f)) - ColorModeSwitcher() - } + disabled = stopped + ) + } + + SectionItemView( + click = { + ModalManager.start.showModalCloseable { close -> + SettingsView(chatModel, setPerformLA, close) + } + }, + padding = if (appPlatform.isDesktop) PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING + 2.dp) else PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF) + ) { + val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) + Icon(painterResource(MR.images.ic_settings), text, tint = MaterialTheme.colors.secondary) + TextIconSpaced() + Text(text, color = Color.Unspecified) + Spacer(Modifier.weight(1f)) + ColorModeSwitcher() } } @@ -361,7 +378,7 @@ private fun GlobalSettingsSection( fun UserProfilePickerItem( u: User, unreadCount: Int = 0, - enabled: Boolean = chatModel.chatRunning.value == true || chatModel.connectedToRemote, + enabled: Boolean = remember { chatModel.chatRunning }.value == true || chatModel.connectedToRemote, onLongClick: () -> Unit = {}, openSettings: () -> Unit = {}, onClick: () -> Unit @@ -410,7 +427,7 @@ fun UserProfilePickerItem( } @Composable -fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == true || chatModel.connectedToRemote) { +fun UserProfileRow(u: User, enabled: Boolean = remember { chatModel.chatRunning }.value == true || chatModel.connectedToRemote) { Row( Modifier .widthIn(max = windowWidth() * 0.7f) @@ -433,31 +450,13 @@ fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == tr @Composable fun UserPickerOptionRow(icon: Painter, text: String, click: (() -> Unit)? = null, disabled: Boolean = false) { - SectionItemView(click, disabled = disabled, extraPadding = true) { + SectionItemView(click, disabled = disabled, extraPadding = appPlatform.isDesktop) { Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.secondary) TextIconSpaced() Text(text = text, color = if (disabled) MaterialTheme.colors.secondary else Color.Unspecified) } } -@Composable -fun UserPickerInactiveUserBadge(userInfo: UserInfo, stopped: Boolean, size: Dp = 60.dp, onClick: (user: User) -> Unit) { - Box { - IconButton( - onClick = { onClick(userInfo.user) }, - enabled = !stopped - ) { - Box { - ProfileImage(size = size, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) - - if (userInfo.unreadCount > 0) { - unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs) - } - } - } - } -} - @OptIn(ExperimentalLayoutApi::class) @Composable private fun DevicePickerRow( @@ -471,7 +470,7 @@ private fun DevicePickerRow( Modifier .fillMaxWidth() .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) - .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), + .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_PADDING + DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { @@ -517,10 +516,9 @@ private fun DevicePickerRow( } @Composable -expect fun UserPickerInactiveUsersSection( +expect fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) @@ -606,14 +604,14 @@ fun HostDisconnectButton(onClick: (() -> Unit)?) { } @Composable -private fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean) { +fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean, hasPadding: Boolean) { Text( if (unreadCount > 0) unreadCountStr(unreadCount) else "", color = Color.White, fontSize = 10.sp, style = TextStyle(textAlign = TextAlign.Center), modifier = Modifier - .offset(y = 3.sp.toDp()) + .offset(y = if (hasPadding) 3.sp.toDp() else -4.sp.toDp(), x = if (hasPadding) 0.dp else 4.sp.toDp()) .background(if (userMuted) MaterialTheme.colors.primaryVariant else MaterialTheme.colors.secondary, shape = CircleShape) .badgeLayout() .padding(horizontal = 2.sp.toDp()) @@ -622,7 +620,6 @@ private fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean) { ) } - private suspend fun closePicker(userPickerState: MutableStateFlow) { delay(500) userPickerState.value = AnimatedViewState.HIDING diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt index 583d5437c3..d9b53b9485 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt @@ -1,31 +1,31 @@ package chat.simplex.common.views.chatlist import androidx.compose.animation.* -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import chat.simplex.common.model.User import chat.simplex.common.model.UserInfo import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.MutableStateFlow @Composable -actual fun UserPickerInactiveUsersSection( +actual fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) { if (users.isNotEmpty()) { @@ -34,13 +34,13 @@ actual fun UserPickerInactiveUsersSection( val horizontalPadding = DEFAULT_PADDING_HALF + 8.dp Column(Modifier - .padding(horizontal = horizontalPadding, vertical = DEFAULT_PADDING_HALF) - .height(55.dp * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) + .padding(horizontal = horizontalPadding) + .height((55.dp + 16.sp.toDp()) * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) ) { ColumnWithScrollBar( verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING) ) { - val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (55.dp * 5)) / 5 + val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (65.dp * 5)) / 5 userRows.forEach { row -> Row( @@ -48,8 +48,31 @@ actual fun UserPickerInactiveUsersSection( horizontalArrangement = Arrangement.spacedBy(spaceBetween), ) { row.forEach { u -> - UserPickerInactiveUserBadge(u, stopped, size = 55.dp) { - onUserClicked(u.user) + Column(modifier = Modifier + .clickable ( + onClick = { onUserClicked(u.user) }, + enabled = !stopped + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val user = u.user + Box { + ProfileImage(size = 55.dp, image = user.profile.image, color = MaterialTheme.colors.secondaryVariant) + + if (u.unreadCount > 0 && !user.activeUser) { + unreadBadge(u.unreadCount, user.showNtfs, true) + } + } + + Text( + user.displayName, + fontSize = 12.sp, + fontWeight = if (user.activeUser) FontWeight.Bold else FontWeight.Normal, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.width(65.dp), + textAlign = TextAlign.Center + ) } } } @@ -57,12 +80,6 @@ actual fun UserPickerInactiveUsersSection( } } } - - UserPickerOptionRow( - painterResource(MR.images.ic_manage_accounts), - stringResource(MR.strings.your_chat_profiles), - onShowAllProfilesClicked - ) } @Composable @@ -83,4 +100,4 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow< } } } -} \ No newline at end of file +} diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index f07eea9ec7..4507f940cb 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -27,7 +27,7 @@ kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 android.version_name=6.1-beta.1 -android.version_code=240 +android.version_code=242 desktop.version_name=6.1-beta.1 desktop.version_code=67 From 65c7ecbddffe7dbf725284591e34130f9d16c5a7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 26 Sep 2024 13:45:12 +0100 Subject: [PATCH 177/440] core: 6.1.0.4 (simplexmq 6.1.0.1) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cabal.project b/cabal.project index d5e17ea299..3823a31790 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 7dcac19a671f76dedbe769030742714783946bd3 + tag: 03168b9fbfd8a507a5374ff3375609e705225e29 source-repository-package type: git diff --git a/package.yaml b/package.yaml index 4e4ba3a549..edeb47ce27 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.3 +version: 6.1.0.4 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 91e5e8866f..eaa7f0d129 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."7dcac19a671f76dedbe769030742714783946bd3" = "0c1jygir4c1s8g4hdz7b6vw69bvcrknbih9rq8y8rv3d8zl32qpq"; + "https://github.com/simplex-chat/simplexmq.git"."03168b9fbfd8a507a5374ff3375609e705225e29" = "0g6xry0far2wppfxv99hqh3ckldglmpazib5l8vsvcmivcz5zww1"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index a6c96ecc91..2386128bb6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.0.3 +version: 6.1.0.4 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 0d39951b90..ff77ac0546 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 1, 0, 2] +minRemoteCtrlVersion = AppVersion [6, 1, 0, 4] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 1, 0, 2] +minRemoteHostVersion = AppVersion [6, 1, 0, 4] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 4a39b481b148f04177510bbf2d20e1e4f5fe6a0f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 26 Sep 2024 17:28:14 +0100 Subject: [PATCH 178/440] ios: avoid message changing width when sent/received ticks appear (#4945) --- .../Shared/Views/Chat/ChatItem/CIMetaView.swift | 13 +++++++++---- apps/ios/SimpleXChat/ChatTypes.swift | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 9840b22fc8..32249506d3 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -75,9 +75,14 @@ enum MetaColorMode { } } - var statusSpacer: Text { + func statusSpacer(_ sent: Bool) -> Text { switch self { - case .normal, .transparent: Text(Image(systemName: "circlebadge.fill")).foregroundColor(.clear) + case .normal, .transparent: + Text( + sent + ? Image("checkmark.wide") + : Image(systemName: "circlebadge.fill") + ).foregroundColor(.clear) case .invertedMaterial: Text(" ").kerning(13) } } @@ -130,10 +135,10 @@ func ciMetaText( colorMode.resolve(statusColor) } r = r + colored(Text(image), metaColor) - space = Text(" ") } else if !meta.disappearing { - space = colorMode.statusSpacer + Text(" ") + r = r + colorMode.statusSpacer(meta.itemStatus.sent) } + space = Text(" ") } if let enc = encrypted { appendSpace() diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 0777503650..6e687538c0 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2820,6 +2820,20 @@ public enum CIStatus: Decodable, Hashable { case .invalid: return "invalid" } } + + public var sent: Bool { + switch self { + case .sndNew: true + case .sndSent: true + case .sndRcvd: true + case .sndErrorAuth: true + case .sndError: true + case .sndWarning: true + case .rcvNew: false + case .rcvRead: false + case .invalid: false + } + } public func statusIcon(_ metaColor: Color, _ paleMetaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { switch self { From 95c1d8d7982db9fe9d5d061745a18d0ea64ff754 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 27 Sep 2024 02:18:05 +0700 Subject: [PATCH 179/440] android, desktop: calls switching from audio to video and back (#4814) * android, desktop: calls switching from audio to video and back * refactor * working all 4 streams with mute handling differently * changes * changes * wrong file * changes * padding * android camera service type * icons, sizes, clickable * refactor * Revert "android camera service type" This reverts commit 9878ff38e9ef2a18e81dcdaaed24e65d88abf934. * late init camera permissions * enabling camera sooner than call establishes (not fully done) * changes * alpha * fixes for Safari * enhancements * fix Safari sound * padding between buttons on desktop * android default values for padding * changes * calls without encryption are supported and flipping camera on some devices works * unused param * logs * background color * play local video in Safari * no line height * removed one listener from per frame processing * enhancements --------- Co-authored-by: Evgeny Poberezkin --- .../main/java/chat/simplex/app/CallService.kt | 5 +- .../simplex/app/views/call/CallActivity.kt | 39 +- .../views/call/CallAudioDeviceManager.kt | 6 +- .../common/views/call/CallView.android.kt | 249 ++--- .../views/chatlist/ChatListView.android.kt | 3 +- .../chat/simplex/common/model/SimpleXAPI.kt | 2 +- .../simplex/common/views/call/CallManager.kt | 2 +- .../chat/simplex/common/views/call/WebRTC.kt | 41 +- .../simplex/common/views/chat/ChatView.kt | 2 +- .../helpers/ExposedDropDownSettingRow.kt | 28 +- .../commonMain/resources/MR/base/strings.xml | 1 + .../resources/MR/images/ic_volume_off.svg | 1 + .../resources/assets/www/android/call.html | 19 + .../resources/assets/www/android/style.css | 54 ++ .../commonMain/resources/assets/www/call.js | 849 ++++++++++++++--- .../resources/assets/www/desktop/call.html | 31 +- .../www/desktop/images/ic_call_end_filled.svg | 2 +- .../www/desktop/images/ic_volume_off.svg | 1 + .../resources/assets/www/desktop/style.css | 75 +- .../resources/assets/www/desktop/ui.js | 170 +++- .../common/views/call/CallView.desktop.kt | 26 +- .../views/chatlist/ChatListView.desktop.kt | 3 +- .../simplex-chat-webrtc/src/android/call.html | 19 + .../simplex-chat-webrtc/src/android/style.css | 54 ++ packages/simplex-chat-webrtc/src/call.ts | 900 +++++++++++++++--- .../simplex-chat-webrtc/src/desktop/call.html | 31 +- .../src/desktop/images/ic_call_end_filled.svg | 2 +- .../src/desktop/images/ic_volume_off.svg | 1 + .../simplex-chat-webrtc/src/desktop/style.css | 75 +- .../simplex-chat-webrtc/src/desktop/ui.ts | 166 +++- 30 files changed, 2319 insertions(+), 538 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_off.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/images/ic_volume_off.svg create mode 100644 packages/simplex-chat-webrtc/src/desktop/images/ic_volume_off.svg diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt index 3b334bf70b..6c3d96bebc 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt @@ -2,6 +2,7 @@ package chat.simplex.app import android.app.* import android.content.* +import android.content.pm.PackageManager import android.content.pm.ServiceInfo import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -83,7 +84,7 @@ class CallService: Service() { generalGetString(MR.strings.notification_preview_somebody) else call?.contact?.profile?.displayName ?: "" - val text = generalGetString(if (call?.supportsVideo() == true) MR.strings.call_service_notification_video_call else MR.strings.call_service_notification_audio_call) + val text = generalGetString(if (call?.hasVideo == true) MR.strings.call_service_notification_video_call else MR.strings.call_service_notification_audio_call) val image = call?.contact?.image val largeIcon = if (image == null || previewMode == NotificationPreviewMode.HIDDEN.name) BitmapFactory.decodeResource(resources, R.drawable.icon) @@ -105,7 +106,7 @@ class CallService: Service() { 0 } } else if (Build.VERSION.SDK_INT >= 30) { - if (call.supportsVideo()) { + if (call.hasVideo && ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA } else { ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index a9697069c0..e7503733ac 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -116,7 +116,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { private fun hasGrantedPermissions(): Boolean { val grantedAudio = ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED - val grantedCamera = !callSupportsVideo() || ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED + val grantedCamera = !callHasVideo() || ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED return grantedAudio && grantedCamera } @@ -124,7 +124,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { override fun onBackPressed() { if (isOnLockScreenNow()) { super.onBackPressed() - } else if (!hasGrantedPermissions() && !callSupportsVideo()) { + } else if (!hasGrantedPermissions() && !callHasVideo()) { val call = m.activeCall.value if (call != null) { withBGApi { chatModel.callManager.endCall(call) } @@ -142,7 +142,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { override fun onUserLeaveHint() { super.onUserLeaveHint() // On Android 12+ PiP is enabled automatically when a user hides the app - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callHasVideo() && platform.androidPictureInPictureAllowed()) { enterPictureInPictureMode() } } @@ -198,7 +198,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { fun getKeyguardManager(context: Context): KeyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager -private fun callSupportsVideo() = m.activeCall.value?.supportsVideo() == true || m.activeCallInvitation.value?.callType?.media == CallMediaType.Video +private fun callHasVideo() = m.activeCall.value?.hasVideo == true || m.activeCallInvitation.value?.callType?.media == CallMediaType.Video @Composable fun CallActivityView() { @@ -212,7 +212,7 @@ fun CallActivityView() { .collect { collapsed -> when { collapsed -> { - if (!platform.androidPictureInPictureAllowed() || !callSupportsVideo()) { + if (!platform.androidPictureInPictureAllowed() || !callHasVideo()) { activity.moveTaskToBack(true) activity.startActivity(Intent(activity, MainActivity::class.java)) } else if (!activity.isInPictureInPictureMode && activity.lifecycle.currentState == Lifecycle.State.RESUMED) { @@ -221,7 +221,7 @@ fun CallActivityView() { activity.enterPictureInPictureMode() } } - callSupportsVideo() && !platform.androidPictureInPictureAllowed() -> { + callHasVideo() && !platform.androidPictureInPictureAllowed() -> { // PiP disabled by user platform.androidStartCallActivity(false) } @@ -242,28 +242,43 @@ fun CallActivityView() { Box(Modifier.background(Color.Black)) { if (call != null) { val permissionsState = rememberMultiplePermissionsState( - permissions = if (callSupportsVideo()) { + permissions = if (callHasVideo()) { listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) } else { listOf(Manifest.permission.RECORD_AUDIO) } ) - if (permissionsState.allPermissionsGranted) { + // callState == connected is needed in a situation when a peer enabled camera in audio call while a user didn't grant camera permission yet, + // so no need to hide active call view in this case + if (permissionsState.allPermissionsGranted || call.callState == CallState.Connected) { ActiveCallView() LaunchedEffect(Unit) { activity.startServiceAndBind() } - } else { - CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) { + } + if ((!permissionsState.allPermissionsGranted && call.callState != CallState.Connected) || call.wantsToEnableCamera) { + CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callHasVideo() || call.wantsToEnableCamera) { withBGApi { chatModel.callManager.endCall(call) } } + val cameraAndMicPermissions = rememberMultiplePermissionsState(permissions = listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)) + DisposableEffect(cameraAndMicPermissions.allPermissionsGranted) { + onDispose { + if (call.wantsToEnableCamera && cameraAndMicPermissions.allPermissionsGranted) { + val activeCall = chatModel.activeCall.value + if (activeCall != null && activeCall.contact.apiId == call.contact.apiId) { + chatModel.activeCall.value = activeCall.copy(wantsToEnableCamera = false) + chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Camera, enable = true)) + } + } + } + } } val view = LocalView.current - if (callSupportsVideo()) { + if (callHasVideo()) { val scope = rememberCoroutineScope() LaunchedEffect(Unit) { scope.launch { - activity.setPipParams(callSupportsVideo(), viewRatio = Rational(view.width, view.height)) + activity.setPipParams(callHasVideo(), viewRatio = Rational(view.width, view.height)) activity.trackPipAnimationHintView(view) } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt index bada85746f..ec0fd9fea8 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt @@ -47,7 +47,7 @@ class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface { Log.d(TAG, "Added audio devices2: ${devices.value.map { it.type }}") if (devices.value.size - oldDevices.size > 0) { - selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, false) + selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, false) } } @@ -116,14 +116,14 @@ class PreSCallAudioDeviceManager: CallAudioDeviceManagerInterface { Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}") super.onAudioDevicesAdded(addedDevices) devices.value = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).filter { it.hasSupportedType() }.excludeSameType().excludeEarpieceIfWired() - selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, false) + selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, false) } override fun onAudioDevicesRemoved(removedDevices: Array) { Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}") super.onAudioDevicesRemoved(removedDevices) devices.value = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).filter { it.hasSupportedType() }.excludeSameType().excludeEarpieceIfWired() - selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, true) + selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, true) } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 22f0c8d70b..e7fd11f5ac 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -7,6 +7,7 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.* import android.content.pm.ActivityInfo +import android.content.pm.PackageManager import android.media.* import android.os.Build import android.os.PowerManager @@ -16,9 +17,12 @@ import android.view.ViewGroup import android.webkit.* import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* +import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.snapshots.SnapshotStateList @@ -27,12 +31,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.* import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat import androidx.lifecycle.* import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewClientCompat @@ -119,26 +124,26 @@ actual fun ActiveCallView() { val callRh = call.remoteHostId when (val r = apiMsg.resp) { is WCallResponse.Capabilities -> withBGApi { - val callType = CallType(call.localMedia, r.capabilities) + val callType = CallType(call.initialCallType, r.capabilities) chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType) updateActiveCall(call) { it.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // Starting is delayed to make Android <= 11 working good with Bluetooth callAudioDeviceManager.start() } else { - callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.supportsVideo(), true) + callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) } CallSoundsPlayer.startConnectingCallSound(scope) activeCallWaitDeliveryReceipt(scope) } is WCallResponse.Offer -> withBGApi { - chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities) + chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.initialCallType, r.capabilities) updateActiveCall(call) { it.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // Starting is delayed to make Android <= 11 working good with Bluetooth callAudioDeviceManager.start() } else { - callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.supportsVideo(), true) + callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) } } is WCallResponse.Answer -> withBGApi { @@ -162,6 +167,17 @@ actual fun ActiveCallView() { is WCallResponse.Connected -> { updateActiveCall(call) { it.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo) } } + is WCallResponse.PeerMedia -> { + updateActiveCall(call) { + val sources = it.peerMediaSources + when (r.source) { + CallMediaSource.Mic -> it.copy(peerMediaSources = sources.copy(mic = r.enabled)) + CallMediaSource.Camera -> it.copy(peerMediaSources = sources.copy(camera = r.enabled)) + CallMediaSource.ScreenAudio -> it.copy(peerMediaSources = sources.copy(screenAudio = r.enabled)) + CallMediaSource.ScreenVideo -> it.copy(peerMediaSources = sources.copy(screenVideo = r.enabled)) + } + } + } is WCallResponse.End -> { withBGApi { chatModel.callManager.endCall(call) } } @@ -174,16 +190,19 @@ actual fun ActiveCallView() { updateActiveCall(call) { it.copy(callState = CallState.Negotiated) } is WCallCommand.Media -> { updateActiveCall(call) { - when (cmd.media) { - CallMediaType.Video -> it.copy(videoEnabled = cmd.enable) - CallMediaType.Audio -> it.copy(audioEnabled = cmd.enable) + val sources = it.localMediaSources + when (cmd.source) { + CallMediaSource.Mic -> it.copy(localMediaSources = sources.copy(mic = cmd.enable)) + CallMediaSource.Camera -> it.copy(localMediaSources = sources.copy(camera = cmd.enable)) + CallMediaSource.ScreenAudio -> it.copy(localMediaSources = sources.copy(screenAudio = cmd.enable)) + CallMediaSource.ScreenVideo -> it.copy(localMediaSources = sources.copy(screenVideo = cmd.enable)) } } } is WCallCommand.Camera -> { updateActiveCall(call) { it.copy(localCamera = cmd.camera) } - if (!call.audioEnabled) { - chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Audio, enable = false)) + if (!call.localMediaSources.mic) { + chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = false)) } } is WCallCommand.End -> { @@ -200,7 +219,6 @@ actual fun ActiveCallView() { val showOverlay = when { call == null -> false !platform.androidPictureInPictureAllowed() -> true - !call.supportsVideo() -> true !chatModel.activeCallViewIsCollapsed.value -> true else -> false } @@ -208,6 +226,11 @@ actual fun ActiveCallView() { ActiveCallOverlay(call, chatModel, callAudioDeviceManager) } } + KeyChangeEffect(call?.hasVideo) { + if (call != null) { + callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) + } + } val context = LocalContext.current DisposableEffect(Unit) { val activity = context as? Activity ?: return@DisposableEffect onDispose {} @@ -237,9 +260,15 @@ private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, callAudioDeviceM devices = remember { callAudioDeviceManager.devices }.value, currentDevice = remember { callAudioDeviceManager.currentDevice }, dismiss = { withBGApi { chatModel.callManager.endCall(call) } }, - toggleAudio = { chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Audio, enable = !call.audioEnabled)) }, + toggleAudio = { chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = !call.localMediaSources.mic)) }, selectDevice = { callAudioDeviceManager.selectDevice(it.id) }, - toggleVideo = { chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Video, enable = !call.videoEnabled)) }, + toggleVideo = { + if (ContextCompat.checkSelfPermission(androidAppContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Camera, enable = !call.localMediaSources.camera)) + } else { + updateActiveCall(call) { it.copy(wantsToEnableCamera = true) } + } + }, toggleSound = { val enableSpeaker = callAudioDeviceManager.currentDevice.value?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE val preferredInternalDevice = callAudioDeviceManager.devices.value.firstOrNull { it.type == if (enableSpeaker) AudioDeviceInfo.TYPE_BUILTIN_SPEAKER else AudioDeviceInfo.TYPE_BUILTIN_EARPIECE } @@ -293,30 +322,30 @@ private fun ActiveCallOverlayLayout( flipCamera: () -> Unit ) { Column { - val media = call.peerMedia ?: call.localMedia CloseSheetBar({ chatModel.activeCallViewIsCollapsed.value = true }, true, tintColor = Color(0xFFFFFFD8)) { - if (media == CallMediaType.Video) { + if (call.hasVideo) { Text(call.contact.chatViewName, Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING), color = Color(0xFFFFFFD8), style = MaterialTheme.typography.h2, overflow = TextOverflow.Ellipsis, maxLines = 1) } } Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { @Composable - fun SelectSoundDevice() { + fun SelectSoundDevice(size: Dp) { if (devices.size == 2 && devices.all { it.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE || it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } || currentDevice.value == null || devices.none { it.id == currentDevice.value?.id } ) { val isSpeaker = currentDevice.value?.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER - ToggleSoundButton(call, enabled, isSpeaker, toggleSound) + ToggleSoundButton(enabled, isSpeaker, !call.peerMediaSources.mic, toggleSound, size = size) } else { ExposedDropDownSettingWithIcon( - devices.map { Triple(it, it.icon, if (it.name != null) generalGetString(it.name!!) else it.productName.toString()) }, + devices.map { Triple(it, if (call.peerMediaSources.mic) it.icon else MR.images.ic_volume_off, if (it.name != null) generalGetString(it.name!!) else it.productName.toString()) }, currentDevice, fontSize = 18.sp, - iconSize = 40.dp, + boxSize = size, listIconSize = 30.dp, iconColor = Color(0xFFFFFFD8), + background = controlButtonsBackground(), minWidth = 300.dp, onSelected = { if (it != null) { @@ -327,29 +356,9 @@ private fun ActiveCallOverlayLayout( } } - when (media) { - CallMediaType.Video -> { - VideoCallInfoView(call) - Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { - DisabledBackgroundCallsButton() - } - Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { - ToggleAudioButton(call, enabled, toggleAudio) - SelectSoundDevice() - IconButton(onClick = dismiss, enabled = enabled) { - Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp)) - } - if (call.videoEnabled) { - ControlButton(call, painterResource(MR.images.ic_flip_camera_android_filled), MR.strings.icon_descr_flip_camera, enabled, flipCamera) - ControlButton(call, painterResource(MR.images.ic_videocam_filled), MR.strings.icon_descr_video_off, enabled, toggleVideo) - } else { - Spacer(Modifier.size(48.dp)) - ControlButton(call, painterResource(MR.images.ic_videocam_off), MR.strings.icon_descr_video_on, enabled, toggleVideo) - } - } - } - - CallMediaType.Audio -> { + when (call.hasVideo) { + true -> VideoCallInfoView(call) + false -> { Spacer(Modifier.fillMaxHeight().weight(1f)) Column( Modifier.fillMaxWidth(), @@ -359,23 +368,26 @@ private fun ActiveCallOverlayLayout( ProfileImage(size = 192.dp, image = call.contact.profile.image) AudioCallInfoView(call) } - Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { - DisabledBackgroundCallsButton() - } - Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - IconButton(onClick = dismiss, enabled = enabled) { - Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp)) - } - } - Box(Modifier.padding(start = 32.dp)) { - ToggleAudioButton(call, enabled, toggleAudio) - } - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { - Box(Modifier.padding(end = 32.dp)) { - SelectSoundDevice() - } - } + } + } + Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { + DisabledBackgroundCallsButton() + } + + BoxWithConstraints(Modifier.padding(start = 6.dp, end = 6.dp, bottom = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { + val size = ((maxWidth - DEFAULT_PADDING_HALF * 4) / 5).coerceIn(0.dp, 60.dp) + // limiting max width for tablets/wide screens, will be displayed in the center + val padding = ((min(420.dp, maxWidth) - size * 5) / 4).coerceAtLeast(0.dp) + Row(horizontalArrangement = Arrangement.spacedBy(padding), verticalAlignment = Alignment.CenterVertically) { + ToggleMicButton(call, enabled, toggleAudio, size = size) + SelectSoundDevice(size = size) + ControlButton(painterResource(MR.images.ic_call_end_filled), MR.strings.icon_descr_hang_up, enabled = enabled, dismiss, background = Color.Red, size = size, iconPaddingPercent = 0.166f) + if (call.localMediaSources.camera) { + ControlButton(painterResource(MR.images.ic_flip_camera_android_filled), MR.strings.icon_descr_flip_camera, enabled, flipCamera, size = size) + ControlButton(painterResource(MR.images.ic_videocam_filled), MR.strings.icon_descr_video_off, enabled, toggleVideo, size = size) + } else { + Spacer(Modifier.size(size)) + ControlButton(painterResource(MR.images.ic_videocam_off), MR.strings.icon_descr_video_on, enabled, toggleVideo, size = size) } } } @@ -384,34 +396,52 @@ private fun ActiveCallOverlayLayout( } @Composable -private fun ControlButton(call: Call, icon: Painter, iconText: StringResource, enabled: Boolean = true, action: () -> Unit) { - if (call.hasMedia) { - IconButton(onClick = action, enabled = enabled) { - Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.size(40.dp)) - } - } else { - Spacer(Modifier.size(40.dp)) +private fun ControlButton(icon: Painter, iconText: StringResource, enabled: Boolean = true, action: () -> Unit, background: Color = controlButtonsBackground(), size: Dp, iconPaddingPercent: Float = 0.2f) { + ControlButtonWrap(enabled, action, background, size) { + Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.padding(size * iconPaddingPercent).fillMaxSize()) } } @Composable -private fun ToggleAudioButton(call: Call, enabled: Boolean = true, toggleAudio: () -> Unit) { - if (call.audioEnabled) { - ControlButton(call, painterResource(MR.images.ic_mic), MR.strings.icon_descr_audio_off, enabled, toggleAudio) - } else { - ControlButton(call, painterResource(MR.images.ic_mic_off), MR.strings.icon_descr_audio_on, enabled, toggleAudio) +private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, background: Color = controlButtonsBackground(), size: Dp, content: @Composable () -> Unit) { + Box( + Modifier + .background(background, CircleShape) + .size(size) + .clickable( + onClick = action, + role = Role.Button, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false, radius = size / 2, color = background.lighter(0.1f)), + enabled = enabled + ), + contentAlignment = Alignment.Center + ) { + content() } } @Composable -private fun ToggleSoundButton(call: Call, enabled: Boolean, speaker: Boolean, toggleSound: () -> Unit) { - if (speaker) { - ControlButton(call, painterResource(MR.images.ic_volume_up), MR.strings.icon_descr_speaker_off, enabled, toggleSound) +private fun ToggleMicButton(call: Call, enabled: Boolean = true, toggleAudio: () -> Unit, size: Dp) { + if (call.localMediaSources.mic) { + ControlButton(painterResource(MR.images.ic_mic), MR.strings.icon_descr_audio_off, enabled, toggleAudio, size = size) } else { - ControlButton(call, painterResource(MR.images.ic_volume_down), MR.strings.icon_descr_speaker_on, enabled, toggleSound) + ControlButton(painterResource(MR.images.ic_mic_off), MR.strings.icon_descr_audio_on, enabled, toggleAudio, size = size) } } +@Composable +private fun ToggleSoundButton(enabled: Boolean, speaker: Boolean, muted: Boolean, toggleSound: () -> Unit, size: Dp) { + when { + muted -> ControlButton(painterResource(MR.images.ic_volume_off), MR.strings.icon_descr_sound_muted, enabled, toggleSound, size = size) + speaker -> ControlButton(painterResource(MR.images.ic_volume_up), MR.strings.icon_descr_speaker_off, enabled, toggleSound, size = size) + else -> ControlButton(painterResource(MR.images.ic_volume_down), MR.strings.icon_descr_speaker_on, enabled, toggleSound, size = size) + } +} + +@Composable +fun controlButtonsBackground(): Color = if (chatModel.activeCall.value?.peerMediaSources?.hasVideo == true) Color.Black.copy(0.2f) else Color.White.copy(0.2f) + @Composable fun AudioCallInfoView(call: Call) { Column(horizontalAlignment = Alignment.CenterHorizontally) { @@ -553,38 +583,39 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni } } } else { - ColumnWithScrollBar(Modifier.fillMaxSize()) { - Spacer(Modifier.height(AppBarHeight * fontSizeSqrtMultiplier)) - - AppBarTitle(stringResource(MR.strings.permissions_required)) - Spacer(Modifier.weight(1f)) - - val onClick = { - if (permissionsState.shouldShowRationale) { - context.showAllowPermissionInSettingsAlert() - } else { - permissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled, context::showAllowPermissionInSettingsAlert) + ModalView(background = Color.Black, showClose = false, close = {}) { + ColumnWithScrollBar(Modifier.fillMaxSize()) { + AppBarTitle(stringResource(MR.strings.permissions_required)) + Spacer(Modifier.weight(1f)) + val onClick = { + if (permissionsState.shouldShowRationale) { + context.showAllowPermissionInSettingsAlert() + } else { + permissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled, context::showAllowPermissionInSettingsAlert) + } } - } - Text(stringResource(MR.strings.permissions_grant), Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), textAlign = TextAlign.Center, color = Color(0xFFFFFFD8)) - SectionSpacer() - SectionView { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - val text = if (hasVideo && audioPermission.status is PermissionStatus.Denied && cameraPermission.status is PermissionStatus.Denied) { - stringResource(MR.strings.permissions_camera_and_record_audio) - } else if (audioPermission.status is PermissionStatus.Denied) { - stringResource(MR.strings.permissions_record_audio) - } else if (hasVideo && cameraPermission.status is PermissionStatus.Denied) { - stringResource(MR.strings.permissions_camera) - } else "" - GrantPermissionButton(text, buttonEnabled.value, onClick) + Text(stringResource(MR.strings.permissions_grant), Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), textAlign = TextAlign.Center, color = Color(0xFFFFFFD8)) + SectionSpacer() + SectionView { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + val text = if (hasVideo && audioPermission.status is PermissionStatus.Denied && cameraPermission.status is PermissionStatus.Denied) { + stringResource(MR.strings.permissions_camera_and_record_audio) + } else if (audioPermission.status is PermissionStatus.Denied) { + stringResource(MR.strings.permissions_record_audio) + } else if (hasVideo && cameraPermission.status is PermissionStatus.Denied) { + stringResource(MR.strings.permissions_camera) + } else null + if (text != null) { + GrantPermissionButton(text, buttonEnabled.value, onClick) + } + } } - } - Spacer(Modifier.weight(1f)) - Box(Modifier.fillMaxWidth().padding(bottom = if (hasVideo) 0.dp else DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.Center) { - SimpleButtonFrame(cancel, Modifier.height(64.dp)) { - Text(stringResource(MR.strings.call_service_notification_end_call), fontSize = 20.sp, color = Color(0xFFFFFFD8)) + Spacer(Modifier.weight(1f)) + Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING), contentAlignment = Alignment.Center) { + SimpleButtonFrame(cancel, Modifier.height(60.dp)) { + Text(stringResource(MR.strings.call_service_notification_end_call), fontSize = 20.sp, color = Color(0xFFFFFFD8)) + } } } } @@ -768,8 +799,8 @@ fun PreviewActiveCallOverlayVideo() { userProfile = Profile.sampleData, contact = Contact.sampleData, callState = CallState.Negotiated, - localMedia = CallMediaType.Video, - peerMedia = CallMediaType.Video, + initialCallType = CallMediaType.Video, + peerMediaSources = CallMediaSources(), callUUID = "", connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "tcp"), @@ -798,8 +829,8 @@ fun PreviewActiveCallOverlayAudio() { userProfile = Profile.sampleData, contact = Contact.sampleData, callState = CallState.Negotiated, - localMedia = CallMediaType.Audio, - peerMedia = CallMediaType.Audio, + initialCallType = CallMediaType.Audio, + peerMediaSources = CallMediaSources(), callUUID = "", connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "udp"), diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt index 3283593e09..e0fd81f7b6 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt @@ -54,8 +54,7 @@ actual fun ActiveCallInteractiveArea(call: Call) { .align(Alignment.BottomCenter), contentAlignment = Alignment.Center ) { - val media = call.peerMedia ?: call.localMedia - if (media == CallMediaType.Video) { + if (call.hasVideo) { Icon(painterResource(MR.images.ic_videocam_filled), null, Modifier.size(27.dp).offset(x = 2.5.dp, y = 2.dp), tint = Color.White) } else { Icon(painterResource(MR.images.ic_call_filled), null, Modifier.size(27.dp).offset(x = -0.5.dp, y = 2.dp), tint = Color.White) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 7bf97a6e37..09b0ececb0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -2525,7 +2525,7 @@ object ChatController { // TODO askConfirmation? // TODO check encryption is compatible withCall(r, r.contact) { call -> - chatModel.activeCall.value = call.copy(callState = CallState.OfferReceived, peerMedia = r.callType.media, sharedKey = r.sharedKey) + chatModel.activeCall.value = call.copy(callState = CallState.OfferReceived, sharedKey = r.sharedKey) val useRelay = appPrefs.webrtcPolicyRelay.get() val iceServers = getIceServers() Log.d(TAG, ".callOffer iceServers $iceServers") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index 7704509148..405094f72a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -49,7 +49,7 @@ class CallManager(val chatModel: ChatModel) { contact = invitation.contact, callUUID = invitation.callUUID, callState = CallState.InvitationAccepted, - localMedia = invitation.callType.media, + initialCallType = invitation.callType.media, sharedKey = invitation.sharedKey, ) showCallView.value = true diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index 5332bc650e..f723306456 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -2,6 +2,7 @@ package chat.simplex.common.views.call import chat.simplex.common.views.helpers.generalGetString import chat.simplex.common.model.* +import chat.simplex.common.platform.appPlatform import chat.simplex.res.MR import kotlinx.datetime.Instant import kotlinx.serialization.SerialName @@ -15,18 +16,21 @@ data class Call( val contact: Contact, val callUUID: String?, val callState: CallState, - val localMedia: CallMediaType, + val initialCallType: CallMediaType, + val localMediaSources: CallMediaSources = CallMediaSources(mic = true, camera = initialCallType == CallMediaType.Video && appPlatform.isAndroid), val localCapabilities: CallCapabilities? = null, - val peerMedia: CallMediaType? = null, + val peerMediaSources: CallMediaSources = CallMediaSources(), val sharedKey: String? = null, - val audioEnabled: Boolean = true, - val videoEnabled: Boolean = localMedia == CallMediaType.Video, var localCamera: VideoCamera = VideoCamera.User, val connectionInfo: ConnectionInfo? = null, var connectedAt: Instant? = null, + + // When a user has audio call, and then he wants to enable camera but didn't grant permissions for using camera yet, + // we show permissions view without enabling camera before permissions are granted. After they are granted, enabling camera + val wantsToEnableCamera: Boolean = false ) { val encrypted: Boolean get() = localEncrypted && sharedKey != null - val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false + private val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false val encryptionStatus: String get() = when(callState) { CallState.WaitCapabilities -> "" @@ -35,10 +39,8 @@ data class Call( else -> generalGetString(if (!localEncrypted) MR.strings.status_no_e2e_encryption else if (sharedKey == null) MR.strings.status_contact_has_no_e2e_encryption else MR.strings.status_e2e_encrypted) } - val hasMedia: Boolean get() = callState == CallState.OfferSent || callState == CallState.Negotiated || callState == CallState.Connected - - fun supportsVideo(): Boolean = peerMedia == CallMediaType.Video || localMedia == CallMediaType.Video - + val hasVideo: Boolean + get() = localMediaSources.hasVideo || peerMediaSources.hasVideo } enum class CallState { @@ -68,6 +70,16 @@ enum class CallState { @Serializable data class WVAPICall(val corrId: Int? = null, val command: WCallCommand) @Serializable data class WVAPIMessage(val corrId: Int? = null, val resp: WCallResponse, val command: WCallCommand? = null) +@Serializable data class CallMediaSources( + val mic: Boolean = false, + val camera: Boolean = false, + val screenAudio: Boolean = false, + val screenVideo: Boolean = false +) { + val hasVideo: Boolean + get() = camera || screenVideo +} + @Serializable sealed class WCallCommand { @Serializable @SerialName("capabilities") data class Capabilities(val media: CallMediaType): WCallCommand() @@ -75,7 +87,7 @@ sealed class WCallCommand { @Serializable @SerialName("offer") data class Offer(val offer: String, val iceCandidates: String, val media: CallMediaType, val aesKey: String? = null, val iceServers: List? = null, val relay: Boolean? = null): WCallCommand() @Serializable @SerialName("answer") data class Answer (val answer: String, val iceCandidates: String): WCallCommand() @Serializable @SerialName("ice") data class Ice(val iceCandidates: String): WCallCommand() - @Serializable @SerialName("media") data class Media(val media: CallMediaType, val enable: Boolean): WCallCommand() + @Serializable @SerialName("media") data class Media(val source: CallMediaSource, val enable: Boolean): WCallCommand() @Serializable @SerialName("camera") data class Camera(val camera: VideoCamera): WCallCommand() @Serializable @SerialName("description") data class Description(val state: String, val description: String): WCallCommand() @Serializable @SerialName("layout") data class Layout(val layout: LayoutType): WCallCommand() @@ -90,6 +102,7 @@ sealed class WCallResponse { @Serializable @SerialName("ice") data class Ice(val iceCandidates: String): WCallResponse() @Serializable @SerialName("connection") data class Connection(val state: ConnectionState): WCallResponse() @Serializable @SerialName("connected") data class Connected(val connectionInfo: ConnectionInfo): WCallResponse() + @Serializable @SerialName("peerMedia") data class PeerMedia(val source: CallMediaSource, val enabled: Boolean): WCallResponse() @Serializable @SerialName("end") object End: WCallResponse() @Serializable @SerialName("ended") object Ended: WCallResponse() @Serializable @SerialName("ok") object Ok: WCallResponse() @@ -165,6 +178,14 @@ enum class CallMediaType { @SerialName("audio") Audio } +@Serializable +enum class CallMediaSource { + @SerialName("mic") Mic, + @SerialName("camera") Camera, + @SerialName("screenAudio") ScreenAudio, + @SerialName("screenVideo") ScreenVideo +} + @Serializable enum class VideoCamera { @SerialName("user") User, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 18deb48597..48387f4abb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -576,7 +576,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) if (chatInfo is ChatInfo.Direct) { val contactInfo = chatModel.controller.apiContactInfo(remoteHostId, chatInfo.contact.contactId) val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi - chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) + chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, initialCallType = media, userProfile = profile) chatModel.showCallView.value = true chatModel.callCommand.add(WCallCommand.Capabilities(media)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt index 4141fd2ead..8349841973 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt @@ -1,7 +1,12 @@ package chat.simplex.common.views.helpers +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* +import androidx.compose.material.ripple.rememberRipple import dev.icerock.moko.resources.compose.painterResource import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -9,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.res.MR @@ -85,10 +91,12 @@ fun ExposedDropDownSettingWithIcon( values: List>, selection: State, fontSize: TextUnit = 16.sp, - iconSize: Dp = 40.dp, + iconPaddingPercent: Float = 0.2f, listIconSize: Dp = 30.dp, + boxSize: Dp = 60.dp, iconColor: Color = MenuTextColor, enabled: State = mutableStateOf(true), + background: Color, minWidth: Dp = 200.dp, onSelected: (T) -> Unit ) { @@ -99,13 +107,21 @@ fun ExposedDropDownSettingWithIcon( expanded.value = !expanded.value && enabled.value } ) { - Row( - Modifier.padding(start = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End + Box( + Modifier + .background(background, CircleShape) + .size(boxSize) + .clickable( + onClick = {}, + role = Role.Button, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false, radius = boxSize / 2, color = background.lighter(0.1f)), + enabled = enabled.value + ), + contentAlignment = Alignment.Center ) { val choice = values.first { it.first == selection.value } - Icon(painterResource(choice.second), choice.third, Modifier.size(iconSize), tint = iconColor) + Icon(painterResource(choice.second), choice.third, Modifier.padding(boxSize * iconPaddingPercent).fillMaxSize(), tint = iconColor) } DefaultExposedDropdownMenu( modifier = Modifier.widthIn(min = minWidth), diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 08a16075a4..901a0565e1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1075,6 +1075,7 @@ Audio on Speaker off Speaker on + Sound muted Flip camera diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_off.svg new file mode 100644 index 0000000000..497864dd56 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html index cbdf7a23a3..51815e2995 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html @@ -6,6 +6,15 @@ + + + + +