344 Commits

Author SHA1 Message Date
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
5ce8f74aa7 release: Bump to 0.2.3 2022-08-28 15:30:04 +02:00
07954bf51b docs: Add a link to IzzyOnDroid's repository 2022-08-28 15:28:48 +02:00
b518ddbfb8 ui: Fix all messages being left aligned (closes #101) 2022-08-28 15:24:58 +02:00
e5aa20a446 meta: Change Android App ID to org.moxxy.moxxyv2 2022-08-28 12:52:48 +02:00
dc08515e1d meta: Update moxplatform 2022-08-28 12:41:33 +02:00
a49bce8292 Merge pull request 'Implement sharing media with Moxxy' (#98) from feat/sharing-intent into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/98
2022-08-28 11:55:46 +02:00
0021d0d84d ui: Make colors a bit better
I was too lazy to move it into its own branch (or master)
2022-08-28 00:08:26 +02:00
b952b73738 ui: Update the ShareSelectionBloc on roster and conversation changes 2022-08-27 23:10:08 +02:00
ec3ed96771 ui: Prevent overflows of the lastMessageBody 2022-08-27 22:44:59 +02:00
d02a2e74b0 ui: Fix weird placement of the FAB
This might be caused due to some weird interaction between
FloatingActionButton and Visibility
2022-08-27 22:37:35 +02:00
40cb344e0d ui: Also allow tapping the checkboxes 2022-08-27 22:29:58 +02:00
8c32e42fe9 service: Allow sharing text with others 2022-08-27 22:26:08 +02:00
cbceab7cff ui: Fix share selection not resetting itself 2022-08-27 21:56:54 +02:00
a0864933de service: Move blurhash thumbnail generation further back 2022-08-27 21:56:40 +02:00
df7bc4aede ui: Set the isLoggedIn in more places 2022-08-27 21:33:24 +02:00
009ce28c04 ui: Prevent sharing being accepted before a login 2022-08-27 21:14:39 +02:00
5f087121a3 ui: Replace radio buttons with checkboxes 2022-08-27 19:41:58 +02:00
074381ff67 ui: Make going back minimize the app 2022-08-27 19:36:24 +02:00
757e0be5ef service: Implement a sharing page 2022-08-27 19:32:22 +02:00
bb7adceeb7 ui: Stub out the sharing page 2022-08-26 22:59:17 +02:00
15a401e681 android: Change AppID to org.moxxy.moxxyv2 2022-08-26 20:59:35 +02:00
976478d5bb Merge pull request 'Privacy Redirects' (#97) from feat/privacy-redirects into master
Reviewed-on: https://codeberg.org/moxxy/moxxyv2/pulls/97
2022-08-26 13:38:34 +02:00
b3fcbfd4fe ui: Redirect Youtube and Twitter links 2022-08-26 13:32:47 +02:00
8ae4821bc8 ui: Move privacy.dart into the privacy folder 2022-08-26 13:11:09 +02:00
da767a8981 ui: Fix the redirect settings 2022-08-26 13:09:50 +02:00
ae793f480c ui: Stub things out 2022-08-25 22:35:51 +02:00
a039954f9e ui: Migrate away from deprecated flutter_settings_ui 2022-08-25 21:46:55 +02:00
03a775ef1c ui: Make URLs clickable 2022-08-25 21:12:57 +02:00
346 changed files with 8975 additions and 13903 deletions

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,9 +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/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
@@ -14,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

View File

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

View File

@@ -43,7 +43,7 @@ android {
}
defaultConfig {
applicationId "me.polynom.moxxyv2"
applicationId "org.moxxy.moxxyv2"
// TODO: Remove once https://github.com/fluttercommunity/flutter_launcher_icons/pull/313 is merged
minSdkVersion 23

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

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

View File

@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.moxxyv2">
package="org.moxxy.moxxyv2">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.moxxyv2">
package="org.moxxy.moxxyv2">
<application
android:label="Moxxy"
android:name="${applicationName}"
@@ -7,7 +7,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
@@ -18,16 +18,26 @@
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:resource="@style/NormalTheme" />
<meta-data
android:name="flutterEmbedding"
android:value="2"
/>
android:value="2" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Allow receiving share intents for all kinds of things -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
@@ -36,6 +46,8 @@
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" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />

View File

@@ -1,4 +1,4 @@
package com.example.moxxyv2
package org.moxxy.moxxyv2
import io.flutter.embedding.android.FlutterActivity

View File

@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.moxxyv2">
package="org.moxxy.moxxyv2">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

Binary file not shown.

View File

@@ -0,0 +1,253 @@
{
"@@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"
}
},
"messages": {
"image": "Image",
"video": "Video",
"audio": "Audio",
"file": "File",
"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"
},
"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"
}
},
"warnings": {
"message": {
"integrityCheckFailed": "Could not verify file integrity"
}
},
"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"
},
"conversation": {
"unencrypted": "Unencrypted",
"encrypted": "Encrypted",
"closeChat": "Close chat",
"closeChatConfirmTitle": "Close chat",
"closeChatConfirmSubtext": "Are you sure you want to close this chat?",
"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"
},
"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": {
"self": {
"devices": "Devices"
},
"conversation": {
"muteChatTooltip": "Mute chat",
"unmuteChatTooltip": "Unmute chat",
"muteChat": "Mute",
"unmuteChat": "Unmute",
"devices": "Devices"
},
"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."
},
"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",
"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"
},
"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",
"wifi": "Wifi",
"mobileData": "Mobile data"
},
"privacy": {
"title": "Pricacy",
"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"
}
}
}
}

