Compare commits

..

478 Commits

Author SHA1 Message Date
Cody Henthorne c4bc2162f2 Bump version to 5.36.3 2022-04-26 11:55:37 -04:00
Cody Henthorne bfd966217f Updated language translations. 2022-04-26 11:49:27 -04:00
Greyson Parrelli 797c02e893 Improve reliability of launching FCM foreground service.
We were getting weird errors around the service not calling
startForeground() that I couldn't reproduce. I figure it must be
something with how we only sometimes start the FcmFetchService in the
foreground. So I think the safest thing to do is to just use
GenericForegroundService.
2022-04-26 11:22:01 -04:00
Cody Henthorne 65372e547a Auto-decline invites to a group by a blocked user. 2022-04-25 13:41:50 -04:00
Alex Hart 2454b2e0db Fix crash when trying to access media controller after activity is destroyed and reference is nullified. 2022-04-25 10:29:30 -03:00
Alex Hart 0505a46603 Fix crash when item animation ends after we leave fragment. 2022-04-25 10:09:28 -03:00
Alex Hart 7f77cd6a22 Prevent crash when user quickly leaves the share fragment. 2022-04-25 10:04:21 -03:00
Alex Hart efe7b3099f Bump version to 5.36.2 2022-04-22 16:50:21 -03:00
Alex Hart 26a831b49f Updated language translations. 2022-04-22 16:50:21 -03:00
Alex Hart a3a5bb8177 Fix direct shares. 2022-04-22 16:50:21 -03:00
Alex Hart 4282f3eb6d Add explicit export to ShareActivity. 2022-04-22 16:50:21 -03:00
Greyson Parrelli 8a49db650a Do not show SMS contacts as shortcuts if we're not the SMS app. 2022-04-22 16:50:21 -03:00
Greyson Parrelli fadd4ac61e Fix possible index out of bounds exception in ConversationAdapter.
If we're deferring to super.getItem(), then we should be guarding with
super.getItemCount().
2022-04-22 16:50:21 -03:00
Alex Hart d0c14895d0 Fix crash when parent does not implement optional bottom sheet callback. 2022-04-22 16:50:21 -03:00
Greyson Parrelli 32ee18240b Fix crash that occurs if we don't have permission to add an account. 2022-04-22 14:32:05 -04:00
Cody Henthorne cd10aa90cc Adjust tap area on forwarding fab. 2022-04-22 13:40:07 -04:00
Rashad Sookram 33d28c4359 Inset audio level indicator by nav bar height. 2022-04-22 12:36:05 -04:00
Greyson Parrelli 530403ec04 Updated emoji to version 14.0 2022-04-22 11:40:07 -04:00
Greyson Parrelli f15072bc8d Fix other group update description bugs and add tests. 2022-04-22 08:32:07 -04:00
Ehren Kret 8c2db972cf Fix crash if recipient appears multiple times in group update description.
Without starting from start index, if the same recipient appears
multiple times in the recipient list, this function will crash.
2022-04-22 07:55:42 -04:00
Alex Hart ff8f9ca81a Bump version to 5.36.1 2022-04-21 12:45:39 -03:00
Alex Hart 40991cc8e9 Updated language translations. 2022-04-21 12:45:12 -03:00
Cody Henthorne 2f551ee3f2 Do not auto-leave groups you have requested to join. 2022-04-21 11:30:13 -04:00
Alex Hart f1ab0a05f1 Fix issue preventing stories shared element transition from starting. 2022-04-21 12:12:15 -03:00
Greyson Parrelli fb919466de Enqueue a profile fetch to resolve identity key conflicts. 2022-04-20 19:04:24 -04:00
Greyson Parrelli 4a4cf08cd8 Do not run StorageForcePushJob if you're not registered. 2022-04-20 18:52:41 -04:00
Alex Hart ed20c24326 Bump version to 5.36.0 2022-04-20 16:32:55 -03:00
Alex Hart e01cbcec62 Updated language translations. 2022-04-20 16:32:55 -03:00
Rashad Sookram 04d6ccc30e Re-enable audio level indicators in calls. 2022-04-20 16:32:55 -03:00
Alex Hart 6860f96973 Add subscription retry on 402 and print out status when we think a sub is active. 2022-04-20 16:32:55 -03:00
Alex Hart 944c8530d8 Improve remote delete handling in group story threads. 2022-04-20 16:32:55 -03:00
Alex Hart 8b1552952c Stories: Pass recipients through via constructor. 2022-04-20 16:32:55 -03:00
Greyson Parrelli dfcadde076 Ensure we enqueue a storage sync after a safety number change. 2022-04-20 16:32:55 -03:00
Cody Henthorne 55acd0f048 Auto-leave group if added by blocked user. 2022-04-20 16:32:55 -03:00
Alex Hart 820c016aad Allow quoted story to launch into viewer. 2022-04-20 16:32:55 -03:00
Alex Hart d1d63d83dc Drop stories from inactive groups. 2022-04-20 16:32:55 -03:00
Alex Hart 7da5b2cdef Fix spinner group change description. 2022-04-20 16:32:55 -03:00
Alex Hart 442dde5c40 Keep caption when forwarding media with a body to a story. 2022-04-20 16:32:55 -03:00
Cody Henthorne f038e81ff3 Fix long name issues in reaction and recipient bottom sheet.
Fixes #12113
2022-04-20 16:32:55 -03:00
Greyson Parrelli 32c4fcb065 Improve handling of empty profiles. 2022-04-20 16:32:55 -03:00
Alex Hart e2703b459f Rework color selector and background. 2022-04-20 16:32:55 -03:00
Cody Henthorne 405d99fbe2 Allow keyboard switch when disabling pin reminders.
Fixes #9862
2022-04-20 16:32:55 -03:00
Evan Hahn 7b89687206 Update donation strings: "One-time", not "One Time". 2022-04-20 16:32:55 -03:00
Alex Hart d74f1a386c Set story text placeholder alpha to 60 percent. 2022-04-20 16:32:55 -03:00
Alex Hart b041ed1510 Ensure delivery receipts are sent for 1:1 story replies and reactions. 2022-04-20 16:32:55 -03:00
Alex Hart 3426556a51 Disable group private replies. 2022-04-20 16:32:54 -03:00
Greyson Parrelli e2cb535f3f Make names in group update descriptions tappable. 2022-04-20 16:32:54 -03:00
Alex Hart 3b17a41415 Send actual quote author in story direct reply. 2022-04-20 16:32:54 -03:00
Alex Hart 631720f111 Ensure direct replies respect disappearing message timeout. 2022-04-20 16:32:54 -03:00
Cody Henthorne ab031d3dad Add internal setting to clear keep longer logs. 2022-04-19 08:17:21 -04:00
Alex Hart 6101048f07 Update donor badge strings. 2022-04-18 16:38:06 -03:00
Alex Hart 115f7063d5 Add support and tracking of ChargeFailure in ActiveSubscription. 2022-04-18 16:37:12 -03:00
Alex Hart 159d67ec59 Fix crash for non-story replies. 2022-04-18 13:26:28 -03:00
Greyson Parrelli e09ce4c820 Use foreground services to process notification when appropriate.
Right now, the only condition is once every 3 minutes on Android 12.

This is ok because Android 12 will allow us (once every 2 minutes or
so) to start a foreground service, and it won't show it for the first 10
seconds. So we can kind of do it without any visual penalty.
2022-04-18 11:27:32 -04:00
Alex Hart 8cfc013960 Fix issue where story viewer would get stuck. 2022-04-18 10:24:54 -03:00
Alex Hart a436c46cb2 Remove label from remote deleted story reactions. 2022-04-18 09:43:59 -03:00
Alex Hart 893be51810 Allow 1:1 replies to increment thread unread counter. 2022-04-18 09:40:05 -03:00
Greyson Parrelli 97b5a49e36 Update default state for whether legacy passwords are disabled.
This was a feature that was removed from the app over 4.5 years ago.
The value should have been manually set to false when they set a
password, meaning that it should be safe to set the default to true.

Fixes #10367
2022-04-17 10:12:44 -04:00
Alex Hart 043f06e188 Prevent sending videos over 30s in length to a story. 2022-04-15 16:07:15 -04:00
Alex Hart fa13b464f8 Fix elongated message bubbles when intentional newlines are present. 2022-04-15 16:07:15 -04:00
Alex Hart bfaaf20fd9 Fix image editor outlining on Android 12+. 2022-04-15 16:07:15 -04:00
Peter Thatcher 2f97b80b9c Add internal setting for call bandwidth mode. 2022-04-15 16:07:15 -04:00
Alex Hart eee9c967fa Fix overlapping text issue in review cards. 2022-04-15 16:07:15 -04:00
Alex Hart 515981c044 Add horizontal margins to donation receipt PNG. 2022-04-15 16:07:15 -04:00
Alex Hart a06528e5e1 Always notifyIfReady for each boolean change. 2022-04-15 16:07:14 -04:00
Alex Hart 98194c854a Add blur and adjust layout for story error slate. 2022-04-15 16:07:14 -04:00
Alex Hart 2d60a88a75 Fix crossfade target aspect ratio. 2022-04-15 16:07:14 -04:00
Alex Hart c3e7d6c74c Fix bug causing cancellation of dialog fragment. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 8da66bc789 Fix corner case in story distribution list syncing. 2022-04-15 16:07:14 -04:00
Alex Hart 9ceb5b2e85 Fix view-off-main bug in Landing fragment. 2022-04-15 16:07:14 -04:00
Alex Hart 17b8e086c9 Fix crash when trying to view 1:1 conversation with reaction quote. 2022-04-15 16:07:14 -04:00
Alex Hart 9a097d113d Update share interstitial to use proper title. 2022-04-15 16:07:14 -04:00
Alex Hart 46ca1e16bb Fix crash when trying to add a link to a text post. 2022-04-15 16:07:14 -04:00
Alex Hart d4d3124a90 Prevent flicker of user avatar in MyStories when moving between tabs. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 35a9fddbb2 Add basic support for receiving messages at your PNI.
We haven't implemented merging yet, so this is still very basic, but it
"works".
2022-04-15 16:07:14 -04:00
Alex Hart 41e417ff0b Add proper interpolator and duration to chrome show/hide. 2022-04-15 16:07:14 -04:00
Alex Hart f6614c1174 Set story viewer background exit fade duration to 100ms. 2022-04-15 16:07:14 -04:00
Alex Hart 9136bcf5e8 Add radius animator to cross fade when launching story viewer. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 7c156d10d6 Keep active table selected in Browse page in Spinner. 2022-04-15 16:07:14 -04:00
Alex Hart 3372d942ec Swap out outlinethumbnailview for shapeableimageviews in mystories. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 7fc9876b1e Start transaction earlier in backup restore.
Fixes #12159
2022-04-15 16:07:14 -04:00
Alex Hart cff62e9528 Add 12dp margin to top of stories context menu. 2022-04-15 16:07:14 -04:00
Alex Hart 24f59b0a17 Hide bottom nav when viewing archived conversations. 2022-04-15 16:07:14 -04:00
Alex Hart 0a07800eba Fix caption sending for outgoing image and video stories. 2022-04-15 16:07:14 -04:00
Alex Hart c863e9ed4d Fix crash when trying to create a text story. 2022-04-15 16:07:14 -04:00
Alex Hart 523537cf05 Enable sharing to stories and refactor share activity. 2022-04-15 16:07:14 -04:00
Greyson Parrelli fd4543ffe0 Improve speed of many SMS/MMS queries by removing unnecessary attachment join. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 83b0309f23 Fix bug in Spinner where some query history items didn't work. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 5cabe5ecfa Default the query history to hidden in Spinner. 2022-04-15 16:07:14 -04:00
Greyson Parrelli fae3004512 Fix issue where you could have multiple context menus.
Fixes #12149
2022-04-15 16:07:14 -04:00
Alex Hart e143c47c25 Fix crash and icon change issue with shared element transition. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 27c3fca324 Change ContactRecordProcess to merge identity key/state as a group. 2022-04-15 16:07:14 -04:00
Alex Hart 26d637cafc Add explicit themes to fabs. 2022-04-15 16:07:14 -04:00
Alex Hart 03e8fe9f27 Migrate all internal shares to MultiselectForwardFragment. 2022-04-15 16:07:14 -04:00
Artem Varaksa 23939aeee3 Removed unused string and the variable that was used in it.
Close #12146
2022-04-15 16:07:14 -04:00
clauz9 d7b793ce4c Fade out fab buttons and megaphone when entering action mode or search.
Closes #12112
2022-04-15 16:07:14 -04:00
Greyson Parrelli d3096c56cb Basic client usage of CDSHv2.
This provides a basic (read: useful-for-development-yet-broken) client
usage of CDSHv2.
2022-04-15 16:07:14 -04:00
clauz9 b0e7b49056 Remove redundant exit animation.
Fixes #12119
2022-04-15 16:07:14 -04:00
Cody Henthorne 2f0f26c328 Add story send multi-send, error, and improved SNC states. 2022-04-15 16:07:14 -04:00
Alex Hart 7f2f5a182f Add shared element transition for camera fab. 2022-04-15 16:07:14 -04:00
Alex Hart 33b88796e8 Simplify layout and do not display until data is loaded to prevent flashing. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 31e4db2186 Bump version to 5.35.3 2022-04-15 16:06:11 -04:00
Greyson Parrelli 76ad7866ec Updated language translations. 2022-04-15 16:05:41 -04:00
Rashad Sookram e9804eccbb Disable audio level indicator in calls. 2022-04-15 15:45:50 -04:00
Rashad Sookram d62d0efb1d Fix screen pulsing when your video is disabled. 2022-04-15 11:44:49 -04:00
Greyson Parrelli 3ec9cd1244 Bump version to 5.35.2 2022-04-13 11:56:41 -04:00
Greyson Parrelli bd5f48f193 Updated language translations. 2022-04-13 11:56:22 -04:00
Alex Hart 98fc3e5b0b Log warning instead of throwing NPE for voice note controller. 2022-04-13 12:37:24 -03:00
Greyson Parrelli d06c633dc4 Fix full text search filter.
Closes #12158
2022-04-13 10:58:16 -04:00
Greyson Parrelli d401386e2d Bump version to 5.35.1 2022-04-11 20:44:10 -04:00
Greyson Parrelli 8f0f9e64b9 Updated language translations. 2022-04-11 20:43:47 -04:00
Greyson Parrelli bd5ac85ac0 Fix old DB migration.
A cautionary tale that serves as a reminder to never call external code
during a migration...

