Compare commits
366 Commits
v0.2.2
...
05074ed4f0
| Author | SHA1 | Date | |
|---|---|---|---|
| 05074ed4f0 | |||
| 4e4ed58605 | |||
| 6a109fe03d | |||
| a783aab229 | |||
| 43dc2285b3 | |||
| eac8e3fb44 | |||
| 035d29fabc | |||
| ad2b10972c | |||
| e65e1f3ec4 | |||
| 10b86812cd | |||
| fe4c794f68 | |||
| 6115d748e3 | |||
| b0f266bb0a | |||
| 646c99feb5 | |||
| e8461d7059 | |||
| b2efd9f22f | |||
| 8e426b7fd6 | |||
| c13a65b204 | |||
| 503a24e003 | |||
| dfddd3d3d0 | |||
| 26e01bb7f8 | |||
| 5f88626ddf | |||
| e04bb29bb2 | |||
| c9690e028b | |||
| 8709f0bd8e | |||
| 13d7f33c37 | |||
| eac8592536 | |||
| 3e0feaa3e8 | |||
| 85023299d2 | |||
| 98cfb79961 | |||
| f628ec908c | |||
| 6a166a6ef7 | |||
| b10f7dd888 | |||
| 0e768e7768 | |||
| d59a37f185 | |||
| f0c6713d76 | |||
| 0eeac949ea | |||
| 04c0c1c0e2 | |||
| 03011d107f | |||
| c88003bea1 | |||
| 6c83373d72 | |||
| 888a1cf296 | |||
| ed6c01243d | |||
| 5945f78e97 | |||
| c58fca395b | |||
| 9203905ed8 | |||
| 259e0dacd0 | |||
| 0acd13f0f0 | |||
| cfdb948372 | |||
| 9b3130f363 | |||
| d10efc274c | |||
| 2e1b7fab53 | |||
| 91b92b2cc4 | |||
| 6e3ab111f3 | |||
| 7de0b1c00e | |||
| 8107743af2 | |||
| aeff82f625 | |||
| 935cb1c38b | |||
| acd5b7706b | |||
| 0b111d1012 | |||
| 211d8f37d8 | |||
| e2517a7786 | |||
| aaf5b4fecc | |||
| 173e251e9f | |||
| 3c0891e069 | |||
| 2dc3de43d1 | |||
| 1b332b5b3b | |||
| 6ef34afc6d | |||
| 1cd6b63f1e | |||
| 6fd17ee70e | |||
| 050d151c67 | |||
| 25ec569cd8 | |||
| 2dd9847566 | |||
| ef108f2e4a | |||
| 4894c2d627 | |||
| f0861a9a62 | |||
| 221cf89d10 | |||
| c54ef9b84a | |||
| 7aad272316 | |||
| 1f43353360 | |||
| 5b54566c89 | |||
| a9e3d331fc | |||
| 192086546a | |||
| a30b8f888d | |||
| 1e3fc9be3d | |||
| 2ec08c7f68 | |||
| 3adf9b0d00 | |||
| 6fba0f28db | |||
| 9bdc2c09e5 | |||
| 1eb95cde92 | |||
| 719e793860 | |||
| f0e68f7b48 | |||
| 72501bd0b3 | |||
| bd6aaa07c8 | |||
| 77a9f81a1d | |||
| 3eb88f66cf | |||
| 3fb76d59b2 | |||
| 72db2863d0 | |||
| 81ad0cf4db | |||
| b6f2a89e04 | |||
| e2c735b804 | |||
| 3f576ce3e5 | |||
| 1e51e8bb8b | |||
| ad48191b53 | |||
| d851f302cc | |||
| 09ba2122e7 | |||
| 9b16bf6e6f | |||
| ab6b5eefc0 | |||
| 993cd5ed1c | |||
| b733bb4154 | |||
| 2edbbc3ede | |||
| af3013ad67 | |||
| d02fe73952 | |||
| 32444d5a7e | |||
| b003b5e04b | |||
| 5d217a264a | |||
| 8d8c4d2da3 | |||
|
|
943a03bd2c | ||
|
|
680303cfa2 | ||
|
|
e16bbf8acf | ||
| 42ebbdba6d | |||
| a2b477a3dc | |||
| 3c7d5ad5ad | |||
| 4261e26f19 | |||
| a0ee4e1312 | |||
| 93630650fc | |||
| 36b20fa2dd | |||
| ae1c4dd3e6 | |||
| 69438f44b3 | |||
| aceaa01cdb | |||
| 7fe220a630 | |||
| f07599adf2 | |||
| 1b89b16705 | |||
| 0fb1148508 | |||
| 208145d288 | |||
| a6bd60077d | |||
| 387c20a708 | |||
| 1a9d34d347 | |||
| ed4ee53fdb | |||
| 4848a13fa0 | |||
| b9ebd506c8 | |||
| 249f49f7b3 | |||
| db3f5eb066 | |||
| c2f43e0096 | |||
| d81586d026 | |||
| 18a9419cef | |||
| 8a69083c19 | |||
| ba1b79f657 | |||
| 1a2415925f | |||
| 16a597183a | |||
| 2461430869 | |||
| 605201dbc8 | |||
| 214d3250fe | |||
| ea9c634a25 | |||
| 62095cb170 | |||
| aba85c70c1 | |||
| 98569cff69 | |||
| 952dfdc521 | |||
| 93724802d8 | |||
| e5f19e3b8b | |||
| 4479506ee0 | |||
| 359d4508b1 | |||
| ef9ba68790 | |||
| 53ce0d9e54 | |||
| 505921045e | |||
| 2c71e01e5a | |||
| c3cf84ee7d | |||
| 5787a8943d | |||
| 313e276ad6 | |||
| 310891bf16 | |||
| 8852356966 | |||
| 6243766ecc | |||
| 2d5e987fcc | |||
| bf851c2bd6 | |||
| 0327f254a2 | |||
| a0c7078593 | |||
| 0cbea9607e | |||
| e799d516ea | |||
| c630d8f091 | |||
| 2494fbb837 | |||
| cb2560d46f | |||
| 240ed5f859 | |||
| 0365730e0e | |||
| b9d5eab3ea | |||
| 16c84d59dc | |||
| 621c396407 | |||
| 3f2cc3d97a | |||
| ce927308c4 | |||
| df99eb0aab | |||
| e8473d4f5b | |||
| 1175d77c55 | |||
| 570f4ca7d9 | |||
| e4b9c8f1bc | |||
| ea6e7c5d8c | |||
| a462945c98 | |||
| 003d4d65e5 | |||
| 38fa5ab991 | |||
| e22e7b9c90 | |||
| fc6a8eae9d | |||
| 4dab811388 | |||
| 012dc5ec69 | |||
| f72f67342d | |||
| 283ac315d8 | |||
| 1a5b0f372d | |||
| de40e859d7 | |||
| 6140de8eea | |||
| a9fcbd7909 | |||
| a963153c2a | |||
| 8efb743b84 | |||
| f251d6b97b | |||
| 8a5a96d02c | |||
| fc0dd14b4d | |||
| 4d67c157f0 | |||
| a678ef70e7 | |||
| 87d320b6da | |||
| 6b7d3c4b7c | |||
| 8a99f8f6b1 | |||
| 4aa24cc0a1 | |||
| e309f3bbd0 | |||
| a757c45b84 | |||
| ed397e352f | |||
| cf6cec4d32 | |||
| c4422355e3 | |||
| dd947ecd39 | |||
| 968b59aaee | |||
| b9cb023306 | |||
| 031ef140f3 | |||
| 7376607475 | |||
| 1aea6ee588 | |||
| 12717ba25e | |||
| a1fa666cd3 | |||
| 30cfd67e28 | |||
| 068d156da3 | |||
| ce4ed9b0a9 | |||
| b8acbe7359 | |||
| c61485638b | |||
| fd20d5177d | |||
| 2a603e1e41 | |||
| e4d71c5a39 | |||
| 842a6ebe16 | |||
| 14ecc63944 | |||
| 18fb728973 | |||
| a11b75f1cb | |||
| 86abadd6bb | |||
| ab47b06fd6 | |||
| 640ffcb77e | |||
| 26b6abe66b | |||
| c00df84f2a | |||
| 2b7b7a10bc | |||
| 5b18b3d50d | |||
| be24afc8bf | |||
| 5332572b2e | |||
| 6551fda493 | |||
| e3d33f201c | |||
| ea3d550f64 | |||
| 21c1632233 | |||
| 1a66cadb53 | |||
| c8b1330244 | |||
| 4c5204598e | |||
| b8aedc842e | |||
| c1579cb106 | |||
| c1ff949346 | |||
| 3eea6c2ff9 | |||
| d1f826bdb5 | |||
| 5586fcff7a | |||
| f98b18affc | |||
| 28135244c3 | |||
| 47533c7512 | |||
| f79f35e2be | |||
| c43c4a9b24 | |||
| 81a47a12ec | |||
| 9e3a0a0f1d | |||
| 2d0426c0a3 | |||
| 7f366d3f3c | |||
| 3dd1e0461c | |||
| 8036a3a5be | |||
| 29a692de5f | |||
| 4f515d4733 | |||
| 2c28e95bd9 | |||
| e7d354d4c7 | |||
| 5b64506612 | |||
| f2135081ef | |||
| ae9b1a8215 | |||
| 0f5b3d62b1 | |||
| cf63408023 | |||
| 2bfcd52c8e | |||
| 95075c1664 | |||
| 55e639a61c | |||
| fb414c4764 | |||
| 2a53238cad | |||
| 59dfb606ad | |||
| e1271419eb | |||
| a6faa103b6 | |||
| 72de6a4fb5 | |||
| 5c7de2406a | |||
| a6ccc2cc24 | |||
| 7a132d96ca | |||
| 1e2fcf98c7 | |||
| 8fc3df0672 | |||
| 930370c1b5 | |||
| 25677c028e | |||
| 6444e9f1d5 | |||
| 94f9474e2a | |||
| 392606e165 | |||
| f8950d9fb3 | |||
| 6ab4b4062c | |||
| 31464e5025 | |||
| 1f166cbc44 | |||
| 0d8bf8dd12 | |||
| fdbf2534b7 | |||
| 8fd69c4d5a | |||
| 94c8224cf8 | |||
| 7bcccd5558 | |||
| bb987ed257 | |||
| 80f4104969 | |||
| 78ecd57aee | |||
| da66c3fc17 | |||
| 94b75f0eee | |||
| a798b72515 | |||
| 8bd7ef6cfe | |||
| d66a6790f9 | |||
| 50f488ae21 | |||
| 2a18715f77 | |||
| dd39fea3f8 | |||
| 650fcc9215 | |||
| c52bdbecf8 | |||
| 9171f42d09 | |||
| 82885b2bde | |||
| 805a701daa | |||
| 691a1efe16 | |||
| 4f39c995e0 | |||
| 7c5a9bccec | |||
| b8ccbd9722 | |||
| 3c8d942c67 | |||
| 9ef0b851a9 | |||
| 630da4a9e9 | |||
| df4bf316fb | |||
| dc1a17677e | |||
| 5ce8f74aa7 | |||
| 07954bf51b | |||
| b518ddbfb8 | |||
| e5aa20a446 | |||
| dc08515e1d | |||
| a49bce8292 | |||
| 0021d0d84d | |||
| b952b73738 | |||
| ec3ed96771 | |||
| d02a2e74b0 | |||
| 40cb344e0d | |||
| 8c32e42fe9 | |||
| cbceab7cff | |||
| a0864933de | |||
| df7bc4aede | |||
| 009ce28c04 | |||
| 5f087121a3 | |||
| 074381ff67 | |||
| 757e0be5ef | |||
| bb7adceeb7 | |||
| 15a401e681 | |||
| 976478d5bb | |||
| b3fcbfd4fe | |||
| 8ae4821bc8 | |||
| da767a8981 | |||
| ae793f480c | |||
| a039954f9e | |||
| 03a775ef1c |
4
.gitignore
vendored
@@ -52,7 +52,11 @@ app.*.map.json
|
||||
**/*.g.dart
|
||||
**/*.freezed.dart
|
||||
**/*.moxxy.dart
|
||||
lib/i18n/*.dart
|
||||
|
||||
# Direnv
|
||||
.envrc
|
||||
.direnv/
|
||||
|
||||
# Android artifacts
|
||||
.android
|
||||
|
||||
2
.gitlint
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||

|
||||
[<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
|
||||
|
||||
@@ -13,3 +13,6 @@ analyzer:
|
||||
- "**/*.freezed.dart"
|
||||
- "**/*.moxxy.dart"
|
||||
- "test/"
|
||||
- "integration_test/"
|
||||
- "lib/service/database/migrations/*.dart"
|
||||
- "lib/i18n/*.dart"
|
||||
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
-keep class net.sqlcipher.** { *; }
|
||||
@@ -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.
|
||||
-->
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.moxxyv2
|
||||
package org.moxxy.moxxyv2
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
@@ -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.
|
||||
-->
|
||||
|
||||
BIN
assets/fonts/RobotoMono-Regular.ttf
Normal file
256
assets/i18n/strings.i18n.json
Normal file
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"@@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",
|
||||
"closeChat": "Close chat",
|
||||
"closeChatBody": "Are you sure you want to close the chat with ${conversationTitle}?",
|
||||
"markAsRead": "Mark as read"
|
||||
},
|
||||
"conversation": {
|
||||
"unencrypted": "Unencrypted",
|
||||
"encrypted": "Encrypted",
|
||||
"closeChat": "Close chat",
|
||||
"closeChatConfirmTitle": "Close chat",
|
||||
"closeChatConfirmSubtext": "Are you sure you want to close this chat?",
|
||||
"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": "Privacy",
|
||||
"generalSection": "General",
|
||||
"showContactRequests": "Show contact requests",
|
||||
"showContactRequestsSubtext": "This will show people who added you to their contact list but sent no message yet",
|
||||
"profilePictureVisibility": "Make profile picture public",
|
||||
"profilePictureVisibilitSubtext": "If enabled, everyone can see your profile picture. If disabled, only users on your contact list can see your profile picture.",
|
||||
"autoAcceptSubscriptionRequests": "Auto-accept subscription requests",
|
||||
"autoAcceptSubscriptionRequestsSubtext": "If enabled, subscription requests will be automatically accepted if the user is in the contact list.",
|
||||
"conversationsSection": "Conversation",
|
||||
"sendChatMarkers": "Send chat markers",
|
||||
"sendChatMarkersSubtext": "This will tell your conversation partner if you received or read a message",
|
||||
"sendChatStates": "Send chat states",
|
||||
"sendChatStatesSubtext": "This will show your conversation partner if you are typing or looking at the chat",
|
||||
"redirectsSection": "Redirects",
|
||||
"redirectText": "This will redirect $serviceName links that you tap to a proxy service, e.g. $exampleProxy",
|
||||
"currentlySelected": "Currently selected: $proxy",
|
||||
"redirectsTitle": "$serviceName Redirect",
|
||||
"cannotEnableRedirect": "Cannot enable $serviceName redirects",
|
||||
"cannotEnableRedirectSubtext": "You must first set a proxy service to redirect to. To do so, tap the field next to the switch.",
|
||||
"urlEmpty": "URL cannot be empty",
|
||||
"urlInvalid": "Invalid URL",
|
||||
"redirectDialogTitle": "$serviceName Redirect"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
256
assets/i18n/strings_de.i18n.json
Normal file
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"@@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",
|
||||
"closeChat": "Chat schließen",
|
||||
"closeChatBody": "Bist du dir sicher, dass du den Chat mit ${conversationTitle} schließen möchtest?",
|
||||
"markAsRead": "Als gelesen markieren"
|
||||
},
|
||||
"conversation": {
|
||||
"unencrypted": "Unverschlüsselt",
|
||||
"encrypted": "Verschlüsselt",
|
||||
"closeChat": "Chat schließen",
|
||||
"closeChatConfirmTitle": "Chat schließen",
|
||||
"closeChatConfirmSubtext": "Bist Du dir sicher, dass du den Chat schließen möchtest?",
|
||||
"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
@@ -0,0 +1,7 @@
|
||||
targets:
|
||||
$default:
|
||||
builders:
|
||||
slang_build_runner:
|
||||
options:
|
||||
input_directory: assets/i18n
|
||||
output_directory: lib/i18n
|
||||
12
flake.lock
generated
@@ -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": {
|
||||
|
||||
@@ -44,9 +44,7 @@
|
||||
ripgrep # General utilities
|
||||
];
|
||||
|
||||
ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
|
||||
JAVA_HOME = pinnedJDK;
|
||||
ANDROID_AVD_HOME = (toString ./.) + "/.android/avd";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
67
integration_test/backoff_test.dart
Normal 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
@@ -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
|
||||
@@ -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>
|
||||
@@ -1 +0,0 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -1 +0,0 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -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 */;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 980 B |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 11 KiB |
@@ -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"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
@@ -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.
|
||||
@@ -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"}}
|
||||
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 759 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -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"}}
|
||||
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 328 B |
|
Before Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 537 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1 +0,0 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -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,40 @@ files:
|
||||
extends: BackgroundEvent
|
||||
implements:
|
||||
- JsonImplementation
|
||||
- name: GetConversationOmemoFingerprintsResult
|
||||
extends: BackgroundEvent
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
fingerprints:
|
||||
type: List<OmemoDevice>
|
||||
deserialise: true
|
||||
- name: GetOwnOmemoFingerprintsResult
|
||||
extends: BackgroundEvent
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
ownDeviceFingerprint: String
|
||||
ownDeviceId: int
|
||||
fingerprints:
|
||||
type: List<OmemoDevice>
|
||||
deserialise: true
|
||||
- name: RegenerateOwnDeviceResult
|
||||
extends: BackgroundEvent
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
device:
|
||||
type: OmemoDevice
|
||||
deserialise: true
|
||||
- name: MessageNotificationTappedEvent
|
||||
extends: BackgroundEvent
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
conversationJid: String
|
||||
title: String
|
||||
avatarUrl: String
|
||||
generate_builder: true
|
||||
builder_name: "Event"
|
||||
builder_baseclass: "BackgroundEvent"
|
||||
@@ -190,6 +225,8 @@ files:
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
systemLocaleCode: String
|
||||
- name: AddConversationCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
@@ -216,7 +253,7 @@ files:
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
jid: String
|
||||
recipients: List<String>
|
||||
body: String
|
||||
chatState: String
|
||||
quotedMessage:
|
||||
@@ -227,7 +264,7 @@ files:
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
jid: String
|
||||
recipients: List<String>
|
||||
paths: List<String>
|
||||
- name: BlockJidCommand
|
||||
extends: BackgroundCommand
|
||||
@@ -308,6 +345,77 @@ files:
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
- name: SetConversationMuteStatusCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
jid: String
|
||||
muted: bool
|
||||
- name: GetConversationOmemoFingerprintsCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
jid: String
|
||||
- name: SetOmemoDeviceEnabledCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
jid: String
|
||||
deviceId: int
|
||||
enabled: bool
|
||||
- name: RecreateSessionsCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
jid: String
|
||||
- name: SetOmemoEnabledCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
jid: String
|
||||
enabled: bool
|
||||
- name: GetOwnOmemoFingerprintsCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
- name: RemoveOwnDeviceCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
deviceId: int
|
||||
- name: RegenerateOwnDeviceCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
- name: RetractMessageCommentCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
originId: String
|
||||
conversationJid: String
|
||||
- name: MarkConversationAsReadCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
conversationId: int
|
||||
- name: MarkMessageAsReadCommand
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
conversationJid: String
|
||||
sid: String
|
||||
newUnreadCounter: int
|
||||
generate_builder: true
|
||||
# get${builder_Name}FromJson
|
||||
builder_name: "Command"
|
||||
|
||||
140
lib/main.dart
@@ -1,24 +1,31 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxplatform/moxplatform.dart';
|
||||
import 'package:moxxyv2/i18n/strings.g.dart';
|
||||
import 'package:moxxyv2/service/service.dart';
|
||||
import 'package:moxxyv2/shared/commands.dart';
|
||||
import 'package:moxxyv2/shared/synchronized_queue.dart';
|
||||
import 'package:moxxyv2/ui/bloc/addcontact_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/blocklist_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/conversation_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/conversations_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/crop_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/cropbackground_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/devices_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/login_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/navigation_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/newconversation_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/own_devices_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/preferences_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/profile_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/sendfiles_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/server_info_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/share_selection_bloc.dart';
|
||||
import 'package:moxxyv2/ui/bloc/sharedmedia_bloc.dart';
|
||||
import 'package:moxxyv2/ui/constants.dart';
|
||||
import 'package:moxxyv2/ui/events.dart';
|
||||
@@ -34,25 +41,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,30 +76,30 @@ 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
|
||||
// Padding(padding: ..., child: Column(children: [ ... ]))
|
||||
// TODO(Unknown): Theme the switches
|
||||
void main() async {
|
||||
GetIt.I.registerSingleton<Completer<void>>(Completer());
|
||||
|
||||
setupLogging();
|
||||
await setupUIServices();
|
||||
|
||||
@@ -138,16 +151,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
|
||||
@@ -162,8 +189,44 @@ class MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
_setupSharingHandler();
|
||||
|
||||
// Lift the UI block
|
||||
GetIt.I.get<Completer<void>>().complete();
|
||||
GetIt.I.get<SynchronizedQueue<Map<String, dynamic>?>>().removeQueueLock();
|
||||
}
|
||||
|
||||
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 +262,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 +286,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 +293,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;
|
||||
|
||||
@@ -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:');
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
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';
|
||||
import 'package:moxxyv2/shared/models/message.dart';
|
||||
|
||||
class ConversationService {
|
||||
|
||||
ConversationService()
|
||||
: _conversationCache = LRUCache(100),
|
||||
_loadedConversations = false;
|
||||
@@ -56,27 +56,33 @@ class ConversationService {
|
||||
|
||||
/// Wrapper around [DatabaseService]'s [updateConversation] that modifies the cache.
|
||||
Future<Conversation> updateConversation(int id, {
|
||||
String? lastMessageBody,
|
||||
int? lastChangeTimestamp,
|
||||
bool? open,
|
||||
int? unreadCounter,
|
||||
String? avatarUrl,
|
||||
List<DBSharedMedium>? sharedMedia,
|
||||
ChatState? chatState,
|
||||
}
|
||||
) async {
|
||||
final conversation = await _getConversationById(id);
|
||||
final newConversation = await GetIt.I.get<DatabaseService>().updateConversation(
|
||||
int? lastChangeTimestamp,
|
||||
Message? lastMessage,
|
||||
bool? open,
|
||||
int? unreadCounter,
|
||||
String? avatarUrl,
|
||||
ChatState? chatState,
|
||||
bool? muted,
|
||||
bool? encrypted,
|
||||
}) async {
|
||||
final conversation = (await _getConversationById(id))!;
|
||||
var newConversation = await GetIt.I.get<DatabaseService>().updateConversation(
|
||||
id,
|
||||
lastMessageBody: lastMessageBody,
|
||||
lastMessage: lastMessage,
|
||||
lastChangeTimestamp: lastChangeTimestamp,
|
||||
open: open,
|
||||
unreadCounter: unreadCounter,
|
||||
avatarUrl: avatarUrl,
|
||||
sharedMedia: sharedMedia,
|
||||
chatState: conversation?.chatState ?? ChatState.gone,
|
||||
chatState: conversation.chatState,
|
||||
muted: muted,
|
||||
encrypted: encrypted,
|
||||
);
|
||||
|
||||
// Copy over the old lastMessage if a new one was not set
|
||||
if (conversation.lastMessage != null && lastMessage == null) {
|
||||
newConversation = newConversation.copyWith(lastMessage: conversation.lastMessage);
|
||||
}
|
||||
|
||||
_conversationCache.cache(id, newConversation);
|
||||
return newConversation;
|
||||
}
|
||||
@@ -84,26 +90,39 @@ class ConversationService {
|
||||
/// Wrapper around [DatabaseService]'s [addConversationFromData] that updates the cache.
|
||||
Future<Conversation> addConversationFromData(
|
||||
String title,
|
||||
String lastMessageBody,
|
||||
Message? lastMessage,
|
||||
String avatarUrl,
|
||||
String jid,
|
||||
int unreadCounter,
|
||||
int lastChangeTimestamp,
|
||||
List<DBSharedMedium> sharedMedia,
|
||||
bool open,
|
||||
bool muted,
|
||||
bool encrypted,
|
||||
) async {
|
||||
final newConversation = await GetIt.I.get<DatabaseService>().addConversationFromData(
|
||||
title,
|
||||
lastMessageBody,
|
||||
lastMessage,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
138
lib/service/cryptography/cryptography.dart
Normal 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!);
|
||||
}
|
||||
}
|
||||
150
lib/service/cryptography/implementations.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
64
lib/service/cryptography/types.dart
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
16
lib/service/database/constants.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
const conversationsTable = 'Conversations';
|
||||
const messagesTable = 'Messages';
|
||||
const rosterTable = 'RosterItems';
|
||||
const mediaTable = 'SharedMedia';
|
||||
const preferenceTable = 'Preferences';
|
||||
const omemoDeviceTable = 'OmemoDevices';
|
||||
const omemoDeviceListTable = 'OmemoDeviceList';
|
||||
const omemoRatchetsTable = 'OmemoSessions';
|
||||
const omemoTrustCacheTable = 'OmemoTrustCacheList';
|
||||
const omemoTrustDeviceListTable = 'OmemoTrustDeviceList';
|
||||
const omemoTrustEnableListTable = 'OmemoTrustEnableList';
|
||||
const xmppStateTable = 'XmppState';
|
||||
|
||||
const typeString = 0;
|
||||
const typeInt = 1;
|
||||
const typeBool = 2;
|
||||
338
lib/service/database/creation.dart
Normal file
@@ -0,0 +1,338 @@
|
||||
import 'package:moxxyv2/service/database/constants.dart';
|
||||
import 'package:moxxyv2/shared/models/preference.dart';
|
||||
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||
|
||||
Future<void> configureDatabase(Database db) async {
|
||||
await db.execute('PRAGMA foreign_keys = OFF');
|
||||
}
|
||||
|
||||
Future<void> createDatabase(Database db, int version) async {
|
||||
// XMPP state
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $xmppStateTable (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
)''',
|
||||
);
|
||||
|
||||
// Messages
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $messagesTable (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sender TEXT NOT NULL,
|
||||
body TEXT,
|
||||
timestamp INTEGER NOT NULL,
|
||||
sid TEXT NOT NULL,
|
||||
conversationJid TEXT NOT NULL,
|
||||
isMedia INTEGER NOT NULL,
|
||||
isFileUploadNotification INTEGER NOT NULL,
|
||||
encrypted INTEGER NOT NULL,
|
||||
errorType INTEGER,
|
||||
warningType INTEGER,
|
||||
mediaUrl TEXT,
|
||||
mediaType TEXT,
|
||||
thumbnailData TEXT,
|
||||
mediaWidth INTEGER,
|
||||
mediaHeight INTEGER,
|
||||
srcUrl TEXT,
|
||||
key TEXT,
|
||||
iv TEXT,
|
||||
encryptionScheme TEXT,
|
||||
received INTEGER,
|
||||
displayed INTEGER,
|
||||
acked INTEGER,
|
||||
originId TEXT,
|
||||
quote_id INTEGER,
|
||||
filename TEXT,
|
||||
plaintextHashes TEXT,
|
||||
ciphertextHashes TEXT,
|
||||
isDownloading INTEGER NOT NULL,
|
||||
isUploading INTEGER NOT NULL,
|
||||
mediaSize INTEGER,
|
||||
isRetracted INTEGER,
|
||||
CONSTRAINT fk_quote FOREIGN KEY (quote_id) REFERENCES $messagesTable (id)
|
||||
)''',
|
||||
);
|
||||
|
||||
// Conversations
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $conversationsTable (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
avatarUrl TEXT NOT NULL,
|
||||
lastChangeTimestamp INTEGER NOT NULL,
|
||||
unreadCounter INTEGER NOT NULL,
|
||||
open INTEGER NOT NULL,
|
||||
muted INTEGER NOT NULL,
|
||||
encrypted INTEGER NOT NULL,
|
||||
lastMessageId INTEGER NOT NULL,
|
||||
CONSTRAINT fk_last_message FOREIGN KEY (lastMessageId) REFERENCES $messagesTable (id)
|
||||
)''',
|
||||
);
|
||||
|
||||
// Shared media
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $mediaTable (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL,
|
||||
mime TEXT,
|
||||
timestamp INTEGER NOT NULL,
|
||||
conversation_id INTEGER NOT NULL,
|
||||
message_id INTEGER,
|
||||
FOREIGN KEY (conversation_id) REFERENCES $conversationsTable (id),
|
||||
FOREIGN KEY (message_id) REFERENCES $messagesTable (id)
|
||||
)''',
|
||||
);
|
||||
|
||||
// Roster
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $rosterTable (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
avatarUrl TEXT NOT NULL,
|
||||
avatarHash TEXT NOT NULL,
|
||||
subscription TEXT NOT NULL,
|
||||
ask TEXT NOT NULL
|
||||
)''',
|
||||
);
|
||||
|
||||
// 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(),
|
||||
);
|
||||
}
|
||||