View File

@@ -0,0 +1,253 @@
{
"@@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"
}
},
"messages": {
"image": "Bild",
"video": "Video",
"audio": "Audio",
"file": "Datei",
"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"
},
"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"
}
},
"warnings": {
"message": {
"integrityCheckFailed": "Konnte Integrität der Datei nicht überprüfen"
}
},
"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"
},
"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?",
"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"
},
"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": {
"self": {
"devices": "Geräte"
},
"conversation": {
"muteChatTooltip": "Chat stummschalten",
"unmuteChatTooltip": "Chat lautstellen",
"muteChat": "Stummschalten",
"unmuteChat": "Lautstellen",
"devices": "Geräte"
},
"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": "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": "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."
},
"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",
"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"
},
"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",
"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"
}
}
}
}

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

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:
@@ -69,8 +71,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:
@@ -109,7 +110,7 @@ files:
- JsonImplementation
attributes:
id: int
progress: double
progress: double?
# Triggered by [RosterService] if we receive a roster push.
- name: RosterDiffEvent
extends: BackgroundEvent
@@ -172,6 +173,32 @@ 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
generate_builder: true
builder_name: "Event"
builder_baseclass: "BackgroundEvent"
@@ -190,6 +217,8 @@ files:
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
systemLocaleCode: String
- name: AddConversationCommand
extends: BackgroundCommand
implements:
@@ -216,7 +245,7 @@ files:
implements:
- JsonImplementation
attributes:
jid: String
recipients: List<String>
body: String
chatState: String
quotedMessage:
@@ -227,7 +256,7 @@ files:
implements:
- JsonImplementation
attributes:
jid: String
recipients: List<String>
paths: List<String>
- name: BlockJidCommand
extends: BackgroundCommand
@@ -308,6 +337,63 @@ 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: RetractMessageComment
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
originId: String
conversationJid: String
generate_builder: true
# get${builder_Name}FromJson
builder_name: "Command"

View File

