529 Commits

Author SHA1 Message Date
6f1493808f feat(ui): Move the bubbles into their own directory 2023-01-02 19:01:55 +01:00
c9d32694db fix(i18n): Translate forgotten strings 2023-01-02 18:59:28 +01:00
8632a2fc81 fix(ui): Finally fix message bubbles? 2023-01-02 18:59:08 +01:00
46a09d5b62 feat(service): Manage sticker pack privacy
Fixes #192.
2023-01-02 18:04:27 +01:00
b7e5bbc7d2 fix(service): Fix avatars sometimes being not available 2023-01-02 17:38:22 +01:00
ed264f0c16 fix(service): Fix 'ghost' devices appearing 2023-01-02 17:19:23 +01:00
f1820575ad feat(ui): Show the ink splash on new device messages 2023-01-02 17:12:50 +01:00
d2e42d0a3c feat(meta): Show a message if a contact adds a new device 2023-01-02 15:19:08 +01:00
842cf5aaaa fix(service): Maybe fix avatar fetching crashes 2023-01-02 14:02:13 +01:00
c8f727e982 feat(meta): Update moxxmpp and omemo_dart 2023-01-02 14:00:21 +01:00
fd3c9190de Merge pull request 'Migrate to OmemoManager API' (#194) from feat/omemo-improvement into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/194
2023-01-01 19:09:17 +00:00
69439d2b13 feat(meta): Remove commented-out omemo_dart override 2023-01-01 19:36:58 +01:00
6d41fee73f feat(meta): Lockfile update 2023-01-01 18:24:13 +01:00
0de99adeed fix(service): Adjust to new omemo_dart API 2023-01-01 18:22:41 +01:00
f71fd7c82c feat(meta): Update omemo_dart and moxxmpp 2023-01-01 18:22:25 +01:00
0a6b0b8fa5 feat(service): Migrate to new omemo_dart design 2023-01-01 15:02:35 +01:00
5e0ce8f098 fix(ui): Only show stickers that are images 2022-12-27 12:51:23 +01:00
9fc5989bd4 feat(ui): Add an assertion for adding a contact 2022-12-25 13:20:13 +01:00
cbe81861a5 fix(service): Fix avatars being empty when OMEMO is enabled 2022-12-25 13:09:24 +01:00
76a03cc2fa feat(service): Rework the blocklist service
Maybe fixes #14.
2022-12-25 01:25:12 +01:00
3774760548 fix(service): Fix missing type 2022-12-24 22:33:54 +01:00
4b1942b949 fix(ui): Move the date bubbles out of the chat bubble 2022-12-23 14:45:32 +01:00
Millesimus
2f03c02b58 fix(service): Remove unnecessary dio error handling. 2022-12-22 20:28:26 +01:00
Millesimus
639143934f Chore(service): A little tidying. 2022-12-22 20:28:26 +01:00
Millesimus
81bbbcd8e4 Fix(service): Re-enable progress indication using a completer. 2022-12-22 20:28:26 +01:00
Millesimus
bedd46756d Fix(service): flatten memory usage of downloads (sacrificing download progress indication). 2022-12-22 20:28:26 +01:00
Millesimus
bb6b342d82 Fix(service): flatten memory usage of uploads. 2022-12-22 20:28:17 +01:00
b6eb12cf30 feat(ui): Replace settings_ui with custom UI elements 2022-12-22 13:39:07 +01:00
80f8129011 feat(ui): Fix dialog corner radius and make barrier dismissible 2022-12-22 12:20:57 +01:00
86daad2455 feat(ui): Replace the modal with a dialog 2022-12-22 00:49:01 +01:00
e71cbd5ba9 fix(ui): Translate the "Shared media" in the profile
Also left-align the title.
2022-12-22 00:22:06 +01:00
c0fb9beef7 feat(ui): Replace the squircles with a simple list 2022-12-22 00:16:14 +01:00
db4b69a24a feat(ui): Also make the profile picture summon the profile page 2022-12-21 23:41:18 +01:00
7746784949 fix(ui): Fix InkWell overflowing for shared media 2022-12-21 23:13:28 +01:00
024bd48aba fix(ui): Fix InkWell padding for files 2022-12-21 22:45:18 +01:00
cb13c9faa4 feat(ui): Show a toast when a file cannot be opened 2022-12-21 22:39:50 +01:00
009ec759a3 feat(ui): Make quotes look better 2022-12-21 21:00:08 +01:00
6ba16ad020 feat(ui): Make the file widget look better 2022-12-21 20:54:15 +01:00
43b0b34cdd fix(service): Fix crash when conversation partner has no OMEMO:2 devices 2022-12-21 20:29:54 +01:00
94e6eb2d10 fix(ui): Scroll to bottom does not respect emoji/sticker picker 2022-12-21 20:27:48 +01:00
578eea5d9f fix(ui): Fix missing space 2022-12-21 20:15:18 +01:00
724450e049 fix(ui): Use auto_size_text to prevent fingerprints from overflowing 2022-12-21 20:07:34 +01:00
1759baebad feat(ui): Display something when no sticker packs are installed 2022-12-21 18:58:52 +01:00
896ef50b9a fix(ui): Show toasts on verification errors 2022-12-20 16:03:36 +01:00
c4d52b6687 docs(meta): Add Ko-Fi link 2022-12-20 00:05:44 +01:00
5c611a59aa docs(meta): Add funding 2022-12-19 23:04:22 +01:00
7068b989ef Merge pull request 'Implement Stickers' (#184) from feat/stickers into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/184
2022-12-19 14:38:43 +00:00
820fda78e7 fix(ui): Fix sticker overviews having a black background 2022-12-19 15:35:22 +01:00
d758423ec6 fix(ui): Close the pickers when starting an audio message 2022-12-19 15:29:50 +01:00
5472f097a4 fix(meta): Depend on a git revision of moxxmpp 2022-12-19 14:15:13 +01:00
e373f5cffe feat(service): Implement automatic sticker pack downloading 2022-12-19 14:09:30 +01:00
f04729261b feat(ui): Add toasts 2022-12-19 13:33:05 +01:00
b6c8778aec fix(ui): Remove the restriction on clickable sticker messages 2022-12-19 12:48:15 +01:00
8dfe8d55a0 docs(meta): Add sticker pack format docs 2022-12-19 12:46:49 +01:00
36b7d5ce42 feat(ui): Show a spinner while a sticker pack import is running 2022-12-19 12:38:50 +01:00
8d780c3252 fix(ui): Fix alignment of description 2022-12-19 12:36:36 +01:00
a841d5de2d fix(ui): Some translations were off 2022-12-19 12:36:21 +01:00
fdd8d306f7 fix(ui): Close the emoji picker/sticker picker if the keyboard is visible 2022-12-19 12:14:25 +01:00
9510a0fced feat(ui): Make managing sticker packs nicer 2022-12-19 12:02:26 +01:00
c3ec9dfb11 fix(ui): Do not trigger the sticker pack page for sent messages 2022-12-18 20:28:29 +01:00
82c136b684 feat(ui): Update the conversation when stickers get added or removed 2022-12-18 20:19:49 +01:00
ea4bb752b9 fix(service): Guard against importing a sticker pack twice 2022-12-18 18:56:39 +01:00
bac673df99 fix(ui): Give the StickerPicker a background 2022-12-18 18:56:23 +01:00
df2c2f5e4b feat(ui): Honour restricted sticker packs 2022-12-18 18:25:44 +01:00
8c3863f970 feat(ui): Handle sticker XMPP URIs 2022-12-18 18:14:29 +01:00
bc49e31164 fix(service): Add missing attributes to stickers and sticker packs 2022-12-18 17:39:25 +01:00
ce4c54b0d5 fix(service): Freshly downloaded sticker packs are shown as still remote 2022-12-18 17:23:06 +01:00
7b09cdeefd feat(ui): Fix sticker messages not being rebuilt after downloading sticker pack 2022-12-18 17:20:50 +01:00
39dc96ab7a feat(ui): Add a shimmer for loading stickers 2022-12-18 17:10:10 +01:00
2d13ff328e feat(service): Publish a sticker pack after downloading it 2022-12-18 15:15:14 +01:00
53dd598547 feat(meta): Implement installing a remote sticker pack 2022-12-18 15:05:53 +01:00
40b4a540a8 feat(ui): Implement showing a remote sticker pack 2022-12-18 14:20:18 +01:00
33ae53c199 feat(meta): Mark XEP-0449 as complete 2022-12-18 00:57:34 +01:00
97e9b0636b fix(service): Fix OOB fallback messing the body up 2022-12-18 00:56:46 +01:00
b0b21e9d53 feat(ui): Prepare for remote sticker packs 2022-12-17 21:54:12 +01:00
53d5402502 feat(ui): Add a wrapper for local and remote images 2022-12-17 21:43:02 +01:00
a190a9564e fix(service): Silence some warning 2022-12-17 21:36:22 +01:00
7846520788 feat(service): Show 'Sticker' in the notification 2022-12-17 21:34:35 +01:00
3444683983 fix(service): Fix sent stickers not being visible in the preview 2022-12-17 21:29:06 +01:00
00118ddafe fix(meta): Switch to a hashKey 2022-12-17 21:16:38 +01:00
525ba293e3 feat(service): Remove sticker pack files on removal 2022-12-17 19:40:12 +01:00
071f6c08fd feat(ui): Add forgotten i18n string 2022-12-17 19:33:40 +01:00
da70236a45 feat(ui): Translate missing strings 2022-12-17 19:31:39 +01:00
cfdda2d293 feat(meta): Cleanup + simple sticker pack management 2022-12-17 19:24:20 +01:00
aba265d787 feat(service): Do not import sticker packs that are restricted 2022-12-17 17:42:54 +01:00
bbcb37bc4e feat(meta): Update DOAP 2022-12-17 17:34:58 +01:00
eff7d7493d feat(service): Calculate the sticker pack's id on import 2022-12-17 17:34:09 +01:00
730916758e feat(ui): Implement a simple sticker overview page 2022-12-17 16:21:14 +01:00
9acfe2751e feat(ui): Show the sticker in the conversation preview 2022-12-17 14:04:12 +01:00
386569d7cf feat(ui): Correctly handle quoting stickers 2022-12-17 14:00:41 +01:00
39a7e1eb19 feat(ui): Fix linter issues + i18n 2022-12-17 13:53:22 +01:00
f492845235 feat(ui): Somewhat handle not locally available stickers 2022-12-17 13:45:08 +01:00
ab42fc8b57 feat(ui): Add a sticker settings page 2022-12-17 13:36:44 +01:00
a5a9fce330 fix(ui): Fix crash 2022-12-17 13:21:21 +01:00
a70286dda4 feat(service): Allow receiving stickers 2022-12-17 12:40:50 +01:00
2b3e587be4 feat(meta): Display and sending of stickers 2022-12-17 12:22:10 +01:00
ebfac9730b fix(meta): Fix linter issues 2022-12-16 23:09:27 +01:00
fbd3c6ca92 refactor(ui): Refactor the StickerPicker 2022-12-16 23:02:40 +01:00
1cd3dabcea fix(ui): Make the send button's bottom padding adhere to the pickers 2022-12-16 22:54:31 +01:00
eba17880d0 feat(meta): Adapt the sticker picker 2022-12-16 22:49:59 +01:00
c168f910a9 feat(service): Allow importing sticker packs 2022-12-16 22:33:06 +01:00
98dd704fda feat(meta): Begin work in stickers 2022-12-16 20:58:02 +01:00
4ecebe8982 fix(service): Do not reopen conversations on subscription requests
Maybe fixes #165.
2022-12-12 12:56:30 +01:00
8f1d17636e Merge pull request 'Implement reading contact data from the phonebook' (#182) from feat/contact-integration into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/182
2022-12-12 11:54:16 +00:00
fb1c202586 refactor(service): contact.dart -> contacts.dart 2022-12-12 12:50:47 +01:00
d7a4ce022e feat(service): Reset a roster item to pseudoRosterItem on removal 2022-12-12 12:40:09 +01:00
64c3796429 feat(ui): Prevent removing a pseudo contact 2022-12-12 12:37:21 +01:00
80a517beaa fix(ui): Display pseudo roster items in the share selection 2022-12-12 12:35:16 +01:00
cec31550f8 fix(service): Handle inRoster for pseudo roster items 2022-12-12 12:23:12 +01:00
bee760adf5 feat(ui): Handle removing pseudo contacts 2022-12-12 12:18:49 +01:00
155d5747f8 feat(service): Implement pseudo roster items 2022-12-12 12:02:13 +01:00
fd531a360e feat(service): Better handle contact removal 2022-12-12 00:20:22 +01:00
c3884a460d fix(service): Remove debug command 2022-12-11 23:06:10 +01:00
5f5c30673d fix(ui): Make the UI elements react to changes of the contact integration 2022-12-11 22:22:57 +01:00
f423cd5611 feat(service): The correct avatar now appears in the notification 2022-12-11 22:01:00 +01:00
7e059e13ef fix(ui): Prevent the avatar image from flickering 2022-12-11 21:53:38 +01:00
d965fbd57e feat(service): Make the service more togglable 2022-12-10 23:08:24 +01:00
55854ec586 feat(ui): Handle contact info in the profile page 2022-12-10 22:46:42 +01:00
8886c8e695 fix(ui): Fix contact info not being retrieved 2022-12-10 22:13:22 +01:00
d58f5f9a01 feat(service): Make the contact integration configurable 2022-12-10 21:30:47 +01:00
e060b0f549 feat(service): First attempt at handling phone contacts 2022-12-10 19:34:11 +01:00
73913c4ae6 fix(ui): Delete icon was visible on both ends after dismiss 2022-12-10 12:12:03 +01:00
21878ae135 fix(service): Fix defaultMuteState not being honoured 2022-12-10 12:02:09 +01:00
a08a110ef6 feat(ui): Allow verifying our own devices 2022-12-10 11:52:15 +01:00
f723c43603 feat(service): Cache fingerprints for all JIDs 2022-12-09 21:17:39 +01:00
d88876c928 feat(service): Cache our own device fingerprints 2022-12-09 20:51:56 +01:00
f15a3e6bf4 fix(service): Crash when accesing our own devices 2022-12-09 18:43:51 +01:00
4852237bf8 feat(service): Implement verifying OMEMO devices 2022-12-09 18:34:24 +01:00
9a0bc87636 Merge pull request 'Message reactions' (#178) from feat/reactions into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/178
2022-12-09 14:50:55 +00:00
d73d27dccc fix(service): Fix senders being added multiple times to a reaction 2022-12-09 15:47:15 +01:00
6fa5e73226 feat(service): Handle messages with <no-store/> Message Processing Hints 2022-12-09 12:57:26 +01:00
1ff9ea256b fix(ui): Switch from Row to Wrap 2022-12-06 22:40:59 +01:00
7fca7e0246 fix(meta): Add a moxxmpp git override 2022-12-06 18:49:56 +01:00
846270b714 feat(ui): Translate the reaction button 2022-12-06 18:46:40 +01:00
50e7c5683f feat(service): Handle reactions from our own carbons 2022-12-06 15:45:31 +01:00
6883a9570f feat(ui): Swap around the emoji and the number of reactions 2022-12-06 14:10:44 +01:00
8f34bc001d docs(meta): Update DOAP 2022-12-06 14:09:54 +01:00
2f95e5452b fix(service): We don't need chat states for reactions 2022-12-06 13:57:02 +01:00
59a6307a21 feat(service): Send reactions 2022-12-06 13:52:42 +01:00
c8d52e6c41 feat(service): Implement receiving reactions 2022-12-06 13:23:17 +01:00
044766bf8a feat(ui): Build the UI for reactions 2022-12-06 12:19:21 +01:00
1f7c851228 feat(ui): Implement the basic UI for displaying reactions 2022-12-06 00:18:06 +01:00
ca90c658ff feat(ui): Make QR Codes more consistent 2022-12-05 20:19:03 +01:00
19de68e4f0 feat(ui): Make the QR scanner page more versatile 2022-12-04 18:45:38 +01:00
dc17d7d304 feat(ui): Implement scanning a contact's QR code 2022-12-04 18:31:24 +01:00
2372dbf6b3 fix(ui): Reset AddConversationPage's state when popping the route 2022-12-04 13:07:24 +01:00
3382e35447 fix(ui): Prevent clipping of the ink drop of buttons 2022-12-04 12:56:46 +01:00
a9cc4f55b8 fix(ui): Improve the look of the overview menu 2022-12-04 12:45:49 +01:00
63fbf7ebe4 fix(service): Auto-downloads are not marked as downloading (Fixes #167) 2022-12-04 12:04:44 +01:00
d1d6b67fd6 feat(ui): Replace 'custom' icon button with a FAB 2022-12-03 20:38:05 +01:00
87160e8648 fix(ui): Re-add JIDs to the newconversation page (Fixes #176) 2022-12-03 18:35:32 +01:00
e5553699c5 feat(ui): Show an indicator for the swipe gesture 2022-12-03 18:26:06 +01:00
adcfdc1a73 feat(ui,service): Implement sending audio messages 2022-12-03 17:55:23 +01:00
70464a2b71 feat(ui): Make cancelling a recording also possible via dragging 2022-12-03 13:24:38 +01:00
0852a75d9f fix(ui): The timer was opaque during hit testing when not recording 2022-12-03 13:11:48 +01:00
9affa0e89a feat(ui): Show a toast when the record button is tapped 2022-12-03 13:01:54 +01:00
cc13078ec5 feat(ui): Improve the touch behaviour of the overview menu 2022-12-03 12:48:34 +01:00
35285343b1 feat(ui): Add a timer while recording audio 2022-12-03 12:40:45 +01:00
cb6bce0c56 feat(ui): Implement the UI for audio recording 2022-12-02 23:05:06 +01:00
5c1eda72c3 fix(ui): Reimplement file sending 2022-12-02 20:30:03 +01:00
4542accc33 feat(ui): Remove the speeddial 2022-12-02 20:27:08 +01:00
8f5470076b feat(ui): Implement UI for audio messages during up/download 2022-12-02 16:55:02 +01:00
3427c3c761 feat(ui): Move the circle to the bottom bar 2022-11-30 20:50:51 +01:00
0cc8d0947b feat(ui): Don't generate video thumbnails for problematic formats 2022-11-30 19:19:54 +01:00
c3795450a9 feat(ui): Begin working on the recording UI 2022-11-30 19:08:42 +01:00
868d924836 fix(ui): Translate quoted message strings 2022-11-30 12:50:47 +01:00
d8f634d67c feat(ui): Hide the microphone and sticker buttons when quoting 2022-11-30 12:47:09 +01:00
09b97ab4c5 feat(ui): Make SharedFileWidget adhere to the other widget's style 2022-11-30 12:45:16 +01:00
8b4d7dd569 feat(ui): Make audio messages quotable 2022-11-30 12:38:16 +01:00
a63205d5e1 feat(ui): Implement shared media view for audio 2022-11-29 23:44:43 +01:00
e7a4e93366 fix(ui): Remove unused value 2022-11-29 23:38:45 +01:00
2c23f40415 feat(ui): Implement an audio widget 2022-11-29 23:36:09 +01:00
daf4ee79f2 fix(ui): Fix indentation 2022-11-27 13:01:00 +01:00
38a73d2890 feat(ui): Replace video spinners with a shimmer
Fixes #172.
2022-11-27 13:00:01 +01:00
b004d8364c docs(ui): Rename string 2022-11-26 22:42:13 +01:00
2498e23bd5 feat(ui): Generate video thumbnails 2022-11-26 22:40:42 +01:00
3a80d50cf5 docs(ui): Remove fixed TODO 2022-11-26 21:19:29 +01:00
8c12eb47ce feat(ui): Minor touch-ups for the 'about' page 2022-11-26 19:24:12 +01:00
cfec6afc7d feat(ui): Allow tapping the profile picture in a conversation 2022-11-26 19:04:13 +01:00
a3bdabca3c feat(ui): Allow tapping on the profile picture 2022-11-26 19:00:24 +01:00
f094a326ac feat(ui): Switch cropping libraries
This makes the avatar cropper much more consistent
with the background cropper. Fixes #168.
2022-11-26 18:40:46 +01:00
d24cab9c1a fix(i18n): Add missing translations 2022-11-26 18:09:42 +01:00
c7d1ecce35 feat(ui): Show whether the last received message was edited 2022-11-26 15:57:06 +01:00
306fd99b84 feat(service): Handle received corrections (Fixes #81) 2022-11-26 15:50:24 +01:00
ab63bc44a6 feat(service,ui): Allow correcting the last message 2022-11-26 15:17:33 +01:00
ef15f15458 feat(ui): Build the backbone for LMC support 2022-11-26 14:29:46 +01:00
db86136aa8 fix(service): Enable carbons (Fixes #171) 2022-11-26 13:56:24 +01:00
c344aed471 feat(ui): Encode backgrounds as JPEG (Fixes #94) 2022-11-26 13:47:09 +01:00
79b0a4ba7a feat(meta): Remove image_size_getter 2022-11-26 13:40:57 +01:00
4ce5e29a81 fix(ui): Use the CancelButton for the avatar cropper 2022-11-26 13:24:35 +01:00
524dec0991 fix(ui): Hide the media preview if the contact is typing 2022-11-26 12:12:36 +01:00
05074ed4f0 feat(ui): Replace the custom code with InteractiveViewer 2022-11-26 00:50:29 +01:00
4e4ed58605 feat(ui): Use the InteractiveViewer 2022-11-26 00:40:43 +01:00
6a109fe03d fix(service): Set a foreign key constraint on the conversations database 2022-11-25 21:36:41 +01:00
a783aab229 feat(ui): Show a media preview if it is thumbnailable 2022-11-25 20:35:43 +01:00
43dc2285b3 fix(service): Update conversation when a file is downloaded 2022-11-25 20:35:24 +01:00
eac8e3fb44 feat(ui): Show a media preview in the body line 2022-11-25 20:20:44 +01:00
035d29fabc fix(ui): Use the emoji + mime type combo for media messages 2022-11-25 20:01:54 +01:00
ad2b10972c docs(ui): Remove TODO 2022-11-25 18:11:05 +01:00
e65e1f3ec4 fix(service): Fix conversations not updating on delivery receipt 2022-11-25 18:08:28 +01:00
10b86812cd fix(service): Fix conversations not being properly loaded
Meanwhile, also remove the Completer system for preventing race
conditions with, hopefully, something better.
2022-11-25 17:58:09 +01:00
fe4c794f68 refactor(service): Conversations now point to the last message 2022-11-25 17:01:30 +01:00
6115d748e3 feat(ui): Show the last message state in the conversations list 2022-11-25 16:09:06 +01:00
b0f266bb0a fix(ui): Make the border shape const 2022-11-24 22:56:14 +01:00
646c99feb5 feat(ui): Allow scaling in the background cropper 2022-11-24 22:54:39 +01:00
e8461d7059 fix(ui): Round the dialog corners 2022-11-24 18:07:08 +01:00
b2efd9f22f fix(i18n): Fix typo 2022-11-24 18:04:17 +01:00
8e426b7fd6 feat(service): Tapping a message notification opens correct chat
Fixes #91.
2022-11-24 18:00:54 +01:00
c13a65b204 feat(service): Allow marking messages as read from the notification 2022-11-24 15:23:59 +01:00
503a24e003 feat(service): Send read marker when marking a chat as read 2022-11-24 15:02:52 +01:00
dfddd3d3d0 fix(shared): RetractMessageComment -> RetractMessageCommentCommand 2022-11-24 13:34:52 +01:00
26e01bb7f8 feat(ui): Implement marking a chat as read from the long-press menu 2022-11-24 13:32:32 +01:00
5f88626ddf feat(ui): Allow closing a chat from the long-press menu 2022-11-24 13:09:48 +01:00
e04bb29bb2 feat(ui): Allow copying a text message 2022-11-24 13:01:02 +01:00
c9690e028b fix(service): Fix null exception when closing a chat 2022-11-24 11:57:29 +01:00
8709f0bd8e refactor(ui): Move the overview code out of the chat bubble 2022-11-24 11:55:51 +01:00
13d7f33c37 refactor(ui): Move ChatBubble to OverviewMenuItem 2022-11-24 11:19:19 +01:00
eac8592536 feat(ui): Make conversations long-pressable 2022-11-23 23:16:05 +01:00
3e0feaa3e8 feat(ui): Allow quoting via the long-press menu 2022-11-23 22:16:07 +01:00
85023299d2 feat(service): Migrate to awesome_notifications
Fixes #90.
Fixes #96.
Preparation for #91.
2022-11-23 21:51:47 +01:00
98cfb79961 feat(meta): Update Flutter to 3.3.8 2022-11-23 16:41:59 +01:00
f628ec908c docs(service): Add TODO 2022-11-23 12:12:23 +01:00
6a166a6ef7 feat(service): Notifications are now more messaging like 2022-11-23 12:10:57 +01:00
b10f7dd888 feat(i18n): Translate OMEMO errors (Fixes #162) 2022-11-22 23:14:05 +01:00
0e768e7768 feat(service): Figure out the correct error type (Fixes #56) 2022-11-22 22:54:03 +01:00
d59a37f185 feat(service): React to messages of type error 2022-11-22 22:21:05 +01:00
f0c6713d76 refactor(shared): isWarning and isError should just be properties 2022-11-22 22:04:05 +01:00
0eeac949ea feat(ui): Show errors 2022-11-22 21:58:55 +01:00
04c0c1c0e2 feat(ui): Show filesize for media (Fixes #129) 2022-11-22 20:39:28 +01:00
03011d107f feat(service): Delete file on retraction
Fixes #163.
2022-11-22 20:27:22 +01:00
c88003bea1 feat(service): Remove shared media entry on message retraction
Also: Fix some ordering oddities with the shared media widget and page.
2022-11-22 19:51:28 +01:00
6c83373d72 fix(ui): Make the BorderRadius const 2022-11-22 18:53:28 +01:00
888a1cf296 fix(ui): Maybe fix the shared media display not updating 2022-11-22 18:51:56 +01:00
ed6c01243d feat(service): Link shared media to their message (Fixes #152) 2022-11-22 18:45:48 +01:00
5945f78e97 feat(ui): Round the corners of the QR code 2022-11-21 22:31:47 +01:00
c58fca395b fix(ui): Popup messages have corners 2022-11-21 22:23:58 +01:00
9203905ed8 feat(service): Remove thumbnail data on retraction 2022-11-21 21:05:08 +01:00
259e0dacd0 feat(ui): Remove the QR code's dialog 2022-11-21 18:46:47 +01:00
0acd13f0f0 feat(ui): Use curves for bettwe animations 2022-11-21 18:37:14 +01:00
cfdb948372 feat(ui): Make the longpress vibration stronger 2022-11-21 18:24:03 +01:00
9b3130f363 feat(ui): Animate a transition between chat state and no chat state 2022-11-21 18:19:58 +01:00
d10efc274c fix(service): Every download is marked as verification failed 2022-11-21 17:52:02 +01:00
2e1b7fab53 Merge pull request 'Implement message retraction' (#161) from feat/message_retraction into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/161
2022-11-21 16:28:42 +00:00
91b92b2cc4 fix(ui): Make colors more readable 2022-11-21 17:23:46 +01:00
6e3ab111f3 feat(service): Set isMedia to false when retracting 2022-11-21 17:23:32 +01:00
7de0b1c00e fix(meta): Bump moxxmpp 2022-11-21 16:08:18 +01:00
8107743af2 fix(ui): Retract messages had no padding 2022-11-21 16:02:08 +01:00
aeff82f625 fix(service): Fix images being retracted 2022-11-21 15:48:32 +01:00
935cb1c38b feat(ui): Render all retracted messages as a text message 2022-11-21 14:05:24 +01:00
acd5b7706b fix(ui): Make showConfirmationDialog more like Flutter 2022-11-21 12:40:54 +01:00
0b111d1012 feat(ui): Indicate retracted messages in the overview 2022-11-21 12:22:44 +01:00
211d8f37d8 feat(service): Store if a last message is retracted 2022-11-21 11:59:17 +01:00
e2517a7786 feat(ui): Show a 'show warning' button 2022-11-21 11:13:57 +01:00
aaf5b4fecc refactor(ui): Move some functions into the message model 2022-11-21 10:56:47 +01:00
173e251e9f fix(ui): Images cannot be long pressed 2022-11-21 10:45:04 +01:00
3c0891e069 feat(meta): Update DOAP 2022-11-20 23:44:16 +01:00
2dc3de43d1 fix(ui): Only allow sending retractions on our own messages 2022-11-20 23:41:55 +01:00
1b332b5b3b feat(service): Tell the service to send the retraction 2022-11-20 23:40:22 +01:00
6ef34afc6d fix(i18n): Translate the new strings 2022-11-20 22:52:49 +01:00
1cd6b63f1e feat(ui): Implement a message selection menu 2022-11-20 22:33:18 +01:00
6fd17ee70e fix(ui): Prevent quoting retracted messages 2022-11-20 17:45:27 +01:00
050d151c67 fix(service): Ensure only the original sender can retract a message 2022-11-20 17:38:37 +01:00
25ec569cd8 fix(i18n): Translate the retracted message 2022-11-20 17:33:34 +01:00
2dd9847566 feat(service): Handle message retraction 2022-11-20 17:30:32 +01:00
ef108f2e4a Merge pull request 'Make Moxxy translatable' (#158) from feat/i18n into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/158
2022-11-20 11:56:06 +00:00
4894c2d627 feat(i18n): Give each translation a name 2022-11-20 12:52:57 +01:00
f0861a9a62 fix(service): Store the system's default language in the service 2022-11-20 12:07:44 +01:00
221cf89d10 feat(service): Tell the foreground service the systems default locale 2022-11-20 00:11:26 +01:00
c54ef9b84a feat(meta): Make the default language configurable 2022-11-19 23:56:38 +01:00
7aad272316 feat(service): Make login errors translatable 2022-11-19 22:54:14 +01:00
1f43353360 fix(service): Adjust to moxxmpp changes 2022-11-19 22:40:20 +01:00
5b54566c89 feat(shared): Make the message emoji messages translatable 2022-11-18 22:41:22 +01:00
a9e3d331fc feat(service): Make service strings translatable 2022-11-18 22:29:47 +01:00
192086546a feat(ui): Translate the entire UI 2022-11-17 23:25:05 +01:00
a30b8f888d feat(ui): Localise more pages 2022-11-17 21:33:00 +01:00
1e3fc9be3d feat(ui): Localise the conversations page 2022-11-17 20:23:34 +01:00
2ec08c7f68 refactor(ui): Use super parameters 2022-11-17 20:10:37 +01:00
3adf9b0d00 feat(ui): Use slang for the intro page 2022-11-17 18:13:29 +01:00
6fba0f28db test(service): Integration test MoxxyReconnectionPolicy 2022-11-16 22:58:25 +01:00
9bdc2c09e5 fix(ui): Open URL externally (fixes #155) 2022-11-16 20:27:38 +01:00
1eb95cde92 fix(ui): Make switches easier to visually decipher
Fixes #153.
2022-11-16 19:11:23 +01:00
719e793860 fix(xmpp): Bump moxxmpp to fix SCRAM-SHA-{256,512}
Turns out that the PBKDF was incorrectly configured
for hashes other than SHA-1.
2022-11-16 15:40:10 +01:00
f0e68f7b48 fix(service): Fix crash if a sign out happened 2022-11-16 14:28:56 +01:00
72501bd0b3 feat(ui,service): Only enable debug logging if required
So, either it's enabled or the app is a debug build.
2022-11-16 14:27:58 +01:00
bd6aaa07c8 feat(service): Replace the Migrator system with the database 2022-11-15 13:16:32 +01:00
77a9f81a1d fix(ui): Add a fallback error text 2022-11-14 21:10:24 +01:00
3eb88f66cf fix(service): Fix crash if login failed with no reason 2022-11-14 21:05:12 +01:00
3fb76d59b2 feat(meta): Release 0.3.0 2022-11-13 12:24:08 +01:00
72db2863d0 feat(meta): Bump moxxmpp 2022-11-12 21:52:04 +01:00
81ad0cf4db fix(meta): Remove unused dependencies 2022-11-12 21:04:27 +01:00
b6f2a89e04 fix(xmpp): Don't deadlock on TLS issues 2022-11-12 21:02:19 +01:00
e2c735b804 fix(meta): Remove ANDROID_* from the Flake 2022-11-12 20:13:36 +01:00
3f576ce3e5 docs(ui): Add TODO 2022-11-12 18:51:01 +01:00
1e51e8bb8b fix(ui): Always center the conversation title
Fixes #72.
2022-11-12 18:47:55 +01:00
ad48191b53 fix(ui): Make the UI more consistent colorwise
Fixes #34.
Fixes #144.

This commit makes more UI elements use the primary color. Also
adds an enabled state to the RoundedButton.
2022-11-12 17:54:54 +01:00
d851f302cc feat(service,ui): Successful login sends a PreStartDoneEvent
Closes #102.
2022-11-12 16:36:30 +01:00
09ba2122e7 fix(service): Fix loading the wrong quote from the database 2022-11-12 14:12:15 +01:00
9b16bf6e6f fix(service): File Upload Notification replacements should now be acked
Fixes #111.
2022-11-12 13:40:42 +01:00
ab6b5eefc0 fix(ui): Prevent quoting File Upload Notifications 2022-11-12 13:23:33 +01:00
993cd5ed1c refactor(ui): Remove ThumbnailService
Closes #31.
2022-11-12 13:02:47 +01:00
b733bb4154 fix(service): Set the caphash node 2022-11-12 12:53:10 +01:00
2edbbc3ede feat(meta): Bump moxxmpp and moxxmpp_socket_tcp 2022-11-12 12:52:20 +01:00
af3013ad67 refactor(service): Move manager overrides into moxxmpp/ 2022-11-09 16:43:44 +01:00
d02fe73952 refactor(meta): Migrate to using moxxmpp
Fixes #139.
2022-11-09 16:41:38 +01:00
32444d5a7e fix(ui): Error messages now cannot be quoted
Fixes #142.
2022-11-09 11:56:04 +01:00
b003b5e04b fix(ui): Make message draggable only in one direction
Fixes #118.
2022-11-09 11:47:55 +01:00
5d217a264a fix(shared): Fix smaller style issues 2022-11-09 11:38:08 +01:00
8d8c4d2da3 Merge pull request 'Add fallback body to quotes' (#134) from millesimus/moxxyv2:fix_quote_fallback into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/134
2022-11-09 11:34:13 +01:00
Millesimus
943a03bd2c feat(service): Return readable file sizes in quote fallback body.
Following Conversations' file-size-to-string logic.
2022-11-07 16:29:56 +01:00
Millesimus
680303cfa2 feat(service): Save media size of downloaded and uploaded files. 2022-11-07 16:29:03 +01:00
Millesimus
e16bbf8acf fix(xmpp+service): Enrich fallback message for quoted media…
…with srcUrl, messageEmojis and messageSizes. Fixes moxxy#128.
2022-11-07 16:29:01 +01:00
42ebbdba6d Merge pull request 'Implement OMEMO' (#126) from feat/omemo into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/126
2022-11-04 21:53:43 +01:00
a2b477a3dc fix(ui): Add missing comma 2022-11-04 21:52:06 +01:00
3c7d5ad5ad feat(ui): Show a spinner while we are regenerating the device 2022-11-04 21:52:06 +01:00
4261e26f19 feat(ui): Update our own device fingerprint after regeneration 2022-11-04 21:52:06 +01:00
a0ee4e1312 feat(xmpp): Add an event for receiving a device list update 2022-11-04 21:52:06 +01:00
93630650fc fix(xmpp): Sessions are not built for new Omemo devices 2022-11-04 21:52:06 +01:00
36b20fa2dd fix(xmpp): Wrong return for queued disco request 2022-11-04 21:52:06 +01:00
ae1c4dd3e6 fix(xmpp): Remove unneeded critical section exit 2022-11-04 21:52:06 +01:00
69438f44b3 feat(service): Notify if we could not publish the Omemo device 2022-11-04 21:52:06 +01:00
aceaa01cdb fix(xmpp): Disco#info requests stall after one fails 2022-11-04 21:52:06 +01:00
7fe220a630 feat(ui): Show which chats are encrypted 2022-11-04 21:52:06 +01:00
f07599adf2 feat(ui): Warn when sending a file to multiple chats where >= 1 is unencrypted 2022-11-04 21:52:06 +01:00
1b89b16705 fix(xmpp): Awaiting a stanza does not return the transformed received stanza 2022-11-04 21:52:06 +01:00
0fb1148508 fix(meta): Ignore .android 2022-11-04 21:52:06 +01:00
208145d288 fix(xmpp): I think ratchet acking should work better now 2022-11-04 21:52:06 +01:00
a6bd60077d fix(xmpp): Fix outgoing presence being wrongly processed 2022-11-04 21:52:06 +01:00
387c20a708 fix(xmpp): Fix encrypting direct Presence and IQs 2022-11-04 21:52:06 +01:00
1a9d34d347 fix(service): Fix dynamic access 2022-11-04 21:52:06 +01:00
ed4ee53fdb fix(tests): Fix tests 2022-11-04 21:52:06 +01:00
4848a13fa0 fix(xmpp): Remove the sendRawXml method of Managers' attributes 2022-11-04 21:52:06 +01:00
b9ebd506c8 chore(ui): Refactor 'Key' to 'Device' 2022-11-04 21:52:06 +01:00
249f49f7b3 feat(xmpp): Enable OMEMO for IQs and Presence Stanzas as well 2022-11-04 21:52:06 +01:00
db3f5eb066 feat(service): Perform (most) file hashing operations on the native side 2022-11-04 21:52:06 +01:00
c2f43e0096 chore(meta): Remove cryptography_flutter 2022-11-04 21:52:06 +01:00
d81586d026 feat(service): Massively speed up BlurHash calculation 2022-11-04 21:52:06 +01:00
18a9419cef fix(service): Make HttpFileTransferService update messages on error 2022-11-04 21:52:06 +01:00
8a69083c19 feat(service): Improve encryption and decryption speed 2022-11-04 21:52:06 +01:00
ba1b79f657 test(xmpp): Test hash parsing for SFS 2022-11-04 21:52:06 +01:00
1a2415925f feat(xmpp): Attempt PubSub subscription only once per session 2022-11-04 21:52:06 +01:00
16a597183a feat(xmpp): Log stanzas in their decrypted forms 2022-11-04 21:52:06 +01:00
2461430869 fix(xmpp): Tell the UI about the message change 2022-11-04 21:52:06 +01:00
605201dbc8 feat(xmpp): Try to keep our device list up-to-date 2022-11-04 21:52:06 +01:00
214d3250fe feat(service): Mark message if the chat is encrypted while the file isn't 2022-11-04 21:52:06 +01:00
ea9c634a25 fix(xmpp): Fix a small logic bug in _findNewSessions 2022-11-04 21:52:06 +01:00
62095cb170 feat(service): Move isUploading and isDownloading to the database 2022-11-04 21:52:06 +01:00
aba85c70c1 feat(service): Remove support for SIMS 2022-11-04 21:52:06 +01:00
98569cff69 feat(service): Pull the filename from SFS, if given 2022-11-04 21:52:06 +01:00
952dfdc521 feat(meta): Make gitlint rules nicer 2022-11-04 21:52:06 +01:00
93724802d8 feat(xmpp): Bail early on encryption if the stanza contains PubSub 2022-11-04 21:52:06 +01:00
e5f19e3b8b feat(xmpp): Make SM's setState async 2022-11-04 21:52:06 +01:00
4479506ee0 fix(xmpp): Fix the Stream Management not counting awaited stanzas 2022-11-04 21:52:06 +01:00
359d4508b1 test(xmpp): Write test to ensure early-ended stanzas are counted 2022-11-04 21:52:06 +01:00
ef9ba68790 fix(xmpp): Make SM counting run at the very beginning or end 2022-11-04 21:52:06 +01:00
53ce0d9e54 chore(xmpp): Track kex timestamp to prevent performing old kex 2022-11-04 21:52:06 +01:00
505921045e fix(xmpp): Fix receiving kex again breaking the ratchets 2022-11-04 21:52:06 +01:00
2c71e01e5a fix(xmpp): Do not try to decrypt if the stanza has no from 2022-11-04 21:52:06 +01:00
c3cf84ee7d fix(service): Clear session related tables when regenerating the device 2022-11-04 21:52:06 +01:00
5787a8943d feat(xmpp): Ignore PubSub elements 2022-11-04 21:52:06 +01:00
313e276ad6 feat(service): Implement regenerating one's device 2022-11-04 21:52:06 +01:00
310891bf16 feat(ui): Add more confirmation prompts 2022-11-04 21:52:06 +01:00
8852356966 feat(xmpp): Work around ejabberd not accepting max in publish options 2022-11-04 21:52:06 +01:00
6243766ecc fix(ui): Fix not popping the dialog 2022-11-04 21:52:06 +01:00
2d5e987fcc feat(service): Implement deleting devices
- Fix accidentally deleting the entire bundles node instead of just
  retracting the single item.
- ASK IF THE USER WANTED TO DO THIS
- Fix checking for the wrong result type
2022-11-04 21:52:06 +01:00
bf851c2bd6 fix(service): For now, revert the multiple PK situation 2022-11-04 21:52:06 +01:00
0327f254a2 feat(ui): Display warnings for messages 2022-11-04 21:52:06 +01:00
a0c7078593 feat(service): Verify given file hashes 2022-11-04 21:52:06 +01:00
0cbea9607e feat(xmpp): Communicate encryption errors due to missing devices 2022-11-04 21:52:06 +01:00
e799d516ea feat(xmpp): The send cancelled event now carries the handler data 2022-11-04 21:52:06 +01:00
c630d8f091 fix(xmpp): Only add EME for messages 2022-11-04 21:52:06 +01:00
2494fbb837 fix(ui): Fix title of the contact devices page 2022-11-04 21:52:06 +01:00
cb2560d46f fix(xmpp): Clean the publish method 2022-11-04 21:52:06 +01:00
240ed5f859 feat(ui): Add warning to enabling OMEMO by default 2022-11-04 21:52:06 +01:00
0365730e0e chore(xmpp): Move namespace into namespaces.dart 2022-11-04 21:52:06 +01:00
b9d5eab3ea chore(xmpp): Move xep_0060.dart into its folder 2022-11-04 21:52:06 +01:00
16c84d59dc fix(service): Make more message attributes primary keys 2022-11-04 21:52:06 +01:00
621c396407 feat(service): Handle file decryption errors 2022-11-04 21:52:06 +01:00
3f2cc3d97a feat(xmpp): When sending is cancelled, return an error stanza
This will help once we try to encrypt IQ stanzas.
2022-11-04 21:52:06 +01:00
ce927308c4 feat(xmpp): Allow stanza handlers to cancel sending 2022-11-04 21:52:06 +01:00
df99eb0aab chore(service): Add TODO 2022-11-04 21:52:06 +01:00
e8473d4f5b chore(service): Refactor the MediaFileLocation 2022-11-04 21:52:06 +01:00
1175d77c55 feat(service): Generate the OMEMO device in the background 2022-11-04 21:52:06 +01:00
570f4ca7d9 chore(service): Clean the cryptography service 2022-11-04 21:52:06 +01:00
e4b9c8f1bc feat(service): Generate a plaintext hash in all cases 2022-11-04 21:52:06 +01:00
ea6e7c5d8c feat(service): Hash the file before sending the metadata 2022-11-04 21:52:06 +01:00
a462945c98 feat(service): Perform encryption and decryption off-thread 2022-11-04 21:52:06 +01:00
003d4d65e5 chore(meta): Bump omemo_dart 2022-11-04 21:52:06 +01:00
38fa5ab991 fix(service): Fix the result of file encryption 2022-11-04 21:52:06 +01:00
e22e7b9c90 fix(xmpp): Fix not parsing ESFS 2022-11-04 21:52:06 +01:00
fc6a8eae9d fix(service): Fix trust device list not loaded 2022-11-04 21:52:06 +01:00
4dab811388 fix(service): Fix file not encrypted before upload 2022-11-04 21:52:06 +01:00
012dc5ec69 feat(xmpp): Collection commit
- Fix Db issue when saving the trust device list
- Prevent constantly requesting our own device bundles if it's just our
  one device.
- Encrypt uploads and decrypt downloads
2022-11-04 21:52:06 +01:00
f72f67342d feat(service): Implement a service for file cryptography 2022-11-04 21:52:06 +01:00
283ac315d8 feat(service): Also store the used encryption mechanism 2022-11-04 21:52:06 +01:00
1a5b0f372d chore(meta): Update DOAP 2022-11-04 21:52:06 +01:00
de40e859d7 feat(xmpp): Implement XEP-0448 2022-11-04 21:52:06 +01:00
6140de8eea feat(ui): Add (untested) support for recreating own sessions 2022-11-04 21:52:06 +01:00
a9fcbd7909 feat(xmpp): Add Message Processing Hints to OMEMO messages 2022-11-04 21:52:06 +01:00
a963153c2a feat(service): Use cryptography_flutter for possible speedup 2022-11-04 21:52:06 +01:00
8efb743b84 feat(ui): Implement (untested) device deletion 2022-11-04 21:52:06 +01:00
f251d6b97b feat(xmpp): Implement PubSub item delete 2022-11-04 21:52:06 +01:00
8a5a96d02c feat(ui): Implement enabling and disabling one's own sessions 2022-11-04 21:52:06 +01:00
fc0dd14b4d feat(ui): Display key controls only on keys we have a session with 2022-11-04 21:52:06 +01:00
4d67c157f0 feat(service): Implement getting one's own device fingerprints 2022-11-04 21:52:06 +01:00
a678ef70e7 fix(ui): isEmpty -> isNotEmpty 2022-11-04 21:52:06 +01:00
87d320b6da feat(ui): Implement viewing the device's fingerprint 2022-11-04 21:52:06 +01:00
6b7d3c4b7c chore(ui): Move the fingerprint widget into its own file 2022-11-04 21:52:06 +01:00
8a99f8f6b1 feat(service): Remove the need for secure storage in OmemoService 2022-11-04 21:52:06 +01:00
4aa24cc0a1 chore(service): Move shouldEncrypt check to ConversationService 2022-11-04 21:52:06 +01:00
e309f3bbd0 fix(ui): Make the fingerprint display font size better 2022-11-04 21:52:06 +01:00
a757c45b84 fix(ui): Make the message list react to changes in encryption 2022-11-04 21:52:06 +01:00
ed397e352f chore(xmpp): Add TODO 2022-11-04 21:52:06 +01:00
cf6cec4d32 fix(xmpp): Fix not encrypting messages 2022-11-04 21:52:06 +01:00
c4422355e3 feat(xmpp): Make Omemo optional 2022-11-04 21:52:06 +01:00
dd947ecd39 feat(service): Improve initializing the OmemoManager 2022-11-04 21:52:06 +01:00
968b59aaee fix(service): Ensure OmemoService is safe against late initialization 2022-11-04 21:52:06 +01:00
b9cb023306 chore(ui): Add TODO 2022-11-04 21:52:06 +01:00
031ef140f3 feat(ui): Color bubbles red if they are unencrypted when they should not 2022-11-04 21:52:06 +01:00
7376607475 feat(ui): Implement enabling and disabling Omemo (UI Only) 2022-11-04 21:52:06 +01:00
1aea6ee588 feat(ui): Make enabling Omemo by default configurable 2022-11-04 21:52:06 +01:00
12717ba25e feat(service): Store the encryption status of conversations in the DB 2022-11-04 21:52:03 +01:00
a1fa666cd3 chore(meta): Finally make gitlint rules prettier 2022-11-04 21:51:18 +01:00
30cfd67e28 xmpp: Encrypt to self 2022-11-04 21:51:18 +01:00
068d156da3 xmpp: Attempt to ignore our own device ratchet 2022-11-04 21:51:18 +01:00
ce4ed9b0a9 xmpp: Implement the race condition detection 2022-11-04 21:51:18 +01:00
b8acbe7359 ui: Fix issues with const 2022-11-04 21:51:18 +01:00
c61485638b ui: Prevent session rebuilding if there are no sessions 2022-11-04 21:51:18 +01:00
fd20d5177d xmpp: Move Disco classes into their own file 2022-11-04 21:51:18 +01:00
2a603e1e41 xmpp: Migrate disco to Resultv2 2022-11-04 21:51:18 +01:00
e4d71c5a39 xmpp: Migrate discoItemsQuery to Resultsv2 2022-11-04 21:51:18 +01:00
842a6ebe16 meta: Fix style issues 2022-11-04 21:51:18 +01:00
14ecc63944 xmpp: Add documentation to member functions of the Omemo manager 2022-11-04 21:51:18 +01:00
18fb728973 service: Send an empty OMEMO message on session recreation 2022-11-04 21:51:18 +01:00
a11b75f1cb xmpp: (Hopefully) fix session resetting not working 2022-11-04 21:51:18 +01:00
86abadd6bb xmpp: Fix not adding new bundles 2022-11-04 21:51:18 +01:00
ab47b06fd6 tmp: Migrate OMEMO to Resultsv2 api 2022-11-04 21:51:18 +01:00
640ffcb77e meta: Update omemo_dart to 0.3.0 2022-11-04 21:51:18 +01:00
26b6abe66b ui: Make bubble icons smaller 2022-11-04 21:51:18 +01:00
c00df84f2a xmpp: Unholy fix for errors on publish 2022-11-04 21:51:18 +01:00
2b7b7a10bc ui: Fix every sent message having an error 2022-11-04 21:51:18 +01:00
5b18b3d50d ui: Fix messages having no text 2022-11-04 21:51:18 +01:00
be24afc8bf xmpp: Fix bug with invalid affix elements 2022-11-04 21:51:18 +01:00
5332572b2e ui: Display decryption errors 2022-11-04 21:51:18 +01:00
6551fda493 service: Restore the trust manager 2022-11-04 21:51:18 +01:00
e3d33f201c service: Implement enabling and disabling keys 2022-11-04 21:51:18 +01:00
ea3d550f64 xmpp: Implement Explicit Message Encryption 2022-11-04 21:51:18 +01:00
21c1632233 xmpp: Don't try to decrypt unencrypted stanzas 2022-11-04 21:51:18 +01:00
1a66cadb53 style: Fix minor style issues 2022-11-04 21:51:18 +01:00
c8b1330244 xmpp: Also fail affix checks if the afix elements are missing 2022-11-04 21:51:18 +01:00
4c5204598e xmpp: Make the decision which elements to encrypt an implementation issue 2022-11-04 21:51:18 +01:00
b8aedc842e xmpp: Move conversion functions into the helpers file 2022-11-04 21:51:18 +01:00
c1579cb106 service: Commit the device map and handle device changes 2022-11-04 21:51:18 +01:00
c1ff949346 ui: Implement listing a Jid's Omemo fingerprints 2022-11-04 21:51:18 +01:00
3eea6c2ff9 xmpp: Clean the OMEMO implementation a bit 2022-11-04 21:51:18 +01:00
d1f826bdb5 xmpp: Generate affix elements 2022-11-04 21:51:18 +01:00
5586fcff7a refactor: Move the OMEMO implementation 2022-11-04 21:51:18 +01:00
f98b18affc xmpp: Move envelope creation into _encryptChildren 2022-11-04 21:51:18 +01:00
28135244c3 xmpp: I SENT TWO MESSAGES BETWEEN TWO MOXXY INSTANCES 2022-11-04 21:51:18 +01:00
47533c7512 xmpp: Communicate decryption errors 2022-11-04 21:51:18 +01:00
f79f35e2be meta: Update DOAP 2022-11-04 21:51:18 +01:00
c43c4a9b24 service: Mark only encrypted messages as encrypted (receive only) 2022-11-04 21:51:18 +01:00
81a47a12ec ui: Display encrypted messages as encrypted 2022-11-04 21:51:18 +01:00
9e3a0a0f1d xmpp: RECEIVE THE FIRST OMEMO MESSAGE! 2022-11-04 21:51:18 +01:00
2d0426c0a3 xmpp: Fix PubSub issues
- Setting max_items=max sets max_items=#items + 1 if it is not supported
- Publish options were not working
2022-11-04 21:51:18 +01:00
7f366d3f3c service: Publish OMEMO bundle after connecting 2022-11-04 21:51:18 +01:00
3dd1e0461c service: Initialize the OMEMO service 2022-11-04 21:51:18 +01:00
8036a3a5be xmpp: Implement publishing a bundle 2022-11-04 21:51:18 +01:00
29a692de5f xmpp: Implement retrieving the device bundle 2022-11-04 21:51:18 +01:00
4f515d4733 xmpp: Start working on OMEMO 2022-11-04 21:51:18 +01:00
2c28e95bd9 ui: Change the icon and wording for the keys page 2022-11-04 21:51:18 +01:00
e7d354d4c7 meta: Pull in omemo_dart 2022-11-04 21:51:18 +01:00
5b64506612 ui: Add comment about OmemoKey usage 2022-11-04 21:51:18 +01:00
f2135081ef ui: Make dialogs prettier 2022-11-04 21:51:18 +01:00
ae9b1a8215 ui: Stub the scanning button 2022-11-04 21:51:18 +01:00
0f5b3d62b1 ui: Stub out the OMEMO key page 2022-11-04 21:51:18 +01:00
cf63408023 service: Add missing boolToInt 2022-09-09 17:32:09 +02:00
2bfcd52c8e service: Store the media dimensions in a less stupid way 2022-09-08 21:28:00 +02:00
95075c1664 android: Declare external storage permissions 2022-09-08 20:45:35 +02:00
55e639a61c meta: Remove the iOS folder as the iOS 'support' is non-existent 2022-09-08 20:20:46 +02:00
fb414c4764 xmpp: Replace MessageAckedEvent with StanzaAckedEvent 2022-09-08 20:12:17 +02:00
2a53238cad xmpp: Remove the transmitted attribute of stanza sending 2022-09-08 19:39:50 +02:00
59dfb606ad xmpp: Ignore socket closures at the socket level 2022-09-08 19:34:27 +02:00
e1271419eb service: Fix message acking not sending an event 2022-09-08 14:45:16 +02:00
a6faa103b6 service: Check the roster item when creating a new chat 2022-09-08 14:19:00 +02:00
72de6a4fb5 Merge pull request 'Replace isar with sqflite' (#124) from rework/sqflite into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/124
2022-09-08 14:17:36 +02:00
5c7de2406a meta: Remove isar dependency 2022-09-08 14:04:04 +02:00
a6ccc2cc24 service: Set a database password 2022-09-08 13:55:21 +02:00
7a132d96ca shared: Move preferences.dart into models/ 2022-09-08 13:39:02 +02:00
1e2fcf98c7 service: Fix SQL issue 2022-09-08 13:29:39 +02:00
8fc3df0672 service: Migrate preferences to database 2022-09-08 13:25:31 +02:00
930370c1b5 service: Prepare storing preferences in the database 2022-09-08 13:02:40 +02:00
25677c028e service: Fix shared media 2022-09-08 12:27:17 +02:00
6444e9f1d5 ui: Fix text padding if message is a bit longer 2022-09-08 11:59:32 +02:00
94f9474e2a service: Deduplicate some code 2022-09-08 11:59:19 +02:00
392606e165 service: Fix quotes 2022-09-07 23:15:24 +02:00
f8950d9fb3 service: Migrate the basics to sqflite 2022-09-07 22:10:55 +02:00
6ab4b4062c service: Create the tables 2022-09-07 13:01:50 +02:00
31464e5025 service: Move the database service somewhere else 2022-09-07 12:31:30 +02:00
1f166cbc44 Merge pull request 'Hopefully fix background cropper issues' (#122) from debug/image-cropper-issues into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/122
2022-09-07 00:37:51 +02:00
0d8bf8dd12 ui: Move chat background into chat 2022-09-07 00:29:30 +02:00
fdbf2534b7 ui: Remove the debug print statements 2022-09-07 00:09:57 +02:00
8fd69c4d5a ui: Maybe fix the crash issue 2022-09-06 23:57:08 +02:00
94c8224cf8 meta: We don't really care about the version of meta 2022-09-06 14:33:34 +02:00
7bcccd5558 meta: Update moxplatform 2022-09-06 14:32:05 +02:00
bb987ed257 Merge pull request 'Allow muting conversations' (#121) from feat/mute-conversations into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/121
2022-09-06 13:22:58 +02:00
80f4104969 ui: Comment out the subscription button 2022-09-06 13:13:31 +02:00
78ecd57aee ui: Color the tiles according to the system theme 2022-09-06 12:31:33 +02:00
da66c3fc17 ui: Move account settings down 2022-09-06 12:25:20 +02:00
94b75f0eee ui: Add settings page for chat settings 2022-09-06 12:24:51 +02:00
a798b72515 service: Make the default mute state configurable 2022-09-06 12:17:50 +02:00
8bd7ef6cfe ui: Set the tooltip text conditionally 2022-09-06 12:12:26 +02:00
d66a6790f9 service: Don't create a notification if the chat is muted 2022-09-06 12:12:08 +02:00
50f488ae21 ui: Implement muting chats 2022-09-06 12:06:54 +02:00
2a18715f77 xmpp: Update File Metadata Element to 0.2.0 2022-09-05 20:50:22 +02:00
dd39fea3f8 ui: Maybe fix crash when loading a background image 2022-09-05 20:31:42 +02:00
650fcc9215 ui: Replace server info bottom sheet with a new page 2022-09-05 20:06:16 +02:00
c52bdbecf8 service: Make the entrypoint a VM entrypoint
Should help with Flutter optimizing the function away.
Based on
6c353dd4fc.
2022-08-31 12:54:02 +02:00
9171f42d09 android: Fix wakelock issue by updating moxplatform 2022-08-31 12:53:18 +02:00
82885b2bde ui: Fix the TextField having no border 2022-08-30 12:03:05 +02:00
805a701daa service: Use native_imaging to generate blurhash thumbnail
Closes #109.

This should be much faster than before but required a bump
of isar, which means that you need to delete all data of
Moxxy before running it again. This is, however, not that
big a deal as I'm going to replace isar with sqlite
soon(tm) anyway.
2022-08-30 11:52:53 +02:00
691a1efe16 meta: Auto-accepting subscription requests should not be the default 2022-08-29 21:53:10 +02:00
4f39c995e0 docs: Make screenshots smaller 2022-08-29 21:46:52 +02:00
7c5a9bccec docs: Update README screenshots 2022-08-29 21:46:11 +02:00
b8ccbd9722 ui: Fix emoji picker not working if the TextField is not focused 2022-08-29 21:38:17 +02:00
3c8d942c67 ui: Make the scroll to bottom button animated 2022-08-29 21:30:44 +02:00
9ef0b851a9 ui: Fix quotes images looking weird 2022-08-29 21:01:22 +02:00
630da4a9e9 ui: Reset the scroll to bottom button on conversation request 2022-08-29 20:53:22 +02:00
df4bf316fb service: Fix sending File Upload Notifications 2022-08-29 20:49:50 +02:00
dc1a17677e docs: Update IzzyOnDroid link 2022-08-29 20:15:07 +02:00
405 changed files with 17006 additions and 15068 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
ko_fi: papatutuwawa

4
.gitignore vendored
View File

@@ -52,7 +52,11 @@ app.*.map.json
**/*.g.dart
**/*.freezed.dart
**/*.moxxy.dart
lib/i18n/*.dart
# Direnv
.envrc
.direnv/
# Android artifacts
.android

View File

@@ -7,7 +7,7 @@ line-length=72
[title-trailing-punctuation]
[title-hard-tab]
[title-match-regex]
regex=^(ui,service|service,xmpp|feat|test|refactor|xmpp|service|redux|ui|lint|style|docs|build|misc|flake|shared|meta|android|ios|release):.*$
regex=^(feat|fix|chore|refactor|docs|release|test)\((xmpp|service|ui|shared|meta|tests|i18n)+(,(xmpp|service|ui|shared|meta|tests|i18n))*\): .*$
[body-trailing-whitespace]

View File

@@ -4,11 +4,12 @@ An experimental XMPP client that tries to be as easy, modern and beautiful as po
The code is also available on [codeberg](https://codeberg.org/moxxy/moxxyv2).
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" alt="Get it on IzzyOnDroid" height="80" />](https://apt.izzysoft.de/fdroid/index/apk/me.polynom.moxxyv2)
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" alt="Get it on IzzyOnDroid" height="80" />](https://apt.izzysoft.de/fdroid/index/apk/org.moxxy.moxxyv2)
## Screenshots
![screenshots](./assets/repo/title.png)
[<img src="https://codeberg.org/moxxy/moxxyv2/raw/branch/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" width="20%"></img>](./fastlane/metadata/android/en-US/images/phoneScreenshots/1.png)
[<img src="https://codeberg.org/moxxy/moxxyv2/raw/branch/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" width="20%"></img>](./fastlane/metadata/android/en-US/images/phoneScreenshots/2.png)
## Developing and Building
@@ -16,7 +17,8 @@ Clone using `git clone --recursive https://github.com/Polynomdivision/moxxyv2.gi
In order to build Moxxy, you need to have [Flutter](https://docs.flutter.dev/get-started/install) set
up. If you are running NixOS or using Nix, you can also use the Flake at the root of the repository
by running `nix develop` to get a development shell including everything that is needed.
by running `nix develop` to get a development shell including everything that is needed. Note
that if you decide to use the Flake, `ANDROID_HOME` and `ANDROID_AVD_HOME` must be set to the respective directories.
Before building Moxxy, you need to generate all needed data classes. To do this, run
`flutter pub get` to install all dependencies. Then run `flutter pub run build_runner build` to generate
@@ -44,3 +46,9 @@ See `./LICENSE`.
## Special Thanks
- New logo designed by [Synoh](https://twitter.com/synoh_manda)
## Support
If you like what I do and you want to support me, feel free to donate to me on Ko-Fi.
[<img src="https://codeberg.org/moxxy/moxxyv2/raw/branch/master/assets/repo/kofi.png" height="36" style="height: 36px; border: 0px;"></img>](https://ko-fi.com/papatutuwawa)

View File

@@ -13,3 +13,6 @@ analyzer:
- "**/*.freezed.dart"
- "**/*.moxxy.dart"
- "test/"
- "integration_test/"
- "lib/service/database/migrations/*.dart"
- "lib/i18n/*.dart"

1
android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1 @@
-keep class net.sqlcipher.** { *; }

View File

@@ -46,6 +46,9 @@
android:value="2" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />

Binary file not shown.

View File

@@ -0,0 +1,336 @@
{
"@@name": "English",
"global": {
"title": "Moxxy",
"moxxySubtitle": "An experiment into building a modern, easy and beautiful XMPP client.",
"dialogAccept": "Okay",
"dialogCancel": "Cancel",
"yes": "Yes",
"no": "No"
},
"notifications": {
"permanent": {
"idle": "Idle",
"ready": "Ready to receive messages",
"connecting": "Connecting...",
"disconnect": "Disconnected",
"error": "Error"
},
"message": {
"reply": "Reply",
"markAsRead": "Mark as read"
},
"channels": {
"messagesChannelName": "Messages",
"messagesChannelDescription": "The notification channel for received messages",
"warningChannelName": "Warnings",
"warningChannelDescription": "Warnings related to Moxxy"
}
},
"dateTime": {
"justNow": "Just now",
"nMinutesAgo": "${min}min ago",
"mondayAbbrev": "Mon",
"tuesdayAbbrev": "Tue",
"wednessdayAbbrev": "Wed",
"thursdayAbbrev": "Thu",
"fridayAbbrev": "Fri",
"saturdayAbbrev": "Sat",
"sundayAbbrev": "Sun",
"january": "January",
"february": "February",
"march": "March",
"april": "April",
"may": "May",
"june": "June",
"july": "July",
"august": "August",
"september": "September",
"october": "October",
"november": "November",
"december": "December",
"today": "Today",
"yesterday": "Yesterday"
},
"messages": {
"image": "Image",
"video": "Video",
"audio": "Audio",
"file": "File",
"sticker": "Sticker",
"retracted": "The message has been retracted",
"retractedFallback": "A previous message has been retracted but your client does not support it"
},
"errors": {
"omemo": {
"couldNotPublish": "Could not publish the cryptographic identity to the server. This means that end-to-end encryption may not work.",
"notEncryptedForDevice": "This message was not encrypted for this device",
"invalidHmac": "Could not decrypt message",
"noDecryptionKey": "No decryption key available",
"messageInvalidAfixElement": "Invalid encrypted message",
"verificationInvalidOmemoUrl": "Invalid OMEMO:2 fingerprint",
"verificationWrongJid": "Wrong XMPP-address",
"verificationWrongDevice": "Wrong OMEMO:2 device",
"verificationNotInList": "Wrong OMEMO:2 device",
"verificationWrongFingerprint": "Wrong OMEMO:2 fingerprint"
},
"connection": {
"connectionTimeout": "Could not connect to server"
},
"login": {
"saslFailed": "Invalid login credentials",
"startTlsFailed": "Failed to establish a secure connection",
"noConnection": "Failed to establish a connection",
"unspecified": "Unspecified error"
},
"message": {
"unspecified": "Unknown error",
"fileUploadFailed": "The file upload failed",
"contactDoesntSupportOmemo": "The contact does not support encryption using OMEMO:2",
"fileDownloadFailed": "The file download failed",
"serviceUnavailable": "The message could not be delivered to the contact",
"remoteServerTimeout": "The message could not be delivered to the contact's server",
"remoteServerNotFound": "The message could not be delivered to the contact's server as it cannot be found",
"failedToEncrypt": "The message could not be encrypted",
"failedToEncryptFile": "The file could not be encrypted",
"failedToDecryptFile": "The file could not be decrypted",
"fileNotEncrypted": "The chat is encrypted but the file is not encrypted"
},
"conversation": {
"audioRecordingError": "Failed to finalize audio recording",
"openFileNoAppError": "No app found to open this file",
"openFileGenericError": "Failed to open file"
}
},
"warnings": {
"message": {
"integrityCheckFailed": "Could not verify file integrity"
},
"conversation": {
"holdForLonger": "Hold button longer to record a voice message"
}
},
"pages": {
"intro": {
"noAccount": "Have no XMPP account? No worries, creating one is really easy.",
"loginButton": "Login",
"registerButton": "Register"
},
"login": {
"title": "Login",
"xmppAddress": "XMPP-Address",
"password": "Password",
"advancedOptions": "Advanced options",
"createAccount": "Create account on server"
},
"conversations": {
"speeddialNewChat": "New chat",
"speeddialJoinGroupchat": "Join groupchat",
"overlaySettings": "Settings",
"noOpenChats": "You have no open chats",
"startChat": "Start a chat",
"closeChat": "Close chat",
"closeChatBody": "Are you sure you want to close the chat with ${conversationTitle}?",
"markAsRead": "Mark as read"
},
"conversation": {
"unencrypted": "Unencrypted",
"encrypted": "Encrypted",
"closeChat": "Close chat",
"closeChatConfirmTitle": "Close chat",
"closeChatConfirmSubtext": "Are you sure you want to close this chat?",
"blockShort": "Block",
"blockUser": "Block user",
"online": "Online",
"retract": "Retract message",
"retractBody": "Are you sure you want to retract the message? Keep in mind that this is only a request that the client does not have to honour.",
"forward": "Forward",
"edit": "Edit",
"quote": "Quote",
"copy": "Copy content",
"addReaction": "Add reaction",
"showError": "Show error",
"showWarning": "Show warning",
"addToContacts": "Add to contacts",
"addToContactsTitle": "Add ${jid} to contacts",
"addToContactsBody": "Are you sure you want to add ${jid} to your contacts?",
"stickerPickerNoStickersLine1": "You have no sticker packs installed.",
"stickerPickerNoStickersLine2": "They can be installed in the sticker settings.",
"stickerSettings": "Sticker settings",
"newDeviceMessage": "${title} added a new encryption device"
},
"addcontact": {
"title": "Add new contact",
"xmppAddress": "XMPP-Address",
"subtitle": "You can add a contact either by typing in their XMPP address or by scanning their QR code",
"buttonAddToContact": "Add to contacts"
},
"newconversation": {
"title": "Start new chat",
"addContact": "Add contact",
"createGroupchat": "Create groupchat"
},
"crop": {
"setProfilePicture": "Set as profile picture"
},
"shareselection": {
"shareWith": "Share with...",
"confirmTitle": "Send file",
"confirmBody": "One or more chats are unencrypted. This means that the file will be leaked to the server. Do you still want to continue?"
},
"profile": {
"general": {
"omemo": "Security"
},
"conversation": {
"notifications": "Notifications",
"notificationsMuted": "Muted",
"notificationsEnabled": "Enabled",
"sharedMedia": "Media"
},
"owndevices": {
"title": "Own Devices",
"thisDevice": "This device",
"otherDevices": "Other devices",
"deleteDeviceConfirmTitle": "Delete device",
"deleteDeviceConfirmBody": "This means that contacts will not be able to encrypt for that device. Continue?",
"recreateOwnSessions": "Rebuild sessions",
"recreateOwnSessionsConfirmTitle": "Recreate own sessions?",
"recreateOwnSessionsConfirmBody": "This will recreate the cryptographic sessions with your own devices. Use only if your own devices throw decryption errors.",
"recreateOwnDevice": "Recreate device",
"recreateOwnDeviceConfirmTitle": "Recreate own device?",
"recreateOwnDeviceConfirmBody": "This will recreate this device's cryptographic identity. It will take some time. If contacts verified your device, they will have to do it again. Continue?"
},
"devices": {
"title": "Devices",
"recreateSessions": "Rebuild sessions",
"recreateSessionsConfirmTitle": "Rebuild sessions?",
"recreateSessionsConfirmBody": "This will recreate the cryptographic sessions with your own devices. Use only if your own devices throw decryption errors."
}
},
"blocklist": {
"title": "Blocklist",
"noUsersBlocked": "You have no users blocked",
"unblockAll": "Unblock all",
"unblockAllConfirmTitle": "Are you sure?",
"unblockAllConfirmBody": "Are you sure you want to unblock all users?",
"unblockJidConfirmTitle": "Unblock ${jid}?",
"unblockJidConfirmBody": "Are you sure you want to unblock ${jid}? You will receive messages from this user again."
},
"cropbackground": {
"blur": "Blur background",
"setAsBackground": "Set as background image"
},
"stickerPack": {
"removeConfirmTitle": "Remove sticker pack",
"removeConfirmBody": "Are you sure you want to remove this sticker pack?",
"installConfirmTitle": "Install sticker pack",
"installConfirmBody": "Are you sure you want to install this sticker pack?",
"restricted": "This sticker pack is restricted. That means that the stickers will be displayed but cannot be sent.",
"fetchingFailure": "Could not find the sticker pack"
},
"settings": {
"settings": {
"title": "Settings",
"conversationsSection": "Conversations",
"accountSection": "Account",
"signOut": "Sign out",
"signOutConfirmTitle": "Sign Out",
"signOutConfirmBody": "You are about to sign out. Proceed?",
"miscellaneousSection": "Miscellaneous",
"debuggingSection": "Debugging"
},
"about": {
"title": "About",
"licensed": "Licensed under GPL3",
"version": "Version ${version}",
"viewSourceCode": "View source code"
},
"appearance": {
"title": "Appearance",
"languageSection": "Language",
"language": "App language",
"languageSubtext": "Currently selected: $selectedLanguage",
"systemLanguage": "Default language"
},
"licenses": {
"title": "Open-Source Licenses",
"licensedUnder": "Licensed under $license"
},
"conversation": {
"title": "Chat",
"appearance": "Appearance",
"selectBackgroundImage": "Select background image",
"selectBackgroundImageDescription": "This image will be the background of all your chats",
"removeBackgroundImage": "Remove background image",
"removeBackgroundImageConfirmTitle": "Remove background image",
"removeBackgroundImageConfirmBody": "Are you sure you want to remove your conversation background image?",
"newChatsSection": "New Conversations",
"newChatsMuteByDefault": "Mute new chats by default",
"newChatsE2EE": "Enable end-to-end encryption by default. WARNING: Experimental",
"behaviourSection": "Behaviour",
"contactsIntegration": "Contacts integration",
"contactsIntegrationBody": "When enabled, data from the phonebook will be used to provide chat titles and profile pictures. No data will be sent to the server."
},
"debugging": {
"title": "Debugging options",
"generalSection": "General",
"generalEnableDebugging": "Enable debugging",
"generalEncryptionPassword": "Encryption password",
"generalEncryptionPasswordSubtext": "The logs may contain sensitive information so pick a strong passphrase",
"generalLoggingIp": "Logging IP",
"generalLoggingIpSubtext": "The IP the logs should be sent to",
"generalLoggingPort": "Logging Port",
"generalLoggingPortSubtext": "The IP the logs should be sent to"
},
"network": {
"title": "Network",
"automaticDownloadsSection": "Automatic Downloads",
"automaticDownloadsText": "Moxxy will automatically download files on...",
"automaticDownloadsMaximumSize": "Maximum Download Size",
"automaticDownloadsMaximumSizeSubtext": "The maximum file size for a file to be automatically downloaded",
"automaticDownloadAlways": "Always",
"wifi": "Wifi",
"mobileData": "Mobile data"
},
"privacy": {
"title": "Privacy",
"generalSection": "General",
"showContactRequests": "Show contact requests",
"showContactRequestsSubtext": "This will show people who added you to their contact list but sent no message yet",
"profilePictureVisibility": "Make profile picture public",
"profilePictureVisibilitSubtext": "If enabled, everyone can see your profile picture. If disabled, only users on your contact list can see your profile picture.",
"autoAcceptSubscriptionRequests": "Auto-accept subscription requests",
"autoAcceptSubscriptionRequestsSubtext": "If enabled, subscription requests will be automatically accepted if the user is in the contact list.",
"conversationsSection": "Conversation",
"sendChatMarkers": "Send chat markers",
"sendChatMarkersSubtext": "This will tell your conversation partner if you received or read a message",
"sendChatStates": "Send chat states",
"sendChatStatesSubtext": "This will show your conversation partner if you are typing or looking at the chat",
"redirectsSection": "Redirects",
"redirectText": "This will redirect $serviceName links that you tap to a proxy service, e.g. $exampleProxy",
"currentlySelected": "Currently selected: $proxy",
"redirectsTitle": "$serviceName Redirect",
"cannotEnableRedirect": "Cannot enable $serviceName redirects",
"cannotEnableRedirectSubtext": "You must first set a proxy service to redirect to. To do so, tap the field next to the switch.",
"urlEmpty": "URL cannot be empty",
"urlInvalid": "Invalid URL",
"redirectDialogTitle": "$serviceName Redirect",
"stickersPrivacy": "Keep sticker list public",
"stickersPrivacySubtext": "If enabled, everyone will be able to see your list of installed sticker packs."
},
"stickers": {
"title": "Stickers",
"stickerSection": "Sticker",
"displayStickers": "Display stickers in chat",
"autoDownload": "Automatically download stickers",
"autoDownloadBody": "If enabled, stickers are automatically downloaded when the sender is in your contact list.",
"stickerPacksSection": "Sticker packs",
"importStickerPack": "Import sticker pack",
"importSuccess": "Sticker pack successfully imported",
"importFailure": "Failed to import sticker pack"
}
}
}
}

View File

@@ -0,0 +1,336 @@
{
"@@name": "Deutsch",
"global": {
"title": "Moxxy",
"moxxySubtitle": "Ein Experiment im Entwickeln eines modernen, einfachen und schönen XMPP-Clients.",
"dialogAccept": "Okay",
"dialogCancel": "Abbrechen",
"yes": "Ja",
"no": "Nein"
},
"notifications": {
"permanent": {
"idle": "Bereit",
"ready": "Bereit zum Nachrichtenempfang",
"connecting": "Verbinde...",
"disconnect": "Keine Verbindung",
"error": "Fehler"
},
"message": {
"reply": "Antworten",
"markAsRead": "Als gelesen markieren"
},
"channels": {
"messagesChannelName": "Nachrichten",
"messagesChannelDescription": "Empfangene Nachrichten",
"warningChannelName": "Warnungen",
"warningChannelDescription": "Warnungen im Bezug auf Moxxy"
}
},
"dateTime": {
"justNow": "Gerade",
"nMinutesAgo": "vor ${min}min",
"mondayAbbrev": "Mon",
"tuesdayAbbrev": "Die",
"wednessdayAbbrev": "Mit",
"thursdayAbbrev": "Don",
"fridayAbbrev": "Fre",
"saturdayAbbrev": "Sam",
"sundayAbbrev": "Son",
"january": "Januar",
"february": "Februar",
"march": "März",
"april": "April",
"may": "Mai",
"june": "Juni",
"july": "Juli",
"august": "August",
"september": "September",
"october": "Oktober",
"november": "November",
"december": "Dezember",
"today": "Heute",
"yesterday": "Gestern"
},
"messages": {
"image": "Bild",
"video": "Video",
"audio": "Audio",
"file": "Datei",
"sticker": "Sticker",
"retracted": "Die Nachricht wurde zurückgezogen",
"retractedFallback": "Eine vorherige Nachricht wurde zurückgezogen. Dein Client unterstüzt dies jedoch nicht"
},
"errors": {
"omemo": {
"couldNotPublish": "Konnte die kryptographische Identität nicht auf dem Server veröffentlichen. Ende-zu-Ende-Verschlüsselung funktioniert eventuell nicht.",
"notEncryptedForDevice": "Die Nachricht wurde nicht für dieses Gerät verschlüsselt",
"invalidHmac": "Die Nachricht konnte nicht entschlüsselt werden",
"noDecryptionKey": "Kein Schlüssel zum Entschlüsseln vorhanden",
"messageInvalidAfixElement": "Ungültige verschlüsselte Nachricht",
"verificationInvalidOmemoUrl": "Ungültiger OMEMO:2 Fingerabdruck",
"verificationWrongJid": "Falsche XMPP-Addresse",
"verificationWrongDevice": "Falsches OMEMO:2 Gerät",
"verificationNotInList": "OMEMO:2 Gerät unbekannt",
"verificationWrongFingerprint": "Falscher OMEMO:2 Fingerabdruck"
},
"connection": {
"connectionTimeout": "Verbindung zum Server nicht möglich"
},
"login": {
"saslFailed": "Ungültige Logindaten",
"startTlsFailed": "Konnte keine sichere Verbindung zum Server aufbauen",
"noConnection": "Konnte keine Verbindung zum Server aufbauen",
"unspecified": "Unbestimmter Fehler"
},
"message": {
"unspecified": "Unbekannter Fehler",
"fileUploadFailed": "Das Hochladen der Datei ist fehlgeschlagen",
"contactDoesntSupportOmemo": "Der Kontakt unterstützt Verschlüsselung mit OMEMO:2 nicht",
"fileDownloadFailed": "Das Herunterladen der Datei ist fehlgeschlagen",
"serviceUnavailable": "Die Nachricht konnte nicht gesendet werden",
"remoteServerTimeout": "Die Nachricht konnte nicht zugestellt werden",
"remoteServerNotFound": "Die Nachricht konnte nicht gesendet werden, da der Empfängerserver unbekannt ist",
"failedToEncrypt": "Die Nachricht konnte nicht verschlüsselt werden",
"failedToEncryptFile": "Die Datei konnte nicht verschlüsselt werden",
"failedToDecryptFile": "Die Datei konnte nicht entschlüsselt werden",
"fileNotEncrypted": "Der Chat ist verschlüsselt, aber die Datei wurde unverschlüsselt übertragen"
},
"conversation": {
"audioRecordingError": "Fehler beim Fertigstellen der Audioaufnahme",
"openFileNoAppError": "Keine App vorhanden, um die Datei zu öffnen",
"openFileGenericError": "Fehler beim Öffnen der Datei"
}
},
"warnings": {
"message": {
"integrityCheckFailed": "Konnte Integrität der Datei nicht überprüfen"
},
"conversation": {
"holdForLonger": "Button länger gedrückt halten, um eine Sprachnachricht aufzunehmen"
}
},
"pages": {
"intro": {
"noAccount": "Kein XMPP-Account vorhanden? Einen zu erstellen ist sehr einfach.",
"loginButton": "Einloggen",
"registerButton": "Registrieren"
},
"login": {
"title": "Login",
"xmppAddress": "XMPP-Adresse",
"password": "Passwort",
"advancedOptions": "Fortgeschrittene Optionen",
"createAccount": "Account auf dem Server erstellen"
},
"conversations": {
"speeddialNewChat": "Neuer chat",
"speeddialJoinGroupchat": "Gruppenchat beitreten",
"overlaySettings": "Einstellungen",
"noOpenChats": "Du hast keine offenen chats",
"startChat": "Einen chat anfangen",
"closeChat": "Chat schließen",
"closeChatBody": "Bist du dir sicher, dass du den Chat mit ${conversationTitle} schließen möchtest?",
"markAsRead": "Als gelesen markieren"
},
"conversation": {
"unencrypted": "Unverschlüsselt",
"encrypted": "Verschlüsselt",
"closeChat": "Chat schließen",
"closeChatConfirmTitle": "Chat schließen",
"closeChatConfirmSubtext": "Bist Du dir sicher, dass du den Chat schließen möchtest?",
"blockShort": "Blockieren",
"blockUser": "Nutzer blockieren",
"online": "Online",
"retract": "Nachricht löschen",
"retractBody": "Bist du dir sicher, dass du die Nachricht löschen willst? Bedenke, dass dies nur eine Bitte ist, die dein gegenüber nicht beachten muss.",
"forward": "Weiterleiten",
"edit": "Bearbeiten",
"quote": "Zitieren",
"copy": "Inhalt kopieren",
"addReaction": "Reaktion hinzufügen",
"showError": "Fehler anzeigen",
"showWarning": "Warnung anzeigen",
"addToContacts": "Zu Kontaken hinzufügen",
"addToContactsTitle": "${jid} zu Kontakten hinzufügen",
"addToContactsBody": "Bist du dir sicher, dass du ${jid} zu deinen Kontakten hinzufügen möchtest?",
"stickerPickerNoStickersLine1": "Du hast keine Stickerpacks installiert.",
"stickerPickerNoStickersLine2": "Diese können in den Stickereinstellungen installiert werden.",
"stickerSettings": "Stickereinstellungen",
"newDeviceMessage": "${title} hat ein neues Verschlüsselungsgerät hinzugefügt"
},
"addcontact": {
"title": "Neuen Kontakt hinzufügen",
"xmppAddress": "XMPP-Adresse",
"subtitle": "Du kannst einen Kontakt hinzufügen, indem Du entweder die XMPP-Adresse eingibst oder den QR-Code deines Kontaktes scannst",
"buttonAddToContact": "Kontakt hinzufügen"
},
"newconversation": {
"title": "Neuer chat",
"addContact": "Kontakt hinzufügen",
"createGroupchat": "Gruppenchat erstellen"
},
"crop": {
"setProfilePicture": "Als Profilbild festlegen"
},
"shareselection": {
"shareWith": "Teilen mit...",
"confirmTitle": "Dateien senden?",
"confirmBody": "Einer oder mehr Chats sind unverschlüsselt. Das bedeutet, dass die Dateien dem Server unverschlüsselt vorliegen. Dateien trotzdem senden?"
},
"profile": {
"general": {
"omemo": "Sicherheit"
},
"conversation": {
"notifications": "Benachrichtigungen",
"notificationsMuted": "Stumm",
"notificationsEnabled": "Eingeschaltet",
"sharedMedia": "Medien"
},
"owndevices": {
"title": "Eigene Geräte",
"thisDevice": "Dieses Gerät",
"otherDevices": "Andere Geräte",
"deleteDeviceConfirmTitle": "Gerät löschen",
"deleteDeviceConfirmBody": "Das bedeutet, dass Kontakte für dieses Gerät nichtmehr verschlüsseln können. Fortfahren?",
"recreateOwnSessions": "Sessions neuerstellen",
"recreateOwnSessionsConfirmTitle": "Eigene Sessions neuerstellen?",
"recreateOwnSessionsConfirmBody": "Das wird alle kryptographischen Sessions mit den eigenen Geräten neuerstellen. Verwende dies nur, wenn deine eigenen Geräte Entschlüsselungsfehler erzeugen.",
"recreateOwnDevice": "Gerät neuerstellen",
"recreateOwnDeviceConfirmTitle": "Gerät neuerstellen?",
"recreateOwnDeviceConfirmBody": "Das wird die kryptographische Identität dieses Geräts neu erstellen. Wenn Kontakte die kryptographische Indentität verifiziert haben, dann müssen diese es erneut tun. Fortfahren?"
},
"devices": {
"title": "Geräte",
"recreateSessions": "Sessions zurücksetzen",
"recreateSessionsConfirmTitle": "Sessions zurücksetzen?",
"recreateSessionsConfirmBody": "Dies wird alle Sessions mit Deinen Geräten neu erstellen. Tue dies nur, wenn deine Geräte Fehler beim Entschlüsseln erzeugen."
}
},
"blocklist": {
"title": "Blockliste",
"noUsersBlocked": "Du hast niemanden blockiert",
"unblockAll": "Alle entblocken",
"unblockAllConfirmTitle": "Alle entblocken",
"unblockAllConfirmBody": "Bist Du dir sicher, dass du alle geblockten Personen entblocken möchtest?",
"unblockJidConfirmTitle": "${jid} entblocken?",
"unblockJidConfirmBody": "Bist du dir sicher, dass du ${jid} entblocken möchtest? Du wirst wieder Nachrichten von dieser Person erhalten können."
},
"cropbackground": {
"blur": "Hintergrund weichzeichnen",
"setAsBackground": "Als Hintergrundbild festlegen"
},
"stickerPack": {
"removeConfirmTitle": "Stickerpack entfernen",
"removeConfirmBody": "Bist Du Dir sicher, dass du das Stickerpack entfernen möchtest?",
"installConfirmTitle": "Stickerpack installieren",
"installConfirmBody": "Bist Du Dir sicher, dass Du das Stickerpack installieren möchtest?",
"restricted": "Dieses Stickerpack ist eingeschränkt. Das bedeutet, dass es im Chat angezeigt wird, jedoch nicht versendet werden kann.",
"fetchingFailure": "Konnte das Stickerpack nicht finden"
},
"settings": {
"settings": {
"title": "Einstellungen",
"conversationsSection": "Unterhaltungen",
"accountSection": "Account",
"signOut": "Abmelden",
"signOutConfirmTitle": "Abmelden",
"signOutConfirmBody": "Du bist dabei dich abzumelden. Fortfahren?",
"miscellaneousSection": "Unterschiedlich",
"debuggingSection": "Debugging"
},
"about": {
"title": "Über",
"licensed": "Lizensiert unter GPL3",
"version": "Version ${version}",
"viewSourceCode": "Quellcode anschauen"
},
"appearance": {
"title": "Aussehen",
"languageSection": "Sprache",
"language": "Appsprache",
"languageSubtext": "Aktuell ausgewählt: $selectedLanguage",
"systemLanguage": "Systemsprache"
},
"licenses": {
"title": "Open-Source Lizenzen",
"licensedUnder": "Lizensiert unter $license"
},
"conversation": {
"title": "Chat",
"appearance": "Aussehen",
"selectBackgroundImage": "Hintergrundbild auswählen",
"selectBackgroundImageDescription": "Dieses Bild wird als Hintergrundbild in allen Chats verwendet",
"removeBackgroundImage": "Hintergrundbild entfernen",
"removeBackgroundImageConfirmTitle": "Hintergrundbild entfernen",
"removeBackgroundImageConfirmBody": "Bist Du dir sicher, dass Du das Hintergrundbild entfernen möchtest?",
"newChatsSection": "Neue Chats",
"newChatsMuteByDefault": "Neue Chats standardmäßig stummschalten",
"newChatsE2EE": "Ende-zu-Ende-Verschlüsselung standardmäßig aktivieren. WARNUNG: Experimentell",
"behaviourSection": "Verhalten",
"contactsIntegration": "Kontaktintegration",
"contactsIntegrationBody": "Wenn aktiviert, dann werden Kontakte aus dem Kontaktbuch verwendet, um Chatnamen und Profilbilder anzuzeigen. Dabei werden keine Daten an den Server gesendet."
},
"debugging": {
"title": "Debuggingoptionen",
"generalSection": "Generell",
"generalEnableDebugging": "Debugging einschalten",
"generalEncryptionPassword": "Verschlüsselungspasswort",
"generalEncryptionPasswordSubtext": "Die Logs enthalten eventuell sensible Daten. Wähle also daher eine starke Passphrase",
"generalLoggingIp": "Logging-IP",
"generalLoggingIpSubtext": "Die IP-Adresse an die die Logs gesendet werden",
"generalLoggingPort": "Logging-Port",
"generalLoggingPortSubtext": "Der Port an den die Logs gesendet werden"
},
"network": {
"title": "Netzwerk",
"automaticDownloadsSection": "Automatische Downloads",
"automaticDownloadsText": "Moxxy läd Dateien automatisch herunter, wenn verbunden mit...",
"automaticDownloadsMaximumSize": "Maximale Downloadgröße",
"automaticDownloadsMaximumSizeSubtext": "Die maximale Dateigröße, die automatisch heruntergeladen werden soll",
"automaticDownloadAlways": "Immer",
"wifi": "Wifi",
"mobileData": "Mobile Daten"
},
"privacy": {
"title": "Privatsphäre",
"generalSection": "Generell",
"showContactRequests": "Kontaktanfragen zeigen",
"showContactRequestsSubtext": "Dies zeigt Personen in der Chatübersicht an, die Dich zu ihrer Kontaktliste hinzugefügt haben, aber noch keine Nachricht gesendet haben",
"profilePictureVisibility": "Öffentliches Profilbild",
"profilePictureVisibilitSubtext": "Wenn aktiviert, dann kann jeder Dein Profilbild sehen. Wenn deaktiviert, dann können nur Personen aus deiner Kontaktliste kein Profilbild sehen",
"autoAcceptSubscriptionRequests": "Subscriptionanfragen automatisch annehmen",
"autoAcceptSubscriptionRequestsSubtext": "Wenn aktiviert, dann werden Subscriptionanfragen automatisch angenommen, wenn die Person in deiner Kontaktliste ist",
"conversationsSection": "Unterhaltungen",
"sendChatMarkers": "Chatmarker senden",
"sendChatMarkersSubtext": "Dies teilt Deinem Gesprächspartner mit, ob du Nachrichten empfangen oder gelesen hast",
"sendChatStates": "Chatstates senden",
"sendChatStatesSubtext": "Dies teilt Deinem Gesprächspartner mit, ob du gerade im Chat aktiv bist oder schreibst",
"redirectsSection": "Weiterleitungen",
"redirectText": "Dies leitet Links von $serviceName, die du öffnest, an einen Proxydienst weiter, wie zum Beispiel $exampleProxy",
"currentlySelected": "Aktuell ausgewählt: $proxy",
"redirectsTitle": "${serviceName}weiterleitung",
"cannotEnableRedirect": "Kann ${serviceName}weiterleitung nicht aktivieren",
"cannotEnableRedirectSubtext": "Du must zuerst einen Proxydienst auswählen. Dazu berühre das Feld neben dem Schalter.",
"urlEmpty": "URL kann nicht leer sein",
"urlInvalid": "Ungültige URL",
"redirectDialogTitle": "${serviceName}weiterleitung",
"stickersPrivacy": "Stickerliste öffentlich halten",
"stickersPrivacySubtext": "Wenn eingeschaltet, dann kann jeder die Liste Deiner installierten Stickerpacks sehen."
},
"stickers": {
"title": "Stickers",
"stickerSection": "Sticker",
"displayStickers": "Sticker im Chat anzeigen",
"autoDownload": "Sticker automatisch herunterladen",
"autoDownloadBody": "Wenn aktiviert, dann werden Sticker automatisch heruntergeladen, wenn der Sender in der Kontaktliste ist.",
"stickerPacksSection": "Stickerpacks",
"importStickerPack": "Stickerpack importieren",
"importSuccess": "Stickerpack erfolgreich importiert",
"importFailure": "Beim Import des Stickerpacks ist ein Fehler aufgetreten"
}
}
}
}

BIN
assets/repo/kofi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

7
build.yaml Normal file
View File

@@ -0,0 +1,7 @@
targets:
$default:
builders:
slang_build_runner:
options:
input_directory: assets/i18n
output_directory: lib/i18n

43
docs/stickerpacks.md Normal file
View File

@@ -0,0 +1,43 @@
# Sticker Packs
Moxxy supports sending and receiving sticker packs using XEP-0449 version 0.1.1. Sticker
packs can also be imported using a Moxxy specific format.
## File Format
A Moxxy sticker pack is a flat tar archive that contains the following files:
- `urn.xmpp.stickers.0.xml`
- The sticker files
### `urn.xmpp.stickers.0.xml`
This file is the sticker pack's metadata file. It describes the sticker pack the same
way as the examples in XEP-0449 do. There are, however, some differences:
- Each `<file />` element must contain a `<name />` element that matches with a file in the tar archive
- Each sticker MUST contain at least one HTTP(s) source
- The `<hash />` of the `<pack />` element is ignored as Moxxy computes it itself, so it can be omitted
An example for the metadata file is the following:
```xml
<pack xmlns='urn:xmpp:stickers:0'>
<name>Example</name>
<summary>Example sticker pack.</summary>
<item>
<file xmlns='urn:xmpp:file:metadata:0'>
<media-type>image/png</media-type>
<desc>:some-sticker:</desc>
<name>suprise.png</name>
<size>531910</size>
<dimensions>1030x1030</dimensions>
<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>1Ha4okUGNRAA04KibwWUmklqqBqdhg7+20dfsr/wLik=</hash>
</file>
<sources xmlns='urn:xmpp:sfs:0'>
<url-data xmlns='http://jabber.org/protocol/url-data' target='...' />
</sources>
</item>
<!-- ... -->
</pack>
```

12
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1649676176,
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
@@ -17,11 +17,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1660551188,
"narHash": "sha256-a1LARMMYQ8DPx1BgoI/UN4bXe12hhZkCNqdxNi6uS0g=",
"lastModified": 1669165918,
"narHash": "sha256-hIVruk2+0wmw/Kfzy11rG3q7ev3VTi/IKVODeHcVjFo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "441dc5d512153039f19ef198e662e4f3dbb9fd65",
"rev": "3b400a525d92e4085e46141ff48cbf89fd89739e",
"type": "github"
},
"original": {

View File

@@ -44,9 +44,7 @@
ripgrep # General utilities
];
ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
JAVA_HOME = pinnedJDK;
ANDROID_AVD_HOME = (toString ./.) + "/.android/avd";
};
});
}

View File

@@ -0,0 +1,67 @@
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
import 'package:moxxyv2/service/connectivity.dart';
import 'package:moxxyv2/service/moxxmpp/reconnect.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:test/test.dart';
class StubConnectivityService extends ConnectivityService {
StubConnectivityService() : super();
@override
ConnectivityResult get currentState => ConnectivityResult.wifi;
}
void main() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
final log = Logger('FailureReconnectionTest');
GetIt.I.registerSingleton<ConnectivityService>(StubConnectivityService());
test('Failing an awaited connection with MoxxyReconnectionPolicy', () async {
var errors = 0;
final connection = XmppConnection(
MoxxyReconnectionPolicy(maxBackoffTime: 1),
TCPSocketWrapper(false),
);
connection.registerFeatureNegotiators([
StartTlsNegotiator(),
]);
connection.registerManagers([
DiscoManager(),
RosterManager(),
PingManager(),
MessageManager(),
PresenceManager('http://moxxmpp.example'),
]);
connection.asBroadcastStream().listen((event) {
if (event is ConnectionStateChangedEvent) {
if (event.state == XmppConnectionState.error) {
errors++;
}
}
});
connection.setConnectionSettings(
ConnectionSettings(
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
password: 'abc123',
useDirectTLS: true,
allowPlainAuth: true,
),
);
final result = await connection.connectAwaitable();
log.info('Connection failed as expected');
expect(result.success, false);
expect(errors, 1);
log.info('Waiting 20 seconds for unexpected reconnections');
await Future.delayed(const Duration(seconds: 20));
expect(errors, 1);
}, timeout: Timeout.factor(2));
}

34
ios/.gitignore vendored
View File

@@ -1,34 +0,0 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
</dict>
</plist>

View File

@@ -1 +0,0 @@
#include "Generated.xcconfig"

View File

@@ -1 +0,0 @@
#include "Generated.xcconfig"

View File

@@ -1,481 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = assets/images/icon_ios.png;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.moxxyv2;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = assets/images/icon_ios.png;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.moxxyv2;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = assets/images/icon_ios.png;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.moxxyv2;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -1,13 +0,0 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

View File

@@ -1,5 +0,0 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -1 +0,0 @@
{"images":[{"size":"20x20","idiom":"iphone","filename":"assets/images/icon_ios.png-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"assets/images/icon_ios.png-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"assets/images/icon_ios.png-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"assets/images/icon_ios.png-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"assets/images/icon_ios.png-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"assets/images/icon_ios.png-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"assets/images/icon_ios.png-40x40@3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"assets/images/icon_ios.png-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"assets/images/icon_ios.png-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"assets/images/icon_ios.png-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"assets/images/icon_ios.png-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"assets/images/icon_ios.png-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"assets/images/icon_ios.png-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"assets/images/icon_ios.png-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"assets/images/icon_ios.png-40x40@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"assets/images/icon_ios.png-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"assets/images/icon_ios.png-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"assets/images/icon_ios.png-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"assets/images/icon_ios.png-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

View File

@@ -1 +0,0 @@
{"images":[{"size":"20x20","idiom":"iphone","filename":"assets/images/logo_ios.png-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"assets/images/logo_ios.png-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"assets/images/logo_ios.png-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"assets/images/logo_ios.png-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"assets/images/logo_ios.png-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"assets/images/logo_ios.png-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"assets/images/logo_ios.png-40x40@3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"assets/images/logo_ios.png-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"assets/images/logo_ios.png-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"assets/images/logo_ios.png-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"assets/images/logo_ios.png-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"assets/images/logo_ios.png-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"assets/images/logo_ios.png-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"assets/images/logo_ios.png-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"assets/images/logo_ios.png-40x40@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"assets/images/logo_ios.png-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"assets/images/logo_ios.png-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"assets/images/logo_ios.png-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"assets/images/logo_ios.png-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Moxxy</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>moxxyv2</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>LSApplicationQueriesScheme</key>
<array>
<string>https</string>
</array>
</dict>
</plist>

View File

@@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -7,13 +7,15 @@ files:
- JsonImplementation
attributes:
jid: String
displayName: String
preStart:
type: PreStartDoneEvent
deserialise: true
- name: LoginFailureEvent
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
reason: String
reason: String?
- name: PreStartDoneEvent
extends: BackgroundEvent
implements:
@@ -34,6 +36,9 @@ files:
roster:
type: List<RosterItem>?
deserialise: true
stickers:
type: List<StickerPack>?
deserialise: true
# Returned by [GetMessagesForJidCommand]
- name: MessagesResultEvent
extends: BackgroundEvent
@@ -69,8 +74,7 @@ files:
extends: BackgroundEvent
implements:
- JsonImplementation
# Send by the service if a message has been received or returned by
# [SendMessageCommand].
# Send by the service if a message has been received or returned by # [SendMessageCommand].
- name: MessageAddedEvent
extends: BackgroundEvent
implements:
@@ -102,6 +106,13 @@ files:
extends: BackgroundEvent
implements:
- JsonImplementation
# Triggered in response to a [GetBlocklistCommand]
- name: GetBlocklistResultEvent
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
entries: List<String>
# Triggered by DownloadService or UploadService.
- name: ProgressEvent
extends: BackgroundEvent
@@ -109,7 +120,7 @@ files:
- JsonImplementation
attributes:
id: int
progress: double
progress: double?
# Triggered by [RosterService] if we receive a roster push.
- name: RosterDiffEvent
extends: BackgroundEvent
@@ -162,6 +173,7 @@ files:
supportsCsi: bool
supportsUserBlocking: bool
supportsHttpFileUpload: bool
supportsCarbons: bool
# Returned by [SignOutCommand]
- name: SignedOutEvent
extends: BackgroundEvent
@@ -172,6 +184,87 @@ files:
extends: BackgroundEvent
implements:
- JsonImplementation
- name: GetConversationOmemoFingerprintsResult
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
fingerprints:
type: List<OmemoDevice>
deserialise: true
- name: GetOwnOmemoFingerprintsResult
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
ownDeviceFingerprint: String
ownDeviceId: int
fingerprints:
type: List<OmemoDevice>
deserialise: true
- name: RegenerateOwnDeviceResult
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
device:
type: OmemoDevice
deserialise: true
- name: MessageNotificationTappedEvent
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
conversationJid: String
title: String
avatarUrl: String
- name: StickerPackImportSuccessEvent
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
stickerPack:
type: StickerPack
deserialise: true
- name: StickerPackImportFailureEvent
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
- name: FetchStickerPackSuccessResult
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
stickerPack:
type: StickerPack
deserialise: true
- name: FetchStickerPackFailureResult
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
- name: StickerPackInstallSuccessEvent
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
stickerPack:
type: StickerPack
deserialise: true
- name: StickerPackInstallFailureEvent
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
- name: StickerPackAddedEvent
extends: BackgroundEvent
implements:
- JsonImplementation
attributes:
stickerPack:
type: StickerPack
deserialise: true
generate_builder: true
builder_name: "Event"
builder_baseclass: "BackgroundEvent"
@@ -190,6 +283,8 @@ files:
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
systemLocaleCode: String
- name: AddConversationCommand
extends: BackgroundCommand
implements:
@@ -222,6 +317,8 @@ files:
quotedMessage:
type: Message?
deserialise: true
editSid: String?
editId: int?
- name: SendFilesCommand
extends: BackgroundCommand
implements:
@@ -308,6 +405,140 @@ files:
extends: BackgroundCommand
implements:
- JsonImplementation
- name: SetConversationMuteStatusCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
jid: String
muted: bool
- name: GetConversationOmemoFingerprintsCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
jid: String
- name: SetOmemoDeviceEnabledCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
jid: String
deviceId: int
enabled: bool
- name: RecreateSessionsCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
jid: String
- name: SetOmemoEnabledCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
jid: String
enabled: bool
- name: GetOwnOmemoFingerprintsCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
- name: RemoveOwnDeviceCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
deviceId: int
- name: RegenerateOwnDeviceCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
- name: RetractMessageCommentCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
originId: String
conversationJid: String
- name: MarkConversationAsReadCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
conversationId: int
- name: MarkMessageAsReadCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
conversationJid: String
sid: String
newUnreadCounter: int
- name: AddReactionToMessageCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
messageId: int
conversationJid: String
emoji: String
- name: RemoveReactionFromMessageCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
messageId: int
conversationJid: String
emoji: String
- name: MarkOmemoDeviceAsVerifiedCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
deviceId: int
jid: String
- name: ImportStickerPackCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
path: String
- name: RemoveStickerPackCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
stickerPackId: String
- name: SendStickerCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
stickerPackId: String
stickerHashKey: String
recipient: String
- name: FetchStickerPackCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
stickerPackId: String
jid: String
- name: InstallStickerPackCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
stickerPack:
type: StickerPack
deserialise: true
- name: GetBlocklistCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
generate_builder: true
# get${builder_Name}FromJson
builder_name: "Command"

View File

@@ -1,26 +1,34 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:moxplatform/moxplatform.dart';
import 'package:moxxyv2/i18n/strings.g.dart';
import 'package:moxxyv2/service/service.dart';
import 'package:moxxyv2/shared/commands.dart';
import 'package:moxxyv2/shared/synchronized_queue.dart';
import 'package:moxxyv2/ui/bloc/addcontact_bloc.dart';
import 'package:moxxyv2/ui/bloc/blocklist_bloc.dart';
import 'package:moxxyv2/ui/bloc/conversation_bloc.dart';
import 'package:moxxyv2/ui/bloc/conversations_bloc.dart';
import 'package:moxxyv2/ui/bloc/crop_bloc.dart';
import 'package:moxxyv2/ui/bloc/cropbackground_bloc.dart';
import 'package:moxxyv2/ui/bloc/devices_bloc.dart';
import 'package:moxxyv2/ui/bloc/login_bloc.dart';
import 'package:moxxyv2/ui/bloc/navigation_bloc.dart';
import 'package:moxxyv2/ui/bloc/newconversation_bloc.dart';
import 'package:moxxyv2/ui/bloc/own_devices_bloc.dart';
import 'package:moxxyv2/ui/bloc/preferences_bloc.dart';
import 'package:moxxyv2/ui/bloc/profile_bloc.dart';
import 'package:moxxyv2/ui/bloc/sendfiles_bloc.dart';
import 'package:moxxyv2/ui/bloc/server_info_bloc.dart';
import 'package:moxxyv2/ui/bloc/share_selection_bloc.dart';
import 'package:moxxyv2/ui/bloc/sharedmedia_bloc.dart';
import 'package:moxxyv2/ui/bloc/sticker_pack_bloc.dart';
import 'package:moxxyv2/ui/bloc/stickers_bloc.dart';
import 'package:moxxyv2/ui/constants.dart';
import 'package:moxxyv2/ui/events.dart';
/*
@@ -35,27 +43,34 @@ import 'package:moxxyv2/ui/pages/crop.dart';
import 'package:moxxyv2/ui/pages/intro.dart';
import 'package:moxxyv2/ui/pages/login.dart';
import 'package:moxxyv2/ui/pages/newconversation.dart';
import 'package:moxxyv2/ui/pages/profile/devices.dart';
import 'package:moxxyv2/ui/pages/profile/own_devices.dart';
import 'package:moxxyv2/ui/pages/profile/profile.dart';
import 'package:moxxyv2/ui/pages/sendfiles.dart';
import 'package:moxxyv2/ui/pages/server_info.dart';
import 'package:moxxyv2/ui/pages/settings/about.dart';
import 'package:moxxyv2/ui/pages/settings/appearance/appearance.dart';
import 'package:moxxyv2/ui/pages/settings/appearance/cropbackground.dart';
import 'package:moxxyv2/ui/pages/settings/conversation.dart';
import 'package:moxxyv2/ui/pages/settings/debugging.dart';
import 'package:moxxyv2/ui/pages/settings/licenses.dart';
import 'package:moxxyv2/ui/pages/settings/network.dart';
import 'package:moxxyv2/ui/pages/settings/privacy/privacy.dart';
import 'package:moxxyv2/ui/pages/settings/settings.dart';
import 'package:moxxyv2/ui/pages/settings/stickers.dart';
import 'package:moxxyv2/ui/pages/share_selection.dart';
import 'package:moxxyv2/ui/pages/sharedmedia.dart';
import 'package:moxxyv2/ui/pages/splashscreen/splashscreen.dart';
import 'package:moxxyv2/ui/pages/sticker_pack.dart';
import 'package:moxxyv2/ui/pages/util/qrcode.dart';
import 'package:moxxyv2/ui/service/data.dart';
import 'package:moxxyv2/ui/service/progress.dart';
import 'package:moxxyv2/ui/service/thumbnail.dart';
import 'package:moxxyv2/ui/theme.dart';
import 'package:page_transition/page_transition.dart';
import 'package:share_handler/share_handler.dart';
void setupLogging() {
Logger.root.level = Level.ALL;
Logger.root.level = kDebugMode ? Level.ALL : Level.INFO;
Logger.root.onRecord.listen((record) {
// ignore: avoid_print
print('[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}');
@@ -66,7 +81,6 @@ void setupLogging() {
Future<void> setupUIServices() async {
GetIt.I.registerSingleton<UIProgressService>(UIProgressService());
GetIt.I.registerSingleton<UIDataService>(UIDataService());
GetIt.I.registerSingleton<ThumbnailCacheService>(ThumbnailCacheService());
}
void setupBlocs(GlobalKey<NavigatorState> navKey) {
@@ -74,8 +88,7 @@ void setupBlocs(GlobalKey<NavigatorState> navKey) {
GetIt.I.registerSingleton<ConversationsBloc>(ConversationsBloc());
GetIt.I.registerSingleton<NewConversationBloc>(NewConversationBloc());
GetIt.I.registerSingleton<ConversationBloc>(ConversationBloc());
GetIt.I.registerSingleton<BlocklistBloc>(BlocklistBloc());
GetIt.I.registerSingleton<ProfileBloc>(ProfileBloc());
GetIt.I.registerSingleton<BlocklistBloc>(BlocklistBloc()); GetIt.I.registerSingleton<ProfileBloc>(ProfileBloc());
GetIt.I.registerSingleton<PreferencesBloc>(PreferencesBloc());
GetIt.I.registerSingleton<AddContactBloc>(AddContactBloc());
GetIt.I.registerSingleton<SharedMediaBloc>(SharedMediaBloc());
@@ -83,14 +96,17 @@ void setupBlocs(GlobalKey<NavigatorState> navKey) {
GetIt.I.registerSingleton<SendFilesBloc>(SendFilesBloc());
GetIt.I.registerSingleton<CropBackgroundBloc>(CropBackgroundBloc());
GetIt.I.registerSingleton<ShareSelectionBloc>(ShareSelectionBloc());
GetIt.I.registerSingleton<ServerInfoBloc>(ServerInfoBloc());
GetIt.I.registerSingleton<DevicesBloc>(DevicesBloc());
GetIt.I.registerSingleton<OwnDevicesBloc>(OwnDevicesBloc());
GetIt.I.registerSingleton<StickersBloc>(StickersBloc());
GetIt.I.registerSingleton<StickerPackBloc>(StickerPackBloc());
}
// TODO(Unknown): Replace all Column(children: [ Padding(), Padding, ...]) with a
// Padding(padding: ..., child: Column(children: [ ... ]))
// TODO(Unknown): Theme the switches
void main() async {
GetIt.I.registerSingleton<Completer<void>>(Completer());
setupLogging();
await setupUIServices();
@@ -100,7 +116,7 @@ void main() async {
setupBlocs(navKey);
await initializeServiceIfNeeded();
runApp(
MultiBlocProvider(
providers: [
@@ -146,15 +162,32 @@ void main() async {
BlocProvider<ShareSelectionBloc>(
create: (_) => GetIt.I.get<ShareSelectionBloc>(),
),
BlocProvider<ServerInfoBloc>(
create: (_) => GetIt.I.get<ServerInfoBloc>(),
),
BlocProvider<DevicesBloc>(
create: (_) => GetIt.I.get<DevicesBloc>(),
),
BlocProvider<OwnDevicesBloc>(
create: (_) => GetIt.I.get<OwnDevicesBloc>(),
),
BlocProvider<StickersBloc>(
create: (_) => GetIt.I.get<StickersBloc>(),
),
BlocProvider<StickerPackBloc>(
create: (_) => GetIt.I.get<StickerPackBloc>(),
),
],
child: MyApp(navKey),
child: TranslationProvider(
child: MyApp(navKey),
),
),
);
}
class MyApp extends StatefulWidget {
const MyApp(this.navigationKey, { Key? key }) : super(key: key);
const MyApp(this.navigationKey, { super.key });
final GlobalKey<NavigatorState> navigationKey;
@override
@@ -169,10 +202,10 @@ class MyAppState extends State<MyApp> with WidgetsBindingObserver {
super.initState();
WidgetsBinding.instance.addObserver(this);
// Lift the UI block
GetIt.I.get<Completer<void>>().complete();
_setupSharingHandler();
// Lift the UI block
GetIt.I.get<SynchronizedQueue<Map<String, dynamic>?>>().removeQueueLock();
}
Future<void> _handleSharedMedia(SharedMedia media) async {
@@ -242,44 +275,12 @@ class MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
return MaterialApp(
locale: TranslationProvider.of(context).flutterLocale,
supportedLocales: LocaleSettings.supportedLocales,
localizationsDelegates: GlobalMaterialLocalizations.delegates,
title: 'Moxxy',
theme: ThemeData(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
primary: primaryColor,
onPrimary: Colors.white,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
primary: primaryColor,
),
),
// NOTE: Mainly for the SettingsSection
colorScheme: const ColorScheme.light(
secondary: primaryColor,
),
),
darkTheme: ThemeData(
brightness: Brightness.dark,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
primary: primaryColor,
onPrimary: Colors.white,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
primary: primaryColor,
),
),
// NOTE: Mainly for the SettingsSection
colorScheme: const ColorScheme.dark(
secondary: primaryColor,
),
backgroundColor: const Color(0xff303030),
),
theme: getThemeData(context, Brightness.light),
darkTheme: getThemeData(context, Brightness.dark),
navigatorKey: widget.navigationKey,
onGenerateRoute: (settings) {
switch (settings.name) {
@@ -298,7 +299,6 @@ class MyAppState extends State<MyApp> with WidgetsBindingObserver {
case settingsRoute: return SettingsPage.route;
case aboutRoute: return SettingsAboutPage.route;
case licensesRoute: return SettingsLicensesPage.route;
case appearanceRoute: return AppearancePage.route;
case networkRoute: return NetworkPage.route;
case privacyRoute: return PrivacyPage.route;
case debuggingRoute: return DebuggingPage.route;
@@ -307,6 +307,16 @@ class MyAppState extends State<MyApp> with WidgetsBindingObserver {
case sendFilesRoute: return SendFilesPage.route;
case backgroundCroppingRoute: return CropBackgroundPage.route;
case shareSelectionRoute: return ShareSelectionPage.route;
case serverInfoRoute: return ServerInfoPage.route;
case conversationSettingsRoute: return ConversationSettingsPage.route;
case devicesRoute: return DevicesPage.route;
case ownDevicesRoute: return OwnDevicesPage.route;
case appearanceRoute: return AppearanceSettingsPage.route;
case qrCodeScannerRoute: return QrCodeScanningPage.getRoute(
settings.arguments! as QrCodeScanningArguments,
);
case stickersRoute: return StickersSettingsPage.route;
case stickerPackRoute: return StickerPackPage.route;
}
return null;

View File

@@ -3,8 +3,8 @@ import 'dart:io';
import 'package:cryptography/cryptography.dart';
import 'package:get_it/get_it.dart';
import 'package:hex/hex.dart';
import 'package:image_size_getter/image_size_getter.dart';
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/service/conversation.dart';
import 'package:moxxyv2/service/preferences.dart';
import 'package:moxxyv2/service/roster.dart';
@@ -13,13 +13,6 @@ import 'package:moxxyv2/service/xmpp.dart';
import 'package:moxxyv2/shared/avatar.dart';
import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/xmpp/connection.dart';
import 'package:moxxyv2/xmpp/managers/namespaces.dart';
import 'package:moxxyv2/xmpp/namespaces.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0030/helpers.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0030/xep_0030.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0054.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0084.dart';
/// Removes line breaks and spaces from [original]. This might happen when we request the
/// avatar data. Returns the cleaned version.
@@ -32,56 +25,48 @@ String _cleanBase64String(String original) {
return ret;
}
class _AvatarData {
const _AvatarData(this.data, this.id);
final List<int> data;
final String id;
}
class AvatarService {
AvatarService() : _log = Logger('AvatarService');
final Logger _log;
final Logger _log = Logger('AvatarService');
UserAvatarManager _getUserAvatarManager() => GetIt.I.get<XmppConnection>().getManagerById<UserAvatarManager>(userAvatarManager)!;
DiscoManager _getDiscoManager() => GetIt.I.get<XmppConnection>().getManagerById<DiscoManager>(discoManager)!;
Future<void> handleAvatarUpdate(AvatarUpdatedEvent event) async {
await updateAvatarForJid(
event.jid,
event.hash,
base64Decode(_cleanBase64String(event.base64)),
);
}
Future<void> updateAvatarForJid(String jid, String hash, String base64) async {
Future<void> updateAvatarForJid(String jid, String hash, List<int> data) async {
final cs = GetIt.I.get<ConversationService>();
final rs = GetIt.I.get<RosterService>();
final originalConversation = await cs.getConversationByJid(jid);
var saved = false;
final originalRoster = await rs.getRosterItemByJid(jid);
// Clean the raw data. Since this may arrive by chunks, those chunks may contain
// weird data pieces.
final base64Data = base64Decode(_cleanBase64String(base64));
if (originalConversation == null && originalRoster == null) return;
final avatarPath = await saveAvatarInCache(
data,
hash,
jid,
(originalConversation?.avatarUrl ?? originalRoster?.avatarUrl)!,
);
if (originalConversation != null) {
final avatarPath = await saveAvatarInCache(
base64Data,
hash,
jid,
originalConversation.avatarUrl,
);
saved = true;
final conv = await cs.updateConversation(
originalConversation.id,
avatarUrl: avatarPath,
);
sendEvent(ConversationUpdatedEvent(conversation: conv));
} else {
_log.warning('Failed to get conversation');
}
final originalRoster = await rs.getRosterItemByJid(jid);
if (originalRoster != null) {
var avatarPath = '';
if (saved) {
avatarPath = await getAvatarPath(jid, hash);
} else {
avatarPath = await saveAvatarInCache(
base64Data,
hash,
jid,
originalRoster.avatarUrl,
);
}
final roster = await rs.updateRosterItem(
originalRoster.id,
avatarUrl: avatarPath,
@@ -91,63 +76,73 @@ class AvatarService {
sendEvent(RosterDiffEvent(modified: [roster]));
}
}
Future<_AvatarData?> _handleUserAvatar(String jid, String oldHash) async {
final am = GetIt.I.get<XmppConnection>()
.getManagerById<UserAvatarManager>(userAvatarManager)!;
final idResult = await am.getAvatarId(jid);
if (idResult.isType<AvatarError>()) {
_log.warning('Failed to get avatar id via XEP-0084 for $jid');
return null;
}
final id = idResult.get<String>();
if (id == oldHash) return null;
final avatarResult = await am.getUserAvatar(jid);
if (avatarResult.isType<AvatarError>()) {
_log.warning('Failed to get avatar data via XEP-0084 for $jid');
return null;
}
final avatar = avatarResult.get<UserAvatar>();
return _AvatarData(
base64Decode(_cleanBase64String(avatar.base64)),
avatar.hash,
);
}
Future<_AvatarData?> _handleVcardAvatar(String jid, String oldHash) async {
// Query the vCard
final vm = GetIt.I.get<XmppConnection>()
.getManagerById<VCardManager>(vcardManager)!;
final vcardResult = await vm.requestVCard(jid);
if (vcardResult.isType<VCardError>()) return null;
final binval = vcardResult.get<VCard>().photo?.binval;
if (binval == null) return null;
final data = base64Decode(_cleanBase64String(binval));
final rawHash = await Sha1().hash(data);
final hash = HEX.encode(rawHash.bytes);
vm.setLastHash(jid, hash);
return _AvatarData(
data,
hash,
);
}
Future<void> fetchAndUpdateAvatarForJid(String jid, String oldHash) async {
final items = (await _getDiscoManager().discoItemsQuery(jid)) ?? [];
final itemNodes = items.map((i) => i.node);
_AvatarData? data;
data ??= await _handleUserAvatar(jid, oldHash);
data ??= await _handleVcardAvatar(jid, oldHash);
_log.finest('Disco items for $jid:');
for (final item in itemNodes) {
_log.finest('- $item');
if (data != null) {
await updateAvatarForJid(jid, data.id, data.data);
}
var base64 = '';
var hash = '';
if (listContains<DiscoItem>(items, (item) => item.node == userAvatarDataXmlns)) {
final avatar = _getUserAvatarManager();
final pubsubHash = await avatar.getAvatarId(jid);
// Don't request if we already have the newest avatar
if (pubsubHash == oldHash) return;
// Query via PubSub
final data = await avatar.getUserAvatar(jid);
if (data == null) return;
base64 = data.base64;
hash = data.hash;
} else {
// Query the vCard
final vm = GetIt.I.get<XmppConnection>().getManagerById<VCardManager>(vcardManager)!;
final vcard = await vm.requestVCard(jid);
if (vcard != null) {
final binval = vcard.photo?.binval;
if (binval != null) {
// Clean the raw data. Since this may arrive by chunks, those chunks may contain
// weird data pieces.
base64 = _cleanBase64String(binval);
final rawHash = await Sha1().hash(base64Decode(base64));
hash = HEX.encode(rawHash.bytes);
vm.setLastHash(jid, hash);
} else {
return;
}
} else {
return;
}
}
await updateAvatarForJid(jid, hash, base64);
}
Future<bool> subscribeJid(String jid) async {
return _getUserAvatarManager().subscribe(jid);
return (await GetIt.I.get<XmppConnection>()
.getManagerById<UserAvatarManager>(userAvatarManager)!
.subscribe(jid)).isType<bool>();
}
Future<bool> unsubscribeJid(String jid) async {
return _getUserAvatarManager().unsubscribe(jid);
return (await GetIt.I.get<XmppConnection>()
.getManagerById<UserAvatarManager>(userAvatarManager)!
.unsubscribe(jid)).isType<bool>();
}
/// Publishes the data at [path] as an avatar with PubSub ID
@@ -161,21 +156,22 @@ class AvatarService {
final public = prefs.isAvatarPublic;
// Read the image metadata
final imageSize = ImageSizeGetter.getSize(MemoryInput(bytes));
final imageSize = (await getImageSizeFromData(bytes))!;
// Publish data and metadata
final manager = _getUserAvatarManager();
await manager.publishUserAvatar(
final am = GetIt.I.get<XmppConnection>()
.getManagerById<UserAvatarManager>(userAvatarManager)!;
await am.publishUserAvatar(
base64,
hash,
public,
);
await manager.publishUserAvatarMetadata(
await am.publishUserAvatarMetadata(
UserAvatarMetadata(
hash,
bytes.length,
imageSize.width,
imageSize.height,
imageSize.width.toInt(),
imageSize.height.toInt(),
// TODO(PapaTutuWawa): Maybe do a check here
'image/png',
),
@@ -186,34 +182,41 @@ class AvatarService {
}
Future<void> requestOwnAvatar() async {
final avatar = _getUserAvatarManager();
final am = GetIt.I.get<XmppConnection>()
.getManagerById<UserAvatarManager>(userAvatarManager)!;
final xmpp = GetIt.I.get<XmppService>();
final state = await xmpp.getXmppState();
final jid = state.jid!;
final id = await avatar.getAvatarId(jid);
final idResult = await am.getAvatarId(jid);
if (idResult.isType<AvatarError>()) {
_log.info('Error while getting latest avatar id for own avatar');
return;
}
final id = idResult.get<String>();
if (id == state.avatarHash) return;
_log.info('Mismatch between saved avatar data and server-side avatar data about ourself');
final data = await avatar.getUserAvatar(jid);
if (data == null) {
final avatarDataResult = await am.getUserAvatar(jid);
if (avatarDataResult.isType<AvatarError>()) {
_log.severe('Failed to fetch our avatar');
return;
}
final avatarData = avatarDataResult.get<UserAvatar>();
_log.info('Received data for our own avatar');
final avatarPath = await saveAvatarInCache(
base64Decode(_cleanBase64String(data.base64)),
data.hash,
base64Decode(_cleanBase64String(avatarData.base64)),
avatarData.hash,
jid,
state.avatarUrl,
);
await xmpp.modifyXmppState((state) => state.copyWith(
avatarUrl: avatarPath,
avatarHash: data.hash,
avatarHash: avatarData.hash,
),);
sendEvent(SelfAvatarChangedEvent(path: avatarPath, hash: data.hash));
sendEvent(SelfAvatarChangedEvent(path: avatarPath, hash: avatarData.hash));
}
}

View File

@@ -1,9 +1,10 @@
import 'dart:async';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/service/database/database.dart';
import 'package:moxxyv2/service/service.dart';
import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/xmpp/connection.dart';
import 'package:moxxyv2/xmpp/managers/namespaces.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0191.dart';
enum BlockPushType {
block,
@@ -11,35 +12,93 @@ enum BlockPushType {
}
class BlocklistService {
BlocklistService() :
_blocklistCache = List.empty(growable: true),
_requestedBlocklist = false;
final List<String> _blocklistCache;
bool _requestedBlocklist;
BlocklistService();
List<String>? _blocklist;
bool _requested = false;
bool? _supported;
final Logger _log = Logger('BlocklistService');
Future<List<String>> _requestBlocklist() async {
final manager = GetIt.I.get<XmppConnection>().getManagerById<BlockingManager>(blockingManager)!;
_blocklistCache
..clear()
..addAll(await manager.getBlocklist());
_requestedBlocklist = true;
return _blocklistCache;
void onNewConnection() {
// Invalidate the caches
_blocklist = null;
_requested = false;
_supported = null;
}
Future<bool> _checkSupport() async {
return _supported ??= await GetIt.I.get<XmppConnection>()
.getManagerById<BlockingManager>(blockingManager)!
.isSupported();
}
Future<void> _requestBlocklist() async {
assert(_blocklist != null, 'The blocklist must be loaded from the database before requesting');
// Check if blocking is supported
if (!(await _checkSupport())) {
_log.warning('Blocklist requested but server does not support it.');
return;
}
final blocklist = await GetIt.I.get<XmppConnection>()
.getManagerById<BlockingManager>(blockingManager)!
.getBlocklist();
// Diff the received blocklist with the cache
final newItems = List<String>.empty(growable: true);
final removedItems = List<String>.empty(growable: true);
final db = GetIt.I.get<DatabaseService>();
for (final item in blocklist) {
if (!_blocklist!.contains(item)) {
await db.addBlocklistEntry(item);
_blocklist!.add(item);
newItems.add(item);
}
}
// Diff the cache with the received blocklist
for (final item in _blocklist!) {
if (!blocklist.contains(item)) {
await db.removeBlocklistEntry(item);
_blocklist!.remove(item);
removedItems.add(item);
}
}
_requested = true;
// Trigger an UI event if we have anything to tell the UI
if (newItems.isNotEmpty || removedItems.isNotEmpty) {
sendEvent(
BlocklistPushEvent(
added: newItems,
removed: removedItems,
),
);
}
}
/// Returns the blocklist from the database
Future<List<String>> getBlocklist() async {
if (!_requestedBlocklist) {
_blocklistCache
..clear()
..addAll(await _requestBlocklist());
if (_blocklist == null) {
_blocklist = await GetIt.I.get<DatabaseService>().getBlocklistEntries();
if (!_requested) {
unawaited(_requestBlocklist());
}
return _blocklist!;
}
return _blocklistCache;
if (!_requested) {
unawaited(_requestBlocklist());
}
return _blocklist!;
}
void onUnblockAllPush() {
_blocklistCache.clear();
_blocklist = List<String>.empty(growable: true);
sendEvent(
BlocklistUnblockAllEvent(),
);
@@ -47,21 +106,25 @@ class BlocklistService {
Future<void> onBlocklistPush(BlockPushType type, List<String> items) async {
// We will fetch it later when getBlocklist is called
if (!_requestedBlocklist) return;
if (!_requested) return;
final newBlocks = List<String>.empty(growable: true);
final removedBlocks = List<String>.empty(growable: true);
for (final item in items) {
switch (type) {
case BlockPushType.block: {
if (_blocklistCache.contains(item)) continue;
_blocklistCache.add(item);
if (_blocklist!.contains(item)) continue;
_blocklist!.add(item);
newBlocks.add(item);
await GetIt.I.get<DatabaseService>().addBlocklistEntry(item);
}
break;
case BlockPushType.unblock: {
_blocklistCache.removeWhere((i) => i == item);
_blocklist!.removeWhere((i) => i == item);
removedBlocks.add(item);
await GetIt.I.get<DatabaseService>().removeBlocklistEntry(item);
}
break;
}
@@ -76,17 +139,47 @@ class BlocklistService {
}
Future<bool> blockJid(String jid) async {
final manager = GetIt.I.get<XmppConnection>().getManagerById<BlockingManager>(blockingManager)!;
return manager.block([ jid ]);
// Check if blocking is supported
if (!(await _checkSupport())) {
_log.warning('Blocking $jid requested but server does not support it.');
return false;
}
_blocklist!.add(jid);
await GetIt.I.get<DatabaseService>()
.addBlocklistEntry(jid);
return GetIt.I.get<XmppConnection>()
.getManagerById<BlockingManager>(blockingManager)!
.block([jid]);
}
Future<bool> unblockJid(String jid) async {
final manager = GetIt.I.get<XmppConnection>().getManagerById<BlockingManager>(blockingManager)!;
return manager.unblock([ jid ]);
// Check if blocking is supported
if (!(await _checkSupport())) {
_log.warning('Unblocking $jid requested but server does not support it.');
return false;
}
_blocklist!.remove(jid);
await GetIt.I.get<DatabaseService>()
.removeBlocklistEntry(jid);
return GetIt.I.get<XmppConnection>()
.getManagerById<BlockingManager>(blockingManager)!
.unblock([jid]);
}
Future<bool> unblockAll() async {
final manager = GetIt.I.get<XmppConnection>().getManagerById<BlockingManager>(blockingManager)!;
return manager.unblockAll();
// Check if blocking is supported
if (!(await _checkSupport())) {
_log.warning('Unblocking all JIDs requested but server does not support it.');
return false;
}
_blocklist!.clear();
await GetIt.I.get<DatabaseService>()
.removeAllBlocklistEntries();
return GetIt.I.get<XmppConnection>()
.getManagerById<BlockingManager>(blockingManager)!
.unblockAll();
}
}

View File

@@ -1,15 +1,13 @@
import 'dart:io' show Platform;
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/service/httpfiletransfer/httpfiletransfer.dart';
import 'package:moxxyv2/service/moxxmpp/reconnect.dart';
import 'package:moxxyv2/xmpp/connection.dart';
class ConnectivityService {
ConnectivityService() : _log = Logger('ConnectivityService');
final Logger _log;

View File

@@ -2,9 +2,10 @@ import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/i18n/strings.g.dart';
import 'package:moxxyv2/service/connectivity.dart';
import 'package:moxxyv2/service/notifications.dart';
import 'package:moxxyv2/xmpp/connection.dart';
class ConnectivityWatcherService {
@@ -17,7 +18,7 @@ class ConnectivityWatcherService {
Future<void> _onTimerElapsed() async {
await GetIt.I.get<NotificationsService>().showWarningNotification(
'Moxxy',
'Could not connect to server',
t.errors.connection.connectionTimeout,
);
_stopTimer();
}

300
lib/service/contacts.dart Normal file
View File

@@ -0,0 +1,300 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:moxxyv2/service/conversation.dart';
import 'package:moxxyv2/service/database/database.dart';
import 'package:moxxyv2/service/preferences.dart';
import 'package:moxxyv2/service/roster.dart';
import 'package:moxxyv2/service/service.dart';
import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/shared/models/roster.dart';
import 'package:permission_handler/permission_handler.dart';
class ContactWrapper {
const ContactWrapper(this.id, this.jid, this.displayName, this.thumbnail);
final String id;
final String jid;
final String displayName;
final Uint8List? thumbnail;
}
class ContactsService {
ContactsService() {
// NOTE: Apparently, this means that if false, contacts that are in 0 groups
// are not returned.
FlutterContacts.config.includeNonVisibleOnAndroid = true;
}
final Logger _log = Logger('ContactsService');
/// JID -> Id
Map<String, String>? _contactIds;
/// Contact ID -> Display name from the contact or null if we cached that there is
/// none
final Map<String, String?> _contactDisplayNames = {};
Future<void> init() async {
if (await _canUseContactIntegration()) {
enableDatabaseListener();
}
}
/// Enable listening to contact database events
void enableDatabaseListener() {
FlutterContacts.addListener(_onContactsDatabaseUpdate);
}
/// Disable listening to contact database events
void disableDatabaseListener() {
FlutterContacts.removeListener(_onContactsDatabaseUpdate);
}
Future<void> _onContactsDatabaseUpdate() async {
_log.finest('Got contacts database update');
await scanContacts();
}
/// Queries the contact list for contacts that include a XMPP URI.
Future<List<ContactWrapper>> _fetchContactsWithJabber() async {
final contacts = await FlutterContacts.getContacts(
withProperties: true,
withThumbnail: true,
);
_log.finest('Got ${contacts.length} contacts');
final jabberContacts = List<ContactWrapper>.empty(growable: true);
for (final c in contacts) {
final index = c.socialMedias
.indexWhere((s) => s.label == SocialMediaLabel.jabber);
if (index == -1) continue;
jabberContacts.add(
ContactWrapper(
c.id,
c.socialMedias[index].userName,
c.displayName,
c.thumbnail,
),
);
}
_log.finest('${jabberContacts.length} contacts have an XMPP address');
return jabberContacts;
}
/// Checks whether the contact integration is enabled by the user in the preferences.
/// Returns true if that is the case. If not, returns false.
Future<bool> isContactIntegrationEnabled() async {
final prefs = await GetIt.I.get<PreferencesService>().getPreferences();
return prefs.enableContactIntegration;
}
/// Checks if we a) have the permission to access the contact list and b) if the
/// user wants to use this integration.
/// Returns true if we can proceed with accessing the contact list. False, if not.
Future<bool> _canUseContactIntegration() async {
if (!(await isContactIntegrationEnabled())) {
_log.finest('_canUseContactIntegration: Returning false since enableContactIntegration is false');
return false;
}
final permission = await Permission.contacts.status;
if (permission == PermissionStatus.denied) {
_log.finest("_canUseContactIntegration: Returning false since we don't have the contacts permission");
return false;
}
return true;
}
/// Queries the database for the mapping of JID -> Contact ID. The result is
/// cached after the first call.
Future<Map<String, String>> _getContactIds() async {
if (_contactIds != null) return _contactIds!;
_contactIds = await GetIt.I.get<DatabaseService>().getContactIds();
return _contactIds!;
}
/// Queries the contact list, if enabled and allowed, and returns the contact's
/// display name.
///
/// [id] is the id of the contact. A null value indicates that there is no
/// contact and null will be returned immediately.
Future<String?> getContactDisplayName(String? id) async {
if (id == null ||
!(await _canUseContactIntegration())) return null;
if (_contactDisplayNames.containsKey(id)) return _contactDisplayNames[id];
final result = await FlutterContacts.getContact(
id,
withThumbnail: false,
);
_contactDisplayNames[id] = result?.displayName;
return result?.displayName;
}
/// Returns the contact Id for the JID [jid]. If either the contact integration is
/// disabled, not possible (due to missing permissions) or there is no contact with
/// [jid] as their Jabber attribute, returns null.
Future<String?> getContactIdForJid(String jid) async {
if (!(await _canUseContactIntegration())) return null;
return (await _getContactIds())[jid];
}
/// Returns the path to the avatar file for the contact with JID [jid] as their
/// Jabber attribute. If either the contact integration is disabled, not possible
/// (due to missing permissions) or there is no contact with [jid] as their Jabber
/// attribute, returns null.
Future<String?> getProfilePicturePathForJid(String jid) async {
final id = await getContactIdForJid(jid);
if (id == null) return null;
final avatarPath = await getContactProfilePicturePath(id);
return File(avatarPath).existsSync() ?
avatarPath :
null;
}
Future<void> scanContacts() async {
final db = GetIt.I.get<DatabaseService>();
final cs = GetIt.I.get<ConversationService>();
final rs = GetIt.I.get<RosterService>();
final contacts = await _fetchContactsWithJabber();
// JID -> Id
final knownContactIds = await _getContactIds();
// Id -> JID
final knownContactIdsReverse = knownContactIds
.map((key, value) => MapEntry(value, key));
final modifiedRosterItems = List<RosterItem>.empty(growable: true);
final addedRosterItems = List<RosterItem>.empty(growable: true);
final removedRosterItems = List<String>.empty(growable: true);
for (final id in List<String>.from(knownContactIds.values)) {
final index = contacts.indexWhere((c) => c.id == id);
if (index != -1) continue;
final jid = knownContactIdsReverse[id]!;
await db.removeContactId(id);
_contactIds!.remove(knownContactIdsReverse[id]);
// Remove the avatar file, if it existed
final avatarPath = await getContactProfilePicturePath(id);
final avatarFile = File(avatarPath);
if (avatarFile.existsSync()) {
unawaited(avatarFile.delete());
}
// Remove the contact attributes from the conversation, if it existed
final c = await cs.getConversationByJid(jid);
if (c != null) {
final newConv = await cs.updateConversation(
c.id,
contactId: null,
contactAvatarPath: null,
contactDisplayName: null,
);
sendEvent(
ConversationUpdatedEvent(
conversation: newConv,
),
);
}
// Remove the contact attributes from the roster item, if it existed
final r = await rs.getRosterItemByJid(jid);
if (r != null) {
if (r.pseudoRosterItem) {
_log.finest('Removing pseudo roster item $jid');
await rs.removeRosterItem(r.id);
removedRosterItems.add(jid);
} else {
final newRosterItem = await rs.updateRosterItem(
r.id,
contactId: null,
contactAvatarPath: null,
contactDisplayName: null,
);
modifiedRosterItems.add(newRosterItem);
}
}
}
for (final contact in contacts) {
// Add the ID to the cache and the database if it does not already exist
if (!knownContactIds.containsKey(contact.jid)) {
await db.addContactId(contact.id, contact.jid);
_contactIds![contact.jid] = contact.id;
}
// Store the avatar image
// NOTE: We do not check if the file already exists since this function may also
// be triggered by the contact database listener. That listener fires when
// a change happened, without telling us exactly what happened. So, we
// just overwrite it.
final contactAvatarPath = await getContactProfilePicturePath(contact.id);
if (contact.thumbnail != null) {
final file = File(contactAvatarPath);
await file.writeAsBytes(contact.thumbnail!);
}
// Update a possibly existing conversation
final c = await cs.getConversationByJid(contact.jid);
if (c != null) {
final newConv = await cs.updateConversation(
c.id,
contactId: contact.id,
contactAvatarPath: contactAvatarPath,
contactDisplayName: contact.displayName,
);
sendEvent(
ConversationUpdatedEvent(
conversation: newConv,
),
);
}
// Update a possibly existing roster item
final r = await rs.getRosterItemByJid(contact.jid);
if (r != null) {
final newRosterItem = await rs.updateRosterItem(
r.id,
contactId: contact.id,
contactAvatarPath: contactAvatarPath,
contactDisplayName: contact.displayName,
);
modifiedRosterItems.add(newRosterItem);
} else {
final newRosterItem = await rs.addRosterItemFromData(
'',
'',
contact.jid,
contact.jid.split('@').first,
'none',
'none',
true,
contact.id,
contactAvatarPath,
contact.displayName,
);
addedRosterItems.add(newRosterItem);
}
}
if (addedRosterItems.isNotEmpty ||
modifiedRosterItems.isNotEmpty ||
removedRosterItems.isNotEmpty) {
sendEvent(
RosterDiffEvent(
added: addedRosterItems,
modified: modifiedRosterItems,
removed: removedRosterItems,
),
);
}
}
}

View File

@@ -1,13 +1,14 @@
import 'package:get_it/get_it.dart';
import 'package:moxxyv2/service/database.dart';
import 'package:moxxyv2/service/db/media.dart';
import 'package:moxlib/moxlib.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/service/database/database.dart';
import 'package:moxxyv2/service/not_specified.dart';
import 'package:moxxyv2/service/preferences.dart';
import 'package:moxxyv2/shared/cache.dart';
import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0085.dart';
import 'package:moxxyv2/shared/models/message.dart';
class ConversationService {
ConversationService()
: _conversationCache = LRUCache(100),
_loadedConversations = false;
@@ -56,27 +57,39 @@ class ConversationService {
/// Wrapper around [DatabaseService]'s [updateConversation] that modifies the cache.
Future<Conversation> updateConversation(int id, {
String? lastMessageBody,
int? lastChangeTimestamp,
bool? open,
int? unreadCounter,
String? avatarUrl,
List<DBSharedMedium>? sharedMedia,
ChatState? chatState,
}
) async {
final conversation = await _getConversationById(id);
final newConversation = await GetIt.I.get<DatabaseService>().updateConversation(
int? lastChangeTimestamp,
Message? lastMessage,
bool? open,
int? unreadCounter,
String? avatarUrl,
ChatState? chatState,
bool? muted,
bool? encrypted,
Object? contactId = notSpecified,
Object? contactAvatarPath = notSpecified,
Object? contactDisplayName = notSpecified,
}) async {
final conversation = (await _getConversationById(id))!;
var newConversation = await GetIt.I.get<DatabaseService>().updateConversation(
id,
lastMessageBody: lastMessageBody,
lastMessage: lastMessage,
lastChangeTimestamp: lastChangeTimestamp,
open: open,
unreadCounter: unreadCounter,
avatarUrl: avatarUrl,
sharedMedia: sharedMedia,
chatState: conversation?.chatState ?? ChatState.gone,
chatState: conversation.chatState,
muted: muted,
encrypted: encrypted,
contactId: contactId,
contactAvatarPath: contactAvatarPath,
contactDisplayName: contactDisplayName,
);
// Copy over the old lastMessage if a new one was not set
if (conversation.lastMessage != null && lastMessage == null) {
newConversation = newConversation.copyWith(lastMessage: conversation.lastMessage);
}
_conversationCache.cache(id, newConversation);
return newConversation;
}
@@ -84,26 +97,45 @@ class ConversationService {
/// Wrapper around [DatabaseService]'s [addConversationFromData] that updates the cache.
Future<Conversation> addConversationFromData(
String title,
String lastMessageBody,
Message? lastMessage,
String avatarUrl,
String jid,
int unreadCounter,
int lastChangeTimestamp,
List<DBSharedMedium> sharedMedia,
bool open,
bool muted,
bool encrypted,
String? contactId,
String? contactAvatarPath,
String? contactDisplayName,
) async {
final newConversation = await GetIt.I.get<DatabaseService>().addConversationFromData(
title,
lastMessageBody,
lastMessage,
avatarUrl,
jid,
unreadCounter,
lastChangeTimestamp,
sharedMedia,
open,
muted,
encrypted,
contactId,
contactAvatarPath,
contactDisplayName,
);
_conversationCache.cache(newConversation.id, newConversation);
return newConversation;
}
/// Returns true if the stanzas to the conversation with [jid] should be encrypted.
/// If not, returns false.
///
/// If the conversation does not exist, then the value of the preference for
/// enableOmemoByDefault is used.
Future<bool> shouldEncryptForConversation(JID jid) async {
final prefs = await GetIt.I.get<PreferencesService>().getPreferences();
final conversation = await getConversationByJid(jid.toString());
return conversation?.encrypted ?? prefs.enableOmemoByDefault;
}
}

View File

@@ -0,0 +1,138 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:logging/logging.dart';
import 'package:moxplatform/moxplatform.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/service/cryptography/types.dart';
List<int> _randomBuffer(int length) {
final buf = List<int>.empty(growable: true);
final random = Random.secure();
for (var i = 0; i < length; i++) {
buf.add(random.nextInt(256));
}
return buf;
}
CipherAlgorithm _sfsToCipher(SFSEncryptionType type) {
switch (type) {
case SFSEncryptionType.aes128GcmNoPadding: return CipherAlgorithm.aes128GcmNoPadding;
case SFSEncryptionType.aes256GcmNoPadding: return CipherAlgorithm.aes256GcmNoPadding;
case SFSEncryptionType.aes256CbcPkcs7: return CipherAlgorithm.aes256CbcPkcs7;
}
}
class CryptographyService {
CryptographyService() : _log = Logger('CryptographyService');
final Logger _log;
/// Encrypt the file at path [source] and write the encrypted data to [dest]. For the
/// encryption, use the algorithm indicated by [encryption].
Future<EncryptionResult> encryptFile(String source, String dest, SFSEncryptionType encryption) async {
_log.finest('Beginning encryption routine for $source');
final key = encryption == SFSEncryptionType.aes128GcmNoPadding ?
_randomBuffer(16) :
_randomBuffer(32);
final iv = _randomBuffer(12);
final result = (await MoxplatformPlugin.crypto.encryptFile(
source,
dest,
Uint8List.fromList(key),
Uint8List.fromList(iv),
_sfsToCipher(encryption),
'SHA-256',
))!;
_log.finest('Encryption done for $source ($result)');
return EncryptionResult(
key,
iv,
<String, String>{
hashSha256: base64Encode(result.plaintextHash),
},
<String, String>{
hashSha256: base64Encode(result.ciphertextHash),
},
);
}
/// Decrypt the file at [source] and write the decrypted version to [dest]. For the
/// decryption, use the algorithm indicated by [encryption] with the key [key] and the
/// IV or nonce [iv].
Future<DecryptionResult> decryptFile(
String source,
String dest,
SFSEncryptionType encryption,
List<int> key,
List<int> iv,
Map<String, String> plaintextHashes,
Map<String, String> ciphertextHashes,
) async {
_log.finest('Beginning decryption for $source');
final result = await MoxplatformPlugin.crypto.encryptFile(
source,
dest,
Uint8List.fromList(key),
Uint8List.fromList(iv),
_sfsToCipher(encryption),
// TODO(Unknown): How to we get hash agility here?
'SHA-256',
);
_log.finest('Decryption done for $source (${result != null})');
var passedPlaintextIntegrityCheck = true;
var passedCiphertextIntegrityCheck = true;
for (final entry in plaintextHashes.entries) {
if (entry.key == hashSha256) {
if (base64Encode(result!.plaintextHash) != entry.value) {
passedPlaintextIntegrityCheck = false;
} else {
passedPlaintextIntegrityCheck = true;
}
break;
}
}
for (final entry in ciphertextHashes.entries) {
if (entry.key == hashSha256) {
if (base64Encode(result!.ciphertextHash) != entry.value) {
passedCiphertextIntegrityCheck = false;
} else {
passedCiphertextIntegrityCheck = true;
}
break;
}
}
return DecryptionResult(
result != null,
passedPlaintextIntegrityCheck,
passedCiphertextIntegrityCheck,
);
}
/// Read the file at [path] and calculate the base64-encoded hash using the algorithm
/// indicated by [hash].
Future<String> hashFile(String path, HashFunction hash) async {
String hashSpec;
if (hash == HashFunction.sha256) {
hashSpec = 'SHA-256';
} else if (hash == HashFunction.sha512) {
hashSpec = 'SHA-512';
} else {
// Android itself does not provide more
throw Exception();
}
_log.finest('Beginning hash generation of $path');
final data = await MoxplatformPlugin.crypto.hashFile(path, hashSpec);
_log.finest('Hash generation done for $path');
return base64Encode(data!);
}
}

View File

@@ -0,0 +1,150 @@
import 'dart:convert';
import 'dart:io';
import 'package:cryptography/cryptography.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/service/cryptography/types.dart';
Future<List<int>> hashFileImpl(HashRequest request) async {
final data = await File(request.path).readAsBytes();
return CryptographicHashManager.hashFromData(data, request.hash);
}
Future<EncryptionResult> encryptFileImpl(EncryptionRequest request) async {
Cipher algorithm;
switch (request.encryption) {
case SFSEncryptionType.aes128GcmNoPadding:
algorithm = AesGcm.with128bits();
break;
case SFSEncryptionType.aes256GcmNoPadding:
algorithm = AesGcm.with256bits();
break;
case SFSEncryptionType.aes256CbcPkcs7:
// TODO(Unknown): Implement
throw Exception();
// ignore: dead_code
break;
}
// Generate a key and an IV for the file
final key = await algorithm.newSecretKey();
final iv = algorithm.newNonce();
final plaintext = await File(request.source).readAsBytes();
final secretBox = await algorithm.encrypt(
plaintext,
secretKey: key,
nonce: iv,
);
final ciphertext = [
...secretBox.cipherText,
...secretBox.mac.bytes,
];
// Write the file
await File(request.dest).writeAsBytes(ciphertext);
return EncryptionResult(
await key.extractBytes(),
iv,
{
hashSha256: base64Encode(
await CryptographicHashManager.hashFromData(plaintext, HashFunction.sha256),
),
},
{
hashSha256: base64Encode(
await CryptographicHashManager.hashFromData(ciphertext, HashFunction.sha256),
),
},
);
}
// TODO(PapaTutuWawa): Somehow fail when the ciphertext hash is not matching the provided data
Future<DecryptionResult> decryptFileImpl(DecryptionRequest request) async {
Cipher algorithm;
switch (request.encryption) {
case SFSEncryptionType.aes128GcmNoPadding:
algorithm = AesGcm.with128bits();
break;
case SFSEncryptionType.aes256GcmNoPadding:
algorithm = AesGcm.with256bits();
break;
case SFSEncryptionType.aes256CbcPkcs7:
// TODO(Unknown): Implement
throw Exception();
// ignore: dead_code
break;
}
final ciphertextRaw = await File(request.source).readAsBytes();
final mac = List<int>.empty(growable: true);
final ciphertext = List<int>.empty(growable: true);
// TODO(PapaTutuWawa): Somehow handle aes256CbcPkcs7
if (request.encryption == SFSEncryptionType.aes128GcmNoPadding ||
request.encryption == SFSEncryptionType.aes256GcmNoPadding) {
mac.addAll(ciphertextRaw.sublist(ciphertextRaw.length - 16));
ciphertext.addAll(ciphertextRaw.sublist(0, ciphertextRaw.length - 16));
}
var passedCiphertextIntegrityCheck = true;
var passedPlaintextIntegrityCheck = true;
// Try to find one hash we can verify
for (final entry in request.ciphertextHashes.entries) {
if ([hashSha256, hashSha512, hashBlake2b512].contains(entry.key)) {
final hash = await CryptographicHashManager.hashFromData(
ciphertext,
hashFunctionFromName(entry.key),
);
if (base64Encode(hash) == entry.value) {
passedCiphertextIntegrityCheck = true;
} else {
passedCiphertextIntegrityCheck = false;
}
break;
}
}
final secretBox = SecretBox(
ciphertext,
nonce: request.iv,
mac: Mac(mac),
);
try {
final data = await algorithm.decrypt(
secretBox,
secretKey: SecretKey(request.key),
);
for (final entry in request.plaintextHashes.entries) {
if ([hashSha256, hashSha512, hashBlake2b512].contains(entry.key)) {
final hash = await CryptographicHashManager.hashFromData(
data,
hashFunctionFromName(entry.key),
);
if (base64Encode(hash) == entry.value) {
passedPlaintextIntegrityCheck = true;
} else {
passedPlaintextIntegrityCheck = false;
}
break;
}
}
await File(request.dest).writeAsBytes(data);
} catch (_) {
return DecryptionResult(
false,
passedPlaintextIntegrityCheck,
passedCiphertextIntegrityCheck,
);
}
return DecryptionResult(
true,
passedPlaintextIntegrityCheck,
passedCiphertextIntegrityCheck,
);
}

View File

@@ -0,0 +1,64 @@
import 'package:meta/meta.dart';
import 'package:moxxmpp/moxxmpp.dart';
@immutable
class EncryptionResult {
const EncryptionResult(this.key, this.iv, this.plaintextHashes, this.ciphertextHashes);
final List<int> key;
final List<int> iv;
final Map<String, String> plaintextHashes;
final Map<String, String> ciphertextHashes;
}
@immutable
class EncryptionRequest {
const EncryptionRequest(this.source, this.dest, this.encryption);
final String source;
final String dest;
final SFSEncryptionType encryption;
}
@immutable
class DecryptionResult {
const DecryptionResult(
this.decryptionOkay,
this.plaintextOkay,
this.ciphertextOkay,
);
final bool decryptionOkay;
final bool plaintextOkay;
final bool ciphertextOkay;
}
@immutable
class DecryptionRequest {
const DecryptionRequest(
this.source,
this.dest,
this.encryption,
this.key,
this.iv,
this.plaintextHashes,
this.ciphertextHashes,
);
final String source;
final String dest;
final SFSEncryptionType encryption;
final List<int> key;
final List<int> iv;
final Map<String, String> plaintextHashes;
final Map<String, String> ciphertextHashes;
}
@immutable
class HashRequest {
const HashRequest(this.path, this.hash);
final String path;
final HashFunction hash;
}

View File

@@ -1,427 +0,0 @@
import 'dart:async';
import 'package:get_it/get_it.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
import 'package:moxxyv2/service/db/conversation.dart';
import 'package:moxxyv2/service/db/media.dart';
import 'package:moxxyv2/service/db/message.dart';
import 'package:moxxyv2/service/db/roster.dart';
import 'package:moxxyv2/service/roster.dart';
import 'package:moxxyv2/shared/error_types.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:moxxyv2/shared/models/media.dart';
import 'package:moxxyv2/shared/models/message.dart';
import 'package:moxxyv2/shared/models/roster.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0085.dart';
import 'package:path_provider/path_provider.dart';
SharedMedium sharedMediumDbToModel(DBSharedMedium s) {
return SharedMedium(
s.id!,
s.path,
s.timestamp,
mime: s.mime,
);
}
Conversation conversationDbToModel(DBConversation c, bool inRoster, String subscription, ChatState chatState) {
final media = c.sharedMedia
.map(sharedMediumDbToModel)
.toList();
// ignore: cascade_invocations
media.sort((a, b) => a.timestamp.compareTo(b.timestamp));
return Conversation(
c.title,
c.lastMessageBody,
c.avatarUrl,
c.jid,
c.unreadCounter,
c.lastChangeTimestamp,
media,
c.id!,
c.open,
inRoster,
subscription,
chatState,
);
}
RosterItem rosterDbToModel(DBRosterItem i) {
return RosterItem(
i.id!,
i.avatarUrl,
i.avatarHash,
i.jid,
i.title,
i.subscription,
i.ask,
i.groups,
);
}
Message messageDbToModel(DBMessage m) {
return Message(
m.sender,
m.body,
m.timestamp,
m.sid,
m.id!,
m.conversationJid,
m.isMedia,
m.isFileUploadNotification,
originId: m.originId,
received: m.received,
displayed: m.displayed,
acked: m.acked,
mediaUrl: m.mediaUrl,
mediaType: m.mediaType,
thumbnailData: m.thumbnailData,
thumbnailDimensions: m.thumbnailDimensions,
srcUrl: m.srcUrl,
quotes: m.quotes.value != null ? messageDbToModel(m.quotes.value!) : null,
errorType: m.errorType,
filename: m.filename,
);
}
class DatabaseService {
DatabaseService() : _log = Logger('DatabaseService');
late Isar _isar;
final Logger _log;
Future<void> initialize() async {
final dir = await getApplicationSupportDirectory();
_isar = await Isar.open(
schemas: [
DBConversationSchema,
DBRosterItemSchema,
DBMessageSchema,
DBSharedMediumSchema
],
directory: dir.path,
);
_log.finest('Database setup done');
}
/// Loads all conversations from the database and adds them to the state and cache.
Future<List<Conversation>> loadConversations() async {
final conversationsRaw = await _isar.dBConversations.where().findAll();
final tmp = List<Conversation>.empty(growable: true);
for (final c in conversationsRaw) {
await c.sharedMedia.load();
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid);
final conv = conversationDbToModel(
c,
rosterItem != null,
rosterItem?.subscription ?? 'none',
ChatState.gone,
);
tmp.add(conv);
}
return tmp;
}
/// Load messages for [jid] from the database.
Future<List<Message>> loadMessagesForJid(String jid) async {
final rawMessages = await _isar.dBMessages.where().conversationJidEqualTo(jid).findAll();
final messages = List<Message>.empty(growable: true);
for (final m in rawMessages) {
await m.quotes.load();
final msg = messageDbToModel(m);
messages.add(msg);
}
return messages;
}
/// Updates the conversation with id [id] inside the database.
Future<Conversation> updateConversation(int id, {
String? lastMessageBody,
int? lastChangeTimestamp,
bool? open,
int? unreadCounter,
String? avatarUrl,
List<DBSharedMedium>? sharedMedia,
ChatState? chatState,
}
) async {
final c = (await _isar.dBConversations.get(id))!;
await c.sharedMedia.load();
if (lastMessageBody != null) {
c.lastMessageBody = lastMessageBody;
}
if (lastChangeTimestamp != null) {
c.lastChangeTimestamp = lastChangeTimestamp;
}
if (open != null) {
c.open = open;
}
if (unreadCounter != null) {
c.unreadCounter = unreadCounter;
}
if (avatarUrl != null) {
c.avatarUrl = avatarUrl;
}
if (sharedMedia != null) {
c.sharedMedia.addAll(sharedMedia);
}
await _isar.writeTxn(() async {
await _isar.dBConversations.put(c);
await c.sharedMedia.save();
});
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid);
final conversation = conversationDbToModel(c, rosterItem != null, rosterItem?.subscription ?? 'none', chatState ?? ChatState.gone);
return conversation;
}
/// Creates a [Conversation] inside the database given the data. This is so that the
/// [Conversation] object can carry its database id.
Future<Conversation> addConversationFromData(
String title,
String lastMessageBody,
String avatarUrl,
String jid,
int unreadCounter,
int lastChangeTimestamp,
List<DBSharedMedium> sharedMedia,
bool open,
) async {
final c = DBConversation()
..jid = jid
..title = title
..avatarUrl = avatarUrl
..lastChangeTimestamp = lastChangeTimestamp
..unreadCounter = unreadCounter
..lastMessageBody = lastMessageBody
..open = open;
c.sharedMedia.addAll(sharedMedia);
await _isar.writeTxn(() async {
await _isar.dBConversations.put(c);
});
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid);
final conversation = conversationDbToModel(c, rosterItem != null, rosterItem?.subscription ?? 'none', ChatState.gone);
return conversation;
}
/// Like [addConversationFromData] but for [SharedMedium].
Future<DBSharedMedium> addSharedMediumFromData(String path, int timestamp, { String? mime }) async {
final s = DBSharedMedium()
..path = path
..mime = mime
..timestamp = timestamp;
await _isar.writeTxn(() async {
await _isar.dBSharedMediums.put(s);
});
return s;
}
/// Same as [addConversationFromData] but for a [Message].
Future<Message> addMessageFromData(
String body,
int timestamp,
String sender,
String conversationJid,
bool isMedia,
String sid,
bool isFileUploadNotification,
{
String? srcUrl,
String? mediaUrl,
String? mediaType,
String? thumbnailData,
String? thumbnailDimensions,
String? originId,
String? quoteId,
String? filename,
}
) async {
final m = DBMessage()
..conversationJid = conversationJid
..timestamp = timestamp
..body = body
..sender = sender
..isMedia = isMedia
..mediaType = mediaType
..mediaUrl = mediaUrl
..srcUrl = srcUrl
..sid = sid
..thumbnailData = thumbnailData
..thumbnailDimensions = thumbnailDimensions
..received = false
..displayed = false
..acked = false
..originId = originId
..errorType = noError
..isFileUploadNotification = isFileUploadNotification
..filename = filename;
if (quoteId != null) {
final quotes = await getMessageByXmppId(quoteId, conversationJid);
if (quotes != null) {
m.quotes.value = quotes;
} else {
_log.warning('Failed to add quote for message with id $quoteId');
}
}
await _isar.writeTxn(() async {
await _isar.dBMessages.put(m);
});
final msg = messageDbToModel(m);
return msg;
}
Future<DBMessage?> getMessageByXmppId(String id, String conversationJid) async {
return _isar.dBMessages.filter()
.conversationJidEqualTo(conversationJid)
.and()
.group((q) => q
.sidEqualTo(id)
.or()
.originIdEqualTo(id),
).findFirst();
}
/// Updates the message item with id [id] inside the database.
Future<Message> updateMessage(int id, {
String? mediaUrl,
String? mediaType,
bool? received,
bool? displayed,
bool? acked,
int? errorType,
bool? isFileUploadNotification,
String? srcUrl,
}) async {
final i = (await _isar.dBMessages.get(id))!;
if (mediaUrl != null) {
i.mediaUrl = mediaUrl;
}
if (mediaType != null) {
i.mediaType = mediaType;
}
if (received != null) {
i.received = received;
}
if (displayed != null) {
i.displayed = displayed;
}
if (acked != null) {
i.acked = acked;
}
if (errorType != null) {
i.errorType = errorType;
}
if (isFileUploadNotification != null) {
i.isFileUploadNotification = isFileUploadNotification;
}
if (srcUrl != null) {
i.srcUrl = srcUrl;
}
await _isar.writeTxn(() async {
await _isar.dBMessages.put(i);
});
await i.quotes.load();
final msg = messageDbToModel(i);
return msg;
}
/// Loads roster items from the database
Future<List<RosterItem>> loadRosterItems() async {
final roster = await _isar.dBRosterItems.where().findAll();
return roster.map(rosterDbToModel).toList();
}
/// Removes a roster item from the database and cache
Future<void> removeRosterItem(int id) async {
await _isar.writeTxn(() async {
await _isar.dBRosterItems.delete(id);
});
}
/// Create a roster item from data
Future<RosterItem> addRosterItemFromData(
String avatarUrl,
String avatarHash,
String jid,
String title,
String subscription,
String ask,
{
List<String> groups = const [],
}
) async {
final rosterItem = DBRosterItem()
..jid = jid
..title = title
..avatarUrl = avatarUrl
..avatarHash = avatarHash
..subscription = subscription
..ask = ask
..groups = groups;
await _isar.writeTxn(() async {
await _isar.dBRosterItems.put(rosterItem);
});
final item = rosterDbToModel(rosterItem);
return item;
}
/// Updates the roster item with id [id] inside the database.
Future<RosterItem> updateRosterItem(
int id, {
String? avatarUrl,
String? avatarHash,
String? title,
String? subscription,
String? ask,
List<String>? groups,
}
) async {
final i = (await _isar.dBRosterItems.get(id))!;
if (avatarUrl != null) {
i.avatarUrl = avatarUrl;
}
if (avatarHash != null) {
i.avatarHash = avatarHash;
}
if (title != null) {
i.title = title;
}
if (groups != null) {
i.groups = groups;
}
if (subscription != null) {
i.subscription = subscription;
}
if (ask != null) {
i.ask = ask;
}
await _isar.writeTxn(() async {
await _isar.dBRosterItems.put(i);
});
final item = rosterDbToModel(i);
return item;
}
}

View File

@@ -0,0 +1,21 @@
const conversationsTable = 'Conversations';
const messagesTable = 'Messages';
const rosterTable = 'RosterItems';
const mediaTable = 'SharedMedia';
const preferenceTable = 'Preferences';
const omemoDeviceTable = 'OmemoDevices';
const omemoDeviceListTable = 'OmemoDeviceList';
const omemoRatchetsTable = 'OmemoSessions';
const omemoTrustCacheTable = 'OmemoTrustCacheList';
const omemoTrustDeviceListTable = 'OmemoTrustDeviceList';
const omemoTrustEnableListTable = 'OmemoTrustEnableList';
const omemoFingerprintCache = 'OmemoFingerprintCache';
const xmppStateTable = 'XmppState';
const contactsTable = 'Contacts';
const stickersTable = 'Stickers';
const stickerPacksTable = 'StickerPacks';
const blocklistTable = 'Blocklist';
const typeString = 0;
const typeInt = 1;
const typeBool = 2;

View File

@@ -0,0 +1,430 @@
import 'package:moxxyv2/service/database/constants.dart';
import 'package:moxxyv2/shared/models/preference.dart';
import 'package:sqflite_sqlcipher/sqflite.dart';
Future<void> configureDatabase(Database db) async {
await db.execute('PRAGMA foreign_keys = OFF');
}
Future<void> createDatabase(Database db, int version) async {
// XMPP state
await db.execute(
'''
CREATE TABLE $xmppStateTable (
key TEXT PRIMARY KEY,
value TEXT
)''',
);
// Messages
await db.execute(
'''
CREATE TABLE $messagesTable (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender TEXT NOT NULL,
body TEXT,
timestamp INTEGER NOT NULL,
sid TEXT NOT NULL,
conversationJid TEXT NOT NULL,
isMedia INTEGER NOT NULL,
isFileUploadNotification INTEGER NOT NULL,
encrypted INTEGER NOT NULL,
errorType INTEGER,
warningType INTEGER,
mediaUrl TEXT,
mediaType TEXT,
thumbnailData TEXT,
mediaWidth INTEGER,
mediaHeight INTEGER,
srcUrl TEXT,
key TEXT,
iv TEXT,
encryptionScheme TEXT,
received INTEGER,
displayed INTEGER,
acked INTEGER,
originId TEXT,
quote_id INTEGER,
filename TEXT,
plaintextHashes TEXT,
ciphertextHashes TEXT,
isDownloading INTEGER NOT NULL,
isUploading INTEGER NOT NULL,
mediaSize INTEGER,
isRetracted INTEGER,
isEdited INTEGER NOT NULL,
reactions TEXT NOT NULL,
containsNoStore INTEGER NOT NULL,
stickerPackId TEXT,
stickerHashKey TEXT,
pseudoMessageType INTEGER,
pseudoMessageData TEXT,
CONSTRAINT fk_quote FOREIGN KEY (quote_id) REFERENCES $messagesTable (id),
)''',
);
// Conversations
await db.execute(
'''
CREATE TABLE $conversationsTable (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT NOT NULL,
title TEXT NOT NULL,
avatarUrl TEXT NOT NULL,
lastChangeTimestamp INTEGER NOT NULL,
unreadCounter INTEGER NOT NULL,
open INTEGER NOT NULL,
muted INTEGER NOT NULL,
encrypted INTEGER NOT NULL,
lastMessageId INTEGER,
contactId TEXT,
contactAvatarPath TEXT,
contactDisplayName TEXT,
CONSTRAINT fk_last_message FOREIGN KEY (lastMessageId) REFERENCES $messagesTable (id),
CONSTRAINT fk_contact_id FOREIGN KEY (contactId) REFERENCES $contactsTable (id)
ON DELETE SET NULL
)''',
);
// Contacts
await db.execute(
'''
CREATE TABLE $contactsTable (
id TEXT PRIMARY KEY,
jid TEXT NOT NULL
)'''
);
// Shared media
await db.execute(
'''
CREATE TABLE $mediaTable (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL,
mime TEXT,
timestamp INTEGER NOT NULL,
conversation_id INTEGER NOT NULL,
message_id INTEGER,
FOREIGN KEY (conversation_id) REFERENCES $conversationsTable (id),
FOREIGN KEY (message_id) REFERENCES $messagesTable (id)
)''',
);
// Roster
await db.execute(
'''
CREATE TABLE $rosterTable (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT NOT NULL,
title TEXT NOT NULL,
avatarUrl TEXT NOT NULL,
avatarHash TEXT NOT NULL,
subscription TEXT NOT NULL,
ask TEXT NOT NULL,
contactId TEXT,
contactAvatarPath TEXT,
contactDisplayName TEXT,
pseudoRosterItem INTEGER NOT NULL,
CONSTRAINT fk_contact_id FOREIGN KEY (contactId) REFERENCES $contactsTable (id)
ON DELETE SET NULL
)''',
);
// Stickers
await db.execute(
'''
CREATE TABLE $stickersTable (
hashKey TEXT PRIMARY KEY,
mediaType TEXT NOT NULL,
desc TEXT NOT NULL,
size INTEGER NOT NULL,
width INTEGER,
height INTEGER,
hashes TEXT NOT NULL,
urlSources TEXT NOT NULL,
path TEXT NOT NULL,
stickerPackId TEXT NOT NULL,
suggests TEXT NOT NULL,
CONSTRAINT fk_sticker_pack FOREIGN KEY (stickerPackId) REFERENCES $stickerPacksTable (id)
ON DELETE CASCADE
)''',
);
await db.execute(
'''
CREATE TABLE $stickerPacksTable (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
hashAlgorithm TEXT NOT NULL,
hashValue TEXT NOT NULL,
restricted INTEGER NOT NULL
)''',
);
// Blocklist
await db.execute(
'''
CREATE TABLE $blocklistTable (
jid TEXT PRIMARY KEY
);
''',
);
// OMEMO
await db.execute(
'''
CREATE TABLE $omemoRatchetsTable (
id INTEGER NOT NULL,
jid TEXT NOT NULL,
dhs TEXT NOT NULL,
dhs_pub TEXT NOT NULL,
dhr TEXT,
rk TEXT NOT NULL,
cks TEXT,
ckr TEXT,
ns INTEGER NOT NULL,
nr INTEGER NOT NULL,
pn INTEGER NOT NULL,
ik_pub TEXT NOT NULL,
session_ad TEXT NOT NULL,
acknowledged INTEGER NOT NULL,
mkskipped TEXT NOT NULL,
kex_timestamp INTEGER NOT NULL,
kex TEXT,
PRIMARY KEY (jid, id)
)''',
);
await db.execute(
'''
CREATE TABLE $omemoTrustCacheTable (
key TEXT PRIMARY KEY NOT NULL,
trust INTEGER NOT NULL
)''',
);
await db.execute(
'''
CREATE TABLE $omemoTrustDeviceListTable (
jid TEXT NOT NULL,
device INTEGER NOT NULL
)''',
);
await db.execute(
'''
CREATE TABLE $omemoTrustEnableListTable (
key TEXT PRIMARY KEY NOT NULL,
enabled INTEGER NOT NULL
)''',
);
await db.execute(
'''
CREATE TABLE $omemoDeviceTable (
jid TEXT NOT NULL,
id INTEGER NOT NULL,
data TEXT NOT NULL,
PRIMARY KEY (jid, id)
)''',
);
await db.execute(
'''
CREATE TABLE $omemoDeviceListTable (
jid TEXT NOT NULL,
id INTEGER NOT NULL,
PRIMARY KEY (jid, id)
)''',
);
await db.execute(
'''
CREATE TABLE $omemoFingerprintCache (
jid TEXT NOT NULL,
id INTEGER NOT NULL,
fingerprint TEXT NOT NULL,
PRIMARY KEY (jid, id)
)''',
);
// Settings
await db.execute(
'''
CREATE TABLE $preferenceTable (
key TEXT NOT NULL PRIMARY KEY,
type INTEGER NOT NULL,
value TEXT NOT NULL
)''',
);
await db.insert(
preferenceTable,
Preference(
'sendChatMarkers',
typeBool,
'true',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'sendChatStates',
typeBool,
'true',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'showSubscriptionRequests',
typeBool,
'true',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'autoDownloadWifi',
typeBool,
'true',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'autoDownloadMobile',
typeBool,
'false',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'maximumAutoDownloadSize',
typeInt,
'15',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'backgroundPath',
typeString,
'',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'isAvatarPublic',
typeBool,
'true',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'autoAcceptSubscriptionRequests',
typeBool,
'false',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'debugEnabled',
typeBool,
'false',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'debugPassphrase',
typeString,
'',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'debugIp',
typeString,
'',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'debugPort',
typeInt,
'-1',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'twitterRedirect',
typeString,
'',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'youtubeRedirect',
typeString,
'',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'enableTwitterRedirect',
typeBool,
'false',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'enableYoutubeRedirect',
typeBool,
'false',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'defaultMuteState',
typeBool,
'false',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'enableOmemoByDefault',
typeBool,
'false',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'languageLocaleCode',
typeString,
'default',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'enableContactIntegration',
typeBool,
'false',
).toDatabaseJson(),
);
await db.insert(
preferenceTable,
Preference(
'isStickersNodePublic',
typeBool,
'true',
).toDatabaseJson(),
);
}

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