Fixes #12147
2022-04-11 20:38:32 -04:00
Greyson Parrelli 417070e957 Prevent possible deadlock in identity cache. 2022-04-11 12:30:18 -04:00
Greyson Parrelli a92638e897 Fix possible threading issue in RetrieveProfileJob. 2022-04-11 12:00:01 -04:00
Rashad Sookram 08abe890ff Adjust audio levels animation. 2022-04-11 11:56:46 -04:00
Cody Henthorne 66b6420f21 Revert "Fix overlapping text with voice notes."
This reverts commit dabd131222.
2022-04-11 11:54:29 -04:00
Rashad Sookram b21bd5a01e Ensure PiP view is animated to its final position. 2022-04-08 17:34:50 -04:00
Cody Henthorne d11e8ec04b Bump version to 5.35.0 2022-04-08 12:29:29 -04:00
Cody Henthorne 3e5fe0f1cb Updated language translations. 2022-04-08 12:22:37 -04:00
Alex Hart b65d62e065 Update prioritization of donation error bottom sheets. 2022-04-08 12:19:27 -04:00
Alex Hart fc55be0916 Stop voice note on video playback. 2022-04-08 12:19:27 -04:00
Alex Hart a87aa0fbe2 Don't keep around shortcuts for archived chats. 2022-04-08 12:19:27 -04:00
Alex Hart a44a105cbc Add ability to copy text slides in full. 2022-04-08 12:19:27 -04:00
Alex Hart c4817ac017 Allow generic links to be sent as stories. 2022-04-08 12:19:27 -04:00
Cody Henthorne 65835606cc Fix reply UX on reply disabled 1:1 stories. 2022-04-08 12:19:27 -04:00
Cody Henthorne ff26922afb Tweak private story reaction UI. 2022-04-08 12:19:27 -04:00
Alex Hart a894ba7a51 Implement cross-fade for story thumb shared element animation. 2022-04-08 12:19:26 -04:00
Cody Henthorne cb63fe600c Fix react with any closing for story direct replies. 2022-04-08 12:19:26 -04:00
Jim Gustafson 2d6146351d Update to RingRTC v2.20.1 2022-04-08 12:19:26 -04:00
Alex Hart e5953b25e1 Disallow fling gesture when we are translating for on back. 2022-04-08 12:19:26 -04:00
Alex Hart 6354cb194c Update ordering query to display content in expected order. 2022-04-08 12:19:26 -04:00
Cody Henthorne 8d6beb92cb Reply sheet polish. 2022-04-08 12:19:26 -04:00
Cody Henthorne bb5edccf34 Update view count in My Story view. 2022-04-08 12:19:26 -04:00
Cody Henthorne 6d86b25acd Improve story contact search. 2022-04-08 12:19:26 -04:00
Alex Hart 04677d21bb Push repository calls to background. 2022-04-08 12:19:26 -04:00
Alex Hart 20022b88fc Fix issue where if no stories exist we would never display. 2022-04-08 12:19:26 -04:00
Cody Henthorne 3088d7f182 Adjust quote view colors. 2022-04-08 12:19:26 -04:00
Alex Hart ce8dafd33d Start align text when displaying in smallest size otherwise center. 2022-04-08 12:19:26 -04:00
Alex Hart 6054285ddb Add description to story link previews. 2022-04-08 12:19:26 -04:00
Alex Hart dabea5169b Fix opening long messages. 2022-04-08 12:19:26 -04:00
Alex Hart 7fb5ceeda4 Allow hidden story viewing. 2022-04-06 14:37:25 -03:00
Cody Henthorne dc6fd8be7f Fix story reply crash and tweak UI. 2022-04-06 13:17:33 -04:00
Alex Hart c271b9c2de Prevent multiple clicks when accessing the viewer. 2022-04-06 12:38:43 -04:00
Alex Hart 6fb6092a6b Implement a cache for faster typeface resolution. 2022-04-06 12:38:43 -04:00
Alex Hart 46bb64ad24 Display reply icon if you responded 1:1 to the displayed story. 2022-04-06 12:38:43 -04:00
Alex Hart bcd16ce296 Lock orientation when creating a text post. 2022-04-06 12:38:43 -04:00
Alex Hart 07ec14d5c4 Fix issue where reaction animation would only play first time. 2022-04-06 12:38:43 -04:00
Alex Hart d716416d1d Prevent caption from swallowing taps if no overlay needed. 2022-04-06 12:38:43 -04:00
Alex Hart 343871ed8b Use unread thread count for bottom bar. 2022-04-06 12:38:43 -04:00
Alex Hart aa60247e42 Add rounded corners back to secondary story. 2022-04-06 12:38:43 -04:00
Alex Hart 283e3e99a5 Adjust badge positioning on stories landing items. 2022-04-06 12:38:43 -04:00
Alex Hart ca79bdb16b Preserve tab state between configuration changes. 2022-04-06 12:38:43 -04:00
Alex Hart a75d2cfa34 Adjust viewer ordering to match landing page. 2022-04-06 12:38:43 -04:00
Cody Henthorne 1746f37276 Use smarter scrolling for group story replies. 2022-04-06 12:38:43 -04:00
Rashad Sookram 73f32868a2 Display audio levels in 1:1 calls. 2022-04-06 12:38:43 -04:00
Cody Henthorne dabd131222 Fix overlapping text with voice notes. 2022-04-06 12:38:43 -04:00
Greyson Parrelli 612c6db6db Rename some CDS-related classes. 2022-04-06 12:38:43 -04:00
Cody Henthorne c56ef33833 Fix resend after safety number change in groups or distribution lists. 2022-04-06 12:38:43 -04:00
Alex Hart 2253e25ae1 Consolidate toolbar_basic usage to single location. 2022-04-06 12:38:43 -04:00
Alex Hart adb24d480a Remove access modifier from ChatColors constructor. 2022-04-06 12:38:43 -04:00
Cody Henthorne 9fb1dcf28f Fix overlapping text with remote delete. 2022-04-06 12:38:43 -04:00
Cody Henthorne bba36a5724 Keep gif search open when viewing a result. 2022-04-06 12:38:43 -04:00
Alex Hart fa515be258 Hide tab bar during multiselect. 2022-04-06 12:38:43 -04:00
Cody Henthorne be241524db Fix font networking main thread crash. 2022-04-06 12:38:43 -04:00
Cody Henthorne bb66c3fa68 Fix story views not using entire bottom sheet space. 2022-04-06 12:38:43 -04:00
Greyson Parrelli a32d5bef20 Refactor more ContactDiscovery code. 2022-04-06 12:38:43 -04:00
Greyson Parrelli d409278dd5 Do not allow emoji in image editing if device doesn't support it. 2022-04-06 12:37:43 -04:00
Alex Hart 3328e43a40 Add initial shared element transition between conversation list and stories. 2022-04-06 12:37:43 -04:00
Alex Hart 678e832058 Update stories camera fab coloring. 2022-04-06 12:37:43 -04:00
Alex Hart 2c341f450f Add finalized private story icons. 2022-04-06 12:37:43 -04:00
Alex Hart 5854074d4a Update my stories item and landing page empty notice. 2022-04-06 12:37:43 -04:00
Alex Hart dbe186248d Rework sizing on landing page. 2022-04-06 12:37:43 -04:00
Alex Hart 0504161b04 Change stories camera fab icon to outline. 2022-04-06 12:37:43 -04:00
Alex Hart 9748f1cff8 Add padding to context menus in story landing and my story. 2022-04-06 12:37:43 -04:00
Alex Hart 6a061ed52c Remove stroke from story thumbnail. 2022-04-06 12:37:43 -04:00
Alex Hart 102d58502a Fix bad layout of group story text replies. 2022-04-06 12:37:43 -04:00
Alex Hart 477698f917 Use media forward sheet when forwarding in Media preview. 2022-04-06 12:37:43 -04:00
Alex Hart d865b5d7b5 Color the send button properly for insecure chats. 2022-04-06 12:37:43 -04:00
Alex Hart eeaf6df925 Change wording of receipts button. 2022-04-06 12:37:43 -04:00
Alex Hart 95abca4e03 Dismiss after pressing don't show this again. 2022-04-06 12:37:43 -04:00
Alex Hart c5906b6f3a Fix background color on forward sheet bottom bar. 2022-04-06 12:37:43 -04:00
Alex Hart 91c581b475 Do not process story records if capability doesn't support it. 2022-04-06 12:37:43 -04:00
Fumiaki Yoshimatsu 47760867d5 Adds additional padding to the bottom of the line so the following line wouldn't overlap the previous line.
Fixes a bug [reported by Salt505 in the beta forum](https://community.signalusers.org/t/beta-feedback-for-the-upcoming-android-5-26-release/38629/163).
2022-04-06 12:37:43 -04:00
Alex Hart 5612a5d9e4 Fix issue where all forwarded MMS media would try to send as a secure message. 2022-04-06 12:37:43 -04:00
Alex Hart 4c462bd75a Enforce L1 media restrictions on link preview thumbnails.
Co-Authored-By: Alexandre Erwin Ittner <110642+ittner@users.noreply.github.com>
2022-04-06 12:37:43 -04:00
Cesar Valiente 092b30f64f Utilize isSeparating for better foldable device support. 2022-04-06 12:37:43 -04:00
Greyson Parrelli b34ca8ca2f Improve handling of unknown IDs in storage service. 2022-04-06 12:37:43 -04:00
clauz9 e2c54eef77 Filter out some Base64 encoded status messages from search. 2022-04-06 12:37:43 -04:00
Fumiaki Yoshimatsu 2a2c27edef Remove unnecessary marginEnd attribute. 2022-04-06 12:37:43 -04:00
Rashad Sookram ec92d5ddb7 Display audio levels for each participant in group calls. 2022-04-06 12:37:43 -04:00
Alex Hart a9f208153c Fix artifacting corners on landing page. 2022-04-06 12:37:43 -04:00
Alex Hart d9ffd67f36 Update My Stories logic when user has not sent to a distribution list. 2022-04-06 12:37:43 -04:00
Alex Hart 19861ef0d1 Implement specification testing for StoryViewerViewModel. 2022-04-06 12:37:43 -04:00
Alex Hart 469879c211 Implement proper story viewer ordering. 2022-04-06 12:37:43 -04:00
clado 157198fd17 Add content descriptions for incoming call buttons.
Fixes #11995
2022-04-06 12:37:43 -04:00
Greyson Parrelli 0d61b8db38 Fixed threading issues with Spinner recent queries. 2022-04-06 12:37:43 -04:00
Greyson Parrelli 593334456a Add a local query history to Spinner. 2022-04-06 12:37:43 -04:00
Greyson Parrelli 98b9cc23e4 Add extension functions to improve writability of database queries. 2022-04-06 12:37:43 -04:00
Alex Hart 3e42c044b8 Add RxStore and StoryViewerPage forward navigation. 2022-04-06 12:37:43 -04:00
Cody Henthorne 11c3ea769e Fix emoji keyboard bugs and group story replies. 2022-04-06 12:37:43 -04:00
Sgn-32 b8bb2e234b Elimination of country code 0 in Delete account 2022-04-06 12:37:43 -04:00
Cody Henthorne 87b00bb156 Fix various story reply bottom sheet issues. 2022-04-06 12:37:43 -04:00
Alex Hart 3da2fc4d9b Clear storage keys for deleted distribution lists. 2022-04-06 12:37:43 -04:00
Cody Henthorne 972ab9b368 Process incomming story views even if read receipts are disabled. 2022-04-06 12:37:43 -04:00
Alex Hart c359b0134a Implement StoryDistributionListRecord and processing. 2022-04-06 12:37:43 -04:00
Alex Hart 2cd7462573 Add long-press action to mystories items for helpful debugging info. 2022-04-06 12:37:43 -04:00
Alex Hart 2a7d515932 Add handler to My Stories row to open my stories
If there are sent group stories.
2022-04-06 12:37:43 -04:00
Alex Hart 1bb04035ab Update playback to match specifications. 2022-04-06 12:37:43 -04:00
Alex Hart 267efb0763 Start viewer when clicking on story ring. 2022-04-06 12:37:43 -04:00
Alex Hart 0ef215dfc5 Size story bottomsheets to 60 percent height. 2022-04-06 12:37:43 -04:00
Cody Henthorne 50bea8140f Fix story reply mention fragment from taking over screen. 2022-04-06 12:37:43 -04:00
Chris Eager 086e3ed4ec Update message reporting to use sender ACI instead of E164.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2022-04-06 12:37:43 -04:00
Cody Henthorne b02539684a Fix unresolved authors of story replies. 2022-04-06 12:37:43 -04:00
Alex Hart b9747607ad Add blur to background for non 9:16 story content. 2022-04-06 12:37:43 -04:00
Cody Henthorne 284a6ae667 Add defaults for script/text font pairings and guessing of script based on body contents.
Co-authored-by: Alex Hart <alex@signal.org>
2022-04-06 12:37:43 -04:00
Alex Hart 116e711f1a Add logging for when we do nothing during a keep-alive job. 2022-04-06 12:36:32 -04:00
Cody Henthorne 7aeb641036 Include mentions on incoming story replies. 2022-04-06 12:36:32 -04:00
Fumiaki Yoshimatsu e4f69c0b6f Add padding to the toolbar in media preview activities.
Fixes #10298
2022-04-06 12:36:32 -04:00
Sgn-32 48d7228ae7 Replace use of AlertDialog.Builder with MaterialAlertDialogBuilder. 2022-04-06 12:36:32 -04:00
Alex Hart ae28df901f Hide view once for story first sending. 2022-04-06 12:36:32 -04:00
Alex Hart f17f45f277 Fix bad check for story context. 2022-04-06 12:36:32 -04:00
Alex Hart 2b15fc2966 Do not drop group stories if not profile sharing with sender. 2022-04-06 12:36:32 -04:00
Alex Hart 76a9342afa Safety number check and profile refresh for group sends. 2022-04-06 12:36:32 -04:00
Greyson Parrelli 14849d6e45 Fix case-insensitive queries for groups with non-ASCII characters.
Fixes #11464
2022-04-06 12:36:32 -04:00
Greyson Parrelli 77ea2deada Move more util classes to core-util. 2022-04-06 12:36:32 -04:00
Greyson Parrelli 390b7ff834 Convert SqlUtil to Kotlin. 2022-04-06 12:36:32 -04:00
Greyson Parrelli 0e4187b062 Use existing contact type for our linked entry. Add test to sample app.
Fixes #9431
Closes #9434

Co-authored-by: swatts <github@stargw.net>
2022-04-06 12:36:32 -04:00
Cody Henthorne 4098f77e08 Bump version to 5.34.10 2022-04-06 12:18:57 -04:00
Cody Henthorne a60eed35fe Updated language translations. 2022-04-06 12:18:10 -04:00
Cody Henthorne 904215fe38 Fix font networking main thread crash. 2022-04-06 12:04:55 -04:00
Greyson Parrelli 6376642d38 Pre-resolve recipient needed for group messages in the conversation list. 2022-04-06 11:13:56 -04:00
Cody Henthorne 6b36a446f0 Bump version to 5.34.9 2022-04-05 10:12:35 -04:00
Cody Henthorne 95ee7f5c00 Updated language translations. 2022-04-05 10:08:22 -04:00
Greyson Parrelli b109effc94 Prevent possibility of recursively enqueuing early message jobs. 2022-04-04 19:26:08 -04:00
Alex Hart 44efda8318 Bump version to 5.34.8 2022-03-31 17:09:07 -03:00
Alex Hart e6e1b6d746 Updated language translations. 2022-03-31 17:08:31 -03:00
Greyson Parrelli e303cbcc22 Fix some toolbar theming issues. 2022-03-31 14:06:37 -04:00
Greyson Parrelli c67aed5b65 Keep some crucial local logs (like crashes) for longer. 2022-03-30 16:32:45 -04:00
Greyson Parrelli 0278882c30 Prevent possible crash while reading contacts cursor. 2022-03-30 16:17:15 -04:00
Alex Hart 42ccd638bd Bump version to 5.34.7 2022-03-30 12:19:36 -03:00
Alex Hart e11577bd23 Updated language translations. 2022-03-30 12:15:24 -03:00
Greyson Parrelli 3c0b87bbca Fix possible backup crash due to foreign key constraint. 2022-03-30 09:56:26 -04:00
Christian Clauss 84a61b01ca Fix syntax error in apntool.py
Old-style exceptions are syntax errors in Python 3 while new-style exceptions work as expected on both Python 2 and Python 3.

Closes #10622
2022-03-30 09:39:34 -04:00
Greyson Parrelli c149e008fd Update apostrophes in code comments. 2022-03-30 09:37:12 -04:00
Greyson Parrelli c7a345eb0b Ignore inbound SMS/MMS from yourself. 2022-03-29 18:39:46 -04:00
Sgn-32 348b6e9742 Enable hyphenation on signal bottom action bar item title.
Closes #11896
2022-03-29 18:21:25 -04:00
Greyson Parrelli 003b3e02e4 Add ability to view your own profile photo fullscreen.
From the profile management screen.

Fixes #11033
2022-03-29 18:21:16 -04:00
Fumiaki Yoshimatsu 5b668d7931 Refresh the storage managment title on long-click.
Fixes #9698
Closes #10021
2022-03-29 18:21:08 -04:00
Greyson Parrelli 87748fa80c Use an inset ripple for contact list items.
Closes #10786

Co-authored-by: Thore Goebel <hello@thore.io>
2022-03-29 18:20:58 -04:00
Greyson Parrelli ad0482fb5b Bump version to 5.34.6 2022-03-29 13:58:01 -04:00
Greyson Parrelli 12ceb1cb32 Updated language translations. 2022-03-29 13:57:18 -04:00
Greyson Parrelli 9ec2c5da52 Show some buttons in media preview toolbar if there's room.
Fixes #11661
2022-03-29 10:43:04 -04:00
Greyson Parrelli f742d34588 Remove unnecessary package exclusions.
Fixes #9540
2022-03-29 10:27:24 -04:00
Cody Henthorne 4d8e058d33 Allow hyphenation of enter sms code buttons. 2022-03-29 10:13:32 -04:00
Cody Henthorne 77ba6e0f7b Enable telecom integration for internal users. 2022-03-29 10:08:04 -04:00
Fumiaki Yoshimatsu 76a0e5c851 Allow search view to be full width of the screen in landscape.
Fixes #10299
Closes #10321
2022-03-29 10:05:45 -04:00
Greyson Parrelli f3096cc24c Use more direct language in PIN reminder toast.
Fixes #10207
2022-03-29 09:37:20 -04:00
Greyson Parrelli 49957e1d95 Prevent changing disappearing message timer for blocked users.
Fixes #10973
2022-03-29 09:32:46 -04:00
Cody Henthorne 2f5cb5f090 Add story distribution list deduplication handling. 2022-03-28 19:43:42 -04:00
Ehren Kret ba394e1021 Use notifyDataSetChanged for potentially structural modification.
This has the potential to change the structure of the result set
displayed in the recycler view. Thus the functions that tell it the
structure changed need to be called. For an immediate fix, changing
this back to notifyDataSetChanged seems to resolve the crash.
2022-03-28 19:22:02 -04:00
Greyson Parrelli 7611c64493 Do not bidi-isolate all-ASCII strings.
Fixes #11630
2022-03-28 19:07:21 -04:00
Greyson Parrelli 231248d20a Update android test orchestrator to 1.4.1 2022-03-28 19:07:21 -04:00
Alex Hart a3a79fc58d Adjust text story button protections. 2022-03-28 19:07:21 -04:00
Alex Hart 6476e585c4 Fix cast in story landing fragment. 2022-03-28 19:07:21 -04:00
Greyson Parrelli fd2961710d Fix androidTests. 2022-03-28 19:07:21 -04:00
Greyson Parrelli 7f4ab67f98 Fix timing issue with receipt updates. 2022-03-28 19:07:21 -04:00
Greyson Parrelli b9ce38b85b Don't allow KEEP_LONGER logs to get too large. 2022-03-28 19:07:21 -04:00
Greyson Parrelli 50d5658add Fix possible NPE when backing out of conversation. 2022-03-28 19:07:21 -04:00
Greyson Parrelli 72777bc6cd Disallow some unicode sequences in link previews. 2022-03-28 19:07:21 -04:00
Rashad Sookram f2046c3c05 Vibrate when entering call reconnecting state. 2022-03-28 19:07:21 -04:00
Sgn-32 745dfc3fbb Fix translation issue with media preview.
Fixes #12072
Closes #12092
2022-03-28 19:07:21 -04:00
Greyson Parrelli cb77165b53 Fix issue where search results could flicker.
Shoutout to @clauz9 for the help in researching this bug!
2022-03-28 19:07:21 -04:00
Alex Hart bde4700e87 Hide new user entry in story recipient selection. 2022-03-28 19:07:21 -04:00
Alex Hart e58cea9a26 Fix bad use of toString in StripeApi. 2022-03-28 19:07:21 -04:00
Alex Hart 556dc0d1ec Show story rings for self and if you sent a story to a group. 2022-03-28 19:07:21 -04:00
Alex Hart 8c1ddcf1c0 Fix issue where thumb resource wasn't set to null after clear. 2022-03-28 19:07:21 -04:00
Alex Hart 2549c1f97d Add placeholder for text post thumbs. 2022-03-28 19:07:21 -04:00
Alex Hart 5faa497821 Get receipt credential presentation BEFORE recording receipt so that the retry does not add another receipt. 2022-03-28 19:07:21 -04:00
Alex Hart d7a7e72c3a Fix flashing when entering text story. 2022-03-28 19:07:21 -04:00
Alex Hart af1701e6fa Add HTTPS scheme when user enters a web address. 2022-03-28 19:07:21 -04:00
Rashad Sookram 32d1cc7d54 Recreate MainActivity on language change. 2022-03-28 19:07:20 -04:00
Alex Hart 783a615c07 Adjust text story creation layout size. 2022-03-28 19:07:20 -04:00
Alex Hart 65bfee6eba Display total unread messages including mark as unread instead of unread thread count. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 8d4419705b Update to libsignal-client 0.15.0 2022-03-28 19:07:20 -04:00
Alex Hart 6c3baf229c Fix broadcast send when user sends to both story and non-story. 2022-03-28 19:07:20 -04:00
Rashad Sookram 6e9a6283fc Fix layout for long story replies. 2022-03-28 19:07:20 -04:00
Alex Hart 5b3899237b Trampoline call to generate preview if view is not laid out. 2022-03-28 19:07:20 -04:00
Greyson Parrelli dddf830e47 Move system contact interactions into their own module. 2022-03-28 19:07:20 -04:00
Alex Hart fd930d0b1d Conversation tab bar animations. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 2b5d65ae04 Revert "Update to libsignal-client 0.15.0"
This reverts commit 3d5f04eba757563dd92366d994a96cf323b8d540.
2022-03-28 19:07:20 -04:00
clauz9 2ebaa04c2f Remove leftover code in SearchUtil.
Closes #12090
2022-03-28 19:07:20 -04:00
Jordan Rose 1e316ea19f Update to libsignal-client 0.15.0 2022-03-28 19:07:20 -04:00
Cody Henthorne ac9257ec1c Revert "Track inconsistencies between new and old network availability for internal users."
This reverts commit 007975e7da.
2022-03-28 19:07:20 -04:00
Alex Hart 9b83c5e283 Ensure we do not send captions for non-story messages. 2022-03-28 19:07:20 -04:00
Alex Hart a7a4972013 Check if there is an attachment available before trying to send it to a story. 2022-03-28 19:07:20 -04:00
Alex Hart f6f4e6fde7 Add animations to camera toggle. 2022-03-28 19:07:20 -04:00
Alex Hart e9160c2449 Suppress multiple clicks on tap to add. 2022-03-28 19:07:20 -04:00
Alex Hart b0b1029d0f Add transition fixes and improvements. 2022-03-28 19:07:20 -04:00
Rashad Sookram 72b3a0555d Improve transition back to creation fragment. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 135fde68c1 Migrate some cursor utils to core-util. 2022-03-28 19:07:20 -04:00
Alex Hart 954e45ed97 Fix capitalization retention for text stories. 2022-03-28 19:07:20 -04:00
Alex Hart 2b3f16d3ad Material3 Bottom Bar styling and fab for Stories only. 2022-03-28 19:07:20 -04:00
Alex Hart 6820b84921 Story Viewer shared element transition. 2022-03-28 19:07:20 -04:00
Cody Henthorne 6a5f5f4ffa Show verified badge on Note to Self. 2022-03-28 19:07:20 -04:00
Rashad Sookram 19381342b3 Update UI for replies to unavailable stories. 2022-03-28 19:07:20 -04:00
Greyson Parrelli c2627dda8d Migrate contact interactions to SystemContactsRepository. 2022-03-28 19:07:20 -04:00
Alex Hart db309b7930 Implement slide to close in Story viewer. 2022-03-28 19:07:20 -04:00
Cody Henthorne 403958fed3 Handle 1:1 call reconnecting events. 2022-03-28 19:07:20 -04:00
Cody Henthorne 3af53f2089 Allow ringrtc to decided how to respond to send message failure. 2022-03-28 19:07:20 -04:00
Cody Henthorne e472760d92 Fix MessageCountsViewModel memory leak. 2022-03-28 19:07:20 -04:00
Alex Hart b04acd8ae0 Add caption to outgoing stories. 2022-03-28 19:07:20 -04:00
Cody Henthorne 6890973ce8 Enforce limit for total number of blocked requests. 2022-03-28 19:07:20 -04:00
Alex Hart b3d9a85fa2 Fix button alignment. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 9f027ed584 Ensure unidentified access is correct when fetching own profile. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 8fb598e60a Restart app after refreshing remote config via internal settings. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 2edaba39a0 Fix support for PniIdentity sync message. 2022-03-28 19:07:20 -04:00
clauz9 b0be7effe8 Use notifyItemRangeChange in ConversationListFragment onResume().
Closes #12085
2022-03-28 19:07:20 -04:00
Greyson Parrelli 142979ce93 Add job to clean up early message receipts. 2022-03-28 19:07:20 -04:00
Rashad Sookram 093dd7c62c Update Material Design Components to 1.5.0. 2022-03-28 19:07:20 -04:00
Cody Henthorne 4acafc3d77 Fix translation issue with media preview.
Fixes #12072
2022-03-28 19:07:20 -04:00
Alex Hart 65bf0aad79 Fixes text story preview sizing on pixel 2. 2022-03-28 19:07:20 -04:00
Alex Hart ef6e846512 Text Camera Switch view positioning fixes. 2022-03-28 19:07:20 -04:00
Alex Hart 782464f664 Make messaging in keepalive more explicit. 2022-03-28 19:07:20 -04:00
Jim Gustafson c7352f62e5 Update to RingRTC v2.20.0 2022-03-28 19:07:20 -04:00
Rashad Sookram 90dd6b7cb3 Compile against API level 31. 2022-03-28 19:07:20 -04:00
Rashad Sookram ddfb4bf0a5 Fix context menu animation crash on Kitkat. 2022-03-28 19:07:20 -04:00
Alex Hart cdef21d6c0 Add animation when you directly react to a story. 2022-03-28 19:07:20 -04:00
Alex Hart c0f843061e Donations logs. 2022-03-28 19:07:19 -04:00
Alex Hart 5774771ea6 Reverse direction of swipe to reply. 2022-03-28 19:07:19 -04:00
Rashad Sookram ab8d5474e0 Handle keyboard resize when creating a text story. 2022-03-28 19:07:19 -04:00
Alex Hart 6497ec8098 Adjust sizes for text post text size. 2022-03-28 19:07:19 -04:00
Greyson Parrelli 83c3b16b92 Add ContactDiscovery abstraction for doing CDS refreshes. 2022-03-28 19:07:19 -04:00
Alex Hart 3c2bd032ba Fix NPE when secondary story does not have media and is not a text story. 2022-03-28 19:07:19 -04:00
Alex Hart f798866619 Maintain app bar layout when switching tabs. 2022-03-28 19:07:19 -04:00
Greyson Parrelli ffad2c7386 Bump version to 5.34.5 2022-03-28 12:53:12 -04:00
Greyson Parrelli 7252e54593 Updated language translations. 2022-03-28 12:53:12 -04:00
Greyson Parrelli cfab4dc658 Update stale-bot config. 2022-03-28 12:53:11 -04:00
Cody Henthorne 7f5a8ce6bb Disable telecom integration. 2022-03-28 12:34:50 -04:00
Greyson Parrelli d02a597451 Bump version to 5.34.4 2022-03-23 11:54:11 -04:00
Greyson Parrelli 8d92a1f195 Updated language translations. 2022-03-23 11:53:53 -04:00
Greyson Parrelli 74e630aacb Fix possible crash and remove old fastRecord system. 2022-03-23 11:44:37 -04:00
Greyson Parrelli 65a12767f9 Bump version to 5.34.3 2022-03-21 18:46:52 -04:00
Greyson Parrelli ab9d813636 Updated language translations. 2022-03-21 18:46:07 -04:00
Cody Henthorne 007975e7da Track inconsistencies between new and old network availability for internal users. 2022-03-21 17:21:42 -04:00
Greyson Parrelli 86ca1ebda0 Add basic handling for ProofRequiredExceptions on other message types. 2022-03-21 16:36:07 -04:00
Cody Henthorne 57fb3e6377 Fix crash when accidently triggering a second voice note record. 2022-03-21 13:36:03 -04:00
Cody Henthorne 7e6fcb80a3 Revert all new network detection API usage and refactorings. 2022-03-21 12:21:46 -04:00
Cody Henthorne 5e46e1e3d9 Bump version to 5.34.2 2022-03-18 15:48:26 -04:00
Cody Henthorne 21e370de9b Updated language translations. 2022-03-18 15:45:33 -04:00
Cody Henthorne 77ef877c59 Show block request if request follows a collapsed event. 2022-03-18 15:26:38 -04:00
Greyson Parrelli 77caedb3bb Avoid recipient resolves in Recipient#getGroupName(). 2022-03-18 14:49:06 -04:00
Cody Henthorne 3e77975c17 Revert "Fix soft keyboard popping up when the text was selected when the other keyboard was open."
This reverts commit 6d41d1f6d2.
2022-03-18 14:09:52 -04:00
Alex Hart 75e15c81e1 Ensure messages are marked read when entering a conversation with an active typing indicator. 2022-03-18 13:15:37 -03:00
Cody Henthorne 782a1ce301 Fix NPE in conversation update group info. 2022-03-18 12:00:04 -04:00
Cody Henthorne be21b9e163 Bump version to 5.34.1 2022-03-18 11:14:35 -04:00
Cody Henthorne 284140871e Updated language translations. 2022-03-18 11:06:32 -04:00
Cody Henthorne e6ac40a07c Fix various corner case block/reject join request bugs. 2022-03-18 11:03:38 -04:00
Rashad Sookram b8e98350c1 Add back aapt2 checksums for osx/windows. 2022-03-18 11:03:38 -04:00
Cody Henthorne 445ff263c6 Fix receiving group invite bug. 2022-03-18 11:03:38 -04:00
Alex Hart e10c40d2b8 Fix infinite loop when dealing with large video files. 2022-03-18 11:03:38 -04:00
Alex Hart a41a2b3e64 Fix wonky bottom sheet animations. 2022-03-18 11:03:38 -04:00
Alex Hart e603391c35 Fix several theming issues for Contact Selection. 2022-03-18 10:32:03 -03:00
Cody Henthorne 7e0cd99f48 Fix block request button not showing up always. 2022-03-17 19:51:22 -04:00
Cody Henthorne daedb8261d Fix empty group update bug. 2022-03-17 19:28:50 -04:00
Cody Henthorne 2c8744a319 Bump version to 5.34.0 2022-03-17 16:18:31 -04:00
Cody Henthorne a7c441225b Updated language translations. 2022-03-17 16:06:22 -04:00
Alex Hart e3c491860a Allow forwarding of Text Stories. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 43ad0b2294 Fix issue where group read was happening on main thread. 2022-03-17 16:02:43 -04:00
Alex Hart bf897d10d2 Add avatar to my story row and wire in badges. 2022-03-17 16:02:43 -04:00
Cody Henthorne 0b1a93d3e6 Disable Telecom integration solely for OnePlus devices. 2022-03-17 16:02:43 -04:00
Alex Hart 7edef20f4f Guard first time on add to my story. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 945c308cf5 Update group update messages faster. 2022-03-17 16:02:43 -04:00
Cody Henthorne f91494f813 Remove newer network detection APIs. 2022-03-17 16:02:43 -04:00
Cody Henthorne 9d28caac00 Fix bug preventing adding and inviting by phone number. 2022-03-17 16:02:43 -04:00
Alex Hart 798f3a7b0e Only display tilted image for My Story. 2022-03-17 16:02:43 -04:00
Alex Hart 768e170ed4 Fix hidden story behaviour. 2022-03-17 16:02:43 -04:00
Greyson Parrelli a0ebb891de Resolve multiple times when generating static IPs.
An attempt to make the list somewhat more stable.
2022-03-17 16:02:43 -04:00
Greyson Parrelli 570b39f82e Debounce menu invalidations in conversation. 2022-03-17 16:02:43 -04:00
Alex Hart dc50899fe0 Allow addition of text to text slide by tapping anywhere. 2022-03-17 16:02:43 -04:00
Alex Hart 0f889e0259 Add progress indicator to story text post link loader. 2022-03-17 16:02:43 -04:00
Greyson Parrelli cb906edd11 More accurate timings of conversation-open component. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 604f6709db Fix bug where wallpaper didn't update after changing. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 0359f27cd9 Fix true update queries for blobs. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 0ca438ed25 Update MSL appending to create a new entry if the original one is gone. 2022-03-17 16:02:43 -04:00
Rashad Sookram 6b6e9e92e8 Fix remote delete for private stories. 2022-03-17 16:02:43 -04:00
Greyson Parrelli b5e0991f5e Log early delivery receipts. 2022-03-17 16:02:43 -04:00
Alex Hart f06f0e7ae0 Pop open keyboard when we enter the link entry fragment. 2022-03-17 16:02:43 -04:00
Alex Hart 0fcbb5ffda Send user to My Stories from failure notification. 2022-03-17 16:02:42 -04:00
Alex Hart b1f7dbefd8 Drop stories from users we would normally show a message request for. 2022-03-17 16:02:42 -04:00
Alex Hart 8fc2d5be37 Add 32dp space to bottom of choose story type bottom sheet. 2022-03-17 16:02:42 -04:00
Alex Hart 40020728de Ensure proper text size is used when displaying and editing text stories. 2022-03-17 16:02:42 -04:00
Greyson Parrelli 4abb169568 Do not suggest SMS during onboarding. 2022-03-17 16:02:42 -04:00
Alex Hart da1ac5358f Add initial support for rendering link previews in text story previews. 2022-03-17 16:02:42 -04:00
Greyson Parrelli d504bd593a Improve wallpaper load speed. 2022-03-17 16:02:42 -04:00
Alex Hart 63e48efdfe Allow user to launch directly to a specific story, fix story chronology. 2022-03-17 16:02:42 -04:00
Greyson Parrelli 8bb27b60fa Revert "Fix data race preventing some story sends."
This reverts commit cde70269817464880ddb8e2c67e59ca8b571073b.
2022-03-17 16:02:42 -04:00
Alex Hart 437c1e2f21 Implement UI and backend for sending story reactions.
Co-authored-by: Rashad Sookram <rashad@signal.org>
2022-03-17 16:02:42 -04:00
Alex Hart 7f4a12c179 Fix issue where stories with links would fail to send. 2022-03-17 16:02:42 -04:00
Alex Hart 19d3bbc70a Order recipients in viewer by story sent date. 2022-03-17 16:02:42 -04:00
Alex Hart 559561bf72 Add support for message resends. 2022-03-17 16:02:42 -04:00
Rashad Sookram c8c0589ac4 Hide spell check errors in post preview. 2022-03-17 16:02:42 -04:00
Greyson Parrelli 666218773c Improve conversation open speed.
Co-authored-by: Cody Henthorne <cody@signal.org>
2022-03-17 16:02:42 -04:00
Cody Henthorne d3049a3433 Add block request action button to collapsed join request events. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 130d5a8945 Add index to improve speed of MMS count. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 172751cd42 Iterate over a snapshot of transaction listeners. 2022-03-17 12:12:56 -04:00
Rashad Sookram 3ad7c96a3c Fix ellipsis appearing in the middle of a message. 2022-03-17 12:12:56 -04:00
Fumiaki Yoshimatsu 6d41d1f6d2 Fix soft keyboard popping up when the text was selected when the other keyboard was open.
Fixes #11780
2022-03-17 12:12:56 -04:00
Rashad Sookram cb74833dc2 Fix scheduling of ExpireStoriesAlarm. 2022-03-17 12:12:56 -04:00
Alex Hart 8c7b6293fb Fix data race preventing some story sends. 2022-03-17 12:12:56 -04:00
Cody Henthorne 9d1f46da9f Collapse multiple join request/cancels when from a single person. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 216059b659 Fix layout for long text in SMS verification buttons.
Fixes #12037
2022-03-17 12:12:56 -04:00
Greyson Parrelli 18392ed0a4 Render date dividers based on sent time.
The time we use to render date headers needs to match the time we use to
render timestamps in the footer. We should be using sent time in both
cases.

Fixes #11589
2022-03-17 12:12:56 -04:00
Rashad Sookram 63a4d20ea9 Keep 1:1 replies after expiry and fix queries. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 057231b9c3 Update libsignal-client to 0.14.0 2022-03-17 12:12:56 -04:00
clauz9 749bbf428d Make sure isSearchRequest is true when searchViewItem is expanded.
fixes signalapp#12054
2022-03-17 12:12:56 -04:00
Alex Hart b0458f10a3 Ensure identity records are good before trying to send media. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 5b91c927b6 Refresh our own profile before rotating our profile key. 2022-03-17 12:12:56 -04:00
Greyson Parrelli b45740884b Only upload your avatar if it's being changed.
New server param means we don't have to upload the avatar if we want to
keep it the same.
2022-03-17 12:12:56 -04:00
Alex Hart 87ad4be117 Fix issue where user could not select a group story. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 78de70881f Fix responsiveness of profile photo edit UI.
There were various issues around the profile photo updating correctly in
the edit view. We want to make sure that what the user sees there is
what other people are seeing.

So I made some changes to make sure that when you remove your profile
photo the UI updates right away, as well as fixed most flickering
issues.
2022-03-17 12:12:56 -04:00
Greyson Parrelli e7a370a549 Fix paging issue where DataStatus was not updated on insert. 2022-03-17 12:12:56 -04:00
Alex Hart 54eb579558 Allow external shares to a story. 2022-03-17 12:12:56 -04:00
Alex Hart 732b67d8cb Allow injectable typefaces in image text editor.
Co-authored-by: Rashad Sookram <rashad@signal.org>
2022-03-17 12:12:56 -04:00
Cody Henthorne eed45b57a1 Prevent rejected/kicked group members from joining again via group link. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 3503c60fd1 Add routine check to ensure GV2 profiles are up-to-date. 2022-03-17 12:12:56 -04:00
Cody Henthorne c17ba30cfc Show different messages based on join group link error header. 2022-03-17 12:12:56 -04:00
Rashad Sookram 5167c7235d Don't animate to replies tab during open. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 803f94012a Handle profile key changes consistently. 2022-03-17 12:12:56 -04:00
Alex Hart 9281bcdd7d Only display stories if you entered through stories. 2022-03-17 12:12:56 -04:00
Alex Hart 4dca554967 Add better text reflow as font changes. 2022-03-17 12:12:55 -04:00
Alex Hart 7c45fb6c17 Fix issue where names with emoji would not display. 2022-03-17 12:12:55 -04:00
Alex Hart 8aa283488f Clear search query in Story recipient selection after a selection is made. 2022-03-17 12:12:55 -04:00
Alex Hart 604c65c7fb Add finalized story icon assets. 2022-03-17 12:12:55 -04:00
Alex Hart 711148423d Excise PowerMock and reenable like a bunch of ignored tests.
Co-authored-by: Rashad Sookram <rashad@signal.org>
2022-03-17 12:12:55 -04:00
Alex Hart 1f82ceecc6 Story Status for landing page and my stories. 2022-03-17 12:12:55 -04:00
Rashad Sookram 1ac8701ada Update Gradle to 7.4.1. 2022-03-17 12:12:55 -04:00
Alex Hart d61e33fdf3 Fix story display size logic. 2022-03-17 12:12:55 -04:00
Greyson Parrelli e552b5160f Implement CdshV2Service. 2022-03-17 12:12:55 -04:00
Greyson Parrelli 7e063e8ad8 Refactor CDSH to allow for code reuse. 2022-03-17 12:12:55 -04:00
Greyson Parrelli 88a34936cd Add more device info at the top of Spinner. 2022-03-17 12:12:55 -04:00
Greyson Parrelli c1181478dd Remove GV2 capability check. 2022-03-17 12:12:55 -04:00
Greyson Parrelli d13d8628b5 Small UI tweaks.
- Update distance below avatar in conversation settings.
- Slightly increase bubble corner radius.
2022-03-17 12:12:55 -04:00
Alex Hart 6048208c8c Fix crash with incorrectly tagged story. 2022-03-17 12:12:55 -04:00
Alex Hart 78214fb39b Update click boundaries in story viewer. 2022-03-17 12:12:55 -04:00
Alex Hart ff8d7fa6c2 Add send/recv/render support for text stories. 2022-03-17 12:12:55 -04:00
Greyson Parrelli 3a2e8b9b19 Add internal button to force an emoji search index download. 2022-03-17 12:12:55 -04:00
Cody Henthorne bca4289c96 Updated language translations. 2022-03-17 12:12:55 -04:00
Cody Henthorne 3fbd9baf0c Disable telecom integration. 2022-03-17 12:11:51 -04:00
Cody Henthorne e12c96f4b2 Bump version to 5.33.6 2022-03-17 12:09:16 -04:00
Cody Henthorne eec26aa481 Updated language translations. 2022-03-17 12:07:17 -04:00
Cody Henthorne 865aeda6f2 Disable telecom integration. 2022-03-17 12:04:33 -04:00
Cody Henthorne 2c4ebedda4 Bump version to 5.33.5 2022-03-16 13:57:08 -04:00
Cody Henthorne 042bc8d79a Updated language translations. 2022-03-16 13:56:52 -04:00
Cody Henthorne 4c7bd80f72 Fix early ringing state on slow connections. 2022-03-16 13:37:28 -04:00
Cody Henthorne 3a8591fdfb Bump version to 5.33.4 2022-03-16 10:22:25 -04:00
Cody Henthorne 629aaa2093 Updated language translations. 2022-03-16 10:18:41 -04:00
Cody Henthorne 5b5b118b7a Fix disconnect sound on call termination. 2022-03-16 10:08:06 -04:00
Greyson Parrelli c7016aa462 Fallback to legacy network detection. 2022-03-15 11:28:31 -04:00
Cody Henthorne cf857e109a Fix mention rendering bug. 2022-03-15 10:32:59 -04:00
Alex Hart 1c79840684 Bump version to 5.33.3 2022-03-11 16:10:06 -04:00
Alex Hart 4ba7de9519 Updated language translations. 2022-03-11 16:09:14 -04:00
Cody Henthorne 2eb8df347e Fix mention rendering regression. 2022-03-11 14:18:42 -05:00
Rashad Sookram 9056371c41 Fix quote preview being cut off.
When determining the height to force for the animation, the text was
being measured assuming it had infinite width, which made
it seem like it could fit on one line.
2022-03-11 11:38:56 -05:00
Greyson Parrelli 1f57e1f366 Add more logging around network changes. 2022-03-11 10:35:40 -05:00
Alex Hart aeb568bcf4 Fix crash when recreating conversation react with any emoji fragment. 2022-03-11 09:44:47 -04:00
Cody Henthorne b7afe4411e Fix NPE in telecom integration. 2022-03-10 16:17:13 -05:00
Alex Hart cba784b8ec Bump version to 5.33.2 2022-03-10 16:53:01 -04:00
Alex Hart 3aba15e88d Updated language translations. 2022-03-10 16:52:01 -04:00
Cody Henthorne fa384e93dc Fix crash importing backups. 2022-03-10 15:46:45 -05:00
Alex Hart 1f3e04da29 Fix text size in generated text drawables. 2022-03-10 12:32:42 -04:00
Greyson Parrelli a484d48377 Update network connectivity observer to be more optimistic. 2022-03-10 11:21:21 -05:00
Greyson Parrelli 15f51ea26e Fix crash if synced pinned contact is malformed. 2022-03-10 11:14:55 -05:00
Greyson Parrelli 80bfa103ab Fix narrow race around generation of some ACI keys. 2022-03-10 11:11:14 -05:00
Cody Henthorne 66f93e0d32 Do not drop 1:1 messages with mentions due to iOS and desktop regression.
iOS and Desktop both regressed in multi-forwarding by including mentions
in 1:1 forwards instead of replacing them with plain text. Android by
default drops these as invalid messages. Since there are clients in the
wild that do this now, we have to stop dropping them and try to resolve
them per normal mechanisms.
2022-03-09 12:09:46 -05:00
Rashad Sookram 366780f6cb Revert "Avoid querying conversation size twice."
This reverts commit fe088c39c7.
2022-03-09 11:55:52 -05:00
Alex Hart fb4c1fc268 Bump version to 5.33.1 2022-03-08 16:11:46 -04:00
Alex Hart e3bb7ccbd3 Updated language translations. 2022-03-08 16:11:09 -04:00
Alex Hart e3fb8a2137 Only update tab if it has actually changed. 2022-03-08 12:01:21 -04:00
1512 changed files with 48495 additions and 17853 deletions
+23
View File
@@ -0,0 +1,23 @@
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
issues:
exemptLabels:
- acknowledged
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been closed due to inactivity.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 1
+1 -1
View File
@@ -96,7 +96,7 @@ try:
print("\nTo include this in the distribution, copy it to the project's assets/databases/ directory.") print("\nTo include this in the distribution, copy it to the project's assets/databases/ directory.")
print("If you support API 10 or lower, you must use the gzipped version to avoid corruption.") print("If you support API 10 or lower, you must use the gzipped version to avoid corruption.")
except sqlite3.Error, e: except sqlite3.Error as e:
if connection: if connection:
connection.rollback() connection.rollback()
print("Error: %s" % e.args[0]) print("Error: %s" % e.args[0])
+6 -8
View File
@@ -63,8 +63,8 @@ ktlint {
version = "0.43.2" version = "0.43.2"
} }
def canonicalVersionCode = 1019 def canonicalVersionCode = 1044
def canonicalVersionName = "5.33.0" def canonicalVersionName = "5.36.3"
def postFixSize = 100 def postFixSize = 100
def abiPostFix = ['universal' : 0, def abiPostFix = ['universal' : 0,
@@ -455,8 +455,9 @@ dependencies {
implementation project(':device-transfer') implementation project(':device-transfer')
implementation project(':image-editor') implementation project(':image-editor')
implementation project(':donations') implementation project(':donations')
implementation project(':contacts')
implementation libs.signal.client.android implementation libs.libsignal.android
implementation libs.google.protobuf.javalite implementation libs.google.protobuf.javalite
implementation(libs.mobilecoin) { implementation(libs.mobilecoin) {
@@ -524,10 +525,7 @@ dependencies {
testImplementation testLibs.junit.junit testImplementation testLibs.junit.junit
testImplementation testLibs.assertj.core testImplementation testLibs.assertj.core
testImplementation testLibs.mockito.core testImplementation testLibs.mockito.core
testImplementation testLibs.powermock.api.mockito testImplementation testLibs.mockito.kotlin
testImplementation testLibs.powermock.module.junit4.core
testImplementation testLibs.powermock.module.junit4.rule
testImplementation testLibs.powermock.classloading.xstream
testImplementation testLibs.androidx.test.core testImplementation testLibs.androidx.test.core
testImplementation (testLibs.robolectric.robolectric) { testImplementation (testLibs.robolectric.robolectric) {
@@ -554,7 +552,7 @@ dependencies {
implementation libs.rxjava3.rxkotlin implementation libs.rxjava3.rxkotlin
implementation libs.rxdogtag implementation libs.rxdogtag
androidTestUtil 'androidx.test:orchestrator:1.4.0' androidTestUtil 'androidx.test:orchestrator:1.4.1'
} }
def getLastCommitTimestamp() { def getLastCommitTimestamp() {
+1
View File
@@ -2,6 +2,7 @@
-dontobfuscate -dontobfuscate
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; } -keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.protocol.** { *; }
-keep class org.thoughtcrime.securesms.** { *; } -keep class org.thoughtcrime.securesms.** { *; }
-keepclassmembers class ** { -keepclassmembers class ** {
public void onEvent*(**); public void onEvent*(**);
@@ -8,7 +8,6 @@ import org.thoughtcrime.securesms.database.model.DistributionListRecord
import org.thoughtcrime.securesms.database.model.StoryType import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.ACI
import java.lang.IllegalStateException
import java.util.UUID import java.util.UUID
class DistributionListDatabaseTest { class DistributionListDatabaseTest {
@@ -0,0 +1,194 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class MmsDatabaseTest_stories {
private lateinit var mms: MmsDatabase
private val localAci = ACI.from(UUID.randomUUID())
private val localPni = PNI.from(UUID.randomUUID())
private lateinit var myStory: Recipient
private lateinit var recipients: List<RecipientId>
@Before
fun setUp() {
mms = SignalDatabase.mms
mms.deleteAllThreads()
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY))
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
}
@Test
fun givenNoStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectAnEmptyList() {
// WHEN
val result = mms.orderedStoryRecipientsAndIds
// THEN
assertEquals(0, result.size)
}
@Test
fun givenOneOutgoingAndOneIncomingStory_whenIGetOrderedStoryRecipientsAndIds_thenIExpectIncomingThenOutgoing() {
// GIVEN
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
val sender = recipients[0]
MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 1,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = threadId
)
MmsHelper.insert(
IncomingMediaMessage(
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES
),
-1L
)
// WHEN
val result = mms.orderedStoryRecipientsAndIds
// THEN
assertEquals(listOf(sender.toLong(), myStory.id.toLong()), result.map { it.recipientId.toLong() })
}
@Test
fun givenAStory_whenISetIncomingStoryMessageViewed_thenIExpectASetReceiptTimestamp() {
// GIVEN
val sender = recipients[0]
val messageId = MmsHelper.insert(
IncomingMediaMessage(
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES
),
-1L
).get().messageId
val messageBeforeMark = SignalDatabase.mms.getMessageRecord(messageId)
assertFalse(messageBeforeMark.incomingStoryViewedAtTimestamp > 0)
// WHEN
SignalDatabase.mms.setIncomingMessageViewed(messageId)
// THEN
val messageAfterMark = SignalDatabase.mms.getMessageRecord(messageId)
assertTrue(messageAfterMark.incomingStoryViewedAtTimestamp > 0)
}
@Test
fun given5ViewedStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectLatestViewedFirst() {
// GIVEN
val messageIds = recipients.take(5).map {
MmsHelper.insert(
IncomingMediaMessage(
from = it,
sentTimeMillis = 2,
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES,
),
-1L
).get().messageId
}
val randomizedOrderedIds = messageIds.shuffled()
randomizedOrderedIds.forEach {
SignalDatabase.mms.setIncomingMessageViewed(it)
Thread.sleep(5)
}
// WHEN
val result = SignalDatabase.mms.orderedStoryRecipientsAndIds
val resultOrderedIds = result.map { it.messageId }
// THEN
assertEquals(randomizedOrderedIds.reversed(), resultOrderedIds)
}
@Test
fun given15Stories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectUnviewedThenInterspersedViewedAndSelfSendsAllDescending() {
val myStoryThread = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
val unviewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES,
),
-1L
).get().messageId
}
val viewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES,
),
-1L
).get().messageId
}
val interspersedIds: List<Long> = (0 until 10).map {
Thread.sleep(5)
if (it % 2 == 0) {
SignalDatabase.mms.setIncomingMessageViewed(viewedIds[it / 2])
viewedIds[it / 2]
} else {
MmsHelper.insert(
recipient = myStory,
sentTimeMillis = System.currentTimeMillis(),
storyType = StoryType.STORY_WITH_REPLIES,
threadId = myStoryThread
)
}
}
val result = SignalDatabase.mms.orderedStoryRecipientsAndIds
val resultOrderedIds = result.map { it.messageId }
assertEquals(unviewedIds.reversed() + interspersedIds.reversed(), resultOrderedIds)
}
}
@@ -0,0 +1,64 @@
package org.thoughtcrime.securesms.database
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
import org.thoughtcrime.securesms.recipients.Recipient
import java.util.Optional
/**
* Helper methods for inserting an MMS message into the MMS table.
*/
object MmsHelper {
fun insert(
recipient: Recipient = Recipient.UNKNOWN,
body: String = "body",
sentTimeMillis: Long = System.currentTimeMillis(),
subscriptionId: Int = -1,
expiresIn: Long = 0,
viewOnce: Boolean = false,
distributionType: Int = ThreadDatabase.DistributionTypes.DEFAULT,
threadId: Long = 1,
storyType: StoryType = StoryType.NONE
): Long {
val message = OutgoingMediaMessage(
recipient,
body,
emptyList(),
sentTimeMillis,
subscriptionId,
expiresIn,
viewOnce,
distributionType,
storyType,
null,
false,
null,
emptyList(),
emptyList(),
emptyList(),
emptySet(),
emptySet()
)
return insert(
message = message,
threadId = threadId,
)
}
fun insert(
message: OutgoingMediaMessage,
threadId: Long
): Long {
return SignalDatabase.mms.insertMessageOutbox(message, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null)
}
fun insert(
message: IncomingMediaMessage,
threadId: Long
): Optional<MessageDatabase.InsertResult> {
return SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, threadId)
}
}
@@ -18,10 +18,9 @@ import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.PNI
import java.lang.IllegalArgumentException import java.util.Optional
import java.util.UUID import java.util.UUID
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@@ -523,7 +522,7 @@ class RecipientDatabaseTest {
} }
private fun ensureDbEmpty() { private fun ensureDbEmpty() {
SignalDatabase.rawDatabase.rawQuery("SELECT COUNT(*) FROM ${RecipientDatabase.TABLE_NAME}", null).use { cursor -> SignalDatabase.rawDatabase.rawQuery("SELECT COUNT(*) FROM ${RecipientDatabase.TABLE_NAME} WHERE ${RecipientDatabase.DISTRIBUTION_LIST_ID} IS NULL ", null).use { cursor ->
assertTrue(cursor.moveToFirst()) assertTrue(cursor.moveToFirst())
assertEquals(0, cursor.getLong(0)) assertEquals(0, cursor.getLong(0))
} }
@@ -9,13 +9,16 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.signal.core.util.CursorUtil
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.signal.libsignal.protocol.state.SessionRecord
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.storageservice.protos.groups.local.DecryptedGroup import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedMember import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.signal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.conversation.colors.AvatarColor import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.model.DistributionListId import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListRecord import org.thoughtcrime.securesms.database.model.DistributionListRecord
@@ -30,14 +33,10 @@ import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingTextMessage import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.thoughtcrime.securesms.util.CursorUtil
import org.whispersystems.libsignal.IdentityKey
import org.whispersystems.libsignal.SignalProtocolAddress
import org.whispersystems.libsignal.state.SessionRecord
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.util.UuidUtil import org.whispersystems.signalservice.api.util.UuidUtil
import java.util.Optional
import java.util.UUID import java.util.UUID
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@@ -76,8 +75,6 @@ class RecipientDatabaseTest_merges {
SignalStore.account().setAci(localAci) SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni) SignalStore.account().setPni(localPni)
ensureDbEmpty()
} }
/** High trust lets you merge two different users into one. You should prefer the ACI user. Not shown: merging threads, dropping e164 sessions, etc. */ /** High trust lets you merge two different users into one. You should prefer the ACI user. Not shown: merging threads, dropping e164 sessions, etc. */
@@ -217,19 +214,12 @@ class RecipientDatabaseTest_merges {
private val context: Application private val context: Application
get() = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application get() = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
private fun ensureDbEmpty() { private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingTextMessage {
SignalDatabase.rawDatabase.rawQuery("SELECT COUNT(*) FROM ${RecipientDatabase.TABLE_NAME}", null).use { cursor ->
assertTrue(cursor.moveToFirst())
assertEquals(0, cursor.getLong(0))
}
}
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.absent()): IncomingTextMessage {
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null) return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
} }
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.absent()): IncomingMediaMessage { private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMediaMessage {
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.absent()) return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.empty())
} }
private fun identityKey(value: Byte): IdentityKey { private fun identityKey(value: Byte): IdentityKey {
@@ -0,0 +1,292 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.notNullValue
import org.hamcrest.Matchers.nullValue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Hex
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.thoughtcrime.securesms.database.model.databaseprotos.addMember
import org.thoughtcrime.securesms.database.model.databaseprotos.addRequestingMember
import org.thoughtcrime.securesms.database.model.databaseprotos.deleteRequestingMember
import org.thoughtcrime.securesms.database.model.databaseprotos.groupChange
import org.thoughtcrime.securesms.database.model.databaseprotos.groupContext
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.Optional
import java.util.UUID
@Suppress("ClassName", "TestFunctionName")
@RunWith(AndroidJUnit4::class)
class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
private lateinit var recipients: RecipientDatabase
private lateinit var sms: SmsDatabase
private val localAci = ACI.from(UUID.randomUUID())
private val localPni = PNI.from(UUID.randomUUID())
private var wallClock: Long = 1000
private lateinit var alice: RecipientId
private lateinit var bob: RecipientId
@Before
fun setUp() {
recipients = SignalDatabase.recipients
sms = SignalDatabase.sms
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
alice = recipients.getOrInsertFromServiceId(aliceServiceId)
bob = recipients.getOrInsertFromServiceId(bobServiceId)
}
/**
* Do nothing if no previous messages.
*/
@Test
fun noPreviousMessage() {
val result = sms.collapseJoinRequestEventsIfPossible(
1,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
}
/**
* Do nothing if previous message is text.
*/
@Test
fun previousTextMesssage() {
val threadId = sms.insertMessageInbox(smsMessage(sender = alice, body = "What up")).get().threadId
val result = sms.collapseJoinRequestEventsIfPossible(
threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
}
/**
* Do nothing if previous is unrelated group change.
*/
@Test
fun previousUnrelatedGroupChange() {
val threadId = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addMember(bobServiceId)
}
}
)
).get().threadId
val result = sms.collapseJoinRequestEventsIfPossible(
threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
}
/**
* Do nothing if previous join request is from a different recipient.
*/
@Test
fun previousJoinRequestFromADifferentRecipient() {
val threadId = sms.insertMessageInbox(
groupUpdateMessage(
sender = bob,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = bobServiceId) {
deleteRequestingMember(bobServiceId)
}
}
)
).get().threadId
val result = sms.collapseJoinRequestEventsIfPossible(
threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
}
/**
* Collapse if previous is join request from same.
*/
@Test
fun previousJoinRequestCollapse() {
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addRequestingMember(aliceServiceId)
}
}
)
).get()
val result = sms.collapseJoinRequestEventsIfPossible(
latestMessage.threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
assertThat("result message id should be same as latest message", result.get().messageId, `is`(latestMessage.messageId))
}
/**
* Collapse if previous is join request from same, and leave second previous alone if text.
*/
@Test
fun previousJoinThenTextCollapse() {
val secondLatestMessage = sms.insertMessageInbox(smsMessage(sender = alice, body = "What up")).get()
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addRequestingMember(aliceServiceId)
}
}
)
).get()
assert(secondLatestMessage.threadId == latestMessage.threadId)
val result = sms.collapseJoinRequestEventsIfPossible(
latestMessage.threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
assertThat("result message id should be same as latest message", result.get().messageId, `is`(latestMessage.messageId))
}
/**
* Collapse "twice" is previous is a join request and second previous is already collapsed join/delete from the same recipient.
*/
@Test
fun previousCollapseAndJoinRequestDoubleCollapse() {
val secondLatestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addRequestingMember(aliceServiceId)
deleteRequestingMember(aliceServiceId)
}
}
)
).get()
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addRequestingMember(aliceServiceId)
}
}
)
).get()
assert(secondLatestMessage.threadId == latestMessage.threadId)
val result = sms.collapseJoinRequestEventsIfPossible(
latestMessage.threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
assertThat("result message id should be same as second latest message", result.get().messageId, `is`(secondLatestMessage.messageId))
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue())
}
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingTextMessage {
wallClock++
return IncomingTextMessage(sender, 1, wallClock, wallClock, wallClock, body, Optional.of(groupId), 0, true, null)
}
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingGroupUpdateMessage {
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext)
}
companion object {
private val aliceServiceId: ServiceId = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
private val bobServiceId: ServiceId = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
private val masterKey = GroupMasterKey(Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
private val groupId = GroupId.v2(masterKey)
}
}
@@ -0,0 +1,192 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.containsInAnyOrder
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is`
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class StorySendsDatabaseTest {
private lateinit var recipients1to10: List<RecipientId>
private lateinit var recipients11to20: List<RecipientId>
private lateinit var recipients6to15: List<RecipientId>
private lateinit var recipients6to10: List<RecipientId>
private var messageId1: Long = 0
private var messageId2: Long = 0
private var messageId3: Long = 0
private lateinit var storySends: StorySendsDatabase
@Before
fun setup() {
storySends = SignalDatabase.storySends
messageId1 = MmsHelper.insert(storyType = StoryType.STORY_WITHOUT_REPLIES)
messageId2 = MmsHelper.insert(storyType = StoryType.STORY_WITH_REPLIES)
messageId3 = MmsHelper.insert(storyType = StoryType.STORY_WITHOUT_REPLIES)
recipients1to10 = makeRecipients(10)
recipients11to20 = makeRecipients(10)
recipients6to15 = recipients1to10.takeLast(5) + recipients11to20.take(5)
recipients6to10 = recipients1to10.takeLast(5)
}
@Test
fun getRecipientsToSendTo_noOverlap() {
storySends.insert(messageId1, recipients1to10, 100, false)
storySends.insert(messageId2, recipients11to20, 200, true)
storySends.insert(messageId3, recipients1to10, 300, false)
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 200, true)
assertThat(recipientIdsForMessage1, hasSize(10))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(10))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients11to20.toTypedArray()))
}
@Test
fun getRecipientsToSendTo_overlap() {
storySends.insert(messageId1, recipients1to10, 100, false)
storySends.insert(messageId2, recipients6to15, 100, true)
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, true)
assertThat(recipientIdsForMessage1, hasSize(5))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(10))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.toTypedArray()))
}
@Test
fun getRecipientsToSendTo_overlapAll() {
val recipient1 = recipients1to10.first()
val recipient2 = recipients11to20.first()
storySends.insert(messageId1, listOf(recipient1, recipient2), 100, false)
storySends.insert(messageId2, listOf(recipient1), 100, true)
storySends.insert(messageId3, listOf(recipient2), 100, true)
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, true)
val recipientIdsForMessage3 = storySends.getRecipientsToSendTo(messageId3, 100, true)
assertThat(recipientIdsForMessage1, hasSize(0))
assertThat(recipientIdsForMessage2, hasSize(1))
assertThat(recipientIdsForMessage2, containsInAnyOrder(recipient1))
assertThat(recipientIdsForMessage3, hasSize(1))
assertThat(recipientIdsForMessage3, containsInAnyOrder(recipient2))
}
@Test
fun getRecipientsToSendTo_overlapWithEarlierMessage() {
storySends.insert(messageId1, recipients6to15, 100, true)
storySends.insert(messageId2, recipients1to10, 100, false)
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, true)
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, false)
assertThat(recipientIdsForMessage1, hasSize(10))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients6to15.toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(5))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
}
@Test
fun getRemoteDeleteRecipients_noOverlap() {
storySends.insert(messageId1, recipients1to10, 100, false)
storySends.insert(messageId2, recipients11to20, 200, true)
storySends.insert(messageId3, recipients1to10, 300, false)
val recipientIdsForMessage1 = storySends.getRemoteDeleteRecipients(messageId1, 100)
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
assertThat(recipientIdsForMessage1, hasSize(10))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(10))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients11to20.toTypedArray()))
}
@Test
fun getRemoteDeleteRecipients_overlapNoPreviousDeletes() {
storySends.insert(messageId1, recipients1to10, 200, false)
storySends.insert(messageId2, recipients6to15, 200, true)
val recipientIdsForMessage1 = storySends.getRemoteDeleteRecipients(messageId1, 200)
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
assertThat(recipientIdsForMessage1, hasSize(5))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(5))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.takeLast(5).toTypedArray()))
}
@Test
fun getRemoteDeleteRecipients_overlapWithPreviousDeletes() {
storySends.insert(messageId1, recipients1to10, 200, false)
SignalDatabase.mms.markAsRemoteDelete(messageId1)
storySends.insert(messageId2, recipients6to15, 200, true)
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
assertThat(recipientIdsForMessage2, hasSize(10))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.toTypedArray()))
}
@Test
fun canReply_storyWithReplies() {
storySends.insert(messageId2, recipients1to10, 200, true)
val canReply = storySends.canReply(recipients1to10[0], 200)
assertThat(canReply, `is`(true))
}
@Test
fun canReply_storyWithoutReplies() {
storySends.insert(messageId1, recipients1to10, 200, false)
val canReply = storySends.canReply(recipients1to10[0], 200)
assertThat(canReply, `is`(false))
}
@Test
fun canReply_storyWithAndWithoutRepliesOverlap() {
storySends.insert(messageId1, recipients1to10, 200, false)
storySends.insert(messageId2, recipients6to10, 200, true)
val message1OnlyRecipientCanReply = storySends.canReply(recipients1to10[0], 200)
val message2RecipientCanReply = storySends.canReply(recipients6to10[0], 200)
assertThat(message1OnlyRecipientCanReply, `is`(false))
assertThat(message2RecipientCanReply, `is`(true))
}
private fun makeRecipients(count: Int): List<RecipientId> {
return (1..count).map {
SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))
}
}
}
@@ -4,7 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.thoughtcrime.securesms.util.Hex; import org.signal.core.util.Hex;
import org.whispersystems.signalservice.api.kbs.HashedPin; import org.whispersystems.signalservice.api.kbs.HashedPin;
import org.whispersystems.signalservice.api.kbs.KbsData; import org.whispersystems.signalservice.api.kbs.KbsData;
import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.kbs.MasterKey;
+5 -2
View File
@@ -179,11 +179,12 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity android:name=".sharing.ShareActivity" <activity android:name=".sharing.v2.ShareActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:taskAffinity="" android:taskAffinity=""
android:windowSoftInputMode="stateHidden" android:windowSoftInputMode="stateHidden"
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@@ -403,7 +404,7 @@
<activity <activity
android:name=".stories.viewer.StoryViewerActivity" android:name=".stories.viewer.StoryViewerActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar" android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" /> android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity" <activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
@@ -757,6 +758,8 @@
<receiver android:name=".service.ExpirationListener" /> <receiver android:name=".service.ExpirationListener" />
<receiver android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm" />
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" /> <receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" /> <receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
package com.google.android.material.bottomsheet
import android.view.View
import android.widget.FrameLayout
import java.lang.ref.WeakReference
/**
* Manually adjust the nested scrolling child for a given [BottomSheetBehavior].
*/
object BottomSheetBehaviorHack {
fun setNestedScrollingChild(behavior: BottomSheetBehavior<FrameLayout>, view: View) {
behavior.nestedScrollingChildRef = WeakReference(view)
}
}
@@ -35,6 +35,7 @@ import org.signal.core.util.logging.AndroidLogger;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.core.util.tracing.Tracer; import org.signal.core.util.tracing.Tracer;
import org.signal.glide.SignalGlideCodecs; import org.signal.glide.SignalGlideCodecs;
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider;
import org.signal.ringrtc.CallManager; import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage; import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
@@ -53,6 +54,7 @@ import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob; import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
import org.thoughtcrime.securesms.jobs.FcmRefreshJob; import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
import org.thoughtcrime.securesms.jobs.FontDownloaderJob; import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
@@ -91,7 +93,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VersionTracker; import org.thoughtcrime.securesms.util.VersionTracker;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper; import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
import java.net.SocketException; import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
@@ -201,6 +202,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(() -> AndroidTelecomUtil.registerPhoneAccount()) .addPostRender(() -> AndroidTelecomUtil.registerPhoneAccount())
.addPostRender(() -> ApplicationDependencies.getJobManager().add(new FontDownloaderJob())) .addPostRender(() -> ApplicationDependencies.getJobManager().add(new FontDownloaderJob()))
.addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary) .addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary)
.addPostRender(GroupV2UpdateSelfProfileKeyJob::enqueueForGroupsIfNecessary)
.execute(); .execute();
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms"); Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
@@ -14,6 +14,7 @@ import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
@@ -58,6 +59,12 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle(); return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle();
} }
@Override
protected void attachBaseContext(@NonNull Context newBase) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
super.attachBaseContext(newBase);
}
@Override @Override
protected void onCreate(Bundle savedInstanceState, boolean ready) { protected void onCreate(Bundle savedInstanceState, boolean ready) {
super.onCreate(savedInstanceState, ready); super.onCreate(savedInstanceState, ready);
@@ -132,7 +139,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
findViewById(android.R.id.content).setOnClickListener(v -> fullscreenHelper.toggleUiVisibility()); findViewById(android.R.id.content).setOnClickListener(v -> fullscreenHelper.toggleUiVisibility());
fullscreenHelper.configureToolbarSpacer(findViewById(R.id.toolbar_cutout_spacer)); fullscreenHelper.configureToolbarLayout(findViewById(R.id.toolbar_cutout_spacer), toolbar);
fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout)); fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
} }
@@ -26,10 +26,10 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional;
import java.util.Set; import java.util.Set;
public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, Colorizable, Multiselectable { public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, Colorizable, Multiselectable {
@@ -60,6 +60,10 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
// Intentionally Blank. // Intentionally Blank.
} }
default void updateSelectedState() {
// Intentionall Blank.
}
interface EventListener { interface EventListener {
void onQuoteClicked(MmsMessageRecord messageRecord); void onQuoteClicked(MmsMessageRecord messageRecord);
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview); void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
@@ -94,6 +98,8 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void onChangeNumberUpdateContact(@NonNull Recipient recipient); void onChangeNumberUpdateContact(@NonNull Recipient recipient);
void onCallToAction(@NonNull String action); void onCallToAction(@NonNull String action);
void onDonateClicked(); void onDonateClicked();
void onBlockJoinRequest(@NonNull Recipient recipient);
void onRecipientNameClicked(@NonNull RecipientId target);
/** @return true if handled, false if you want to let the normal url handling continue */ /** @return true if handled, false if you want to let the normal url handling continue */
boolean onUrlClicked(@NonNull String url); boolean onUrlClicked(@NonNull String url);
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet; import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
@@ -13,7 +13,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.signal.core.util.concurrent.SimpleTask;
/** /**
* This should be used whenever we want to prompt the user to block/unblock a recipient. * This should be used whenever we want to prompt the user to block/unblock a recipient.
@@ -27,16 +27,16 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.ContactFilterView; import org.thoughtcrime.securesms.components.ContactFilterView;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@@ -153,7 +153,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
@Override @Override
protected Void doInBackground(Context... params) { protected Void doInBackground(Context... params) {
try { try {
DirectoryHelper.refreshDirectory(params[0], true); ContactDiscovery.refreshAll(params[0], true);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }
@@ -70,7 +70,7 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.HeaderAction; import org.thoughtcrime.securesms.contacts.HeaderAction;
import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration; import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration;
import org.thoughtcrime.securesms.contacts.SelectedContact; import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.groups.SelectionLimits; import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog; import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
@@ -85,13 +85,13 @@ import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter; import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader; import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.signal.core.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -576,7 +576,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
@Override @Override
protected Boolean doInBackground(Void... voids) { protected Boolean doInBackground(Void... voids) {
try { try {
DirectoryHelper.refreshDirectory(context, false); ContactDiscovery.refreshAll(context, false);
return true; return true;
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
@@ -637,8 +637,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener { private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
@Override @Override
public void onItemClick(ContactSelectionListItem contact) { public void onItemClick(ContactSelectionListItem contact) {
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber()) SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orElse(null), contact.getNumber())
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber()); : SelectedContact.forPhone(contact.getRecipientId().orElse(null), contact.getNumber());
if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) { if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show(); Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
@@ -774,7 +774,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
markContactUnselected(selectedContact); markContactUnselected(selectedContact);
if (onContactSelectedListener != null) { if (onContactSelectedListener != null) {
onContactSelectedListener.onContactDeselected(Optional.of(recipient.getId()), recipient.getE164().orNull()); onContactSelectedListener.onContactDeselected(Optional.of(recipient.getId()), recipient.getE164().orElse(null));
} }
}); });
@@ -17,12 +17,14 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import org.signal.core.util.ThreadUtil; import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.protocol.IdentityKeyPair;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -34,11 +36,6 @@ import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException; import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException;
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -38,8 +38,8 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.text.AfterTextChanged; import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -251,7 +251,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
for (SelectedContact contact : contacts) { for (SelectedContact contact : contacts) {
RecipientId recipientId = contact.getOrCreateRecipientId(context); RecipientId recipientId = contact.getOrCreateRecipientId(context);
Recipient recipient = Recipient.resolved(recipientId); Recipient recipient = Recipient.resolved(recipientId);
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null, null); MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null, null);
@@ -10,7 +10,6 @@ import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
@@ -19,14 +18,12 @@ import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLock
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.stories.Stories; import org.thoughtcrime.securesms.stories.Stories;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository; import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsState;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel; import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
import org.thoughtcrime.securesms.util.AppStartup; import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.CachedInflater; import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.WindowUtil; import org.thoughtcrime.securesms.util.WindowUtil;
public class MainActivity extends PassphraseRequiredActivity implements VoiceNoteMediaControllerOwner { public class MainActivity extends PassphraseRequiredActivity implements VoiceNoteMediaControllerOwner {
@@ -61,8 +58,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
ConversationListTabRepository repository = new ConversationListTabRepository(); ConversationListTabRepository repository = new ConversationListTabRepository();
ConversationListTabsViewModel.Factory factory = new ConversationListTabsViewModel.Factory(repository); ConversationListTabsViewModel.Factory factory = new ConversationListTabsViewModel.Factory(repository);
navigator.onCreate(savedInstanceState);
handleGroupLinkInIntent(getIntent()); handleGroupLinkInIntent(getIntent());
handleProxyInIntent(getIntent()); handleProxyInIntent(getIntent());
handleSignalMeIntent(getIntent()); handleSignalMeIntent(getIntent());
@@ -70,18 +65,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
CachedInflater.from(this).clear(); CachedInflater.from(this).clear();
conversationListTabsViewModel = new ViewModelProvider(this, factory).get(ConversationListTabsViewModel.class); conversationListTabsViewModel = new ViewModelProvider(this, factory).get(ConversationListTabsViewModel.class);
Transformations.map(conversationListTabsViewModel.getState(), ConversationListTabsState::getTab)
.observe(this, tab -> {
switch (tab) {
case CHATS:
getSupportFragmentManager().popBackStack();
break;
case STORIES:
navigator.goToStories();
break;
}
});
updateTabVisibility(); updateTabVisibility();
} }
@@ -135,11 +118,11 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
private void updateTabVisibility() { private void updateTabVisibility() {
if (Stories.isFeatureEnabled()) { if (Stories.isFeatureEnabled()) {
findViewById(R.id.conversation_list_tabs).setVisibility(View.VISIBLE); findViewById(R.id.conversation_list_tabs).setVisibility(View.VISIBLE);
WindowUtil.setNavigationBarColor(getWindow(), ContextCompat.getColor(this, R.color.signal_background_secondary)); WindowUtil.setNavigationBarColor(getWindow(), ContextCompat.getColor(this, R.color.signal_colorSecondaryContainer));
} else { } else {
findViewById(R.id.conversation_list_tabs).setVisibility(View.GONE); findViewById(R.id.conversation_list_tabs).setVisibility(View.GONE);
WindowUtil.setNavigationBarColor(getWindow(), ContextCompat.getColor(this, R.color.signal_background_primary)); WindowUtil.setNavigationBarColor(getWindow(), ContextCompat.getColor(this, R.color.signal_background_primary));
navigator.goToChats(); conversationListTabsViewModel.onChatsSelected();
} }
} }
@@ -2,26 +2,19 @@ package org.thoughtcrime.securesms;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity; import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.conversation.ConversationIntents; import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment;
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity; import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.insights.InsightsLauncher; import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.stories.landing.StoriesLandingFragment;
public class MainNavigator { public class MainNavigator {
public static final String STORIES_TAG = "STORIES";
public static final int REQUEST_CONFIG_CHANGES = 901; public static final int REQUEST_CONFIG_CHANGES = 901;
private final MainActivity activity; private final MainActivity activity;
@@ -38,16 +31,6 @@ public class MainNavigator {
return ((MainActivity) activity).getNavigator(); return ((MainActivity) activity).getNavigator();
} }
public void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
return;
}
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, ConversationListFragment.newInstance())
.commit();
}
/** /**
* @return True if the back pressed was handled in our own custom way, false if it should be given * @return True if the back pressed was handled in our own custom way, false if it should be given
* to the system to do the default behavior. * to the system to do the default behavior.
@@ -76,29 +59,6 @@ public class MainNavigator {
activity.startActivityForResult(AppSettingsActivity.home(activity), REQUEST_CONFIG_CHANGES); activity.startActivityForResult(AppSettingsActivity.home(activity), REQUEST_CONFIG_CHANGES);
} }
public void goToArchiveList() {
getFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
.replace(R.id.fragment_container, ConversationListArchiveFragment.newInstance())
.addToBackStack(null)
.commit();
}
public void goToStories() {
if (getFragmentManager().findFragmentByTag(STORIES_TAG) == null) {
getFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new StoriesLandingFragment(), STORIES_TAG)
.addToBackStack(null)
.commit();
}
}
public void goToChats() {
if (getFragmentManager().findFragmentByTag(STORIES_TAG) != null) {
getFragmentManager().popBackStack();
}
}
public void goToGroupCreation() { public void goToGroupCreation() {
activity.startActivity(CreateGroupActivity.newIntent(activity)); activity.startActivity(CreateGroupActivity.newIntent(activity));
} }
@@ -55,6 +55,10 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.animation.DepthPageTransformer; import org.thoughtcrime.securesms.animation.DepthPageTransformer;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener; import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment;
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs;
import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
@@ -67,7 +71,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sharing.ShareActivity;
import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.FullscreenHelper; import org.thoughtcrime.securesms.util.FullscreenHelper;
@@ -87,7 +90,8 @@ import java.util.Objects;
public final class MediaPreviewActivity extends PassphraseRequiredActivity public final class MediaPreviewActivity extends PassphraseRequiredActivity
implements LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>, implements LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>,
MediaRailAdapter.RailItemListener, MediaRailAdapter.RailItemListener,
MediaPreviewFragment.Events MediaPreviewFragment.Events,
VoiceNoteMediaControllerOwner
{ {
private final static String TAG = Log.tag(MediaPreviewActivity.class); private final static String TAG = Log.tag(MediaPreviewActivity.class);
@@ -127,6 +131,8 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
private MediaDatabase.Sorting sorting; private MediaDatabase.Sorting sorting;
private FullscreenHelper fullscreenHelper; private FullscreenHelper fullscreenHelper;
private VoiceNoteMediaController voiceNoteMediaController;
private @Nullable Cursor cursor = null; private @Nullable Cursor cursor = null;
public static @NonNull Intent intentFromMediaRecord(@NonNull Context context, public static @NonNull Intent intentFromMediaRecord(@NonNull Context context,
@@ -159,6 +165,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
setSupportActionBar(findViewById(R.id.toolbar)); setSupportActionBar(findViewById(R.id.toolbar));
voiceNoteMediaController = new VoiceNoteMediaController(this);
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class); viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
fullscreenHelper = new FullscreenHelper(this); fullscreenHelper = new FullscreenHelper(this);
@@ -203,23 +210,25 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
else from = ""; else from = "";
if (showThread) { if (showThread) {
String to = null; String titleText = null;
Recipient threadRecipient = mediaItem.threadRecipient; Recipient threadRecipient = mediaItem.threadRecipient;
if (threadRecipient != null) { if (threadRecipient != null) {
if (mediaItem.outgoing || threadRecipient.isGroup()) { if (mediaItem.outgoing) {
if (threadRecipient.isSelf()) { if (threadRecipient.isSelf()) {
from = getString(R.string.note_to_self); titleText = getString(R.string.note_to_self);
} else { } else {
to = threadRecipient.getDisplayName(this); titleText = getString(R.string.MediaPreviewActivity_you_to_s, threadRecipient.getDisplayName(this));
} }
} else { } else {
to = getString(R.string.MediaPreviewActivity_you); if (threadRecipient.isGroup()) {
titleText = getString(R.string.MediaPreviewActivity_s_to_s, from, threadRecipient.getDisplayName(this));
} else {
titleText = getString(R.string.MediaPreviewActivity_s_to_you, from);
}
} }
} }
return titleText != null ? titleText : from;
return to != null ? getString(R.string.MediaPreviewActivity_s_to_s, from, to)
: from;
} else { } else {
return from; return from;
} }
@@ -286,7 +295,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
anchorMarginsToBottomInsets(detailsContainer); anchorMarginsToBottomInsets(detailsContainer);
fullscreenHelper.configureToolbarSpacer(findViewById(R.id.toolbar_cutout_spacer)); fullscreenHelper.configureToolbarLayout(findViewById(R.id.toolbar_cutout_spacer), findViewById(R.id.toolbar));
fullscreenHelper.showAndHideWithSystemUI(getWindow(), detailsContainer, toolbarLayout); fullscreenHelper.showAndHideWithSystemUI(getWindow(), detailsContainer, toolbarLayout);
} }
@@ -386,10 +395,12 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
MediaItem mediaItem = getCurrentMediaItem(); MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem != null) { if (mediaItem != null) {
Intent composeIntent = new Intent(this, ShareActivity.class); MultiselectForwardFragmentArgs.create(
composeIntent.putExtra(Intent.EXTRA_STREAM, mediaItem.uri); this,
composeIntent.setType(mediaItem.type); mediaItem.uri,
startActivity(composeIntent); mediaItem.type,
args -> MultiselectForwardFragment.showBottomSheet(getSupportFragmentManager(), args)
);
} }
} }
@@ -597,6 +608,15 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
finish(); finish();
} }
@Override
public void onMediaReady() {
}
@Override
public @NonNull VoiceNoteMediaController getVoiceNoteMediaController() {
return voiceNoteMediaController;
}
private class ViewPagerListener extends ExtendedOnPageChangedListener { private class ViewPagerListener extends ExtendedOnPageChangedListener {
@Override @Override
@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.conversation.ConversationIntents; import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity; import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
@@ -33,11 +33,11 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.signal.core.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@@ -79,7 +79,7 @@ public class NewConversationActivity extends ContactSelectionActivity
if (!resolved.isRegistered() || !resolved.hasServiceId()) { if (!resolved.isRegistered() || !resolved.hasServiceId()) {
Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh."); Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.");
try { try {
DirectoryHelper.refreshDirectoryFor(this, resolved, false); ContactDiscovery.refresh(this, resolved, false);
resolved = Recipient.resolved(resolved.getId()); resolved = Recipient.resolved(resolved.getId());
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact."); Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
@@ -61,7 +61,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
passphrase); passphrase);
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret); MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
SignalStore.account().generateAciIdentityKey(); SignalStore.account().generateAciIdentityKeyIfNecessary();
SignalStore.account().generatePniIdentityKeyIfNecessary(); SignalStore.account().generatePniIdentityKeyIfNecessary();
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this); VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
@@ -9,7 +9,9 @@ import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.CharacterCalculator; import org.thoughtcrime.securesms.util.CharacterCalculator;
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState; import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Optional;
public class TransportOption implements Parcelable { public class TransportOption implements Parcelable {
@@ -18,8 +20,8 @@ public class TransportOption implements Parcelable {
TEXTSECURE TEXTSECURE
} }
private final int drawable; private final int drawable;
private final int backgroundColor; private final int backgroundColor;
private final @NonNull String text; private final @NonNull String text;
private final @NonNull Type type; private final @NonNull Type type;
private final @NonNull String composeHint; private final @NonNull String composeHint;
@@ -35,7 +37,7 @@ public class TransportOption implements Parcelable {
@NonNull CharacterCalculator characterCalculator) @NonNull CharacterCalculator characterCalculator)
{ {
this(type, drawable, backgroundColor, text, composeHint, characterCalculator, this(type, drawable, backgroundColor, text, composeHint, characterCalculator,
Optional.<CharSequence>absent(), Optional.<Integer>absent()); Optional.empty(), Optional.empty());
} }
public TransportOption(@NonNull Type type, public TransportOption(@NonNull Type type,
@@ -64,8 +66,8 @@ public class TransportOption implements Parcelable {
in.readString(), in.readString(),
in.readString(), in.readString(),
CharacterCalculator.readFromParcel(in), CharacterCalculator.readFromParcel(in),
Optional.fromNullable(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)), Optional.ofNullable(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)),
in.readInt() == 1 ? Optional.of(in.readInt()) : Optional.absent()); in.readInt() == 1 ? Optional.of(in.readInt()) : Optional.empty());
} }
public @NonNull Type getType() { public @NonNull Type getType() {
@@ -123,7 +125,7 @@ public class TransportOption implements Parcelable {
dest.writeString(text); dest.writeString(text);
dest.writeString(composeHint); dest.writeString(composeHint);
CharacterCalculator.writeToParcel(dest, characterCalculator); CharacterCalculator.writeToParcel(dest, characterCalculator);
TextUtils.writeToParcel(simName.orNull(), dest, flags); TextUtils.writeToParcel(simName.orElse(null), dest, flags);
if (simSubscriptionId.isPresent()) { if (simSubscriptionId.isPresent()) {
dest.writeInt(1); dest.writeInt(1);
@@ -14,13 +14,14 @@ import org.thoughtcrime.securesms.util.PushCharacterCalculator;
import org.thoughtcrime.securesms.util.SmsCharacterCalculator; import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat; import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat; import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.util.OptionalUtil;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional;
import static org.thoughtcrime.securesms.TransportOption.Type; import static org.thoughtcrime.securesms.TransportOption.Type;
@@ -33,8 +34,8 @@ public class TransportOptions {
private final List<TransportOption> enabledTransports; private final List<TransportOption> enabledTransports;
private Type defaultTransportType = Type.SMS; private Type defaultTransportType = Type.SMS;
private Optional<Integer> defaultSubscriptionId = Optional.absent(); private Optional<Integer> defaultSubscriptionId = Optional.empty();
private Optional<TransportOption> selectedOption = Optional.absent(); private Optional<TransportOption> selectedOption = Optional.empty();
private final Optional<Integer> systemSubscriptionId; private final Optional<Integer> systemSubscriptionId;
@@ -54,7 +55,7 @@ public class TransportOptions {
setSelectedTransport(null); setSelectedTransport(null);
} else { } else {
this.defaultTransportType = Type.SMS; this.defaultTransportType = Type.SMS;
this.defaultSubscriptionId = Optional.absent(); this.defaultSubscriptionId = Optional.empty();
notifyTransportChangeListeners(); notifyTransportChangeListeners();
} }
@@ -81,7 +82,7 @@ public class TransportOptions {
} }
public void setSelectedTransport(@Nullable TransportOption transportOption) { public void setSelectedTransport(@Nullable TransportOption transportOption) {
this.selectedOption = Optional.fromNullable(transportOption); this.selectedOption = Optional.ofNullable(transportOption);
notifyTransportChangeListeners(); notifyTransportChangeListeners();
} }
@@ -93,7 +94,7 @@ public class TransportOptions {
if (selectedOption.isPresent()) return selectedOption.get(); if (selectedOption.isPresent()) return selectedOption.get();
if (defaultTransportType == Type.SMS) { if (defaultTransportType == Type.SMS) {
TransportOption transportOption = findEnabledSmsTransportOption(defaultSubscriptionId.or(systemSubscriptionId)); TransportOption transportOption = findEnabledSmsTransportOption(OptionalUtil.or(defaultSubscriptionId, systemSubscriptionId));
if (transportOption != null) { if (transportOption != null) {
return transportOption; return transportOption;
} }
@@ -124,7 +125,7 @@ public class TransportOptions {
for (TransportOption transportOption : enabledTransports) { for (TransportOption transportOption : enabledTransports) {
if (transportOption.getType() == Type.SMS && if (transportOption.getType() == Type.SMS &&
subId == transportOption.getSimSubscriptionId().or(-1)) { subId == transportOption.getSimSubscriptionId().orElse(-1)) {
return transportOption; return transportOption;
} }
} }
@@ -133,7 +134,7 @@ public class TransportOptions {
} }
public void disableTransport(Type type) { public void disableTransport(Type type) {
TransportOption selected = selectedOption.orNull(); TransportOption selected = selectedOption.orElse(null);
Iterator<TransportOption> iterator = enabledTransports.iterator(); Iterator<TransportOption> iterator = enabledTransports.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
@@ -49,6 +49,7 @@ import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey;
import org.thoughtcrime.securesms.components.TooltipPopup; import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow; import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
@@ -75,16 +76,19 @@ import org.thoughtcrime.securesms.util.FullscreenHelper;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThrottledDebouncer; import org.thoughtcrime.securesms.util.ThrottledDebouncer;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VibrateUtil;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState; import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState;
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager; import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage; import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE; import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback { public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback {
@@ -92,6 +96,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private static final String TAG = Log.tag(WebRtcCallActivity.class); private static final String TAG = Log.tag(WebRtcCallActivity.class);
private static final int STANDARD_DELAY_FINISH = 1000; private static final int STANDARD_DELAY_FINISH = 1000;
private static final int VIBRATE_DURATION = 50;
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION"; public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION"; public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
@@ -111,6 +116,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private WindowLayoutInfoConsumer windowLayoutInfoConsumer; private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
private ThrottledDebouncer requestNewSizesThrottle; private ThrottledDebouncer requestNewSizesThrottle;
private Disposable ephemeralStateDisposable = Disposable.empty();
@Override @Override
protected void attachBaseContext(@NonNull Context newBase) { protected void attachBaseContext(@NonNull Context newBase) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES); getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
@@ -153,6 +160,18 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
requestNewSizesThrottle = new ThrottledDebouncer(TimeUnit.SECONDS.toMillis(1)); requestNewSizesThrottle = new ThrottledDebouncer(TimeUnit.SECONDS.toMillis(1));
} }
@Override
protected void onStart() {
super.onStart();
ephemeralStateDisposable = ApplicationDependencies.getSignalCallManager()
.ephemeralStates()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(state -> {
viewModel.updateFromEphemeralState(state);
});
}
@Override @Override
public void onResume() { public void onResume() {
Log.i(TAG, "onResume()"); Log.i(TAG, "onResume()");
@@ -193,6 +212,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
Log.i(TAG, "onStop"); Log.i(TAG, "onStop");
super.onStop(); super.onStop();
ephemeralStateDisposable.dispose();
if (!isInPipMode() || isFinishing()) { if (!isInPipMode() || isFinishing()) {
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
requestNewSizesThrottle.clear(); requestNewSizesThrottle.clear();
@@ -295,7 +316,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
LiveDataUtil.combineLatest(viewModel.getCallParticipantsState(), LiveDataUtil.combineLatest(viewModel.getCallParticipantsState(),
viewModel.getOrientationAndLandscapeEnabled(), viewModel.getOrientationAndLandscapeEnabled(),
(s, o) -> new CallParticipantsViewState(s, o.first == PORTRAIT_BOTTOM_EDGE, o.second)) viewModel.getEphemeralState(),
(s, o, e) -> new CallParticipantsViewState(s, e, o.first == PORTRAIT_BOTTOM_EDGE, o.second))
.observe(this, p -> callScreen.updateCallParticipants(p)); .observe(this, p -> callScreen.updateCallParticipants(p));
viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate); viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate);
viewModel.getSafetyNumberChangeEvent().observe(this, this::handleSafetyNumberChangeEvent); viewModel.getSafetyNumberChangeEvent().observe(this, this::handleSafetyNumberChangeEvent);
@@ -501,6 +523,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
} }
} }
private void handleCallReconnecting() {
callScreen.setStatus(getString(R.string.WebRtcCallActivity__reconnecting));
VibrateUtil.vibrate(this, VIBRATE_DURATION);
}
private void handleRecipientUnavailable() { private void handleRecipientUnavailable() {
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class); EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
callScreen.setStatus(getString(R.string.RedPhone_recipient_unavailable)); callScreen.setStatus(getString(R.string.RedPhone_recipient_unavailable));
@@ -623,6 +650,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
handleCallPreJoin(event); break; handleCallPreJoin(event); break;
case CALL_CONNECTED: case CALL_CONNECTED:
handleCallConnected(event); break; handleCallConnected(event); break;
case CALL_RECONNECTING:
handleCallReconnecting(); break;
case NETWORK_FAILURE: case NETWORK_FAILURE:
handleServerFailure(); break; handleServerFailure(); break;
case CALL_RINGING: case CALL_RINGING:
@@ -807,7 +836,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
if (feature.isPresent()) { if (feature.isPresent()) {
FoldingFeature foldingFeature = (FoldingFeature) feature.get(); FoldingFeature foldingFeature = (FoldingFeature) feature.get();
Rect bounds = foldingFeature.getBounds(); Rect bounds = foldingFeature.getBounds();
if (foldingFeature.getState() == FoldingFeature.State.HALF_OPENED && bounds.top == bounds.bottom) { if (foldingFeature.isSeparating()) {
Log.d(TAG, "OnWindowLayoutInfo accepted: ensure call view is in table-top display mode"); Log.d(TAG, "OnWindowLayoutInfo accepted: ensure call view is in table-top display mode");
viewModel.setFoldableState(WebRtcControls.FoldableState.folded(bounds.top)); viewModel.setFoldableState(WebRtcControls.FoldableState.folded(bounds.top));
} else { } else {
@@ -0,0 +1,63 @@
package org.thoughtcrime.securesms.animation.transitions
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.transition.Transition
import android.transition.TransitionValues
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
@RequiresApi(21)
class CrossfaderTransition(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
companion object {
private const val WIDTH = "CrossfaderTransition.WIDTH"
}
override fun captureStartValues(transitionValues: TransitionValues) {
if (transitionValues.view is Crossfadeable) {
transitionValues.values[WIDTH] = transitionValues.view.width
}
}
override fun captureEndValues(transitionValues: TransitionValues) {
if (transitionValues.view is Crossfadeable) {
transitionValues.values[WIDTH] = transitionValues.view.width
}
}
override fun createAnimator(sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
if (startValues == null || endValues == null) {
return null
}
val startWidth = (startValues.values[WIDTH] ?: 0) as Int
val endWidth = (endValues.values[WIDTH] ?: 0) as Int
val view: Crossfadeable = endValues.view as? Crossfadeable ?: return null
val reverse = startWidth > endWidth
return ValueAnimator.ofFloat(0f, 1f).apply {
addUpdateListener {
view.onCrossfadeAnimationUpdated(it.animatedValue as Float, reverse)
}
doOnStart {
view.onCrossfadeStarted(reverse)
}
doOnEnd {
view.onCrossfadeFinished(reverse)
}
}
}
interface Crossfadeable {
fun onCrossfadeAnimationUpdated(progress: Float, reverse: Boolean)
fun onCrossfadeStarted(reverse: Boolean)
fun onCrossfadeFinished(reverse: Boolean)
}
}
@@ -0,0 +1,53 @@
package org.thoughtcrime.securesms.animation.transitions
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.transition.Transition
import android.transition.TransitionValues
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import com.google.android.material.floatingactionbutton.FloatingActionButton
@RequiresApi(21)
class FabElevationFadeTransform(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
companion object {
private const val ELEVATION = "CrossfaderTransition.ELEVATION"
}
override fun captureStartValues(transitionValues: TransitionValues) {
if (transitionValues.view is FloatingActionButton) {
transitionValues.values[ELEVATION] = transitionValues.view.elevation
}
}
override fun captureEndValues(transitionValues: TransitionValues) {
if (transitionValues.view is FloatingActionButton) {
transitionValues.values[ELEVATION] = transitionValues.view.elevation
}
}
override fun createAnimator(sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
if (startValues?.view !is FloatingActionButton || endValues?.view !is FloatingActionButton) {
return null
}
val startElevation = startValues.view.elevation
val endElevation = endValues.view.elevation
if (startElevation == endElevation) {
return null
}
return ValueAnimator.ofFloat(
startValues.values[ELEVATION] as Float,
endValues.values[ELEVATION] as Float
).apply {
addUpdateListener {
val elevation = it.animatedValue as Float
endValues.view.elevation = elevation
}
}
}
}
@@ -9,12 +9,12 @@ import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional;
public class PointerAttachment extends Attachment { public class PointerAttachment extends Attachment {
@@ -93,7 +93,7 @@ public class PointerAttachment extends Attachment {
} }
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer, @Nullable StickerLocator stickerLocator, @Nullable String fastPreflightId) { public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer, @Nullable StickerLocator stickerLocator, @Nullable String fastPreflightId) {
if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.absent(); if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.empty();
String encodedKey = null; String encodedKey = null;
@@ -103,12 +103,12 @@ public class PointerAttachment extends Attachment {
return Optional.of(new PointerAttachment(pointer.get().getContentType(), return Optional.of(new PointerAttachment(pointer.get().getContentType(),
AttachmentDatabase.TRANSFER_PROGRESS_PENDING, AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
pointer.get().asPointer().getSize().or(0), pointer.get().asPointer().getSize().orElse(0),
pointer.get().asPointer().getFileName().orNull(), pointer.get().asPointer().getFileName().orElse(null),
pointer.get().asPointer().getCdnNumber(), pointer.get().asPointer().getCdnNumber(),
pointer.get().asPointer().getRemoteId().toString(), pointer.get().asPointer().getRemoteId().toString(),
encodedKey, null, encodedKey, null,
pointer.get().asPointer().getDigest().orNull(), pointer.get().asPointer().getDigest().orElse(null),
fastPreflightId, fastPreflightId,
pointer.get().asPointer().getVoiceNote(), pointer.get().asPointer().getVoiceNote(),
pointer.get().asPointer().isBorderless(), pointer.get().asPointer().isBorderless(),
@@ -116,9 +116,9 @@ public class PointerAttachment extends Attachment {
pointer.get().asPointer().getWidth(), pointer.get().asPointer().getWidth(),
pointer.get().asPointer().getHeight(), pointer.get().asPointer().getHeight(),
pointer.get().asPointer().getUploadTimestamp(), pointer.get().asPointer().getUploadTimestamp(),
pointer.get().asPointer().getCaption().orNull(), pointer.get().asPointer().getCaption().orElse(null),
stickerLocator, stickerLocator,
BlurHash.parseOrNull(pointer.get().asPointer().getBlurHash().orNull()))); BlurHash.parseOrNull(pointer.get().asPointer().getBlurHash().orElse(null))));
} }
@@ -127,13 +127,13 @@ public class PointerAttachment extends Attachment {
return Optional.of(new PointerAttachment(pointer.getContentType(), return Optional.of(new PointerAttachment(pointer.getContentType(),
AttachmentDatabase.TRANSFER_PROGRESS_PENDING, AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
thumbnail != null ? thumbnail.asPointer().getSize().or(0) : 0, thumbnail != null ? thumbnail.asPointer().getSize().orElse(0) : 0,
pointer.getFileName(), pointer.getFileName(),
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0, thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0", thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null, thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
null, null,
thumbnail != null ? thumbnail.asPointer().getDigest().orNull() : null, thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
null, null,
false, false,
false, false,
@@ -141,7 +141,7 @@ public class PointerAttachment extends Attachment {
thumbnail != null ? thumbnail.asPointer().getWidth() : 0, thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
thumbnail != null ? thumbnail.asPointer().getHeight() : 0, thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0, thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
thumbnail != null ? thumbnail.asPointer().getCaption().orNull() : null, thumbnail != null ? thumbnail.asPointer().getCaption().orElse(null) : null,
null, null,
null)); null));
} }
@@ -12,7 +12,6 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft; import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft;
import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
@@ -14,10 +14,10 @@ import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.profiles.AvatarHelper import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.MediaUtil
import org.whispersystems.libsignal.util.guava.Optional
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.util.Optional
import javax.annotation.meta.Exhaustive import javax.annotation.meta.Exhaustive
/** /**
@@ -128,6 +128,6 @@ object AvatarRenderer {
} }
private fun createMedia(uri: Uri, size: Long): Media { private fun createMedia(uri: Uri, size: Long): Media {
return Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), DIMENSIONS, DIMENSIONS, size, 0, false, false, Optional.absent(), Optional.absent(), Optional.absent()) return Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), DIMENSIONS, DIMENSIONS, size, 0, false, false, Optional.empty(), Optional.empty(), Optional.empty())
} }
} }
@@ -31,8 +31,8 @@ class TextAvatarDrawable(
} }
override fun draw(canvas: Canvas) { override fun draw(canvas: Canvas) {
val textSize = Avatars.getTextSizeForLength(context, avatar.text, size * 0.8f, size * 0.45f)
val width = bounds.width() val width = bounds.width()
val textSize = Avatars.getTextSizeForLength(context, avatar.text, width * 0.8f, width * 0.45f)
val candidates = EmojiProvider.getCandidates(avatar.text) val candidates = EmojiProvider.getCandidates(avatar.text)
textPaint.textSize = textSize textPaint.textSize = textSize
@@ -26,7 +26,10 @@ class AvatarView @JvmOverloads constructor(
isClickable = false isClickable = false
} }
private val avatar: AvatarImageView = findViewById(R.id.avatar_image_view) private val avatar: AvatarImageView = findViewById<AvatarImageView>(R.id.avatar_image_view).apply {
initialize(context, attrs)
}
private val storyRing: View = findViewById(R.id.avatar_story_ring) private val storyRing: View = findViewById(R.id.avatar_story_ring)
private fun showStoryRing(hasUnreadStory: Boolean) { private fun showStoryRing(hasUnreadStory: Boolean) {
@@ -37,8 +40,8 @@ class AvatarView @JvmOverloads constructor(
storyRing.visible = true storyRing.visible = true
storyRing.isActivated = hasUnreadStory storyRing.isActivated = hasUnreadStory
avatar.scaleX = 0.82f avatar.scaleX = 0.8f
avatar.scaleY = 0.82f avatar.scaleY = 0.8f
} }
private fun hideStoryRing() { private fun hideStoryRing() {
@@ -48,6 +51,10 @@ class AvatarView @JvmOverloads constructor(
avatar.scaleY = 1f avatar.scaleY = 1f
} }
fun hasStory(): Boolean {
return storyRing.visible
}
fun setStoryRingFromState(storyViewState: StoryViewState) { fun setStoryRingFromState(storyViewState: StoryViewState) {
when (storyViewState) { when (storyViewState) {
StoryViewState.NONE -> hideStoryRing() StoryViewState.NONE -> hideStoryRing()
@@ -23,6 +23,8 @@ import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -110,7 +112,7 @@ public class BackupDialog {
@RequiresApi(29) @RequiresApi(29)
public static void showChooseBackupLocationDialog(@NonNull Fragment fragment, int requestCode) { public static void showChooseBackupLocationDialog(@NonNull Fragment fragment, int requestCode) {
new AlertDialog.Builder(fragment.requireContext()) new MaterialAlertDialogBuilder(fragment.requireContext())
.setView(R.layout.backup_choose_location_dialog) .setView(R.layout.backup_choose_location_dialog)
.setCancelable(true) .setCancelable(true)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> { .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
@@ -141,7 +143,7 @@ public class BackupDialog {
} }
public static void showDisableBackupDialog(@NonNull Context context, @NonNull Runnable onBackupsDisabled) { public static void showDisableBackupDialog(@NonNull Context context, @NonNull Runnable onBackupsDisabled) {
new AlertDialog.Builder(context) new MaterialAlertDialogBuilder(context)
.setTitle(R.string.BackupDialog_delete_backups) .setTitle(R.string.BackupDialog_delete_backups)
.setMessage(R.string.BackupDialog_disable_and_delete_all_local_backups) .setMessage(R.string.BackupDialog_disable_and_delete_all_local_backups)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
@@ -5,8 +5,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.util.ByteUtil;
import org.whispersystems.libsignal.util.ByteUtil;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@@ -18,10 +18,11 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.Conversions; import org.signal.core.util.Conversions;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.kdf.HKDFv3;
import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.attachments.AttachmentId; import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.EmojiSearchDatabase; import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
@@ -47,13 +48,11 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.CursorUtil; import org.signal.core.util.CursorUtil;
import org.thoughtcrime.securesms.util.SetUtil; import org.signal.core.util.SetUtil;
import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.kdf.HKDFv3;
import org.whispersystems.libsignal.util.ByteUtil;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@@ -17,6 +17,8 @@ import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.Conversions; import org.signal.core.util.Conversions;
import org.signal.core.util.StreamUtil; import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.kdf.HKDFv3;
import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.backup.BackupProtos.Attachment; import org.thoughtcrime.securesms.backup.BackupProtos.Attachment;
import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame; import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame;
import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion; import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion;
@@ -36,9 +38,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BackupUtil; import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.SqlUtil; import org.signal.core.util.SqlUtil;
import org.whispersystems.libsignal.kdf.HKDFv3;
import org.whispersystems.libsignal.util.ByteUtil;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@@ -69,6 +69,18 @@ public class FullBackupImporter extends FullBackupBase {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = Log.tag(FullBackupImporter.class); private static final String TAG = Log.tag(FullBackupImporter.class);
private static final String[] TABLES_TO_DROP_FIRST = {
"distribution_list_member",
"distribution_list",
"message_send_log_recipients",
"msl_recipient",
"msl_message",
"reaction",
"notification_profile_schedule",
"notification_profile_allowed_members",
"story_sends"
};
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase) @NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
throws IOException throws IOException
@@ -85,12 +97,12 @@ public class FullBackupImporter extends FullBackupBase {
int count = 0; int count = 0;
SQLiteDatabase keyValueDatabase = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).getSqlCipherDatabase(); SQLiteDatabase keyValueDatabase = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).getSqlCipherDatabase();
db.beginTransaction();
keyValueDatabase.beginTransaction();
try { try {
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase); BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
db.beginTransaction();
keyValueDatabase.beginTransaction();
dropAllTables(db); dropAllTables(db);
BackupFrame frame; BackupFrame frame;
@@ -272,12 +284,17 @@ public class FullBackupImporter extends FullBackupBase {
} }
private static void dropAllTables(@NonNull SQLiteDatabase db) { private static void dropAllTables(@NonNull SQLiteDatabase db) {
for (String name : TABLES_TO_DROP_FIRST) {
db.execSQL("DROP TABLE IF EXISTS " + name);
}
try (Cursor cursor = db.rawQuery("SELECT name, type FROM sqlite_master", null)) { try (Cursor cursor = db.rawQuery("SELECT name, type FROM sqlite_master", null)) {
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
String name = cursor.getString(0); String name = cursor.getString(0);
String type = cursor.getString(1); String type = cursor.getString(1);
if ("table".equals(type) && !name.startsWith("sqlite_")) { if ("table".equals(type) && !name.startsWith("sqlite_")) {
Log.i(TAG, "Dropping table: " + name);
db.execSQL("DROP TABLE IF EXISTS " + name); db.execSQL("DROP TABLE IF EXISTS " + name);
} }
} }
@@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.ThemeUtil import org.thoughtcrime.securesms.util.ThemeUtil
import java.lang.IllegalArgumentException
class BadgeImageView @JvmOverloads constructor( class BadgeImageView @JvmOverloads constructor(
context: Context, context: Context,
@@ -9,13 +9,13 @@ import com.google.android.flexbox.FlexboxLayoutManager
import com.google.android.flexbox.JustifyContent import com.google.android.flexbox.JustifyContent
import org.signal.core.util.DimensionUnit import org.signal.core.util.DimensionUnit
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.util.Pair
import org.thoughtcrime.securesms.BuildConfig import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.badges.models.Badge import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.badges.models.Badge.Category.Companion.fromCode import org.thoughtcrime.securesms.badges.models.Badge.Category.Companion.fromCode
import org.thoughtcrime.securesms.components.settings.DSLConfiguration import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
import org.thoughtcrime.securesms.util.ScreenDensity import org.thoughtcrime.securesms.util.ScreenDensity
import org.whispersystems.libsignal.util.Pair
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import java.math.BigDecimal import java.math.BigDecimal
import java.sql.Timestamp import java.sql.Timestamp
@@ -45,6 +45,7 @@ class CantProcessSubscriptionPaymentBottomSheetDialogFragment : DSLSettingsBotto
text = DSLSettingsText.from(R.string.CantProcessSubscriptionPaymentBottomSheetDialogFragment__dont_show_this_again) text = DSLSettingsText.from(R.string.CantProcessSubscriptionPaymentBottomSheetDialogFragment__dont_show_this_again)
) { ) {
SignalStore.donationsValues().showCantProcessDialog = false SignalStore.donationsValues().showCantProcessDialog = false
dismissAllowingStateLoss()
} }
} }
} }
@@ -41,9 +41,9 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
sectionHeaderPref( sectionHeaderPref(
DSLSettingsText.from( DSLSettingsText.from(
if (badge.isBoost()) { if (badge.isBoost()) {
R.string.ExpiredBadgeBottomSheetDialogFragment__your_badge_has_expired R.string.ExpiredBadgeBottomSheetDialogFragment__boost_badge_expired
} else { } else {
R.string.ExpiredBadgeBottomSheetDialogFragment__subscription_cancelled R.string.ExpiredBadgeBottomSheetDialogFragment__monthly_donation_cancelled
}, },
DSLSettingsText.CenterModifier DSLSettingsText.CenterModifier
) )
@@ -54,11 +54,11 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
noPadTextPref( noPadTextPref(
DSLSettingsText.from( DSLSettingsText.from(
if (badge.isBoost()) { if (badge.isBoost()) {
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_boost_badge_has_expired) getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_boost_badge_has_expired_and)
} else if (inactive) { } else if (inactive) {
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_sustainer_subscription_was_automatically, badge.name) getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_automatically, badge.name)
} else { } else {
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_sustainer_subscription_was_canceled) getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_canceled)
}, },
DSLSettingsText.CenterModifier DSLSettingsText.CenterModifier
) )
@@ -72,7 +72,7 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
if (isLikelyASustainer) { if (isLikelyASustainer) {
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_reactivate R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_reactivate
} else { } else {
R.string.ExpiredBadgeBottomSheetDialogFragment__to_continue_supporting_technology R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_keep
} }
} else { } else {
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can R.string.ExpiredBadgeBottomSheetDialogFragment__you_can
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.InternetConnectionObserver import org.thoughtcrime.securesms.util.InternetConnectionObserver
import org.thoughtcrime.securesms.util.livedata.Store import org.thoughtcrime.securesms.util.livedata.Store
import org.whispersystems.libsignal.util.guava.Optional import java.util.Optional
private val TAG = Log.tag(BadgesOverviewViewModel::class.java) private val TAG = Log.tag(BadgesOverviewViewModel::class.java)
@@ -54,13 +54,13 @@ class BadgesOverviewViewModel(
subscriptionsRepository.getSubscriptions() subscriptionsRepository.getSubscriptions()
) { active, all -> ) { active, all ->
if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd() == true) { if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd() == true) {
Optional.fromNullable<String>(all.firstOrNull { it.level == active.activeSubscription?.level }?.badge?.id) Optional.ofNullable<String>(all.firstOrNull { it.level == active.activeSubscription?.level }?.badge?.id)
} else { } else {
Optional.absent() Optional.empty()
} }
}.subscribeBy( }.subscribeBy(
onSuccess = { badgeId -> onSuccess = { badgeId ->
store.update { it.copy(fadedBadgeId = badgeId.orNull()) } store.update { it.copy(fadedBadgeId = badgeId.orElse(null)) }
}, },
onError = { throwable -> onError = { throwable ->
Log.w(TAG, "Could not retrieve data from server", throwable) Log.w(TAG, "Could not retrieve data from server", throwable)
@@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.thoughtcrime.securesms.badges.BadgeRepository import org.thoughtcrime.securesms.badges.BadgeRepository
import org.thoughtcrime.securesms.badges.models.Badge import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
@@ -24,8 +24,8 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
public class BlockedUsersActivity extends PassphraseRequiredActivity implements BlockedUsersFragment.Listener, ContactSelectionListFragment.OnContactSelectedListener { public class BlockedUsersActivity extends PassphraseRequiredActivity implements BlockedUsersFragment.Listener, ContactSelectionListFragment.OnContactSelectedListener {
@@ -88,7 +88,7 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
@Override @Override
public void onBeforeContactSelected(@NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) { public void onBeforeContactSelected(@NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
final String displayName = recipientId.transform(id -> Recipient.resolved(id).getDisplayName(this)).or(number); final String displayName = recipientId.map(id -> Recipient.resolved(id).getDisplayName(this)).orElse(number);
AlertDialog confirmationDialog = new MaterialAlertDialogBuilder(this) AlertDialog confirmationDialog = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.BlockedUsersActivity__block_user) .setTitle(R.string.BlockedUsersActivity__block_user)
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.util.OptionalUtil;
import java.util.Objects; import java.util.Objects;
@@ -64,7 +65,9 @@ final class BlockedUsersAdapter extends ListAdapter<Recipient, BlockedUsersAdapt
displayName.setText(recipient.getDisplayName(itemView.getContext())); displayName.setText(recipient.getDisplayName(itemView.getContext()));
if (recipient.hasAUserSetDisplayName(itemView.getContext())) { if (recipient.hasAUserSetDisplayName(itemView.getContext())) {
String identifier = recipient.getE164().transform(PhoneNumberFormatter::prettyPrint).or(recipient.getUsername()).orNull(); String identifier = OptionalUtil.or(recipient.getE164().map(PhoneNumberFormatter::prettyPrint),
recipient.getUsername())
.orElse(null);
if (identifier != null) { if (identifier != null) {
numberOrUsername.setText(identifier); numberOrUsername.setText(identifier);
@@ -95,7 +95,7 @@ public final class AvatarImageView extends AppCompatImageView {
initialize(context, attrs); initialize(context, attrs);
} }
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) { public void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
setScaleType(ScaleType.CENTER_CROP); setScaleType(ScaleType.CENTER_CROP);
if (attrs != null) { if (attrs != null) {
@@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.components.mention.MentionValidatorWatcher;
import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.StringUtil; import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.List; import java.util.List;
@@ -41,10 +41,10 @@ import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat; import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat; import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class ConversationItemFooter extends ConstraintLayout { public class ConversationItemFooter extends ConstraintLayout {
@@ -11,15 +11,11 @@ import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.badges.BadgeImageView; import org.thoughtcrime.securesms.badges.BadgeImageView;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.libsignal.util.Pair;
import java.util.LinkedList;
import java.util.List; import java.util.List;
public class ConversationTypingView extends ConstraintLayout { public class ConversationTypingView extends ConstraintLayout {
@@ -7,11 +7,9 @@ import android.graphics.Path;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode; import android.graphics.PorterDuffXfermode;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.drawable.shapes.RoundRectShape;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class CornerMask { public class CornerMask {
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.util.OptionalUtil;
public class DocumentView extends FrameLayout { public class DocumentView extends FrameLayout {
@@ -105,11 +106,11 @@ public class DocumentView extends FrameLayout {
this.documentSlide = documentSlide; this.documentSlide = documentSlide;
this.fileName.setText(documentSlide.getFileName() this.fileName.setText(OptionalUtil.or(documentSlide.getFileName(),
.or(documentSlide.getCaption()) documentSlide.getCaption())
.or(getContext().getString(R.string.DocumentView_unnamed_file))); .orElse(getContext().getString(R.string.DocumentView_unnamed_file)));
this.fileSize.setText(Util.getPrettyFileSize(documentSlide.getFileSize())); this.fileSize.setText(Util.getPrettyFileSize(documentSlide.getFileSize()));
this.document.setText(documentSlide.getFileType(getContext()).or("").toLowerCase()); this.document.setText(documentSlide.getFileType(getContext()).orElse("").toLowerCase());
this.setOnClickListener(new OpenClickedListener(documentSlide)); this.setOnClickListener(new OpenClickedListener(documentSlide));
} }
@@ -1,9 +1,11 @@
package org.thoughtcrime.securesms.components package org.thoughtcrime.securesms.components
import android.app.Dialog import android.app.Dialog
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.View import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
@@ -26,6 +28,9 @@ abstract class FixedRoundedCornerBottomSheetDialogFragment : BottomSheetDialogFr
@StyleRes @StyleRes
protected open val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners protected open val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners
@ColorInt
protected var backgroundColor: Int = Color.TRANSPARENT
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, themeResId) setStyle(STYLE_NORMAL, themeResId)
@@ -44,7 +49,8 @@ abstract class FixedRoundedCornerBottomSheetDialogFragment : BottomSheetDialogFr
val dialogBackground = MaterialShapeDrawable(shapeAppearanceModel) val dialogBackground = MaterialShapeDrawable(shapeAppearanceModel)
val bottomSheetStyle = ThemeUtil.getThemedResourceId(ContextThemeWrapper(requireContext(), themeResId), R.attr.bottomSheetStyle) val bottomSheetStyle = ThemeUtil.getThemedResourceId(ContextThemeWrapper(requireContext(), themeResId), R.attr.bottomSheetStyle)
dialogBackground.setTint(ThemeUtil.getThemedColor(ContextThemeWrapper(requireContext(), bottomSheetStyle), R.attr.backgroundTint)) backgroundColor = ThemeUtil.getThemedColor(ContextThemeWrapper(requireContext(), bottomSheetStyle), R.attr.backgroundTint)
dialogBackground.setTint(backgroundColor)
dialog.behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { dialog.behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
@@ -62,7 +62,7 @@ public class FromTextView extends SimpleEmojiTextView {
builder.append(suffix); builder.append(suffix);
} }
if (recipient.isReleaseNotes()) { if (recipient.showVerified()) {
Drawable official = ContextUtil.requireDrawable(getContext(), R.drawable.ic_official_20); Drawable official = ContextUtil.requireDrawable(getContext(), R.drawable.ic_official_20);
official.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20)); official.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20));
@@ -13,7 +13,6 @@ import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ThemeUtil;
/** /**
* Base dialog fragment for rendering as a full screen dialog with animation * Base dialog fragment for rendering as a full screen dialog with animation
@@ -53,11 +53,11 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener; import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class InputPanel extends LinearLayout public class InputPanel extends LinearLayout
@@ -181,13 +181,19 @@ public class InputPanel extends LinearLayout
@NonNull CharSequence body, @NonNull CharSequence body,
@NonNull SlideDeck attachments) @NonNull SlideDeck attachments)
{ {
this.quoteView.setQuote(glideRequests, id, author, body, false, attachments, null); this.quoteView.setQuote(glideRequests, id, author, body, false, attachments, null, null);
int originalHeight = this.quoteView.getVisibility() == VISIBLE ? this.quoteView.getMeasuredHeight() int originalHeight = this.quoteView.getVisibility() == VISIBLE ? this.quoteView.getMeasuredHeight()
: 0; : 0;
this.quoteView.setVisibility(VISIBLE); this.quoteView.setVisibility(VISIBLE);
this.quoteView.measure(0, 0);
int maxWidth = composeContainer.getWidth();
if (quoteView.getLayoutParams() instanceof MarginLayoutParams) {
MarginLayoutParams layoutParams = (MarginLayoutParams) quoteView.getLayoutParams();
maxWidth -= layoutParams.leftMargin + layoutParams.rightMargin;
}
this.quoteView.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), 0);
if (quoteAnimator != null) { if (quoteAnimator != null) {
quoteAnimator.cancel(); quoteAnimator.cancel();
@@ -252,7 +258,7 @@ public class InputPanel extends LinearLayout
if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) { if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) {
return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getId(), quoteView.getBody().toString(), false, quoteView.getAttachments(), quoteView.getMentions())); return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getId(), quoteView.getBody().toString(), false, quoteView.getAttachments(), quoteView.getMentions()));
} else { } else {
return Optional.absent(); return Optional.empty();
} }
} }
@@ -13,7 +13,6 @@ import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Guideline; import androidx.constraintlayout.widget.Guideline;
import org.signal.glide.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
@@ -35,7 +35,7 @@ abstract class KeyboardEntryDialogFragment(@LayoutRes contentLayoutId: Int) :
dialog.window?.setDimAmount(0f) dialog.window?.setDimAmount(0f)
} }
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE or WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
return dialog return dialog
} }
@@ -22,13 +22,13 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Locale; import java.util.Locale;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import org.thoughtcrime.securesms.util.ViewUtil;
/** /**
* The view shown in the compose box or conversation that represents the state of the link preview. * The view shown in the compose box or conversation that represents the state of the link preview.
@@ -19,7 +19,6 @@ import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
@@ -101,7 +100,7 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) { if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) {
if (listener != null) listener.onRecordPermissionRequired(); if (listener != null) listener.onRecordPermissionRequired();
} else { } else if (state == State.NOT_RUNNING) {
state = State.RUNNING_HELD; state = State.RUNNING_HELD;
floatingRecordButton.display(event.getX(), event.getY()); floatingRecordButton.display(event.getX(), event.getY());
lockDropTarget.display(); lockDropTarget.display();
@@ -1,174 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.RecipientsAdapter;
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
/**
* Panel component combining both an editable field with a button for
* a list-based contact selector.
*
* @author Moxie Marlinspike
*/
public class PushRecipientsPanel extends RelativeLayout implements RecipientForeverObserver {
private final String TAG = Log.tag(PushRecipientsPanel.class);
private RecipientsPanelChangedListener panelChangeListener;
private RecipientsEditor recipientsText;
private View panel;
private static final int RECIPIENTS_MAX_LENGTH = 312;
public PushRecipientsPanel(Context context) {
super(context);
initialize();
}
public PushRecipientsPanel(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public PushRecipientsPanel(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Stream.of(getRecipients()).map(Recipient::live).forEach(r -> r.removeForeverObserver(this));
}
public List<Recipient> getRecipients() {
String rawText = recipientsText.getText().toString();
return getRecipientsFromString(getContext(), rawText);
}
public void disable() {
recipientsText.setText("");
panel.setVisibility(View.GONE);
}
public void setPanelChangeListener(RecipientsPanelChangedListener panelChangeListener) {
this.panelChangeListener = panelChangeListener;
}
private void initialize() {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.push_recipients_panel, this, true);
View imageButton = findViewById(R.id.contacts_button);
((MarginLayoutParams) imageButton.getLayoutParams()).topMargin = 0;
panel = findViewById(R.id.recipients_panel);
initRecipientsEditor();
}
private void initRecipientsEditor() {
this.recipientsText = (RecipientsEditor)findViewById(R.id.recipients_text);
List<Recipient> recipients = getRecipients();
Stream.of(recipients).map(Recipient::live).forEach(r -> r.observeForever(this));
recipientsText.setAdapter(new RecipientsAdapter(this.getContext()));
recipientsText.populate(recipients);
recipientsText.setOnFocusChangeListener(new FocusChangedListener());
recipientsText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (panelChangeListener != null) {
panelChangeListener.onRecipientsPanelUpdate(getRecipients());
}
recipientsText.setText("");
}
});
}
private @NonNull List<Recipient> getRecipientsFromString(Context context, @NonNull String rawText) {
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
List<Recipient> recipients = new LinkedList<>();
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if (!TextUtils.isEmpty(token)) {
if (hasBracketedNumber(token)) recipients.add(Recipient.external(context, parseBracketedNumber(token)));
else recipients.add(Recipient.external(context, token));
}
}
return recipients;
}
private boolean hasBracketedNumber(String recipient) {
int openBracketIndex = recipient.indexOf('<');
return (openBracketIndex != -1) &&
(recipient.indexOf('>', openBracketIndex) != -1);
}
private String parseBracketedNumber(String recipient) {
int begin = recipient.indexOf('<');
int end = recipient.indexOf('>', begin);
String value = recipient.substring(begin + 1, end);
return value;
}
@Override
public void onRecipientChanged(@NonNull Recipient recipient) {
recipientsText.populate(getRecipients());
}
private class FocusChangedListener implements View.OnFocusChangeListener {
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus && (panelChangeListener != null)) {
panelChangeListener.onRecipientsPanelUpdate(getRecipients());
}
}
}
public interface RecipientsPanelChangedListener {
public void onRecipientsPanelUpdate(List<Recipient> recipients);
}
}
@@ -24,6 +24,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation; import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.conversation.colors.ChatColors; import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.Mention;
@@ -34,10 +35,13 @@ import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Projection; import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.util.List; import java.util.List;
public class QuoteView extends FrameLayout implements RecipientForeverObserver { public class QuoteView extends FrameLayout implements RecipientForeverObserver {
@@ -49,7 +53,9 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
PREVIEW(0), PREVIEW(0),
OUTGOING(1), OUTGOING(1),
INCOMING(2), INCOMING(2),
STORY_REPLY(3); STORY_REPLY_OUTGOING(3),
STORY_REPLY_INCOMING(4),
STORY_REPLY_PREVIEW(5);
private final int code; private final int code;
@@ -68,16 +74,18 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
} }
} }
private ViewGroup mainView; private ViewGroup mainView;
private ViewGroup footerView; private ViewGroup footerView;
private TextView authorView; private TextView authorView;
private TextView bodyView; private TextView bodyView;
private View quoteBarView; private View quoteBarView;
private ImageView thumbnailView; private ImageView thumbnailView;
private View attachmentVideoOverlayView; private View attachmentVideoOverlayView;
private ViewGroup attachmentContainerView; private ViewGroup attachmentContainerView;
private TextView attachmentNameView; private TextView attachmentNameView;
private ImageView dismissView; private ImageView dismissView;
private EmojiImageView missingStoryReaction;
private EmojiImageView storyReactionEmoji;
private long id; private long id;
private LiveRecipient author; private LiveRecipient author;
@@ -129,6 +137,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
this.dismissView = findViewById(R.id.quote_dismiss); this.dismissView = findViewById(R.id.quote_dismiss);
this.mediaDescriptionText = findViewById(R.id.media_type); this.mediaDescriptionText = findViewById(R.id.media_type);
this.missingLinkText = findViewById(R.id.quote_missing_text); this.missingLinkText = findViewById(R.id.quote_missing_text);
this.missingStoryReaction = findViewById(R.id.quote_missing_story_reaction_emoji);
this.storyReactionEmoji = findViewById(R.id.quote_story_reaction_emoji);
this.largeCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_large); this.largeCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_large);
this.smallCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom); this.smallCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
@@ -177,13 +187,11 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview); int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview);
cornerMask.setTopLeftRadius(radius); cornerMask.setTopLeftRadius(radius);
cornerMask.setTopRightRadius(radius); cornerMask.setTopRightRadius(radius);
} else if (messageType == MessageType.STORY_REPLY) { } else if (isStoryReply()) {
thumbWidth = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_width); thumbWidth = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_width);
thumbHeight = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_height); thumbHeight = getResources().getDimensionPixelOffset(R.dimen.quote_story_thumb_height);
} }
mainView.setMinimumHeight(thumbHeight);
ViewGroup.LayoutParams params = thumbnailView.getLayoutParams(); ViewGroup.LayoutParams params = thumbnailView.getLayoutParams();
params.height = thumbHeight; params.height = thumbHeight;
params.width = thumbWidth; params.width = thumbWidth;
@@ -197,7 +205,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
@Nullable CharSequence body, @Nullable CharSequence body,
boolean originalMissing, boolean originalMissing,
@NonNull SlideDeck attachments, @NonNull SlideDeck attachments,
@Nullable ChatColors chatColors) @Nullable ChatColors chatColors,
@Nullable String storyReaction)
{ {
if (this.author != null) this.author.removeForeverObserver(this); if (this.author != null) this.author.removeForeverObserver(this);
@@ -208,8 +217,8 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
this.author.observeForever(this); this.author.observeForever(this);
setQuoteAuthor(author); setQuoteAuthor(author);
setQuoteText(body, attachments); setQuoteText(body, attachments, originalMissing, storyReaction);
setQuoteAttachment(glideRequests, attachments); setQuoteAttachment(glideRequests, body, attachments, originalMissing);
setQuoteMissingFooter(originalMissing); setQuoteMissingFooter(originalMissing);
if (Build.VERSION.SDK_INT < 21 && messageType == MessageType.INCOMING && chatColors != null) { if (Build.VERSION.SDK_INT < 21 && messageType == MessageType.INCOMING && chatColors != null) {
@@ -248,10 +257,10 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
} }
private void setQuoteAuthor(@NonNull Recipient author) { private void setQuoteAuthor(@NonNull Recipient author) {
boolean outgoing = messageType != MessageType.INCOMING; boolean outgoing = messageType != MessageType.INCOMING && messageType != MessageType.STORY_REPLY_INCOMING;
boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY; boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY_PREVIEW;
if (messageType == MessageType.STORY_REPLY) { if (isStoryReply()) {
authorView.setText(author.isSelf() ? getContext().getString(R.string.QuoteView_your_story) authorView.setText(author.isSelf() ? getContext().getString(R.string.QuoteView_your_story)
: getContext().getString(R.string.QuoteView_s_story, author.getDisplayName(getContext()))); : getContext().getString(R.string.QuoteView_s_story, author.getDisplayName(getContext())));
} else { } else {
@@ -259,14 +268,70 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
: author.getDisplayName(getContext())); : author.getDisplayName(getContext()));
} }
quoteBarView.setBackgroundColor(ContextCompat.getColor(getContext(), outgoing ? R.color.core_white : android.R.color.transparent)); quoteBarView.setBackgroundColor(ContextCompat.getColor(getContext(), outgoing || isStoryReply() ? R.color.core_white : android.R.color.transparent));
mainView.setBackgroundColor(ContextCompat.getColor(getContext(), preview ? R.color.quote_preview_background : R.color.quote_view_background));
int mainViewColor;
if (preview) {
mainViewColor = R.color.quote_preview_background;
} else if (!outgoing && isStoryReply()) {
mainViewColor = R.color.quote_incoming_story_background;
} else {
mainViewColor = R.color.quote_view_background;
}
mainView.setBackgroundColor(ContextCompat.getColor(getContext(), mainViewColor));
} }
private void setQuoteText(@Nullable CharSequence body, @NonNull SlideDeck attachments) { private boolean isStoryReply() {
return messageType == MessageType.STORY_REPLY_OUTGOING ||
messageType == MessageType.STORY_REPLY_INCOMING ||
messageType == MessageType.STORY_REPLY_PREVIEW;
}
private void setQuoteText(@Nullable CharSequence body,
@NonNull SlideDeck attachments,
boolean originalMissing,
@Nullable String storyReaction)
{
if (originalMissing && isStoryReply()) {
bodyView.setVisibility(GONE);
storyReactionEmoji.setVisibility(View.GONE);
mediaDescriptionText.setVisibility(VISIBLE);
mediaDescriptionText.setText(R.string.QuoteView_no_longer_available);
if (storyReaction != null) {
missingStoryReaction.setVisibility(View.VISIBLE);
missingStoryReaction.setImageEmoji(body);
} else {
missingStoryReaction.setVisibility(View.GONE);
}
return;
}
if (storyReaction != null) {
storyReactionEmoji.setImageEmoji(storyReaction);
storyReactionEmoji.setVisibility(View.VISIBLE);
missingStoryReaction.setVisibility(View.INVISIBLE);
} else {
storyReactionEmoji.setVisibility(View.GONE);
missingStoryReaction.setVisibility(View.GONE);
}
boolean isTextStory = !attachments.containsMediaSlide() && isStoryReply();
if (!TextUtils.isEmpty(body) || !attachments.containsMediaSlide()) { if (!TextUtils.isEmpty(body) || !attachments.containsMediaSlide()) {
if (isTextStory && body != null) {
try {
bodyView.setText(getStoryTextPost(body).getText());
} catch (Exception e) {
Log.w(TAG, "Could not parse body of text post.", e);
bodyView.setText("");
}
} else {
bodyView.setText(body == null ? "" : body);
}
bodyView.setVisibility(VISIBLE); bodyView.setVisibility(VISIBLE);
bodyView.setText(body == null ? "" : body);
mediaDescriptionText.setVisibility(GONE); mediaDescriptionText.setVisibility(GONE);
return; return;
} }
@@ -305,7 +370,22 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
} }
} }
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) { private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull CharSequence body, @NonNull SlideDeck slideDeck, boolean originalMissing) {
mainView.setMinimumHeight(isStoryReply() && originalMissing ? 0 : thumbHeight);
if (!attachments.containsMediaSlide() && isStoryReply()) {
StoryTextPostModel model = getStoryTextPost(body);
attachmentVideoOverlayView.setVisibility(GONE);
attachmentContainerView.setVisibility(GONE);
thumbnailView.setVisibility(VISIBLE);
glideRequests.load(model)
.centerCrop()
.override(thumbWidth, thumbHeight)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(thumbnailView);
return;
}
Slide imageVideoSlide = slideDeck.getSlides().stream().filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker()).findFirst().orElse(null); Slide imageVideoSlide = slideDeck.getSlides().stream().filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker()).findFirst().orElse(null);
Slide documentSlide = slideDeck.getSlides().stream().filter(Slide::hasDocument).findFirst().orElse(null); Slide documentSlide = slideDeck.getSlides().stream().filter(Slide::hasDocument).findFirst().orElse(null);
Slide viewOnceSlide = slideDeck.getSlides().stream().filter(Slide::hasViewOnce).findFirst().orElse(null); Slide viewOnceSlide = slideDeck.getSlides().stream().filter(Slide::hasViewOnce).findFirst().orElse(null);
@@ -330,7 +410,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
} else if (documentSlide != null){ } else if (documentSlide != null){
thumbnailView.setVisibility(GONE); thumbnailView.setVisibility(GONE);
attachmentContainerView.setVisibility(VISIBLE); attachmentContainerView.setVisibility(VISIBLE);
attachmentNameView.setText(documentSlide.getFileName().or("")); attachmentNameView.setText(documentSlide.getFileName().orElse(""));
} else { } else {
thumbnailView.setVisibility(GONE); thumbnailView.setVisibility(GONE);
attachmentContainerView.setVisibility(GONE); attachmentContainerView.setVisibility(GONE);
@@ -343,10 +423,22 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
} }
private void setQuoteMissingFooter(boolean missing) { private void setQuoteMissingFooter(boolean missing) {
footerView.setVisibility(missing ? VISIBLE : GONE); footerView.setVisibility(missing && !isStoryReply() ? VISIBLE : GONE);
footerView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.quote_view_background)); footerView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.quote_view_background));
} }
private @Nullable StoryTextPostModel getStoryTextPost(@Nullable CharSequence body) {
if (Util.isEmpty(body)) {
return null;
}
try {
return StoryTextPostModel.parseFrom(body.toString(), id, author.getId());
} catch (IOException ioException) {
return null;
}
}
public void setTextSize(int unit, float size) { public void setTextSize(int unit, float size) {
bodyView.setTextSize(unit, size); bodyView.setTextSize(unit, size);
} }
@@ -1,11 +1,7 @@
package org.thoughtcrime.securesms.components; package org.thoughtcrime.securesms.components;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
@@ -58,6 +58,7 @@ public class SearchToolbar extends LinearLayout {
EditText searchText = searchView.findViewById(R.id.search_src_text); EditText searchText = searchView.findViewById(R.id.search_src_text);
searchView.setSubmitButtonEnabled(false); searchView.setSubmitButtonEnabled(false);
searchView.setMaxWidth(Integer.MAX_VALUE);
if (searchText != null) searchText.setHint(R.string.SearchToolbar_search); if (searchText != null) searchText.setHint(R.string.SearchToolbar_search);
else searchView.setQueryHint(getResources().getString(R.string.SearchToolbar_search)); else searchView.setQueryHint(getResources().getString(R.string.SearchToolbar_search));
@@ -65,7 +66,9 @@ public class SearchToolbar extends LinearLayout {
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override @Override
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
if (listener != null) listener.onSearchTextChange(query); if (listener != null) {
listener.onSearchTextChange(query);
}
return true; return true;
} }
@@ -12,7 +12,9 @@ import org.thoughtcrime.securesms.TransportOptions;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener; import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.TransportOptionsPopup; import org.thoughtcrime.securesms.TransportOptionsPopup;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Optional;
public class SendButton extends AppCompatImageButton public class SendButton extends AppCompatImageButton
implements TransportOptions.OnTransportChangedListener, implements TransportOptions.OnTransportChangedListener,
@@ -22,7 +24,7 @@ public class SendButton extends AppCompatImageButton
private final TransportOptions transportOptions; private final TransportOptions transportOptions;
private Optional<TransportOptionsPopup> transportOptionsPopup = Optional.absent(); private Optional<TransportOptionsPopup> transportOptionsPopup = Optional.empty();
@SuppressWarnings("unused") @SuppressWarnings("unused")
public SendButton(Context context) { public SendButton(Context context) {
@@ -34,16 +34,17 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Collections; import java.util.Collections;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
@@ -68,7 +69,7 @@ public class ThumbnailView extends FrameLayout {
private final int[] bounds = new int[4]; private final int[] bounds = new int[4];
private final int[] measureDimens = new int[2]; private final int[] measureDimens = new int[2];
private Optional<TransferControlView> transferControls = Optional.absent(); private Optional<TransferControlView> transferControls = Optional.empty();
private SlideClickListener thumbnailClickListener = null; private SlideClickListener thumbnailClickListener = null;
private SlidesClickedListener downloadClickListener = null; private SlidesClickedListener downloadClickListener = null;
private Slide slide = null; private Slide slide = null;
@@ -391,6 +392,32 @@ public class ThumbnailView extends FrameLayout {
return future; return future;
} }
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull StoryTextPostModel model, int width, int height) {
SettableFuture<Boolean> future = new SettableFuture<>();
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
GlideRequest request = glideRequests.load(model)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.placeholder(model.getPlaceholder())
.transition(withCrossFade());
if (width > 0 && height > 0) {
request = request.override(width, height);
}
if (radius > 0) {
request = request.transforms(new CenterCrop(), new RoundedCorners(radius));
} else {
request = request.transforms(new CenterCrop());
}
request.into(new GlideDrawableListeningTarget(image, future));
blurhash.setImageDrawable(null);
return future;
}
public void setThumbnailClickListener(SlideClickListener listener) { public void setThumbnailClickListener(SlideClickListener listener) {
this.thumbnailClickListener = listener; this.thumbnailClickListener = listener;
} }
@@ -401,11 +428,15 @@ public class ThumbnailView extends FrameLayout {
public void clear(GlideRequests glideRequests) { public void clear(GlideRequests glideRequests) {
glideRequests.clear(image); glideRequests.clear(image);
image.setImageDrawable(null);
if (transferControls.isPresent()) { if (transferControls.isPresent()) {
getTransferControls().clear(); getTransferControls().clear();
} }
glideRequests.clear(blurhash);
blurhash.setImageDrawable(null);
slide = null; slide = null;
} }
@@ -2,15 +2,20 @@ package org.thoughtcrime.securesms.components;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.Target;
import com.davemorrissey.labs.subscaleview.ImageSource; import com.davemorrissey.labs.subscaleview.ImageSource;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
@@ -24,11 +29,12 @@ import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.ActionRequestListener;
import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.signal.core.util.concurrent.SimpleTask;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -79,7 +85,7 @@ public class ZoomingImageView extends FrameLayout {
} }
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
public void setImageUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull String contentType) public void setImageUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull String contentType, @NonNull Runnable onMediaReady)
{ {
final Context context = getContext(); final Context context = getContext();
final int maxTextureSize = BitmapUtil.getMaxTextureSize(); final int maxTextureSize = BitmapUtil.getMaxTextureSize();
@@ -101,15 +107,16 @@ public class ZoomingImageView extends FrameLayout {
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) { if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
Log.i(TAG, "Loading in standard image view..."); Log.i(TAG, "Loading in standard image view...");
setImageViewUri(glideRequests, uri); setImageViewUri(glideRequests, uri, onMediaReady);
} else { } else {
Log.i(TAG, "Loading in subsampling image view..."); Log.i(TAG, "Loading in subsampling image view...");
setSubsamplingImageViewUri(uri); setSubsamplingImageViewUri(uri);
onMediaReady.run();
} }
}); });
} }
private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri) { private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull Runnable onMediaReady) {
photoView.setVisibility(View.VISIBLE); photoView.setVisibility(View.VISIBLE);
subsamplingImageView.setVisibility(View.GONE); subsamplingImageView.setVisibility(View.GONE);
@@ -117,6 +124,7 @@ public class ZoomingImageView extends FrameLayout {
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.dontTransform() .dontTransform()
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.addListener(ActionRequestListener.onEither(onMediaReady))
.into(photoView); .into(photoView);
} }
@@ -42,12 +42,12 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class CameraView extends ViewGroup { public class CameraView extends ViewGroup {
@@ -56,8 +56,8 @@ public class CameraView extends ViewGroup {
private final CameraSurfaceView surface; private final CameraSurfaceView surface;
private final OnOrientationChange onOrientationChange; private final OnOrientationChange onOrientationChange;
private volatile Optional<Camera> camera = Optional.absent(); private volatile Optional<Camera> camera = Optional.empty();
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK; private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
private volatile int displayOrientation = -1; private volatile int displayOrientation = -1;
private @NonNull State state = State.PAUSED; private @NonNull State state = State.PAUSED;
@@ -104,7 +104,7 @@ public class CameraView extends ViewGroup {
Void onRunBackground() { Void onRunBackground() {
try { try {
long openStartMillis = System.currentTimeMillis(); long openStartMillis = System.currentTimeMillis();
camera = Optional.fromNullable(Camera.open(cameraId)); camera = Optional.ofNullable(Camera.open(cameraId));
Log.i(TAG, "camera.open() -> " + (System.currentTimeMillis() - openStartMillis) + "ms"); Log.i(TAG, "camera.open() -> " + (System.currentTimeMillis() - openStartMillis) + "ms");
synchronized (CameraView.this) { synchronized (CameraView.this) {
CameraView.this.notifyAll(); CameraView.this.notifyAll();
@@ -145,7 +145,7 @@ public class CameraView extends ViewGroup {
@Override @Override
protected void onPreMain() { protected void onPreMain() {
cameraToDestroy = camera; cameraToDestroy = camera;
camera = Optional.absent(); camera = Optional.empty();
} }
@Override @Override
@@ -14,7 +14,6 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable; import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class EmojiEditText extends AppCompatEditText { public class EmojiEditText extends AppCompatEditText {
@@ -21,8 +21,8 @@ import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.Emoj
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener; import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
import org.thoughtcrime.securesms.util.ContextUtil; import org.thoughtcrime.securesms.util.ContextUtil;
import org.thoughtcrime.securesms.util.DrawableUtil; import org.thoughtcrime.securesms.util.DrawableUtil;
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -10,6 +10,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -19,7 +20,6 @@ import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo; import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser; import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.emoji.EmojiFiles;
import org.thoughtcrime.securesms.emoji.EmojiPageCache; import org.thoughtcrime.securesms.emoji.EmojiPageCache;
import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.JumboEmoji; import org.thoughtcrime.securesms.emoji.JumboEmoji;
@@ -101,6 +101,10 @@ public class EmojiProvider {
} }
static @Nullable Drawable getEmojiDrawable(@NonNull Context context, @Nullable CharSequence emoji, boolean jumboEmoji) { static @Nullable Drawable getEmojiDrawable(@NonNull Context context, @Nullable CharSequence emoji, boolean jumboEmoji) {
if (TextUtils.isEmpty(emoji)) {
return null;
}
EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length()); EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length());
return getEmojiDrawable(context, drawInfo, null, jumboEmoji); return getEmojiDrawable(context, drawInfo, null, jumboEmoji);
} }
@@ -33,9 +33,10 @@ import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
import org.thoughtcrime.securesms.emoji.JumboEmoji; import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import kotlin.Unit; import kotlin.Unit;
@@ -141,7 +142,7 @@ public class EmojiTextView extends AppCompatTextView {
previousTransformationMethod = getTransformationMethod(); previousTransformationMethod = getTransformationMethod();
if (useSystemEmoji || candidates == null || candidates.size() == 0) { if (useSystemEmoji || candidates == null || candidates.size() == 0) {
super.setText(new SpannableStringBuilder(Optional.fromNullable(text).or("")), BufferType.SPANNABLE); super.setText(new SpannableStringBuilder(Optional.ofNullable(text).orElse("")), BufferType.SPANNABLE);
} else { } else {
CharSequence emojified = EmojiProvider.emojify(candidates, text, this, isJumbomoji || forceJumboEmoji); CharSequence emojified = EmojiProvider.emojify(candidates, text, this, isJumbomoji || forceJumboEmoji);
super.setText(new SpannableStringBuilder(emojified), BufferType.SPANNABLE); super.setText(new SpannableStringBuilder(emojified), BufferType.SPANNABLE);
@@ -149,7 +150,7 @@ public class EmojiTextView extends AppCompatTextView {
// Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688) // Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688)
// We ellipsize them ourselves by manually truncating the appropriate section. // We ellipsize them ourselves by manually truncating the appropriate section.
if (getText() != null && getText().length() > 0 && getEllipsize() == TextUtils.TruncateAt.END) { if (getText() != null && getText().length() > 0 && isEllipsizedAtEnd()) {
if (maxLength > 0) { if (maxLength > 0) {
ellipsizeAnyTextForMaxLength(); ellipsizeAnyTextForMaxLength();
} else if (getMaxLines() > 0) { } else if (getMaxLines() > 0) {
@@ -162,6 +163,17 @@ public class EmojiTextView extends AppCompatTextView {
} }
} }
/**
* Used to determine whether to apply custom ellipsizing logic without necessarily having the
* ellipsize property set. This allows us to work around implementations of Layout which apply an
* ellipsis even when maxLines is not set.
*/
private boolean isEllipsizedAtEnd() {
return getEllipsize() == TextUtils.TruncateAt.END ||
(getMaxLines() > 0 && getMaxLines() < Integer.MAX_VALUE) ||
maxLength > 0;
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec); widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec);
@@ -201,7 +213,7 @@ public class EmojiTextView extends AppCompatTextView {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
float measuredTextWidth = hasMetricAffectingSpan(text) ? Layout.getDesiredWidth(text, getPaint()) : getPaint().measureText(text, 0, text.length()); float measuredTextWidth = hasMetricAffectingSpan(text) ? Layout.getDesiredWidth(text, getPaint()) : getLongestLineWidth(text);
int desiredWidth = (int) measuredTextWidth + getPaddingLeft() + getPaddingRight(); int desiredWidth = (int) measuredTextWidth + getPaddingLeft() + getPaddingRight();
if (widthSpecMode == MeasureSpec.AT_MOST && desiredWidth < widthSpecSize) { if (widthSpecMode == MeasureSpec.AT_MOST && desiredWidth < widthSpecSize) {
@@ -221,6 +233,20 @@ public class EmojiTextView extends AppCompatTextView {
return ((Spanned) text).nextSpanTransition(-1, text.length(), CharacterStyle.class) != text.length(); return ((Spanned) text).nextSpanTransition(-1, text.length(), CharacterStyle.class) != text.length();
} }
private float getLongestLineWidth(@NonNull CharSequence text) {
if (TextUtils.isEmpty(text)) {
return 0f;
}
long maxLines = getMaxLines() > 0 ? getMaxLines() : Long.MAX_VALUE;
return Arrays.stream(text.toString().split("\n"))
.limit(maxLines)
.map(s -> getPaint().measureText(s, 0, s.length()))
.max(Float::compare)
.orElse(0f);
}
public int getLastLineWidth() { public int getLastLineWidth() {
return lastLineWidth; return lastLineWidth;
} }
@@ -291,7 +317,7 @@ public class EmojiTextView extends AppCompatTextView {
SpannableStringBuilder newContent = new SpannableStringBuilder(); SpannableStringBuilder newContent = new SpannableStringBuilder();
newContent.append(getText().subSequence(0, overflowStart)) newContent.append(getText().subSequence(0, overflowStart))
.append(ellipsized.subSequence(0, ellipsized.length())) .append(ellipsized.subSequence(0, ellipsized.length()))
.append(Optional.fromNullable(overflowText).or("")); .append(Optional.ofNullable(overflowText).orElse(""));
EmojiParser.CandidateList newCandidates = isInEditMode() ? null : EmojiProvider.getCandidates(newContent); EmojiParser.CandidateList newCandidates = isInEditMode() ? null : EmojiProvider.getCandidates(newContent);
CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, isJumbomoji || forceJumboEmoji); CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, isJumbomoji || forceJumboEmoji);
@@ -1,10 +1,12 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.AppCompatImageButton;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
@@ -24,17 +26,17 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
public EmojiToggle(Context context) { public EmojiToggle(Context context) {
super(context); super(context);
initialize(); initialize(null);
} }
public EmojiToggle(Context context, AttributeSet attrs) { public EmojiToggle(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
initialize(); initialize(attrs);
} }
public EmojiToggle(Context context, AttributeSet attrs, int defStyle) { public EmojiToggle(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
initialize(); initialize(attrs);
} }
public void setToMedia() { public void setToMedia() {
@@ -45,11 +47,18 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
setImageDrawable(imeToggle); setImageDrawable(imeToggle);
} }
private void initialize() { private void initialize(@Nullable AttributeSet attrs) {
this.emojiToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_emoji); boolean forceOutline = false;
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiToggle, 0, 0);
forceOutline = typedArray.getBoolean(R.styleable.EmojiToggle_force_outline, false);
typedArray.recycle();
}
this.emojiToggle = ContextUtil.requireDrawable(getContext(), forceOutline ? R.drawable.ic_emoji_outline : R.drawable.ic_emoji);
this.stickerToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_sticker_24); this.stickerToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_sticker_24);
this.gifToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_gif_24); this.gifToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_gif_24);
this.imeToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_keyboard_24); this.imeToggle = ContextUtil.requireDrawable(getContext(), forceOutline ? R.drawable.ic_keyboard_outline_24 : R.drawable.ic_keyboard_24);
this.mediaToggle = emojiToggle; this.mediaToggle = emojiToggle;
setToMedia(); setToMedia();
@@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser; import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.ObsoleteEmoji; import org.thoughtcrime.securesms.emoji.ObsoleteEmoji;
import org.thoughtcrime.securesms.util.StringUtil; import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.util.HashSet; import java.util.HashSet;
@@ -18,6 +18,7 @@ import java.util.regex.Pattern;
public final class EmojiUtil { public final class EmojiUtil {
private static final Pattern EMOJI_PATTERN = Pattern.compile("^(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+)+$"); private static final Pattern EMOJI_PATTERN = Pattern.compile("^(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+)+$");
private static final String EMOJI_REGEX = "[^\\p{L}\\p{M}\\p{N}\\p{P}\\p{Z}\\p{Cf}\\p{Cs}\\s]";
private EmojiUtil() {} private EmojiUtil() {}
@@ -84,4 +85,12 @@ public final class EmojiUtil {
return (candidates != null && candidates.size() > 0) || EMOJI_PATTERN.matcher(text).matches(); return (candidates != null && candidates.size() > 0) || EMOJI_PATTERN.matcher(text).matches();
} }
public static String stripEmoji(@Nullable String text) {
if (text == null) {
return text;
}
return text.replaceAll(EMOJI_REGEX, "");
}
} }
@@ -10,8 +10,6 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.TypedArrayUtils;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@@ -23,7 +21,6 @@ import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
import org.thoughtcrime.securesms.keyboard.KeyboardPage; import org.thoughtcrime.securesms.keyboard.KeyboardPage;
import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment; import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment;
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment; import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ThemedFragment; import org.thoughtcrime.securesms.util.ThemedFragment;
public class MediaKeyboard extends FrameLayout implements InputView { public class MediaKeyboard extends FrameLayout implements InputView {
@@ -149,6 +146,10 @@ public class MediaKeyboard extends FrameLayout implements InputView {
.commitAllowingStateLoss(); .commitAllowingStateLoss();
} }
public boolean isEmojiSearchMode() {
return keyboardState == State.EMOJI_SEARCH;
}
private void initView() { private void initView() {
if (!isInitialised) { if (!isInitialised) {

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