@@ -1,10 +1,12 @@
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/ui/bloc/addcontact_bloc.dart';
@@ -13,12 +15,16 @@ 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/constants.dart';
import 'package:moxxyv2/ui/events.dart';
@@ -34,25 +40,31 @@ 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.dart';
import 'package:moxxyv2/ui/pages/settings/privacy/privacy.dart';
import 'package:moxxyv2/ui/pages/settings/settings.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/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}');
@@ -63,22 +75,24 @@ void setupLogging() {
Future<void> setupUIServices() async {
GetIt.I.registerSingleton<UIProgressService>(UIProgressService());
GetIt.I.registerSingleton<UIDataService>(UIDataService());
GetIt.I.registerSingleton<ThumbnailCacheService>(ThumbnailCacheService());
await GetIt.I.get<UIDataService>().init();}
}
void setupBlocs(GlobalKey<NavigatorState> navKey) {
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navigationKey: 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());
GetIt.I.registerSingleton<CropBloc>(CropBloc());
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());
}
// TODO(Unknown): Replace all Column(children: [ Padding(), Padding, ...]) with a
@@ -138,16 +152,30 @@ void main() async {
),
BlocProvider<CropBackgroundBloc>(
create: (_) => GetIt.I.get<CropBackgroundBloc>(),
)
),
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>(),
),
],
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
@@ -164,6 +192,42 @@ class MyAppState extends State<MyApp> with WidgetsBindingObserver {
// Lift the UI block
GetIt.I.get<Completer<void>>().complete();
_setupSharingHandler();
}
Future<void> _handleSharedMedia(SharedMedia media) async {
final attachments = media.attachments ?? [];
GetIt.I.get<ShareSelectionBloc>().add(
ShareSelectionRequestedEvent(
attachments.map((a) => a!.path).toList(),
media.content,
media.content != null ? ShareSelectionType.text : ShareSelectionType.media,
),
);
}
Future<void> _setupSharingHandler() async {
final handler = ShareHandlerPlatform.instance;
final media = await handler.getInitialSharedMedia();
// Shared while the app was closed
if (media != null) {
if (GetIt.I.get<UIDataService>().isLoggedIn) {
await _handleSharedMedia(media);
}
await handler.resetInitialSharedMedia();
}
// Shared while the app is stil running
handler.sharedMediaStream.listen((SharedMedia media) async {
if (GetIt.I.get<UIDataService>().isLoggedIn) {
await _handleSharedMedia(media);
}
await handler.resetInitialSharedMedia();
});
}
@override
@@ -199,44 +263,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) {
@@ -255,7 +287,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;
@@ -263,6 +294,12 @@ class MyAppState extends State<MyApp> with WidgetsBindingObserver {
case cropRoute: return CropPage.route;
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;
}
return null;

View File

@@ -5,6 +5,8 @@ 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:moxlib/moxlib.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/service/conversation.dart';
import 'package:moxxyv2/service/preferences.dart';
import 'package:moxxyv2/service/roster.dart';
@@ -12,14 +14,6 @@ import 'package:moxxyv2/service/service.dart';
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.
@@ -93,7 +87,10 @@ class AvatarService {
}
Future<void> fetchAndUpdateAvatarForJid(String jid, String oldHash) async {
final items = (await _getDiscoManager().discoItemsQuery(jid)) ?? [];
final response = await _getDiscoManager().discoItemsQuery(jid);
final items = response.isType<DiscoError>() ?
<DiscoItem>[] :
response.get<List<DiscoItem>>();
final itemNodes = items.map((i) => i.node);
_log.finest('Disco items for $jid:');

View File

@@ -1,9 +1,7 @@
import 'package:get_it/get_it.dart';
import 'package:moxxmpp/moxxmpp.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,

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();
}

View File

@@ -1,13 +1,12 @@
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/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';
class ConversationService {
ConversationService()
: _conversationCache = LRUCache(100),
_loadedConversations = false;
@@ -56,25 +55,30 @@ 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 {
String? lastMessageBody,
int? lastChangeTimestamp,
bool? lastMessageRetracted,
int? lastMessageId,
bool? open,
int? unreadCounter,
String? avatarUrl,
ChatState? chatState,
bool? muted,
bool? encrypted,
}) async {
final conversation = await _getConversationById(id);
final newConversation = await GetIt.I.get<DatabaseService>().updateConversation(
id,
lastMessageBody: lastMessageBody,
lastMessageRetracted: lastMessageRetracted,
lastMessageId: lastMessageId,
lastChangeTimestamp: lastChangeTimestamp,
open: open,
unreadCounter: unreadCounter,
avatarUrl: avatarUrl,
sharedMedia: sharedMedia,
chatState: conversation?.chatState ?? ChatState.gone,
muted: muted,
encrypted: encrypted,
);
_conversationCache.cache(id, newConversation);
@@ -84,26 +88,43 @@ class ConversationService {
/// Wrapper around [DatabaseService]'s [addConversationFromData] that updates the cache.
Future<Conversation> addConversationFromData(
String title,
int lastMessageId,
bool lastMessageRetracted,
String lastMessageBody,
String avatarUrl,
String jid,
int unreadCounter,
int lastChangeTimestamp,
List<DBSharedMedium> sharedMedia,
bool open,
bool muted,
bool encrypted,
) async {
final newConversation = await GetIt.I.get<DatabaseService>().addConversationFromData(
title,
lastMessageId,
lastMessageRetracted,
lastMessageBody,
avatarUrl,
jid,
unreadCounter,
lastChangeTimestamp,
sharedMedia,
open,
muted,
encrypted,
);
_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,16 @@
const conversationsTable = 'Conversations';
const messsagesTable = '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 xmppStateTable = 'XmppState';
const typeString = 0;
const typeInt = 1;
const typeBool = 2;

View File

@@ -0,0 +1,339 @@
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 = ON');
}
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 $messsagesTable (
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,
CONSTRAINT fk_quote FOREIGN KEY (quote_id) REFERENCES $messsagesTable (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,
lastMessageBody TEXT NOT NULL,
open INTEGER NOT NULL,
muted INTEGER NOT NULL,
encrypted INTEGER NOT NULL,
lastMessageId INTEGER NOT NULL,
lastMessageRetracted INTEGER 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 $messsagesTable (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
)''',
);
// 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)
)''',
);
// 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(),
);
}

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