diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 71943c3bb..de2084ee1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,7 +3,7 @@ name: Bug report about: Report something that isn't working. title: '' assignees: '' - +labels: bug --- - + + + + @@ -267,7 +270,11 @@ android:clearTaskOnLaunch="true" android:finishOnTaskLaunch="true" /> - + + diff --git a/CHANGELOG.md b/CHANGELOG.md index bcfc4ccda..07a6987ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,93 @@ # Delta Chat Android Changelog +## v1.41.0 Testrun +2023-10 + +* keep screen on while playing voice messages +* pause background music when starting voice messages +* use the system camera as default; the old built-in camera can be enabled at "Settings / Advanced" +* add "Verified by" information to contact profiles +* screen reader: read out message types +* screen reader: allow tapping anywhere in the message to start voice or audio playback +* set different wallpapers for different accounts +* add "Select All" to gallery and to file lists +* resend attachments from profile (long tap, then "Resend" in the menu) +* allow to import a key file instead of a folder containing keys +* search in "Attach Contact" dialog +* improve landscape mode for webxdc apps +* adapt webxdc loading screen to dark mode +* add file name to dialog shown if a webxdc app wants to share information +* add app icon to webxdc info messages and improve webxdc app icon layout +* improve layout of input bar when system emojis are used +* ask for permissions before adding notifications on Android 13 (needed by the required update to API 33) +* switch account if needed when opening webxdc app on the system's home screen +* improve video error messages and logging +* fix sometimes wrong avatar shown in notifications when using multiple accounts +* fix: save map preferences per account to avoid resetting location and zoom +* fix: play audio and voice messages: do not show progress in unrelated messages +* fix: update relative times directly after entering chatlist, do not wait for a minute +* fix issues when after selecting a non-system-language, system-language strings still show up +* fix: only jump to message if info message is from webxdc +* fix: update webxdc document name in titles immediately +* fix: do not open Connectivity when tapping forward/share titles +* fix starting conversation with contact from the phone contacts app +* fix WASM support for some webxdc apps +* fix off-by-one mismatch in manual language selection +* fix: sanitize invalid filename we get from some camera apps +* fix: display sticker footer properly +* fix: webxdc apps starting twice sometimes +* fix sending images and other files in location steaming mode +* fix connectivity view layout if eg. storage shows values larger than 100% +* fix scanning account-QR-codes on older phones that miss the Let's Encrypt system certificate +* fix: make Thunderbird show encrypted subjects +* fix: do not forward document name when forwarding only a webxdc app +* fix: do not create new groups if someone replies to a group message with status "failed" +* fix: do not block new group chats if 1:1 chat is blocked +* fix "Show full message" showing a black screen for some messages received from Microsoft Exchange +* fix: skip read-only mailing lists from forwarding/share chat lists +* fix: do not allow dots at the end of email addresses +* fix: do not send images pasted from the keyboard unconditionally as stickers +* fix: forbid membership changes from possible non-members, allow from possible members +* fix: improve group consistency across members +* fix: delete messages from SMTP queue only on user demand +* fix: improve wrapping of email messages on the wire +* fix memory leak in IMAP +* update translations and local help +* update to core 1.124.1 + + +## v1.40.1 +2023-08 + +* fix: correct core-submodule picked up by f-droid +* update to core119.1 + + +## v1.40.0 +2023-08 + +* use image editor for avatar selection when possible +* allow media from blob: and data: in webxdc +* optimized native library size +* improve loading screen in dark mode +* improve IMAP logs +* update "verified icon" +* fix webxdc issues with dark mode +* fix crash in android 4.2 or older when opening a HTML message in full message view +* fix: avoid IMAP move loops when DeltaChat folder is aliased +* fix: accept webxdc updates in mailing lists +* fix: delete webxdc status updates together with webxdc instance +* fix: prevent corruption of large unencrypted webxdc updates +* fix "Member added by me" message appearing sometimes within wrong context +* fix core panic after sending 29 offline messages +* fix: make avatar in qr-codes work on more platforms +* fix: preserve indentation when converting plaintext to HTML +* fix: remove superfluous spaces at start of lines when converting HTML to plaintext +* fix: always rewrite and translate member added/removed messages +* add Luri Bakhtiari translation, update other translations and local help +* update to core119 + + ## v1.38.2 2023-06 diff --git a/androidTest/com/b44t/messenger/uitests/offline/ForwardingTest.java b/androidTest/com/b44t/messenger/uitests/offline/ForwardingTest.java index 9b7755dbd..c8c7f6483 100644 --- a/androidTest/com/b44t/messenger/uitests/offline/ForwardingTest.java +++ b/androidTest/com/b44t/messenger/uitests/offline/ForwardingTest.java @@ -43,7 +43,7 @@ public class ForwardingTest { } @Rule - public ActivityScenarioRule activityRule = TestUtils.getOfflineActivityRule(false); + public final ActivityScenarioRule activityRule = TestUtils.getOfflineActivityRule(false); @Before public void createChats() { diff --git a/androidTest/com/b44t/messenger/uitests/offline/SharingTest.java b/androidTest/com/b44t/messenger/uitests/offline/SharingTest.java index 360bac152..17fdd680f 100644 --- a/androidTest/com/b44t/messenger/uitests/offline/SharingTest.java +++ b/androidTest/com/b44t/messenger/uitests/offline/SharingTest.java @@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.ShareActivity; import org.thoughtcrime.securesms.connect.DcHelper; -import org.thoughtcrime.securesms.util.Util; import java.io.File; @@ -32,7 +31,6 @@ import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; import static androidx.test.espresso.matcher.ViewMatchers.isClickable; -import static androidx.test.espresso.matcher.ViewMatchers.withChild; import static androidx.test.espresso.matcher.ViewMatchers.withHint; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; @@ -46,13 +44,11 @@ public class SharingTest { // PLEASE BACKUP YOUR ACCOUNT BEFORE RUNNING THIS! // ============================================================================================== - @SuppressWarnings("unused") - private final static String TAG = SharingTest.class.getSimpleName(); private static int createdGroupId; private static int createdSingleChatId; @Rule - public ActivityScenarioRule activityRule = TestUtils.getOfflineActivityRule(false); + public final ActivityScenarioRule activityRule = TestUtils.getOfflineActivityRule(false); @Before public void createGroup() { diff --git a/assets/help/cs/help.html b/assets/help/cs/help.html index 9f3fe913b..20ff62894 100644 --- a/assets/help/cs/help.html +++ b/assets/help/cs/help.html @@ -839,6 +839,9 @@ Pro ostatní programy by mělo jít nalézt řešení na Interentu.

Yes. Delta Chat 1.36 comes with a new, experimental function for using the same account on different devices:

-
  • Private Apps / webxdc +
  • webxdc apps
  • Experimental Features
  • @@ -375,7 +379,7 @@ of a contact. It means they were “recently seen”.
  • because they wrote something to a group you are both a member of,
  • because they sent you a read receipt for a message you wrote,
  • or because they sent data to your Delta Chat app by using a -private app.
  • +webxdc app.
  • So this is not a real time online status - and if someone doesn’t answer @@ -534,199 +538,292 @@ this way, all messages will be deleted from your device as soon as they older than that.
  • -

    +

    - Encryption + Encryption and Security

    -

    +

    - Does Delta Chat support end-to-end-encryption? + Which standards are used for end-to-end encryption?

    -
      -
    • -

      Yes. Delta Chat implements the Autocrypt Level 1 standard and can -thus E2E-encrypt messages with other Autocrypt-capable apps.

      -
    • -
    • -

      Delta Chat also supports a strong form of end-to-end encryption that is -even safe against active attacks, see “verified groups” further below.

      -
    • -
    +

    Autocrypt is used for automatically +establishing E2E-encryption with contacts and group chats. +Autocrypt uses a limited and secure subset of the OpenPGP standard.

    + +

    Secure-Join protocols +are used to implement verified groups +which provide pervasive protection against network attacks and compromised servers. +Verified groups enforce all messages in a chat to be safely E2E-encrypted +with an unparalleled ease of use that avoids users having to learn about +public key cryptography, key management or key verification.

    -

    +

    - What do I have to do to activate the end-to-end-encryption? + When will messages be E2E-encrypted?

    -
      -
    • -

      Nothing.

      -
    • -
    • -

      Delta Chat apps (and other Autocrypt-compatible -e-mail apps) share the keys required for end-to-end-encryption automatically -as the first messages are sent. -After this, all subsequent messages are encrypted end-to-end automatically. -If one of the chat partners uses a non-Autocrypt e-mail app, subsequent -messages are not encrypted until an Autocrypt-compliant app is available again.

      -
    • -
    • -

      If you want to rather avoid end-to-end-encrypted e-mails by default, -use the corresponding Autocrypt setting in “Settings” or “Advanced settings”.

      -
    • -
    +

    After a first message was received from a Delta Chat or other Autocrypt-capable contact, +Delta Chat defaults to using E2E-encryption with that contact (and vice versa). +When creating a group chat with contacts where E2E-encrypted is individually in place, +all group members will automatically use E2E-encryption with each other and in the group. +However, if you add a contact that lacks E2E-encryption, +the group chat will not use E2E-encryption.

    + +

    If you want to be sure to always and only use E2E-encryption in a group +use verified chat groups +which additionally protects against compromised or malfeasant e-mail servers.

    -

    +

    - If end-to-end-encryption is not available, is the connection not encrypted at all? + Are attachments (pictures, files, audio etc.) E2E-encrypted?

    -
      -
    • With most mail servers, Delta Chat establishes transport encryption -(TLS). -This only secures the connection between your device and your e-mail -server. Whereas e2e-encryption provides safety between -your device and a friend’s device.
    • -
    +

    Yes. +When we talk about an “E2E-encrypted message” +we always mean a whole message is encrypted, +including all the attachments +and attachment metadata such as filenames.

    -

    +

    - How can I verify cryptographic status with a sender? + Is OpenPGP secure?

    -

    If you are within immediate distance of the chat partner:

    +

    Yes, Delta Chat uses a secure subset of OpenPGP +and only displays a padlock security indicator on a message +if the whole message is properly encrypted and signed. +For example, “Detached signatures” are not treated as secure.

    + +

    OpenPGP is not insecure by itself. +Most publically discussed OpenPGP security problems +actually stem from bad usability or bad implementations of tools or apps (or both). +It is particularly important to distinguish between OpenPGP, the IETF encryption standard, +and GnuPG (GPG), a command line tool implementing OpenPGP. +Many public critiques of OpenPGP actually discuss GnuPG which Delta Chat has never used. +Delta Chat rather uses the OpenPGP Rust implementation rPGP, +available as an independent “pgp” package, +and security-audited in 2019.

    + +

    We aim, along with other OpenPGP implementors, +to further improve security characteristics by implementing the +new IETF OpenPGP Crypto-Refresh +which was thankfully adopted in summer 2023.

    + +

    + + + Did you consider using alternatives to OpenPGP for E2E-encryption? + + +

    + +

    Yes, we are following efforts like MLS +or Saltpack +but adopting them would mean breaking E2E-encryption interoperability +with all other e-mail apps that typically support OpenPGP encryption. +So it would not be a light decision to take +and there must be tangible improvements for users.

    + +

    Delta Chat takes a holistic “usable security” approach +and works with a wide range of activist groupings as well as +renowned researchers such as TeamUSEC +to improve actual user outcomes against security threats. +The wire protocol and standard for establishing E2E-encryption is +only one part of “user outcomes”, +see also our answers to device-seizure +and message-metadata questions.

    + +

    + + + Is Delta Chat vulnerable to EFAIL? + + +

    + +

    No, Delta Chat never was vulnerable to EFAIL +because its OpenPGP implementation rPGP +uses Modification Detection Code when encrypting messages +and returns an error +if the Modification Detection Code is incorrect.

    + +

    Delta Chat also never was vulnerable to the “Direct Exfiltration” EFAIL attack +because it only decrypts multipart/encrypted messages +which contain exactly one encrypted and signed part, +as defined by the Autocrypt Level 1 specification.

    + +

    + + + Is a message exposed in cleartext if E2E-encryption is not available? + + +

    + +

    No, +this does not necessarily mean that the message is exposed in cleartext.

    + +

    Delta Chat always uses (TLS) encryption +which secures connections between your device and your e-mail provider +unless you explicitly disable it. +All of Delta Chat’s TLS-handling has been independently security audited.

    + +

    Moreover, the connection between your and the recipient’s e-mail provider +will today typically be transport-encrypted as well. +If the involved e-mail servers support MTA-STS +then transport encryption will be enforced in all inter e-mail server communications +in which case Delta Chat communications will never be exposed in cleartext to the network +even if the message was not E2E-encrypted.

    + +

    Note that maintaining E2E-encryption on top of TLS encryption is highly advisable +because it provides safety between your device and a contact’s device, +irrespective of any hops over potentially compromised e-mail servers.

    + +

    + + + How does Delta Chat protect metadata in messages? + + +

    + +

    Delta Chat protects most message metadata by putting the following information +into the E2E-encrypted part of messages:

      -
    • Select QR Invite code on one device and then Scan QR code -on the other one and scan the code. If both devices are online, -they will introduce a chat channel with each-other (if it doesn’t exist already) -and the encryption keys will also be verified. Both will see a -“sender verified” system message in their 1:1 chat.
    • +
    • Subject line
    • +
    • Group avatar and name
    • +
    • MDN (read receipt) requests (Chat-Disposition-Notification-To)
    • +
    • Disappearing message timer (Ephemeral-Timer)
    • +
    • Chat-Group-Member-Removed, Chat-Group-Member-Added
    • +
    • Secure-Join header containing secure join commands
    • +
    • Notification about enabling location streaming
    • +
    • WebRTC room URL
    -

    If you are not near the chat partner, you can check the status manually in the “Encryption” dialog -(user profile on Android/iOS or right-click a user’s chat-list item on desktop):

    +

    E-Mail servers do not get access to this protected metadata +but they do see the message date as well as the message size, +and, more importantly, the sender and receiver addresses. +E-mail servers need receiver addresses to route and +deliver messages to recipient’s devices.

    + +

    + + + How to protect metadata and contacts when a device is seized? + + +

    -
      -
    • -

      For end-to-end-encryption, Delta Chat shows two fingerprints there. -If the same fingerprints appear on your chat partner’s device, +

      Both for protecting against metadata-collecting e-mail servers +as well as against the threat of device seizure +we recommend to use a Delta Chat optimized e-mail server instance +to create pseudonymous temporary accounts through QR-code scans. +Note that Delta Chat apps on all platforms support multiple accounts +so you can easily use action-specific “1-week” or “1-month” accounts next to your “main” account +with the knowledge that all temporary account data, along with all metadata, will be deleted. +Moreover, if a device is seized then contacts using temporary e-mail accounts +can not be identified easily, as compared to messengers which reveal +phone numbers in chat groups which in turn are often associated with passport identities.

      + +

      + + + How can I verify E2E-security with a contact? + + +

      + +

      If you are within immediate distance of your contact, +select QR Invite code on one device and then Scan QR code +from within Delta Chat on the other device. +If both devices are online, +they will setup a chat with each-other (if it doesn’t exist already) +and both will see a “sender verified” system message in their chat. +Showing and scanning a QR code can also happen in any “second channel” +such as a video call or another messenger.

      + +

      If QR code scanning is for some reason not viable, +you may check the E2E encryption status manually in the “Encryption” dialog +(user profile on Android/iOS or right-click a user’s chat-list item on desktop). +Delta Chat shows two fingerprints there. +If the same fingerprints appear on your own and your contact’s device, the connection is safe.

      -
    • -
    • -

      For transport encryption, this state is just shown there

      -
    • -
    -

    +

    - How can I check the encryption of messages? + How can I check the encryption status of messages?

    -
      -
    • -

      A little padlock shown beside a message denotes whether the message is end-to-end-encrypted from from the given sender.

      -
    • -
    • -

      If there is no padlock, the message is usually transported unencrypted e.g. because you or the sender have turned off end-to-end-encryption, or the sender uses an app without support for end-to-end-encryption.

      -
    • -
    +

    A little padlock in a message bubble denotes +that the message was properly E2E-encrypted from the given sender. +If there is no padlock, the message was not properly E2E-encrypted +most likely because the sender uses an app or webmail interface +without support for E2E–encryption.

    -

    +

    - How can I ensure message encryption and deletion? + Why do I see unencrypted messages? + + +

    + +

    If a contact uses a non-Autocrypt e-mail app, +all messages involving this contact (in a group or 1:1 chat) +will not be E2E-encrypted, and thus not show a “padlock” with messages. +Note that even if your contacts use Delta Chat on their account, +they might also use a non-Autocrypt e-mail app on that account +which then may cause intermittently unencrypted messages. +Replying unencrypted to unencrypted messages is mandated by Autocrypt +to prevent unreadable messages on the side of your contacts +and their non-Autocrypt e-mail app.

    + +

    If you need a safely E2E-encrypted chat with contacts +who are using their account also with Autocrypt-incapable apps (e.g. webmail) +it’s best to create a verified group chat with them. +Any message sent into a verified group chat will be E2E-encrypted +irrespective of the last incoming message from a contact.

    + +

    + + + How can I ensure message E2E-encryption and deletion?

    The best way to ensure every message is encrypted, and metadata deleted as quickly as possible -is creating a verified group and turning on -disappearing messages.

    +is using verified groups +and turning on disappearing messages.

    -

    Verified groups are always encrypted and protected against MITM -attacks.

    +

    Verified groups are always encrypted and protected against MITM attacks +and turning on “disappearing messages” deletes the messages +on the server after a user-configured time.

    -

    Metadata can’t be encrypted, as the server needs to know where to deliver your -messages. But turning on “disappearing messages” deletes the messages on -the server after they were delivered.

    - -

    If you need the messages on your device, but not on the server, you can also -agree in the group to turn on “delete messages from server -automatically”.

    - -

    If you want to protect a 1:1 conversation like this, you should create a -verified group with only 2 people. If the other person loses their device but -not their account, you can still communicate in the 1:1 chat. (Read more)

    - -

    - - - Which standards are used for end-to-end-encryption? - - -

    - -
      -
    • -

      Autocrypt is used for establishing -e2e-encryption with other Delta Chat and other Autocrypt-capable mail apps. -Autocrypt uses a limited subset of OpenPGP functionality.

      -
    • -
    • -

      Delta Chat implements countermitm setup-contact and verified-group protocols to achieve protection against active network attacks. This goes beyond the opportunistic -base protection of Autocrypt Level 1, while maintaining its ease of use.

      -
    • -
    - -

    - - - What is the difference between verified groups and 1:1 chats with verified contacts? - - -

    - -
      -
    • -

      1:1 chats with a verified contact and verified groups are not the same, even -if there are only 2 people in the verified group. One difference is that you -could easily add more people to the group, but there are other implications as -well.

      -
    • -
    • -

      Verified groups are invariably secured. Any breakage (cleartext or wrongly -signed messages etc.) will be flagged and such messages will not be shown in -this chat. You can trust all messages in this verified-checkmark chat to have -not been read/altered by middle parties.

      -
    • -
    • -

      1:1 chats are opportunistic, it is meant to allow people to communicate no -matter if they change e-mail clients, devices, setups etc. That’s why there -is no verification checkmark, even if you have verified the contact.

      -
    • -
    +

    If you don’t need a longer-lived copy of your messages on the server, +you can also turn on “delete messages from server automatically”.

    @@ -736,57 +833,23 @@ is no verification checkmark, even if you have verified the contact.

    -
      -
    • -

      No, OpenPGP doesn’t support Perfect Forward Secrecy. Perfect Forward Secrecy -works session-oriented, but E-Mail is asynchronous by nature -and often used from multiple devices independently. This means that if your -Delta Chat private key is leaked, and someone has a record -of all your in-transit messages, they will be able to read them.

      -
    • -
    • -

      Note that if anyone has seized or hacked your running phone, they will -typically be able to read all messages, no matter if Perfect Forward Secrecy -is in place or not. Having access to a single device from a member of a group, -will typically expose a lot of the social graph. Using e-mail addresses that -are not easily tracked back to persons helps group members to stay safer from -the effects of device seizure.

      -
    • -
    • -

      We are sketching ways to protect communications better against the event -of device seizure.

      -
    • -
    - -

    - - - How does Delta Chat protect my Metadata? - - -

    +

    No, Delta Chat doesn’t support Perfect Forward Secrecy (PFS). +This means that if your Delta Chat private decryption key is leaked, +and someone has collected your prior in-transit messages, +they will be able to decrypt and read them using the leaked decryption key.

    -
      -
    • -

      As Delta Chat is a decentralized messenger, the metadata of Delta Chat users -are not stored on a single central server. However, they are stored on the mail -servers of the sender and the recipient of a message.

      -
    • -
    • -

      Each mail server currently knows about who sent and who received a message by -inspecting the unencrypted To/Cc headers and thus determine which e-mail addresses -are part of a group. Delta Chat itself could avoid unencrypted To/Cc headers quite -and always put them only into the encrypted section. See -Avoid sending To/CC headers for verified groups. -For opportunistic chats the main concern is how it affects other mail apps who -might participate in chats.

      -
    • -
    • -

      Many other e-mail headers, in particular the “Subject” header, are -end-to-end-encryption protected, see also this upcoming IETF -RFC.

      -
    • -
    +

    Note, however, that if anyone obtains to your decryption keys, +they will typically also be able to obtain your messages, +irrespective if Perfect Forward Secrecy is in place or not. +The typical real-world situation for leaked decryption keys is device seizure +which we discuss in our answer on metadata and device seizure.

    + +

    It is possible that Delta Chat evolves to support Perfect Forward Secrecy, +because OpenPGP is just a container for encrypted messages +but encryption key management (and thus key rotation or key “ratcheting”) +could be organized in flexible ways. +See Seqouia’s PFS prototype +for existing experiments in the OpenPGP implementor community.

    @@ -796,14 +859,12 @@ RFC.

    -
      -
    • -

      Yes. The best way is to send an Autocrypt Setup Message from the other e-mail client. Look for something like Start Autocrypt Setup Transfer in the settings of the other client and follow the instructions shown there.

      -
    • -
    • -

      Alternatively, you can import the key manually in “Settings -> Advanced settings -> Import secret keys”. Caution: Make sure the key is not protected by a password, or remove the password beforehand.

      -
    • -
    +

    Yes. +The best way is to send an Autocrypt Setup Message from the other e-mail client. +Look for something like Start Autocrypt Setup Transfer in the settings of the other client and follow the instructions shown there.

    + +

    Alternatively, you can import the key manually in “Settings -> Advanced settings -> Import secret keys”. +Caution: Make sure the key is not protected by a password, or remove the password beforehand.

    If you don’t have a key or don’t even know you would need one - don’t worry: Delta Chat generates keys as needed, you don’t have to hit a button for it.

    @@ -853,6 +914,9 @@ For other programs, you should be able to find a solution online.

    Yes. Delta Chat 1.36 comes with a new, experimental function for using the same account on different devices:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • On the first device, go to Settings → Add Second Device, unlock the screen if needed and wait a moment until a QR code is shown

      @@ -1008,62 +1072,63 @@ Otherwise, deleting messages or multi-device setups might not work properly.

      - Private Apps / webxdc + webxdc apps

      -

      In Delta Chat, you can share “private apps”, attachments with an .xdc file +

      In Delta Chat, you can share webxdc apps, attachments with an .xdc file extension. They can do very different things, and make Delta Chat a truly -extendable messenger. The technical term is webxdc.

      +extendable messenger.

      -

      +

      - How private are private apps? + How private are webxdc apps?

        -
      • Private apps can not send data to the Internet, or download anything.
      • -
      • A private app can only exchange data within a Delta Chat chat, with its +
      • webxdc apps can not send data to the Internet, or download anything.
      • +
      • A webxdc app can only exchange data within a Delta Chat chat, with its copies on the devices of your chat partners. Other than that, it’s completely isolated from the Internet.
      • -
      • The privacy a private app offers is the privacy of your chat - as long as you -trust the people you chat with, you can trust the private app as well.
      • -
      • This also means: it can be a privacy risk to open private apps in chats where +
      • The privacy a webxdc app offers is the privacy of your chat - as long as you +trust the people you chat with, you can trust the webxdc app as well.
      • +
      • This also means: it can be a privacy risk to open webxdc apps in chats where you don’t trust the members - as you know it from e-mail attachments, where you only open attachments from senders you trust, and not from spammers.
      -

      +

      - Where can I get private apps? + Where can I get webxdc apps?

        -
      • In general, there is no curated appstore and Delta Chat is not responsible -for private apps or their content. Anyone can share private apps with each +
      • In general, anyone can share webxdc apps with each other without restrictions.
      • -
      • You can find some example apps on webxdc.org.
      • -
      • Many people write their own private apps and post them to the Delta Chat +
      • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
      • +
      • Many people write their own webxdc apps and post them to the Delta Chat forum.
      -

      +

      - How can I create my own private apps? + How can I create my own webxdc apps?

        -
      • Private apps are just zip files containing html, css, and javascript code.
      • +
      • webxdc apps are just zip files containing html, css, and javascript code.
      • You can extend the Hello World example app to get started.
      • All else you need to know is written in the @@ -1107,31 +1172,32 @@ link in the settings.
      • this way, you will have a new random jitsi room every time you call someone.
      -

      +

      - What is a verified group? Why is it experimental? + What is a verified group? Why is it experimental?

      -
        -
      • -

        A verified group is a chat that guarantees safety against an active -attacker. All Messages in a verified chat view are e2e-encrypted, and -members can join by scanning a “QR invite code”. All members are thus -connected with each other through a chain of invites, which guarantee -cryptographic consistency against active network or provider attacks. +

        Verified groups carry a green verification checkmark in the group title that guarantees +that all messages are E2E-encrypted and can not be read or altered by e-mail servers. +Each member in a verified group chat can add already verified contacts +or tap “QR Invite code” to let invitees scan the code to get verified and added (“secure-join”). +This “secure-join” protocol ensures that all verified group chat members +are connected with each other through a chain of verifications (“web of trust”), +guaranteeing E2E-encryption consistency even if e-mail servers are compromised or malfeasant. See countermitm.readthedocs.io -for the R&D behind this feature.

        -
      • -
      • -

        As of Oct 2022, “verified groups” remain an experimental feature. It is -continuously improved and many bugs have been fixed since the original -introduction in 2018. However, there remain cases, especially with large -groups where inconsistencies can occur, or messages become unreadable.

        -
      • -
      +for a detailed security discussion.

      + +

      Note that “1:1” chats are currently only opportunistically encrypted (Autocrypt). +You need to create a verified group with your contact +to ensure that all messages will be safely E2E-encrypted between you two. +We plan to introduce verified 1:1 chats around the end of 2023, +simplifying and extending the guarantees of verified E2E-encryption for all chat types. +Until then verified groups will remain classified as an experimental feature +although they are widely and successfully used already, +and reported bugs have been continously fixed in the last years.

      @@ -1343,10 +1409,10 @@ uses.

    • See Standards used in Delta Chat.
    -

    +

    - Was Delta Chat independently audited for security vulnerabilities? + Was Delta Chat independently audited for security vulnerabilities?

    diff --git a/assets/help/es/help.html b/assets/help/es/help.html index df850c05e..aecea3c28 100644 --- a/assets/help/es/help.html +++ b/assets/help/es/help.html @@ -843,6 +843,9 @@ For other programs, you should be able to find a solution online.

    Yes. Delta Chat 1.36 comes with a new, experimental function for using the same account on different devices:

  • Groupes @@ -35,7 +35,7 @@
  • Si le chiffrement de bout en bout n’est pas disponible, la connexion n’est-elle pas chiffrée du tout ?
  • Comment puis-je vérifier le statut de chiffrement auprès d’un expéditeur ?
  • Comment puis-je vérifier le chiffrement des messages ?
  • -
  • Comment puis-je faire en sorte que les messages soient chiffrés et supprimés ?
  • +
  • Comment m’assurer que les messages sont chiffrés et supprimés ?
  • Quels standards sont utilisés pour le chiffrement de bout en bout ?
  • Quelle est la différence entre des groupes vérifiés et des discussions à deux avec des contacts vérifiés ?
  • Delta Chat prend-il en charge la confidentialité persistante parfaite (Perfect Forward Secrecy) ?
  • @@ -57,20 +57,20 @@
  • Applications privées / webxdc
  • Fonctionnalités expérimentales
  • Divers @@ -80,8 +80,8 @@
  • Si Delta Chat se base sur le courrier électronique, est-ce vraiment une messagerie instantanée?
  • Delta Chat est-il compatible avec Protonmail / Tutanota / Criptext ?
  • Les détails techniques m’intéressent. Pouvez-vous m’en dire plus ?
  • -
  • Was Delta Chat independently audited for security vulnerabilities?
  • -
  • Comment est financé le développement de Delta Chat?
  • +
  • Est-ce qu’un audit indépendant des failles de sécurité a été réalisé sur Delta Chat ?
  • +
  • Comment est financé le développement de Delta Chat ?
  • @@ -141,12 +141,12 @@

    Par défaut, Delta Chat affiche tous les courriels.

    -

    Vous pouvez changer cette préférence en allant dans “Paramètres -> Discussions & fichiers multimédia -> Voir les courriels classiques”. Trois options sont disponibles :

    +

    Vous pouvez changer cette préférence en vous rendant dans vos “Paramètres -> Discussions & fichiers multimédia -> Voir les courriels classiques”. Trois options sont disponibles :

    • “Non, seulement les discussions”: vous verrez seulement les messages envoyés par d’autres utilisateurs de Delta Chat et les réponses à vos messages Delta Chat. Cette option est recommandée si vous utilisez le même compte pour recevoir aussi des courriels normaux.
    • “Tout”: vous verrez dans Delta Chat tous les courriels envoyés à votre adresse. Cette option est recommandée si vous souhaitez utiliser Delta Chat pour tous vos courriels, afin de ne rater aucun message.
    • -
    • “Pour les contacts acceptés”: vous verrez dans Delta Chat tous les courriels envoyés par les personnes avec qui vous avez déjà ouvert une discussion, mais les notifications de nouvelles conversations ne s’afficheront que pour des messages envoyés via Delta Chat. Cette option vous permet de décider au cas par cas si vous souhaitez échanger via Delta Chat ou via une application de courriel “normale”.
    • +
    • “Pour les contacts acceptés”: vous verrez dans Delta Chat tous les courriels envoyés par les personnes avec qui vous avez déjà ouvert une discussion, mais les notifications de nouvelles conversations ne s’afficheront que pour des messages envoyés via Delta Chat. Cette option vous permet de décider au cas par cas si vous souhaitez échanger via Delta Chat ou via une application de courriel “classique”.

    @@ -179,21 +179,21 @@ recevoir de messages de cette personne, nous vous conseillons de la bloquer<

    Oui. En plus du texte brut, toutes les pièces jointes aux courriels sont affichées sous forme de messages séparés. Les messages sortants expédient automatiquement les pièces jointes que vous ajoutez.

      -
    • Pour de meilleures performances, les images sont optimisées et envoyées en taille réduite par défaut, mais vous pouvez les envoyer en tant que “fichier” pour en conserver la taille originale.
    • +
    • Pour améliorer les performances, les images sont redimensionnées et envoyées en taille réduite par défaut ; mais vous pouvez les envoyer en tant que “fichier” pour en conserver la taille originale.

    - Comment naviguer entre mes différents comptes et comment en ajouter ? + Comment naviguer entre plusieurs comptes et comment en ajouter ?

    -

    Vous pouvez facilement gérer des comptes supplémentaires sur les clients Delta Chat mobile ou de bureau en cliquant sur :

    +

    Vous pouvez facilement gérer des comptes supplémentaires sur les clients Delta Chat mobile ou de bureau en cliquant :

      -
    • le bouton du menu, puis sur “Changer de compte” (Android et bureau) ;
    • -
    • l’icône de profil, puis sur “Ajouter un compte” (iOS).
    • +
    • soit sur le bouton du menu, puis sur “Changer de compte” (Android et PC) ;
    • +
    • soit sur l’icône de profil, puis sur “Ajouter un compte” (iOS).

    Peut-être voudrez-vous aussi savoir comment ajouter un compte sur plusieurs appareils.

    @@ -340,41 +340,31 @@ pour mettre une discussion en sourdine, ouvrez le menu de la conversation (Andro

      -
    • Depuis la version 1.34 de Delta Chat, vous voyez parfois un “point vert” à côté de la photo de profil -d’un contact. Cela signifie qu’il a été “vu récemment”.
    • +
    • Depuis la version 1.34 de Delta Chat, vous voyez parfois un “point vert” à côté de la photo de profil d’un contact. Cela signifie qu’il a été “vu récemment”.
    • Plus précisément, cela signifie que Delta Chat a détecté l’activité de ce contact au cours des 10 dernières minutes :
      • soit parce qu’il vous a écrit personnellement ;
      • soit parce qu’il a écrit dans un groupe dont vous êtres tous les deux membres ;
      • -
      • soit parce qu’il a envoyé l’accusé de lecture d’un message que vous lui avez écrit ;
      • -
      • ou bien parce qu’il a envoyé des données à votre appli Delta Chat via une -appli privée.
      • +
      • soit parce qu’il a renvoyé l’accusé de lecture d’un message que vous lui avez écrit ;
      • +
      • ou bien parce qu’il a envoyé des données à votre appli Delta Chat via une appli privée.
    • -
    • Le “point vert” n’est donc pas un statut en temps réel. Aussi, si quelqu’un ne vous répond pas -immédiatement alors qu’il semble être en ligne, ne vous en faites pas et -patientez ;-)
    • -
    • D’un autre côté, les autres ne vous voient pas forcément “en ligne”. En particulier, - si vous avez désactivé les accusés de lecture, ils ne verront pas de point vert -tant que vous ne leur écrivez pas directement ou n’écrivez pas dans un groupe dont ils font aussi partie.
    • +
    • Le “point vert” n’est donc pas un statut en temps réel. Aussi, si quelqu’un ne vous répond pas immédiatement alors qu’il semble être en ligne, ne vous inquiétez pas et donnez-lui du temps ;-)
    • +
    • D’un autre côté, vos contacts ne vous voient pas forcément “en ligne”. Si vous avez désactivé les accusés de lecture, ils ne verront pas de point vert tant que vous ne leur écrivez pas directement ou n’écrivez pas dans un groupe dont ils font aussi partie.
    -

    +

    - How can I delete my account? + Comment supprimer mon compte ?

    -

    As you use an e-mail account for Delta Chat, -how you can delete your account depends on your e-mail provider. -We don’t have any control over your e-mail account, -so unfortunately we can’t help you with that.

    +

    Étant donné que vous utilisez Delta Chat à travers un compte courriel, la façon dont vous pouvez supprimer votre compte dépend de votre fournisseur de courriel. +Comme nous n’exerçons aucun contrôle sur votre compte courriel, nous ne sommes pas en mesure de vous aider à le supprimer.

    -

    If you want to keep the account, -but uninstall Delta Chat, -it is recommended to leave any group chat before uninstalling Delta Chat.

    +

    Si vous souhaitez conserver votre compte mais désinstaller Delta Chat, nous vous recommandons de quitter toutes les discussions de groupe avant de procéder à la désinstallation.

    @@ -470,15 +460,15 @@ qu’un humain a lu ou compris le message ;)
    • -

      Par défaut, Delta Chat stocke tous les messages sur votre appareil. Vous pouvez donc choisir de supprimer les anciens messages automatiquement pour libérer de l’espace de stockage chez votre fournisseur de courriel. Ces messages seront conservés localement jusqu’à ce que vous les supprimiez aussi de votre appareil.

      +

      Par défaut, Delta Chat stocke tous les messages sur votre appareil. Vous pouvez donc configurer Delta Chat pour qu’il supprime automatiquement les anciens messages reçus, si vous souhaitez libérer de l’espace de stockage chez votre fournisseur de courriel. Ces messages seront conservés localement jusqu’à ce que vous les supprimiez aussi de votre appareil.

    • -

      Pour activer cette option, allez dans les paramètres de “Discussions et fichiers multimédia” et Supprimer les anciens messages → Supprimer les messages du serveur. +

      Pour activer cette option, accédez à vos paramètres de “Discussions et fichiers multimédia”, puis à Supprimer les anciens messages → Supprimer les messages du serveur. Vous pouvez régler un délai allant de “Immédiatement” à “Après 1 année”. Tous les courriels reçus sur Delta Chat seront supprimés du serveur une fois le délai écoulé.

    • -

      Attention : si vous utilisez Delta Chat sur plusieurs appareils, laissez les messages sur le serveur jusqu’à ce que l’(es) autre(s) appareil(s) ai(en)t pu les télécharger aussi. Dans ce cas, nous vous conseillons de paramétrer la suppression automatique sur “Après 1 jour” ou plus, en fonction de la fréquence à laquelle vous vous servez de ces autres appareils.

      +

      Attention : si vous utilisez Delta Chat sur plusieurs appareils, vous devez laisser vos messages sur le serveur suffisamment longtemps pour permettre à votre/vos autre(s) appareil(s) de les télécharger aussi.

    @@ -593,15 +583,15 @@ Ce type de connexion sécurise les échanges seulement entre votre appareil et v -

    +

    - Comment puis-je faire en sorte que les messages soient chiffrés et supprimés ? + Comment m’assurer que les messages sont chiffrés et supprimés ?

    -

    La meilleure façon de garantir que chaque message soit chiffré et les métadonnées effacées aussi vite que possible c’est de créer un groupe vérifié et d’activer les messages éphémères.

    +

    Créer un groupe vérifié et activer les messages éphémères constitue le meilleur moyen de garantir que chaque message est chiffré et les métadonnées effacées aussi tôt que possible.

    Les groupes vérifiés sont toujours chiffrés et protégés contre les attaques de l’homme du milieu.

    @@ -609,7 +599,7 @@ Ce type de connexion sécurise les échanges seulement entre votre appareil et v

    Si vous avez besoin de conserver vos messages sur votre appareil, mais pas sur le serveur, vous pouvez aussi vous mettre d’accord dans le groupe pour activer l’option “Supprimer les messages du serveur immédiatement”.

    -

    Si vous souhaitez protéger une conversation à deux de cette manière, nous vous conseillons de créer un groupe vérifié avec seulement deux personnes. Si jamais l’autre personne perdait son appareil, mais pas son compte, vous pourriez toujours communiquer dans la conversation à deux. (En savoir plus)

    +

    Si vous souhaitez protéger une discussion à deux de cette manière, nous vous conseillons de créer un groupe vérifié avec seulement deux personnes. Si jamais l’autre personne perdait son appareil mais pas son compte, vous pourriez toujours communiquer dans la conversation à deux. (En savoir plus)

    @@ -750,6 +740,9 @@ Pour les autres programmes, vous devriez pouvoir trouver la solution en ligne.Oui. Delta Chat 1.36 propose une nouvelle fonctionnalité expérimentale pour utiliser le même compte sur plusieurs appareils.

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • Sur le premier appareil, allez dans Paramètres → Ajouter un deuxième appareil, déverrouillez l’écran si nécessaire, et patientez un peu jusqu’à ce qu’un code QR s’affiche.

    • @@ -794,8 +787,8 @@ Ne quittez pas Delta Chat ! (Nous faisons de notre mieux pour que l’application fonctionne en arrière-plan, mais les systèmes ont une fâcheuse tendance à tuer les applis.)

    • -

      Delta Chat est déjà connecté sur l’appareil de destination. -Vous pouvez utiliser plusieurs comptes sur tous vos appareils : ajoutez un nouveau compte.

      +

      Si Delta Chat est déjà connecté sur l’appareil de destination. +Vous pouvez utiliser plusieurs comptes par appareil : ajoutez un nouveau compte.

    • Si les problèmes persistent, ou si vous ne pouvez pas scanner de code QR, essayez la méthode de transfert manuel décrite ci-dessous.

      @@ -884,10 +877,10 @@ Sinon, la suppression de messages dans une configuration multi-appareils pourrai

      Dans Delta Chat, vous pouvez partager des “applications privées” : ce sont des pièces jointes avec une extension de fichier .xdc. Elles possèdent des fonctionnalités très variées et peuvent faire de Delta Chat une messagerie vraiment extensible. En jargon technique, on parle de webxdc.

      -

      +

      - Dans quelle mesure les applications privées sont-elles privées ? + À quel point les applications privées sont-elles privées ?

      @@ -896,7 +889,7 @@ Sinon, la suppression de messages dans une configuration multi-appareils pourrai
    • Les applications privées ne peuvent ni envoyer des données sur internet ni télécharger quoi que ce soit.
    • Une application privée peut échanger des données uniquement au sein d’une discussion Delta Chat, ainsi qu’avec les copies sur les différents appareils des autres membres de la discussion. En dehors de cela, l’appli privée est complètement isolée d’internet.
    • La confidentialité offerte par une application privée est celle de votre discussion : si vous faites confiance aux personnes avec qui vous discutez, vous pouvez aussi vous fier à l’application privée.
    • -
    • Cela signifie aussi qu’ouvrir des applications privées dans des discussions où se trouvent des membres à qui vous ne faites pas confiance peut représenter un risque en matière de confidentialité. En fait, c’est la même situation qu’avec les courriels : vous ouvrez uniquement les pièces jointes envoyées par une adresse que vous connaissez et pas celles envoyées par un émetteur malveillant.
    • +
    • Cela signifie aussi qu’ouvrir des applications privées dans des discussions où se trouvent des membres à qui vous ne faites pas confiance peut représenter un risque en matière de confidentialité. En fait, la situation est la même qu’avec les courriels : vous ouvrez uniquement les pièces jointes envoyées par une adresse que vous connaissez et pas celles contenues dans les pourriels.

    @@ -908,9 +901,13 @@ Sinon, la suppression de messages dans une configuration multi-appareils pourrai

      -
    • De manière générale, il n’existe aucun magasin d’applications présélectionnées et Delta Chat n’est pas responsable des applications privées ou de leur contenu. Tout le monde peut partager des applications privées avec n’importe qui, sans restrictions.
    • -
    • Vous trouverez des exemples d’applications sur webxdc.org.
    • -
    • Beaucoup de gens développent leurs propres applications privées et les postent ensuite sur le forum Delta Chat.
    • +
    • In general, anyone can share private apps with each +other without restrictions.
    • +
    • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
    • +
    • Many people write their own private apps and post them to the Delta Chat +forum.

    @@ -938,20 +935,20 @@ Sinon, la suppression de messages dans une configuration multi-appareils pourrai

    Nous apprécions tout particulièrement les retours sur ces fonctionnalités. Vous voulez donner votre avis ? Rejoignez-nous sur le Forum pour contribuer. (Vous aimez faire des expériences ? Inscrivez-vous en cliquant sur “Sign up -> with Delta Chat” !)

    -

    +

    - Comment utiliser les appels audio/vidéo avec Delta Chat ? + Comment passer des appels audio/vidéo avec Delta Chat ?

      -
    • Pour activer les appels audio/vidéo, allez dans la section “Fonctionnalités expérimentales” des paramètres avancés et choisissez une “instance de discussion vidéo”.
    • +
    • Pour activer les appels audio/vidéo, accédez à la section “Fonctionnalités expérimentales” des paramètres avancés et choisissez une “instance de discussion vidéo”.
    • Quand vous invitez d’autres personnes dans une discussion vidéo, elle s’ouvre automatiquement dans votre application/navigateur et les destinataires reçoivent un courriel contenant un lien vers votre salon Jitsi/BBB. De cette manière, même les personnes n’utilisant pas Delta Chat peuvent participer.
    • Veuillez noter qu’il n’y a pas de sonnerie pour les destinataires, qui ne sont donc pas dérangé⋅e⋅s par votre invitation à une discussion vidéo.
    • Vous pouvez utiliser n’importe quel service de discussion vidéo permettant de rejoindre un salon par un lien. Il vous suffit de renseigner le lien dans vos paramètres.
    • -
    • Par exemple, pour utiliser l’instance Jitsi Meet principale, vous pouvez insérer :https://meet.jit.si/$ROOM. La variable$ROOMest une valeur aléatoire pour que vous puissiez rejoindre un nouveau salon Jitsi choisi au hasard à chaque nouvel appel.
    • +
    • Par exemple, pour utiliser l’instance Jitsi Meet principale, vous pouvez insérer :https://meet.jit.si/$ROOM. La variable$ROOMest une valeur aléatoire qui vous permet de rejoindre un nouveau salon Jitsi choisi au hasard à chaque nouvel appel.

    @@ -964,11 +961,11 @@ Sinon, la suppression de messages dans une configuration multi-appareils pourrai
    • -

      Un groupe vérifié est une discussion qui garantit une sécurité contre un attaquant actif. Tous les messages d’une discussion vérifiée sont chiffrés de bout en bout (e2ee), et les membres peuvent joindre en scannant un code barre à deux dimensions (QR code) d’invitation. Tous les membres sont alors connectés l’un à l’autre à travers une chaîne d’invitation, qui garanti une cohérence cryptographique contre des attaques réseaux actives ou des fournisseurs. -Consultez countermitm.readthedocs.io pour en savoir plus sur la R&D derrière cette fonctionnalité.

      +

      Un groupe vérifié est une discussion qui garantit une sécurité contre un attaquant actif. Tous les messages d’une discussion vérifiée sont chiffrés de bout en bout (E2E), et les membres peuvent rejoindre le groupe en scannant un code QR d’invitation. Tous les membres sont alors connectés les uns avec autres à travers une chaîne d’invitation, qui garantit une cohérence cryptographique contre des attaques de réseaux actifs ou des fournisseurs. +Consultez countermitm.readthedocs.io pour en savoir plus sur la recherche et le développement qui se trouvent derrière cette fonctionnalité.

    • -

      En octobre 2022, nous avons décidé que la fonctionnalité “groupes vérifiés” restait expérimentale, bien qu’elle soit améliorée en permanence et que de nombreux bugs aient été réparés depuis sa mise en place en 2018. En effet, certaines erreurs persistent, en particulier avec les groupes très larges, et les messages deviennent parfois illisibles.

      +

      Depuis octobre 2022, la fonctionnalité “groupes vérifiés” reste expérimentale, bien qu’elle soit améliorée en permanence et que de nombreux bugs aient été réparés depuis sa mise en place en 2018. En effet, il reste encore des cas où des problèmes peuvent survenir et les messages devenir illisibles, en particulier avec les très grands groupes.

    @@ -985,88 +982,76 @@ Consultez counte
  • Concrètement, c’est un courriel avec plusieurs destinataires en copie cachée.
  • Vous pouvez activer cette option dans la section “Fonctionnalités expérimentales” des paramètres avancés. Vous pourrez ensuite créer une liste de diffusion à partir du bouton “Nouvelle discussion”.
  • Si vous utilisez plusieurs appareils, veuillez noter qu’il n’est pas possible, pour l’instant, de synchroniser les listes de diffusion.
  • -
  • Les messages envoyés à des listes de diffusion ne sont pas chiffrés, parce que le chiffrement empêche l’anonymat puisque tous les destinataires peuvent voir qui d’autre a reçu le message. -(Mais envoyer des courriels individuels à tout le monde serait encore pire pour la consommation du réseau et à cause des limites d’envoi).
  • +
  • Les messages envoyés à des listes de diffusion ne sont pas chiffrés, parce que le chiffrement romprait l’anonymat puisque tous les destinataires pourraient alors voir qui d’autre a reçu le message. +(Mais envoyer des courriels individuels à tout le monde serait encore pire à cause de la consommation du réseau requise et des limites d’envoi).
  • -

    +

    - Comment puis-je partager ma géolocalisation avec mes contacts ? + Comment partager ma géolocalisation avec mes contacts ?

    • Vous pouvez activer l’envoi de la géolocalisation à la demande dans la section “Fonctionnalités expérimentales” des paramètres avancés.
    • -
    • Ensuite, si vous voulez partager votre position dans une discussion, allez dans “Ajouter une pièce jointe” et sélectionnez “Position”. Vous pouvez alors choisir la durée pendant laquelle vous voulez partager votre position avec les membres de la discussion, de 5 minutes à 6 heures.
    • +
    • Pour partager votre position dans une discussion, allez dans “Ajouter une pièce jointe” et sélectionnez “Position”. Vous pouvez alors choisir la durée pendant laquelle vous voulez partager votre position avec les membres de la discussion, de 5 minutes à 6 heures.
    • Quand vous vous déplacez, vos contacts voient votre position sur une carte dans la discussion.
    • Vous devez activer cette fonctionnalité dans les paramètres avancés pour pouvoir voir la carte et la position de vos contacts.
    • -
    • Cette fonctionnalité ne partage votre position avec personne d’autre que les membres de la discussion. Cependant, pour que la carte s’affiche, les tuiles de carte sont téléchargées depuis mapbox.com, donc si vous visualisez la cart, une requête pour afficher une zone spécifique est envoyée à mapbox.com. Si vous considérez cela comme une faille de sécurité, cette fonctionnalité n’est malheureusement pas faite pour vous. Nous nous efforçons de trouver une alternative décentralisée à Mapbox.
    • -
    • Sur le bureau, votre OS ne peut généralement pas déterminer votre position. À la place, vous pouvez faire un clic droit sur la carte pour décrire votre position, qui sera ensuite envoyée dans la discussion en tant que message tout en apparaissant sur la carte.
    • +
    • Cette fonctionnalité ne partage votre position avec personne d’autre que les membres de la discussion. Cependant, pour que la carte s’affiche, les tuiles de carte sont téléchargées depuis mapbox.com, donc si vous visualisez la carte, une requête pour afficher une zone spécifique est envoyée à mapbox.com. Si cela constitue un risque pour la protection de votre vie privée, cette fonctionnalité n’est probablement pas faite pour vous. Nous nous efforçons de trouver une alternative décentralisée à Mapbox.
    • +
    • Sur l’application de bureau, votre système d’exploitation ne peut généralement pas déterminer votre position. À la place, vous pouvez faire un clic droit sur la carte pour décrire votre position, qui sera ensuite envoyée dans la discussion en tant que message, tout en apparaissant sur la carte.
    -

    +

    - Qu’est-ce que le chiffrement expérimental de base de données protège réellement ? + Que protège réellement le chiffrement expérimental de base de données ?

      -
    • Right now, the database encryption is still very experimental. Don’t rely on -it for protection, you should additionally use encryption of your operating -system, if it provides any.
    • -
    • The database encryption does not yet encrypt the blobs, only the rows and -columns of the database. This more or less means that your messages are safe, -but not your attachments.
    • -
    • For iOS and Android, the encryption keys are stored in the system keychain. -This means the encryption is as secure as the operating system it’s running -on.
    • -
    • The Delta Chat desktop client doesn’t offer database encryption yet, as there -is no standard way to store the encryption keys on the different supported -platforms.
    • +
    • À l’heure actuelle, le chiffrement de la base de données est encore expérimental. Mieux vaut ne pas vous y fier pour la protection de vos données et activer aussi le chiffrement votre système d’exploitation si vous le pouvez.
    • +
    • Le chiffrement de base de données ne permet pas encore de chiffrer les blobs, seulement les lignes et les colonnes de la base. Cela signifie grosso modo que vos messages sont bien protégés, mais pas les pièces jointes.
    • +
    • Sur iOS et Android, les clefs de chiffrements sont stockées dans le trousseau système. Par conséquent, le chiffrement est aussi fiable que le système d’exploitation dont il dépend.
    • +
    • Comme il n’existe aucun moyen standardisé de stocker les clefs de chiffrement sur les différentes plateformes disponibles, le chiffrement de la base de données n’est pas encore disponible pour le client bureau de Delta Chat.
    -

    +

    - Why can I choose to only watch the DeltaChat folder? + Pourquoi peut-on choisir de ne consulter que le dossier Delta Chat ?

    -

    C’est un paramètre expérimental pour certaines personnes qui expérimentent avec des règles côté serveur. Tous les fournisseurs ne supportent pas ceci mais certains permettent de déplacer tous les courriels avec un en-tête “Chat-Version” dans le dossier DeltaChat. Normalement, cela marchera dans l’application Delta Chat.

    +

    Il s’agit d’un paramètre expérimental à destination des personnes qui expérimentent avec des règles côté serveur. Tous les fournisseurs ne le prennent pas en charge, mais certains permettent de déplacer tous les courriels avec un en-tête “Chat-Version” dans le dossier DeltaChat. Normalement, cette action est réalisé par l’application Delta Chat.

    -

    Enabling “Only Fetch from DeltaChat folder” makes sense if you have both:

    +

    Activer l’option “Ne consulter que le dossier Delta Chat” est intéressant si vous avez à la fois :

      -
    • activé une règle côté serveur pour déplacer tous les messages avec un en-tête “Chat-Version” dans le dossier DeltaChat, et - défini le paramètre “Voir les mails classiques” sur “Non, seulement les tchates”.
    • +
    • activé une règle côté serveur pour déplacer tous les messages avec un en-tête “Chat-Version” dans le dossier Delta Chat ;
    • +
    • modifié le paramètre “Voir les courriels classiques” pour “Non, seulement les discussions”.
    -

    In this case, Delta Chat doesn’t need to watch the Inbox, and it’s enough to only watch the DeltaChat folder.

    +

    Si ces deux conditions sont remplies, Delta Chat n’a pas besoin de surveiller la boîte de réception, seulement le dossier Delta Chat.

    -

    +

    - Comment puis-je changer mon compte pour une adresse courriel différente ? + Comment changer mon compte pour une adresse courriel différente ?

      -
    1. Change your address at the “Password and Account” seetings screen in Delta -Chat, enter your password (and if necessary, server settings) for the new -account
    2. -
    3. If possible, make your old e-mail provider forward all e-mails to your new -email address
    4. -
    5. Tell your contacts that you changed your address. If you write this to a -verified group, they will acknowledge this automatically.
    6. +
    7. Remplacez votre adresse courriel dans la fenêtre de configuration “Mot de passe et compte” et renseignez le mot de passe (ainsi que les paramètres serveur, si nécessaire) du nouveau compte.
    8. +
    9. Si vous le pouvez, demandez à votre ancien fournisseur de transférer tous vos courriels sur votre nouveau compte.
    10. +
    11. Informez vos contacts de votre changement d’adresse courriel. Si vous écrivez à un groupe vérifié, le changement sera pris en compte automatiquement par tout le monde.
    -

    To learn about the details behind this, read our blogpost on -it.

    +

    Pour connaître les détails derrière ce procédé, lisez notre article de blog en anglais.

    @@ -1098,7 +1083,7 @@ Cependant, il faut activer des options spéciales pour que cela fonctionne corre

      -
    • La plupart du temps, tous les serveurs courriel fonctionnent très bien. Mais nous conseillons un mélange de mailcow et mailadm, ainsi que nous le décrivons sur notre blog.
    • +
    • La plupart des serveurs courriel fonctionneront très bien. Mais nous conseillons de combiner mailcow et mailadm, ainsi que nous le décrivons sur notre blog.
    • Vous trouverez un guide d’installation sur notre site internet.
    @@ -1128,10 +1113,9 @@ Pour plus d’informations, voir dontkillmy

    @@ -1146,136 +1130,96 @@ Pour plus d’informations, voir dontkillmy
  • Consultez les standards utilisés dans Delta Chat.
  • -

    +

    - Was Delta Chat independently audited for security vulnerabilities? + Est-ce qu’un audit indépendant des failles de sécurité a été réalisé sur Delta Chat ?

    -

    The Delta Chat project underwent four independent security audits in the last years:

    +

    Quatre audits indépendants ont été réalisés sur le projet Delta Chat ces dernières années :

    • -

      In 2019, Include Security analyzed Delta -Chat’s PGP and -RSA libraries. -It found no critical issues, -but two high-severity issues that we subsequently fixed. -It also revealed one medium-severity and some less severe issues, -but there was no way to exploit these vulnerabilities in the Delta Chat implementation. -Some of them we nevertheless fixed since the audit was concluded. -You can read the full report here.

      +

      En 2019, Include Security a analysé les bibliothèques PGP et +RSA de Delta Chat. +Aucune faille critique n’a été trouvée, mais deux failles sévères ont été identifiées et immédiatement réparées par nos soins. +Une faille de gravité moyenne ainsi que quelques failles de gravité moindre ont également été découvertes, sans qu’il soit toutefois possible de les exploiter dans le fonctionnement de Delta Chat. +Nous en avons néanmoins réparées certaines depuis le rapport d’audit. +Le rapport complet est consultable ici.

    • -

      In 2020, Include Security analyzed Delta -Chat’s Rust core, -IMAP, -SMTP, and -TLS libraries. -It did not find any critical or high-severity issues. -The report raised a few medium-severity weaknesses - -they are no threat to Delta Chat users on their own -because they depend on the environment in which Delta Chat is used. -For usability and compatibility reasons, -we can not mitigate all of them -and decided to provide security recommendations to threatened users. -You can read the full report here.

      +

      En 2020, Include Security a analysé les bibliothèques principales Rust de Delta Chat, ainsi que ses bibliothèques IMAP, SMTP et TLS. +Aucun problème grave ou critique n’a été découvert. +Le rapport a tout de même révélé quelques vulnérabilités de gravité moyenne, qui ne représentent pas une menace en elles-mêmes pour les utilisateurs et les utilisatrices de Delta Chat, car elles dépendent de l’environnement dans lequel Delta Chat est utilisé. +Pour des raison de compatibilité et de facilité d’utilisation, nous ne pouvons pas les pallier toutes et avons préféré fournir des préconisations de sécurité aux personnes exposées. +Le rapport complet est consultable ici.

    • -

      Beginning 2023, Cure53 analyzed both the transport encryption of -Delta Chat’s network connections and a reproducible mail server setup as -recommended on this site. -You can read more about the audit on our blog -or read the full report here.

      +

      Début 2023, Cure53 a analysé le chiffrement d’acheminement des connexions réseau de Delta Chat et testé une configuration de serveur de courriel reproductible, telle que recommandée sur ce site. +Vous trouverez plus d’informations sur cet audit sur notre blog ou dans le rapport complet ici.

    • -

      Beginning 2023, we fixed security and privacy issues with the “web -apps shared in a chat” feature, related to failures of sandboxing -especially with Chromium. We subsequently got an independent security -audit from Cure53 and all issues found were fixed in the 1.36 app series released in April 2023. -See here for the full background story on E2E security in the web.

      +

      Début 2023, nous avons réparé les failles de sécurité et de confidentialité de la fonctionnalité “partage d’appli web dans une discussion” liées à des dysfonctionnements en mode bac à sable, en particulier avec Chromium. Après quoi, nous avons soumis Delta Chat à un nouvel audit de sécurité indépendant par Cure53, puis effectué les réparations de toutes les failles découvertes pour la version 1.36 de nos applications, publiée en avril 2023. +Vous trouverez ici un article de fond complet à propos de la sécurité du chiffrement de bout-en-bout sur internet.

    -

    +

    - Comment est financé le développement de Delta Chat? + Comment est financé le développement de Delta Chat ?

    -

    Delta Chat does not receive any Venture Capital and -is not indebted, and under no pressure to produce huge profits, or to -sell users and their friends and family to advertisers (or worse). -We rather use public funding sources, so far from EU and US origins, to help -our efforts in instigating a decentralized and diverse chat messaging eco-system -based on Free and Open-Source community developments.

    +

    Delta Chat ne reçoit par de fonds en capital-risque, n’est pas endetté et ne subit aucune pression pour générer de gros profits ou vendre ses utilisateurs et utilisatrices - en même temps que leurs amis et leur famille - à des annonceurs (ou pire). +Nous préférons utiliser des fonds provenant d’institutions publiques, jusqu’à présent basées en Europe ou aux États-Unis, pour soutenir nos efforts de développement d’un système de messagerie diversifié et décentralisé, basé sur les contributions de la communauté du libre et de l’open-source.

    -

    Concretely, Delta Chat developments have so far been funded from these sources:

    +

    Jusqu’à présent, le développement de Delta Chat a été financé par les sources suivantes :

    • -

      The NEXTLEAP EU project funded the research -and implementation of verified groups and setup contact protocols -in 2017 and 2018 and also helped to integrate End-to-End Encryption -through Autocrypt.

      +

      Le projet européen NEXTLEAP a financé la recherche et le développement des groupes vérifiés, ainsi que la configuration des protocoles de contact en 2017 et 2018. Il a également aidé à intégrer le chiffrement de bout-en-bout avec Autocrypt.

    • -

      The Open Technology Fund gave us a -first 2018/2019 grant (~$200K) during which we majorly improved the Android app -and released a first Desktop app beta version, and which moreover -moored our feature developments in UX research in human rights contexts, -see our concluding Needfinding and UX report. -The second 2019/2020 grant (~$300K) helped us to -release Delta/iOS versions, to convert our core library to Rust, and -to provide new features for all platforms.

      +

      L’association Open Technology Fund nous a octroyé une première subvention en 2018/2019 (de 200.000$ environ), grâce à laquelle nous avons pu apporter des améliorations majeures à l’application Android et publier une première version Beta de l’application de bureau. Elle nous a aussi permis d’ancrer notre recherche UX de développement de fonctionnalités dans des contextes de droits humains.
      +À ce sujet, vous pouvez consulter notre rapport en anglais “Needfinding and UX report”. +La seconde subvention de 2019/2020 (environ 300.000$) nous a permis de publier des version iOS de Delta Chat, de convertir notre bibliothèque principale en Rust et de créer de nouvelles fonctionnalités pour toutes les plateformes.

    • -

      The NLnet foundation granted in 2019/2020 EUR 46K for -completing Rust/Python bindings and instigating a Chat-bot ecosystem.

      +

      La NLnet foundation nous a octroyé une subvention de 46.000€ en 2019/2020 pour avoir achevé la liaison entre Rust et Python, ainsi que pour avoir instauré un système de chatbot.

    • -

      In 2021 we received further EU funding for two Next-Generation-Internet -proposals, namely for EPPD - e-mail provider portability directory (~97K EUR) and AEAP - email address porting (~90K EUR) which resulted in better multi-account support, improved QR-code contact and group setups and many networking improvements on all platforms.

      +

      En 2021, nous avons reçu un financement européen supplémentaire pour deux initiatives de Next Generation Internet : 97.000€ pour la création d’un annuaire de la portabilité des fournisseurs de courriel (ou EPPD pour “e-mail provider portability directory”), et environ 90.000€ pour le développement du portage des adresses courriel (ou AEAP pour “email address porting”). Ces deux projets ont aussi permis une meilleure prise en charge de la fonctionnalité multi-compte, un QR code de contact plus performant et de nombreuses améliorations concernant la gestion des réseaux et les configurations de groupe sur toutes les plateformes.

    • -

      From End 2021 till March 2023 we received Internet Freedom funding (500K USD) from the -U.S. Bureau of Democracy, Human Rights and Labor (DRL). -This funding supported our long-running goals to make Delta Chat more usable -and compatible with a wide range of e-mail servers world-wide, and more resilient and secure -in places often affected by internet censorship and shutdowns.

      +

      Entre la fin de l’année 2021 et mars 2023, nous avons bénéficié d’un fonds Internet Freedom (pour la “liberté d’internet”, de 500.000$) du Bureau of Democracy, Human Rights and Labor (DRL) des États-Unis. +Cet argent est venu soutenir nos deux objectifs de longue date ; à savoir améliorer la compatibilité de Delta Chat pour le rendre utilisable plus facilement avec un large éventail de serveurs de courriel dans le monde entier, et en améliorer la résistance et la sécurité dans les régions où les coupures internet et la censure d’internet sont monnaie courante.

    • -

      Beginning 2023 we got accepted in the Next Generation Internet (NGI) -Entrust program for our “Private Decentralized Apps” proposals. -Exact amount is to be determined (around 100K EUR). -This funding supports further developments of webxdc “apps shared in a chat”.

      +

      Début 2023, nous avons été acceptés dans le programme Entrust de Next Generation Internet (NGI) pour nos projets d’applications privées décentralisées. +Le montant exact de la subvention reste à déterminer (autour de 100.000€). +Ce financement nous permettra de continuer le développement des appli webxdc partagées dans une discussion.

    • -

      Sometimes we receive one-time donations from private individuals. -For example, in 2021 a generous individual bank-wired us 4K EUR -with the subject “keep up the good developments!”. 💜 -We use such money to fund development gatherings or to care for ad-hoc expenses -that can not easily be predicted for, or reimbursed from, public funding grants. -Receiving more donations also helps us to become more independent and long-term viable -as a contributor community.

      +

      Nous recevons parfois des dons ponctuels de la part de personnes privées. + En 2021 par exemple, une généreuse personne nous a envoyé 4000€ par virement bancaire, avec l’intitulé “continuez votre super travail de développement !”. 💜 + Nous utilisons l’argent de ces dons pour financer des rencontres entre développeurs et développeuses ou pour des dépenses ponctuelles difficiles à anticiper ou à rembourser avec des subventions publiques. +Recevoir plus de dons aide notre communauté de contributrices et contributeurs à devenir plus indépendante et à rester viable sur le long terme.

      -
    • Enfin, en dernier mais largement pas des moindres, plusieurs passionné⋅e⋅s et expert⋅e⋅s ont contribué, et contribuent toujours, bénévolement au développement de Delta Chat sans gagner d’argent, ou seulement de petites sommes. Sans leur énergie, Delta Chat n’en serait pas là aujourd’hui… et de très loin.
    • +
    • Dernier point, mais certainement pas des moindres : quelques personnes passionnées et expertes ont apporté, et apportent toujours, leur contribution bénévole au développement de Delta Chat sans contrepartie financière - ou seulement de petites sommes. Sans leur énergie, Delta Chat n’en serait pas là aujourd’hui… et de très loin.
    -

    The monetary funding mentioned above is mostly organized by merlinux GmbH in -Freiburg (Germany), and is distributed to more than a dozen contributors world-wide.

    +

    Les financements mentionnés précédemment sont gérés principalement par merlinux GmbH à Fribourg (Allemagne) et redistribués à plus d’une douzaine de contributeurs et contributrices du monde entier.

    -

    Please see Delta Chat Contribution channels -for both monetary and and other contribution possibilities.

    +

    N’hésitez pas à consulter les Canaux de contribution à Delta Chat Contribution pour en savoir plus sur les différentes manières de contribuer, financières ou non.

    diff --git a/assets/help/help.css b/assets/help/help.css index 3fa35d540..b2770e33c 100644 --- a/assets/help/help.css +++ b/assets/help/help.css @@ -1,3 +1,7 @@ +html { + color-scheme: dark light; +} + a { text-decoration: none; } diff --git a/assets/help/id/help.html b/assets/help/id/help.html index 2e635fa72..269096d5c 100644 --- a/assets/help/id/help.html +++ b/assets/help/id/help.html @@ -835,6 +835,9 @@ For other programs, you should be able to find a solution online.

    Yes. Delta Chat 1.36 comes with a new, experimental function for using the same account on different devices:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • On the first device, go to Settings → Add Second Device, unlock the screen if needed and wait a moment until a QR code is shown

      @@ -1028,10 +1031,11 @@ you only open attachments from senders you trust, and not from spammers.
      • -
      • In general, there is no curated appstore and Delta Chat is not responsible -for private apps or their content. Anyone can share private apps with each +
      • In general, anyone can share private apps with each other without restrictions.
      • -
      • You can find some example apps on webxdc.org.
      • +
      • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
      • Many people write their own private apps and post them to the Delta Chat forum.
      diff --git a/assets/help/it/help.html b/assets/help/it/help.html index fbb522e80..9ec1be310 100644 --- a/assets/help/it/help.html +++ b/assets/help/it/help.html @@ -376,9 +376,9 @@ il modo in cui puoi eliminare il tuo account dipende dal tuo provider e-mail. Non abbiamo alcun controllo sul tuo account e-mail, quindi purtroppo non possiamo aiutarti in questo.

      -

      If you want to keep the account, -but uninstall Delta Chat, -it is recommended to leave any group chat before uninstalling Delta Chat.

      +

      Se vuoi mantenere l’account, +ma disinstallare Delta Chat, +si consiglia di uscire da qualsiasi chat di gruppo prima di disinstallare Delta Chat.

      @@ -480,16 +480,14 @@ ad esempio, si vuole risparmiare spazio di archiviazione presso il proprio provi eliminare automaticamente i vecchi messaggi. I messaggi rimangono comunque sul dispositivo finché non li si cancella anche lì.

    • -

      To turn it on, go to Delete Old Messages → Delete Messages from Server -in the “Chats and Media” settings. -You can set a timeframe between “At once” and “After 1 year”. -All e-mails received by Delta Chat will be deleted from the server after this timeframe.

      +

      Per attivarlo, vai su Elimina i vecchi messaggi → Elimina messaggi dal server +nelle impostazioni “Chat e Media”. +Puoi impostare un intervallo di tempo compreso tra “Subito” e “Dopo 1 anno”. +Tutte le e-mail ricevute da Delta Chat verranno eliminate dal server dopo questo periodo di tempo.

    • -

      Si noti che se si utilizza Delta Chat su più di un dispositivo, è consigliabile lasciare i messaggi sul server, -finché anche l’altro dispositivo non potrà scaricarli. In questo caso, -si dovrebbe impostare la cancellazione automatica su “Dopo 1 giorno” o qualcosa di simile, -a seconda della frequenza con cui si accende l’altro dispositivo.

      +

      Tieni presente che se utilizzi Delta Chat su più di un dispositivo, è necessario lasciare il messaggio sul server con un tempo sufficiente +in modo che anche gli altri dispositivi(o) possano scaricarli.

    @@ -828,6 +826,9 @@ Per altri programmi, dovresti essere in grado di trovare una soluzione online.Sì. Delta Chat 1.36 è dotato di una nuova funzione sperimentale che consente di utilizzare lo stesso account su diversi dispositivi:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • Sul primo dispositivo, andare su Impostazioni → Aggiungi Secondo Dispositivo, sbloccare lo schermo se necessario e attendere un attimo fino a quando non viene visualizzato un codice QR

      @@ -1017,12 +1018,13 @@ solo gli allegati di mittenti di cui ci si fida e non quelli degli spammer.
      • -
      • In generale, non esiste un appstore curato e Delta Chat non è responsabile delle applicazioni private o del loro contenuto. -Chiunque può condividere apps private con -gli altri senza restrizioni.
      • -
      • Potete trovare alcuni esempi di app su webxdc.org.
      • -
      • Molte persone scrivono le proprie applicazioni private e le pubblicano sul forum di Delta Chat -.
      • +
      • In general, anyone can share private apps with each +other without restrictions.
      • +
      • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
      • +
      • Many people write their own private apps and post them to the Delta Chat +forum.

      @@ -1214,8 +1216,8 @@ indirizzo e-mail gruppo verificato, lo riconosceranno automaticamente. -

      Per conoscere i dettagli alla base di questo, leggi il nostro post sul blog -it.

      +

      Per conoscere i dettagli alla base di questo, leggi il nostro post +sul blog.

      @@ -1317,7 +1319,7 @@ problema per molte app legittime. Per ulteriori informazioni, vedi

      -

      The Delta Chat project underwent four independent security audits in the last years:

      +

      Il progetto Delta Chat è stato sottoposto a quattro verifiche di sicurezza indipendenti negli ultimi anni:

      • @@ -1354,11 +1356,11 @@ Puoi leggere ulteriori informazioni sull’audit rapporto completo qui.

      • -

        Beginning 2023, we fixed security and privacy issues with the “web -apps shared in a chat” feature, related to failures of sandboxing -especially with Chromium. We subsequently got an independent security -audit from Cure53 and all issues found were fixed in the 1.36 app series released in April 2023. -See here for the full background story on E2E security in the web.

        +

        A partire dal 2023, abbiamo risolto i problemi di sicurezza e privacy con le “web +app condivise in una chat”, relativa ai fallimenti del sandboxing +soprattutto con Chromium. Successivamente abbiamo ottenuto una sicurezza indipendente +verificata da Cure53 e tutti i problemi rilevati sono stati risolti nella serie di app 1.36 rilasciata nell’Aprile 2023. +Vedi qui per la storia completa sulla sicurezza E2E nel web.

      diff --git a/assets/help/nl/help.html b/assets/help/nl/help.html index 9792f197c..6b413ecb0 100644 --- a/assets/help/nl/help.html +++ b/assets/help/nl/help.html @@ -14,7 +14,7 @@
    • Welke Android-rechten heeft Delta Chat nodig?
    • Wat is vastmaken, negeren en archiveren?
    • Wat betekent die groene stip?
    • -
    • How can I delete my account?
    • +
    • Hoe kan ik mijn account verwijderen?
  • Groepen @@ -375,22 +375,22 @@ leesbevestigingen hebt uitgeschakeld, dan wordt er geen groen stipje getoond tot iemand benaderd of iets in een groep plaatst..
  • -

    +

    - How can I delete my account? + Hoe kan ik mijn account verwijderen?

    -

    As you use an e-mail account for Delta Chat, -how you can delete your account depends on your e-mail provider. -We don’t have any control over your e-mail account, -so unfortunately we can’t help you with that.

    +

    Delta Chat maakt gebruik van je e-mailaccount, +dus de verwijderprocedure is afhankelijk van je provider. +We hebben geen zeggenschap over je account, +dus helaas kunnen we je daar niet bij helpen.

    -

    If you want to keep the account, -but uninstall Delta Chat, -it is recommended to leave any group chat before uninstalling Delta Chat.

    +

    Als je je account wilt behouden maar Delta Chat +niet wilt verwijderen, verlaat dan groeps- +gesprekken voordat je Delta Chat verwijdert.

    @@ -835,6 +835,9 @@ Voor andere programma’s kun je zélf een oplossing zoeken op internet.

    Ja, Delta Chat 1.36 bevat een nieuwe, experimentele functie om hetzelfde account op meerdere apparaten te gebruiken:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • Ga op het eerste apparaat naar Instellingen → Tweede apparaat toevoegen, ontgrendel - indien nodig - het scherm en wacht totdat de QR-code in beeld verschijnt.

      @@ -1019,10 +1022,13 @@ niet per definitie veilig. Open dus alléén privé-apps van personen die je ver

      -
    • Er is in principe geen appwinkel met privé-apps en Delta Chat is niet verantwoordelijk voor -de apps of inhoud ervan. Iedereen kan ongelimiteerd privé-apps met elkaar delen.
    • -
    • Op webxdc.org staan wel enkele voorbeeldapps.
    • -
    • De meeste mensen maken hun eigen privé-apps en delen ze op het Delta Chat-forum.
    • +
    • In general, anyone can share private apps with each +other without restrictions.
    • +
    • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
    • +
    • Many people write their own private apps and post them to the Delta Chat +forum.

    diff --git a/assets/help/pl/help.html b/assets/help/pl/help.html index feb919a51..bf6a856f6 100644 --- a/assets/help/pl/help.html +++ b/assets/help/pl/help.html @@ -14,7 +14,7 @@
  • Jakich uprawnień potrzebuje Delta Chat?
  • Co oznacza przypinanie, wyciszanie i archiwizowanie?
  • Co oznacza zielona kropka?
  • -
  • How can I delete my account?
  • +
  • Jak mogę usunąć swoje konto?
  • Grupy @@ -332,22 +332,18 @@ aby wyciszyć czat, użyj menu czatu (Android/Desktop) lub profilu czatu (iOS).<
  • Z drugiej strony inni nie zawsze „zobaczą, że jesteś online”. Jeśli wyłączyłeś potwierdzenia odczytu, nie zobaczą oni zielonej kropki, dopóki nie wyślesz do nich wiadomości lub nie napiszesz do grupy, w której również się znajdują.
  • -

    +

    - How can I delete my account? + Jak mogę usunąć swoje konto?

    -

    As you use an e-mail account for Delta Chat, -how you can delete your account depends on your e-mail provider. -We don’t have any control over your e-mail account, -so unfortunately we can’t help you with that.

    +

    Gdy korzystasz z konta e-mail w Delta Chat, sposób usunięcia konta zależy od dostawcy poczty e-mail. +Nie mamy żadnej kontroli nad twoim kontem e-mail, więc niestety nie możemy ci w tym pomóc.

    -

    If you want to keep the account, -but uninstall Delta Chat, -it is recommended to leave any group chat before uninstalling Delta Chat.

    +

    Jeśli chcesz zachować konto, ale odinstalować Delta Chat, zaleca się opuszczenie każdego czatu grupowego przed odinstalowaniem Delta Chat.

    @@ -715,6 +711,9 @@ W przypadku innych programów można znaleźć rozwiązanie online.

    Tak. Delta Chat 1.36 zawiera nową, eksperymentalną funkcję korzystania z tego samego konta na różnych urządzeniach:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • Na pierwszym urządzeniu przejdź do Ustawienia → Dodaj kolejne urządzenie, w razie potrzeby odblokuj ekran i poczekaj chwilę, aż pojawi się kod QR

    • @@ -864,9 +863,13 @@ W przypadku innych programów można znaleźć rozwiązanie online.

      -
    • Ogólnie rzecz biorąc, nie ma wyselekcjonowanego sklepu z aplikacjami, a Delta Chat nie ponosi odpowiedzialności za prywatne aplikacje ani ich zawartość. Każdy może udostępniać sobie prywatne aplikacje bez ograniczeń.
    • -
    • Przykładowe aplikacje można znaleźć na stronie webxdc.org.
    • -
    • Wiele osób pisze własne prywatne aplikacje i publikuje je na forum Delta Chat.
    • +
    • In general, anyone can share private apps with each +other without restrictions.
    • +
    • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
    • +
    • Many people write their own private apps and post them to the Delta Chat +forum.

    diff --git a/assets/help/pt/help.html b/assets/help/pt/help.html index b30eabd43..164262782 100644 --- a/assets/help/pt/help.html +++ b/assets/help/pt/help.html @@ -816,6 +816,9 @@ Para outros programas, você deveria achar uma solução online.

    Yes. Delta Chat 1.36 comes with a new, experimental function for using the same account on different devices:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • On the first device, go to Settings → Add Second Device, unlock the screen if needed and wait a moment until a QR code is shown

      @@ -1009,10 +1012,11 @@ you only open attachments from senders you trust, and not from spammers.

      -
    • In general, there is no curated appstore and Delta Chat is not responsible -for private apps or their content. Anyone can share private apps with each +
    • In general, anyone can share private apps with each other without restrictions.
    • -
    • You can find some example apps on webxdc.org.
    • +
    • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
    • Many people write their own private apps and post them to the Delta Chat forum.
    diff --git a/assets/help/ru/help.html b/assets/help/ru/help.html index 137340f3c..5dc850b57 100644 --- a/assets/help/ru/help.html +++ b/assets/help/ru/help.html @@ -852,6 +852,9 @@ RFC.

    Yes. Delta Chat 1.36 comes with a new, experimental function for using the same account on different devices:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • On the first device, go to Settings → Add Second Device, unlock the screen if needed and wait a moment until a QR code is shown

      @@ -1044,10 +1047,11 @@ you only open attachments from senders you trust, and not from spammers.
      • -
      • In general, there is no curated appstore and Delta Chat is not responsible -for private apps or their content. Anyone can share private apps with each +
      • In general, anyone can share private apps with each other without restrictions.
      • -
      • You can find some example apps on webxdc.org.
      • +
      • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
      • Many people write their own private apps and post them to the Delta Chat forum.
      diff --git a/assets/help/sk/help.html b/assets/help/sk/help.html index 36ff5fa4a..71fe78055 100644 --- a/assets/help/sk/help.html +++ b/assets/help/sk/help.html @@ -852,6 +852,9 @@ Pre iné programy by ste mali vedieť nájsť riešenie online.

      Yes. Delta Chat 1.36 comes with a new, experimental function for using the same account on different devices:

    • Grupe @@ -390,22 +390,22 @@ u merakosni dhe lëruni ca kohë ;-)
    • t’u dërgoni një mesazh, ose të shkruani te një grup ku bëjnë pjesë edhe ata.
    -

    +

    - How can I delete my account? + Si mund ta fshij llogarinë time?

    -

    As you use an e-mail account for Delta Chat, -how you can delete your account depends on your e-mail provider. -We don’t have any control over your e-mail account, -so unfortunately we can’t help you with that.

    +

    Ngaqë për Delta Chat-in përdorni një llogari email, +se si mund të fshini llogarinë tuaj, varet nga shërbimi juaj email. +Nuk kemi fuqi mbi llogarinë tuaj email, +ndaj, mjerisht, s’mund t’ju ndihmojmë për këtë.

    -

    If you want to keep the account, -but uninstall Delta Chat, -it is recommended to leave any group chat before uninstalling Delta Chat.

    +

    Nëse doni ta mbani llogarinë, +por të çinstaloni Delta Chat-in, +rekomandohet të braktisni çfarëdo grupi fjalosjeje, para çinstalimit të Delta Chat-it.

    @@ -509,10 +509,10 @@ të zgjidhni të fshihen automatikisht mesazhet e vjetër. Mbeten ende në pajis deri sa t’i fshini edhe prej andej.

  • -

    To turn it on, go to Delete Old Messages → Delete Messages from Server -in the “Chats and Media” settings. -You can set a timeframe between “At once” and “After 1 year”. -All e-mails received by Delta Chat will be deleted from the server after this timeframe.

    +

    Për ta vënë në punë, kaloni te Fshi Mesazhe të Vjetër → Fshiji Mesazhet te Shërbyesi +te rregullimet “Fjalosje dhe Media”. +Mund të caktoni një interval kohor, mes “Menjëherë” dhe “Pas 1 viti”. +Krejt email-et e marrë nga Delta Chat-i do të fshihen te shërbyesi pas këtij intervali kohor.

  • Kini parasysh se, nëse e përdorni Delta Chat-in në më shumë se një pajisje, duhet t’i @@ -865,6 +865,9 @@ Për programe të tjera, mund të gjeni një zgjidhje në internet.

    Po. Delta Chat 1.36 vjen me një funksion të ri eksperimental për përdorimin e të njëjtës llogari në pajisje të ndryshme:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • Te pajisja e parë, kaloni te Rregullime → Shtoni Pajisje të Dytë, shkyçni ekranin, në u dashtë dhe prisni një çast sa të shfaqet një kod QR

      @@ -1063,12 +1066,13 @@ hapni bashkëngjitje vetëm nga dërgues që u besoni dhe jo prej kujtdo.
    • -
    • Në përgjithësi, s’ka shitore aplikacionesh nën përkujdesje dhe Delta Chat-i -s’është përgjegjës për aplikacionet private apo lëndën e tyre. Cilido mund -të ndajë me të tjerët aplikacione private pa kufizime.
    • -
    • Mund të gjeni disa aplikacione shembuj te webxdc.org.
    • -
    • Mjaft vetë shkruajnë aplikacionet e tyre private dhe i postojnë te forumi i -Delta Chat-it.
    • +
    • In general, anyone can share private apps with each +other without restrictions.
    • +
    • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
    • +
    • Many people write their own private apps and post them to the Delta Chat +forum.

    @@ -1369,7 +1373,7 @@ email-i me çfarëdo aplikacioni

    -

    The Delta Chat project underwent four independent security audits in the last years:

    +

    Projekti Delta Chat ka pasur katër auditime të pavarur sigurie gjatë viteve të fundit:

    diff --git a/assets/help/uk/help.html b/assets/help/uk/help.html index dd141d250..5159094b0 100644 --- a/assets/help/uk/help.html +++ b/assets/help/uk/help.html @@ -14,7 +14,7 @@
  • Яких дозволів потребує Delta Chat?
  • Що значить Закріплення, Приглушення, Архівування?
  • Що означає зелена точка?
  • -
  • How can I delete my account?
  • +
  • Як мені видалити свій обліковий запис?
  • Групи @@ -345,22 +345,17 @@ – З іншого боку, інші не завжди «побачать, що ви онлайн». Якщо ви вимкнули сповіщення про прочитання, вони не побачать зелену крапку, доки ви не надішлете їм повідомлення або напишете у групу, до якої вони входять.
  • -

    +

    - How can I delete my account? + Як мені видалити свій обліковий запис?

    -

    As you use an e-mail account for Delta Chat, -how you can delete your account depends on your e-mail provider. -We don’t have any control over your e-mail account, -so unfortunately we can’t help you with that.

    +

    Оскільки ви використовуєте обліковий запис електронної пошти для Delta Chat, спосіб видалення облікового запису залежить від вашого постачальника послуг електронної пошти. Ми не маємо жодного контролю над вашим обліковим записом електронної пошти, тому, на жаль, ми не можемо вам у цьому допомогти.

    -

    If you want to keep the account, -but uninstall Delta Chat, -it is recommended to leave any group chat before uninstalling Delta Chat.

    +

    Якщо ви хочете зберегти обліковий запис, але видалити Delta Chat, перед видаленням Delta Chat рекомендується покинути будь-які групові чати.

    @@ -721,6 +716,9 @@ it is recommended to leave any group chat before uninstalling Delta Chat.

    Так. У Delta Chat 1.36 є нова експериментальна функція для використання одного облікового запису на різних пристроях:

      +
    • +

      Make sure both devices are on the same Wi-Fi or network

      +
    • На першому пристрої перейдіть до Налаштування → Додати другий пристрій, розблокуйте екран, якщо потрібно і трохи зачекайте, поки не з’явиться QR-код

    • @@ -868,9 +866,13 @@ it is recommended to leave any group chat before uninstalling Delta Chat.

      -
    • Загалом немає спеціального магазину програм, і Delta Chat не несе відповідальності за приватні програми чи їхній вміст. Кожен може ділитися один з одним приватними програмами без обмежень.
    • -
    • Приклади програм можна знайти на webxdc.org.
    • -
    • Багато людей пишуть власні приватні програми та публікують їх на форумі Delta Chat.
    • +
    • In general, anyone can share private apps with each +other without restrictions.
    • +
    • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
    • +
    • Many people write their own private apps and post them to the Delta Chat +forum.

    diff --git a/assets/help/zh_CN/help.html b/assets/help/zh_CN/help.html index 4e0b63d2b..03b5c2ef9 100644 --- a/assets/help/zh_CN/help.html +++ b/assets/help/zh_CN/help.html @@ -7,14 +7,14 @@
  • 哪些消息会在 Delta Chat 中出现?
  • 如果希望收到我从未发送过消息的人的消息,该怎么办?
  • Delta Chat 支持图像、视频和其他附件吗?
  • -
  • How can I add or switch between multiple accounts?
  • +
  • 如何添加账户或在多个账户之间切换?
  • 谁会看见我的个人资料图片?
  • Delta Chat 支持 HTML 电子邮件吗?
  • 为什么我必须在 Delta Chat 中输入我的电子邮件密码? 这样安全吗?
  • Delta Chat 需要哪些权限?
  • 固定、静音、归档是什么意思?
  • 绿色圆点代表什么?
  • -
  • How can I delete my account?
  • +
  • 如何删除我的账户?
  • 群组 @@ -182,7 +182,7 @@

    - How can I add or switch between multiple accounts? + 如何添加账户或在多个账户之间切换?

    @@ -348,10 +348,10 @@ Archived chats remain accessible above the chat list or via search.

  • 另一方面,其他人并不总是能“看到您在线”。如果您关掉了已读回执,那么在您发消息给其他人以及和他们的共有群组前,其他人将不会看到绿色圆点。
  • -

    +

    - How can I delete my account? + 如何删除我的账户?

    @@ -738,6 +738,9 @@ not their account, you can still communicate in the 1:1 chat. (webxdc.org 找到一些示例应用。 -
  • 很多人编写了他们自己的私人应用,并将应用发布到了 Delta Chat 论坛
  • +
  • In general, anyone can share private apps with each +other without restrictions.
  • +
  • You can send ‘hi’ to xstore@testrun.org +to see an experimental webxdc appstore. +All of the apps are open source and for free.
  • +
  • Many people write their own private apps and post them to the Delta Chat +forum.
  • diff --git a/build.gradle b/build.gradle index 03f4f4628..4278127da 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { android { namespace "org.thoughtcrime.securesms" flavorDimensions "none" - compileSdkVersion 32 + compileSdkVersion 33 // Set NDK version to strip native libraries. // Even though we compile our libraries outside Gradle with `scripts/ndk-make.sh`, @@ -94,14 +94,14 @@ android { useLibrary 'org.apache.http.legacy' defaultConfig { - versionCode 658 - versionName "1.38.2" + versionCode 661 + versionName "1.41.0" applicationId "com.b44t.messenger" multiDexEnabled true minSdkVersion 16 - targetSdkVersion 32 + targetSdkVersion 33 vectorDrawables.useSupportLibrary = true diff --git a/docs/release-checklist.md b/docs/release-checklist.md index a5848f96d..c52d566ed 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -97,11 +97,12 @@ This may take some days. on : -12. a) for "Delta Chat", select tab "Add upcoming version" - b) at "App Information" hit "Edit" abottom and then "Replace APK" atop, - upload the APK from above - c) on the same tab, add "Release notes" from CHANGELOG.md, "Save" - d) hit "Submit app" at the upper right corner +12. a) for "Delta Chat", select "Add upcoming version" on the left + b) at "Step 1 / Existing file(s)" hit "Replace", upload the APK from above + c) on the "Step 1" page, add "Release notes" from CHANGELOG.md, hit "Next" + d) on "Step 2" page: "Does your app collect or transfer user data to third parties?" -> No, then "Next" + e) on "Step 3" page: "Next" + f) on "Step 4" page: "Submit app" ## Releases on Apklis, Passkoocheh diff --git a/flake.lock b/flake.lock index 8d336f07d..7fd8b23b9 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1687378940, - "narHash": "sha256-xGPTFVCWzbQh3DwuKedetTIpq2p6vq36xHe7HrasqPU=", + "lastModified": 1692995047, + "narHash": "sha256-L7PzSUM0bomAax+i+L+PoHokIgkD03m7MaiirIU5VDA=", "owner": "tadfisher", "repo": "android-nixpkgs", - "rev": "28e855d3273cf858a3e233c7a591ad19b3066aed", + "rev": "6529bfb4b0d5fc38de7ac9093a14edf04a2258c5", "type": "github" }, "original": { @@ -29,11 +29,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1687173957, - "narHash": "sha256-GOds2bAQcZ94fb9/Nl/aM+r+0wGSi4EKYuZYR8Dw4R8=", + "lastModified": 1692793255, + "narHash": "sha256-yVyj0AE280JkccDHuG1XO9oGxN6bW8ksr/xttXcXzK0=", "owner": "numtide", "repo": "devshell", - "rev": "2cf83bb31720fcc29a999aee28d6da101173e66a", + "rev": "2aa26972b951bc05c3632d4e5ae683cb6771a7c6", "type": "github" }, "original": { @@ -47,11 +47,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1687171271, - "narHash": "sha256-BJlq+ozK2B1sJDQXS3tzJM5a+oVZmi1q0FlBK/Xqv7M=", + "lastModified": 1692799911, + "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", "owner": "numtide", "repo": "flake-utils", - "rev": "abfb11bd1aec8ced1c9bb9adfe68018230f4fb3c", + "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", "type": "github" }, "original": { @@ -65,11 +65,11 @@ "systems": "systems_3" }, "locked": { - "lastModified": 1687171271, - "narHash": "sha256-BJlq+ozK2B1sJDQXS3tzJM5a+oVZmi1q0FlBK/Xqv7M=", + "lastModified": 1692799911, + "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", "owner": "numtide", "repo": "flake-utils", - "rev": "abfb11bd1aec8ced1c9bb9adfe68018230f4fb3c", + "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", "type": "github" }, "original": { @@ -98,11 +98,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1686960236, - "narHash": "sha256-AYCC9rXNLpUWzD9hm+askOfpliLEC9kwAo7ITJc4HIw=", + "lastModified": 1692734709, + "narHash": "sha256-SCFnyHCyYjwEmgUsHDDuU0TsbVMKeU1vwkR+r7uS2Rg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "04af42f3b31dba0ef742d254456dc4c14eedac86", + "rev": "b85ed9dcbf187b909ef7964774f8847d554fab3b", "type": "github" }, "original": { @@ -114,11 +114,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1687488839, - "narHash": "sha256-7JDjuyHwUvGJJge9jxfRJkuYyL5G5yipspc4J3HwjGA=", + "lastModified": 1692808169, + "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f9e94676ce6c7531c44d38da61d2669ebec0f603", + "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", "type": "github" }, "original": { @@ -158,11 +158,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1687400833, - "narHash": "sha256-rVENiSupjAE8o1+ZXNRIqewUzM2brm+aeme8MUrwl0U=", + "lastModified": 1692929460, + "narHash": "sha256-zdN6UVtEml7t0WQHVy0avsE+TWJLklXnqJyiPaOa8u0=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "fc0a266e836c079a9131108f4334e5af219dbb93", + "rev": "673e2d3d2a3951adc6f5e3351c9fce6ad130baed", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index df197d0b5..9ee6f14a4 100644 --- a/flake.nix +++ b/flake.nix @@ -33,7 +33,7 @@ buildInputs = [ android-sdk pkgs.openjdk17 - (pkgs.buildPackages.rust-bin.stable."1.70.0".minimal.override { + (pkgs.buildPackages.rust-bin.stable."1.72.0".minimal.override { targets = [ "armv7-linux-androideabi" "aarch64-linux-android" diff --git a/jni/Android.mk b/jni/Android.mk index 8597475da..e2867679c 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -23,7 +23,10 @@ LOCAL_C_INCLUDES := $(JNI_DIR)/utils/ LOCAL_LDLIBS := -llog LOCAL_STATIC_LIBRARIES := deltachat-core -LOCAL_CFLAGS := -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 +# -Werror flag is important to catch incompatibilities between the JNI bindings and the core. +# Otherwise passing a variable of different type such as char * instead of int +# causes only a -Wint-conversion warning. +LOCAL_CFLAGS := -Werror -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 LOCAL_CFLAGS += -Drestrict='' -D__EMX__ -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT -DHAVE_LRINTF -fno-math-errno LOCAL_CFLAGS += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -ffast-math -D__STDC_CONSTANT_MACROS diff --git a/jni/dc_wrapper.c b/jni/dc_wrapper.c index 01d01f21d..802fce7bb 100644 --- a/jni/dc_wrapper.c +++ b/jni/dc_wrapper.c @@ -1026,15 +1026,6 @@ JNIEXPORT jlong Java_com_b44t_messenger_DcContext_getProviderFromEmailWithDnsCPt } -JNIEXPORT jlong Java_com_b44t_messenger_DcContext_getHttpResponseCPtr(JNIEnv *env, jobject obj, jstring url) -{ - CHAR_REF(url); - jlong ret = (jlong)dc_get_http_response(get_dc_context(env, obj), urlPtr); - CHAR_UNREF(url); - return ret; -} - - /******************************************************************************* * DcEventEmitter ******************************************************************************/ @@ -1923,6 +1914,12 @@ JNIEXPORT jboolean Java_com_b44t_messenger_DcContact_isVerified(JNIEnv *env, job } +JNIEXPORT jint Java_com_b44t_messenger_DcContact_getVerifierId(JNIEnv *env, jobject obj) +{ + return dc_contact_get_verifier_id(get_dc_contact(env, obj)); +} + + /******************************************************************************* * DcLot ******************************************************************************/ @@ -2088,66 +2085,6 @@ JNIEXPORT jstring Java_com_b44t_messenger_DcProvider_getOverviewPage(JNIEnv *env } -/******************************************************************************* - * DcHttpResponse - ******************************************************************************/ - - -static dc_http_response_t* get_dc_http_response(JNIEnv *env, jobject obj) -{ - static jfieldID fid = 0; - if (fid==0) { - jclass cls = (*env)->GetObjectClass(env, obj); - fid = (*env)->GetFieldID(env, cls, "httpResponseCPtr", "J" /*Signature, J=long*/); - } - if (fid) { - return (dc_http_response_t*)(*env)->GetLongField(env, obj, fid); - } - return NULL; -} - - -JNIEXPORT void Java_com_b44t_messenger_DcHttpResponse_unrefHttpResponseCPtr(JNIEnv *env, jobject obj) -{ - dc_http_response_unref(get_dc_http_response(env, obj)); -} - - -JNIEXPORT jstring Java_com_b44t_messenger_DcHttpResponse_getMimetype(JNIEnv *env, jobject obj) -{ - char* temp = dc_http_response_get_mimetype(get_dc_http_response(env, obj)); - jstring ret = NULL; - if (temp) { - ret = JSTRING_NEW(temp); - } - dc_str_unref(temp); - return ret; -} - - -JNIEXPORT jstring Java_com_b44t_messenger_DcHttpResponse_getEncoding(JNIEnv *env, jobject obj) -{ - char* temp = dc_http_response_get_encoding(get_dc_http_response(env, obj)); - jstring ret = NULL; - if (temp) { - ret = JSTRING_NEW(temp); - } - dc_str_unref(temp); - return ret; -} - - -JNIEXPORT jbyteArray Java_com_b44t_messenger_DcHttpResponse_getBlob(JNIEnv *env, jobject obj) -{ - jbyteArray ret = NULL; - dc_http_response_t* http_response = get_dc_http_response(env, obj); - size_t ptr_size = dc_http_response_get_size(http_response); - uint8_t* ptr = dc_http_response_get_blob(http_response); - ret = ptr2jbyteArray(env, ptr, ptr_size); - dc_str_unref((char*)ptr); - return ret; -} - /******************************************************************************* * DcJsonrpcInstance ******************************************************************************/ diff --git a/jni/deltachat-core-rust b/jni/deltachat-core-rust index 8ee07e792..40d19f181 160000 --- a/jni/deltachat-core-rust +++ b/jni/deltachat-core-rust @@ -1 +1 @@ -Subproject commit 8ee07e7920b4288149bf1db6ec228a32efa1cd92 +Subproject commit 40d19f181db38b947620434c6094ce089708904c diff --git a/metadata/en-US/changelogs/6594.txt b/metadata/en-US/changelogs/6594.txt new file mode 100644 index 000000000..a0ea3e8f0 --- /dev/null +++ b/metadata/en-US/changelogs/6594.txt @@ -0,0 +1,5 @@ +* use image editor for avatar selection when possible +* improve loading screen in dark mode +* update "verified icon" +* add Luri Bakhtiari translation, update other translations +* lots of bug fixes diff --git a/metadata/en-US/changelogs/6600.txt b/metadata/en-US/changelogs/6600.txt new file mode 100644 index 000000000..a0ea3e8f0 --- /dev/null +++ b/metadata/en-US/changelogs/6600.txt @@ -0,0 +1,5 @@ +* use image editor for avatar selection when possible +* improve loading screen in dark mode +* update "verified icon" +* add Luri Bakhtiari translation, update other translations +* lots of bug fixes diff --git a/res/drawable/ic_verified.xml b/res/drawable/ic_verified.xml index a92c98538..c8b706df6 100644 --- a/res/drawable/ic_verified.xml +++ b/res/drawable/ic_verified.xml @@ -3,6 +3,6 @@ - diff --git a/res/drawable/reaction_pill_background.xml b/res/drawable/reaction_pill_background.xml new file mode 100644 index 000000000..bad81d54c --- /dev/null +++ b/res/drawable/reaction_pill_background.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/drawable/reaction_pill_background_selected.xml b/res/drawable/reaction_pill_background_selected.xml new file mode 100644 index 000000000..b52af9b3d --- /dev/null +++ b/res/drawable/reaction_pill_background_selected.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/layout/audio_view.xml b/res/layout/audio_view.xml index 6253cbe7b..15786f1c4 100644 --- a/res/layout/audio_view.xml +++ b/res/layout/audio_view.xml @@ -96,4 +96,15 @@ - \ No newline at end of file + + + + diff --git a/res/layout/conversation_input_panel.xml b/res/layout/conversation_input_panel.xml index 963f3924a..fb5da9742 100644 --- a/res/layout/conversation_input_panel.xml +++ b/res/layout/conversation_input_panel.xml @@ -40,6 +40,10 @@ @@ -50,10 +54,8 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:minHeight="40dp" - android:paddingLeft="12dp" - android:paddingStart="12dp" + android:paddingLeft="6dp" android:paddingRight="6dp" - android:paddingEnd="6dp" android:background="@drawable/touch_highlight_background" android:contentDescription="@string/menu_toggle_keyboard" /> diff --git a/res/layout/conversation_item_footer.xml b/res/layout/conversation_item_footer.xml index 170f60666..4ac5a5791 100644 --- a/res/layout/conversation_item_footer.xml +++ b/res/layout/conversation_item_footer.xml @@ -15,7 +15,7 @@ android:src="@drawable/msg_encr_out" android:visibility="gone" android:layout_gravity="center_vertical|end" - android:contentDescription="@string/chat_input_placeholder" + android:importantForAccessibility="no" tools:visibility="visible"/> + android:layout="@layout/conversation_item_sticker" /> @@ -200,25 +201,17 @@ android:alpha="0.7" app:footer_text_color="?conversation_item_incoming_text_secondary_color"/> - - + + diff --git a/res/layout/conversation_item_sent.xml b/res/layout/conversation_item_sent.xml index 9cc41ebcb..6defb23cc 100644 --- a/res/layout/conversation_item_sent.xml +++ b/res/layout/conversation_item_sent.xml @@ -100,7 +100,7 @@ android:id="@+id/sticker_view_stub" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout="@layout/conversation_item_received_sticker" /> + android:layout="@layout/conversation_item_sticker" /> @@ -177,26 +178,18 @@ android:clipToPadding="false" app:footer_text_color="?attr/conversation_item_outgoing_text_secondary_color"/> - - + + - diff --git a/res/layout/conversation_item_received_sticker.xml b/res/layout/conversation_item_sticker.xml similarity index 85% rename from res/layout/conversation_item_received_sticker.xml rename to res/layout/conversation_item_sticker.xml index 6e24028f8..f597ffdc0 100644 --- a/res/layout/conversation_item_received_sticker.xml +++ b/res/layout/conversation_item_sticker.xml @@ -4,4 +4,4 @@ android:id="@+id/image_view" android:layout_width="@dimen/media_bubble_sticker_dimens" android:layout_height="@dimen/media_bubble_sticker_dimens" - android:contentDescription="@string/image" /> + android:contentDescription="@string/sticker" /> diff --git a/res/layout/profile_settings_item.xml b/res/layout/profile_settings_item.xml index 37b3c06b4..1a57eec3a 100644 --- a/res/layout/profile_settings_item.xml +++ b/res/layout/profile_settings_item.xml @@ -10,6 +10,7 @@ diff --git a/res/layout/reaction_recipient_item.xml b/res/layout/reaction_recipient_item.xml new file mode 100644 index 000000000..e4944fc9b --- /dev/null +++ b/res/layout/reaction_recipient_item.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/reactions_details_fragment.xml b/res/layout/reactions_details_fragment.xml new file mode 100644 index 000000000..cb161704b --- /dev/null +++ b/res/layout/reactions_details_fragment.xml @@ -0,0 +1,7 @@ + diff --git a/res/layout/reactions_pill.xml b/res/layout/reactions_pill.xml new file mode 100644 index 000000000..c6f4cddf6 --- /dev/null +++ b/res/layout/reactions_pill.xml @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/res/layout/recent_photo_view_item.xml b/res/layout/recent_photo_view_item.xml index b59d2ab48..d713a2cec 100644 --- a/res/layout/recent_photo_view_item.xml +++ b/res/layout/recent_photo_view_item.xml @@ -11,7 +11,8 @@ + android:scaleType="centerCrop" + android:contentDescription="@string/image"/> diff --git a/res/layout/registration_activity.xml b/res/layout/registration_activity.xml index c22ac042a..66c31e899 100644 --- a/res/layout/registration_activity.xml +++ b/res/layout/registration_activity.xml @@ -151,7 +151,7 @@ app:layout_constraintStart_toEndOf="@id/advanced_icon" app:layout_constraintTop_toBottomOf="@id/no_servers_hint" /> - + + diff --git a/res/layout/webxdc_compact_view.xml b/res/layout/webxdc_compact_view.xml index a1357eae4..5aa6a6622 100644 --- a/res/layout/webxdc_compact_view.xml +++ b/res/layout/webxdc_compact_view.xml @@ -1,6 +1,7 @@ - + android:scaleType="centerCrop" + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize10Percent" /> - + + android:scaleType="centerCrop" + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize10Percent" /> + + + + diff --git a/res/raw/webxdc.js b/res/raw/webxdc.js index 014ad4144..2b99c2760 100644 --- a/res/raw/webxdc.js +++ b/res/raw/webxdc.js @@ -45,15 +45,13 @@ window.webxdc = (() => { if (!message.file && !message.text) { return Promise.reject("sendToChat() error: file or text missing"); } - /** @type {(file: Blob) => Promise} */ + const blobToBase64 = (file) => { const dataStart = ";base64,"; return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { - /** @type {string} */ - //@ts-ignore let data = reader.result; resolve(data.slice(data.indexOf(dataStart) + dataStart.length)); }; @@ -64,7 +62,6 @@ window.webxdc = (() => { data.text = message.text; } - /** @type {{file_name: string, file_message: string} | null} */ if (message.file) { let base64content; if (!message.file.name) { @@ -78,20 +75,12 @@ window.webxdc = (() => { return Promise.reject("sendToChat() error: only one of blob, base64 or plainText allowed"); } - // @ts-ignore - needed because typescript imagines that blob would not exist if (message.file.blob instanceof Blob) { - // @ts-ignore - needed because typescript imagines that blob would not exist base64content = await blobToBase64(message.file.blob); - // @ts-ignore - needed because typescript imagines that base64 would not exist } else if (typeof message.file.base64 === "string") { - // @ts-ignore - needed because typescript imagines that base64 would not exist base64content = message.file.base64; - // @ts-ignore - needed because typescript imagines that plainText would not exist } else if (typeof message.file.plainText === "string") { - base64content = await blobToBase64( - // @ts-ignore - needed because typescript imagines that plainText would not exist - new Blob([message.file.plainText]) - ); + base64content = await blobToBase64(new Blob([message.file.plainText])); } else { return Promise.reject("sendToChat() error: none of blob, base64 or plainText set correctly"); } diff --git a/res/raw/webxdc_wrapper.html b/res/raw/webxdc_wrapper.html index ab20e2fa1..9dd7898c6 100644 --- a/res/raw/webxdc_wrapper.html +++ b/res/raw/webxdc_wrapper.html @@ -1,6 +1,7 @@ + + diff --git a/res/xml/preferences_advanced.xml b/res/xml/preferences_advanced.xml index d43b1d39a..78888de4c 100644 --- a/res/xml/preferences_advanced.xml +++ b/res/xml/preferences_advanced.xml @@ -57,6 +57,12 @@ android:key="pref_incognito_keyboard" android:summary="@string/pref_incognito_keyboard_explain" android:title="@string/pref_incognito_keyboard" /> + + diff --git a/scripts/rust-toolchain b/scripts/rust-toolchain index 832e9afb6..0834888f5 100644 --- a/scripts/rust-toolchain +++ b/scripts/rust-toolchain @@ -1 +1 @@ -1.70.0 +1.72.0 diff --git a/src/com/b44t/messenger/DcChat.java b/src/com/b44t/messenger/DcChat.java index 4b4522a39..e87a407e7 100644 --- a/src/com/b44t/messenger/DcChat.java +++ b/src/com/b44t/messenger/DcChat.java @@ -17,7 +17,10 @@ public class DcChat { public final static int DC_CHAT_VISIBILITY_ARCHIVED = 1; public final static int DC_CHAT_VISIBILITY_PINNED = 2; - public DcChat(long chatCPtr) { + private int accountId; + + public DcChat(int accountId, long chatCPtr) { + this.accountId = accountId; this.chatCPtr = chatCPtr; } @@ -27,6 +30,7 @@ public class DcChat { chatCPtr = 0; } + public int getAccountId () { return accountId; } public native int getId (); public native int getType (); public native int getVisibility (); diff --git a/src/com/b44t/messenger/DcChatlist.java b/src/com/b44t/messenger/DcChatlist.java index 0479abb95..4a250d15d 100644 --- a/src/com/b44t/messenger/DcChatlist.java +++ b/src/com/b44t/messenger/DcChatlist.java @@ -2,7 +2,10 @@ package com.b44t.messenger; public class DcChatlist { - public DcChatlist(long chatlistCPtr) { + private int accountId; + + public DcChatlist(int accountId, long chatlistCPtr) { + this.accountId = accountId; this.chatlistCPtr = chatlistCPtr; } @@ -12,9 +15,10 @@ public class DcChatlist { chatlistCPtr = 0; } + public int getAccountId() { return accountId; } public native int getCnt (); public native int getChatId (int index); - public DcChat getChat (int index) { return new DcChat(getChatCPtr(index)); } + public DcChat getChat (int index) { return new DcChat(accountId, getChatCPtr(index)); } public native int getMsgId (int index); public DcMsg getMsg (int index) { return new DcMsg(getMsgCPtr(index)); } public DcLot getSummary(int index, DcChat chat) { return new DcLot(getSummaryCPtr(index, chat==null? 0 : chat.getChatCPtr())); } diff --git a/src/com/b44t/messenger/DcContact.java b/src/com/b44t/messenger/DcContact.java index 3fc164b3f..a8353e6c1 100644 --- a/src/com/b44t/messenger/DcContact.java +++ b/src/com/b44t/messenger/DcContact.java @@ -58,6 +58,7 @@ public class DcContact { public native boolean wasSeenRecently(); public native boolean isBlocked (); public native boolean isVerified (); + public native int getVerifierId (); // working with raw c-data private long contactCPtr; // CAVE: the name is referenced in the JNI diff --git a/src/com/b44t/messenger/DcContext.java b/src/com/b44t/messenger/DcContext.java index e0314d584..33736f654 100644 --- a/src/com/b44t/messenger/DcContext.java +++ b/src/com/b44t/messenger/DcContext.java @@ -11,6 +11,7 @@ public class DcContext { public final static int DC_EVENT_ERROR = 400; public final static int DC_EVENT_ERROR_SELF_NOT_IN_GROUP = 410; public final static int DC_EVENT_MSGS_CHANGED = 2000; + public final static int DC_EVENT_REACTIONS_CHANGED = 2001; public final static int DC_EVENT_INCOMING_MSG = 2005; public final static int DC_EVENT_MSGS_NOTICED = 2008; public final static int DC_EVENT_MSG_DELIVERED = 2010; @@ -101,10 +102,6 @@ public class DcContext { @Override protected void finalize() throws Throwable { super.finalize(); - unref(); - } - - public void unref() { if (contextCPtr != 0) { unrefContextCPtr(); contextCPtr = 0; @@ -161,8 +158,8 @@ public class DcContext { public native String getContactEncrInfo (int contact_id); public native boolean deleteContact (int id); public native int addAddressBook (String adrbook); - public DcChatlist getChatlist (int listflags, String query, int queryId) { return new DcChatlist(getChatlistCPtr(listflags, query, queryId)); } - public DcChat getChat (int chat_id) { return new DcChat(getChatCPtr(chat_id)); } + public DcChatlist getChatlist (int listflags, String query, int queryId) { return new DcChatlist(getAccountId(), getChatlistCPtr(listflags, query, queryId)); } + public DcChat getChat (int chat_id) { return new DcChat(getAccountId(), getChatCPtr(chat_id)); } public native String getChatEncrInfo (int chat_id); public native void markseenMsgs (int msg_ids[]); public native void marknoticedChat (int chat_id); @@ -216,7 +213,6 @@ public class DcContext { public DcArray getLocations (int chat_id, int contact_id, long timestamp_start, long timestamp_end) { return new DcArray(getLocationsCPtr(chat_id, contact_id, timestamp_start, timestamp_end)); } public native void deleteAllLocations (); public DcProvider getProviderFromEmailWithDns (String email) { long cptr = getProviderFromEmailWithDnsCPtr(email); return cptr!=0 ? new DcProvider(cptr) : null; } - public DcHttpResponse getHttpResponse (String url) { long cptr = getHttpResponseCPtr(url); return cptr!=0 ? new DcHttpResponse(cptr) : null; } public String getNameNAddr() { String displayname = getConfig("displayname"); @@ -256,5 +252,4 @@ public class DcContext { private native long checkQrCPtr (String qr); private native long getProviderFromEmailWithDnsCPtr (String addr); private native long newBackupProviderCPtr(); - private native long getHttpResponseCPtr(String url); } diff --git a/src/com/b44t/messenger/DcHttpResponse.java b/src/com/b44t/messenger/DcHttpResponse.java deleted file mode 100644 index f41f9cd1c..000000000 --- a/src/com/b44t/messenger/DcHttpResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.b44t.messenger; - -public class DcHttpResponse { - - public DcHttpResponse(long httpResponseCPtr) { - this.httpResponseCPtr = httpResponseCPtr; - } - - @Override protected void finalize() throws Throwable { - super.finalize(); - unrefHttpResponseCPtr(); - httpResponseCPtr = 0; - } - - public native String getMimetype (); - public native String getEncoding (); - public native byte[] getBlob (); - - // working with raw c-data - private long httpResponseCPtr; // CAVE: the name is referenced in the JNI - private native void unrefHttpResponseCPtr(); -} diff --git a/src/com/b44t/messenger/rpc/HttpResponse.java b/src/com/b44t/messenger/rpc/HttpResponse.java index bd07a1bb5..ccc6b8c6b 100644 --- a/src/com/b44t/messenger/rpc/HttpResponse.java +++ b/src/com/b44t/messenger/rpc/HttpResponse.java @@ -4,11 +4,11 @@ import android.util.Base64; public class HttpResponse { // base64-encoded response body. - private String blob; + private final String blob; // MIME type, e.g. "text/plain" or "text/html". - private String mimetype; + private final String mimetype; // Encoding, e.g. "utf-8". - private String encoding; + private final String encoding; public HttpResponse(String blob, String mimetype, String encoding) { this.blob = blob; diff --git a/src/com/b44t/messenger/rpc/Reaction.java b/src/com/b44t/messenger/rpc/Reaction.java new file mode 100644 index 000000000..493cb4ec5 --- /dev/null +++ b/src/com/b44t/messenger/rpc/Reaction.java @@ -0,0 +1,39 @@ +package com.b44t.messenger.rpc; + +import androidx.annotation.Nullable; + +public class Reaction { + // The reaction emoji string. + private final String emoji; + // The count of users that have reacted with this reaction. + private final int count; + // true if self-account reacted with this reaction, false otherwise. + private final boolean isFromSelf; + + public Reaction(String emoji, int count, boolean isFromSelf) { + this.emoji = emoji; + this.count = count; + this.isFromSelf = isFromSelf; + } + + public String getEmoji() { + return emoji; + } + + public int getCount() { + return count; + } + + public boolean isFromSelf() { + return isFromSelf; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof Reaction) { + Reaction reaction = (Reaction) obj; + return emoji.equals(reaction.getEmoji()) && count == reaction.getCount() && isFromSelf == reaction.isFromSelf(); + } + return false; + } +} diff --git a/src/com/b44t/messenger/rpc/Reactions.java b/src/com/b44t/messenger/rpc/Reactions.java new file mode 100644 index 000000000..7b8849868 --- /dev/null +++ b/src/com/b44t/messenger/rpc/Reactions.java @@ -0,0 +1,26 @@ +package com.b44t.messenger.rpc; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Reactions { + // Map from a contact to it's reaction to message. + private final HashMap reactionsByContact; + // Unique reactions, sorted in descending order. + private final ArrayList reactions; + + public Reactions(HashMap reactionsByContact, ArrayList reactions) { + this.reactionsByContact = reactionsByContact; + this.reactions = reactions; + } + + public Map getReactionsByContact() { + return reactionsByContact; + } + + public List getReactions() { + return reactions; + } +} diff --git a/src/com/b44t/messenger/rpc/Rpc.java b/src/com/b44t/messenger/rpc/Rpc.java index 8f255c68c..ae8c4e61a 100644 --- a/src/com/b44t/messenger/rpc/Rpc.java +++ b/src/com/b44t/messenger/rpc/Rpc.java @@ -8,7 +8,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -41,7 +40,7 @@ public class Rpc { } else if (response.result != null) { future.set(response.result); } else { - future.setException(new RpcException("Got JSON-RPC response witout result or error: " + jsonResponse)); + future.setException(new RpcException("Got JSON-RPC response without result or error: " + jsonResponse)); } } @@ -100,12 +99,16 @@ public class Rpc { return gson.fromJson(getResult("get_http_response", accountId, url), HttpResponse.class); } + public Reactions getMsgReactions(int accountId, int msgId) throws RpcException { + return gson.fromJson(getResult("get_message_reactions", accountId, msgId), Reactions.class); + } + private static class Request { - public String jsonrpc = "2.0"; - public String method; - public Object[] params; - public int id; + private final String jsonrpc = "2.0"; + public final String method; + public final Object[] params; + public final int id; public Request(String method, Object[] params, int id) { this.method = method; @@ -115,9 +118,9 @@ public class Rpc { } private static class Response { - public int id = 0; - public JsonElement result; - public JsonElement error; + public final int id; + public final JsonElement result; + public final JsonElement error; public Response(int id, JsonElement result, JsonElement error) { this.id = id; diff --git a/src/com/b44t/messenger/util/concurrent/SettableFuture.java b/src/com/b44t/messenger/util/concurrent/SettableFuture.java index 1255b907b..003f93b8a 100644 --- a/src/com/b44t/messenger/util/concurrent/SettableFuture.java +++ b/src/com/b44t/messenger/util/concurrent/SettableFuture.java @@ -3,7 +3,6 @@ package com.b44t.messenger.util.concurrent; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 98227cc7f..e34f6646c 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.notifications.NotificationCenter; import org.thoughtcrime.securesms.util.AndroidSignalProtocolLogger; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; +import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.SignalProtocolLoggerProvider; import java.io.File; @@ -133,6 +134,16 @@ public class ApplicationContext extends MultiDexApplication { dcAccounts.getAccount(accountId).setConfig(CONFIG_VERIFIED_ONE_ON_ONE_CHATS, "1"); } + // migrating chat backgrounds, added 04/10/23, can be removed after some versions + String backgroundImagePath = Prefs.getStringPreference(this, "pref_chat_background", ""); + if (!backgroundImagePath.isEmpty()) { + for (int accId : dcAccounts.getAll()) { + Prefs.setBackgroundImagePath(this, accId, backgroundImagePath); + } + Prefs.setStringPreference(this, "pref_chat_background", ""); + } + // /migrating chat backgrounds + // set translations before starting I/O to avoid sending untranslated MDNs (issue #2288) DcHelper.setStockTranslations(this); diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 89c669678..75e96d619 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -17,6 +17,7 @@ */ package org.thoughtcrime.securesms; +import android.Manifest; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; @@ -30,6 +31,7 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.fragment.app.Fragment; @@ -49,7 +51,6 @@ import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment; import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment; import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment; import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.qr.BackupTransferActivity; @@ -65,9 +66,6 @@ import org.thoughtcrime.securesms.util.ScreenLockUtil; public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarActivity implements SharedPreferences.OnSharedPreferenceChangeListener { - @SuppressWarnings("unused") - private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName(); - private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile"; private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications"; private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance"; @@ -79,15 +77,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA public static final int REQUEST_CODE_SET_BACKGROUND = 11; - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - - @Override - protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); - } - @Override protected void onCreate(Bundle icicle, boolean ready) { //noinspection ConstantConditions @@ -98,13 +87,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA } } - @Override - public void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -137,7 +119,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA DynamicTheme.setDefaultDayNightMode(this); recreate(); } else if (key.equals(Prefs.LANGUAGE_PREF)) { - recreate(); + finish(); } } @@ -252,7 +234,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA } private class CategoryClickListener implements Preference.OnPreferenceClickListener { - private String category; + private final String category; CategoryClickListener(String category) { this.category = category; @@ -264,7 +246,17 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA switch (category) { case PREFERENCE_CATEGORY_NOTIFICATIONS: - fragment = new NotificationsPreferenceFragment(); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity()); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || notificationManager.areNotificationsEnabled()) { + fragment = new NotificationsPreferenceFragment(); + } else { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.notifications_disabled) + .setMessage(R.string.perm_explain_access_to_notifications_denied) + .setPositiveButton(R.string.perm_continue, (dialog, which) -> getActivity().startActivity(Permissions.getApplicationSettingsIntent(getActivity()))) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } break; case PREFERENCE_CATEGORY_CONNECTIVITY: startActivity(new Intent(getActivity(), ConnectivityActivity.class)); diff --git a/src/org/thoughtcrime/securesms/AttachContactActivity.java b/src/org/thoughtcrime/securesms/AttachContactActivity.java new file mode 100644 index 000000000..1cdbf9c81 --- /dev/null +++ b/src/org/thoughtcrime/securesms/AttachContactActivity.java @@ -0,0 +1,28 @@ +package org.thoughtcrime.securesms; + +import android.content.Intent; + +import com.b44t.messenger.DcContext; + +import org.thoughtcrime.securesms.connect.DcHelper; + +public class AttachContactActivity extends ContactSelectionActivity { + + public static final String NAME_EXTRA = "name_extra"; + public static final String ADDR_EXTRA = "addr_extra"; + + @Override + public void onContactSelected(int specialId, String addr) { + String name = ""; + DcContext dcContext = DcHelper.getContext(this); + int contactId = dcContext.lookupContactIdByAddr(addr); + if (contactId != 0) { + name = dcContext.getContact(contactId).getDisplayName(); + } + Intent intent = new Intent(); + intent.putExtra(NAME_EXTRA, name); + intent.putExtra(ADDR_EXTRA, addr); + setResult(RESULT_OK, intent); + finish(); + } +} diff --git a/src/org/thoughtcrime/securesms/BaseActionBarActivity.java b/src/org/thoughtcrime/securesms/BaseActionBarActivity.java index d63654125..6297cd58c 100644 --- a/src/org/thoughtcrime/securesms/BaseActionBarActivity.java +++ b/src/org/thoughtcrime/securesms/BaseActionBarActivity.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms; +import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -15,6 +16,8 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; +import org.thoughtcrime.securesms.util.DynamicLanguage; +import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Prefs; import java.lang.reflect.Field; @@ -25,9 +28,17 @@ public abstract class BaseActionBarActivity extends AppCompatActivity { private static final String TAG = BaseActionBarActivity.class.getSimpleName(); public static final String LOCALE_EXTRA = "locale_extra"; + protected final DynamicLanguage dynamicLanguage = new DynamicLanguage(); + protected DynamicTheme dynamicTheme = new DynamicTheme(); + + protected void onPreCreate() { + dynamicTheme.onCreate(this); + dynamicLanguage.onCreate(this); + } @Override protected void onCreate(Bundle savedInstanceState) { + onPreCreate(); if (BaseActivity.isMenuWorkaroundRequired()) { forceOverflowMenu(); } @@ -38,6 +49,15 @@ public abstract class BaseActionBarActivity extends AppCompatActivity { protected void onResume() { super.onResume(); initializeScreenshotSecurity(); + dynamicTheme.onResume(this); + dynamicLanguage.onResume(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + // on orientation changes locale is reset in the context/activity so set locale as onCreate() + dynamicLanguage.onCreate(this); } @Override diff --git a/src/org/thoughtcrime/securesms/BaseConversationItem.java b/src/org/thoughtcrime/securesms/BaseConversationItem.java index 6d0d36e38..35d353fbd 100644 --- a/src/org/thoughtcrime/securesms/BaseConversationItem.java +++ b/src/org/thoughtcrime/securesms/BaseConversationItem.java @@ -13,9 +13,11 @@ import androidx.appcompat.app.AlertDialog; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcMsg; +import com.b44t.messenger.rpc.Rpc; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.Util; import java.util.HashSet; import java.util.Set; @@ -23,7 +25,7 @@ import java.util.Set; public abstract class BaseConversationItem extends LinearLayout implements BindableConversationItem { - static long PULSE_HIGHLIGHT_MILLIS = 500; + static final long PULSE_HIGHLIGHT_MILLIS = 500; protected DcMsg messageRecord; protected DcChat dcChat; @@ -32,6 +34,7 @@ public abstract class BaseConversationItem extends LinearLayout protected final Context context; protected final DcContext dcContext; protected Recipient conversationRecipient; + protected final Rpc rpc; protected @NonNull Set batchSelected = new HashSet<>(); @@ -41,6 +44,7 @@ public abstract class BaseConversationItem extends LinearLayout super(context, attrs); this.context = context; this.dcContext = DcHelper.getContext(context); + this.rpc = DcHelper.getRpc(context); } protected void bind(@NonNull DcMsg messageRecord, @@ -81,6 +85,8 @@ public abstract class BaseConversationItem extends LinearLayout || messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED); } + protected void onAccessibilityClick() {} + protected class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener { @Override @@ -99,7 +105,7 @@ public abstract class BaseConversationItem extends LinearLayout } protected class ClickListener implements View.OnClickListener { - private OnClickListener parent; + private final OnClickListener parent; ClickListener(@Nullable OnClickListener parent) { this.parent = parent; @@ -107,6 +113,9 @@ public abstract class BaseConversationItem extends LinearLayout public void onClick(View v) { if (!shouldInterceptClicks(messageRecord) && parent != null) { + if (batchSelected.isEmpty() && Util.isTouchExplorationEnabled(context)) { + BaseConversationItem.this.onAccessibilityClick(); + } parent.onClick(v); } else if (messageRecord.isFailed()) { View view = View.inflate(context, R.layout.message_details_view, null); diff --git a/src/org/thoughtcrime/securesms/BindableConversationItem.java b/src/org/thoughtcrime/securesms/BindableConversationItem.java index 39cc245ad..c59d9a73e 100644 --- a/src/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/src/org/thoughtcrime/securesms/BindableConversationItem.java @@ -9,7 +9,6 @@ import com.b44t.messenger.DcMsg; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; -import java.util.List; import java.util.Locale; import java.util.Set; @@ -30,5 +29,6 @@ public interface BindableConversationItem extends Unbindable { void onQuoteClicked(DcMsg messageRecord); void onShowFullClicked(DcMsg messageRecord); void onDownloadClicked(DcMsg messageRecord); + void onReactionClicked(DcMsg messageRecord); } } diff --git a/src/org/thoughtcrime/securesms/BlockedAndShareContactsActivity.java b/src/org/thoughtcrime/securesms/BlockedContactsActivity.java similarity index 65% rename from src/org/thoughtcrime/securesms/BlockedAndShareContactsActivity.java rename to src/org/thoughtcrime/securesms/BlockedContactsActivity.java index 43d2332db..261cc22c8 100644 --- a/src/org/thoughtcrime/securesms/BlockedAndShareContactsActivity.java +++ b/src/org/thoughtcrime/securesms/BlockedContactsActivity.java @@ -1,11 +1,9 @@ package org.thoughtcrime.securesms; -import android.content.Intent; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import androidx.appcompat.app.AlertDialog; @@ -26,42 +24,17 @@ import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter; import org.thoughtcrime.securesms.contacts.ContactSelectionListItem; import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.ViewUtil; -import java.util.Objects; - -public class BlockedAndShareContactsActivity extends PassphraseRequiredActionBarActivity { - - public static final String SHOW_ONLY_BLOCKED_EXTRA = "show_only_blocked"; - public static final String SHARE_CONTACT_NAME_EXTRA = "share_contact_name"; - public static final String SHARE_CONTACT_MAIL_EXTRA = "share_contact_mail"; - - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - - @Override - public void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); - } +public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity { @Override public void onCreate(Bundle bundle, boolean ready) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); - boolean showOnlyBlocked = getIntent().getBooleanExtra(SHOW_ONLY_BLOCKED_EXTRA, false); - getSupportActionBar().setTitle(showOnlyBlocked ? R.string.pref_blocked_contacts : R.string.contacts_headline); + getSupportActionBar().setTitle(R.string.pref_blocked_contacts); initFragment(android.R.id.content, new BlockedAndShareContactsFragment(), null, getIntent().getExtras()); } - @Override - public void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -80,8 +53,6 @@ public class BlockedAndShareContactsActivity extends PassphraseRequiredActionBar private RecyclerView recyclerView; private TextView emptyStateView; - private boolean showOnlyBlocked; - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false); @@ -95,7 +66,6 @@ public class BlockedAndShareContactsActivity extends PassphraseRequiredActionBar @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); - showOnlyBlocked = getArguments().getBoolean(SHOW_ONLY_BLOCKED_EXTRA, false); getLoaderManager().initLoader(0, null, this); } @@ -116,7 +86,7 @@ public class BlockedAndShareContactsActivity extends PassphraseRequiredActionBar @Override public Loader onCreateLoader(int id, Bundle args) { - return new DcContactsLoader(getActivity(), showOnlyBlocked ? -1 : DcContext.DC_GCL_ADD_SELF, null, false, showOnlyBlocked); + return new DcContactsLoader(getActivity(), -1, null, false, true, true); } @Override @@ -152,15 +122,11 @@ public class BlockedAndShareContactsActivity extends PassphraseRequiredActionBar @Override public void onItemClick(ContactSelectionListItem item, boolean handleActionMode) { - if(showOnlyBlocked) { - new AlertDialog.Builder(getActivity()) - .setMessage(R.string.ask_unblock_contact) - .setCancelable(true) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.menu_unblock_contact, (dialog, which) -> unblockContact(item.getContactId())).show(); - } else { - shareContact(item.getName(), item.getNumber()); - } + new AlertDialog.Builder(getActivity()) + .setMessage(R.string.ask_unblock_contact) + .setCancelable(true) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.menu_unblock_contact, (dialog, which) -> unblockContact(item.getContactId())).show(); } private void unblockContact(int contactId) { @@ -169,19 +135,8 @@ public class BlockedAndShareContactsActivity extends PassphraseRequiredActionBar restartLoader(); } - private void shareContact(String name, String mail) { - Intent intent = new Intent(); - intent.putExtra(BlockedAndShareContactsActivity.SHARE_CONTACT_NAME_EXTRA, name); - intent.putExtra(BlockedAndShareContactsActivity.SHARE_CONTACT_MAIL_EXTRA, mail); - FragmentActivity activity = Objects.requireNonNull(getActivity()); - activity.setResult(RESULT_OK, intent); - activity.finish(); - } - @Override - public void onItemLongClick(ContactSelectionListItem view) { - // Not needed - } + public void onItemLongClick(ContactSelectionListItem view) {} } } diff --git a/src/org/thoughtcrime/securesms/ConnectivityActivity.java b/src/org/thoughtcrime/securesms/ConnectivityActivity.java index 81207a98f..ebf0cc06f 100644 --- a/src/org/thoughtcrime/securesms/ConnectivityActivity.java +++ b/src/org/thoughtcrime/securesms/ConnectivityActivity.java @@ -35,7 +35,8 @@ public class ConnectivityActivity extends WebViewActivity implements DcEventCent } private void refresh() { - String connectivityHtml = DcHelper.getContext(this).getConnectivityHtml(); + final String connectivityHtml = DcHelper.getContext(this).getConnectivityHtml() + .replace("", " html { color-scheme: dark light; }"); webView.loadDataWithBaseURL(null, connectivityHtml, "text/html", "utf-8", null); } diff --git a/src/org/thoughtcrime/securesms/ContactMultiSelectionActivity.java b/src/org/thoughtcrime/securesms/ContactMultiSelectionActivity.java index d7090f5f1..81ae4d653 100644 --- a/src/org/thoughtcrime/securesms/ContactMultiSelectionActivity.java +++ b/src/org/thoughtcrime/securesms/ContactMultiSelectionActivity.java @@ -33,9 +33,6 @@ import java.util.List; */ public class ContactMultiSelectionActivity extends ContactSelectionActivity { - @SuppressWarnings("unused") - private final static String TAG = ContactMultiSelectionActivity.class.getSimpleName(); - @Override protected void onCreate(Bundle icicle, boolean ready) { getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true); @@ -65,9 +62,6 @@ public class ContactMultiSelectionActivity extends ContactSelectionActivity { saveSelection(); finish(); return true; - case android.R.id.home: - finish(); - return true; } return false; diff --git a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java index 62e6cb825..cff905c4b 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java @@ -17,11 +17,10 @@ package org.thoughtcrime.securesms; import android.os.Bundle; +import android.view.MenuItem; import org.thoughtcrime.securesms.components.ContactFilterToolbar; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.ViewUtil; /** @@ -35,17 +34,14 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB { private static final String TAG = ContactSelectionActivity.class.getSimpleName(); - private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - protected ContactSelectionListFragment contactsFragment; private ContactFilterToolbar toolbar; @Override protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); + dynamicTheme = new DynamicNoActionBarTheme(); + super.onPreCreate(); } @Override @@ -57,13 +53,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB initializeSearch(); } - @Override - public void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - protected ContactFilterToolbar getToolbar() { return toolbar; } @@ -88,6 +77,17 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB toolbar.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter)); } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + super.onOptionsItemSelected(item); + + switch (item.getItemId()) { + case android.R.id.home: super.onBackPressed(); return true; + } + + return false; + } + @Override public void onContactSelected(int specialId, String number) {} diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java index 4c0791403..fbfb26495 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -77,12 +77,11 @@ public class ContactSelectionListFragment extends Fragment implements LoaderManager.LoaderCallbacks, DcEventCenter.DcEventDelegate { - @SuppressWarnings("unused") private static final String TAG = ContactSelectionListFragment.class.getSimpleName(); public static final String MULTI_SELECT = "multi_select"; public static final String SELECT_VERIFIED_EXTRA = "select_verified"; - public static final String FROM_SHARE_ACTIVITY_EXTRA = "from_share_activity"; + public static final String ALLOW_CREATION = "allow_creation"; public static final String PRESELECTED_CONTACTS = "preselected_contacts"; private DcContext dcContext; @@ -257,10 +256,6 @@ public class ContactSelectionListFragment extends Fragment return getActivity().getIntent().getBooleanExtra(SELECT_VERIFIED_EXTRA, false); } - private boolean isFromShareActivity() { - return getActivity().getIntent().getBooleanExtra(FROM_SHARE_ACTIVITY_EXTRA, false); - } - private void initializeCursor() { ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(), GlideApp.with(this), @@ -282,12 +277,15 @@ public class ContactSelectionListFragment extends Fragment @Override public Loader onCreateLoader(int id, Bundle args) { - boolean addCreateGroupLinks = isFromShareActivity() || isRelayingMessageContent(getActivity()) ? false : !isMulti(); + boolean allowCreation = getActivity().getIntent().getBooleanExtra(ALLOW_CREATION, true); + boolean addCreateContactLink = allowCreation; + boolean addCreateGroupLinks = allowCreation && !isRelayingMessageContent(getActivity()) && !isMulti(); + int listflags = DcContext.DC_GCL_ADD_SELF; if(isSelectVerfied()) { listflags = DcContext.DC_GCL_VERIFIED_ONLY; } - return new DcContactsLoader(getActivity(), listflags, cursorFilter, addCreateGroupLinks, false); + return new DcContactsLoader(getActivity(), listflags, cursorFilter, addCreateGroupLinks, addCreateContactLink, false); } @Override diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index b1daeda69..cf415364c 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -105,7 +105,6 @@ import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.scribbles.ScribbleActivity; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Prefs; @@ -194,23 +193,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ApplicationContext context; private Recipient recipient; private DcContext dcContext; - private DcChat dcChat = new DcChat(0); + private DcChat dcChat = new DcChat(0, 0); private int chatId; private final boolean isSecureText = true; private boolean isDefaultSms = true; private boolean isSecurityInitialized = false; private boolean successfulForwardingAttempt = false; - - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - - @Override - protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); - } - @Override protected void onCreate(Bundle state, boolean ready) { this.context = ApplicationContext.getInstance(getApplicationContext()); @@ -305,16 +294,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity ConversationListRelayingActivity.finishActivity(); } - @Override - protected void onStart() { - super.onStart(); - } - @Override protected void onResume() { super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); quickAttachmentDrawer.onResume(); initializeEnabledCheck(); @@ -340,11 +322,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity AudioSlidePlayer.stopAll(); } - @Override - protected void onStop() { - super.onStop(); - } - @Override public void onConfigurationChanged(Configuration newConfig) { Log.i(TAG, "onConfigurationChanged(" + newConfig.orientation + ")"); @@ -870,19 +847,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity composeText.setOnClickListener(composeKeyPressedListener); composeText.setOnFocusChangeListener(composeKeyPressedListener); - if (QuickAttachmentDrawer.isDeviceSupported(this)) { - quickAttachmentDrawer.setListener(this); - quickCameraToggle.setOnClickListener(new QuickCameraToggleListener()); - } else { - quickCameraToggle.setVisibility(View.GONE); - quickCameraToggle.setEnabled(false); - } + quickAttachmentDrawer.setListener(this); + quickCameraToggle.setOnClickListener(new QuickCameraToggleListener()); initializeBackground(); } private void initializeBackground() { - String backgroundImagePath = Prefs.getBackgroundImagePath(this); + String backgroundImagePath = Prefs.getBackgroundImagePath(this, dcContext.getAccountId()); Drawable background; if(!backgroundImagePath.isEmpty()) { background = Drawable.createFromPath(backgroundImagePath); @@ -916,6 +888,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity AccountManager.getInstance().switchAccount(context, accountId); dcContext = context.dcContext; fragment.dcContext = context.dcContext; + initializeBackground(); } chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, -1); if(chatId == DcChat.DC_CHAT_NO_CHAT) @@ -974,7 +947,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void startContactChooserActivity() { - Intent intent = new Intent(ConversationActivity.this, BlockedAndShareContactsActivity.class); + Intent intent = new Intent(ConversationActivity.this, AttachContactActivity.class); + intent.putExtra(ContactSelectionListFragment.ALLOW_CREATION, false); startActivityForResult(intent, PICK_CONTACT); } @@ -991,8 +965,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void addAttachmentContactInfo(Intent data) { - String name = data.getStringExtra(BlockedAndShareContactsActivity.SHARE_CONTACT_NAME_EXTRA); - String mail = data.getStringExtra(BlockedAndShareContactsActivity.SHARE_CONTACT_MAIL_EXTRA); + String name = data.getStringExtra(AttachContactActivity.NAME_EXTRA); + String mail = data.getStringExtra(AttachContactActivity.ADDR_EXTRA); composeText.append(name + "\n" + mail); } @@ -1388,18 +1362,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private class QuickCameraToggleListener implements OnClickListener { @Override public void onClick(View v) { - if (!quickAttachmentDrawer.isShowing()) { - Permissions.with(ConversationActivity.this) - .request(Manifest.permission.CAMERA) - .ifNecessary() - .withPermanentDenialDialog(getString(R.string.perm_explain_access_to_camera_denied)) - .onAllGranted(() -> { - composeText.clearFocus(); - container.show(composeText, quickAttachmentDrawer); - }) - .execute(); + if (Prefs.isBuiltInCameraPreferred(ConversationActivity.this) + && QuickAttachmentDrawer.isDeviceSupported(ConversationActivity.this)) { + if (!quickAttachmentDrawer.isShowing()) { + Permissions.with(ConversationActivity.this) + .request(Manifest.permission.CAMERA) + .ifNecessary() + .withPermanentDenialDialog(getString(R.string.perm_explain_access_to_camera_denied)) + .onAllGranted(() -> { + composeText.clearFocus(); + container.show(composeText, quickAttachmentDrawer); + }) + .execute(); + } else { + container.hideAttachedInput(false); + } } else { - container.hideAttachedInput(false); + attachmentManager.capturePhoto(ConversationActivity.this, TAKE_PHOTO); } } } diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index a1b5a80b1..3db9b468c 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -22,7 +22,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -94,7 +93,7 @@ public class ConversationAdapter private final @NonNull Context context; private final @NonNull Calendar calendar; - private DcContext dcContext; + private final DcContext dcContext; private @NonNull DcChat dcChat; private @NonNull int[] dcMsgList = new int[0]; private int positionToPulseHighlight = -1; @@ -188,7 +187,7 @@ public class ConversationAdapter } static class HeaderViewHolder extends RecyclerView.ViewHolder { - TextView textView; + final TextView textView; HeaderViewHolder(View itemView) { super(itemView); diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index 879e24704..74645d826 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -64,6 +64,7 @@ import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.reactions.ReactionsDetailsFragment; import org.thoughtcrime.securesms.util.AccessibilityUtil; import org.thoughtcrime.securesms.util.Debouncer; import org.thoughtcrime.securesms.util.StickyHeaderDecoration; @@ -119,6 +120,7 @@ public class ConversationFragment extends MessageSelectorFragment DcEventCenter eventCenter = DcHelper.getEventCenter(getContext()); eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this); eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this); + eventCenter.addObserver(DcContext.DC_EVENT_REACTIONS_CHANGED, this); eventCenter.addObserver(DcContext.DC_EVENT_MSG_DELIVERED, this); eventCenter.addObserver(DcContext.DC_EVENT_MSG_FAILED, this); eventCenter.addObserver(DcContext.DC_EVENT_MSG_READ, this); @@ -476,20 +478,6 @@ public class ConversationFragment extends MessageSelectorFragment } } - private void handleResendMessage(final Set dcMsgsSet) { - int[] ids = DcMsg.msgSetToIds(dcMsgsSet); - if (dcContext.resendMsgs(ids)) { - actionMode.finish(); - Toast.makeText(getContext(), R.string.sending, Toast.LENGTH_SHORT).show(); - } else { - new AlertDialog.Builder(getContext()) - .setMessage(dcContext.getLastError()) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, null) - .show(); - } - } - private void reloadList() { reloadList(false); } @@ -802,7 +790,7 @@ public class ConversationFragment extends MessageSelectorFragment else if(DozeReminder.isDozeReminderMsg(getContext(), messageRecord)) { DozeReminder.dozeReminderTapped(getContext()); } - else if(messageRecord.isInfo() && messageRecord.getParent() != null && messageRecord.getParent().getType() == DcMsg.DC_MSG_WEBXDC) { + else if(messageRecord.getInfoType() == DcMsg.DC_INFO_WEBXDC_INFO_MESSAGE) { scrollMaybeSmoothToMsgId(messageRecord.getParent().getId()); } else { @@ -866,6 +854,12 @@ public class ConversationFragment extends MessageSelectorFragment public void onDownloadClicked(DcMsg messageRecord) { dcContext.downloadFullMsg(messageRecord.getId()); } + + @Override + public void onReactionClicked(DcMsg messageRecord) { + ReactionsDetailsFragment dialog = new ReactionsDetailsFragment(messageRecord.getId()); + dialog.show(getActivity().getSupportFragmentManager(), null); + } } @Override @@ -972,6 +966,7 @@ public class ConversationFragment extends MessageSelectorFragment } break; + case DcContext.DC_EVENT_REACTIONS_CHANGED: case DcContext.DC_EVENT_INCOMING_MSG: case DcContext.DC_EVENT_MSG_DELIVERED: case DcContext.DC_EVENT_MSG_FAILED: diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 4ceac173d..7305a091b 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -22,6 +22,7 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Rect; +import android.os.Build; import android.text.SpannableString; import android.text.TextUtils; import android.util.AttributeSet; @@ -39,6 +40,8 @@ import androidx.annotation.Nullable; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcMsg; +import com.b44t.messenger.rpc.Reactions; +import com.b44t.messenger.rpc.RpcException; import org.thoughtcrime.securesms.audio.AudioSlidePlayer; import org.thoughtcrime.securesms.components.AudioView; @@ -59,6 +62,7 @@ import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.StickerSlide; +import org.thoughtcrime.securesms.reactions.ReactionsConversationView; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.LongClickMovementMethod; import org.thoughtcrime.securesms.util.MediaUtil; @@ -96,7 +100,7 @@ public class ConversationItem extends BaseConversationItem protected View replyView; @Nullable private QuoteView quoteView; private ConversationItemFooter footer; - private ConversationItemFooter stickerFooter; + private ReactionsConversationView reactionsView; private TextView groupSender; private View groupSenderHolder; private AvatarImageView contactPhoto; @@ -132,7 +136,7 @@ public class ConversationItem extends BaseConversationItem this.bodyText = findViewById(R.id.conversation_item_body); this.footer = findViewById(R.id.conversation_item_footer); - this.stickerFooter = findViewById(R.id.conversation_item_sticker_footer); + this.reactionsView = findViewById(R.id.reactions_view); this.groupSender = findViewById(R.id.group_message_sender); this.contactPhoto = findViewById(R.id.contact_photo); this.contactPhotoHolder = findViewById(R.id.contact_photo_container); @@ -183,8 +187,12 @@ public class ConversationItem extends BaseConversationItem setGroupMessageStatus(); setAuthor(messageRecord, showSender); setMessageSpacing(context); + setReactions(messageRecord); setFooter(messageRecord, locale); setQuote(messageRecord); + if (Util.isTouchExplorationEnabled(context)) { + setContentDescription(); + } } @@ -281,9 +289,7 @@ public class ConversationItem extends BaseConversationItem } if (audioViewStub.resolved()) { - audioViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - audioViewStub.get().setClickable(batchSelected.isEmpty()); - audioViewStub.get().setEnabled(batchSelected.isEmpty()); + audioViewStub.get().disablePlayer(!batchSelected.isEmpty()); } if (documentViewStub.resolved()) { @@ -297,6 +303,35 @@ public class ConversationItem extends BaseConversationItem } } + private void setContentDescription() { + String desc = ""; + if (groupSenderHolder.getVisibility() == View.VISIBLE) { + desc = groupSender.getText() + "\n"; + } + + if (audioViewStub.resolved() && audioViewStub.get().getVisibility() == View.VISIBLE) { + desc += audioViewStub.get().getDescription() + "\n"; + } else if (documentViewStub.resolved() && documentViewStub.get().getVisibility() == View.VISIBLE) { + desc += documentViewStub.get().getDescription() + "\n"; + } else if (webxdcViewStub.resolved() && webxdcViewStub.get().getVisibility() == View.VISIBLE) { + desc += webxdcViewStub.get().getDescription() + "\n"; + } else if (mediaThumbnailStub.resolved() && mediaThumbnailStub.get().getVisibility() == View.VISIBLE) { + desc += mediaThumbnailStub.get().getDescription() + "\n"; + } else if (stickerStub.resolved() && stickerStub.get().getVisibility() == View.VISIBLE) { + desc += stickerStub.get().getDescription() + "\n"; + } + + if (bodyText.getVisibility() == View.VISIBLE) { + desc += bodyText.getText() + "\n"; + } + + if (footer.getVisibility() == View.VISIBLE) { + desc += footer.getDescription(); + } + + this.setContentDescription(desc); + } + private boolean hasAudio(DcMsg messageRecord) { int type = messageRecord.getType(); return type==DcMsg.DC_MSG_AUDIO || type==DcMsg.DC_MSG_VOICE; @@ -414,7 +449,7 @@ public class ConversationItem extends BaseConversationItem public void onStop() {} @Override - public void onProgress(double progress, long millis) {} + public void onProgress(AudioSlide slide, double progress, long millis) {} @Override public void onReceivedDuration(int millis) { @@ -438,7 +473,11 @@ public class ConversationItem extends BaseConversationItem } audioViewStub.get().setAudio(new AudioSlide(context, messageRecord), duration); + audioViewStub.get().setOnClickListener(passthroughClickListener); audioViewStub.get().setOnLongClickListener(passthroughClickListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + audioViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -455,6 +494,9 @@ public class ConversationItem extends BaseConversationItem documentViewStub.get().setDocument(new DocumentSlide(context, messageRecord)); documentViewStub.get().setDocumentClickListener(new ThumbnailClickListener()); documentViewStub.get().setOnLongClickListener(passthroughClickListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + documentViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -470,6 +512,9 @@ public class ConversationItem extends BaseConversationItem webxdcViewStub.get().setWebxdc(messageRecord, context.getString(R.string.webxdc_app)); webxdcViewStub.get().setWebxdcClickListener(new ThumbnailClickListener()); webxdcViewStub.get().setOnLongClickListener(passthroughClickListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webxdcViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -504,6 +549,9 @@ public class ConversationItem extends BaseConversationItem mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener); mediaThumbnailStub.get().setOnClickListener(passthroughClickListener); mediaThumbnailStub.get().showShade(TextUtils.isEmpty(messageRecord.getText())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mediaThumbnailStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } setThumbnailOutlineCorners(messageRecord, showSender); @@ -523,9 +571,11 @@ public class ConversationItem extends BaseConversationItem stickerStub.get().setSlide(glideRequests, new StickerSlide(context, messageRecord)); stickerStub.get().setThumbnailClickListener(new StickerClickListener()); - stickerStub.get().setOnLongClickListener(passthroughClickListener); stickerStub.get().setOnClickListener(passthroughClickListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + stickerStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -657,7 +707,6 @@ public class ConversationItem extends BaseConversationItem ViewUtil.updateLayoutParams(footer, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); footer.setVisibility(GONE); - stickerFooter.setVisibility(GONE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().getFooter().setVisibility(GONE); ConversationItemFooter activeFooter = getActiveFooter(current); @@ -665,9 +714,29 @@ public class ConversationItem extends BaseConversationItem activeFooter.setMessageRecord(current, locale); } + private void setReactions(@NonNull DcMsg current) { + try { + Reactions reactions = rpc.getMsgReactions(dcContext.getAccountId(), current.getId()); + if (reactions == null) { + reactionsView.clear(); + } else { + reactionsView.setReactions(reactions.getReactions()); + reactionsView.setOnClickListener(view -> { + if (eventListener != null && batchSelected.isEmpty()) { + eventListener.onReactionClicked(current); + } else { + passthroughClickListener.onClick(view); + } + }); + } + } catch (RpcException e) { + reactionsView.clear(); + } + } + private ConversationItemFooter getActiveFooter(@NonNull DcMsg messageRecord) { if (hasSticker(messageRecord)) { - return stickerFooter; + return stickerStub.get().getFooter(); } else if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getText())) { return mediaThumbnailStub.get().getFooter(); } else { @@ -764,6 +833,14 @@ public class ConversationItem extends BaseConversationItem return availableWidth; } + @Override + public void onAccessibilityClick() { + if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().performClick(); + else if (audioViewStub.resolved()) audioViewStub.get().togglePlay(); + else if (documentViewStub.resolved()) documentViewStub.get().performClick(); + else if (webxdcViewStub.resolved()) webxdcViewStub.get().performClick(); + } + /// Event handlers private class ThumbnailClickListener implements SlideClickListener { diff --git a/src/org/thoughtcrime/securesms/ConversationItemSwipeCallback.java b/src/org/thoughtcrime/securesms/ConversationItemSwipeCallback.java index 2b8f1a030..b252c9f28 100644 --- a/src/org/thoughtcrime/securesms/ConversationItemSwipeCallback.java +++ b/src/org/thoughtcrime/securesms/ConversationItemSwipeCallback.java @@ -19,8 +19,8 @@ import org.thoughtcrime.securesms.util.ServiceUtil; class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback { - private static float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX; - private static long SWIPE_SUCCESS_VIBE_TIME_MS = 10; + private static final float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX; + private static final long SWIPE_SUCCESS_VIBE_TIME_MS = 10; private boolean swipeBack; private boolean shouldTriggerSwipeFeedback; diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 8acd36844..9a8472d38 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -20,6 +20,7 @@ import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA; import static org.thoughtcrime.securesms.ConversationActivity.STARTING_POSITION_EXTRA; import static org.thoughtcrime.securesms.util.RelayUtil.acquireRelayMessageContent; import static org.thoughtcrime.securesms.util.RelayUtil.getDirectSharingChatId; +import static org.thoughtcrime.securesms.util.RelayUtil.getSharedTitle; import static org.thoughtcrime.securesms.util.RelayUtil.isDirectSharing; import static org.thoughtcrime.securesms.util.RelayUtil.isForwarding; import static org.thoughtcrime.securesms.util.RelayUtil.isRelayingMessageContent; @@ -39,6 +40,7 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.TooltipCompat; @@ -55,30 +57,25 @@ import org.thoughtcrime.securesms.connect.AccountManager; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.connect.DirectShareUtil; import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.qr.QrActivity; import org.thoughtcrime.securesms.qr.QrCodeHandler; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.search.SearchFragment; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Prefs; -import org.thoughtcrime.securesms.util.RelayUtil; import org.thoughtcrime.securesms.util.SendRelayedMessageUtil; public class ConversationListActivity extends PassphraseRequiredActionBarActivity implements ConversationListFragment.ConversationSelectedListener { - @SuppressWarnings("unused") private static final String TAG = ConversationListActivity.class.getSimpleName(); private static final String OPENPGP4FPR = "openpgp4fpr"; private static final String NDK_ARCH_WARNED = "ndk_arch_warned"; public static final String CLEAR_NOTIFICATIONS = "clear_notifications"; public static final String ACCOUNT_ID_EXTRA = "account_id"; - private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private ConversationListFragment conversationListFragment; public TextView title; private AvatarImageView selfAvatar; @@ -89,18 +86,18 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit @Override protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); + dynamicTheme = new DynamicNoActionBarTheme(); + super.onPreCreate(); } @Override protected void onCreate(Bundle icicle, boolean ready) { // update messages - for new messages, do not reuse or modify strings but create new ones. // it is not needed to keep all past update messages, however, when deleted, also the strings should be deleted. - DcContext dcContext = DcHelper.getContext(this); - DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); - msg.setText(getString(R.string.update_1_38_android, "https://get.delta.chat/#changelogs")); - dcContext.addDeviceMsg("update_1_38c_android", msg); + //DcContext dcContext = DcHelper.getContext(this); + //DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); + //msg.setText(getString(R.string.update_1_38_android, "https://get.delta.chat/#changelogs")); + //dcContext.addDeviceMsg("update_1_38c_android", msg); // create view setContentView(R.layout.conversation_list_activity); @@ -213,7 +210,16 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit public void refreshTitle() { if (isRelayingMessageContent(this)) { - title.setText(isForwarding(this) ? R.string.forward_to : R.string.chat_share_with_title); + if (isForwarding(this)) { + title.setText(R.string.forward_to); + } else { + String titleStr = getSharedTitle(this); + if (titleStr != null) { // sharing from sendToChat + title.setText(titleStr); + } else { // normal sharing + title.setText(R.string.chat_share_with_title); + } + } getSupportActionBar().setDisplayHomeAsUpEnabled(true); } else { title.setText(DcHelper.getConnectivitySummary(this, R.string.app_name)); @@ -239,9 +245,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit @Override public void onResume() { super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - DirectShareUtil.triggerRefreshDirectShare(this); } @@ -306,7 +309,11 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } private void initializeTitleListener() { - title.setOnClickListener(v -> startActivity(new Intent(this, ConnectivityActivity.class))); + title.setOnClickListener(v -> { + if (!isRelayingMessageContent(this)) { + startActivity(new Intent(this, ConnectivityActivity.class)); + } + }); } @Override @@ -432,4 +439,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit break; } } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); + } } diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java index 909d4ae27..8eb5156c1 100644 --- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java @@ -92,7 +92,7 @@ class ConversationListAdapter extends RecyclerView.Adapter { this.context = new WeakReference<>(context); this.glideRequests = glideRequests; this.dcContext = DcHelper.getContext(context); - this.dcChatlist = new DcChatlist(0); + this.dcChatlist = new DcChatlist(0, 0); this.locale = locale; this.inflater = LayoutInflater.from(context); this.clickListener = clickListener; @@ -195,7 +195,7 @@ class ConversationListAdapter extends RecyclerView.Adapter { } void changeData(@Nullable DcChatlist chatlist) { - dcChatlist = chatlist == null ? new DcChatlist(0) : chatlist; + dcChatlist = chatlist == null ? new DcChatlist(0, 0) : chatlist; notifyDataSetChanged(); } } diff --git a/src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java b/src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java index 6505ce94c..8909aac72 100644 --- a/src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java @@ -9,8 +9,6 @@ import android.view.MenuItem; import com.b44t.messenger.DcChat; import org.thoughtcrime.securesms.connect.DcHelper; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicTheme; import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA; import static org.thoughtcrime.securesms.ConversationActivity.FROM_ARCHIVED_CHATS_EXTRA; @@ -23,15 +21,6 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar { private ConversationListFragment conversationListFragment; - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - - @Override - protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); - } - @Override protected void onCreate(Bundle icicle, boolean ready) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -60,13 +49,6 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right); } - @Override - public void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuInflater inflater = this.getMenuInflater(); diff --git a/src/org/thoughtcrime/securesms/ConversationListFragment.java b/src/org/thoughtcrime/securesms/ConversationListFragment.java index 07d98acec..20e96ef28 100644 --- a/src/org/thoughtcrime/securesms/ConversationListFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationListFragment.java @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms; +import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; @@ -50,6 +51,7 @@ import com.b44t.messenger.DcChat; import com.b44t.messenger.DcChatlist; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; +import com.b44t.messenger.DcMsg; import com.google.android.material.snackbar.Snackbar; import org.thoughtcrime.securesms.ConversationListAdapter.ItemClickListener; @@ -60,6 +62,8 @@ import org.thoughtcrime.securesms.connect.DcEventCenter; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.connect.DirectShareUtil; import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.RelayUtil; import org.thoughtcrime.securesms.util.SendRelayedMessageUtil; import org.thoughtcrime.securesms.util.Util; @@ -86,7 +90,6 @@ public class ConversationListFragment extends Fragment public static final String ARCHIVE = "archive"; public static final String RELOAD_LIST = "reload_list"; - @SuppressWarnings("unused") private static final String TAG = ConversationListFragment.class.getSimpleName(); private ActionMode actionMode; @@ -95,10 +98,11 @@ public class ConversationListFragment extends Fragment private TextView emptySearch; private PulsingFloatingActionButton fab; private Locale locale; - private String queryFilter = ""; + private final String queryFilter = ""; private boolean archive; private Timer reloadTimer; private boolean chatlistJustLoaded; + private boolean reloadTimerInstantly; @Override public void onCreate(Bundle icicle) { @@ -171,23 +175,33 @@ public class ConversationListFragment extends Fragment if (getActivity().getIntent().getIntExtra(RELOAD_LIST, 0) == 1 && !chatlistJustLoaded) { + Log.i(TAG, "🤠 resuming chatlist: loading chatlist"); loadChatlist(); + reloadTimerInstantly = false; } chatlistJustLoaded = false; + Log.i(TAG, "🤠 resuming chatlist: starting update timer"); reloadTimer = new Timer(); reloadTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { - Util.runOnMain(() -> { list.getAdapter().notifyDataSetChanged(); }); + Util.runOnMain(() -> { + Log.i(TAG, "🤠 update timer: refreshing chatlist"); + list.getAdapter().notifyDataSetChanged(); + }); } - }, 60 * 1000, 60 * 1000); + }, reloadTimerInstantly? 0 : 60 * 1000, 60 * 1000); } @Override public void onPause() { super.onPause(); + + Log.i(TAG, "🤠 pausing chatlist: cancel update timer"); reloadTimer.cancel(); + reloadTimerInstantly = true; + fab.stopPulse(); } @@ -256,7 +270,28 @@ public class ConversationListFragment extends Fragment @Override protected void onPostExecute(Void result) { - DozeReminder.maybeAskDirectly(getActivity()); + Activity activity = ConversationListFragment.this.getActivity(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (!Prefs.getBooleanPreference(activity, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, false)) { + Prefs.setBooleanPreference(activity, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, true); + Permissions.with(activity) + .request(Manifest.permission.POST_NOTIFICATIONS) + .ifNecessary() + .onAllGranted(() -> { + DozeReminder.maybeAskDirectly(getActivity()); + }) + .onAnyDenied(() -> { + final DcContext dcContext = DcHelper.getContext(activity); + DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); + msg.setText("\uD83D\uDC49 "+activity.getString(R.string.notifications_disabled)+" \uD83D\uDC48\n\n" + +activity.getString(R.string.perm_explain_access_to_notifications_denied)); + dcContext.addDeviceMsg("android.notifications-disabled", msg); + }) + .execute(); + } + } else { + DozeReminder.maybeAskDirectly(getActivity()); + } } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getActivity()); } diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index 47904089e..1cbd7eff7 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -61,9 +61,6 @@ import java.util.Set; public class ConversationListItem extends RelativeLayout implements BindableConversationListItem, Unbindable { - @SuppressWarnings("unused") - private final static String TAG = ConversationListItem.class.getSimpleName(); - private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL); private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL); diff --git a/src/org/thoughtcrime/securesms/ConversationTitleView.java b/src/org/thoughtcrime/securesms/ConversationTitleView.java index cc0aabda7..f3a0dd14e 100644 --- a/src/org/thoughtcrime/securesms/ConversationTitleView.java +++ b/src/org/thoughtcrime/securesms/ConversationTitleView.java @@ -23,9 +23,6 @@ import org.thoughtcrime.securesms.util.ViewUtil; public class ConversationTitleView extends RelativeLayout { - @SuppressWarnings("unused") - private static final String TAG = ConversationTitleView.class.getSimpleName(); - private View content; private ImageView back; private AvatarView avatar; diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java index fa8e91209..b6a8c654d 100644 --- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java +++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java @@ -42,16 +42,10 @@ import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints; import org.thoughtcrime.securesms.scribbles.ScribbleActivity; -import org.thoughtcrime.securesms.util.BitmapDecodingException; -import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Prefs; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.security.SecureRandom; @@ -65,9 +59,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Emoj private static final int REQUEST_CODE_AVATAR = 1; - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private InputAwareLayout container; private ImageView avatar; private EditText name; @@ -88,9 +79,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Emoj super.onCreate(bundle); this.fromWelcome = getIntent().getBooleanExtra(FROM_WELCOME, false); - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); - setContentView(R.layout.profile_create_activity); getSupportActionBar().setTitle(R.string.pref_profile_info_headline); @@ -113,13 +101,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Emoj return true; } - @Override - public void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); diff --git a/src/org/thoughtcrime/securesms/EphemeralMessagesDialog.java b/src/org/thoughtcrime/securesms/EphemeralMessagesDialog.java index e116397f0..235527664 100644 --- a/src/org/thoughtcrime/securesms/EphemeralMessagesDialog.java +++ b/src/org/thoughtcrime/securesms/EphemeralMessagesDialog.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms; import android.content.Context; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.RadioButton; diff --git a/src/org/thoughtcrime/securesms/FullMsgActivity.java b/src/org/thoughtcrime/securesms/FullMsgActivity.java index 9a04e2f14..d5ed6f1e4 100644 --- a/src/org/thoughtcrime/securesms/FullMsgActivity.java +++ b/src/org/thoughtcrime/securesms/FullMsgActivity.java @@ -40,7 +40,7 @@ public class FullMsgActivity extends WebViewActivity protected void onCreate(Bundle state, boolean ready) { super.onCreate(state, ready); - blockLoadingRemote = getIntent().getBooleanExtra(BLOCK_LOADING_REMOTE, false);; + blockLoadingRemote = getIntent().getBooleanExtra(BLOCK_LOADING_REMOTE, false); loadRemoteContent = !blockLoadingRemote && Prefs.getAlwaysLoadRemoteContent(this); webView.getSettings().setBlockNetworkLoads(!loadRemoteContent); diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index 0be74cef2..5dd52197a 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -42,8 +42,6 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.qr.QrShowActivity; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.scribbles.ScribbleActivity; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener; import org.thoughtcrime.securesms.util.ThemeUtil; @@ -68,9 +66,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity public static final String CREATE_BROADCAST = "group_create_broadcast"; public static final String SUGGESTED_CONTACT_IDS = "suggested_contact_ids"; - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private static final int PICK_CONTACT = 1; public static final int AVATAR_SIZE = 210; private static final int REQUEST_CODE_AVATAR = 2759; @@ -90,12 +85,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity private boolean hasSuggestedContacts; private AttachmentManager attachmentManager; - @Override - protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); - } - @Override protected void onCreate(Bundle state, boolean ready) { dcContext = DcHelper.getContext(this); @@ -130,8 +119,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity @Override public void onResume() { super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); updateViewState(); } @@ -179,8 +166,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity private static class AddMembersTask extends AsyncTask> { static class Result { - Optional recipient; - String reason; + final Optional recipient; + final String reason; public Result(@Nullable Recipient recipient, @Nullable String reason) { this.recipient = Optional.fromNullable(recipient); @@ -188,7 +175,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } } - private GroupCreateActivity activity; + private final GroupCreateActivity activity; public AddMembersTask(@NonNull GroupCreateActivity activity) { this.activity = activity; @@ -442,7 +429,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity @Override public void onActivityResult(int reqCode, int resultCode, final Intent data) { super.onActivityResult(reqCode, resultCode, data); - Uri outputFile = Uri.fromFile(new File(getCacheDir(), "cropped")); if (resultCode != Activity.RESULT_OK) return; @@ -531,7 +517,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask { - GroupCreateActivity activity; + final GroupCreateActivity activity; FillExistingGroupInfoAsyncTask(GroupCreateActivity activity) { super(activity, diff --git a/src/org/thoughtcrime/securesms/LogViewActivity.java b/src/org/thoughtcrime/securesms/LogViewActivity.java index 6bdf38ec3..315390b59 100644 --- a/src/org/thoughtcrime/securesms/LogViewActivity.java +++ b/src/org/thoughtcrime/securesms/LogViewActivity.java @@ -12,24 +12,17 @@ import android.view.MenuItem; import android.widget.Toast; import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Util; public class LogViewActivity extends BaseActionBarActivity { private static final String TAG = LogViewActivity.class.getSimpleName(); - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - LogViewFragment logViewFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); setContentView(R.layout.log_view_activity); logViewFragment = new LogViewFragment(dynamicLanguage); @@ -40,13 +33,6 @@ public class LogViewActivity extends BaseActionBarActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); } - @Override - protected void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuInflater inflater = this.getMenuInflater(); diff --git a/src/org/thoughtcrime/securesms/LogViewFragment.java b/src/org/thoughtcrime/securesms/LogViewFragment.java index c60ce3416..ce9567dd5 100644 --- a/src/org/thoughtcrime/securesms/LogViewFragment.java +++ b/src/org/thoughtcrime/securesms/LogViewFragment.java @@ -61,7 +61,7 @@ public class LogViewFragment extends Fragment { private static final String TAG = LogViewFragment.class.getSimpleName(); private EditText logPreview; - private @NonNull DynamicLanguage dynamicLanguage; + private final @NonNull DynamicLanguage dynamicLanguage; public LogViewFragment(DynamicLanguage dynamicLanguage) { this.dynamicLanguage = dynamicLanguage; @@ -154,7 +154,7 @@ public class LogViewFragment extends Fragment { } private class PopulateLogcatAsyncTask extends AsyncTask { - private WeakReference weakFragment; + private final WeakReference weakFragment; public PopulateLogcatAsyncTask(LogViewFragment fragment) { this.weakFragment = new WeakReference<>(fragment); diff --git a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java index cfc50f6a0..26acf07b6 100644 --- a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms; +import android.app.Activity; import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -62,7 +63,7 @@ import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.DynamicLanguage; +import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import org.thoughtcrime.securesms.util.StorageUtil; @@ -93,8 +94,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity /** USE ONLY IF YOU HAVE NO MESSAGE ID! */ public static final String SIZE_EXTRA = "size"; - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - @Nullable private DcMsg messageRecord; private DcContext dcContext; @@ -107,12 +106,20 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity private int editAvatarChatId = 0; + @Override + protected void onPreCreate() { + dynamicTheme = new DynamicTheme() { + public void onCreate(Activity activity) { + activity.setTheme(R.style.TextSecure_DarkTheme); // force dark theme + } + public void onResume(Activity activity) {} + }; + super.onPreCreate(); + } + @SuppressWarnings("ConstantConditions") @Override protected void onCreate(Bundle bundle, boolean ready) { - this.setTheme(R.style.TextSecure_DarkTheme); - dynamicLanguage.onCreate(this); - setFullscreenIfPossible(); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); @@ -173,8 +180,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity @Override public void onResume() { super.onResume(); - - dynamicLanguage.onResume(this); initializeMedia(); } diff --git a/src/org/thoughtcrime/securesms/MessageSelectorFragment.java b/src/org/thoughtcrime/securesms/MessageSelectorFragment.java index e3ce36c9c..3451ceb05 100644 --- a/src/org/thoughtcrime/securesms/MessageSelectorFragment.java +++ b/src/org/thoughtcrime/securesms/MessageSelectorFragment.java @@ -7,6 +7,7 @@ import android.os.AsyncTask; import android.view.Menu; import android.view.View; import android.widget.TextView; +import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ActionMode; @@ -115,4 +116,18 @@ public abstract class MessageSelectorFragment protected void handleShare(final DcMsg dcMsg) { DcHelper.openForViewOrShare(getContext(), dcMsg.getId(), Intent.ACTION_SEND); } + + protected void handleResendMessage(final Set dcMsgsSet) { + int[] ids = DcMsg.msgSetToIds(dcMsgsSet); + if (dcContext.resendMsgs(ids)) { + actionMode.finish(); + Toast.makeText(getContext(), R.string.sending, Toast.LENGTH_SHORT).show(); + } else { + new AlertDialog.Builder(getContext()) + .setMessage(dcContext.getLastError()) + .setCancelable(false) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + } } diff --git a/src/org/thoughtcrime/securesms/NewConversationActivity.java b/src/org/thoughtcrime/securesms/NewConversationActivity.java index 15f657239..aa48935b5 100644 --- a/src/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/src/org/thoughtcrime/securesms/NewConversationActivity.java @@ -22,7 +22,6 @@ import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.widget.Toast; @@ -51,7 +50,6 @@ import static org.thoughtcrime.securesms.util.RelayUtil.isRelayingMessageContent */ public class NewConversationActivity extends ContactSelectionActivity { - @SuppressWarnings("unused") private static final String TAG = NewConversationActivity.class.getSimpleName(); public static final String MAILTO = "mailto"; private static final String SUBJECT = "subject"; @@ -186,17 +184,6 @@ public class NewConversationActivity extends ContactSelectionActivity { finish(); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - - switch (item.getItemId()) { - case android.R.id.home: super.onBackPressed(); return true; - } - - return false; - } - @Override protected boolean onPrepareOptionsPanel(View view, Menu menu) { /* currently not needed diff --git a/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java b/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java index e8fb9d93a..d01cf6faf 100644 --- a/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java @@ -13,7 +13,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA @Override protected final void onCreate(Bundle savedInstanceState) { Log.w(TAG, "onCreate(" + savedInstanceState + ")"); - onPreCreate(); if (GenericForegroundService.isForegroundTaskStarted()) { // this does not prevent intent set by onNewIntent(), @@ -27,16 +26,16 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA if (!DcHelper.isConfigured(getApplicationContext())) { Intent intent = new Intent(this, WelcomeActivity.class); startActivity(intent); + super.onCreate(savedInstanceState); finish(); + } else { + super.onCreate(savedInstanceState); } - super.onCreate(savedInstanceState); - if (!isFinishing()) { onCreate(savedInstanceState, true); } } - protected void onPreCreate() {} protected void onCreate(Bundle savedInstanceState, boolean ready) {} } diff --git a/src/org/thoughtcrime/securesms/ProfileActivity.java b/src/org/thoughtcrime/securesms/ProfileActivity.java index 91a2d09aa..8e443dab6 100644 --- a/src/org/thoughtcrime/securesms/ProfileActivity.java +++ b/src/org/thoughtcrime/securesms/ProfileActivity.java @@ -32,9 +32,7 @@ import com.google.android.material.tabs.TabLayout; import org.thoughtcrime.securesms.connect.DcEventCenter; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; @@ -46,9 +44,6 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity implements DcEventCenter.DcEventDelegate { - @SuppressWarnings("unused") - private final static String TAG = ProfileActivity.class.getSimpleName(); - public static final String CHAT_ID_EXTRA = "chat_id"; public static final String CONTACT_ID_EXTRA = "contact_id"; public static final String FORCE_TAB_EXTRA = "force_tab"; @@ -64,9 +59,6 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity private static final int REQUEST_CODE_PICK_RINGTONE = 1; - private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private DcContext dcContext; private int chatId; private boolean chatIsMultiUser; @@ -76,7 +68,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity private int contactId; private boolean fromChat; - private ArrayList tabs = new ArrayList<>(); + private final ArrayList tabs = new ArrayList<>(); private Toolbar toolbar; private ConversationTitleView titleView; private TabLayout tabLayout; @@ -84,8 +76,8 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity @Override protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); + dynamicTheme = new DynamicNoActionBarTheme(); + super.onPreCreate(); dcContext = DcHelper.getContext(this); } @@ -188,13 +180,6 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity return true; } - @Override - public void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - boolean backPressed = false; @Override public void onBackPressed() { diff --git a/src/org/thoughtcrime/securesms/ProfileDocumentsAdapter.java b/src/org/thoughtcrime/securesms/ProfileDocumentsAdapter.java index 6ebedaa0d..5aecf166f 100644 --- a/src/org/thoughtcrime/securesms/ProfileDocumentsAdapter.java +++ b/src/org/thoughtcrime/securesms/ProfileDocumentsAdapter.java @@ -27,9 +27,6 @@ import java.util.Set; class ProfileDocumentsAdapter extends StickyHeaderGridAdapter { - @SuppressWarnings("unused") - private static final String TAG = ProfileDocumentsAdapter.class.getSimpleName(); - private final Context context; private final Locale locale; private final ItemClickListener itemClickListener; @@ -53,7 +50,7 @@ class ProfileDocumentsAdapter extends StickyHeaderGridAdapter { } private static class HeaderHolder extends StickyHeaderGridAdapter.HeaderViewHolder { - TextView textView; + final TextView textView; HeaderHolder(View itemView) { super(itemView); @@ -106,9 +103,7 @@ class ProfileDocumentsAdapter extends StickyHeaderGridAdapter { viewHolder.audioView.setAudio((AudioSlide)slide, dcMsg.getDuration()); viewHolder.audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg)); viewHolder.audioView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; }); - viewHolder.audioView.setFocusable(selected.isEmpty()); - viewHolder.audioView.setClickable(selected.isEmpty()); - viewHolder.audioView.setLongClickable(selected.isEmpty()); + viewHolder.audioView.disablePlayer(!selected.isEmpty()); viewHolder.itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg)); } else if (slide != null && slide.isWebxdcDocument()) { @@ -160,6 +155,12 @@ class ProfileDocumentsAdapter extends StickyHeaderGridAdapter { notifyDataSetChanged(); } + public void selectAll() { + selected.clear(); + selected.addAll(media.getAll()); + notifyDataSetChanged(); + } + public int getSelectedMediaCount() { return selected.size(); } diff --git a/src/org/thoughtcrime/securesms/ProfileDocumentsFragment.java b/src/org/thoughtcrime/securesms/ProfileDocumentsFragment.java index 9900ef350..054cf958d 100644 --- a/src/org/thoughtcrime/securesms/ProfileDocumentsFragment.java +++ b/src/org/thoughtcrime/securesms/ProfileDocumentsFragment.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms; +import static com.b44t.messenger.DcChat.DC_CHAT_NO_CHAT; + import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -45,7 +47,7 @@ public class ProfileDocumentsFragment protected TextView noMedia; protected RecyclerView recyclerView; private StickyHeaderGridLayoutManager gridManager; - private ActionModeCallback actionModeCallback = new ActionModeCallback(); + private final ActionModeCallback actionModeCallback = new ActionModeCallback(); private boolean showAudio; private boolean showWebxdc; @@ -124,7 +126,7 @@ public class ProfileDocumentsFragment ((ProfileDocumentsAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged(); noMedia.setVisibility(recyclerView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE); - if (chatId == 0) { + if (chatId == DC_CHAT_NO_CHAT) { noMedia.setText(R.string.tab_all_media_empty_hint); } else if (showAudio) { noMedia.setText(R.string.tab_audio_empty_hint); @@ -148,6 +150,11 @@ public class ProfileDocumentsFragment } } + private void updateActionModeBar() { + actionMode.setTitle(String.valueOf(getListAdapter().getSelectedMediaCount())); + setCorrectMenuVisibility(actionMode.getMenu()); + } + private void handleMediaMultiSelectClick(@NonNull DcMsg mediaRecord) { ProfileDocumentsAdapter adapter = getListAdapter(); @@ -156,8 +163,7 @@ public class ProfileDocumentsFragment actionMode.finish(); actionMode = null; } else { - actionMode.setTitle(String.valueOf(adapter.getSelectedMediaCount())); - setCorrectMenuVisibility(actionMode.getMenu()); + updateActionModeBar(); } } @@ -202,6 +208,15 @@ public class ProfileDocumentsFragment menu.findItem(R.id.show_in_chat).setVisible(singleSelection); menu.findItem(R.id.share).setVisible(singleSelection); + boolean canResend = true; + for (DcMsg messageRecord : messageRecords) { + if (!messageRecord.isOutgoing()) { + canResend = false; + break; + } + } + menu.findItem(R.id.menu_resend).setVisible(canResend); + boolean webxdcApp = singleSelection && messageRecords.iterator().next().getType() == DcMsg.DC_MSG_WEBXDC; menu.findItem(R.id.menu_add_to_home_screen).setVisible(webxdcApp); } @@ -257,6 +272,13 @@ public class ProfileDocumentsFragment case R.id.save: handleSaveAttachment(getListAdapter().getSelectedMedia()); return true; + case R.id.menu_resend: + handleResendMessage(getListAdapter().getSelectedMedia()); + return true; + case R.id.menu_select_all: + getListAdapter().selectAll(); + updateActionModeBar(); + return true; } return false; } diff --git a/src/org/thoughtcrime/securesms/ProfileGalleryAdapter.java b/src/org/thoughtcrime/securesms/ProfileGalleryAdapter.java index 9ab9a1264..5bd775337 100644 --- a/src/org/thoughtcrime/securesms/ProfileGalleryAdapter.java +++ b/src/org/thoughtcrime/securesms/ProfileGalleryAdapter.java @@ -23,9 +23,6 @@ import java.util.Set; class ProfileGalleryAdapter extends StickyHeaderGridAdapter { - @SuppressWarnings("unused") - private static final String TAG = ProfileGalleryAdapter.class.getSimpleName(); - private final Context context; private final GlideRequests glideRequests; private final Locale locale; @@ -35,8 +32,8 @@ class ProfileGalleryAdapter extends StickyHeaderGridAdapter { private BucketedThreadMedia media; private static class ViewHolder extends StickyHeaderGridAdapter.ItemViewHolder { - ThumbnailView imageView; - View selectedIndicator; + final ThumbnailView imageView; + final View selectedIndicator; ViewHolder(View v) { super(v); @@ -46,7 +43,7 @@ class ProfileGalleryAdapter extends StickyHeaderGridAdapter { } private static class HeaderHolder extends StickyHeaderGridAdapter.HeaderViewHolder { - TextView textView; + final TextView textView; HeaderHolder(View itemView) { super(itemView); @@ -124,6 +121,12 @@ class ProfileGalleryAdapter extends StickyHeaderGridAdapter { notifyDataSetChanged(); } + public void selectAll() { + selected.clear(); + selected.addAll(media.getAll()); + notifyDataSetChanged(); + } + public int getSelectedMediaCount() { return selected.size(); } diff --git a/src/org/thoughtcrime/securesms/ProfileGalleryFragment.java b/src/org/thoughtcrime/securesms/ProfileGalleryFragment.java index 06d550ce4..6fa61d3da 100644 --- a/src/org/thoughtcrime/securesms/ProfileGalleryFragment.java +++ b/src/org/thoughtcrime/securesms/ProfileGalleryFragment.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms; +import static com.b44t.messenger.DcChat.DC_CHAT_NO_CHAT; + import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -45,7 +47,7 @@ public class ProfileGalleryFragment protected TextView noMedia; protected RecyclerView recyclerView; private StickyHeaderGridLayoutManager gridManager; - private ActionModeCallback actionModeCallback = new ActionModeCallback(); + private final ActionModeCallback actionModeCallback = new ActionModeCallback(); private int chatId; private Locale locale; @@ -120,7 +122,7 @@ public class ProfileGalleryFragment ((ProfileGalleryAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged(); noMedia.setVisibility(recyclerView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE); - if (chatId == 0) { + if (chatId == DC_CHAT_NO_CHAT) { noMedia.setText(R.string.tab_all_media_empty_hint); } getActivity().invalidateOptionsMenu(); @@ -140,6 +142,11 @@ public class ProfileGalleryFragment } } + private void updateActionModeBar() { + actionMode.setTitle(String.valueOf(getListAdapter().getSelectedMediaCount())); + setCorrectMenuVisibility(actionMode.getMenu()); + } + private void handleMediaMultiSelectClick(@NonNull DcMsg mediaRecord) { ProfileGalleryAdapter adapter = getListAdapter(); @@ -148,8 +155,7 @@ public class ProfileGalleryFragment actionMode.finish(); actionMode = null; } else { - actionMode.setTitle(String.valueOf(adapter.getSelectedMediaCount())); - setCorrectMenuVisibility(actionMode.getMenu()); + updateActionModeBar(); } } @@ -195,6 +201,15 @@ public class ProfileGalleryFragment menu.findItem(R.id.details).setVisible(singleSelection); menu.findItem(R.id.show_in_chat).setVisible(singleSelection); menu.findItem(R.id.share).setVisible(singleSelection); + + boolean canResend = true; + for (DcMsg messageRecord : messageRecords) { + if (!messageRecord.isOutgoing()) { + canResend = false; + break; + } + } + menu.findItem(R.id.menu_resend).setVisible(canResend); } private ProfileGalleryAdapter getListAdapter() { @@ -244,6 +259,13 @@ public class ProfileGalleryFragment case R.id.save: handleSaveAttachment(getListAdapter().getSelectedMedia()); return true; + case R.id.menu_resend: + handleResendMessage(getListAdapter().getSelectedMedia()); + return true; + case R.id.menu_select_all: + getListAdapter().selectAll(); + updateActionModeBar(); + return true; } return false; } diff --git a/src/org/thoughtcrime/securesms/ProfileSettingsAdapter.java b/src/org/thoughtcrime/securesms/ProfileSettingsAdapter.java index 68766932a..553479eb4 100644 --- a/src/org/thoughtcrime/securesms/ProfileSettingsAdapter.java +++ b/src/org/thoughtcrime/securesms/ProfileSettingsAdapter.java @@ -32,13 +32,15 @@ import java.util.Set; public class ProfileSettingsAdapter extends RecyclerView.Adapter implements StickyHeaderAdapter { - public static final int SETTING_SEND_MESSAGE = 120; + public static final int INFO_VERIFIED = 118; + public static final int INFO_LAST_SEEN = 119; + public static final int INFO_SEND_MESSAGE_BUTTON = 120; private final @NonNull Context context; private final @NonNull Locale locale; private final @NonNull DcContext dcContext; - private @NonNull ArrayList itemData = new ArrayList<>(); + private final @NonNull ArrayList itemData = new ArrayList<>(); private int itemDataMemberCount; private DcChatlist itemDataSharedChats; private DcContact itemDataContact; @@ -52,30 +54,34 @@ public class ProfileSettingsAdapter extends RecyclerView.Adapter private final GlideRequests glideRequests; static class ItemData { - static final int TYPE_PRIMARY_SETTING = 1; - static final int TYPE_STATUS = 2; - static final int TYPE_MEMBER = 3; - static final int TYPE_SHARED_CHAT = 4; - int type; - int contactId; - int chatlistIndex; - int settingsId; - String label; + static final int CATEGORY_INFO = 1; + static final int CATEGORY_SIGNATURE = 2; + static final int CATEGORY_MEMBERS = 3; + static final int CATEGORY_SHARED_CHATS = 4; + final int type; + final int contactId; + final int chatlistIndex; + final int settingsId; + final String label; + final int labelColor; + final int iconLeft; - ItemData(int type, int settingsId, String label) { - this(type, 0, 0, settingsId, label); + ItemData(int type, int settingsId, String label, int labelColor, int iconLeft) { + this(type, 0, 0, settingsId, label, labelColor, iconLeft); } ItemData(int type, int contactId, int chatlistIndex) { - this(type, contactId, chatlistIndex, 0, null); + this(type, contactId, chatlistIndex, 0, null, 0, 0); } - ItemData(int type, int contactId, int chatlistIndex, int settingsId, @Nullable String label) { + ItemData(int type, int contactId, int chatlistIndex, int settingsId, @Nullable String label, int labelColor, int iconLeft) { this.type = type; this.contactId = contactId; this.chatlistIndex = chatlistIndex; this.settingsId = settingsId; this.label = label; + this.labelColor = labelColor; + this.iconLeft = iconLeft; } }; @@ -106,7 +112,7 @@ public class ProfileSettingsAdapter extends RecyclerView.Adapter } static class HeaderViewHolder extends RecyclerView.ViewHolder { - TextView textView; + final TextView textView; HeaderViewHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.label); @@ -115,17 +121,17 @@ public class ProfileSettingsAdapter extends RecyclerView.Adapter @Override public ProfileSettingsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType == ItemData.TYPE_MEMBER) { + if (viewType == ItemData.CATEGORY_MEMBERS) { final ContactSelectionListItem item = (ContactSelectionListItem)layoutInflater.inflate(R.layout.contact_selection_list_item, parent, false); item.setNoHeaderPadding(); return new ViewHolder(item); } - else if (viewType == ItemData.TYPE_SHARED_CHAT) { + else if (viewType == ItemData.CATEGORY_SHARED_CHATS) { final ConversationListItem item = (ConversationListItem)layoutInflater.inflate(R.layout.conversation_list_item_view, parent, false); item.hideItemDivider(); return new ViewHolder(item); } - else if (viewType == ItemData.TYPE_STATUS) { + else if (viewType == ItemData.CATEGORY_SIGNATURE) { final ProfileStatusItem item = (ProfileStatusItem)layoutInflater.inflate(R.layout.profile_status_item, parent, false); return new ViewHolder(item); } @@ -191,7 +197,7 @@ public class ProfileSettingsAdapter extends RecyclerView.Adapter int settingsId = itemData.get(i).settingsId; ProfileSettingsItem profileSettingsItem = (ProfileSettingsItem) holder.itemView; profileSettingsItem.setOnClickListener(view -> clickListener.onSettingsClicked(settingsId)); - profileSettingsItem.set(itemData.get(i).label); + profileSettingsItem.set(itemData.get(i).label, itemData.get(i).labelColor, itemData.get(i).iconLeft); } } @@ -222,7 +228,7 @@ public class ProfileSettingsAdapter extends RecyclerView.Adapter public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { String txt = ""; switch(getItemViewType(position)) { - case ItemData.TYPE_MEMBER: + case ItemData.CATEGORY_MEMBERS: if (isMailingList) { txt = context.getString(R.string.contacts_headline); } else if (isBroadcast) { @@ -231,19 +237,13 @@ public class ProfileSettingsAdapter extends RecyclerView.Adapter txt = context.getResources().getQuantityString(R.plurals.n_members, (int) itemDataMemberCount, (int) itemDataMemberCount); } break; - case ItemData.TYPE_SHARED_CHAT: + case ItemData.CATEGORY_SHARED_CHATS: txt = context.getString(R.string.profile_shared_chats); break; - case ItemData.TYPE_PRIMARY_SETTING: - long lastSeen = (itemDataContact!=null? itemDataContact.getLastSeen() : 0); - if (lastSeen == 0) { - txt = context.getString(R.string.last_seen_unknown); - } - else { - txt = context.getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(context, locale, lastSeen)); - } + case ItemData.CATEGORY_INFO: + txt = context.getString(R.string.info); break; - case ItemData.TYPE_STATUS: + case ItemData.CATEGORY_SIGNATURE: txt = context.getString(R.string.pref_default_status_label); break; default: @@ -297,14 +297,14 @@ public class ProfileSettingsAdapter extends RecyclerView.Adapter if (dcChat.isMailingList()) { isMailingList = true; } else if (dcChat.canSend()) { - itemData.add(new ItemData(ItemData.TYPE_MEMBER, DcContact.DC_CONTACT_ID_ADD_MEMBER, 0)); + itemData.add(new ItemData(ItemData.CATEGORY_MEMBERS, DcContact.DC_CONTACT_ID_ADD_MEMBER, 0)); if (!isBroadcast) { - itemData.add(new ItemData(ItemData.TYPE_MEMBER, DcContact.DC_CONTACT_ID_QR_INVITE, 0)); + itemData.add(new ItemData(ItemData.CATEGORY_MEMBERS, DcContact.DC_CONTACT_ID_QR_INVITE, 0)); } } for (int value : memberList) { - itemData.add(new ItemData(ItemData.TYPE_MEMBER, value, 0)); + itemData.add(new ItemData(ItemData.CATEGORY_MEMBERS, value, 0)); } } else if (sharedChats!=null && dcContact!=null) { @@ -312,19 +312,44 @@ public class ProfileSettingsAdapter extends RecyclerView.Adapter itemDataContact = dcContact; if (!chatIsDeviceTalk) { - itemData.add(new ItemData(ItemData.TYPE_PRIMARY_SETTING, SETTING_SEND_MESSAGE, context.getString(R.string.send_message))); + if (dcContact.isVerified()) { + int verifierId = dcContact.getVerifierId(); + + String verifiedInfo; + if (verifierId == DcContact.DC_CONTACT_ID_SELF) { + verifiedInfo = context.getString(R.string.verified_by_you); + } else if (verifierId != 0) { + verifiedInfo = context.getString(R.string.verified_by, dcContext.getContact(verifierId).getAddr()); + } else { + verifiedInfo = context.getString(R.string.verified); + } + itemData.add(new ItemData(ItemData.CATEGORY_INFO, INFO_VERIFIED, verifiedInfo, 0, R.drawable.ic_verified)); + } + + long lastSeenTimestamp = (itemDataContact!=null? itemDataContact.getLastSeen() : 0); + String lastSeenTxt; + if (lastSeenTimestamp == 0) { + lastSeenTxt = context.getString(R.string.last_seen_unknown); + } + else { + lastSeenTxt = context.getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(context, locale, lastSeenTimestamp)); + } + itemData.add(new ItemData(ItemData.CATEGORY_INFO, INFO_LAST_SEEN, lastSeenTxt, 0, 0)); + + + itemData.add(new ItemData(ItemData.CATEGORY_INFO, INFO_SEND_MESSAGE_BUTTON, context.getString(R.string.send_message), R.color.delta_accent, 0)); } itemDataStatusText = dcContact.getStatus(); if (!itemDataStatusText.isEmpty()) { - itemData.add(new ItemData(ItemData.TYPE_STATUS, 0, itemDataStatusText)); + itemData.add(new ItemData(ItemData.CATEGORY_SIGNATURE, 0, itemDataStatusText, 0, 0)); } itemDataSharedChats = sharedChats; if (!chatIsDeviceTalk) { int sharedChatsCnt = sharedChats.getCnt(); for (int i = 0; i < sharedChatsCnt; i++) { - itemData.add(new ItemData(ItemData.TYPE_SHARED_CHAT, 0, i)); + itemData.add(new ItemData(ItemData.CATEGORY_SHARED_CHATS, 0, i)); } } } diff --git a/src/org/thoughtcrime/securesms/ProfileSettingsFragment.java b/src/org/thoughtcrime/securesms/ProfileSettingsFragment.java index 2030e4a15..58cbb3754 100644 --- a/src/org/thoughtcrime/securesms/ProfileSettingsFragment.java +++ b/src/org/thoughtcrime/securesms/ProfileSettingsFragment.java @@ -54,7 +54,7 @@ public class ProfileSettingsFragment extends Fragment private StickyHeaderDecoration listDecoration; private ProfileSettingsAdapter adapter; private ActionMode actionMode; - private ActionModeCallback actionModeCallback = new ActionModeCallback(); + private final ActionModeCallback actionModeCallback = new ActionModeCallback(); private Locale locale; @@ -143,9 +143,12 @@ public class ProfileSettingsFragment extends Fragment @Override public void onSettingsClicked(int settingsId) { switch(settingsId) { - case ProfileSettingsAdapter.SETTING_SEND_MESSAGE: + case ProfileSettingsAdapter.INFO_SEND_MESSAGE_BUTTON: onSendMessage(); break; + case ProfileSettingsAdapter.INFO_VERIFIED: + onVerifiedByClicked(); + break; } } @@ -233,6 +236,18 @@ public class ProfileSettingsFragment extends Fragment getActivity().finish(); } + private void onVerifiedByClicked() { + DcContact dcContact = dcContext.getContact(contactId); + if (dcContact.isVerified()) { + int verifierId = dcContact.getVerifierId(); + if (verifierId != 0 && verifierId != DcContact.DC_CONTACT_ID_SELF && verifierId != contactId) { + Intent intent = new Intent(getContext(), ProfileActivity.class); + intent.putExtra(ProfileActivity.CONTACT_ID_EXTRA, verifierId); + startActivity(intent); + } + } + } + private void onSendMessage() { DcContact dcContact = dcContext.getContact(contactId); int chatId = dcContext.createChatByContactId(dcContact.getId()); @@ -256,6 +271,8 @@ public class ProfileSettingsFragment extends Fragment menu.findItem(R.id.show_in_chat).setVisible(false); menu.findItem(R.id.save).setVisible(false); menu.findItem(R.id.share).setVisible(false); + menu.findItem(R.id.menu_resend).setVisible(false); + menu.findItem(R.id.menu_select_all).setVisible(false); mode.setTitle("1"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { diff --git a/src/org/thoughtcrime/securesms/ProfileSettingsItem.java b/src/org/thoughtcrime/securesms/ProfileSettingsItem.java index c14a029a0..8b113c29d 100644 --- a/src/org/thoughtcrime/securesms/ProfileSettingsItem.java +++ b/src/org/thoughtcrime/securesms/ProfileSettingsItem.java @@ -5,6 +5,10 @@ import android.util.AttributeSet; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.core.content.ContextCompat; + +import org.thoughtcrime.securesms.util.ResUtil; + public class ProfileSettingsItem extends LinearLayout { private TextView labelView; @@ -23,7 +27,15 @@ public class ProfileSettingsItem extends LinearLayout { labelView = findViewById(R.id.label); } - public void set(String label) { + public void set(String label, int labelColor, int iconLeft) { labelView.setText(label==null? "" : label); + labelView.setCompoundDrawablesWithIntrinsicBounds(iconLeft, 0,0,0); + + // we need different color getters as `labelColor` is `R.color.name` while default is `R.attr.name` + if (labelColor != 0) { + labelView.setTextColor(ContextCompat.getColor(getContext(), labelColor)); + } else { + labelView.setTextColor(ResUtil.getColor(getContext(), R.attr.emoji_text_color)); + } } } diff --git a/src/org/thoughtcrime/securesms/ResolveMediaTask.java b/src/org/thoughtcrime/securesms/ResolveMediaTask.java index 3f2d46d01..3bf5c6d09 100644 --- a/src/org/thoughtcrime/securesms/ResolveMediaTask.java +++ b/src/org/thoughtcrime/securesms/ResolveMediaTask.java @@ -6,7 +6,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Process; import android.provider.OpenableColumns; -import androidx.annotation.Nullable; import android.util.Log; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; @@ -29,10 +28,10 @@ public class ResolveMediaTask extends AsyncTask { void onMediaResolved(Uri uri); } - private WeakReference contextRef; - private WeakReference listenerWeakReference; + private final WeakReference contextRef; + private final WeakReference listenerWeakReference; - private static HashSet instances = new HashSet<>(); + private static final HashSet instances = new HashSet<>(); ResolveMediaTask(Activity activityContext, ResolveMediaTask.OnMediaResolvedListener listener) { this.contextRef = new WeakReference<>(activityContext); diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java index 3df404a73..92710d00f 100644 --- a/src/org/thoughtcrime/securesms/ShareActivity.java +++ b/src/org/thoughtcrime/securesms/ShareActivity.java @@ -18,6 +18,7 @@ package org.thoughtcrime.securesms; import static org.thoughtcrime.securesms.util.RelayUtil.setSharedText; +import static org.thoughtcrime.securesms.util.RelayUtil.setSharedTitle; import android.Manifest; import android.content.Intent; @@ -38,9 +39,7 @@ import com.b44t.messenger.DcContext; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.RelayUtil; @@ -57,9 +56,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement private static final String TAG = ShareActivity.class.getSimpleName(); public static final String EXTRA_CHAT_ID = "chat_id"; - - private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); + public static final String EXTRA_TITLE = "extra_title"; private ArrayList resolvedExtras; private DcContext dcContext; @@ -67,8 +64,8 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement @Override protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); + dynamicTheme = new DynamicNoActionBarTheme(); + super.onPreCreate(); } @Override @@ -89,14 +86,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement initializeMedia(); } - @Override - public void onResume() { - Log.w(TAG, "onResume()"); - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override protected void onDestroy() { super.onDestroy(); @@ -274,6 +263,12 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement private Intent getBaseShareIntent(final @NonNull Class target) { final Intent intent = new Intent(this, target); + + String title = getIntent().getStringExtra(EXTRA_TITLE); + if (title != null) { + setSharedTitle(intent, title); + } + String text = getIntent().getStringExtra(Intent.EXTRA_TEXT); if (text==null) { CharSequence cs = getIntent().getCharSequenceExtra(Intent.EXTRA_TEXT); diff --git a/src/org/thoughtcrime/securesms/WebViewActivity.java b/src/org/thoughtcrime/securesms/WebViewActivity.java index 12fc967ef..676b83794 100644 --- a/src/org/thoughtcrime/securesms/WebViewActivity.java +++ b/src/org/thoughtcrime/securesms/WebViewActivity.java @@ -18,13 +18,13 @@ import android.widget.ImageView; import android.widget.Toast; import androidx.annotation.RequiresApi; +import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.SearchView; import androidx.webkit.WebSettingsCompat; import androidx.webkit.WebViewFeature; import androidx.webkit.ProxyController; import androidx.webkit.ProxyConfig; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import java.util.concurrent.Executor; @@ -36,13 +36,10 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity private static final String TAG = WebViewActivity.class.getSimpleName(); protected WebView webView; - private final DynamicTheme dynamicTheme = new DynamicTheme(); - protected final DynamicLanguage dynamicLanguage = new DynamicLanguage(); @Override protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); + super.onPreCreate(); if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) { // Set proxy to non-routable address. @@ -71,7 +68,10 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity @Override protected void onCreate(Bundle state, boolean ready) { setContentView(R.layout.web_view_activity); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } webView = findViewById(R.id.webview); webView.setWebViewClient(new WebViewClient() { @@ -103,7 +103,6 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity } @Override - @SuppressWarnings("deprecation") public WebResourceResponse shouldInterceptRequest(WebView view, String url) { WebResourceResponse res = interceptRequest(url); if (res!=null) { @@ -134,9 +133,10 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity } protected void setForceDark() { - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + if (Build.VERSION.SDK_INT <= 32 && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + // needed for older API (tested on android7) that do not set `color-scheme` without the following hint WebSettingsCompat.setForceDark(webView.getSettings(), - DynamicTheme.isDarkTheme(this) ? WebSettingsCompat.FORCE_DARK_ON : WebSettingsCompat.FORCE_DARK_OFF); + DynamicTheme.isDarkTheme(this) ? WebSettingsCompat.FORCE_DARK_ON : WebSettingsCompat.FORCE_DARK_OFF); } } @@ -149,8 +149,6 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity @Override public void onResume() { super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); webView.onResume(); } diff --git a/src/org/thoughtcrime/securesms/WebxdcActivity.java b/src/org/thoughtcrime/securesms/WebxdcActivity.java index 303b68f48..6efbe6d55 100644 --- a/src/org/thoughtcrime/securesms/WebxdcActivity.java +++ b/src/org/thoughtcrime/securesms/WebxdcActivity.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -14,6 +15,7 @@ import android.util.Base64; import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.webkit.JavascriptInterface; import android.webkit.MimeTypeMap; import android.webkit.ValueCallback; @@ -25,6 +27,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; +import androidx.appcompat.app.ActionBar; import androidx.core.app.TaskStackBuilder; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; @@ -36,6 +39,7 @@ import com.b44t.messenger.DcEvent; import com.b44t.messenger.DcMsg; import org.json.JSONObject; +import org.thoughtcrime.securesms.connect.AccountManager; import org.thoughtcrime.securesms.connect.DcEventCenter; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.util.JsonUtils; @@ -44,7 +48,6 @@ import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.Util; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -61,13 +64,15 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE private boolean internetAccess = false; public static void openWebxdcActivity(Context context, DcMsg instance) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (Prefs.isDeveloperModeEnabled(context)) { - WebView.setWebContentsDebuggingEnabled(true); + if (!Util.isClickedRecently()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (Prefs.isDeveloperModeEnabled(context)) { + WebView.setWebContentsDebuggingEnabled(true); + } + context.startActivity(getWebxdcIntent(context, instance.getId())); + } else { + Toast.makeText(context, "At least Android 5.0 (Lollipop) required for Webxdc.", Toast.LENGTH_LONG).show(); } - context.startActivity(getWebxdcIntent(context, instance.getId())); - } else { - Toast.makeText(context, "At least Android 5.0 (Lollipop) required for Webxdc.", Toast.LENGTH_LONG).show(); } } @@ -99,6 +104,10 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE protected void onCreate(Bundle state, boolean ready) { super.onCreate(state, ready); + // enter fullscreen mode if necessary, + // this is needed here because if the app is opened while already in landscape mode, onConfigurationChanged() is not triggered + setScreenMode(); + webView.setWebChromeClient(new WebChromeClient() { @Override @RequiresApi(21) @@ -118,15 +127,16 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE DcEventCenter eventCenter = DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext()); eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this); + eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this); Bundle b = getIntent().getExtras(); int appMessageId = b.getInt("appMessageId"); + int accountId = b.getInt("accountId"); this.dcContext = DcHelper.getContext(getApplicationContext()); - if (dcContext.getAccountId() != b.getInt("accountId")) { - Toast.makeText(this, "Switch to belonging account first.", Toast.LENGTH_LONG).show(); - finish(); - return; + if (accountId != dcContext.getAccountId()) { + AccountManager.getInstance().switchAccount(getApplicationContext(), accountId); + this.dcContext = DcHelper.getContext(getApplicationContext()); } this.dcAppMsg = this.dcContext.getMsg(appMessageId); @@ -197,6 +207,29 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE return false; } + @Override + public void onConfigurationChanged(Configuration newConfig) { + Log.i(TAG, "onConfigurationChanged(" + newConfig.orientation + ")"); + super.onConfigurationChanged(newConfig); + // orientation might have changed, enter/exit fullscreen mode if needed + setScreenMode(); + } + + private void setScreenMode() { + // enter/exit fullscreen mode depending on orientation (landscape/portrait), + // on tablets there is enough height so fullscreen mode is never enabled there + boolean enable = getResources().getBoolean(R.bool.isLandscape) && !getResources().getBoolean(R.bool.isBigScreen); + getWindow().getDecorView().setSystemUiVisibility(enable? View.SYSTEM_UI_FLAG_FULLSCREEN : 0); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + if (enable) { + actionBar.hide(); + } else { + actionBar.show(); + } + } + } + @Override protected boolean openOnlineUrl(String url) { if (url.startsWith("mailto:")) { @@ -238,6 +271,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE if (mimeType == null) { switch (ext) { case "js": mimeType = "text/javascript"; break; + case "wasm": mimeType = "application/wasm"; break; default: mimeType = "application/octet-stream"; Log.i(TAG, "unknown mime type for " + rawUrl); break; } } @@ -251,7 +285,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE res = new WebResourceResponse("text/plain", "UTF-8", targetStream); } - if (res != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Map headers = new HashMap<>(); headers.put("Content-Security-Policy", "default-src 'self'; " @@ -276,6 +310,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE Log.i(TAG, "handleEvent"); webView.loadUrl("javascript:document.getElementById('frame').contentWindow.__webxdcUpdate();"); } else if ((eventId == DcContext.DC_EVENT_MSGS_CHANGED && event.getData2Int() == dcAppMsg.getId())) { + this.dcAppMsg = this.dcContext.getMsg(event.getData2Int()); // msg changed, reload data from db Util.runOnAnyBackgroundThread(() -> { final JSONObject info = dcAppMsg.getWebxdcInfo(); final DcChat chat = dcContext.getChat(dcAppMsg.getChatId()); @@ -409,7 +444,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE return "provided file is invalid, you need to set both name and base64 content"; } - DcHelper.share(WebxdcActivity.this, data, "application/octet-stream", name, text); + DcHelper.sendToChat(WebxdcActivity.this, data, "application/octet-stream", name, text); return null; } catch (Exception e) { e.printStackTrace(); diff --git a/src/org/thoughtcrime/securesms/WelcomeActivity.java b/src/org/thoughtcrime/securesms/WelcomeActivity.java index 2d4d18beb..8f8505caf 100644 --- a/src/org/thoughtcrime/securesms/WelcomeActivity.java +++ b/src/org/thoughtcrime/securesms/WelcomeActivity.java @@ -38,7 +38,6 @@ import org.thoughtcrime.securesms.qr.RegistrationQrActivity; import org.thoughtcrime.securesms.service.GenericForegroundService; import org.thoughtcrime.securesms.service.NotificationController; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.StorageUtil; import org.thoughtcrime.securesms.util.StreamUtil; import org.thoughtcrime.securesms.util.Util; @@ -65,10 +64,14 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen DcContext dcContext; private NotificationController notificationController; + @Override + protected void onPreCreate() { + dynamicTheme = new DynamicNoActionBarTheme(); + super.onPreCreate(); + } + @Override public void onCreate(Bundle bundle) { - DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); - dynamicTheme.onCreate(this); super.onCreate(bundle); setContentView(R.layout.welcome_activity); diff --git a/src/org/thoughtcrime/securesms/accounts/AccountSelectionListAdapter.java b/src/org/thoughtcrime/securesms/accounts/AccountSelectionListAdapter.java index a61fc92fa..66d9cb891 100644 --- a/src/org/thoughtcrime/securesms/accounts/AccountSelectionListAdapter.java +++ b/src/org/thoughtcrime/securesms/accounts/AccountSelectionListAdapter.java @@ -20,9 +20,6 @@ import org.thoughtcrime.securesms.mms.GlideRequests; public class AccountSelectionListAdapter extends RecyclerView.Adapter { - @SuppressWarnings("unused") - private final static String TAG = AccountSelectionListAdapter.class.getSimpleName(); - private final @NonNull Context context; private final @NonNull DcAccounts accounts; private @NonNull int[] accountList = new int[0]; diff --git a/src/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java b/src/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java index 1ad3161cc..be83c84bc 100644 --- a/src/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java +++ b/src/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java @@ -24,9 +24,6 @@ import static com.b44t.messenger.DcContact.DC_CONTACT_ID_ADD_ACCOUNT; public class AccountSelectionListFragment extends DialogFragment { - @SuppressWarnings("unused") - private static final String TAG = AccountSelectionListFragment.class.getSimpleName(); - private RecyclerView recyclerView; @Override diff --git a/src/org/thoughtcrime/securesms/accounts/AccountSelectionListItem.java b/src/org/thoughtcrime/securesms/accounts/AccountSelectionListItem.java index 726be41ce..79d3f37b2 100644 --- a/src/org/thoughtcrime/securesms/accounts/AccountSelectionListItem.java +++ b/src/org/thoughtcrime/securesms/accounts/AccountSelectionListItem.java @@ -24,9 +24,6 @@ import org.thoughtcrime.securesms.util.ViewUtil; public class AccountSelectionListItem extends LinearLayout { - @SuppressWarnings("unused") - private static final String TAG = AccountSelectionListItem.class.getSimpleName(); - private AvatarImageView contactPhotoImage; private View addrContainer; private TextView addrView; diff --git a/src/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java b/src/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java index 4e745773d..dcddc008d 100644 --- a/src/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java +++ b/src/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.audio; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -7,6 +8,7 @@ import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.Pair; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -151,6 +153,7 @@ public class AudioSlidePlayer { setPlaying(AudioSlidePlayer.this); } + keepScreenOn(true); notifyOnStart(); progressEventHandler.sendEmptyMessage(0); break; @@ -163,6 +166,7 @@ public class AudioSlidePlayer { mediaPlayer = null; } + keepScreenOn(false); notifyOnStop(); progressEventHandler.removeMessages(0); } @@ -200,6 +204,7 @@ public class AudioSlidePlayer { public synchronized void stop() { Log.i(TAG, "Stop called!"); + keepScreenOn(false); removePlaying(this); if (this.mediaPlayer != null) { @@ -264,7 +269,7 @@ public class AudioSlidePlayer { Util.runOnMain(new Runnable() { @Override public void run() { - getListener().onProgress(progress, millis); + getListener().onProgress(slide, progress, millis); } }); } @@ -279,12 +284,22 @@ public class AudioSlidePlayer { @Override public void onStop() {} @Override - public void onProgress(double progress, long millis) {} + public void onProgress(AudioSlide slide, double progress, long millis) {} @Override public void onReceivedDuration(int millis) {} }; } + public void keepScreenOn(boolean keepOn) { + if (context instanceof Activity) { + if (keepOn) { + ((Activity) context).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + ((Activity) context).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + } + private synchronized static void setPlaying(@NonNull AudioSlidePlayer player) { if (playing.isPresent() && playing.get() != player) { playing.get().notifyOnStop(); @@ -303,7 +318,7 @@ public class AudioSlidePlayer { public interface Listener { void onStart(); void onStop(); - void onProgress(double progress, long millis); + void onProgress(AudioSlide slide, double progress, long millis); void onReceivedDuration(int millis); } diff --git a/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java b/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java index a0824f47c..930521977 100644 --- a/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java +++ b/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java @@ -27,7 +27,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupWindow; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.permissions.Permissions; @@ -46,9 +45,6 @@ public class AttachmentTypeSelector extends PopupWindow { private static final int ANIMATION_DURATION = 300; - @SuppressWarnings("unused") - private static final String TAG = AttachmentTypeSelector.class.getSimpleName(); - private final @NonNull LoaderManager loaderManager; private final @NonNull RecentPhotoViewRail recentRail; private final @NonNull ImageView imageButton; @@ -62,7 +58,7 @@ public class AttachmentTypeSelector extends PopupWindow { private @Nullable View currentAnchor; private @Nullable AttachmentClickedListener listener; - private int chatId; + private final int chatId; public AttachmentTypeSelector(@NonNull Context context, @NonNull LoaderManager loaderManager, @Nullable AttachmentClickedListener listener, int chatId) { super(context); @@ -117,7 +113,7 @@ public class AttachmentTypeSelector extends PopupWindow { } public void show(@NonNull Activity activity, final @NonNull View anchor) { - if (Permissions.hasAll(activity, Manifest.permission.READ_EXTERNAL_STORAGE)) { + if (Permissions.hasAll(activity, Permissions.galleryPermissions())) { recentRail.setVisibility(View.VISIBLE); loaderManager.restartLoader(1, null, recentRail); } else { diff --git a/src/org/thoughtcrime/securesms/components/AudioView.java b/src/org/thoughtcrime/securesms/components/AudioView.java index e06345909..0a15ccda0 100644 --- a/src/org/thoughtcrime/securesms/components/AudioView.java +++ b/src/org/thoughtcrime/securesms/components/AudioView.java @@ -6,9 +6,10 @@ import android.content.res.ColorStateList; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.AnimatedVectorDrawable; +import android.media.AudioAttributes; +import android.media.AudioFocusRequest; +import android.media.AudioManager; import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; @@ -18,6 +19,9 @@ import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.audio.AudioSlidePlayer; import org.thoughtcrime.securesms.mms.AudioSlide; @@ -36,8 +40,10 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener private final @NonNull SeekBar seekBar; private final @NonNull TextView timestamp; private final @NonNull TextView title; + private final @NonNull View mask; private @Nullable AudioSlidePlayer audioSlidePlayer; + private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener; private int backwardsCounter; public AudioView(Context context) { @@ -58,6 +64,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener this.seekBar = (SeekBar) findViewById(R.id.seek); this.timestamp = (TextView) findViewById(R.id.timestamp); this.title = (TextView) findViewById(R.id.title); + this.mask = findViewById(R.id.interception_mask); this.timestamp.setText("00:00"); @@ -79,6 +86,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener { controlToggle.displayQuick(playButton); seekBar.setEnabled(true); + seekBar.setProgress(0); audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, this); timestamp.setText(DateUtils.getFormatedDuration(duration)); @@ -91,6 +99,42 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener } } + @Override + public void setOnClickListener(OnClickListener listener) { + super.setOnClickListener(listener); + this.mask.setOnClickListener(listener); + } + + @Override + public void setOnLongClickListener(OnLongClickListener listener) { + super.setOnLongClickListener(listener); + this.mask.setOnLongClickListener(listener); + this.playButton.setOnLongClickListener(listener); + this.pauseButton.setOnLongClickListener(listener); + } + + public void togglePlay() { + if (this.playButton.getVisibility() == View.VISIBLE) { + playButton.performClick(); + } else { + pauseButton.performClick(); + } + } + + public String getDescription() { + String desc; + if (this.title.getVisibility() == View.VISIBLE) { + desc = getContext().getString(R.string.audio); + } else { + desc = getContext().getString(R.string.voice_message); + } + desc += "\n" + this.timestamp.getText(); + if (title.getVisibility() == View.VISIBLE) { + desc += "\n" + this.title.getText(); + } + return desc; + } + public void setDuration(int duration) { if (getProgress()==0) this.timestamp.setText(DateUtils.getFormatedDuration(duration)); @@ -122,39 +166,20 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener if (seekBar.getProgress() + 5 >= seekBar.getMax()) { backwardsCounter = 4; - onProgress(0.0, -1); + onProgress(audioSlidePlayer.getAudioSlide(), 0.0, -1); } } - @Override - public void setFocusable(boolean focusable) { - super.setFocusable(focusable); - this.playButton.setFocusable(focusable); - this.pauseButton.setFocusable(focusable); - this.seekBar.setFocusable(focusable); - this.seekBar.setFocusableInTouchMode(focusable); + public void disablePlayer(boolean disable) { + this.mask.setVisibility(disable? View.VISIBLE : View.GONE); } @Override - public void setClickable(boolean clickable) { - super.setClickable(clickable); - this.playButton.setClickable(clickable); - this.pauseButton.setClickable(clickable); - this.seekBar.setClickable(clickable); - this.seekBar.setOnTouchListener(clickable ? null : new TouchIgnoringListener()); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - this.playButton.setEnabled(enabled); - this.pauseButton.setEnabled(enabled); - this.seekBar.setEnabled(enabled); - } - - @Override - public void onProgress(double progress, long millis) { - int seekProgress = (int)Math.floor(progress * this.seekBar.getMax()); + public void onProgress(AudioSlide slide, double progress, long millis) { + if (!audioSlidePlayer.getAudioSlide().equals(slide)) { + return; + } + int seekProgress = (int) Math.floor(progress * this.seekBar.getMax()); if (seekProgress > seekBar.getProgress() || backwardsCounter > 3) { backwardsCounter = 0; @@ -222,6 +247,31 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener try { Log.w(TAG, "playbutton onClick"); if (audioSlidePlayer != null) { + if (Build.VERSION.SDK_INT >= 26) { + if (audioFocusChangeListener == null) { + audioFocusChangeListener = focusChange -> { + if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { + pauseButton.performClick(); + } + }; + } + + AudioAttributes playbackAttributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .build(); + + AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) + .setAudioAttributes(playbackAttributes) + .setAcceptsDelayedFocusGain(false) + .setWillPauseWhenDucked(false) + .setOnAudioFocusChangeListener(audioFocusChangeListener) + .build(); + + AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + audioManager.requestAudioFocus(focusRequest); + } + togglePlayToPause(); audioSlidePlayer.play(getProgress()); } diff --git a/src/org/thoughtcrime/securesms/components/AvatarImageView.java b/src/org/thoughtcrime/securesms/components/AvatarImageView.java index 685037ddc..fdd22011a 100644 --- a/src/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/src/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -20,8 +20,6 @@ import org.thoughtcrime.securesms.util.ThemeUtil; public class AvatarImageView extends AppCompatImageView { - private static final String TAG = AvatarImageView.class.getSimpleName(); - private OnClickListener listener = null; public AvatarImageView(Context context) { diff --git a/src/org/thoughtcrime/securesms/components/AvatarSelector.java b/src/org/thoughtcrime/securesms/components/AvatarSelector.java index ecf987462..89f6336e7 100644 --- a/src/org/thoughtcrime/securesms/components/AvatarSelector.java +++ b/src/org/thoughtcrime/securesms/components/AvatarSelector.java @@ -31,9 +31,6 @@ public class AvatarSelector extends PopupWindow { private static final int ANIMATION_DURATION = 300; - @SuppressWarnings("unused") - private static final String TAG = AttachmentTypeSelector.class.getSimpleName(); - private final @NonNull LoaderManager loaderManager; private final @NonNull RecentPhotoViewRail recentRail; private final @NonNull ImageView imageButton; @@ -80,7 +77,7 @@ public class AvatarSelector extends PopupWindow { } public void show(@NonNull Activity activity, final @NonNull View anchor) { - if (Permissions.hasAll(activity, Manifest.permission.READ_EXTERNAL_STORAGE)) { + if (Permissions.hasAll(activity, Permissions.galleryPermissions())) { recentRail.setVisibility(View.VISIBLE); loaderManager.restartLoader(1, null, recentRail); } else { diff --git a/src/org/thoughtcrime/securesms/components/BorderlessImageView.java b/src/org/thoughtcrime/securesms/components/BorderlessImageView.java index d4b2af102..4d134588d 100644 --- a/src/org/thoughtcrime/securesms/components/BorderlessImageView.java +++ b/src/org/thoughtcrime/securesms/components/BorderlessImageView.java @@ -18,6 +18,7 @@ public class BorderlessImageView extends FrameLayout { private ThumbnailView image; private View missingShade; + private ConversationItemFooter footer; public BorderlessImageView(@NonNull Context context) { super(context); @@ -34,6 +35,7 @@ public class BorderlessImageView extends FrameLayout { this.image = findViewById(R.id.sticker_thumbnail); this.missingShade = findViewById(R.id.sticker_missing_shade); + this.footer = findViewById(R.id.sticker_footer); } @Override @@ -65,6 +67,14 @@ public class BorderlessImageView extends FrameLayout { missingShade.setVisibility(showControls ? View.VISIBLE : View.GONE); } + public String getDescription() { + return getContext().getString(R.string.sticker) + "\n" + footer.getDescription(); + } + + public ConversationItemFooter getFooter() { + return footer; + } + public void setThumbnailClickListener(@NonNull SlideClickListener listener) { image.setThumbnailClickListener(listener); } diff --git a/src/org/thoughtcrime/securesms/components/ContactFilterToolbar.java b/src/org/thoughtcrime/securesms/components/ContactFilterToolbar.java index 4c2ffa397..c8147bb5a 100644 --- a/src/org/thoughtcrime/securesms/components/ContactFilterToolbar.java +++ b/src/org/thoughtcrime/securesms/components/ContactFilterToolbar.java @@ -19,10 +19,10 @@ import org.thoughtcrime.securesms.util.ViewUtil; public class ContactFilterToolbar extends Toolbar { private OnFilterChangedListener listener; - private EditText searchText; - private AnimatingToggle toggle; - private ImageView clearToggle; - private LinearLayout toggleContainer; + private final EditText searchText; + private final AnimatingToggle toggle; + private final ImageView clearToggle; + private final LinearLayout toggleContainer; private boolean useClearButton; public ContactFilterToolbar(Context context) { diff --git a/src/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/src/org/thoughtcrime/securesms/components/ConversationItemFooter.java index b7f480cd0..2c8761f94 100644 --- a/src/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/src/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -58,11 +58,6 @@ public class ConversationItemFooter extends LinearLayout { } } - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - } - public void setMessageRecord(@NonNull DcMsg messageRecord, @NonNull Locale locale) { presentDate(messageRecord, locale); secureIndicatorView.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE); @@ -105,4 +100,13 @@ public class ConversationItemFooter extends LinearLayout { deliveryStatusView.setTint(textColor); // Reset the color to the standard color (because the footer is re-used in a RecyclerView) } } + + public String getDescription() { + String desc = dateView.getText().toString(); + String deliveryDesc = deliveryStatusView.getDescription(); + if (!"".equals(deliveryDesc)) { + desc += "\n" + deliveryDesc; + } + return desc; + } } diff --git a/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java b/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java index 8837ab6f6..2372b9e2c 100644 --- a/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java +++ b/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java @@ -7,6 +7,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; +import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; @@ -86,6 +87,14 @@ public class ConversationItemThumbnail extends FrameLayout { setTouchDelegate(thumbnail.getTouchDelegate()); } + public String getDescription() { + String desc = thumbnail.getDescription(); + if (footer.getVisibility() == View.VISIBLE) { + desc += "\n" + footer.getDescription(); + } + return desc; + } + @Override protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasureSpec) { int width = MeasureSpec.getSize(originalWidthMeasureSpec); @@ -185,6 +194,11 @@ public class ConversationItemThumbnail extends FrameLayout { thumbnail.setThumbnailClickListener(listener); } + @Override + public boolean performClick() { + return thumbnail.performClick(); + } + @UiThread public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide, int naturalWidth, int naturalHeight) diff --git a/src/org/thoughtcrime/securesms/components/CustomDefaultPreference.java b/src/org/thoughtcrime/securesms/components/CustomDefaultPreference.java index c85f1804c..4c325c985 100644 --- a/src/org/thoughtcrime/securesms/components/CustomDefaultPreference.java +++ b/src/org/thoughtcrime/securesms/components/CustomDefaultPreference.java @@ -32,7 +32,7 @@ public class CustomDefaultPreference extends DialogPreference { private final String customPreference; private final String customToggle; - private CustomPreferenceValidator validator; + private final CustomPreferenceValidator validator; private String defaultValue; public CustomDefaultPreference(Context context, AttributeSet attrs) { diff --git a/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java b/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java index 040631f57..b4db7fb68 100644 --- a/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java +++ b/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java @@ -123,4 +123,11 @@ public class DeliveryStatusView { public void resetTint() { deliveryIndicator.setColorFilter(null); } + + public String getDescription() { + if (deliveryIndicator.getVisibility() == View.VISIBLE) { + return deliveryIndicator.getContentDescription().toString(); + } + return ""; + } } diff --git a/src/org/thoughtcrime/securesms/components/DocumentView.java b/src/org/thoughtcrime/securesms/components/DocumentView.java index 5e0ebf31d..b8769eb4a 100644 --- a/src/org/thoughtcrime/securesms/components/DocumentView.java +++ b/src/org/thoughtcrime/securesms/components/DocumentView.java @@ -59,6 +59,13 @@ public class DocumentView extends FrameLayout { this.setOnClickListener(new OpenClickedListener(documentSlide)); } + public String getDescription() { + String desc = getContext().getString(R.string.file); + desc += "\n" + fileName.getText(); + desc += "\n" + fileSize.getText(); + return desc; + } + @Override public void setFocusable(boolean focusable) { super.setFocusable(focusable); diff --git a/src/org/thoughtcrime/securesms/components/FromTextView.java b/src/org/thoughtcrime/securesms/components/FromTextView.java index e96627e8e..06ff4487f 100644 --- a/src/org/thoughtcrime/securesms/components/FromTextView.java +++ b/src/org/thoughtcrime/securesms/components/FromTextView.java @@ -15,10 +15,8 @@ import android.util.AttributeSet; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; -import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.ResUtil; -import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.spans.CenterAlignedRelativeSizeSpan; public class FromTextView extends EmojiTextView { diff --git a/src/org/thoughtcrime/securesms/components/InputPanel.java b/src/org/thoughtcrime/securesms/components/InputPanel.java index 99f9076bd..a466a2b45 100644 --- a/src/org/thoughtcrime/securesms/components/InputPanel.java +++ b/src/org/thoughtcrime/securesms/components/InputPanel.java @@ -22,7 +22,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.view.ViewCompat; import com.b44t.messenger.DcMsg; import com.b44t.messenger.util.concurrent.ListenableFuture; diff --git a/src/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java b/src/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java index 263ab3cc9..4c9dfe638 100644 --- a/src/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java +++ b/src/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java @@ -19,7 +19,6 @@ import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.permissions.Permissions; diff --git a/src/org/thoughtcrime/securesms/components/QuoteView.java b/src/org/thoughtcrime/securesms/components/QuoteView.java index 17ae0cb9a..fa9d4f256 100644 --- a/src/org/thoughtcrime/securesms/components/QuoteView.java +++ b/src/org/thoughtcrime/securesms/components/QuoteView.java @@ -108,11 +108,6 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver { dismissView.setOnClickListener(view -> setVisibility(GONE)); } - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - } - public void setQuote(GlideRequests glideRequests, DcMsg msg, @Nullable Recipient author, diff --git a/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java b/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java index 4e5eee16c..272cc5ed7 100644 --- a/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java +++ b/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java @@ -80,9 +80,6 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo private static class RecentPhotoAdapter extends CursorRecyclerViewAdapter { - @SuppressWarnings("unused") - private static final String TAG = RecentPhotoAdapter.class.getName(); - @NonNull private final Uri baseUri; @Nullable private OnItemClickedListener clickedListener; @@ -132,7 +129,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo static class RecentPhotoViewHolder extends RecyclerView.ViewHolder { - ImageView imageView; + final ImageView imageView; RecentPhotoViewHolder(View itemView) { super(itemView); diff --git a/src/org/thoughtcrime/securesms/components/RepeatableImageKey.java b/src/org/thoughtcrime/securesms/components/RepeatableImageKey.java index 602db3d9f..48e071c94 100644 --- a/src/org/thoughtcrime/securesms/components/RepeatableImageKey.java +++ b/src/org/thoughtcrime/securesms/components/RepeatableImageKey.java @@ -6,11 +6,9 @@ import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.view.HapticFeedbackConstants; -import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; -import android.widget.EditText; import android.widget.ImageButton; public class RepeatableImageKey extends ImageButton { @@ -71,7 +69,7 @@ public class RepeatableImageKey extends ImageButton { } private class RepeaterTouchListener implements OnTouchListener { - private Repeater repeater; + private final Repeater repeater; public RepeaterTouchListener() { this.repeater = new Repeater(); diff --git a/src/org/thoughtcrime/securesms/components/ScaleStableImageView.java b/src/org/thoughtcrime/securesms/components/ScaleStableImageView.java index 1f2a72901..1fc3f006e 100644 --- a/src/org/thoughtcrime/securesms/components/ScaleStableImageView.java +++ b/src/org/thoughtcrime/securesms/components/ScaleStableImageView.java @@ -38,7 +38,7 @@ public class ScaleStableImageView private Drawable defaultDrawable; private Drawable currentDrawable; - private Map storedSizes = new HashMap<>(); + private final Map storedSizes = new HashMap<>(); public ScaleStableImageView(Context context) { this(context, null); diff --git a/src/org/thoughtcrime/securesms/components/SendButton.java b/src/org/thoughtcrime/securesms/components/SendButton.java index ef9c1a1e2..6de464b80 100644 --- a/src/org/thoughtcrime/securesms/components/SendButton.java +++ b/src/org/thoughtcrime/securesms/components/SendButton.java @@ -22,21 +22,18 @@ public class SendButton extends ImageButton private Optional transportOptionsPopup = Optional.absent(); - @SuppressWarnings("unused") public SendButton(Context context) { super(context); this.transportOptions = initializeTransportOptions(); ViewUtil.mirrorIfRtl(this, getContext()); } - @SuppressWarnings("unused") public SendButton(Context context, AttributeSet attrs) { super(context, attrs); this.transportOptions = initializeTransportOptions(); ViewUtil.mirrorIfRtl(this, getContext()); } - @SuppressWarnings("unused") public SendButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.transportOptions = initializeTransportOptions(); diff --git a/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java b/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java index 5ee890358..0572c3481 100644 --- a/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java +++ b/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.components; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.BitmapFactory; import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.widget.FrameLayout; @@ -14,17 +13,15 @@ public class SquareFrameLayout extends FrameLayout { private final boolean squareHeight; - @SuppressWarnings("unused") public SquareFrameLayout(Context context) { this(context, null); } - @SuppressWarnings("unused") public SquareFrameLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } - @TargetApi(VERSION_CODES.HONEYCOMB) @SuppressWarnings("unused") + @TargetApi(VERSION_CODES.HONEYCOMB) public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index 41403f96f..6efd8702c 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -42,8 +42,8 @@ public class ThumbnailView extends FrameLayout { private static final int MIN_HEIGHT = 2; private static final int MAX_HEIGHT = 3; - private ImageView image; - private View playOverlay; + private final ImageView image; + private final View playOverlay; private OnClickListener parentClickListener; private final int[] dimens = new int[2]; @@ -82,6 +82,14 @@ public class ThumbnailView extends FrameLayout { } + public String getDescription() { + if (slide != null && slide.hasPlayOverlay()) { + return getContext().getString(R.string.video); + } else { + return getContext().getString(R.string.image); + } + } + @Override protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasureSpec) { fillTargetDimensions(measureDimens, dimens, bounds); diff --git a/src/org/thoughtcrime/securesms/components/WebxdcView.java b/src/org/thoughtcrime/securesms/components/WebxdcView.java index 552d0464a..a93904673 100644 --- a/src/org/thoughtcrime/securesms/components/WebxdcView.java +++ b/src/org/thoughtcrime/securesms/components/WebxdcView.java @@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.DocumentSlide; import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.util.JsonUtils; -import org.thoughtcrime.securesms.util.Util; import java.io.ByteArrayInputStream; @@ -90,6 +89,15 @@ public class WebxdcView extends FrameLayout { appSubtitle.setText(summary); } + public String getDescription() { + String desc = getContext().getString(R.string.webxdc_app); + desc += "\n" + appName.getText(); + if (appSubtitle.getText() != null && !appSubtitle.getText().toString().equals("") && !appSubtitle.getText().toString().equals(getContext().getString(R.string.webxdc_app))) { + desc += "\n" + appSubtitle.getText(); + } + return desc; + } + private class OpenClickedListener implements View.OnClickListener { private final @NonNull DocumentSlide slide; diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraView.java b/src/org/thoughtcrime/securesms/components/camera/CameraView.java index 973967e3c..d5ce2efa0 100644 --- a/src/org/thoughtcrime/securesms/components/camera/CameraView.java +++ b/src/org/thoughtcrime/securesms/components/camera/CameraView.java @@ -64,7 +64,7 @@ public class CameraView extends ViewGroup { private @NonNull State state = State.PAUSED; private @Nullable Size previewSize; - private @NonNull List listeners = Collections.synchronizedList(new LinkedList()); + private final @NonNull List listeners = Collections.synchronizedList(new LinkedList()); private int outputOrientation = -1; public CameraView(Context context) { diff --git a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java index 249daaedc..96e7f5e2a 100644 --- a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java +++ b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java @@ -53,7 +53,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView, Camer private ObjectAnimator animator; private DrawerState drawerState = DrawerState.COLLAPSED; - private Rect drawChildrenRect = new Rect(); + private final Rect drawChildrenRect = new Rect(); private boolean paused = false; public QuickAttachmentDrawer(Context context) { @@ -310,7 +310,6 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView, Camer } } - @SuppressWarnings("unused") public void setSlideOffset(int slideOffset) { this.slideOffset = slideOffset; requestLayout(); diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiFilter.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiFilter.java index 2b32d5e4d..f028454e0 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/EmojiFilter.java +++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiFilter.java @@ -7,7 +7,7 @@ import android.text.TextUtils; import android.widget.TextView; public class EmojiFilter implements InputFilter { - private TextView view; + private final TextView view; public EmojiFilter(TextView view) { this.view = view; diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java index 3774e79ca..7949fe6eb 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java +++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java @@ -111,10 +111,10 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider, } private static class EmojiPagerAdapter extends PagerAdapter { - private Context context; - private List pages; - private EmojiEventListener emojiSelectionListener; - private VariationSelectorListener variationSelectorListener; + private final Context context; + private final List pages; + private final EmojiEventListener emojiSelectionListener; + private final VariationSelectorListener variationSelectorListener; public EmojiPagerAdapter(@NonNull Context context, @NonNull List pages, diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java index 3245394f3..f080ba695 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java +++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java @@ -17,12 +17,12 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe private static final String TAG = EmojiPageView.class.getSimpleName(); private EmojiPageModel model; - private EmojiPageViewGridAdapter adapter; - private RecyclerView recyclerView; - private GridLayoutManager layoutManager; - private RecyclerView.OnItemTouchListener scrollDisabler; - private VariationSelectorListener variationSelectorListener; - private EmojiVariationSelectorPopup popup; + private final EmojiPageViewGridAdapter adapter; + private final RecyclerView recyclerView; + private final GridLayoutManager layoutManager; + private final RecyclerView.OnItemTouchListener scrollDisabler; + private final VariationSelectorListener variationSelectorListener; + private final EmojiVariationSelectorPopup popup; public EmojiPageView(@NonNull Context context, @NonNull EmojiEventListener emojiSelectionListener, diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java index 293a135a5..cd9c112b4 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java +++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.components.emoji; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; @@ -153,8 +152,8 @@ public class EmojiProvider { class EmojiDrawable extends Drawable { private final EmojiDrawInfo info; private Bitmap bmp; - private float intrinsicWidth; - private float intrinsicHeight; + private final float intrinsicWidth; + private final float intrinsicHeight; @Override public int getIntrinsicWidth() { diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java index 75a145d62..707d0c024 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java +++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -41,7 +41,7 @@ public class EmojiTextView extends AppCompatTextView { private float originalFontSize; private boolean useSystemEmoji; private boolean sizeChangeInProgress; - private int maxLength; + private final int maxLength; private CharSequence overflowText; private CharSequence previousOverflowText; diff --git a/src/org/thoughtcrime/securesms/components/emoji/parsing/EmojiTree.java b/src/org/thoughtcrime/securesms/components/emoji/parsing/EmojiTree.java index af8caa568..4a8abe2b3 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/parsing/EmojiTree.java +++ b/src/org/thoughtcrime/securesms/components/emoji/parsing/EmojiTree.java @@ -95,7 +95,7 @@ public class EmojiTree { private static class EmojiTreeNode { - private Map children = new HashMap<>(); + private final Map children = new HashMap<>(); private EmojiDrawInfo emoji; public void setEmoji(EmojiDrawInfo emoji) { diff --git a/src/org/thoughtcrime/securesms/components/rangeslider/RangeSliderView.java b/src/org/thoughtcrime/securesms/components/rangeslider/RangeSliderView.java index 4e40ccd02..c2d26c7c2 100644 --- a/src/org/thoughtcrime/securesms/components/rangeslider/RangeSliderView.java +++ b/src/org/thoughtcrime/securesms/components/rangeslider/RangeSliderView.java @@ -6,7 +6,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; + import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; @@ -21,23 +21,23 @@ public class RangeSliderView extends View { private static final String TAG = RangeSliderView.class.getName(); - protected int trackTintColor; - protected int trackHighlightTintColor; - protected float trackHeight; - protected float thumbInnerRadius; - protected float thumbOutlineSize; - protected float displayTextFontSize; - protected float displayTextBasicOffsetY; - protected int sliderPaddingLeft; - protected int sliderPaddingRight; + protected final int trackTintColor; + protected final int trackHighlightTintColor; + protected final float trackHeight; + protected final float thumbInnerRadius; + protected final float thumbOutlineSize; + protected final float displayTextFontSize; + protected final float displayTextBasicOffsetY; + protected final int sliderPaddingLeft; + protected final int sliderPaddingRight; - private ThumbLayer minValueThumb; - private TextLayer minValueDisplayLabel; + private final ThumbLayer minValueThumb; + private final TextLayer minValueDisplayLabel; - private ThumbLayer maxValueThumb; - private TextLayer maxValueDisplayLabel; + private final ThumbLayer maxValueThumb; + private final TextLayer maxValueDisplayLabel; - private TrackLayer track; + private final TrackLayer track; private ArrayList values; @@ -56,7 +56,7 @@ public class RangeSliderView extends View { private boolean isThumbViewLocked; private OnValueChangedListener onValueChangedListener; - GestureDetector longPressDetector; + final GestureDetector longPressDetector; public interface OnValueChangedListener { diff --git a/src/org/thoughtcrime/securesms/components/rangeslider/TextLayer.java b/src/org/thoughtcrime/securesms/components/rangeslider/TextLayer.java index 4f6928aa2..0400d89fa 100644 --- a/src/org/thoughtcrime/securesms/components/rangeslider/TextLayer.java +++ b/src/org/thoughtcrime/securesms/components/rangeslider/TextLayer.java @@ -5,7 +5,7 @@ import android.graphics.Paint; class TextLayer { - private Paint paint; + private final Paint paint; TextLayer(float fontSize, int color) { paint = new Paint(); diff --git a/src/org/thoughtcrime/securesms/components/rangeslider/ThumbLayer.java b/src/org/thoughtcrime/securesms/components/rangeslider/ThumbLayer.java index da0864971..0947eee2c 100644 --- a/src/org/thoughtcrime/securesms/components/rangeslider/ThumbLayer.java +++ b/src/org/thoughtcrime/securesms/components/rangeslider/ThumbLayer.java @@ -6,13 +6,13 @@ import android.graphics.Paint; class ThumbLayer { - private float radius; - private float outlineSize; + private final float radius; + private final float outlineSize; - private int highlightColor; + private final int highlightColor; - private Paint outlinePaint; - private Paint fillPaint; + private final Paint outlinePaint; + private final Paint fillPaint; boolean isHighlight = false; diff --git a/src/org/thoughtcrime/securesms/components/rangeslider/TimeRangeSlider.java b/src/org/thoughtcrime/securesms/components/rangeslider/TimeRangeSlider.java index 21e13f4a5..c27dd9f41 100644 --- a/src/org/thoughtcrime/securesms/components/rangeslider/TimeRangeSlider.java +++ b/src/org/thoughtcrime/securesms/components/rangeslider/TimeRangeSlider.java @@ -18,12 +18,12 @@ import java.util.Locale; public class TimeRangeSlider extends RangeSliderView implements RangeSliderView.OnValueChangedListener { private static final int DEFAULT_TIMEFRAME_2D = 60 * 60 * 24 * 2; // 2d private static final float DEFAULT_DELTA = 1000 * 60 * 30; // 30 min - int timeFrame; // timeframe in seconds + final int timeFrame; // timeframe in seconds - Locale locale; + final Locale locale; OnTimestampChangedListener listener; - private TextLayer minRangeDisplayLabel; - private TextLayer maxRangeDisplayLabel; + private final TextLayer minRangeDisplayLabel; + private final TextLayer maxRangeDisplayLabel; float displayLabelOffsetBelow; diff --git a/src/org/thoughtcrime/securesms/components/rangeslider/TrackLayer.java b/src/org/thoughtcrime/securesms/components/rangeslider/TrackLayer.java index 391932138..31d27e44d 100644 --- a/src/org/thoughtcrime/securesms/components/rangeslider/TrackLayer.java +++ b/src/org/thoughtcrime/securesms/components/rangeslider/TrackLayer.java @@ -6,13 +6,13 @@ import android.graphics.RectF; class TrackLayer { - private float height; - private float radius; + private final float height; + private final float radius; private int paddingLeft = 0; private int paddingRight = 0; - private Paint centerPaint; - private Paint backgoundPaint; + private final Paint centerPaint; + private final Paint backgoundPaint; TrackLayer(int paddingLeft, int paddingRight, float height, int color, int highlightColor) { this.height = height; diff --git a/src/org/thoughtcrime/securesms/components/reminder/DozeReminder.java b/src/org/thoughtcrime/securesms/components/reminder/DozeReminder.java index 2925f9bdf..f9a57ca1d 100644 --- a/src/org/thoughtcrime/securesms/components/reminder/DozeReminder.java +++ b/src/org/thoughtcrime/securesms/components/reminder/DozeReminder.java @@ -10,7 +10,6 @@ import android.net.Uri; import android.os.Build; import android.os.PowerManager; import android.provider.Settings; -import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; diff --git a/src/org/thoughtcrime/securesms/connect/AccountManager.java b/src/org/thoughtcrime/securesms/connect/AccountManager.java index fab0c753b..a149aeacf 100644 --- a/src/org/thoughtcrime/securesms/connect/AccountManager.java +++ b/src/org/thoughtcrime/securesms/connect/AccountManager.java @@ -20,7 +20,6 @@ import org.thoughtcrime.securesms.WelcomeActivity; import org.thoughtcrime.securesms.accounts.AccountSelectionListFragment; import org.thoughtcrime.securesms.crypto.DatabaseSecret; import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider; -import org.thoughtcrime.securesms.notifications.NotificationCenter; import java.io.File; diff --git a/src/org/thoughtcrime/securesms/connect/DcContactsLoader.java b/src/org/thoughtcrime/securesms/connect/DcContactsLoader.java index 30e172885..19e985224 100644 --- a/src/org/thoughtcrime/securesms/connect/DcContactsLoader.java +++ b/src/org/thoughtcrime/securesms/connect/DcContactsLoader.java @@ -17,13 +17,15 @@ public class DcContactsLoader extends AsyncLoader { private final int listflags; private final String query; private final boolean addCreateGroupLinks; + private final boolean addCreateContactLink; private final boolean blockedContacts; - public DcContactsLoader(Context context, int listflags, String query, boolean addCreateGroupLinks, boolean blockedContacts) { + public DcContactsLoader(Context context, int listflags, String query, boolean addCreateGroupLinks, boolean addCreateContactLink, boolean blockedContacts) { super(context); this.listflags = listflags; this.query = (query==null||query.isEmpty())? null : query; this.addCreateGroupLinks = addCreateGroupLinks; + this.addCreateContactLink= addCreateContactLink; this.blockedContacts = blockedContacts; } @@ -39,7 +41,7 @@ public class DcContactsLoader extends AsyncLoader { int[] contact_ids = dcContext.getContacts(listflags, query); if(query!=null) { // show the "new contact" link also for partly typed e-mail addresses, so that the user knows he can continue - if (dcContext.lookupContactIdByAddr(query)==0 && (listflags&DcContext.DC_GCL_VERIFIED_ONLY)==0) { + if (addCreateContactLink && dcContext.lookupContactIdByAddr(query)==0 && (listflags&DcContext.DC_GCL_VERIFIED_ONLY)==0) { contact_ids = Util.appendInt(contact_ids, DcContact.DC_CONTACT_ID_NEW_CONTACT); } return new DcContactsLoader.Ret(contact_ids, query); @@ -65,8 +67,8 @@ public class DcContactsLoader extends AsyncLoader { } public class Ret { - public int[] ids; - public String query; + public final int[] ids; + public final String query; Ret(int[] ids, String query) { this.ids = ids; diff --git a/src/org/thoughtcrime/securesms/connect/DcHelper.java b/src/org/thoughtcrime/securesms/connect/DcHelper.java index 7ea578b6e..02a9024b9 100644 --- a/src/org/thoughtcrime/securesms/connect/DcHelper.java +++ b/src/org/thoughtcrime/securesms/connect/DcHelper.java @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import org.thoughtcrime.securesms.qr.QrActivity; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.IntentUtils; +import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.MediaUtil; import java.io.File; @@ -264,7 +265,7 @@ public class DcHelper { return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); } - public static HashMap sharedFiles = new HashMap<>(); + public static final HashMap sharedFiles = new HashMap<>(); public static void openForViewOrShare(Context activity, int msg_id, String cmd) { DcContext dcContext = getContext(activity); @@ -319,7 +320,7 @@ public class DcHelper { } } - public static void share(Context activity, byte[] data, String mimeType, String fileName, String text) { + public static void sendToChat(Context activity, byte[] data, String mimeType, String fileName, String text) { Intent intent = new Intent(activity, ShareActivity.class); intent.setAction(Intent.ACTION_SEND); @@ -327,10 +328,14 @@ public class DcHelper { Uri uri = PersistentBlobProvider.getInstance().create(activity, data, mimeType, fileName); intent.setType(mimeType); intent.putExtra(Intent.EXTRA_STREAM, uri); + intent.putExtra(ShareActivity.EXTRA_TITLE, activity.getString(R.string.send_file_to, fileName)); } if (text != null) { intent.putExtra(Intent.EXTRA_TEXT, text); + if (data == null) { + intent.putExtra(ShareActivity.EXTRA_TITLE, activity.getString(R.string.send_message_to)); + } } activity.startActivity(intent); @@ -358,6 +363,8 @@ public class DcHelper { } public static String getBlobdirFile(DcContext dcContext, String filename, String ext) { + filename = FileUtils.sanitizeFilename(filename); + ext = FileUtils.sanitizeFilename(ext); String outPath = null; for (int i = 0; i < 1000; i++) { String test = dcContext.getBlobdir() + "/" + filename + (i == 0 ? "" : i < 100 ? "-" + i : "-" + (new Date().getTime() + i)) + ext; diff --git a/src/org/thoughtcrime/securesms/connect/FetchWorker.java b/src/org/thoughtcrime/securesms/connect/FetchWorker.java index 0f3f33481..355f55b6c 100644 --- a/src/org/thoughtcrime/securesms/connect/FetchWorker.java +++ b/src/org/thoughtcrime/securesms/connect/FetchWorker.java @@ -10,7 +10,7 @@ import androidx.work.WorkerParameters; import org.thoughtcrime.securesms.util.Util; public class FetchWorker extends Worker { - private @NonNull Context context; + private final @NonNull Context context; public FetchWorker( @NonNull Context context, diff --git a/src/org/thoughtcrime/securesms/connect/ForegroundDetector.java b/src/org/thoughtcrime/securesms/connect/ForegroundDetector.java index 08fda2f3f..1546a04b3 100644 --- a/src/org/thoughtcrime/securesms/connect/ForegroundDetector.java +++ b/src/org/thoughtcrime/securesms/connect/ForegroundDetector.java @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.connect; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Application; -import android.content.Intent; import android.os.Bundle; import android.util.Log; @@ -14,7 +13,7 @@ public class ForegroundDetector implements Application.ActivityLifecycleCallback private int refs = 0; private static ForegroundDetector Instance = null; - private ApplicationContext application; + private final ApplicationContext application; public static ForegroundDetector getInstance() { return Instance; diff --git a/src/org/thoughtcrime/securesms/connect/NetworkStateReceiver.java b/src/org/thoughtcrime/securesms/connect/NetworkStateReceiver.java index 9b0fb03d8..97ac3423c 100644 --- a/src/org/thoughtcrime/securesms/connect/NetworkStateReceiver.java +++ b/src/org/thoughtcrime/securesms/connect/NetworkStateReceiver.java @@ -8,8 +8,6 @@ import android.net.NetworkInfo; import android.os.Build; import android.util.Log; -import com.b44t.messenger.DcContext; - public class NetworkStateReceiver extends BroadcastReceiver { private int debugConnectedCount; diff --git a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java index 053513fc3..0bf2e2006 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java @@ -17,7 +17,6 @@ package org.thoughtcrime.securesms.contacts; import android.content.Context; -import android.text.TextUtils; import android.util.SparseIntArray; import android.view.LayoutInflater; import android.view.View; diff --git a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java index 3d0cd3891..6998e86e7 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java @@ -20,9 +20,6 @@ import org.thoughtcrime.securesms.util.ViewUtil; public class ContactSelectionListItem extends LinearLayout implements RecipientModifiedListener { - @SuppressWarnings("unused") - private static final String TAG = ContactSelectionListItem.class.getSimpleName(); - private AvatarView avatar; private View numberContainer; private TextView numberView; diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java index ceb1fe443..d9e50f40f 100644 --- a/src/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java +++ b/src/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java @@ -4,7 +4,6 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; import androidx.appcompat.content.res.AppCompatResources; import com.amulyakhare.textdrawable.TextDrawable; diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java index 361a2d2cf..00b24e5c3 100644 --- a/src/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java +++ b/src/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java @@ -4,7 +4,6 @@ import android.content.Context; import com.b44t.messenger.DcChat; -import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.database.Address; public class GroupRecordContactPhoto extends LocalFileContactPhoto { diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java index 0644b56c3..45c8bef00 100644 --- a/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java +++ b/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java @@ -4,7 +4,6 @@ import android.content.Context; import com.b44t.messenger.DcContact; -import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.database.Address; public class ProfileContactPhoto extends LocalFileContactPhoto { diff --git a/src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java b/src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java index 6bbe73cef..5a957c368 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java @@ -151,9 +151,6 @@ public final class KeyStoreHelper { public static class SealedData { - @SuppressWarnings("unused") - private static final String TAG = KeyStoreHelper.class.getSimpleName(); - @JsonProperty @JsonSerialize(using = ByteArraySerializer.class) @JsonDeserialize(using = ByteArrayDeserializer.class) @@ -170,7 +167,7 @@ public final class KeyStoreHelper { } @SuppressWarnings("unused") - public SealedData() {} + public SealedData() { /* needed by JsonUtils.fromJson() */ } public String serialize() { try { diff --git a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java index 9567845fd..91b58ba9a 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java @@ -25,9 +25,6 @@ import java.util.Map; public class BucketedThreadMediaLoader extends AsyncTaskLoader { - @SuppressWarnings("unused") - private static final String TAG = BucketedThreadMediaLoader.class.getSimpleName(); - private final int chatId; private final int msgType1; private final int msgType2; @@ -118,6 +115,17 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader getAll() { + LinkedList messages = new LinkedList<>(); + for (TimeBucket section : TIME_SECTIONS) { + messages.addAll(section.records); + } + for (List records : OLDER.months.values()) { + messages.addAll(records); + } + return messages; + } + public int getSectionCount() { return (int)Stream.of(TIME_SECTIONS) .filter(timeBucket -> !timeBucket.isEmpty()) diff --git a/src/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java index e288da2bf..ded87ae75 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java @@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.util.AsyncLoader; public class PagingMediaLoader extends AsyncLoader { - @SuppressWarnings("unused") private static final String TAG = PagingMediaLoader.class.getSimpleName(); private final DcMsg msg; @@ -45,7 +44,6 @@ public class PagingMediaLoader extends AsyncLoader { DcMsg unfound = context.getMsg(msg.getId()); Log.e(TAG, "did not find message in list: " + unfound.getId() + " / " + unfound.getFile() + " / " + unfound.getText()); } - DcMediaGalleryElement retVal = new DcMediaGalleryElement(mediaMessages, currentIndex, context, leftIsRecent); - return retVal; + return new DcMediaGalleryElement(mediaMessages, currentIndex, context, leftIsRecent); } } diff --git a/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java b/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java index c72696aee..b9dd34c74 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java @@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.permissions.Permissions; public class RecentPhotosLoader extends CursorLoader { - public static Uri BASE_URL = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + public static final Uri BASE_URL = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; private static final String[] PROJECTION = new String[] { MediaStore.Images.ImageColumns._ID, @@ -36,7 +36,7 @@ public class RecentPhotosLoader extends CursorLoader { @Override public Cursor loadInBackground() { - if (Permissions.hasAll(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { + if (Permissions.hasAll(context, Permissions.galleryPermissions())) { return context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, PROJECTION, SELECTION, null, MediaStore.Images.ImageColumns.DATE_MODIFIED + " DESC"); diff --git a/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java b/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java index 20d9c485e..675faf6ff 100644 --- a/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java +++ b/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java @@ -8,8 +8,6 @@ import android.location.Location; import android.os.IBinder; import android.util.Log; -import com.b44t.messenger.DcChatlist; - import org.thoughtcrime.securesms.connect.DcHelper; import java.util.LinkedList; @@ -26,10 +24,10 @@ public class DcLocationManager implements Observer { private static final String TAG = DcLocationManager.class.getSimpleName(); private LocationBackgroundService.LocationBackgroundServiceBinder serviceBinder; - private Context context; + private final Context context; private DcLocation dcLocation = DcLocation.getInstance(); - private LinkedList pendingShareLastLocation = new LinkedList<>(); - private ServiceConnection serviceConnection = new ServiceConnection() { + private final LinkedList pendingShareLastLocation = new LinkedList<>(); + private final ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG, "background service connected"); diff --git a/src/org/thoughtcrime/securesms/imageeditor/Bounds.java b/src/org/thoughtcrime/securesms/imageeditor/Bounds.java index 3b074dcce..6655071d6 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/Bounds.java +++ b/src/org/thoughtcrime/securesms/imageeditor/Bounds.java @@ -33,7 +33,7 @@ public final class Bounds { return new RectF(LEFT, TOP, RIGHT, BOTTOM); } - public static RectF FULL_BOUNDS = newFullBounds(); + public static final RectF FULL_BOUNDS = newFullBounds(); public static boolean contains(float x, float y) { return x >= FULL_BOUNDS.left && x <= FULL_BOUNDS.right && diff --git a/src/org/thoughtcrime/securesms/imageeditor/model/AnimationMatrix.java b/src/org/thoughtcrime/securesms/imageeditor/model/AnimationMatrix.java index ea499c69c..202821cc3 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/model/AnimationMatrix.java +++ b/src/org/thoughtcrime/securesms/imageeditor/model/AnimationMatrix.java @@ -19,7 +19,7 @@ final class AnimationMatrix { private final static Interpolator interpolator = new DecelerateInterpolator(); private final static Interpolator pulseInterpolator = inverse(new CycleInterpolator(0.5f)); - static AnimationMatrix NULL = new AnimationMatrix(); + static final AnimationMatrix NULL = new AnimationMatrix(); static { new Matrix().getValues(iValues); diff --git a/src/org/thoughtcrime/securesms/jobmanager/Job.java b/src/org/thoughtcrime/securesms/jobmanager/Job.java index f231873cb..bfd145104 100644 --- a/src/org/thoughtcrime/securesms/jobmanager/Job.java +++ b/src/org/thoughtcrime/securesms/jobmanager/Job.java @@ -21,7 +21,6 @@ import android.os.PowerManager; import org.thoughtcrime.securesms.jobmanager.requirements.Requirement; import java.io.Serializable; -import java.util.List; /** * An abstract class representing a unit of work that can be scheduled with diff --git a/src/org/thoughtcrime/securesms/jobmanager/JobParameters.java b/src/org/thoughtcrime/securesms/jobmanager/JobParameters.java index 14dae988e..5c950b93e 100644 --- a/src/org/thoughtcrime/securesms/jobmanager/JobParameters.java +++ b/src/org/thoughtcrime/securesms/jobmanager/JobParameters.java @@ -21,7 +21,6 @@ import org.thoughtcrime.securesms.jobmanager.requirements.Requirement; import java.io.Serializable; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.TimeUnit; /** * The set of parameters that describe a {@link org.thoughtcrime.securesms.jobmanager.Job}. @@ -82,12 +81,12 @@ public class JobParameters implements Serializable { } public static class Builder { - private List requirements = new LinkedList<>(); - private int retryCount = 100; - private long retryDuration = 0; - private String groupId = null; - private boolean wakeLock = false; - private long wakeLockTimeout = 0; + private final List requirements = new LinkedList<>(); + private final int retryCount = 100; + private final long retryDuration = 0; + private String groupId = null; + private final boolean wakeLock = false; + private final long wakeLockTimeout = 0; /** * Specify a groupId the job should belong to. Jobs with the same groupId are guaranteed to be diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index b57eb607d..f7897d489 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -37,6 +37,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcMsg; @@ -94,7 +95,7 @@ public class AttachmentManager { private WebxdcView webxdcView; //private SignalMapView mapView; - private @NonNull List garbage = new LinkedList<>(); + private final @NonNull List garbage = new LinkedList<>(); private @NonNull Optional slide = Optional.absent(); private @Nullable Uri imageCaptureUri; private @Nullable Uri videoCaptureUri; @@ -294,7 +295,7 @@ public class AttachmentManager { public void onStop() {} @Override - public void onProgress(double progress, long millis) {} + public void onProgress(AudioSlide slide, double progress, long millis) {} @Override public void onReceivedDuration(int millis) { @@ -422,13 +423,31 @@ public class AttachmentManager { return deck; } + public static @Nullable String getFileName(Context context, Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); + } + } finally { + if (cursor != null) cursor.close(); + } + } + if (result == null) { + result = uri.getLastPathSegment(); + } + return result; + } + public static void selectDocument(Activity activity, int requestCode) { selectMediaType(activity, "*/*", null, requestCode); } public static void selectGallery(Activity activity, int requestCode) { Permissions.with(activity) - .request(Manifest.permission.READ_EXTERNAL_STORAGE) + .request(Permissions.galleryPermissions()) .ifNecessary() .withPermanentDenialDialog(activity.getString(R.string.perm_explain_access_to_storage_denied)) .onAllGranted(() -> selectMediaType(activity, "image/*", new String[] {"image/*", "video/*"}, requestCode)) @@ -437,7 +456,7 @@ public class AttachmentManager { public static void selectImage(Activity activity, int requestCode) { Permissions.with(activity) - .request(Manifest.permission.READ_EXTERNAL_STORAGE) + .request(Permissions.galleryPermissions()) .ifNecessary() .withPermanentDenialDialog(activity.getString(R.string.perm_explain_access_to_storage_denied)) .onAllGranted(() -> selectMediaType(activity, "image/*", null, requestCode)) @@ -538,6 +557,12 @@ public class AttachmentManager { captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); captureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); activity.startActivityForResult(captureIntent, requestCode); + } else { + new AlertDialog.Builder(activity) + .setCancelable(false) + .setMessage("Video recording not available") + .setPositiveButton(android.R.string.ok, null) + .show(); } } catch (Exception e) { Log.w(TAG, e); diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java index 4cbeac1c7..421c2db62 100644 --- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -18,13 +18,11 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; import android.net.Uri; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; + import androidx.annotation.Nullable; import com.b44t.messenger.DcMsg; -import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.DcAttachment; import org.thoughtcrime.securesms.attachments.UriAttachment; import org.thoughtcrime.securesms.database.AttachmentDatabase; @@ -47,6 +45,13 @@ public class AudioSlide extends Slide { super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote)); } + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof AudioSlide)) return false; + return this.getDcMsgId() == ((AudioSlide)other).getDcMsgId(); + } + @Override @Nullable public Uri getThumbnailUri() { diff --git a/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java b/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java index 043a6ed75..1a1cacf4f 100644 --- a/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java +++ b/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java @@ -2,16 +2,11 @@ package org.thoughtcrime.securesms.mms; import android.content.ContentResolver; import android.content.Context; -import android.graphics.Bitmap; import android.net.Uri; import android.util.Log; import com.bumptech.glide.load.data.StreamLocalUriFetcher; -import org.thoughtcrime.securesms.util.MediaUtil; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -20,7 +15,7 @@ class DecryptableStreamLocalUriFetcher extends StreamLocalUriFetcher { private static final String TAG = DecryptableStreamLocalUriFetcher.class.getSimpleName(); - private Context context; + private final Context context; DecryptableStreamLocalUriFetcher(Context context, Uri uri) { super(context.getContentResolver(), uri); diff --git a/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java b/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java index 054de985d..eab67bb90 100644 --- a/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java +++ b/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java @@ -55,7 +55,7 @@ public class DecryptableStreamUriLoader implements ModelLoader attachments; - private DcMsg quotedMsg; + private final DcMsg quotedMsg; public QuoteModel(DcContact author, String text, boolean missing, @Nullable List attachments, DcMsg quotedMsg) { this.author = author; diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index c8f32df9f..89d40e9ef 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -18,7 +18,7 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; import android.net.Uri; -import androidx.annotation.DrawableRes; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; diff --git a/src/org/thoughtcrime/securesms/mms/SlideDeck.java b/src/org/thoughtcrime/securesms/mms/SlideDeck.java index 7fe36e5f5..beee26df8 100644 --- a/src/org/thoughtcrime/securesms/mms/SlideDeck.java +++ b/src/org/thoughtcrime/securesms/mms/SlideDeck.java @@ -19,8 +19,6 @@ package org.thoughtcrime.securesms.mms; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.b44t.messenger.DcMsg; - import org.thoughtcrime.securesms.attachments.Attachment; import java.util.LinkedList; diff --git a/src/org/thoughtcrime/securesms/mms/StickerSlide.java b/src/org/thoughtcrime/securesms/mms/StickerSlide.java index 5379e0ae8..2a491a161 100644 --- a/src/org/thoughtcrime/securesms/mms/StickerSlide.java +++ b/src/org/thoughtcrime/securesms/mms/StickerSlide.java @@ -7,7 +7,6 @@ import androidx.annotation.NonNull; import com.b44t.messenger.DcMsg; -import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.DcAttachment; diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java index 6726d188b..9abe76072 100644 --- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java +++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java @@ -18,8 +18,6 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; import android.net.Uri; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; import com.b44t.messenger.DcMsg; diff --git a/src/org/thoughtcrime/securesms/notifications/InChatSounds.java b/src/org/thoughtcrime/securesms/notifications/InChatSounds.java index dfad1ceaa..6838def9c 100644 --- a/src/org/thoughtcrime/securesms/notifications/InChatSounds.java +++ b/src/org/thoughtcrime/securesms/notifications/InChatSounds.java @@ -6,7 +6,6 @@ import android.media.SoundPool; import android.util.Log; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.util.Prefs; public class InChatSounds { private static final String TAG = InChatSounds.class.getSimpleName(); diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index 641b5e9b4..2001d3d00 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.notifications; +import static com.b44t.messenger.DcChat.DC_CHAT_NO_CHAT; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -23,8 +25,8 @@ public class MarkReadReceiver extends BroadcastReceiver { } final int accountId = intent.getIntExtra(ACCOUNT_ID_EXTRA, 0); - final int chatId = intent.getIntExtra(CHAT_ID_EXTRA, 0); - if (accountId == 0 || chatId == 0) { + final int chatId = intent.getIntExtra(CHAT_ID_EXTRA, DC_CHAT_NO_CHAT); + if (accountId == 0 || chatId == DC_CHAT_NO_CHAT) { return; } diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/org/thoughtcrime/securesms/notifications/NotificationCenter.java index 28192047d..3bf8ce800 100644 --- a/src/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -337,6 +337,11 @@ public class NotificationCenter { return; } + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !notificationManager.areNotificationsEnabled()) { + return; + } + if (Util.equals(visibleChat, chatData)) { if (Prefs.isInChatNotifications(context)) { InChatSounds.getInstance(context).playIncomingSound(); @@ -344,8 +349,6 @@ public class NotificationCenter { return; } - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - // get notification text as a single line NotificationPrivacyPreference privacy = Prefs.getNotificationPrivacy(context); @@ -443,8 +446,7 @@ public class NotificationCenter { // add buttons that allow some actions without opening Delta Chat. // if privacy options are enabled, the buttons are not added. - if (privacy.isDisplayContact() && privacy.isDisplayMessage() - && !Prefs.isScreenLockEnabled(context)) { + if (privacy.isDisplayContact() && privacy.isDisplayMessage()) { try { PendingIntent inNotificationReplyIntent = getRemoteReplyIntent(chatData); PendingIntent markReadIntent = getMarkAsReadIntent(chatData, true); @@ -602,8 +604,8 @@ public class NotificationCenter { } private class ChatData { - public int accountId; - public int chatId; + public final int accountId; + public final int chatId; public ChatData(int accountId, int chatId) { this.accountId = accountId; diff --git a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 9122cfcd4..3233bd6d3 100644 --- a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -17,6 +17,8 @@ package org.thoughtcrime.securesms.notifications; +import static com.b44t.messenger.DcChat.DC_CHAT_NO_CHAT; + import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; @@ -47,9 +49,9 @@ public class RemoteReplyReceiver extends BroadcastReceiver { if (!REPLY_ACTION.equals(intent.getAction())) return; Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); final int accountId = intent.getIntExtra(ACCOUNT_ID_EXTRA, 0); - final int chatId = intent.getIntExtra(CHAT_ID_EXTRA, 0); + final int chatId = intent.getIntExtra(CHAT_ID_EXTRA, DC_CHAT_NO_CHAT); - if (remoteInput == null || chatId == 0 || accountId == 0) return; + if (remoteInput == null || chatId == DC_CHAT_NO_CHAT || accountId == 0) return; final CharSequence responseText = remoteInput.getCharSequence(EXTRA_REMOTE_REPLY); diff --git a/src/org/thoughtcrime/securesms/permissions/Permissions.java b/src/org/thoughtcrime/securesms/permissions/Permissions.java index e1aad5377..caee223ac 100644 --- a/src/org/thoughtcrime/securesms/permissions/Permissions.java +++ b/src/org/thoughtcrime/securesms/permissions/Permissions.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.permissions; +import android.Manifest; import android.app.Activity; import androidx.appcompat.app.AlertDialog; import android.content.Context; @@ -34,6 +35,18 @@ import java.util.Map; public class Permissions { + public static String[] galleryPermissions() { + // on modern androids, the gallery picker works without permissions, + // however, the "camera roll" still requires permissions. + // to get that dialog at a UX-wise good moment, we still always ask for permission when opening gallery. + // just-always-asking this also avoids the mess with handling various paths for various apis. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return new String[]{Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO}; + } else { + return new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; + } + } + private static final Map OUTSTANDING = new LRUCache<>(2); public static PermissionsBuilder with(@NonNull Activity activity) { @@ -134,6 +147,18 @@ public class Permissions { } public void execute() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE does not exist on modern androids + // as file access is done by pickers + String[] r = requestedPermissions; + Arrays.sort(r); + if ( (r.length == 1 && (r[0].equals(Manifest.permission.READ_EXTERNAL_STORAGE) || r[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE))) + || (r.length == 2 && r[0].equals(Manifest.permission.READ_EXTERNAL_STORAGE) && r[1].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) ) { + allGrantedListener.run(); + return; + } + } + PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener, someGrantedListener, someDeniedListener, somePermanentlyDeniedListener); @@ -247,7 +272,7 @@ public class Permissions { resultListener.onResult(permissions, grantResults, shouldShowRationaleDialog); } - private static Intent getApplicationSettingsIntent(@NonNull Context context) { + public static Intent getApplicationSettingsIntent(@NonNull Context context) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", context.getPackageName(), null); @@ -275,7 +300,7 @@ public class Permissions { private static class ActivityPermissionObject extends PermissionObject { - private Activity activity; + private final Activity activity; ActivityPermissionObject(@NonNull Activity activity) { this.activity = activity; @@ -304,7 +329,7 @@ public class Permissions { private static class FragmentPermissionObject extends PermissionObject { - private Fragment fragment; + private final Fragment fragment; FragmentPermissionObject(@NonNull Fragment fragment) { this.fragment = fragment; diff --git a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index c094b01fb..6297ab2c6 100644 --- a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -12,6 +12,7 @@ import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -33,18 +34,25 @@ import org.thoughtcrime.securesms.LogViewActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.DcEventCenter; import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.ScreenLockUtil; +import org.thoughtcrime.securesms.util.StorageUtil; +import org.thoughtcrime.securesms.util.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment implements DcEventCenter.DcEventDelegate { private static final String TAG = AdvancedPreferenceFragment.class.getSimpleName(); - - - + public static final int PICK_SELF_KEYS = 29923; CheckBoxPreference preferE2eeCheckbox; CheckBoxPreference sentboxWatchCheckbox; @@ -167,9 +175,34 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_CONFIRM_CREDENTIALS_KEYS) { - exportKeys(); + if (resultCode != RESULT_OK) return; + if (requestCode == REQUEST_CODE_CONFIRM_CREDENTIALS_KEYS) { + exportKeys(); + } else if (requestCode == PICK_SELF_KEYS) { + Uri uri = (data != null ? data.getData() : null); + if (uri == null) { + Log.e(TAG, " Can't import null URI"); + return; + } + try { + String name = AttachmentManager.getFileName(getContext(), uri); + if (name == null || name.isEmpty()) name = "FILE"; + File file = copyToCacheDir(uri); + showImportKeysDialog(file.getAbsolutePath(), name); + } catch (IOException e) { + e.printStackTrace(); } + } + } + + protected File copyToCacheDir(Uri uri) throws IOException { + try (InputStream inputStream = requireActivity().getContentResolver().openInputStream(uri)) { + File file = File.createTempFile("tmp-keys-file", ".tmp", requireActivity().getCacheDir()); + try (OutputStream outputStream = new FileOutputStream(file)) { + StreamUtil.copy(inputStream, outputStream); + } + return file; + } } public static @NonNull String getVersion(@Nullable Context context) { @@ -283,6 +316,15 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment /*********************************************************************************************** * Key Import/Export **********************************************************************************************/ + protected void showImportKeysDialog(String imexPath, String pathAsDisplayedToUser) { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.pref_managekeys_import_secret_keys) + .setMessage(getActivity().getString(R.string.pref_managekeys_import_explain, pathAsDisplayedToUser)) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, (dialogInterface2, i2) -> startImex(DcContext.DC_IMEX_IMPORT_SELF_KEYS, imexPath, pathAsDisplayedToUser)) + .show(); + } + private class ManageKeysListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { @@ -316,12 +358,12 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment .show(); } else { - new AlertDialog.Builder(getActivity()) - .setTitle(R.string.pref_managekeys_import_secret_keys) - .setMessage(getActivity().getString(R.string.pref_managekeys_import_explain, DcHelper.getImexDir().getAbsolutePath())) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, (dialogInterface2, i2) -> startImex(DcContext.DC_IMEX_IMPORT_SELF_KEYS)) - .show(); + if (Build.VERSION.SDK_INT >= 30) { + AttachmentManager.selectMediaType(getActivity(), "application/pgp-keys", new String[]{"text/plain"}, PICK_SELF_KEYS, StorageUtil.getDownloadUri()); + } else { + String path = DcHelper.getImexDir().getAbsolutePath(); + showImportKeysDialog(path, path); + } } } ) diff --git a/src/org/thoughtcrime/securesms/preferences/AppearancePreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AppearancePreferenceFragment.java index 944a464e9..5372c10e3 100644 --- a/src/org/thoughtcrime/securesms/preferences/AppearancePreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/AppearancePreferenceFragment.java @@ -46,7 +46,7 @@ public class AppearancePreferenceFragment extends ListSummaryPreferenceFragment public void onResume() { super.onResume(); ((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.pref_appearance); - String imagePath = Prefs.getBackgroundImagePath(getContext()); + String imagePath = Prefs.getBackgroundImagePath(getContext(), dcContext.getAccountId()); String backgroundString; if(imagePath.isEmpty()){ backgroundString = this.getString(R.string.def); @@ -75,7 +75,7 @@ public class AppearancePreferenceFragment extends ListSummaryPreferenceFragment int themeIndex = Arrays.asList(themeEntryValues).indexOf(Prefs.getTheme(context)); if (themeIndex == -1) themeIndex = 0; - String imagePath = Prefs.getBackgroundImagePath(context); + String imagePath = Prefs.getBackgroundImagePath(context, DcHelper.getContext(context).getAccountId()); String backgroundString; if(imagePath.isEmpty()){ backgroundString = context.getString(R.string.def); diff --git a/src/org/thoughtcrime/securesms/preferences/ChatBackgroundActivity.java b/src/org/thoughtcrime/securesms/preferences/ChatBackgroundActivity.java index b0aa6fa50..dea68ce2d 100644 --- a/src/org/thoughtcrime/securesms/preferences/ChatBackgroundActivity.java +++ b/src/org/thoughtcrime/securesms/preferences/ChatBackgroundActivity.java @@ -7,7 +7,6 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import androidx.appcompat.app.ActionBar; import android.view.Display; @@ -24,8 +23,9 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.Prefs; @@ -36,9 +36,6 @@ import java.util.concurrent.ExecutionException; public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity { - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - Button galleryButton; Button defaultButton; MenuItem acceptMenuItem; @@ -48,11 +45,7 @@ public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity Uri imageUri; Boolean imageUpdate = false; - @Override - protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); - } + private int accountId; @Override protected void onCreate(Bundle savedInstanceState, boolean ready) { @@ -61,11 +54,12 @@ public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity defaultButton = findViewById(R.id.set_default_button); galleryButton = findViewById(R.id.from_gallery_button); preview = findViewById(R.id.preview); + accountId = DcHelper.getContext(getApplicationContext()).getAccountId(); defaultButton.setOnClickListener(new DefaultClickListener()); galleryButton.setOnClickListener(new GalleryClickListener()); - String backgroundImagePath = Prefs.getBackgroundImagePath(this); + String backgroundImagePath = Prefs.getBackgroundImagePath(this, accountId); if(backgroundImagePath.isEmpty()){ setDefaultLayoutBackgroundImage(); }else { @@ -81,13 +75,6 @@ public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity } - @Override - protected void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuInflater inflater = this.getMenuInflater(); @@ -110,14 +97,14 @@ public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity Thread thread = new Thread() { @Override public void run() { - String destination = context.getFilesDir().getAbsolutePath() + "/background"; - Prefs.setBackgroundImagePath(context, destination); + String destination = context.getFilesDir().getAbsolutePath() + "/background."+ accountId; + Prefs.setBackgroundImagePath(context, accountId, destination); scaleAndSaveImage(context, destination); } }; thread.start(); } else { - Prefs.setBackgroundImagePath(context, ""); + Prefs.setBackgroundImagePath(context, accountId, ""); } } finish(); @@ -137,7 +124,7 @@ public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity Point size = new Point(); display.getSize(size); // resize so that the larger side fits the screen accurately - int largerSide = (size.x > size.y ? size.x : size.y); + int largerSide = Math.max(size.x, size.y); Bitmap scaledBitmap = GlideApp.with(context) .asBitmap() .load(imageUri) @@ -150,15 +137,15 @@ public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 85, outStream); } catch (InterruptedException e) { e.printStackTrace(); - Prefs.setBackgroundImagePath(context, ""); + Prefs.setBackgroundImagePath(context, accountId, ""); showBackgroundSaveError(); } catch (ExecutionException e) { e.printStackTrace(); - Prefs.setBackgroundImagePath(context, ""); + Prefs.setBackgroundImagePath(context, accountId, ""); showBackgroundSaveError(); } catch (FileNotFoundException e) { e.printStackTrace(); - Prefs.setBackgroundImagePath(context, ""); + Prefs.setBackgroundImagePath(context, accountId, ""); showBackgroundSaveError(); } } @@ -169,7 +156,7 @@ public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity } private void setDefaultLayoutBackgroundImage() { - if(dynamicTheme.isDarkTheme(this)) { + if(DynamicTheme.isDarkTheme(this)) { Drawable image = getResources().getDrawable(R.drawable.background_hd_dark); preview.setImageDrawable(image); } @@ -222,17 +209,7 @@ public class ChatBackgroundActivity extends PassphraseRequiredActionBarActivity private class GalleryClickListener implements View.OnClickListener { @Override public void onClick(View view) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("image/*"); - startActivityForResult(intent, ApplicationPreferencesActivity.REQUEST_CODE_SET_BACKGROUND); - } else { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("image/*"); - startActivityForResult(intent, ApplicationPreferencesActivity.REQUEST_CODE_SET_BACKGROUND); - } + AttachmentManager.selectImage(ChatBackgroundActivity.this, ApplicationPreferencesActivity.REQUEST_CODE_SET_BACKGROUND); } } } diff --git a/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java index d4c536a46..967b937a2 100644 --- a/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java @@ -21,7 +21,7 @@ import androidx.preference.Preference; import com.b44t.messenger.DcContext; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; -import org.thoughtcrime.securesms.BlockedAndShareContactsActivity; +import org.thoughtcrime.securesms.BlockedContactsActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.permissions.Permissions; @@ -185,8 +185,7 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment { private class BlockedContactsClickListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(getActivity(), BlockedAndShareContactsActivity.class); - intent.putExtra(BlockedAndShareContactsActivity.SHOW_ONLY_BLOCKED_EXTRA, true); + Intent intent = new Intent(getActivity(), BlockedContactsActivity.class); startActivity(intent); return true; } @@ -202,7 +201,7 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment { } private class AutodelChangeListener implements Preference.OnPreferenceChangeListener { - private String coreKey; + private final String coreKey; AutodelChangeListener(String coreKey) { this.coreKey = coreKey; diff --git a/src/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java index 879bf6fbf..6972127a6 100644 --- a/src/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java @@ -98,12 +98,19 @@ public abstract class ListSummaryPreferenceFragment extends CorrectedPreferenceF pref.setSummary(pref.getEntry()); } - protected ProgressDialog progressDialog = null; - protected int progressWhat = 0; - protected String imexDir = ""; - protected boolean imexUserAborted = false; protected void startImex(int what) { + String path = DcHelper.getImexDir().getAbsolutePath(); + startImex(what, path, path); + } + + protected ProgressDialog progressDialog = null; + protected int progressWhat = 0; + protected String pathAsDisplayedToUser = ""; + protected boolean imexUserAborted = false; + protected void startImex(int what, String imexPath, String pathAsDisplayedToUser) + { + this.pathAsDisplayedToUser = pathAsDisplayedToUser; imexUserAborted = false; notificationController = GenericForegroundService.startForegroundTask(getContext(), getString(R.string.export_backup_desktop)); if( progressDialog!=null ) { @@ -123,10 +130,9 @@ public abstract class ListSummaryPreferenceFragment extends CorrectedPreferenceF }); progressDialog.show(); - imexDir = DcHelper.getImexDir().getAbsolutePath(); DcHelper.getAccounts(getActivity()).stopIo(); DcHelper.getEventCenter(getActivity()).captureNextError(); - dcContext.imex(progressWhat, imexDir); + dcContext.imex(progressWhat, imexPath); } @Override @@ -163,13 +169,13 @@ public abstract class ListSummaryPreferenceFragment extends CorrectedPreferenceF notificationController = null; String msg = ""; if (progressWhat==DcContext.DC_IMEX_EXPORT_BACKUP) { - msg = context.getString(R.string.pref_backup_written_to_x, imexDir); + msg = context.getString(R.string.pref_backup_written_to_x, pathAsDisplayedToUser); } else if (progressWhat==DcContext.DC_IMEX_EXPORT_SELF_KEYS) { - msg = context.getString(R.string.pref_managekeys_secret_keys_exported_to_x, imexDir); + msg = context.getString(R.string.pref_managekeys_secret_keys_exported_to_x, pathAsDisplayedToUser); } else if (progressWhat==DcContext.DC_IMEX_IMPORT_SELF_KEYS) { - msg = context.getString(R.string.pref_managekeys_secret_keys_imported_from_x, imexDir); + msg = context.getString(R.string.pref_managekeys_secret_keys_imported_from_x, pathAsDisplayedToUser); } new AlertDialog.Builder(context) .setMessage(msg) diff --git a/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java index 37bbdf630..0d11d4e35 100644 --- a/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java @@ -12,6 +12,7 @@ import android.os.Bundle; import android.os.PowerManager; import android.provider.Settings; import androidx.annotation.Nullable; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.preference.CheckBoxPreference; import androidx.preference.ListPreference; @@ -21,14 +22,13 @@ import android.text.TextUtils; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.KeepAliveService; +import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.Prefs; import static android.app.Activity.RESULT_OK; public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment { - @SuppressWarnings("unused") - private static final String TAG = NotificationsPreferenceFragment.class.getSimpleName(); private static final int REQUEST_CODE_NOTIFICATION_SELECTED = 1; private CheckBoxPreference ignoreBattery; @@ -198,8 +198,11 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme } public static CharSequence getSummary(Context context) { - boolean notificationsEnabled = Prefs.isNotificationsEnabled(context); - String ret = context.getString(notificationsEnabled ? R.string.on : R.string.off); - return ret; + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || notificationManager.areNotificationsEnabled()) { + return context.getString(Prefs.isNotificationsEnabled(context) ? R.string.on : R.string.off); + } else { + return context.getString(R.string.disabled_in_system_settings); + } } } diff --git a/src/org/thoughtcrime/securesms/providers/SingleUseBlobProvider.java b/src/org/thoughtcrime/securesms/providers/SingleUseBlobProvider.java index 4bad3b2d4..ca61637dc 100644 --- a/src/org/thoughtcrime/securesms/providers/SingleUseBlobProvider.java +++ b/src/org/thoughtcrime/securesms/providers/SingleUseBlobProvider.java @@ -14,9 +14,6 @@ import java.util.Map; public class SingleUseBlobProvider { - @SuppressWarnings("unused") - private static final String TAG = SingleUseBlobProvider.class.getSimpleName(); - public static final String AUTHORITY = "org.thoughtcrime.securesms"; public static final String PATH = "memory/*/#"; private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/memory"); diff --git a/src/org/thoughtcrime/securesms/qr/BackupTransferActivity.java b/src/org/thoughtcrime/securesms/qr/BackupTransferActivity.java index d68f51529..fadf4b5fc 100644 --- a/src/org/thoughtcrime/securesms/qr/BackupTransferActivity.java +++ b/src/org/thoughtcrime/securesms/qr/BackupTransferActivity.java @@ -24,8 +24,6 @@ import org.thoughtcrime.securesms.WebViewActivity; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.service.GenericForegroundService; import org.thoughtcrime.securesms.service.NotificationController; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Util; public class BackupTransferActivity extends BaseActionBarActivity { @@ -54,9 +52,6 @@ public class BackupTransferActivity extends BaseActionBarActivity { private TransferMode transferMode = TransferMode.RECEIVER_SCAN_QR; private TransferState transferState = TransferState.TRANSFER_UNKNOWN; - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - NotificationController notificationController; private boolean notificationControllerClosed = false; public boolean warnAboutCopiedQrCodeOnAbort = false; @@ -64,8 +59,6 @@ public class BackupTransferActivity extends BaseActionBarActivity { @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); transferMode = TransferMode.fromInt(getIntent().getIntExtra(TRANSFER_MODE, TransferMode.INVALID.getInt())); if (transferMode == TransferMode.INVALID) { @@ -104,13 +97,6 @@ public class BackupTransferActivity extends BaseActionBarActivity { DcHelper.getAccounts(this).startIo(); } - @Override - protected void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); @@ -220,7 +206,7 @@ public class BackupTransferActivity extends BaseActionBarActivity { new Thread(() -> { try { // depending on the android version, getting the SSID requires none, all or one of - // ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, ACCESS_NETWORK_STATE and maybe even more. + // ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, NEARBY_WIFI_DEVICES, ACCESS_WIFI_STATE, ACCESS_NETWORK_STATE and maybe even more. final WifiManager wifiManager = (WifiManager)activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE); if (wifiManager.isWifiEnabled()) { final WifiInfo info = wifiManager.getConnectionInfo(); diff --git a/src/org/thoughtcrime/securesms/qr/QrActivity.java b/src/org/thoughtcrime/securesms/qr/QrActivity.java index f3fe4427d..2ddb5181e 100644 --- a/src/org/thoughtcrime/securesms/qr/QrActivity.java +++ b/src/org/thoughtcrime/securesms/qr/QrActivity.java @@ -32,9 +32,7 @@ import org.thoughtcrime.securesms.BaseActionBarActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; @@ -43,9 +41,6 @@ import java.io.InputStream; public class QrActivity extends BaseActionBarActivity { - private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private final static String TAG = QrActivity.class.getSimpleName(); private final static int REQUEST_CODE_IMAGE = 46243; private final static int TAB_SHOW = 0; @@ -55,11 +50,15 @@ public class QrActivity extends BaseActionBarActivity { private ViewPager viewPager; private QrShowFragment qrShowFragment; + @Override + protected void onPreCreate() { + dynamicTheme = new DynamicNoActionBarTheme(); + super.onPreCreate(); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); setContentView(R.layout.activity_qr); tabLayout = ViewUtil.findById(this, R.id.tab_layout); @@ -119,14 +118,6 @@ public class QrActivity extends BaseActionBarActivity { return super.onPrepareOptionsMenu(menu); } - @Override - protected void onResume() { - super.onResume(); - - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - @Override protected void onPause() { super.onPause(); @@ -216,7 +207,7 @@ public class QrActivity extends BaseActionBarActivity { private class ProfilePagerAdapter extends FragmentStatePagerAdapter { - private QrActivity activity; + private final QrActivity activity; ProfilePagerAdapter(QrActivity activity, FragmentManager fragmentManager) { super(fragmentManager, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); diff --git a/src/org/thoughtcrime/securesms/qr/QrCodeHandler.java b/src/org/thoughtcrime/securesms/qr/QrCodeHandler.java index 68183089c..8e2b909f0 100644 --- a/src/org/thoughtcrime/securesms/qr/QrCodeHandler.java +++ b/src/org/thoughtcrime/securesms/qr/QrCodeHandler.java @@ -1,13 +1,11 @@ package org.thoughtcrime.securesms.qr; import android.app.Activity; -import android.content.DialogInterface; import android.content.Intent; -import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; -import android.text.Html; + import android.widget.Toast; import com.b44t.messenger.DcContext; @@ -23,8 +21,8 @@ import org.thoughtcrime.securesms.util.Util; public class QrCodeHandler { - private Activity activity; - private DcContext dcContext; + private final Activity activity; + private final DcContext dcContext; public QrCodeHandler(Activity activity) { this.activity = activity; diff --git a/src/org/thoughtcrime/securesms/qr/QrScanFragment.java b/src/org/thoughtcrime/securesms/qr/QrScanFragment.java index 6dfc3138f..76233bd65 100644 --- a/src/org/thoughtcrime/securesms/qr/QrScanFragment.java +++ b/src/org/thoughtcrime/securesms/qr/QrScanFragment.java @@ -95,7 +95,7 @@ public class QrScanFragment extends Fragment { } public class MyCaptureManager extends CaptureManager { - private Activity myActivity; + private final Activity myActivity; public MyCaptureManager(Activity activity, DecoratedBarcodeView barcodeView) { super(activity, barcodeView); diff --git a/src/org/thoughtcrime/securesms/qr/QrShowFragment.java b/src/org/thoughtcrime/securesms/qr/QrShowFragment.java index 803ce9098..c89590274 100644 --- a/src/org/thoughtcrime/securesms/qr/QrShowFragment.java +++ b/src/org/thoughtcrime/securesms/qr/QrShowFragment.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.qr; -import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; diff --git a/src/org/thoughtcrime/securesms/qr/RegistrationQrActivity.java b/src/org/thoughtcrime/securesms/qr/RegistrationQrActivity.java index f2aadc60d..1187d100b 100644 --- a/src/org/thoughtcrime/securesms/qr/RegistrationQrActivity.java +++ b/src/org/thoughtcrime/securesms/qr/RegistrationQrActivity.java @@ -16,16 +16,11 @@ import com.journeyapps.barcodescanner.CompoundBarcodeView; import org.thoughtcrime.securesms.BaseActionBarActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicTheme; public class RegistrationQrActivity extends BaseActionBarActivity { public static final String ADD_AS_SECOND_DEVICE_EXTRA = "add_as_second_device"; - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private CaptureManager capture; private CompoundBarcodeView barcodeScannerView; @@ -33,8 +28,6 @@ public class RegistrationQrActivity extends BaseActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); boolean addAsAnotherDevice = getIntent().getBooleanExtra(ADD_AS_SECOND_DEVICE_EXTRA, false); if (addAsAnotherDevice) { @@ -103,8 +96,6 @@ public class RegistrationQrActivity extends BaseActionBarActivity { @Override protected void onResume() { super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); if (capture != null) { capture.onResume(); } @@ -138,4 +129,4 @@ public class RegistrationQrActivity extends BaseActionBarActivity { public boolean onKeyDown(int keyCode, KeyEvent event) { return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); } -} \ No newline at end of file +} diff --git a/src/org/thoughtcrime/securesms/reactions/ReactionRecipientItem.java b/src/org/thoughtcrime/securesms/reactions/ReactionRecipientItem.java new file mode 100644 index 000000000..3bac30608 --- /dev/null +++ b/src/org/thoughtcrime/securesms/reactions/ReactionRecipientItem.java @@ -0,0 +1,77 @@ +package org.thoughtcrime.securesms.reactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.b44t.messenger.DcContact; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.AvatarImageView; +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.ViewUtil; + +public class ReactionRecipientItem extends LinearLayout { + + private AvatarImageView contactPhotoImage; + private View addrContainer; + private TextView addrView; + private TextView nameView; + private TextView reactionView; + + private int contactId; + + public ReactionRecipientItem(Context context) { + super(context); + } + + public ReactionRecipientItem(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + this.contactPhotoImage = findViewById(R.id.contact_photo_image); + this.addrContainer = findViewById(R.id.addr_container); + this.addrView = findViewById(R.id.addr); + this.nameView = findViewById(R.id.name); + this.reactionView = findViewById(R.id.reaction); + + ViewUtil.setTextViewGravityStart(this.nameView, getContext()); + } + + public void bind(@NonNull GlideRequests glideRequests, int contactId, String reaction) { + this.contactId = contactId; + DcContact dcContact = DcHelper.getContext(getContext()).getContact(contactId); + Recipient recipient = new Recipient(getContext(), dcContact); + this.contactPhotoImage.setAvatar(glideRequests, recipient, false); + this.reactionView.setText(reaction); + setText(dcContact.getDisplayName(), dcContact.getAddr()); + } + + public void unbind(GlideRequests glideRequests) { + contactPhotoImage.clear(glideRequests); + } + + private void setText(String name, String addr) { + this.nameView.setText(name==null? "#" : name); + + if(addr != null) { + this.addrView.setText(addr); + this.addrContainer.setVisibility(View.VISIBLE); + } else { + this.addrContainer.setVisibility(View.GONE); + } + } + + public int getContactId() { + return contactId; + } +} diff --git a/src/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java b/src/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java new file mode 100644 index 000000000..d86cf473e --- /dev/null +++ b/src/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java @@ -0,0 +1,102 @@ +package org.thoughtcrime.securesms.reactions; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.util.Pair; + +import java.util.ArrayList; + +public class ReactionRecipientsAdapter extends RecyclerView.Adapter +{ + private @NonNull ArrayList> contactsReactions = new ArrayList<>(); + private final LayoutInflater layoutInflater; + private final ItemClickListener clickListener; + private final GlideRequests glideRequests; + + @Override + public int getItemCount() { + return contactsReactions.size(); + } + + public abstract static class ViewHolder extends RecyclerView.ViewHolder { + + public ViewHolder(View itemView) { + super(itemView); + } + + public abstract void bind(@NonNull GlideRequests glideRequests, int contactId, String reaction); + public abstract void unbind(@NonNull GlideRequests glideRequests); + } + + public static class ReactionViewHolder extends ViewHolder { + + ReactionViewHolder(@NonNull final View itemView, + @Nullable final ItemClickListener clickListener) { + super(itemView); + itemView.setOnClickListener(view -> { + if (clickListener != null) { + clickListener.onItemClick(getView()); + } + }); + } + + public ReactionRecipientItem getView() { + return (ReactionRecipientItem) itemView; + } + + public void bind(@NonNull GlideRequests glideRequests, int contactId, String reaction) { + getView().bind(glideRequests, contactId, reaction); + } + + @Override + public void unbind(@NonNull GlideRequests glideRequests) { + getView().unbind(glideRequests); + } + } + + public ReactionRecipientsAdapter(@NonNull Context context, + @NonNull GlideRequests glideRequests, + @Nullable ItemClickListener clickListener) + { + super(); + this.layoutInflater = LayoutInflater.from(context); + this.glideRequests = glideRequests; + this.clickListener = clickListener; + } + + @NonNull + @Override + public ReactionRecipientsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ReactionViewHolder(layoutInflater.inflate(R.layout.reaction_recipient_item, parent, false), clickListener); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) { + + Pair pair = contactsReactions.get(i); + Integer contactId = pair.first(); + String reaction = pair.second(); + + ViewHolder holder = (ViewHolder) viewHolder; + holder.unbind(glideRequests); + holder.bind(glideRequests, contactId, reaction); + } + + public interface ItemClickListener { + void onItemClick(ReactionRecipientItem item); + } + + public void changeData(ArrayList> contactsReactions) { + this.contactsReactions = contactsReactions==null? new ArrayList<>() : contactsReactions; + notifyDataSetChanged(); + } +} diff --git a/src/org/thoughtcrime/securesms/reactions/ReactionsConversationView.java b/src/org/thoughtcrime/securesms/reactions/ReactionsConversationView.java new file mode 100644 index 000000000..ac6f84d8b --- /dev/null +++ b/src/org/thoughtcrime/securesms/reactions/ReactionsConversationView.java @@ -0,0 +1,124 @@ +package org.thoughtcrime.securesms.reactions; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import com.b44t.messenger.rpc.Reaction; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.emoji.EmojiTextView; +import org.thoughtcrime.securesms.util.ViewUtil; + +import java.util.ArrayList; +import java.util.List; + +public class ReactionsConversationView extends LinearLayout { + + // Normally 6dp, but we have 1dp left+right margin on the pills themselves + private static final int OUTER_MARGIN = ViewUtil.dpToPx(5); + + private final List reactions = new ArrayList<>(); + private boolean isIncoming; + + public ReactionsConversationView(Context context) { + super(context); + init(null); + } + + public ReactionsConversationView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + private void init(@Nullable AttributeSet attrs) { + if (attrs != null) { + TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ReactionsConversationView, 0, 0); + isIncoming = typedArray.getInt(R.styleable.ReactionsConversationView_reaction_type, 0) == 2; + } + } + + public void clear() { + this.reactions.clear(); + removeAllViews(); + } + + public void setReactions(List reactions) { + if (reactions.equals(this.reactions)) { + return; + } + + clear(); + this.reactions.addAll(reactions); + + for (Reaction reaction : buildShortenedReactionsList(this.reactions)) { + View pill = buildPill(getContext(), this, reaction); + addView(pill); + } + + if (isIncoming) { + ViewUtil.setLeftMargin(this, OUTER_MARGIN); + } else { + ViewUtil.setRightMargin(this, OUTER_MARGIN); + } + } + + private static @NonNull List buildShortenedReactionsList(@NonNull List reactions) { + if (reactions.size() > 3) { + List shortened = new ArrayList<>(3); + shortened.add(reactions.get(0)); + shortened.add(reactions.get(1)); + int count = 0; + boolean isFromSelf = false; + for (int index = 2; index < reactions.size(); index++) { + count += reactions.get(index).getCount(); + isFromSelf = isFromSelf || reactions.get(index).isFromSelf(); + } + shortened.add(new Reaction(null, count, isFromSelf)); + + return shortened; + } else { + return reactions; + } + } + + private static View buildPill(@NonNull Context context, @NonNull ViewGroup parent, @NonNull Reaction reaction) { + View root = LayoutInflater.from(context).inflate(R.layout.reactions_pill, parent, false); + EmojiTextView emojiView = root.findViewById(R.id.reactions_pill_emoji); + TextView countView = root.findViewById(R.id.reactions_pill_count); + View spacer = root.findViewById(R.id.reactions_pill_spacer); + + if (reaction.getEmoji() != null) { + emojiView.setText(reaction.getEmoji()); + + if (reaction.getCount() > 1) { + countView.setText(String.valueOf(reaction.getCount())); + } else { + countView.setVisibility(GONE); + spacer.setVisibility(GONE); + } + } else { + emojiView.setVisibility(GONE); + spacer.setVisibility(GONE); + countView.setText("+" + reaction.getCount()); + } + + if (reaction.isFromSelf()) { + root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected)); + countView.setTextColor(ContextCompat.getColor(context, R.color.reaction_pill_text_color_selected)); + } else { + root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background)); + } + + return root; + } +} diff --git a/src/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java b/src/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java new file mode 100644 index 000000000..fa35d1448 --- /dev/null +++ b/src/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java @@ -0,0 +1,125 @@ +package org.thoughtcrime.securesms.reactions; + +import android.app.Dialog; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.b44t.messenger.DcContact; +import com.b44t.messenger.DcContext; +import com.b44t.messenger.DcEvent; +import com.b44t.messenger.rpc.RpcException; + +import org.thoughtcrime.securesms.ProfileActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.connect.DcEventCenter; +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.util.Pair; +import org.thoughtcrime.securesms.util.ViewUtil; + +import java.util.ArrayList; +import java.util.Map; + +public class ReactionsDetailsFragment extends DialogFragment implements DcEventCenter.DcEventDelegate { + private static final String TAG = ReactionsDetailsFragment.class.getSimpleName(); + + private RecyclerView recyclerView; + private ReactionRecipientsAdapter adapter; + private final int msgId; + + public ReactionsDetailsFragment(int msgId) { + super(); + this.msgId = msgId; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + adapter = new ReactionRecipientsAdapter(requireActivity(), GlideApp.with(requireActivity()), new ListClickListener()); + + LayoutInflater inflater = requireActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.reactions_details_fragment, null); + recyclerView = ViewUtil.findById(view, R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + + recyclerView.setAdapter(adapter); + + refreshData(); + + DcEventCenter eventCenter = DcHelper.getEventCenter(requireContext()); + eventCenter.addObserver(DcContext.DC_EVENT_REACTIONS_CHANGED, this); + + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()) + .setTitle(R.string.reactions) + .setNegativeButton(R.string.ok, null); + return builder.setView(view).create(); + } + + @Override + public void onDestroy() { + Log.i(TAG, "onDestroy()"); + super.onDestroy(); + DcHelper.getEventCenter(requireActivity()).removeObservers(this); + } + + @Override + public void handleEvent(@NonNull DcEvent event) { + if (event.getId() == DcContext.DC_EVENT_REACTIONS_CHANGED) { + if (event.getData2Int() == msgId) { + refreshData(); + } + } + } + + private void refreshData() { + if (recyclerView == null) return; + + int accId = DcHelper.getContext(requireActivity()).getAccountId(); + try { + Map reactionsByContact = DcHelper.getRpc(requireActivity()).getMsgReactions(accId, msgId).getReactionsByContact(); + ArrayList> contactsReactions = new ArrayList<>(); + String[] selfReactions = reactionsByContact.remove(DcContact.DC_CONTACT_ID_SELF); + for (Integer contact: reactionsByContact.keySet()) { + for (String reaction: reactionsByContact.get(contact)) { + contactsReactions.add(new Pair<>(contact, reaction)); + } + } + if (selfReactions != null) { + for (String reaction: selfReactions) { + contactsReactions.add(new Pair<>(DcContact.DC_CONTACT_ID_SELF, reaction)); + } + } + adapter.changeData(contactsReactions); + } catch (RpcException e) { + e.printStackTrace(); + } + } + + private void openConversation(int contactId) { + Intent intent = new Intent(getContext(), ProfileActivity.class); + intent.putExtra(ProfileActivity.CONTACT_ID_EXTRA, contactId); + requireContext().startActivity(intent); + } + + private class ListClickListener implements ReactionRecipientsAdapter.ItemClickListener { + + @Override + public void onItemClick(ReactionRecipientItem item) { + int contactId = item.getContactId(); + if (contactId != DcContact.DC_CONTACT_ID_SELF) { + ReactionsDetailsFragment.this.dismiss(); + openConversation(contactId); + } + } + } + +} diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index 91fa99b57..9225e79dd 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -55,15 +55,15 @@ public class Recipient { private final @NonNull Address address; - private @Nullable String customLabel; + private final @Nullable String customLabel; private @Nullable Uri systemContactPhoto; - private Uri contactUri; + private final Uri contactUri; - private @Nullable String profileName; + private final @Nullable String profileName; private @Nullable String profileAvatar; - private @Nullable DcChat dcChat; + private final @Nullable DcChat dcChat; private @Nullable DcContact dcContact; public static @NonNull Recipient fromChat(@NonNull Context context, int dcMsgId) { @@ -121,7 +121,7 @@ public class Recipient { int chatId = dcChat.getId(); this.address = Address.fromChat(chatId); if (!dcChat.isMultiUser()) { - DcContext dcContext = DcHelper.getContext(context); + DcContext dcContext = DcHelper.getAccounts(context).getAccount(dcChat.getAccountId()); int[] contacts = dcContext.getChatContacts(chatId); if( contacts.length>=1 ) { this.dcContact = dcContext.getContact(contacts[0]); @@ -171,7 +171,7 @@ public class Recipient { public @NonNull List loadParticipants(Context context) { List participants = new ArrayList<>(); if (dcChat!=null) { - DcContext dcContext = DcHelper.getContext(context); + DcContext dcContext = DcHelper.getAccounts(context).getAccount(dcChat.getAccountId()); int[] contactIds = dcContext.getChatContacts(dcChat.getId()); for (int contactId : contactIds) { participants.add(new Recipient(context, dcContext.getContact(contactId))); @@ -287,7 +287,7 @@ public class Recipient { public DcChat getChat() { - return dcChat!=null? dcChat : new DcChat(0); + return dcChat!=null? dcChat : new DcChat(0, 0); } @Override diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java index e3817957c..de0af535c 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java +++ b/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java @@ -4,12 +4,19 @@ import android.os.Bundle; import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; public class ScribbleActivity extends PassphraseRequiredActionBarActivity { public static final int SCRIBBLE_REQUEST_CODE = 31424; public static final String CROP_AVATAR = "crop_avatar"; ImageEditorFragment imageEditorFragment; + @Override + protected void onPreCreate() { + dynamicTheme = new DynamicNoActionBarTheme(); + super.onPreCreate(); + } + @Override protected void onCreate(Bundle savedInstanceState, boolean ready) { setContentView(R.layout.scribble_activity); diff --git a/src/org/thoughtcrime/securesms/scribbles/StickerSelectFragment.java b/src/org/thoughtcrime/securesms/scribbles/StickerSelectFragment.java index 8c8562753..22f01de54 100644 --- a/src/org/thoughtcrime/securesms/scribbles/StickerSelectFragment.java +++ b/src/org/thoughtcrime/securesms/scribbles/StickerSelectFragment.java @@ -138,7 +138,7 @@ public class StickerSelectFragment extends Fragment implements LoaderManager.Loa class StickerViewHolder extends RecyclerView.ViewHolder { private String fileName; - private ImageView image; + private final ImageView image; StickerViewHolder(View itemView) { super(itemView); diff --git a/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java b/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java index bde217d17..487d4b451 100644 --- a/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java +++ b/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.scribbles; -import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Matrix; diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/ColorPaletteAdapter.java b/src/org/thoughtcrime/securesms/scribbles/widget/ColorPaletteAdapter.java index ffcb9bdf0..020bb2afa 100644 --- a/src/org/thoughtcrime/securesms/scribbles/widget/ColorPaletteAdapter.java +++ b/src/org/thoughtcrime/securesms/scribbles/widget/ColorPaletteAdapter.java @@ -55,7 +55,7 @@ public class ColorPaletteAdapter extends RecyclerView.Adapter': + case '?': + case '\\': + case '|': + case 0x7F: + return false; + default: + return true; + } + } } diff --git a/src/org/thoughtcrime/securesms/util/JsonUtils.java b/src/org/thoughtcrime/securesms/util/JsonUtils.java index e868566de..e21b04ba3 100644 --- a/src/org/thoughtcrime/securesms/util/JsonUtils.java +++ b/src/org/thoughtcrime/securesms/util/JsonUtils.java @@ -9,7 +9,6 @@ import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.Reader; public class JsonUtils { diff --git a/src/org/thoughtcrime/securesms/util/Prefs.java b/src/org/thoughtcrime/securesms/util/Prefs.java index cef5f5e68..cb254e328 100644 --- a/src/org/thoughtcrime/securesms/util/Prefs.java +++ b/src/org/thoughtcrime/securesms/util/Prefs.java @@ -46,6 +46,7 @@ public class Prefs { private static final String ENTER_SENDS_PREF = "pref_enter_sends"; private static final String PROMPTED_DOZE_MSG_ID_PREF = "pref_prompted_doze_msg_id"; public static final String DOZE_ASKED_DIRECTLY = "pref_doze_asked_directly"; + public static final String ASKED_FOR_NOTIFICATION_PERMISSION= "pref_asked_for_notification_permission"; private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications"; public static final String MESSAGE_BODY_TEXT_SIZE_PREF = "pref_message_body_text_size"; @@ -53,12 +54,11 @@ public class Prefs { public static final String NOTIFICATION_PRIORITY_PREF = "pref_notification_priority"; public static final String SYSTEM_EMOJI_PREF = "pref_system_emoji"; + public static final String BUILTIN_CAMERA_PREF = "pref_builtin_camera"; public static final String DIRECT_CAPTURE_CAMERA_ID = "pref_direct_capture_camera_id"; private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id"; public static final String INCOGNITO_KEYBORAD_PREF = "pref_incognito_keyboard"; - public static final String SCREEN_LOCK = "pref_android_screen_lock"; - private static final String PREF_CONTACT_PHOTO_IDENTIFIERS = "pref_contact_photo_identifiers"; private static final String MAP_CENTER_LATITUDE = "pref_map_center_latitude"; @@ -76,14 +76,6 @@ public class Prefs { public static VibrateState fromId(int id) { return values()[id]; } } - public static boolean isScreenLockEnabled(@NonNull Context context) { - return getBooleanPreference(context, SCREEN_LOCK, false); - } - - public static void setScreenLockEnabled(@NonNull Context context, boolean value) { - setBooleanPreference(context, SCREEN_LOCK, value); - } - public static void setDatabaseEncryptedSecret(@NonNull Context context, @NonNull String secret, int accountId) { setStringPreference(context, DATABASE_ENCRYPTED_SECRET + accountId, secret); } @@ -271,29 +263,33 @@ public class Prefs { // map - public static void setMapZoom(Context context, int chatId, double zoom) { - setLongPreference(context, MAP_ZOOM+chatId, Double.doubleToRawLongBits(zoom)); + public static void setMapZoom(Context context, int accountId, int chatId, double zoom) { + setLongPreference(context, MAP_ZOOM+accountId+"."+chatId, Double.doubleToRawLongBits(zoom)); } - - public static double getMapZoom(Context context, int chatId) { - return Double.longBitsToDouble(0); + public static double getMapZoom(Context context, int accountId, int chatId) { + long zoom = getLongPreference(context, MAP_ZOOM+accountId+"."+chatId, Double.doubleToLongBits(MINIMUM_ZOOM)); + return Double.longBitsToDouble(zoom); } // misc. - public static String getBackgroundImagePath(Context context) { - return getStringPreference(context, BACKGROUND_PREF, ""); + public static String getBackgroundImagePath(Context context, int accountId) { + return getStringPreference(context, BACKGROUND_PREF+accountId, ""); } - public static void setBackgroundImagePath(Context context, String path) { - setStringPreference(context, BACKGROUND_PREF, path); + public static void setBackgroundImagePath(Context context, int accountId, String path) { + setStringPreference(context, BACKGROUND_PREF+accountId, path); } public static boolean isSystemEmojiPreferred(Context context) { return getBooleanPreference(context, SYSTEM_EMOJI_PREF, false); } + public static boolean isBuiltInCameraPreferred(Context context) { + return getBooleanPreference(context, BUILTIN_CAMERA_PREF, false); + } + public static boolean getAlwaysLoadRemoteContent(Context context) { return getBooleanPreference(context, Prefs.ALWAYS_LOAD_REMOTE_CONTENT, Prefs.ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT); diff --git a/src/org/thoughtcrime/securesms/util/RelayUtil.java b/src/org/thoughtcrime/securesms/util/RelayUtil.java index 04903c7c0..08a62637d 100644 --- a/src/org/thoughtcrime/securesms/util/RelayUtil.java +++ b/src/org/thoughtcrime/securesms/util/RelayUtil.java @@ -14,6 +14,7 @@ public class RelayUtil { private static final String FORWARDED_MESSAGE_IDS = "forwarded_message_ids"; private static final String SHARED_URIS = "shared_uris"; private static final String IS_SHARING = "is_sharing"; + private static final String SHARED_TITLE = "shared_title"; private static final String DIRECT_SHARING_CHAT_ID = "direct_sharing_chat_id"; public static boolean isRelayingMessageContent(Activity activity) { @@ -79,6 +80,14 @@ public class RelayUtil { } } + public static String getSharedTitle(Activity activity) { + try { + return activity.getIntent().getStringExtra(SHARED_TITLE); + } catch (NullPointerException npe) { + return null; + } + } + public static void resetRelayingMessageContent(Activity activity) { try { @@ -123,6 +132,10 @@ public class RelayUtil { composeIntent.putExtra(IS_SHARING, true); } + public static void setSharedTitle(Intent composeIntent, String text) { + composeIntent.putExtra(SHARED_TITLE, text); + } + public static void setDirectSharing(Intent composeIntent, int chatId) { composeIntent.putExtra(DIRECT_SHARING_CHAT_ID, chatId); } diff --git a/src/org/thoughtcrime/securesms/util/ResUtil.java b/src/org/thoughtcrime/securesms/util/ResUtil.java index 08a28cef8..b23f28863 100644 --- a/src/org/thoughtcrime/securesms/util/ResUtil.java +++ b/src/org/thoughtcrime/securesms/util/ResUtil.java @@ -22,9 +22,7 @@ import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import androidx.annotation.ArrayRes; import androidx.annotation.AttrRes; -import androidx.annotation.DrawableRes; import androidx.core.content.ContextCompat; import android.util.TypedValue; diff --git a/src/org/thoughtcrime/securesms/util/ScreenLockUtil.java b/src/org/thoughtcrime/securesms/util/ScreenLockUtil.java index 89ad8b559..f24888880 100644 --- a/src/org/thoughtcrime/securesms/util/ScreenLockUtil.java +++ b/src/org/thoughtcrime/securesms/util/ScreenLockUtil.java @@ -5,8 +5,6 @@ import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; -import org.thoughtcrime.securesms.R; - public class ScreenLockUtil { public static final int REQUEST_CODE_CONFIRM_CREDENTIALS = 1001; diff --git a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java index 48fa5a517..1a95c83c4 100644 --- a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java +++ b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java @@ -23,7 +23,6 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.guava.Optional; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -32,11 +31,11 @@ import java.util.Set; import static com.b44t.messenger.DcContact.DC_CONTACT_ID_SELF; public class SelectedRecipientsAdapter extends BaseAdapter { - @NonNull private Context context; - @Nullable private OnRecipientDeletedListener onRecipientDeletedListener; - @NonNull private List recipients; - @NonNull private final DcContext dcContext; - @NonNull private final GlideRequests glideRequests; + @NonNull private final Context context; + @Nullable private OnRecipientDeletedListener onRecipientDeletedListener; + @NonNull private final List recipients; + @NonNull private final DcContext dcContext; + @NonNull private final GlideRequests glideRequests; public SelectedRecipientsAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, diff --git a/src/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java b/src/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java index a6d733c9b..ec4820103 100644 --- a/src/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java +++ b/src/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java @@ -18,7 +18,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import static android.app.Activity.RESULT_OK; import static org.thoughtcrime.securesms.util.RelayUtil.getForwardedMessageIDs; import static org.thoughtcrime.securesms.util.RelayUtil.getSharedText; import static org.thoughtcrime.securesms.util.RelayUtil.getSharedUris; diff --git a/src/org/thoughtcrime/securesms/util/ServiceUtil.java b/src/org/thoughtcrime/securesms/util/ServiceUtil.java index 7b416589d..6d9356c01 100644 --- a/src/org/thoughtcrime/securesms/util/ServiceUtil.java +++ b/src/org/thoughtcrime/securesms/util/ServiceUtil.java @@ -1,12 +1,7 @@ package org.thoughtcrime.securesms.util; import android.app.Activity; -import android.app.AlarmManager; -import android.app.NotificationManager; import android.content.Context; -import android.media.AudioManager; -import android.net.ConnectivityManager; -import android.os.PowerManager; import android.os.Vibrator; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; diff --git a/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java b/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java index 57efc019c..592154e2b 100644 --- a/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java +++ b/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java @@ -4,7 +4,6 @@ import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Rect; -import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.ViewHolder; @@ -28,7 +27,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { private final Map headerCache; private final StickyHeaderAdapter adapter; private final boolean renderInline; - private boolean sticky; + private final boolean sticky; private int screenOrientation; /** @@ -218,4 +217,4 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { */ void onBindHeaderViewHolder(T viewHolder, int position); } -} \ No newline at end of file +} diff --git a/src/org/thoughtcrime/securesms/util/ThreadUtil.java b/src/org/thoughtcrime/securesms/util/ThreadUtil.java index df29c2730..ee21bcdfc 100644 --- a/src/org/thoughtcrime/securesms/util/ThreadUtil.java +++ b/src/org/thoughtcrime/securesms/util/ThreadUtil.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.util; -import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index 45d90b0ba..463e452d8 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -20,7 +20,6 @@ import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.ClipData; import android.content.ClipDescription; -import android.content.ClipboardManager; import android.content.Context; import android.graphics.Color; import android.graphics.Typeface; @@ -34,10 +33,12 @@ import android.text.SpannableString; import android.text.TextUtils; import android.text.style.StyleSpan; import android.util.Log; +import android.view.accessibility.AccessibilityManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.ComposeText; @@ -52,12 +53,13 @@ import java.security.SecureRandom; import java.text.DecimalFormat; import java.util.Arrays; import java.util.Collection; +import java.util.Objects; import java.util.concurrent.CountDownLatch; public class Util { private static final String TAG = Util.class.getSimpleName(); - public static Handler handler = new Handler(Looper.getMainLooper()); + public static final Handler handler = new Handler(Looper.getMainLooper()); public static String join(Collection list, String delimiter) { StringBuilder result = new StringBuilder(); @@ -245,7 +247,7 @@ public class Util { } public static boolean equals(@Nullable Object a, @Nullable Object b) { - return a == b || (a != null && a.equals(b)); + return Objects.equals(a, b); } public static int hashCode(@Nullable Object... objects) { @@ -269,18 +271,6 @@ public class Util { } public static void writeTextToClipboard(@NonNull Context context, @NonNull String text) { - int sdk = android.os.Build.VERSION.SDK_INT; - if (sdk < android.os.Build.VERSION_CODES.HONEYCOMB) { - @SuppressWarnings("deprecation") android.text.ClipboardManager clipboard = - (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(text); - } else { - copyToClipboardSdk11(context, text); - } - } - - @TargetApi(android.os.Build.VERSION_CODES.HONEYCOMB) - private static void copyToClipboardSdk11(Context context, String text) { android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText(context.getString(R.string.app_name), text); @@ -345,4 +335,28 @@ public class Util { public static int rgbToArgbColor(int rgb) { return Color.argb(0xFF, Color.red(rgb), Color.green(rgb), Color.blue(rgb)); } + + private static long lastClickTime = 0; + public static boolean isClickedRecently() { + long now = System.currentTimeMillis(); + if (now - lastClickTime < 500) { + Log.i(TAG, "tap discarded"); + return true; + } + lastClickTime = now; + return false; + } + + private static AccessibilityManager accessibilityManager; + public static boolean isTouchExplorationEnabled(Context context) { + try { + if (accessibilityManager == null) { + Context applicationContext = context.getApplicationContext(); + accessibilityManager = ((AccessibilityManager) applicationContext.getSystemService(Context.ACCESSIBILITY_SERVICE)); + } + return accessibilityManager.isTouchExplorationEnabled(); + } catch (Exception e) { + return false; + } + } } diff --git a/src/org/thoughtcrime/securesms/util/ViewUtil.java b/src/org/thoughtcrime/securesms/util/ViewUtil.java index 22383668d..7c032a5e6 100644 --- a/src/org/thoughtcrime/securesms/util/ViewUtil.java +++ b/src/org/thoughtcrime/securesms/util/ViewUtil.java @@ -254,6 +254,16 @@ public class ViewUtil { view.requestLayout(); } + public static void setRightMargin(@NonNull View view, int margin) { + if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) { + ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin = margin; + } else { + ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin = margin; + } + view.forceLayout(); + view.requestLayout(); + } + public static void setTopMargin(@NonNull View view, int margin) { ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).topMargin = margin; view.requestLayout(); diff --git a/src/org/thoughtcrime/securesms/util/guava/Optional.java b/src/org/thoughtcrime/securesms/util/guava/Optional.java index bde8b04a5..185e1f07e 100644 --- a/src/org/thoughtcrime/securesms/util/guava/Optional.java +++ b/src/org/thoughtcrime/securesms/util/guava/Optional.java @@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.util.guava; import static org.thoughtcrime.securesms.util.guava.Preconditions.checkNotNull; import java.io.Serializable; -import java.util.Iterator; import java.util.Set; diff --git a/src/org/thoughtcrime/securesms/util/views/ConversationAdaptiveActionsToolbar.java b/src/org/thoughtcrime/securesms/util/views/ConversationAdaptiveActionsToolbar.java index 336e31a1b..f61d39bf3 100644 --- a/src/org/thoughtcrime/securesms/util/views/ConversationAdaptiveActionsToolbar.java +++ b/src/org/thoughtcrime/securesms/util/views/ConversationAdaptiveActionsToolbar.java @@ -29,7 +29,7 @@ public class ConversationAdaptiveActionsToolbar extends Toolbar { private static final int ID_NEVER_SHOW_AS_ACTION_4 = R.id.menu_resend; private static final int ID_ALWAYS_SHOW_AS_ACTION = R.id.menu_context_forward; - private int maxShown; + private final int maxShown; public ConversationAdaptiveActionsToolbar(@NonNull Context context) { this(context, null); diff --git a/src/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java b/src/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java index 3225f8428..1638db7d4 100644 --- a/src/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java +++ b/src/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java @@ -1,13 +1,10 @@ package org.thoughtcrime.securesms.video.exo; -import android.content.Context; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.TransferListener; public class AttachmentDataSourceFactory implements DataSource.Factory { diff --git a/src/org/thoughtcrime/securesms/video/recode/MP4Builder.java b/src/org/thoughtcrime/securesms/video/recode/MP4Builder.java index 385f4c5fb..b5502928a 100644 --- a/src/org/thoughtcrime/securesms/video/recode/MP4Builder.java +++ b/src/org/thoughtcrime/securesms/video/recode/MP4Builder.java @@ -51,7 +51,7 @@ public class MP4Builder { private long dataOffset = 0; private long writedSinceLastMdat = 0; private boolean writeNewMdat = true; - private HashMap track2SampleSizes = new HashMap<>(); + private final HashMap track2SampleSizes = new HashMap<>(); private ByteBuffer sizeBuffer = null; public MP4Builder createMovie(Mp4Movie mp4Movie) throws Exception { diff --git a/src/org/thoughtcrime/securesms/video/recode/Mp4Movie.java b/src/org/thoughtcrime/securesms/video/recode/Mp4Movie.java index a648afc03..6d2a9b001 100644 --- a/src/org/thoughtcrime/securesms/video/recode/Mp4Movie.java +++ b/src/org/thoughtcrime/securesms/video/recode/Mp4Movie.java @@ -12,7 +12,7 @@ import java.util.ArrayList; @TargetApi(16) public class Mp4Movie { private Matrix matrix = Matrix.ROTATE_0; - private ArrayList tracks = new ArrayList<>(); + private final ArrayList tracks = new ArrayList<>(); private File cacheFile; private int width; private int height; diff --git a/src/org/thoughtcrime/securesms/video/recode/TextureRenderer.java b/src/org/thoughtcrime/securesms/video/recode/TextureRenderer.java index 9ec8c49d7..408de4a28 100644 --- a/src/org/thoughtcrime/securesms/video/recode/TextureRenderer.java +++ b/src/org/thoughtcrime/securesms/video/recode/TextureRenderer.java @@ -39,7 +39,7 @@ public class TextureRenderer { -1.0f, 1.0f, 0, 0.f, 1.f, 1.0f, 1.0f, 0, 1.f, 1.f, }; - private FloatBuffer mTriangleVertices; + private final FloatBuffer mTriangleVertices; private static final String VERTEX_SHADER = "uniform mat4 uMVPMatrix;\n" + @@ -61,8 +61,8 @@ public class TextureRenderer { " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + "}\n"; - private float[] mMVPMatrix = new float[16]; - private float[] mSTMatrix = new float[16]; + private final float[] mMVPMatrix = new float[16]; + private final float[] mSTMatrix = new float[16]; private int mProgram; private int mTextureID = -12345; private int muMVPMatrixHandle; diff --git a/src/org/thoughtcrime/securesms/video/recode/Track.java b/src/org/thoughtcrime/securesms/video/recode/Track.java index c9c62b19b..02f1a29e7 100644 --- a/src/org/thoughtcrime/securesms/video/recode/Track.java +++ b/src/org/thoughtcrime/securesms/video/recode/Track.java @@ -27,20 +27,20 @@ import java.util.Map; @TargetApi(16) public class Track { private long trackId = 0; - private ArrayList samples = new ArrayList<>(); + private final ArrayList samples = new ArrayList<>(); private long duration = 0; private String handler; private AbstractMediaHeaderBox headerBox = null; private SampleDescriptionBox sampleDescriptionBox = null; private LinkedList syncSamples = null; private int timeScale; - private Date creationTime = new Date(); + private final Date creationTime = new Date(); private int height; private int width; private float volume = 0; - private ArrayList sampleDurations = new ArrayList<>(); + private final ArrayList sampleDurations = new ArrayList<>(); private boolean isAudio = false; - private static Map samplingFrequencyIndexMap = new HashMap<>(); + private static final Map samplingFrequencyIndexMap = new HashMap<>(); private long lastPresentationTimeUs = 0; private boolean first = true; diff --git a/src/org/thoughtcrime/securesms/video/recode/VideoRecoder.java b/src/org/thoughtcrime/securesms/video/recode/VideoRecoder.java index 7dbc7b452..467ea75fe 100644 --- a/src/org/thoughtcrime/securesms/video/recode/VideoRecoder.java +++ b/src/org/thoughtcrime/securesms/video/recode/VideoRecoder.java @@ -10,7 +10,8 @@ import android.media.MediaExtractor; import android.media.MediaFormat; import android.os.Build; import android.util.Log; -import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; import com.b44t.messenger.DcMsg; import com.coremedia.iso.IsoFile; @@ -43,7 +44,7 @@ public class VideoRecoder { private final static int PROCESSOR_TYPE_MTK = 3; private final static int PROCESSOR_TYPE_SEC = 4; private final static int PROCESSOR_TYPE_TI = 5; - private boolean cancelCurrentVideoConversion = false; + private final boolean cancelCurrentVideoConversion = false; private final Object videoConvertSync = new Object(); private void checkConversionCanceled() throws Exception { @@ -241,6 +242,7 @@ public class VideoRecoder { File inputFile = new File(videoEditedInfo.originalPath); if (!inputFile.canRead()) { //didWriteData(messageObject, cacheFile, true, true); + Log.w(TAG, "Could not read video file to be recoded"); return false; } @@ -537,7 +539,7 @@ public class VideoRecoder { outputSurface.awaitNewImage(); } catch (Exception e) { errorWait = true; - + Log.w(TAG, "error while waiting for recording output surface", e); } if (!errorWait) { if (Build.VERSION.SDK_INT >= 18) { @@ -545,6 +547,7 @@ public class VideoRecoder { inputSurface.setPresentationTime(info.presentationTimeUs * 1000); inputSurface.swapBuffers(); } else { + Log.w(TAG, "Cannot proceed with the current SDK version"); return false; // TODO: this should be caught much earlier /* int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); @@ -582,7 +585,7 @@ public class VideoRecoder { videoStartTime = videoTime; } } catch (Exception e) { - + Log.w(TAG,"Recoding video failed unexpectedly", e); error = true; } @@ -615,8 +618,8 @@ public class VideoRecoder { readAndWriteTrack(extractor, mediaMuxer, info, videoStartTime, endTime, cacheFile, true); } } catch (Exception e) { + Log.w(TAG,"Recoding video failed unexpectedly/2", e); error = true; - } finally { if (extractor != null) { extractor.release(); @@ -625,13 +628,14 @@ public class VideoRecoder { try { mediaMuxer.finishMovie(false); } catch (Exception e) { - + Log.w(TAG,"Flushing video failed unexpectedly", e); } } //Log.i("DeltaChat", "time = " + (System.currentTimeMillis() - time)); } } else { //didWriteData(messageObject, cacheFile, true, true); + Log.w(TAG,"Video width or height are 0, refusing recode."); return false; } //didWriteData(messageObject, cacheFile, true, error); @@ -662,12 +666,14 @@ public class VideoRecoder { { boolean canRecode = true; if (Build.VERSION.SDK_INT < 16 /*= Jelly Bean 4.1 (before that codecInfo.getName() was not there) */) { + Log.w(TAG, "Cannot recode: API < 16"); canRecode = false; } else if (Build.VERSION.SDK_INT < 18 /*= Jelly Bean 4.3*/) { try { MediaCodecInfo codecInfo = VideoRecoder.selectCodec(VideoRecoder.MIME_TYPE); if (codecInfo == null) { + Log.w(TAG, "Cannot recode: cannot select codec"); canRecode = false; } else { String name = codecInfo.getName(); @@ -678,14 +684,17 @@ public class VideoRecoder { name.equals("OMX.MARVELL.VIDEO.H264ENCODER") || name.equals("OMX.k3.video.encoder.avc") || name.equals("OMX.TI.DUCATI1.VIDEO.H264E")) { + Log.w(TAG, "Cannot recode: no supported codec found"); canRecode = false; } else { if (VideoRecoder.selectColorFormat(codecInfo, VideoRecoder.MIME_TYPE) == 0) { + Log.w(TAG, "Cannot recode: cannot select color format"); canRecode = false; } } } } catch (Exception e) { + Log.w(TAG, "Cannot recode: Determinating recoding capabilities failed unexpectedly", e); canRecode = false; } } @@ -723,7 +732,7 @@ public class VideoRecoder { trackBitrate = (int) (sampleSizes * 8 / originalVideoSeconds); vei.originalDurationMs = originalVideoSeconds * 1000; } catch (Exception e) { - + Log.w(TAG, "Get video info: Calculating sample sizes failed unexpectedly", e); } TrackHeaderBox headerBox = trackBox.getTrackHeaderBox(); if (headerBox.getWidth() != 0 && headerBox.getHeight() != 0) { @@ -734,6 +743,7 @@ public class VideoRecoder { } } if (trackHeaderBox == null) { + Log.w(TAG, "Get video info: No trackHeaderBox"); return null; } @@ -749,6 +759,7 @@ public class VideoRecoder { vei.originalHeight = (int) trackHeaderBox.getHeight(); } catch (Exception e) { + Log.w(TAG, "Get video info: Reading message info failed unexpectedly", e); return null; } @@ -761,10 +772,14 @@ public class VideoRecoder { return size; } - private static void logNtoast(Context context, String str) + private static void alert(Context context, String str) { - Log.w(TAG, str); - Util.runOnMain(()->Toast.makeText(context, str, Toast.LENGTH_LONG).show()); + Log.e(TAG, str); + Util.runOnMain(() -> new AlertDialog.Builder(context) + .setCancelable(false) + .setMessage(str) + .setPositiveButton(android.R.string.ok, null) + .show()); } // prepareVideo() assumes the msg object is set up properly to being sent; @@ -775,11 +790,12 @@ public class VideoRecoder { try { String inPath = msg.getFile(); + Log.i(TAG, "Preparing video: " + inPath); // try to get information from video file VideoEditedInfo vei = getVideoEditInfoFromFile(inPath); if (vei == null) { - logNtoast(context, String.format("recoding for %s failed: cannot get info", inPath)); + alert(context, String.format("Recoding failed for %s: cannot get info", inPath)); return false; } vei.rotationValue = vei.originalRotationValue; @@ -796,7 +812,7 @@ public class VideoRecoder { msg.setDuration((int)vei.originalDurationMs); if (!canRecode()) { - logNtoast(context, String.format("recoding for %s failed: this system cannot recode videos", inPath)); + alert(context, String.format("Recoding failed for %s: this system cannot recode videos", inPath)); return false; } @@ -851,7 +867,7 @@ public class VideoRecoder { vei.resultVideoBitrate, vei.originalDurationMs, vei.originalAudioBytes); if (vei.estimatedBytes > MAX_BYTES+MAX_BYTES/4) { - logNtoast(context, String.format("recoding for %s failed: resulting file probably too large", inPath)); + alert(context, "Video cannot be compressed to a reasonable size. Try a shorter video or a lower quality."); return false; } @@ -859,12 +875,12 @@ public class VideoRecoder { String tempPath = DcHelper.getBlobdirFile(DcHelper.getContext(context), inPath); VideoRecoder videoRecoder = new VideoRecoder(); if (!videoRecoder.convertVideo(vei, tempPath)) { - logNtoast(context, String.format("recoding for %s failed: cannot convert to temporary file %s", inPath, tempPath)); + alert(context, String.format("Recoding failed for %s: cannot convert to temporary file %s", inPath, tempPath)); return false; } if (!Util.moveFile(tempPath, inPath)) { - logNtoast(context, String.format("recoding for %s failed: cannot move temporary file %s", inPath, tempPath)); + alert(context, String.format("Recoding failed for %s: cannot move temporary file %s", inPath, tempPath)); return false; }