Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ee9e0ec6c | ||
|
|
886cad559c | ||
|
|
5fb89f3d3c | ||
|
|
268f8c9bbe | ||
|
|
7dd50cebab | ||
|
|
22fb136cc9 | ||
|
|
88b167c50d | ||
|
|
07dd6f0df6 | ||
|
|
2377f536a3 | ||
|
|
7b353482f6 | ||
|
|
e8b8a8a71c | ||
|
|
6859816e25 | ||
|
|
6b9082e209 | ||
|
|
8c2efa2486 | ||
|
|
b948eb45a9 | ||
|
|
b341f41c8f | ||
|
|
77f16a0caf | ||
|
|
1d9a89d0b3 | ||
|
|
ef804f4931 | ||
|
|
a4ca55d7c6 | ||
|
|
6d41950910 | ||
|
|
87eb10e134 | ||
|
|
0f6bfc6ecb | ||
|
|
164129f09d | ||
|
|
f5e67369ee | ||
|
|
5382b0c022 | ||
|
|
ee08ec2597 | ||
|
|
e1447ad132 | ||
|
|
03e5519715 | ||
|
|
753f39ce93 | ||
|
|
6082d1789d | ||
|
|
2b0dc59829 | ||
|
|
f8cc649da5 | ||
|
|
ba96b02f84 | ||
|
|
6a026b772a | ||
|
|
6e66ddcd53 | ||
|
|
96df682541 | ||
|
|
17c37807bd | ||
|
|
30be70fec6 | ||
|
|
62ae0eeb17 | ||
|
|
deaa30a5f3 | ||
|
|
86c9bea815 | ||
|
|
8ac94776ed | ||
|
|
403fce8b52 | ||
|
|
c0d4b7001a | ||
|
|
a5d77946ce | ||
|
|
0d992cc914 | ||
|
|
5a140fb749 | ||
|
|
108bd1159a | ||
|
|
cf0e6386ba | ||
|
|
f0a976e25a | ||
|
|
9fe9879c72 | ||
|
|
1b5049c3d7 | ||
|
|
9ee2a28e02 | ||
|
|
1e7ff72e55 | ||
|
|
82d2c7e03a | ||
|
|
51e256ab9c | ||
|
|
e50452a6e6 | ||
|
|
6b21124d6e | ||
|
|
f2dae6438a | ||
|
|
ebf41cdc76 | ||
|
|
9983addfdd | ||
|
|
49f7a61717 | ||
|
|
cfb68b904c | ||
|
|
3f440777e5 | ||
|
|
01710cbcb7 | ||
|
|
d8c2c41a67 | ||
|
|
7d86e94613 | ||
|
|
ff6ea4af09 | ||
|
|
5eab316b47 | ||
|
|
f6291f8d7a | ||
|
|
28f7400d8a | ||
|
|
1cc63476b6 | ||
|
|
75697fe57d | ||
|
|
13de85a3d3 | ||
|
|
52158a8672 | ||
|
|
9ddecc91f7 | ||
|
|
c692777d66 | ||
|
|
390074a2c3 | ||
|
|
fb40704cb8 | ||
|
|
4bd931c29f | ||
|
|
fe86f757f0 | ||
|
|
694db33bb0 | ||
|
|
233e00b750 | ||
|
|
78ad5f14b0 | ||
|
|
27be14082d | ||
|
|
e3c9bcc6a3 | ||
|
|
8936223b84 | ||
|
|
eace1ed600 | ||
|
|
e8da25a674 | ||
|
|
4802a8b2c8 | ||
|
|
de266b1cde | ||
|
|
2403be2231 | ||
|
|
e7d7d88bef | ||
|
|
f50729240d | ||
|
|
0a4c69944a | ||
|
|
4dc86f78b2 | ||
|
|
ae3e0c64ce | ||
|
|
fee57e5426 | ||
|
|
c49e24c8b4 | ||
|
|
2667816ecb | ||
|
|
a0880c4c06 | ||
|
|
87e8e713db | ||
|
|
7d8202f68e | ||
|
|
ce29fa7c6b | ||
|
|
28b01666c6 | ||
|
|
a64b7999f2 | ||
|
|
c75079a5ce | ||
|
|
0c4566ddcf | ||
|
|
cbc2d98d52 | ||
|
|
f06b0c10e6 | ||
|
|
9490c22914 | ||
|
|
3488f2ee8e | ||
|
|
c274502392 | ||
|
|
c2e5378a74 | ||
|
|
f9fe3621a6 | ||
|
|
0dc45a8d1b | ||
|
|
f634d57404 | ||
|
|
c588640c7b | ||
|
|
bd586f7370 | ||
|
|
b5d71b783c | ||
|
|
e64a43ff64 | ||
|
|
f207a7deb9 | ||
|
|
12b801711f | ||
|
|
9409db25d6 | ||
|
|
131beadea9 | ||
|
|
6d3c4a96fa | ||
|
|
7e3f4563d1 | ||
|
|
b68aba25b7 | ||
|
|
0698e9dd6b | ||
|
|
15f4af4708 | ||
|
|
2d7566ffc1 | ||
|
|
cfeccc6f62 | ||
|
|
e6e8916e3d | ||
|
|
80755d85d6 | ||
|
|
4e222ec6d1 | ||
|
|
8cbcc864e3 |
4
.idea/assetWizardSettings.xml
generated
@@ -34,7 +34,7 @@
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="scalingPercent" value="78" />
|
||||
<entry key="scalingPercent" value="75" />
|
||||
<entry key="trimmed" value="true" />
|
||||
</map>
|
||||
</option>
|
||||
@@ -47,7 +47,7 @@
|
||||
<map>
|
||||
<entry key="backgroundAssetType" value="COLOR" />
|
||||
<entry key="backgroundColor" value="cccccc" />
|
||||
<entry key="foregroundImage" value="$PROJECT_DIR$/images/6_foreground_3_bis.png" />
|
||||
<entry key="foregroundImage" value="$PROJECT_DIR$/images/4.png" />
|
||||
<entry key="webIconShape" value="NONE" />
|
||||
</map>
|
||||
</option>
|
||||
|
||||
BIN
.idea/caches/build_file_checksums.ser
generated
60
FAQ.md
@@ -1,8 +1,10 @@
|
||||
FairEmail
|
||||
=========
|
||||
# FairEmail
|
||||
|
||||
Frequently Asked Questions
|
||||
--------------------------
|
||||
If you have a feature request or found a bug, you can report it [as an issue](https://github.com/M66B/open-source-email/issues).
|
||||
|
||||
If you have a question, please check the frequently asked questions below first. At the bottom you can find how to ask other questions.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
<a name="FAQ1"></a>
|
||||
**(1) Which permissions are needed and why?**
|
||||
@@ -28,12 +30,14 @@ Most, if not all, other email apps don't show a notification with the "side effe
|
||||
|
||||
The low priority status bar notification shows the number of pending operations, which can be:
|
||||
|
||||
* SEEN: mark message as seen/unseen in remote folder
|
||||
* ADD: add message to remote folder
|
||||
* MOVE: move message to another remote folder
|
||||
* DELETE: delete message from remote folder
|
||||
* SEND: send message
|
||||
* ATTACHMENT download attachment
|
||||
* seen: mark message as seen/unseen in remote folder
|
||||
* add: add message to remote folder
|
||||
* move: move message to another remote folder
|
||||
* delete: delete message from remote folder
|
||||
* send: send message
|
||||
* attachment: download attachment
|
||||
* headers: download message headers
|
||||
* flag: star/unstar remote message
|
||||
|
||||
<a name="FAQ4"></a>
|
||||
**(4) What is a valid security certificate?**
|
||||
@@ -45,6 +49,9 @@ Valid security certificates are officially signed (not self signed) and have mat
|
||||
|
||||
Without [IMAP IDLE](https://en.wikipedia.org/wiki/IMAP_IDLE) emails need to be periodically fetched,
|
||||
which is a waste of battery power and internet bandwidth and will delay notification of new emails.
|
||||
Since the goal of FairEmail is to offer safe and fast email, providers without IMAP IDLE are not supported.
|
||||
You should consider this a problem of the provider, not of the app.
|
||||
Almost all email providers offer IMAP IDLE, with as notable exception Yahoo!
|
||||
|
||||
<a name="FAQ6"></a>
|
||||
**(6) How can I login to Gmail / G suite?**
|
||||
@@ -100,29 +107,30 @@ The latter is both safer and more inconvenient because you'll need to login to w
|
||||
Chrome Custom Tabs are used by default, which can be changed in the advanced options in the setup screen.
|
||||
|
||||
<a name="FAQ13"></a>
|
||||
**(13) How does search on server work?**
|
||||
**(13) How does progressive search work?**
|
||||
|
||||
You can start searching for messages in a folder on the server by using the magnify glass in the action bar of a folder.
|
||||
The server is requested to search on sender, subject and message text.
|
||||
The server executes the search request and determines if the search is case sensitive,
|
||||
if searching will be done on whole words and which messages will be search through.
|
||||
Results will be shown in real time as they become available from the server.
|
||||
For performance reasons attachments are not downloaded and shown.
|
||||
Search on server is a pro feature.
|
||||
You can start searching for messages on sender, subject or text by using the magnify glass in the action bar of a folder.
|
||||
First messages are searched on device, then the server is requested to search.
|
||||
Scrolling down will download more messages from the server.
|
||||
Searching on device is case insensitive and on partial text.
|
||||
Searching on the server might be case sensitive or case insensitive and might be on partial text or whole words, depending on the provider.
|
||||
Progressive search is a pro feature.
|
||||
|
||||
<a name="FAQ14"></a>
|
||||
**(14) How does openPGP integration work?**
|
||||
**(14) How can I setup Outlook with 2FA?**
|
||||
|
||||
You need to install and setup the [OpenKeychain](https://play.google.com/store/apps/details?id=org.sufficientlysecure.keychain).
|
||||
To use Outlook with two factor authentication enabled, you need to create an app password.
|
||||
See [here](https://support.microsoft.com/en-us/help/12409/microsoft-account-app-passwords-two-step-verification) for the details.
|
||||
|
||||
You can send an encrypted message by composing a message as usual and encrypt it by using the *Encrypt* overflow menu just before sending.
|
||||
<a name="FAQ15"></a>
|
||||
**(15) Can you add ... ?**
|
||||
|
||||
If you received an encrypted message as attachment, you can decrypt it by downloading and viewing the attachment with the encrypted text.
|
||||
|
||||
If you received an encrypted message with inline encryption, you can decrypt it by using the *Decrypt* overflow menu.
|
||||
* More themes / account colors: the goal is to keep the app as simple as possible, so this will not be added.
|
||||
* LED notifications: there are less and less devices with a notification light and if there is one, it can mostly be managed by Android, so there is little point in adding support for this.
|
||||
* Encryption: there is too little interest in sending/receiving encrypted messages to justifiy putting effort into this.
|
||||
* POP/poll support: besides that any decent provider is supporting / should support IMAP, polling does consume extra battery power and will delay notification of new messages, so this will not be added.
|
||||
|
||||
<br>
|
||||
|
||||
If you have another question, you can use [this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168).
|
||||
|
||||
If you have a feature request or found a bug, you can report it [as an issue](https://github.com/M66B/open-source-email/issues).
|
||||
Registration is free.
|
||||
|
||||
30
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Question
|
||||
|
||||
* For questions, please use [this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168).
|
||||
|
||||
# Feature request
|
||||
|
||||
* Did you check if there wasn't a similar feature request?
|
||||
* Did you read [this FAQ](https://github.com/M66B/open-source-email/blob/master/FAQ.md#FAQ15)?
|
||||
|
||||
# Bug report
|
||||
|
||||
* Did you check if there wasn't a similar bug report?
|
||||
* Are you using the [latest version](https://github.com/M66B/open-source-email/releases) of the app?
|
||||
|
||||
## Expected behavior
|
||||
|
||||
|
||||
## Actual behavior
|
||||
|
||||
|
||||
## Steps to reproduce the problem
|
||||
|
||||
1.
|
||||
1.
|
||||
1.
|
||||
|
||||
## Version
|
||||
|
||||
* App version:
|
||||
* Android version:
|
||||
14
README.md
@@ -7,7 +7,7 @@ This email app might be for you if your current email app:
|
||||
|
||||
* takes long to receive or show messages
|
||||
* can manage only one mailbox
|
||||
* cannot show related messages
|
||||
* cannot show conversations
|
||||
* cannot work offline
|
||||
* looks outdated
|
||||
* is not maintained
|
||||
@@ -31,10 +31,10 @@ Features
|
||||
Pro features
|
||||
------------
|
||||
|
||||
* Signatures
|
||||
* Standard replies
|
||||
* Progressive search (first local, then server)
|
||||
* Preview sender/subject in new messages status bar notification
|
||||
* Encrypt/decrypt messages using [OpenPGP](https://www.openpgp.org/)
|
||||
* Search on server
|
||||
* Standard answers
|
||||
|
||||
Simple
|
||||
------
|
||||
@@ -48,8 +48,8 @@ Secure
|
||||
|
||||
* Allow encrypted connections only
|
||||
* Accept valid security certificates only
|
||||
* [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) authentication required
|
||||
* Text view only (converted [HTML](https://en.wikipedia.org/wiki/HTML))
|
||||
* Authentication required
|
||||
* Optional text view only (converted [HTML](https://en.wikipedia.org/wiki/HTML))
|
||||
* No special permissions required
|
||||
* No advertisements
|
||||
* No analytics and no tracking
|
||||
@@ -57,7 +57,7 @@ Secure
|
||||
Efficient
|
||||
---------
|
||||
|
||||
* [IMAP IDLE](https://en.wikipedia.org/wiki/IMAP_IDLE) supported
|
||||
* [IMAP IDLE](https://en.wikipedia.org/wiki/IMAP_IDLE) (push messages) supported
|
||||
* Built with latest development tools and libraries
|
||||
* Android 6 Marshmallow or later required
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ android {
|
||||
applicationId "eu.faircode.email"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 28
|
||||
versionCode 32
|
||||
versionName "0.32"
|
||||
versionCode 41
|
||||
versionName "0.41"
|
||||
archivesBaseName = "FairEmail-v$versionName"
|
||||
|
||||
javaCompileOptions {
|
||||
@@ -19,11 +19,13 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
debuggable = false
|
||||
minifyEnabled = true
|
||||
useProguard = true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
debuggable = true
|
||||
minifyEnabled = true
|
||||
useProguard = true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
@@ -78,7 +80,6 @@ dependencies {
|
||||
def javamail_version = "1.6.0"
|
||||
def jsoup_version = "1.11.3"
|
||||
def jcharset_version = "2.0"
|
||||
def openpgp_version = "12.0"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$androidx_version"
|
||||
implementation "androidx.recyclerview:recyclerview:$androidx_version"
|
||||
@@ -102,6 +103,4 @@ dependencies {
|
||||
implementation "org.jsoup:jsoup:$jsoup_version"
|
||||
|
||||
implementation "net.freeutils:jcharset:$jcharset_version"
|
||||
|
||||
implementation "org.sufficientlysecure:openpgp-api:$openpgp_version"
|
||||
}
|
||||
|
||||
919
app/schemas/eu.faircode.email.DB/10.json
Normal file
@@ -0,0 +1,919 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 10,
|
||||
"identityHash": "8a267f8f3cb9ef409377dbdb7cb706d4",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "identity",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyto",
|
||||
"columnName": "replyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starttls",
|
||||
"columnName": "starttls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_identity_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "poll_interval",
|
||||
"columnName": "poll_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen_until",
|
||||
"columnName": "seen_until",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "folder",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `unified` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `after` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unified",
|
||||
"columnName": "unified",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "after",
|
||||
"columnName": "after",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folder_account_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"account",
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_type",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"type"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_unified",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"unified"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_unified` ON `${TABLE_NAME}` (`unified`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "identity",
|
||||
"columnName": "identity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "replying",
|
||||
"columnName": "replying",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "msgid",
|
||||
"columnName": "msgid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "references",
|
||||
"columnName": "references",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "inreplyto",
|
||||
"columnName": "inreplyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thread",
|
||||
"columnName": "thread",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from",
|
||||
"columnName": "from",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to",
|
||||
"columnName": "to",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "cc",
|
||||
"columnName": "cc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bcc",
|
||||
"columnName": "bcc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reply",
|
||||
"columnName": "reply",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "headers",
|
||||
"columnName": "headers",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subject",
|
||||
"columnName": "subject",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "sent",
|
||||
"columnName": "sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "received",
|
||||
"columnName": "received",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "stored",
|
||||
"columnName": "stored",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen",
|
||||
"columnName": "seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_seen",
|
||||
"columnName": "ui_seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_hide",
|
||||
"columnName": "ui_hide",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_found",
|
||||
"columnName": "ui_found",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_message_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_identity",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"identity"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_replying",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"replying"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder_uid",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"folder",
|
||||
"uid"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_msgid_folder",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"msgid",
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_thread",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"thread"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_received",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"received"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_seen",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_seen"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_hide",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_hide"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_found",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_found"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "identity",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"identity"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"replying"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "attachment",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sequence",
|
||||
"columnName": "sequence",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "available",
|
||||
"columnName": "available",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_attachment_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
|
||||
},
|
||||
{
|
||||
"name": "index_attachment_message_sequence",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"message",
|
||||
"sequence"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "operation",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "args",
|
||||
"columnName": "args",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_operation_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_operation_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "answer",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_log_time",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"time"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"8a267f8f3cb9ef409377dbdb7cb706d4\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
925
app/schemas/eu.faircode.email.DB/11.json
Normal file
@@ -0,0 +1,925 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 11,
|
||||
"identityHash": "d0a6171ec8d9a64a1c65e8c7e5d0348a",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "identity",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyto",
|
||||
"columnName": "replyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starttls",
|
||||
"columnName": "starttls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_identity_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `signature` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "signature",
|
||||
"columnName": "signature",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "poll_interval",
|
||||
"columnName": "poll_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen_until",
|
||||
"columnName": "seen_until",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "folder",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `unified` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `after` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unified",
|
||||
"columnName": "unified",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "after",
|
||||
"columnName": "after",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folder_account_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"account",
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_type",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"type"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_unified",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"unified"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_unified` ON `${TABLE_NAME}` (`unified`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "identity",
|
||||
"columnName": "identity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "replying",
|
||||
"columnName": "replying",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "msgid",
|
||||
"columnName": "msgid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "references",
|
||||
"columnName": "references",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "inreplyto",
|
||||
"columnName": "inreplyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thread",
|
||||
"columnName": "thread",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from",
|
||||
"columnName": "from",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to",
|
||||
"columnName": "to",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "cc",
|
||||
"columnName": "cc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bcc",
|
||||
"columnName": "bcc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reply",
|
||||
"columnName": "reply",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "headers",
|
||||
"columnName": "headers",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subject",
|
||||
"columnName": "subject",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "sent",
|
||||
"columnName": "sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "received",
|
||||
"columnName": "received",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "stored",
|
||||
"columnName": "stored",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen",
|
||||
"columnName": "seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_seen",
|
||||
"columnName": "ui_seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_hide",
|
||||
"columnName": "ui_hide",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_found",
|
||||
"columnName": "ui_found",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_message_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_identity",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"identity"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_replying",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"replying"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder_uid",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"folder",
|
||||
"uid"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_msgid_folder",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"msgid",
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_thread",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"thread"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_received",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"received"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_seen",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_seen"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_hide",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_hide"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_found",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_found"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "identity",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"identity"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"replying"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "attachment",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sequence",
|
||||
"columnName": "sequence",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "available",
|
||||
"columnName": "available",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_attachment_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
|
||||
},
|
||||
{
|
||||
"name": "index_attachment_message_sequence",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"message",
|
||||
"sequence"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "operation",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "args",
|
||||
"columnName": "args",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_operation_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_operation_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "answer",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_log_time",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"time"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"d0a6171ec8d9a64a1c65e8c7e5d0348a\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
937
app/schemas/eu.faircode.email.DB/12.json
Normal file
@@ -0,0 +1,937 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 12,
|
||||
"identityHash": "fe661376a25d2e8b6a9f3e1fa9956daf",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "identity",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyto",
|
||||
"columnName": "replyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starttls",
|
||||
"columnName": "starttls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_identity_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `signature` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "signature",
|
||||
"columnName": "signature",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "poll_interval",
|
||||
"columnName": "poll_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen_until",
|
||||
"columnName": "seen_until",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "folder",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `unified` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `after` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unified",
|
||||
"columnName": "unified",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "after",
|
||||
"columnName": "after",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folder_account_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"account",
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_type",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"type"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_unified",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"unified"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_unified` ON `${TABLE_NAME}` (`unified`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "identity",
|
||||
"columnName": "identity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "replying",
|
||||
"columnName": "replying",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "msgid",
|
||||
"columnName": "msgid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "references",
|
||||
"columnName": "references",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "inreplyto",
|
||||
"columnName": "inreplyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thread",
|
||||
"columnName": "thread",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from",
|
||||
"columnName": "from",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to",
|
||||
"columnName": "to",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "cc",
|
||||
"columnName": "cc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bcc",
|
||||
"columnName": "bcc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reply",
|
||||
"columnName": "reply",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "headers",
|
||||
"columnName": "headers",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subject",
|
||||
"columnName": "subject",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "sent",
|
||||
"columnName": "sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "received",
|
||||
"columnName": "received",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "stored",
|
||||
"columnName": "stored",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen",
|
||||
"columnName": "seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "flagged",
|
||||
"columnName": "flagged",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_seen",
|
||||
"columnName": "ui_seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_flagged",
|
||||
"columnName": "ui_flagged",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_hide",
|
||||
"columnName": "ui_hide",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_found",
|
||||
"columnName": "ui_found",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_message_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_identity",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"identity"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_replying",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"replying"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder_uid",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"folder",
|
||||
"uid"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_msgid_folder",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"msgid",
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_thread",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"thread"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_received",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"received"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_seen",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_seen"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_hide",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_hide"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_found",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_found"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "identity",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"identity"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"replying"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "attachment",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sequence",
|
||||
"columnName": "sequence",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "available",
|
||||
"columnName": "available",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_attachment_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
|
||||
},
|
||||
{
|
||||
"name": "index_attachment_message_sequence",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"message",
|
||||
"sequence"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "operation",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "args",
|
||||
"columnName": "args",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_operation_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_operation_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "answer",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_log_time",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"time"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"fe661376a25d2e8b6a9f3e1fa9956daf\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
850
app/schemas/eu.faircode.email.DB/6.json
Normal file
@@ -0,0 +1,850 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 6,
|
||||
"identityHash": "e9ae946be1049502f01f8f30275abc2f",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "identity",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyto",
|
||||
"columnName": "replyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starttls",
|
||||
"columnName": "starttls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_identity_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "poll_interval",
|
||||
"columnName": "poll_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen_until",
|
||||
"columnName": "seen_until",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "folder",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `synchronize` INTEGER NOT NULL, `after` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "after",
|
||||
"columnName": "after",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folder_account_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"account",
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_type",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"type"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "identity",
|
||||
"columnName": "identity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "replying",
|
||||
"columnName": "replying",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "msgid",
|
||||
"columnName": "msgid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "references",
|
||||
"columnName": "references",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "inreplyto",
|
||||
"columnName": "inreplyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thread",
|
||||
"columnName": "thread",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from",
|
||||
"columnName": "from",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to",
|
||||
"columnName": "to",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "cc",
|
||||
"columnName": "cc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bcc",
|
||||
"columnName": "bcc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reply",
|
||||
"columnName": "reply",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subject",
|
||||
"columnName": "subject",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "sent",
|
||||
"columnName": "sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "received",
|
||||
"columnName": "received",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "stored",
|
||||
"columnName": "stored",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen",
|
||||
"columnName": "seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_seen",
|
||||
"columnName": "ui_seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_hide",
|
||||
"columnName": "ui_hide",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_found",
|
||||
"columnName": "ui_found",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_message_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_identity",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"identity"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_replying",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"replying"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder_uid",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"folder",
|
||||
"uid"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_msgid_folder",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"msgid",
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_thread",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"thread"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_received",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"received"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_seen",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_seen"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_hide",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_hide"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "identity",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"identity"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"replying"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "attachment",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sequence",
|
||||
"columnName": "sequence",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "available",
|
||||
"columnName": "available",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_attachment_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
|
||||
},
|
||||
{
|
||||
"name": "index_attachment_message_sequence",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"message",
|
||||
"sequence"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "operation",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "args",
|
||||
"columnName": "args",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_operation_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_operation_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "answer",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"e9ae946be1049502f01f8f30275abc2f\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
891
app/schemas/eu.faircode.email.DB/7.json
Normal file
@@ -0,0 +1,891 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "78658430615109b7c163e62ed1ad0a48",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "identity",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyto",
|
||||
"columnName": "replyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starttls",
|
||||
"columnName": "starttls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_identity_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "poll_interval",
|
||||
"columnName": "poll_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen_until",
|
||||
"columnName": "seen_until",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "folder",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `synchronize` INTEGER NOT NULL, `after` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "after",
|
||||
"columnName": "after",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folder_account_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"account",
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_type",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"type"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "identity",
|
||||
"columnName": "identity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "replying",
|
||||
"columnName": "replying",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "msgid",
|
||||
"columnName": "msgid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "references",
|
||||
"columnName": "references",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "inreplyto",
|
||||
"columnName": "inreplyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thread",
|
||||
"columnName": "thread",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from",
|
||||
"columnName": "from",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to",
|
||||
"columnName": "to",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "cc",
|
||||
"columnName": "cc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bcc",
|
||||
"columnName": "bcc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reply",
|
||||
"columnName": "reply",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subject",
|
||||
"columnName": "subject",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "sent",
|
||||
"columnName": "sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "received",
|
||||
"columnName": "received",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "stored",
|
||||
"columnName": "stored",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen",
|
||||
"columnName": "seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_seen",
|
||||
"columnName": "ui_seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_hide",
|
||||
"columnName": "ui_hide",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_found",
|
||||
"columnName": "ui_found",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_message_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_identity",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"identity"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_replying",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"replying"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder_uid",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"folder",
|
||||
"uid"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_msgid_folder",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"msgid",
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_thread",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"thread"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_received",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"received"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_seen",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_seen"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_hide",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_hide"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "identity",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"identity"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"replying"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "attachment",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sequence",
|
||||
"columnName": "sequence",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "available",
|
||||
"columnName": "available",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_attachment_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
|
||||
},
|
||||
{
|
||||
"name": "index_attachment_message_sequence",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"message",
|
||||
"sequence"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "operation",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "args",
|
||||
"columnName": "args",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_operation_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_operation_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "answer",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_log_time",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"time"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"78658430615109b7c163e62ed1ad0a48\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
899
app/schemas/eu.faircode.email.DB/8.json
Normal file
@@ -0,0 +1,899 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 8,
|
||||
"identityHash": "6127ad940456ed43d7551f7cd7b7ed18",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "identity",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyto",
|
||||
"columnName": "replyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starttls",
|
||||
"columnName": "starttls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_identity_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "poll_interval",
|
||||
"columnName": "poll_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen_until",
|
||||
"columnName": "seen_until",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "folder",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `synchronize` INTEGER NOT NULL, `after` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "after",
|
||||
"columnName": "after",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folder_account_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"account",
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_type",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"type"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "identity",
|
||||
"columnName": "identity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "replying",
|
||||
"columnName": "replying",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "msgid",
|
||||
"columnName": "msgid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "references",
|
||||
"columnName": "references",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "inreplyto",
|
||||
"columnName": "inreplyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thread",
|
||||
"columnName": "thread",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from",
|
||||
"columnName": "from",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to",
|
||||
"columnName": "to",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "cc",
|
||||
"columnName": "cc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bcc",
|
||||
"columnName": "bcc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reply",
|
||||
"columnName": "reply",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subject",
|
||||
"columnName": "subject",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "sent",
|
||||
"columnName": "sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "received",
|
||||
"columnName": "received",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "stored",
|
||||
"columnName": "stored",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen",
|
||||
"columnName": "seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_seen",
|
||||
"columnName": "ui_seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_hide",
|
||||
"columnName": "ui_hide",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_found",
|
||||
"columnName": "ui_found",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_message_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_identity",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"identity"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_replying",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"replying"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder_uid",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"folder",
|
||||
"uid"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_msgid_folder",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"msgid",
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_thread",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"thread"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_received",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"received"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_seen",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_seen"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_hide",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_hide"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_found",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_found"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "identity",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"identity"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"replying"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "attachment",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sequence",
|
||||
"columnName": "sequence",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "available",
|
||||
"columnName": "available",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_attachment_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
|
||||
},
|
||||
{
|
||||
"name": "index_attachment_message_sequence",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"message",
|
||||
"sequence"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "operation",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "args",
|
||||
"columnName": "args",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_operation_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_operation_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "answer",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_log_time",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"time"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6127ad940456ed43d7551f7cd7b7ed18\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
905
app/schemas/eu.faircode.email.DB/9.json
Normal file
@@ -0,0 +1,905 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 9,
|
||||
"identityHash": "67fade7db3a87ec2ef27dcec483c456f",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "identity",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyto",
|
||||
"columnName": "replyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starttls",
|
||||
"columnName": "starttls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_identity_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "auth_type",
|
||||
"columnName": "auth_type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "primary",
|
||||
"columnName": "primary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "store_sent",
|
||||
"columnName": "store_sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "poll_interval",
|
||||
"columnName": "poll_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen_until",
|
||||
"columnName": "seen_until",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "folder",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `synchronize` INTEGER NOT NULL, `after` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synchronize",
|
||||
"columnName": "synchronize",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "after",
|
||||
"columnName": "after",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folder_account_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"account",
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
|
||||
},
|
||||
{
|
||||
"name": "index_folder_type",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"type"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "account",
|
||||
"columnName": "account",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "identity",
|
||||
"columnName": "identity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "replying",
|
||||
"columnName": "replying",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "msgid",
|
||||
"columnName": "msgid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "references",
|
||||
"columnName": "references",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "inreplyto",
|
||||
"columnName": "inreplyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thread",
|
||||
"columnName": "thread",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from",
|
||||
"columnName": "from",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to",
|
||||
"columnName": "to",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "cc",
|
||||
"columnName": "cc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bcc",
|
||||
"columnName": "bcc",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reply",
|
||||
"columnName": "reply",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "headers",
|
||||
"columnName": "headers",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subject",
|
||||
"columnName": "subject",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "sent",
|
||||
"columnName": "sent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "received",
|
||||
"columnName": "received",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "stored",
|
||||
"columnName": "stored",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seen",
|
||||
"columnName": "seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_seen",
|
||||
"columnName": "ui_seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_hide",
|
||||
"columnName": "ui_hide",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_found",
|
||||
"columnName": "ui_found",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_message_account",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"account"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_identity",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"identity"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_replying",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"replying"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_folder_uid",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"folder",
|
||||
"uid"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_msgid_folder",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"msgid",
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_thread",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"thread"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_received",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"received"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_seen",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_seen"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_hide",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_hide"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_ui_found",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"ui_found"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"account"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "identity",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"identity"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"replying"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "attachment",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sequence",
|
||||
"columnName": "sequence",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "available",
|
||||
"columnName": "available",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_attachment_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
|
||||
},
|
||||
{
|
||||
"name": "index_attachment_message_sequence",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"message",
|
||||
"sequence"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "operation",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "folder",
|
||||
"columnName": "folder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "args",
|
||||
"columnName": "args",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_operation_folder",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
|
||||
},
|
||||
{
|
||||
"name": "index_operation_message",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"message"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "folder",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"folder"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "message",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"message"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "answer",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_log_time",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"time"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"67fade7db3a87ec2ef27dcec483c456f\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -62,17 +62,32 @@
|
||||
android:parentActivityName=".ActivityView">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SENDTO" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<data android:mimeType="*/*" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SENDTO" />
|
||||
<data android:scheme="mailto" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="mailto" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name=".ServiceSynchronize" />
|
||||
|
||||
<service
|
||||
android:name=".JobDaily"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}"
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
214
app/src/main/java/eu/faircode/email/ActivityBilling.java
Normal file
@@ -0,0 +1,214 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.BillingClientStateListener;
|
||||
import com.android.billingclient.api.BillingFlowParams;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
abstract class ActivityBilling extends ActivityBase implements PurchasesUpdatedListener {
|
||||
private BillingClient billingClient = null;
|
||||
|
||||
static final String ACTION_PURCHASE = BuildConfig.APPLICATION_ID + ".ACTION_PURCHASE";
|
||||
static final String ACTION_ACTIVATE_PRO = BuildConfig.APPLICATION_ID + ".ACTIVATE_PRO";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Helper.isPlayStoreInstall(this)) {
|
||||
billingClient = BillingClient.newBuilder(this).setListener(this).build();
|
||||
billingClient.startConnection(billingClientStateListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
IntentFilter iff = new IntentFilter();
|
||||
iff.addAction(ACTION_PURCHASE);
|
||||
iff.addAction(ACTION_ACTIVATE_PRO);
|
||||
lbm.registerReceiver(receiver, iff);
|
||||
|
||||
if (billingClient != null && billingClient.isReady())
|
||||
queryPurchases();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
lbm.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (billingClient != null)
|
||||
billingClient.endConnection();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected Intent getIntentPro() {
|
||||
if (Helper.isPlayStoreInstall(this))
|
||||
return null;
|
||||
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("https://email.faircode.eu/pro/?challenge=" + getChallenge()));
|
||||
return intent;
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getChallenge() throws NoSuchAlgorithmException {
|
||||
String android_id = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
return Helper.sha256(android_id);
|
||||
}
|
||||
|
||||
private String getResponse() throws NoSuchAlgorithmException {
|
||||
return Helper.sha256(BuildConfig.APPLICATION_ID + getChallenge());
|
||||
}
|
||||
|
||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (ACTION_PURCHASE.equals(intent.getAction()))
|
||||
onPurchase(intent);
|
||||
else if (ACTION_ACTIVATE_PRO.equals(intent.getAction()))
|
||||
onActivatePro(intent);
|
||||
}
|
||||
};
|
||||
|
||||
private View getView() {
|
||||
return findViewById(android.R.id.content);
|
||||
}
|
||||
|
||||
private void onPurchase(Intent intent) {
|
||||
if (Helper.isPlayStoreInstall(this)) {
|
||||
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
|
||||
.setSku(BuildConfig.APPLICATION_ID + ".pro")
|
||||
.setType(BillingClient.SkuType.INAPP)
|
||||
.build();
|
||||
int responseCode = billingClient.launchBillingFlow(this, flowParams);
|
||||
String text = Helper.getBillingResponseText(responseCode);
|
||||
Log.i(Helper.TAG, "IAB launch billing flow response=" + text);
|
||||
if (responseCode != BillingClient.BillingResponse.OK)
|
||||
Snackbar.make(getView(), text, Snackbar.LENGTH_LONG).show();
|
||||
} else
|
||||
startActivity(getIntentPro());
|
||||
}
|
||||
|
||||
private void onActivatePro(Intent intent) {
|
||||
try {
|
||||
Uri data = intent.getParcelableExtra("uri");
|
||||
String challenge = getChallenge();
|
||||
String response = data.getQueryParameter("response");
|
||||
Log.i(Helper.TAG, "Challenge=" + challenge);
|
||||
Log.i(Helper.TAG, "Response=" + response);
|
||||
String expected = getResponse();
|
||||
if (expected.equals(response)) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.edit().putBoolean("pro", true).apply();
|
||||
Log.i(Helper.TAG, "Response valid");
|
||||
Snackbar.make(getView(), R.string.title_pro_valid, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Log.i(Helper.TAG, "Response invalid");
|
||||
Snackbar.make(getView(), R.string.title_pro_invalid, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
intent.setData(null);
|
||||
setIntent(intent);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Log.e(Helper.TAG, Log.getStackTraceString(ex));
|
||||
Toast.makeText(this, ex.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
|
||||
private int backoff = 4; // seconds
|
||||
|
||||
@Override
|
||||
public void onBillingSetupFinished(@BillingClient.BillingResponse int responseCode) {
|
||||
String text = Helper.getBillingResponseText(responseCode);
|
||||
Log.i(Helper.TAG, "IAB connected response=" + text);
|
||||
if (responseCode == BillingClient.BillingResponse.OK) {
|
||||
backoff = 4;
|
||||
queryPurchases();
|
||||
} else
|
||||
Snackbar.make(getView(), text, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
backoff *= 2;
|
||||
Log.i(Helper.TAG, "IAB disconnected retry in " + backoff + " s");
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!billingClient.isReady())
|
||||
billingClient.startConnection(billingClientStateListener);
|
||||
}
|
||||
}, backoff * 1000L);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onPurchasesUpdated(int responseCode, @android.support.annotation.Nullable List<Purchase> purchases) {
|
||||
String text = Helper.getBillingResponseText(responseCode);
|
||||
Log.i(Helper.TAG, "IAB purchases updated response=" + text);
|
||||
if (responseCode == BillingClient.BillingResponse.OK)
|
||||
checkPurchases(purchases);
|
||||
else
|
||||
Snackbar.make(getView(), text, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void queryPurchases() {
|
||||
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
|
||||
String text = Helper.getBillingResponseText(result.getResponseCode());
|
||||
Log.i(Helper.TAG, "IAB query purchases response=" + text);
|
||||
if (result.getResponseCode() == BillingClient.BillingResponse.OK)
|
||||
checkPurchases(result.getPurchasesList());
|
||||
else
|
||||
Snackbar.make(getView(), text, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void checkPurchases(List<Purchase> purchases) {
|
||||
if (purchases != null) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.remove("pro");
|
||||
for (Purchase purchase : purchases) {
|
||||
Log.i(Helper.TAG, "IAB SKU=" + purchase.getSku());
|
||||
if ((BuildConfig.APPLICATION_ID + ".pro").equals(purchase.getSku())) {
|
||||
editor.putBoolean("pro", true);
|
||||
Log.i(Helper.TAG, "IAB pro features activated");
|
||||
}
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,12 +31,11 @@ import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
||||
public class ActivityCompose extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
|
||||
public class ActivityCompose extends ActivityBilling implements FragmentManager.OnBackStackChangedListener {
|
||||
static final int REQUEST_CONTACT_TO = 1;
|
||||
static final int REQUEST_CONTACT_CC = 2;
|
||||
static final int REQUEST_CONTACT_BCC = 3;
|
||||
static final int REQUEST_ATTACHMENT = 4;
|
||||
static final int REQUEST_OPENPGP = 5;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -49,40 +48,45 @@ public class ActivityCompose extends ActivityBase implements FragmentManager.OnB
|
||||
|
||||
if (getSupportFragmentManager().getFragments().size() == 0) {
|
||||
Bundle args;
|
||||
if (Intent.ACTION_SEND.equals(getIntent().getAction()) ||
|
||||
Intent.ACTION_SENDTO.equals(getIntent().getAction()) ||
|
||||
Intent.ACTION_SEND_MULTIPLE.equals(getIntent().getAction())) {
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
if (Intent.ACTION_VIEW.equals(action) ||
|
||||
Intent.ACTION_SENDTO.equals(action) ||
|
||||
Intent.ACTION_SEND.equals(action) ||
|
||||
Intent.ACTION_SEND_MULTIPLE.equals(action)) {
|
||||
args = new Bundle();
|
||||
|
||||
args.putString("action", "new");
|
||||
args.putLong("account", -1);
|
||||
|
||||
if (getIntent().hasExtra(Intent.EXTRA_EMAIL))
|
||||
args.putString("to", TextUtils.join(", ", getIntent().getStringArrayExtra(Intent.EXTRA_EMAIL)));
|
||||
Uri uri = intent.getData();
|
||||
if (uri != null && "mailto".equals(uri.getScheme()))
|
||||
args.putString("to", uri.getSchemeSpecificPart());
|
||||
|
||||
if (getIntent().hasExtra(Intent.EXTRA_CC))
|
||||
args.putString("cc", TextUtils.join(", ", getIntent().getStringArrayExtra(Intent.EXTRA_CC)));
|
||||
if (intent.hasExtra(Intent.EXTRA_EMAIL))
|
||||
args.putString("to", TextUtils.join(", ", intent.getStringArrayExtra(Intent.EXTRA_EMAIL)));
|
||||
|
||||
if (getIntent().hasExtra(Intent.EXTRA_BCC))
|
||||
args.putString("bcc", TextUtils.join(", ", getIntent().getStringArrayExtra(Intent.EXTRA_BCC)));
|
||||
if (intent.hasExtra(Intent.EXTRA_CC))
|
||||
args.putString("cc", TextUtils.join(", ", intent.getStringArrayExtra(Intent.EXTRA_CC)));
|
||||
|
||||
if (getIntent().hasExtra(Intent.EXTRA_SUBJECT))
|
||||
args.putString("subject", getIntent().getStringExtra(Intent.EXTRA_SUBJECT));
|
||||
if (intent.hasExtra(Intent.EXTRA_BCC))
|
||||
args.putString("bcc", TextUtils.join(", ", intent.getStringArrayExtra(Intent.EXTRA_BCC)));
|
||||
|
||||
if (getIntent().hasExtra(Intent.EXTRA_TEXT))
|
||||
args.putString("body", getIntent().getStringExtra(Intent.EXTRA_TEXT)); // Intent.EXTRA_HTML_TEXT
|
||||
if (intent.hasExtra(Intent.EXTRA_SUBJECT))
|
||||
args.putString("subject", intent.getStringExtra(Intent.EXTRA_SUBJECT));
|
||||
|
||||
if (getIntent().hasExtra(Intent.EXTRA_STREAM))
|
||||
if (Intent.ACTION_SEND_MULTIPLE.equals(getIntent().getAction()))
|
||||
args.putParcelableArrayList("attachments", getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM));
|
||||
if (intent.hasExtra(Intent.EXTRA_TEXT))
|
||||
args.putString("body", intent.getStringExtra(Intent.EXTRA_TEXT)); // Intent.EXTRA_HTML_TEXT
|
||||
|
||||
if (intent.hasExtra(Intent.EXTRA_STREAM))
|
||||
if (Intent.ACTION_SEND_MULTIPLE.equals(action))
|
||||
args.putParcelableArrayList("attachments", intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM));
|
||||
else {
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
uris.add((Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM));
|
||||
uris.add((Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM));
|
||||
args.putParcelableArrayList("attachments", uris);
|
||||
}
|
||||
|
||||
} else
|
||||
args = getIntent().getExtras();
|
||||
args = intent.getExtras();
|
||||
|
||||
FragmentCompose fragment = new FragmentCompose();
|
||||
fragment.setArguments(args);
|
||||
|
||||
@@ -34,8 +34,8 @@ import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
public class ActivitySetup extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
|
||||
boolean hasAccount;
|
||||
public class ActivitySetup extends ActivityBilling implements FragmentManager.OnBackStackChangedListener {
|
||||
private boolean hasAccount;
|
||||
|
||||
static final int REQUEST_PERMISSION = 1;
|
||||
static final int REQUEST_CHOOSE_ACCOUNT = 2;
|
||||
|
||||
@@ -22,17 +22,15 @@ package eu.faircode.email;
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -45,19 +43,11 @@ import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.BillingClientStateListener;
|
||||
import com.android.billingclient.api.BillingFlowParams;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -71,6 +61,7 @@ import javax.mail.Address;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
@@ -78,12 +69,11 @@ import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
public class ActivityView extends ActivityBase implements FragmentManager.OnBackStackChangedListener, PurchasesUpdatedListener {
|
||||
public class ActivityView extends ActivityBilling implements FragmentManager.OnBackStackChangedListener {
|
||||
private View view;
|
||||
private DrawerLayout drawerLayout;
|
||||
private ListView drawerList;
|
||||
private ActionBarDrawerToggle drawerToggle;
|
||||
private BillingClient billingClient = null;
|
||||
|
||||
private boolean newIntent = false;
|
||||
private long attachment = -1;
|
||||
@@ -94,15 +84,12 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
static final int REQUEST_UNSEEN = 2;
|
||||
|
||||
static final int REQUEST_ATTACHMENT = 1;
|
||||
static final int REQUEST_OPENPGP = 2;
|
||||
|
||||
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
|
||||
static final String ACTION_VIEW_MESSAGE = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGE";
|
||||
static final String ACTION_EDIT_FOLDER = BuildConfig.APPLICATION_ID + ".EDIT_FOLDER";
|
||||
static final String ACTION_EDIT_ANSWER = BuildConfig.APPLICATION_ID + ".EDIT_ANSWER";
|
||||
static final String ACTION_STORE_ATTACHMENT = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENT";
|
||||
static final String ACTION_PURCHASE = BuildConfig.APPLICATION_ID + ".ACTION_PURCHASE";
|
||||
static final String ACTION_ACTIVATE_PRO = BuildConfig.APPLICATION_ID + ".ACTIVATE_PRO";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -163,6 +150,9 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
case R.string.menu_about:
|
||||
onMenuAbout();
|
||||
break;
|
||||
case R.string.menu_rate:
|
||||
onMenuRate();
|
||||
break;
|
||||
case R.string.menu_other:
|
||||
onMenuOtherApps();
|
||||
break;
|
||||
@@ -200,6 +190,8 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.layout.item_drawer, R.drawable.baseline_settings_applications_24, R.string.menu_setup));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.layout.item_drawer, R.drawable.baseline_reply_24, R.string.menu_answers));
|
||||
|
||||
drawerArray.add(new DrawerItem(R.layout.item_drawer_separator));
|
||||
|
||||
if (PreferenceManager.getDefaultSharedPreferences(ActivityView.this).getBoolean("debug", false))
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.layout.item_drawer, R.drawable.baseline_list_24, R.string.menu_operations));
|
||||
|
||||
@@ -217,6 +209,11 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.layout.item_drawer, R.drawable.baseline_info_24, R.string.menu_about));
|
||||
|
||||
drawerArray.add(new DrawerItem(R.layout.item_drawer_separator));
|
||||
|
||||
if (getIntentRate().resolveActivity(getPackageManager()) != null)
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.layout.item_drawer, R.drawable.baseline_star_24, R.string.menu_rate));
|
||||
|
||||
if (getIntentOtherApps().resolveActivity(getPackageManager()) != null)
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.layout.item_drawer, R.drawable.baseline_get_app_24, R.string.menu_other));
|
||||
|
||||
@@ -293,7 +290,10 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
draft.received = new Date().getTime();
|
||||
draft.seen = false;
|
||||
draft.ui_seen = false;
|
||||
draft.flagged = false;
|
||||
draft.ui_flagged = false;
|
||||
draft.ui_hide = false;
|
||||
draft.ui_found = false;
|
||||
draft.id = db.message().insertMessage(draft);
|
||||
draft.write(context, body);
|
||||
}
|
||||
@@ -324,11 +324,6 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
}
|
||||
}.load(this, new Bundle());
|
||||
|
||||
if (Helper.isPlayStoreInstall(this)) {
|
||||
billingClient = BillingClient.newBuilder(this).setListener(this).build();
|
||||
billingClient.startConnection(billingClientStateListener);
|
||||
}
|
||||
|
||||
checkIntent(getIntent());
|
||||
}
|
||||
|
||||
@@ -365,17 +360,12 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
iff.addAction(ACTION_EDIT_FOLDER);
|
||||
iff.addAction(ACTION_EDIT_ANSWER);
|
||||
iff.addAction(ACTION_STORE_ATTACHMENT);
|
||||
iff.addAction(ACTION_PURCHASE);
|
||||
iff.addAction(ACTION_ACTIVATE_PRO);
|
||||
lbm.registerReceiver(receiver, iff);
|
||||
|
||||
if (newIntent) {
|
||||
newIntent = false;
|
||||
getSupportFragmentManager().popBackStack("unified", 0);
|
||||
}
|
||||
|
||||
if (billingClient != null && billingClient.isReady())
|
||||
queryPurchases();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -391,13 +381,6 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
drawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (billingClient != null)
|
||||
billingClient.endConnection();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (drawerLayout.isDrawerOpen(drawerList))
|
||||
@@ -433,15 +416,6 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
}
|
||||
}
|
||||
|
||||
private String getChallenge() throws NoSuchAlgorithmException {
|
||||
String android_id = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
return Helper.sha256(android_id);
|
||||
}
|
||||
|
||||
private String getResponse() throws NoSuchAlgorithmException {
|
||||
return Helper.sha256(BuildConfig.APPLICATION_ID + getChallenge());
|
||||
}
|
||||
|
||||
private void checkIntent(Intent intent) {
|
||||
Log.i(Helper.TAG, "View intent=" + intent + " action=" + intent.getAction());
|
||||
String action = intent.getAction();
|
||||
@@ -492,18 +466,11 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
return intent;
|
||||
}
|
||||
|
||||
private Intent getIntentPro() {
|
||||
if (Helper.isPlayStoreInstall(this))
|
||||
return null;
|
||||
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("https://email.faircode.eu/pro/?challenge=" + getChallenge()));
|
||||
return intent;
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return null;
|
||||
}
|
||||
private Intent getIntentRate() {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + BuildConfig.APPLICATION_ID));
|
||||
if (intent.resolveActivity(getPackageManager()) == null)
|
||||
intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID));
|
||||
return intent;
|
||||
}
|
||||
|
||||
private Intent getIntentOtherApps() {
|
||||
@@ -568,6 +535,30 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuRate() {
|
||||
Intent faq = getIntentFAQ();
|
||||
if (faq.resolveActivity(getPackageManager()) == null)
|
||||
startActivity(getIntentRate());
|
||||
else {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder
|
||||
.setMessage(R.string.title_issue)
|
||||
.setPositiveButton(R.string.title_yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
startActivity(getIntentFAQ());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.title_no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
startActivity(getIntentRate());
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void onMenuOtherApps() {
|
||||
startActivity(getIntentOtherApps());
|
||||
}
|
||||
@@ -642,10 +633,6 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
onEditAnswer(intent);
|
||||
else if (ACTION_STORE_ATTACHMENT.equals(intent.getAction()))
|
||||
onStoreAttachment(intent);
|
||||
else if (ACTION_PURCHASE.equals(intent.getAction()))
|
||||
onPurchase(intent);
|
||||
else if (ACTION_ACTIVATE_PRO.equals(intent.getAction()))
|
||||
onActivatePro(intent);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -723,47 +710,6 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
startActivityForResult(create, REQUEST_ATTACHMENT);
|
||||
}
|
||||
|
||||
private void onPurchase(Intent intent) {
|
||||
if (Helper.isPlayStoreInstall(this)) {
|
||||
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
|
||||
.setSku(BuildConfig.APPLICATION_ID + ".pro")
|
||||
.setType(BillingClient.SkuType.INAPP)
|
||||
.build();
|
||||
int responseCode = billingClient.launchBillingFlow(ActivityView.this, flowParams);
|
||||
String text = Helper.getBillingResponseText(responseCode);
|
||||
Log.i(Helper.TAG, "IAB launch billing flow response=" + text);
|
||||
if (responseCode != BillingClient.BillingResponse.OK)
|
||||
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
|
||||
} else
|
||||
startActivity(getIntentPro());
|
||||
}
|
||||
|
||||
private void onActivatePro(Intent intent) {
|
||||
try {
|
||||
Uri data = intent.getParcelableExtra("uri");
|
||||
String challenge = getChallenge();
|
||||
String response = data.getQueryParameter("response");
|
||||
Log.i(Helper.TAG, "Challenge=" + challenge);
|
||||
Log.i(Helper.TAG, "Response=" + response);
|
||||
String expected = getResponse();
|
||||
if (expected.equals(response)) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityView.this);
|
||||
prefs.edit().putBoolean("pro", true).apply();
|
||||
Log.i(Helper.TAG, "Response valid");
|
||||
Snackbar.make(view, R.string.title_pro_valid, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
Log.i(Helper.TAG, "Response invalid");
|
||||
Snackbar.make(view, R.string.title_pro_invalid, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
intent.setData(null);
|
||||
setIntent(intent);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Log.e(Helper.TAG, Log.getStackTraceString(ex));
|
||||
Toast.makeText(ActivityView.this, ex.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
Log.i(Helper.TAG, "View onActivityResult request=" + requestCode + " result=" + resultCode + " data=" + data);
|
||||
@@ -830,68 +776,4 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
||||
}.load(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
|
||||
private int backoff = 4; // seconds
|
||||
|
||||
@Override
|
||||
public void onBillingSetupFinished(@BillingClient.BillingResponse int responseCode) {
|
||||
String text = Helper.getBillingResponseText(responseCode);
|
||||
Log.i(Helper.TAG, "IAB connected response=" + text);
|
||||
if (responseCode == BillingClient.BillingResponse.OK) {
|
||||
backoff = 4;
|
||||
queryPurchases();
|
||||
} else
|
||||
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
backoff *= 2;
|
||||
Log.i(Helper.TAG, "IAB disconnected retry in " + backoff + " s");
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!billingClient.isReady())
|
||||
billingClient.startConnection(billingClientStateListener);
|
||||
}
|
||||
}, backoff * 1000L);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onPurchasesUpdated(int responseCode, @android.support.annotation.Nullable List<Purchase> purchases) {
|
||||
String text = Helper.getBillingResponseText(responseCode);
|
||||
Log.i(Helper.TAG, "IAB purchases updated response=" + text);
|
||||
if (responseCode == BillingClient.BillingResponse.OK)
|
||||
checkPurchases(purchases);
|
||||
else
|
||||
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void queryPurchases() {
|
||||
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
|
||||
String text = Helper.getBillingResponseText(result.getResponseCode());
|
||||
Log.i(Helper.TAG, "IAB query purchases response=" + text);
|
||||
if (result.getResponseCode() == BillingClient.BillingResponse.OK)
|
||||
checkPurchases(result.getPurchasesList());
|
||||
else
|
||||
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void checkPurchases(List<Purchase> purchases) {
|
||||
if (purchases != null) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.remove("pro");
|
||||
for (Purchase purchase : purchases) {
|
||||
Log.i(Helper.TAG, "IAB SKU=" + purchase.getSku());
|
||||
if ((BuildConfig.APPLICATION_ID + ".pro").equals(purchase.getSku())) {
|
||||
editor.putBoolean("pro", true);
|
||||
Log.i(Helper.TAG, "IAB pro features activated");
|
||||
}
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
|
||||
TextView tvName;
|
||||
TextView tvMessages;
|
||||
TextView tvType;
|
||||
ImageView ivUnified;
|
||||
TextView tvAfter;
|
||||
ImageView ivSync;
|
||||
ImageView ivState;
|
||||
@@ -67,6 +68,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
tvMessages = itemView.findViewById(R.id.tvMessages);
|
||||
tvType = itemView.findViewById(R.id.tvType);
|
||||
ivUnified = itemView.findViewById(R.id.ivUnified);
|
||||
tvAfter = itemView.findViewById(R.id.tvAfter);
|
||||
ivSync = itemView.findViewById(R.id.ivSync);
|
||||
tvError = itemView.findViewById(R.id.tvError);
|
||||
@@ -106,6 +108,8 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
|
||||
context.getPackageName());
|
||||
tvType.setText(resid > 0 ? context.getString(resid) : folder.type);
|
||||
|
||||
ivUnified.setVisibility(folder.unified ? View.VISIBLE : View.GONE);
|
||||
|
||||
tvAfter.setText(Integer.toString(folder.after));
|
||||
ivSync.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
@@ -119,7 +123,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
|
||||
ivState.setImageResource(R.drawable.baseline_compare_arrows_24);
|
||||
else
|
||||
ivState.setImageResource(R.drawable.baseline_cloud_off_24);
|
||||
ivState.setVisibility(folder.synchronize || outbox ? View.VISIBLE : View.INVISIBLE);
|
||||
ivState.setVisibility(folder.synchronize || folder.state != null ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
tvError.setText(folder.error);
|
||||
tvError.setVisibility(folder.error == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
162
app/src/main/java/eu/faircode/email/AdapterLog.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
NetGuard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListUpdateCallback;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class AdapterLog extends RecyclerView.Adapter<AdapterLog.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<EntityLog> all = new ArrayList<>();
|
||||
private List<EntityLog> filtered = new ArrayList<>();
|
||||
|
||||
private static final DateFormat DF = SimpleDateFormat.getTimeInstance();
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
View itemView;
|
||||
TextView tvTime;
|
||||
TextView tvData;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
tvTime = itemView.findViewById(R.id.tvTime);
|
||||
tvData = itemView.findViewById(R.id.tvData);
|
||||
}
|
||||
|
||||
private void bindTo(EntityLog log) {
|
||||
tvTime.setText(DF.format(log.time));
|
||||
tvData.setText(log.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AdapterLog(Context context) {
|
||||
this.context = context;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(@NonNull List<EntityLog> logs) {
|
||||
Log.i(Helper.TAG, "Set logs=" + logs.size());
|
||||
|
||||
all.clear();
|
||||
all.addAll(logs);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<EntityLog> prev;
|
||||
private List<EntityLog> next;
|
||||
|
||||
MessageDiffCallback(List<EntityLog> prev, List<EntityLog> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityLog l1 = prev.get(oldItemPosition);
|
||||
EntityLog l2 = next.get(newItemPosition);
|
||||
return l1.id.equals(l2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityLog l1 = prev.get(oldItemPosition);
|
||||
EntityLog l2 = next.get(newItemPosition);
|
||||
return l1.equals(l2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_log, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
EntityLog log = filtered.get(position);
|
||||
holder.bindTo(log);
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,18 @@ package eu.faircode.email;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -37,6 +41,7 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
@@ -57,27 +62,32 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
||||
public class ViewHolder extends RecyclerView.ViewHolder
|
||||
implements View.OnClickListener, View.OnLongClickListener {
|
||||
View itemView;
|
||||
ImageView ivFlagged;
|
||||
TextView tvFrom;
|
||||
TextView tvSize;
|
||||
TextView tvTime;
|
||||
ImageView ivAttachments;
|
||||
TextView tvSubject;
|
||||
TextView tvFolder;
|
||||
TextView tvCount;
|
||||
ImageView ivThread;
|
||||
TextView tvError;
|
||||
ProgressBar pbLoading;
|
||||
|
||||
private static final int action_seen = 1;
|
||||
private static final int action_flag = 2;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
ivFlagged = itemView.findViewById(R.id.ivFlagged);
|
||||
tvFrom = itemView.findViewById(R.id.tvFrom);
|
||||
tvSize = itemView.findViewById(R.id.tvSize);
|
||||
tvTime = itemView.findViewById(R.id.tvTime);
|
||||
ivAttachments = itemView.findViewById(R.id.ivAttachments);
|
||||
tvSubject = itemView.findViewById(R.id.tvSubject);
|
||||
tvFolder = itemView.findViewById(R.id.tvFolder);
|
||||
tvCount = itemView.findViewById(R.id.tvCount);
|
||||
ivThread = itemView.findViewById(R.id.ivThread);
|
||||
tvError = itemView.findViewById(R.id.tvError);
|
||||
pbLoading = itemView.findViewById(R.id.pbLoading);
|
||||
}
|
||||
@@ -93,13 +103,15 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
ivFlagged.setVisibility(View.GONE);
|
||||
tvFrom.setText(null);
|
||||
tvSize.setText(null);
|
||||
tvTime.setText(null);
|
||||
tvSubject.setText(null);
|
||||
ivAttachments.setVisibility(View.GONE);
|
||||
tvSubject.setText(null);
|
||||
tvFolder.setText(null);
|
||||
tvCount.setText(null);
|
||||
ivThread.setVisibility(View.GONE);
|
||||
tvError.setText(null);
|
||||
pbLoading.setVisibility(View.VISIBLE);
|
||||
}
|
||||
@@ -107,6 +119,8 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
||||
private void bindTo(final TupleMessageEx message) {
|
||||
pbLoading.setVisibility(View.GONE);
|
||||
|
||||
ivFlagged.setVisibility(message.ui_flagged ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (EntityFolder.DRAFTS.equals(message.folderType) ||
|
||||
EntityFolder.OUTBOX.equals(message.folderType) ||
|
||||
EntityFolder.SENT.equals(message.folderType)) {
|
||||
@@ -117,8 +131,6 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
||||
tvTime.setText(DateUtils.getRelativeTimeSpanString(context, message.received));
|
||||
}
|
||||
|
||||
tvSize.setVisibility(View.GONE);
|
||||
|
||||
tvSubject.setText(message.subject);
|
||||
ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE);
|
||||
|
||||
@@ -129,11 +141,12 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
||||
else
|
||||
tvFolder.setText(Helper.localizeFolderName(context, message.folderName));
|
||||
|
||||
if (viewType == ViewType.THREAD)
|
||||
if (viewType == ViewType.THREAD) {
|
||||
tvCount.setVisibility(View.GONE);
|
||||
else {
|
||||
ivThread.setVisibility(View.GONE);
|
||||
} else {
|
||||
tvCount.setText(Integer.toString(message.count));
|
||||
tvCount.setVisibility(debug || message.count > 1 ? View.VISIBLE : View.GONE);
|
||||
ivThread.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
@@ -200,12 +213,59 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
||||
if (pos == RecyclerView.NO_POSITION)
|
||||
return false;
|
||||
|
||||
TupleMessageEx message = getItem(pos);
|
||||
final TupleMessageEx message = getItem(pos);
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_VIEW_MESSAGE)
|
||||
.putExtra("message", message));
|
||||
PopupMenu popupMenu = new PopupMenu(context, itemView);
|
||||
popupMenu.getMenu().add(Menu.NONE, action_flag, 1, message.ui_flagged ? R.string.title_unflag : R.string.title_flag);
|
||||
popupMenu.getMenu().add(Menu.NONE, action_seen, 2, message.ui_seen ? R.string.title_unseen : R.string.title_seen);
|
||||
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem target) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
args.putInt("action", target.getItemId());
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onLoad(Context context, Bundle args) {
|
||||
long id = args.getLong("id");
|
||||
int action = args.getInt("action");
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
for (EntityMessage tmessage : db.message().getMessageByThread(message.account, message.thread))
|
||||
if (action == action_flag) {
|
||||
db.message().setMessageUiFlagged(tmessage.id, !message.ui_flagged);
|
||||
EntityOperation.queue(db, tmessage, EntityOperation.FLAG, !tmessage.ui_flagged);
|
||||
} else if (action == action_seen) {
|
||||
db.message().setMessageUiSeen(tmessage.id, !message.ui_seen);
|
||||
EntityOperation.queue(db, tmessage, EntityOperation.SEEN, !tmessage.ui_seen);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
EntityOperation.process(context);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Bundle args, Throwable ex) {
|
||||
Toast.makeText(context, ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}.load(context, owner, args);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
popupMenu.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
NetGuard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
import com.sun.mail.imap.IMAPMessage;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.search.AndTerm;
|
||||
import javax.mail.search.BodyTerm;
|
||||
import javax.mail.search.ComparisonTerm;
|
||||
import javax.mail.search.FromStringTerm;
|
||||
import javax.mail.search.OrTerm;
|
||||
import javax.mail.search.ReceivedDateTerm;
|
||||
import javax.mail.search.SubjectTerm;
|
||||
|
||||
import androidx.lifecycle.GenericLifecycleObserver;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.paging.PagedList;
|
||||
|
||||
public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMessageEx> {
|
||||
private Context context;
|
||||
private long fid;
|
||||
private String search;
|
||||
private Handler mainHandler;
|
||||
private IBoundaryCallbackMessages intf;
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
|
||||
|
||||
private boolean enabled = false;
|
||||
private IMAPStore istore = null;
|
||||
private IMAPFolder ifolder = null;
|
||||
private Message[] imessages = null;
|
||||
|
||||
interface IBoundaryCallbackMessages {
|
||||
void onLoading();
|
||||
|
||||
void onLoaded();
|
||||
|
||||
void onError(Context context, Throwable ex);
|
||||
}
|
||||
|
||||
BoundaryCallbackMessages(Context context, LifecycleOwner owner, long folder, String search, IBoundaryCallbackMessages intf) {
|
||||
this.context = context;
|
||||
this.fid = folder;
|
||||
this.search = search;
|
||||
this.mainHandler = new Handler(context.getMainLooper());
|
||||
this.intf = intf;
|
||||
|
||||
owner.getLifecycle().addObserver(new GenericLifecycleObserver() {
|
||||
@Override
|
||||
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
|
||||
if (event == Lifecycle.Event.ON_DESTROY)
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(Helper.TAG, "Boundary close");
|
||||
try {
|
||||
if (istore != null)
|
||||
istore.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} finally {
|
||||
istore = null;
|
||||
ifolder = null;
|
||||
imessages = null;
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemAtEndLoaded(final TupleMessageEx itemAtEnd) {
|
||||
Log.i(Helper.TAG, "onItemAtEndLoaded enabled=" + enabled);
|
||||
if (!enabled)
|
||||
return;
|
||||
load(itemAtEnd.received);
|
||||
}
|
||||
|
||||
void load(final long before) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
intf.onLoading();
|
||||
}
|
||||
});
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
EntityFolder folder = db.folder().getFolder(fid);
|
||||
EntityAccount account = db.account().getAccount(folder.account);
|
||||
|
||||
if (imessages == null) {
|
||||
// Refresh token
|
||||
if (account.auth_type == Helper.AUTH_TYPE_GMAIL) {
|
||||
account.password = Helper.refreshToken(context, "com.google", account.user, account.password);
|
||||
db.account().setAccountPassword(account.id, account.password);
|
||||
}
|
||||
|
||||
Properties props = MessageHelper.getSessionProperties(context, account.auth_type);
|
||||
props.setProperty("mail.imap.throwsearchexception", "true");
|
||||
Session isession = Session.getInstance(props, null);
|
||||
|
||||
Log.i(Helper.TAG, "Boundary connecting account=" + account.name);
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
|
||||
Log.i(Helper.TAG, "Boundary opening folder=" + folder.name);
|
||||
ifolder = (IMAPFolder) istore.getFolder(folder.name);
|
||||
ifolder.open(Folder.READ_WRITE);
|
||||
|
||||
Log.i(Helper.TAG, "Boundary searching=" + search + " before=" + new Date(before));
|
||||
imessages = ifolder.search(
|
||||
new AndTerm(
|
||||
new ReceivedDateTerm(ComparisonTerm.LT, new Date(before)),
|
||||
new OrTerm(
|
||||
new FromStringTerm(search),
|
||||
new OrTerm(
|
||||
new SubjectTerm(search),
|
||||
new BodyTerm(search)))));
|
||||
Log.i(Helper.TAG, "Boundary found messages=" + imessages.length);
|
||||
}
|
||||
|
||||
int index = imessages.length - 1;
|
||||
while (index >= 0) {
|
||||
if (imessages[index].getReceivedDate().getTime() < before)
|
||||
try {
|
||||
Log.i(Helper.TAG, "Boundary sync uid=" + ifolder.getUID(imessages[index]));
|
||||
ServiceSynchronize.synchronizeMessage(context, folder, ifolder, (IMAPMessage) imessages[index], true);
|
||||
break;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
EntityOperation.process(context); // download small attachments
|
||||
|
||||
Log.i(Helper.TAG, "Boundary done");
|
||||
} catch (final Throwable ex) {
|
||||
Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
intf.onError(context, ex);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
intf.onLoaded();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 5,
|
||||
version = 12,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
@@ -54,6 +54,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
EntityAttachment.class,
|
||||
EntityOperation.class,
|
||||
EntityAnswer.class,
|
||||
EntityLog.class
|
||||
}
|
||||
)
|
||||
|
||||
@@ -73,6 +74,8 @@ public abstract class DB extends RoomDatabase {
|
||||
|
||||
public abstract DaoAnswer answer();
|
||||
|
||||
public abstract DaoLog log();
|
||||
|
||||
private static DB sInstance;
|
||||
|
||||
private static final String DB_NAME = "email";
|
||||
@@ -143,6 +146,59 @@ public abstract class DB extends RoomDatabase {
|
||||
db.execSQL("ALTER TABLE `identity` ADD COLUMN `auth_type` INTEGER NOT NULL DEFAULT 1");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(5, 6) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `ui_found` INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(6, 7) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `log` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)");
|
||||
db.execSQL("CREATE INDEX `index_log_time` ON `log` (`time`)");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(7, 8) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("CREATE INDEX `index_message_ui_found` ON `message` (`ui_found`)");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(8, 9) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `headers` TEXT");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(9, 10) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `folder` ADD COLUMN `unified` INTEGER NOT NULL DEFAULT 0");
|
||||
db.execSQL("CREATE INDEX `index_folder_unified` ON `folder` (`unified`)");
|
||||
db.execSQL("UPDATE `folder` SET unified = 1 WHERE type = '" + EntityFolder.INBOX + "'");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(10, 11) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `account` ADD COLUMN `signature` TEXT");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(11, 12) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `flagged` INTEGER NOT NULL DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `ui_flagged` INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public interface DaoFolder {
|
||||
" JOIN account ON account.id = folder.account" +
|
||||
" JOIN message ON message.folder = folder.id AND NOT message.ui_hide" +
|
||||
" WHERE account.`synchronize`" +
|
||||
" AND folder.type = '" + EntityFolder.INBOX + "'" +
|
||||
" AND folder.unified" +
|
||||
" GROUP BY folder.id")
|
||||
LiveData<List<TupleFolderEx>> liveUnified();
|
||||
|
||||
@@ -114,8 +114,8 @@ public interface DaoFolder {
|
||||
" AND type = :type")
|
||||
int setFolderUser(long account, String type);
|
||||
|
||||
@Query("UPDATE folder SET synchronize = :synchronize, after = :after WHERE id = :id")
|
||||
int setFolderProperties(long id, boolean synchronize, int after);
|
||||
@Query("UPDATE folder SET synchronize = :synchronize, unified = :unified, after = :after WHERE id = :id")
|
||||
int setFolderProperties(long id, boolean synchronize, boolean unified, int after);
|
||||
|
||||
@Query("DELETE FROM folder WHERE account= :account AND name = :name")
|
||||
void deleteFolder(Long account, String name);
|
||||
|
||||
@@ -48,8 +48,8 @@ public interface DaoIdentity {
|
||||
@Query("SELECT * FROM identity WHERE id = :id")
|
||||
LiveData<EntityIdentity> liveIdentity(long id);
|
||||
|
||||
@Query("SELECT * FROM identity WHERE account = :account AND `primary`")
|
||||
EntityIdentity getPrimaryIdentity(long account);
|
||||
@Query("SELECT COUNT(*) FROM identity WHERE synchronize")
|
||||
int getSynchronizingIdentityCount();
|
||||
|
||||
@Insert
|
||||
long insertIdentity(EntityIdentity identity);
|
||||
|
||||
47
app/src/main/java/eu/faircode/email/DaoLog.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
NetGuard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
|
||||
@Dao
|
||||
public interface DaoLog {
|
||||
@Query("SELECT * FROM log" +
|
||||
" WHERE time > :from" +
|
||||
" ORDER BY time DESC")
|
||||
LiveData<List<EntityLog>> liveLogs(long from);
|
||||
|
||||
@Query("SELECT * FROM log" +
|
||||
" WHERE time > :from" +
|
||||
" ORDER BY time DESC")
|
||||
List<EntityLog> getLogs(long from);
|
||||
|
||||
@Insert
|
||||
long insertLog(EntityLog log);
|
||||
|
||||
@Query("DELETE FROM log" +
|
||||
" WHERE time < :before")
|
||||
int deleteLogs(long before);
|
||||
}
|
||||
@@ -39,14 +39,14 @@ public interface DaoMessage {
|
||||
", COUNT(message.id) as count" +
|
||||
", SUM(CASE WHEN message.ui_seen THEN 0 ELSE 1 END) as unseen" +
|
||||
", (SELECT COUNT(a.id) FROM attachment a WHERE a.message = message.id) AS attachments" +
|
||||
", MAX(CASE WHEN folder.type = '" + EntityFolder.INBOX + "' THEN message.id ELSE 0 END) as dummy" +
|
||||
", MAX(CASE WHEN folder.unified THEN message.id ELSE 0 END) as dummy" +
|
||||
" FROM message" +
|
||||
" JOIN account ON account.id = message.account" +
|
||||
" JOIN folder ON folder.id = message.folder" +
|
||||
" WHERE account.`synchronize`" +
|
||||
" AND (NOT message.ui_hide OR :debug)" +
|
||||
" GROUP BY CASE WHEN message.thread IS NULL THEN message.id ELSE message.thread END" +
|
||||
" HAVING SUM(CASE WHEN folder.type = '" + EntityFolder.INBOX + "' THEN 1 ELSE 0 END) > 0" +
|
||||
" HAVING SUM(unified) > 0" +
|
||||
" ORDER BY message.received DESC")
|
||||
DataSource.Factory<Integer, TupleMessageEx> pagedUnifiedInbox(boolean debug);
|
||||
|
||||
@@ -60,10 +60,11 @@ public interface DaoMessage {
|
||||
" JOIN folder ON folder.id = message.folder" +
|
||||
" LEFT JOIN folder f ON f.id = :folder" +
|
||||
" WHERE (NOT message.ui_hide OR :debug)" +
|
||||
" AND (NOT :found OR ui_found = :found)" +
|
||||
" GROUP BY CASE WHEN message.thread IS NULL THEN message.id ELSE message.thread END" +
|
||||
" HAVING SUM(CASE WHEN folder.id = :folder THEN 1 ELSE 0 END) > 0" +
|
||||
" ORDER BY message.received DESC, message.sent DESC")
|
||||
DataSource.Factory<Integer, TupleMessageEx> pagedFolder(long folder, boolean debug);
|
||||
DataSource.Factory<Integer, TupleMessageEx> pagedFolder(long folder, boolean found, boolean debug);
|
||||
|
||||
@Query("SELECT message.*, account.name AS accountName, folder.name as folderName, folder.type as folderType" +
|
||||
", 1 AS count" +
|
||||
@@ -104,9 +105,15 @@ public interface DaoMessage {
|
||||
" JOIN folder ON folder.id = message.folder" +
|
||||
" WHERE message.account = :account" +
|
||||
" AND message.thread = :thread" +
|
||||
" AND folder.type <> '" + EntityFolder.OUTBOX + "'")
|
||||
" AND folder.type <> '" + EntityFolder.OUTBOX + "'" +
|
||||
" AND folder.type <> '" + EntityFolder.DRAFTS + "'")
|
||||
List<EntityMessage> getMessageByThread(long account, String thread);
|
||||
|
||||
@Query("SELECT id FROM message" +
|
||||
" WHERE folder = :folder" +
|
||||
" ORDER BY message.received DESC, message.sent DESC")
|
||||
List<Long> getMessageIDs(long folder);
|
||||
|
||||
@Query("SELECT message.*, account.name AS accountName, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m1.id) FROM message m1 WHERE m1.account = message.account AND m1.thread = message.thread AND NOT m1.ui_hide) AS count" +
|
||||
", (SELECT COUNT(m2.id) FROM message m2 WHERE m2.account = message.account AND m2.thread = message.thread AND NOT m2.ui_hide AND NOT m2.ui_seen) AS unseen" +
|
||||
@@ -121,13 +128,17 @@ public interface DaoMessage {
|
||||
" JOIN account ON account.id = message.account" +
|
||||
" JOIN folder ON folder.id = message.folder" +
|
||||
" WHERE account.`synchronize`" +
|
||||
" AND folder.type = '" + EntityFolder.INBOX + "'" +
|
||||
" AND folder.unified" +
|
||||
" AND NOT message.ui_seen AND NOT message.ui_hide" +
|
||||
" AND (account.seen_until IS NULL OR message.stored > account.seen_until)" +
|
||||
" ORDER BY message.received")
|
||||
LiveData<List<EntityMessage>> liveUnseenUnified();
|
||||
|
||||
@Query("SELECT uid FROM message WHERE folder = :folder AND received >= :received AND NOT uid IS NULL")
|
||||
@Query("SELECT uid FROM message" +
|
||||
" WHERE folder = :folder" +
|
||||
" AND received >= :received" +
|
||||
" AND NOT uid IS NULL" +
|
||||
" AND NOT ui_found" /* keep found messages */)
|
||||
List<Long> getUids(long folder, long received);
|
||||
|
||||
@Insert
|
||||
@@ -145,12 +156,27 @@ public interface DaoMessage {
|
||||
@Query("UPDATE message SET ui_seen = :ui_seen WHERE id = :id")
|
||||
int setMessageUiSeen(long id, boolean ui_seen);
|
||||
|
||||
@Query("UPDATE message SET flagged = :flagged WHERE id = :id")
|
||||
int setMessageFlagged(long id, boolean flagged);
|
||||
|
||||
@Query("UPDATE message SET ui_flagged = :ui_flagged WHERE id = :id")
|
||||
int setMessageUiFlagged(long id, boolean ui_flagged);
|
||||
|
||||
@Query("UPDATE message SET ui_hide = :ui_hide WHERE id = :id")
|
||||
int setMessageUiHide(long id, boolean ui_hide);
|
||||
|
||||
@Query("UPDATE message SET error = :error WHERE id = :id")
|
||||
int setMessageError(long id, String error);
|
||||
|
||||
@Query("UPDATE message SET ui_found = :found WHERE id = :id")
|
||||
int setMessageFound(long id, boolean found);
|
||||
|
||||
@Query("UPDATE message SET ui_found = 0 WHERE folder = :folder")
|
||||
int resetFound(long folder);
|
||||
|
||||
@Query("UPDATE message SET headers = :headers WHERE id = :id")
|
||||
int setMessageHeaders(long id, String headers);
|
||||
|
||||
@Query("DELETE FROM message WHERE id = :id")
|
||||
int deleteMessage(long id);
|
||||
|
||||
@@ -158,8 +184,11 @@ public interface DaoMessage {
|
||||
int deleteMessage(long folder, long uid);
|
||||
|
||||
@Query("DELETE FROM message WHERE folder = :folder")
|
||||
void deleteMessages(long folder);
|
||||
int deleteMessages(long folder);
|
||||
|
||||
@Query("DELETE FROM message WHERE folder = :folder AND received < :received AND NOT uid IS NULL")
|
||||
int deleteMessagesBefore(long folder, long received);
|
||||
|
||||
@Query("DELETE FROM message WHERE ui_found")
|
||||
int deleteFoundMessages();
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public class EntityAccount {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
public String name;
|
||||
public String signature;
|
||||
@NonNull
|
||||
public String host; // IMAP
|
||||
@NonNull
|
||||
@@ -51,7 +52,7 @@ public class EntityAccount {
|
||||
@NonNull
|
||||
public Boolean store_sent; // obsolete
|
||||
@NonNull
|
||||
public Integer poll_interval;
|
||||
public Integer poll_interval; // NOOP interval
|
||||
public Long seen_until;
|
||||
public String state;
|
||||
public String error;
|
||||
|
||||
@@ -42,9 +42,11 @@ import static androidx.room.ForeignKey.CASCADE;
|
||||
@Index(value = {"account", "name"}, unique = true),
|
||||
@Index(value = {"account"}),
|
||||
@Index(value = {"name"}),
|
||||
@Index(value = {"type"})
|
||||
@Index(value = {"type"}),
|
||||
@Index(value = {"unified"})
|
||||
}
|
||||
)
|
||||
|
||||
public class EntityFolder implements Parcelable {
|
||||
static final String TABLE_NAME = "folder";
|
||||
|
||||
@@ -56,6 +58,8 @@ public class EntityFolder implements Parcelable {
|
||||
@NonNull
|
||||
public String type;
|
||||
@NonNull
|
||||
public Boolean unified = false;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
@NonNull
|
||||
public Integer after; // days
|
||||
|
||||
76
app/src/main/java/eu/faircode/email/EntityLog.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
NetGuard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityLog.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"time"})
|
||||
}
|
||||
)
|
||||
public class EntityLog {
|
||||
static final String TABLE_NAME = "log";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
@NonNull
|
||||
public Long time;
|
||||
@NonNull
|
||||
public String data;
|
||||
|
||||
private static ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
static void log(Context context, String data) {
|
||||
final EntityLog entry = new EntityLog();
|
||||
entry.time = new Date().getTime();
|
||||
entry.data = data;
|
||||
|
||||
final DB db = DB.getInstance(context);
|
||||
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
db.log().insertLog(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityLog) {
|
||||
EntityLog other = (EntityLog) obj;
|
||||
return (this.time.equals(other.time) && this.data.equals(other.data));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,8 @@ import static androidx.room.ForeignKey.CASCADE;
|
||||
@Index(value = {"thread"}),
|
||||
@Index(value = {"received"}),
|
||||
@Index(value = {"ui_seen"}),
|
||||
@Index(value = {"ui_hide"})
|
||||
@Index(value = {"ui_hide"}),
|
||||
@Index(value = {"ui_found"})
|
||||
}
|
||||
)
|
||||
public class EntityMessage implements Serializable {
|
||||
@@ -86,6 +87,7 @@ public class EntityMessage implements Serializable {
|
||||
public Address[] cc;
|
||||
public Address[] bcc;
|
||||
public Address[] reply;
|
||||
public String headers;
|
||||
public String subject;
|
||||
public Long sent; // compose = null
|
||||
@NonNull
|
||||
@@ -95,15 +97,19 @@ public class EntityMessage implements Serializable {
|
||||
@NonNull
|
||||
public Boolean seen;
|
||||
@NonNull
|
||||
public Boolean flagged;
|
||||
@NonNull
|
||||
public Boolean ui_seen;
|
||||
@NonNull
|
||||
public Boolean ui_flagged;
|
||||
@NonNull
|
||||
public Boolean ui_hide;
|
||||
@NonNull
|
||||
public Boolean ui_found;
|
||||
public String error;
|
||||
|
||||
@Ignore
|
||||
String body = null;
|
||||
@Ignore
|
||||
boolean virtual = false;
|
||||
|
||||
static String generateMessageId() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
@@ -125,8 +131,9 @@ public class EntityMessage implements Serializable {
|
||||
File file = getFile(context, id);
|
||||
BufferedWriter out = null;
|
||||
try {
|
||||
this.body = (body == null ? "" : body);
|
||||
out = new BufferedWriter(new FileWriter(file));
|
||||
out.write(body == null ? "" : body);
|
||||
out.write(this.body);
|
||||
} finally {
|
||||
if (out != null)
|
||||
try {
|
||||
@@ -184,12 +191,17 @@ public class EntityMessage implements Serializable {
|
||||
equal(this.cc, other.cc) &&
|
||||
equal(this.bcc, other.bcc) &&
|
||||
equal(this.reply, other.reply) &&
|
||||
(this.headers == null ? other.headers == null : this.headers.equals(other.headers)) &&
|
||||
(this.subject == null ? other.subject == null : this.subject.equals(other.subject)) &&
|
||||
(this.sent == null ? other.sent == null : this.sent.equals(other.sent)) &&
|
||||
this.received.equals(other.received) &&
|
||||
this.stored.equals(other.stored) &&
|
||||
this.seen.equals(other.seen) &&
|
||||
this.ui_seen.equals(other.ui_seen) &&
|
||||
this.flagged.equals(other.flagged) &&
|
||||
this.ui_flagged.equals(other.ui_flagged) &&
|
||||
this.ui_hide.equals(other.ui_hide) &&
|
||||
this.ui_found.equals(other.ui_found) &&
|
||||
(this.error == null ? other.error == null : this.error.equals(other.error)));
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -71,6 +71,8 @@ public class EntityOperation {
|
||||
public static final String DELETE = "delete";
|
||||
public static final String SEND = "send";
|
||||
public static final String ATTACHMENT = "attachment";
|
||||
public static final String HEADERS = "headers";
|
||||
public static final String FLAG = "flag";
|
||||
|
||||
private static List<Intent> queue = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -33,14 +33,21 @@ import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.mail.Address;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
public class FragmentAbout extends FragmentEx {
|
||||
private TextView tvVersion;
|
||||
private Button btnLog;
|
||||
private Button btnDebugInfo;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
@@ -48,11 +55,21 @@ public class FragmentAbout extends FragmentEx {
|
||||
|
||||
View view = inflater.inflate(R.layout.fragment_about, container, false);
|
||||
|
||||
TextView tvVersion = view.findViewById(R.id.tvVersion);
|
||||
final Button btnDebugInfo = view.findViewById(R.id.btnDebugInfo);
|
||||
tvVersion = view.findViewById(R.id.tvVersion);
|
||||
btnLog = view.findViewById(R.id.btnLog);
|
||||
btnDebugInfo = view.findViewById(R.id.btnDebugInfo);
|
||||
|
||||
tvVersion.setText(getString(R.string.title_version, BuildConfig.VERSION_NAME));
|
||||
|
||||
btnLog.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentLogs()).addToBackStack("logs");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
});
|
||||
|
||||
btnDebugInfo.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
@@ -80,13 +97,20 @@ public class FragmentAbout extends FragmentEx {
|
||||
sb.append(String.format("Id: %s\r\n", Build.ID));
|
||||
sb.append("\r\n");
|
||||
|
||||
// Get recent log
|
||||
long from = new Date().getTime() - 12 * 3600 * 1000L;
|
||||
DateFormat DF = SimpleDateFormat.getTimeInstance();
|
||||
DB db = DB.getInstance(context);
|
||||
for (EntityLog log : db.log().getLogs(from))
|
||||
sb.append(DF.format(log.time)).append(" ").append(log.data).append("\r\n");
|
||||
sb.append("\r\n");
|
||||
|
||||
sb.append(Helper.getLogcat());
|
||||
|
||||
String body = "<pre>" + sb.toString().replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
|
||||
EntityMessage draft;
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
@@ -103,7 +127,10 @@ public class FragmentAbout extends FragmentEx {
|
||||
draft.received = new Date().getTime();
|
||||
draft.seen = false;
|
||||
draft.ui_seen = false;
|
||||
draft.flagged = false;
|
||||
draft.ui_flagged = false;
|
||||
draft.ui_hide = false;
|
||||
draft.ui_found = false;
|
||||
draft.id = db.message().insertMessage(draft);
|
||||
draft.write(context, body);
|
||||
|
||||
|
||||
@@ -32,11 +32,10 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -51,9 +50,9 @@ import android.widget.ImageButton;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
@@ -75,26 +74,27 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import static android.accounts.AccountManager.newChooseAccountIntent;
|
||||
|
||||
public class FragmentAccount extends FragmentEx {
|
||||
private ViewGroup view;
|
||||
private EditText etName;
|
||||
private Spinner spProvider;
|
||||
private EditText etHost;
|
||||
private EditText etPort;
|
||||
private Button btnAuthorize;
|
||||
private EditText etUser;
|
||||
private TextInputLayout tilPassword;
|
||||
private TextView tvLink;
|
||||
private Button btnAdvanced;
|
||||
private EditText etName;
|
||||
private EditText etSignature;
|
||||
private ImageButton ibPro;
|
||||
private CheckBox cbSynchronize;
|
||||
private CheckBox cbPrimary;
|
||||
private EditText etInterval;
|
||||
private Button btnCheck;
|
||||
private ProgressBar pbCheck;
|
||||
private TextView tvIdle;
|
||||
private Spinner spDrafts;
|
||||
private Spinner spSent;
|
||||
private Spinner spAll;
|
||||
@@ -104,7 +104,9 @@ public class FragmentAccount extends FragmentEx {
|
||||
private ProgressBar pbSave;
|
||||
private ImageButton ibDelete;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpInstructions;
|
||||
private Group grpServer;
|
||||
private Group grpAuthorize;
|
||||
private Group grpAdvanced;
|
||||
private Group grpFolders;
|
||||
|
||||
private long id = -1;
|
||||
@@ -128,29 +130,40 @@ public class FragmentAccount extends FragmentEx {
|
||||
|
||||
// Get controls
|
||||
spProvider = view.findViewById(R.id.spProvider);
|
||||
etName = view.findViewById(R.id.etName);
|
||||
etHost = view.findViewById(R.id.etHost);
|
||||
btnAuthorize = view.findViewById(R.id.btnAuthorize);
|
||||
etPort = view.findViewById(R.id.etPort);
|
||||
|
||||
btnAuthorize = view.findViewById(R.id.btnAuthorize);
|
||||
etUser = view.findViewById(R.id.etUser);
|
||||
tilPassword = view.findViewById(R.id.tilPassword);
|
||||
tvLink = view.findViewById(R.id.tvLink);
|
||||
|
||||
btnAdvanced = view.findViewById(R.id.btnAdvanced);
|
||||
etName = view.findViewById(R.id.etName);
|
||||
etSignature = view.findViewById(R.id.etSignature);
|
||||
ibPro = view.findViewById(R.id.ibPro);
|
||||
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
cbPrimary = view.findViewById(R.id.cbPrimary);
|
||||
etInterval = view.findViewById(R.id.etInterval);
|
||||
|
||||
btnCheck = view.findViewById(R.id.btnCheck);
|
||||
pbCheck = view.findViewById(R.id.pbCheck);
|
||||
tvIdle = view.findViewById(R.id.tvIdle);
|
||||
|
||||
spDrafts = view.findViewById(R.id.spDrafts);
|
||||
spSent = view.findViewById(R.id.spSent);
|
||||
spAll = view.findViewById(R.id.spAll);
|
||||
spTrash = view.findViewById(R.id.spTrash);
|
||||
spJunk = view.findViewById(R.id.spJunk);
|
||||
|
||||
btnSave = view.findViewById(R.id.btnSave);
|
||||
pbSave = view.findViewById(R.id.pbSave);
|
||||
|
||||
ibDelete = view.findViewById(R.id.ibDelete);
|
||||
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpInstructions = view.findViewById(R.id.grpInstructions);
|
||||
|
||||
grpServer = view.findViewById(R.id.grpServer);
|
||||
grpAuthorize = view.findViewById(R.id.grpAuthorize);
|
||||
grpAdvanced = view.findViewById(R.id.grpAdvanced);
|
||||
grpFolders = view.findViewById(R.id.grpFolders);
|
||||
|
||||
// Wire controls
|
||||
@@ -158,27 +171,32 @@ public class FragmentAccount extends FragmentEx {
|
||||
spProvider.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
Integer tag = (Integer) adapterView.getTag();
|
||||
if (tag != null && tag.equals(position))
|
||||
Provider provider = (Provider) adapterView.getSelectedItem();
|
||||
grpServer.setVisibility(position == 1 ? View.VISIBLE : View.GONE);
|
||||
grpAuthorize.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
|
||||
|
||||
btnAuthorize.setVisibility(provider.type == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
btnAdvanced.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
|
||||
if (position == 0)
|
||||
grpAdvanced.setVisibility(View.GONE);
|
||||
|
||||
btnCheck.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
|
||||
grpFolders.setVisibility(View.GONE);
|
||||
btnSave.setVisibility(View.GONE);
|
||||
|
||||
Object tag = adapterView.getTag();
|
||||
if (tag != null && (Integer) tag == position)
|
||||
return;
|
||||
adapterView.setTag(position);
|
||||
|
||||
Provider provider = (Provider) adapterView.getSelectedItem();
|
||||
|
||||
etName.setText(provider.name);
|
||||
|
||||
btnAuthorize.setVisibility(provider.type == null ? View.GONE : View.VISIBLE);
|
||||
if (authorized != null) {
|
||||
authorized = null;
|
||||
etUser.setText(null);
|
||||
tilPassword.getEditText().setText(null);
|
||||
}
|
||||
|
||||
etHost.setText(provider.imap_host);
|
||||
etPort.setText(position == 0 ? null : Integer.toString(provider.imap_port));
|
||||
etPort.setText(provider.imap_host == null ? null : Integer.toString(provider.imap_port));
|
||||
|
||||
tvLink.setText(Html.fromHtml("<a href=\"" + provider.link + "\">" + provider.link + "</a>"));
|
||||
grpInstructions.setVisibility(provider.link == null ? View.GONE : View.VISIBLE);
|
||||
etUser.setText(null);
|
||||
tilPassword.getEditText().setText(null);
|
||||
|
||||
etName.setText(position > 1 ? provider.name : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -215,12 +233,34 @@ public class FragmentAccount extends FragmentEx {
|
||||
}
|
||||
});
|
||||
|
||||
btnAdvanced.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int visibility = (grpAdvanced.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
|
||||
grpAdvanced.setVisibility(visibility);
|
||||
if (visibility == View.VISIBLE)
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
((ScrollView) view).smoothScrollTo(0, btnCheck.getBottom());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ibPro.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
});
|
||||
|
||||
cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
|
||||
cbPrimary.setEnabled(checked);
|
||||
btnCheck.setVisibility(checked ? View.VISIBLE : View.GONE);
|
||||
btnSave.setVisibility(checked ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -231,21 +271,18 @@ public class FragmentAccount extends FragmentEx {
|
||||
btnAuthorize.setEnabled(false);
|
||||
btnCheck.setEnabled(false);
|
||||
pbCheck.setVisibility(View.VISIBLE);
|
||||
btnSave.setVisibility(View.GONE);
|
||||
grpFolders.setVisibility(View.GONE);
|
||||
btnSave.setVisibility(View.GONE);
|
||||
|
||||
Provider provider = (Provider) spProvider.getSelectedItem();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putString("name", etName.getText().toString());
|
||||
args.putString("host", etHost.getText().toString());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||
args.putInt("auth_type", authorized == null ? Helper.AUTH_TYPE_PASSWORD : provider.getAuthType());
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
|
||||
new SimpleTask<List<EntityFolder>>() {
|
||||
@Override
|
||||
@@ -266,12 +303,6 @@ public class FragmentAccount extends FragmentEx {
|
||||
if (TextUtils.isEmpty(password))
|
||||
throw new Throwable(getContext().getString(R.string.title_no_password));
|
||||
|
||||
// Refresh token
|
||||
if (id >= 0 && auth_type == Helper.AUTH_TYPE_GMAIL) {
|
||||
password = Helper.refreshToken(getContext(), "com.google", user, password);
|
||||
args.putString("password", password);
|
||||
}
|
||||
|
||||
// Check IMAP server / get folders
|
||||
List<EntityFolder> folders = new ArrayList<>();
|
||||
Properties props = MessageHelper.getSessionProperties(context, auth_type);
|
||||
@@ -282,11 +313,12 @@ public class FragmentAccount extends FragmentEx {
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
istore.connect(host, Integer.parseInt(port), user, password);
|
||||
|
||||
if (!istore.hasCapability("IDLE"))
|
||||
throw new MessagingException(getContext().getString(R.string.title_no_idle));
|
||||
|
||||
if (!istore.hasCapability("UIDPLUS"))
|
||||
throw new MessagingException(getContext().getString(R.string.title_no_uidplus));
|
||||
|
||||
args.putBoolean("idle", istore.hasCapability("IDLE"));
|
||||
|
||||
for (Folder ifolder : istore.getDefaultFolder().list("*")) {
|
||||
String type = null;
|
||||
|
||||
@@ -349,11 +381,6 @@ public class FragmentAccount extends FragmentEx {
|
||||
btnCheck.setEnabled(true);
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
// Refreshed token
|
||||
tilPassword.getEditText().setText(args.getString("password"));
|
||||
|
||||
tvIdle.setVisibility(args.getBoolean("idle") ? View.GONE : View.VISIBLE);
|
||||
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
@@ -454,15 +481,15 @@ public class FragmentAccount extends FragmentEx {
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putString("name", etName.getText().toString());
|
||||
args.putString("host", etHost.getText().toString());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||
args.putInt("auth_type", authorized == null ? Helper.AUTH_TYPE_PASSWORD : provider.getAuthType());
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
args.putString("name", etName.getText().toString());
|
||||
args.putString("signature", etSignature.getText().toString());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
args.putString("poll_interval", etInterval.getText().toString());
|
||||
args.putParcelable("drafts", drafts);
|
||||
args.putParcelable("sent", sent);
|
||||
args.putParcelable("all", all);
|
||||
@@ -472,15 +499,15 @@ public class FragmentAccount extends FragmentEx {
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onLoad(Context context, Bundle args) throws Throwable {
|
||||
String name = args.getString("name");
|
||||
String host = args.getString("host");
|
||||
String port = args.getString("port");
|
||||
String user = args.getString("user");
|
||||
String password = args.getString("password");
|
||||
int auth_type = args.getInt("auth_type");
|
||||
String name = args.getString("name");
|
||||
String signature = args.getString("signature");
|
||||
boolean synchronize = args.getBoolean("synchronize");
|
||||
boolean primary = args.getBoolean("primary");
|
||||
String poll_interval = args.getString("poll_interval");
|
||||
EntityFolder drafts = args.getParcelable("drafts");
|
||||
EntityFolder sent = args.getParcelable("sent");
|
||||
EntityFolder all = args.getParcelable("all");
|
||||
@@ -498,9 +525,6 @@ public class FragmentAccount extends FragmentEx {
|
||||
if (synchronize && drafts == null)
|
||||
throw new Throwable(getContext().getString(R.string.title_no_drafts));
|
||||
|
||||
if (TextUtils.isEmpty(poll_interval))
|
||||
poll_interval = "9";
|
||||
|
||||
// Check IMAP server
|
||||
if (synchronize) {
|
||||
Session isession = Session.getInstance(MessageHelper.getSessionProperties(context, auth_type), null);
|
||||
@@ -530,6 +554,7 @@ public class FragmentAccount extends FragmentEx {
|
||||
if (account == null)
|
||||
account = new EntityAccount();
|
||||
account.name = name;
|
||||
account.signature = signature;
|
||||
account.host = host;
|
||||
account.port = Integer.parseInt(port);
|
||||
account.user = user;
|
||||
@@ -538,7 +563,7 @@ public class FragmentAccount extends FragmentEx {
|
||||
account.synchronize = synchronize;
|
||||
account.primary = (account.synchronize && primary);
|
||||
account.store_sent = false;
|
||||
account.poll_interval = Integer.parseInt(poll_interval);
|
||||
account.poll_interval = 9;
|
||||
|
||||
if (!synchronize)
|
||||
account.error = null;
|
||||
@@ -557,6 +582,7 @@ public class FragmentAccount extends FragmentEx {
|
||||
inbox.name = "INBOX";
|
||||
inbox.type = EntityFolder.INBOX;
|
||||
inbox.synchronize = true;
|
||||
inbox.unified = true;
|
||||
inbox.after = EntityFolder.DEFAULT_INBOX_SYNC;
|
||||
|
||||
folders.add(inbox);
|
||||
@@ -670,16 +696,22 @@ public class FragmentAccount extends FragmentEx {
|
||||
Helper.setViewsEnabled(view, false);
|
||||
btnAuthorize.setVisibility(View.GONE);
|
||||
tilPassword.setPasswordVisibilityToggleEnabled(id < 0);
|
||||
tvLink.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
btnAuthorize.setEnabled(false);
|
||||
btnCheck.setEnabled(false);
|
||||
|
||||
btnAdvanced.setVisibility(View.GONE);
|
||||
|
||||
btnCheck.setVisibility(View.GONE);
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
btnSave.setVisibility(View.GONE);
|
||||
pbSave.setVisibility(View.GONE);
|
||||
tvIdle.setVisibility(View.GONE);
|
||||
grpFolders.setVisibility(View.GONE);
|
||||
|
||||
ibDelete.setVisibility(View.GONE);
|
||||
|
||||
grpServer.setVisibility(View.GONE);
|
||||
grpAuthorize.setVisibility(View.GONE);
|
||||
grpAdvanced.setVisibility(View.GONE);
|
||||
grpFolders.setVisibility(View.GONE);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -689,6 +721,7 @@ public class FragmentAccount extends FragmentEx {
|
||||
outState.putInt("provider", spProvider.getSelectedItemPosition());
|
||||
outState.putString("authorized", authorized);
|
||||
outState.putString("password", tilPassword.getEditText().getText().toString());
|
||||
outState.putInt("advanced", grpAdvanced.getVisibility());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -697,46 +730,66 @@ public class FragmentAccount extends FragmentEx {
|
||||
|
||||
// Observe
|
||||
DB.getInstance(getContext()).account().liveAccount(id).observe(getViewLifecycleOwner(), new Observer<EntityAccount>() {
|
||||
boolean once = false;
|
||||
private boolean once = false;
|
||||
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityAccount account) {
|
||||
if (once)
|
||||
return;
|
||||
once = true;
|
||||
|
||||
// Get providers
|
||||
List<Provider> providers = Provider.loadProfiles(getContext());
|
||||
providers.add(0, new Provider(getString(R.string.title_custom)));
|
||||
providers.add(0, new Provider(getString(R.string.title_select)));
|
||||
providers.add(1, new Provider(getString(R.string.title_custom)));
|
||||
|
||||
ArrayAdapter<Provider> padapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers);
|
||||
padapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
spProvider.setAdapter(padapter);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (once)
|
||||
return;
|
||||
once = true;
|
||||
|
||||
spProvider.setTag(0);
|
||||
spProvider.setSelection(0);
|
||||
if (account != null) {
|
||||
for (int pos = 1; pos < providers.size(); pos++)
|
||||
if (providers.get(pos).imap_host.equals(account.host)) {
|
||||
boolean found = false;
|
||||
for (int pos = 2; pos < providers.size(); pos++) {
|
||||
Provider provider = providers.get(pos);
|
||||
if (provider.imap_host.equals(account.host) &&
|
||||
provider.imap_port == account.port) {
|
||||
found = true;
|
||||
spProvider.setTag(pos);
|
||||
spProvider.setSelection(pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
spProvider.setTag(1);
|
||||
spProvider.setSelection(1);
|
||||
}
|
||||
etHost.setText(account.host);
|
||||
etPort.setText(Long.toString(account.port));
|
||||
}
|
||||
|
||||
etName.setText(account == null ? null : account.name);
|
||||
|
||||
etHost.setText(account == null ? null : account.host);
|
||||
etPort.setText(account == null ? null : Long.toString(account.port));
|
||||
|
||||
authorized = (account != null && account.auth_type != Helper.AUTH_TYPE_PASSWORD ? account.password : null);
|
||||
etUser.setText(account == null ? null : account.user);
|
||||
tilPassword.getEditText().setText(account == null ? null : account.password);
|
||||
|
||||
etName.setText(account == null ? null : account.name);
|
||||
etSignature.setText(account == null ? null : account.signature);
|
||||
|
||||
cbSynchronize.setChecked(account == null ? true : account.synchronize);
|
||||
cbPrimary.setChecked(account == null ? true : account.primary);
|
||||
etInterval.setText(account == null ? "9" : Integer.toString(account.poll_interval));
|
||||
|
||||
if (account == null)
|
||||
new SimpleTask<Integer>() {
|
||||
@Override
|
||||
protected Integer onLoad(Context context, Bundle args) {
|
||||
return DB.getInstance(context).account().getSynchronizingAccountCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Integer count) {
|
||||
cbPrimary.setChecked(count == 0);
|
||||
}
|
||||
}.load(FragmentAccount.this, new Bundle());
|
||||
} else {
|
||||
int provider = savedInstanceState.getInt("provider");
|
||||
spProvider.setTag(provider);
|
||||
@@ -744,24 +797,25 @@ public class FragmentAccount extends FragmentEx {
|
||||
|
||||
authorized = savedInstanceState.getString("authorized");
|
||||
tilPassword.getEditText().setText(savedInstanceState.getString("password"));
|
||||
grpAdvanced.setVisibility(savedInstanceState.getInt("advanced"));
|
||||
}
|
||||
|
||||
Helper.setViewsEnabled(view, true);
|
||||
|
||||
Provider provider = (Provider) spProvider.getSelectedItem();
|
||||
btnAuthorize.setVisibility(provider.getAuthType() == Helper.AUTH_TYPE_PASSWORD ? View.GONE : View.VISIBLE);
|
||||
tvLink.setText(Html.fromHtml("<a href=\"" + provider.link + "\">" + provider.link + "</a>"));
|
||||
grpInstructions.setVisibility(provider.link == null ? View.GONE : View.VISIBLE);
|
||||
boolean pro = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("pro", false);
|
||||
etSignature.setHint(pro ? R.string.title_optional : R.string.title_pro_feature);
|
||||
etSignature.setEnabled(pro);
|
||||
if (pro) {
|
||||
ViewGroup.LayoutParams lp = ibPro.getLayoutParams();
|
||||
lp.height = 0;
|
||||
lp.width = 0;
|
||||
ibPro.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
||||
|
||||
btnCheck.setVisibility(cbSynchronize.isChecked() ? View.VISIBLE : View.GONE);
|
||||
btnSave.setVisibility(cbSynchronize.isChecked() ? View.GONE : View.VISIBLE);
|
||||
|
||||
// Consider previous check/save/delete as cancelled
|
||||
ibDelete.setVisibility(account == null ? View.GONE : View.VISIBLE);
|
||||
btnAuthorize.setEnabled(true);
|
||||
btnCheck.setEnabled(true);
|
||||
pbWait.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
@@ -801,6 +855,8 @@ public class FragmentAccount extends FragmentEx {
|
||||
Log.i(Helper.TAG, "Accounts=" + accounts.length);
|
||||
for (final Account account : accounts)
|
||||
if (name.equals(account.name)) {
|
||||
final Snackbar snackbar = Snackbar.make(view, R.string.title_authorizing, Snackbar.LENGTH_SHORT);
|
||||
snackbar.show();
|
||||
am.getAuthToken(
|
||||
account,
|
||||
Helper.getAuthTokenType(type),
|
||||
@@ -820,6 +876,8 @@ public class FragmentAccount extends FragmentEx {
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
} finally {
|
||||
snackbar.dismiss();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -72,7 +72,7 @@ public class FragmentAnswer extends FragmentEx {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(MenuItem menuItem) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.action_trash:
|
||||
case R.id.action_delete:
|
||||
onActionTrash();
|
||||
return true;
|
||||
case R.id.action_save:
|
||||
@@ -99,7 +99,7 @@ public class FragmentAnswer extends FragmentEx {
|
||||
public void onChanged(EntityAnswer answer) {
|
||||
etName.setText(answer == null ? null : answer.name);
|
||||
etText.setText(answer == null ? null : answer.text);
|
||||
bottom_navigation.findViewById(R.id.action_trash).setVisibility(answer == null ? View.GONE : View.VISIBLE);
|
||||
bottom_navigation.findViewById(R.id.action_delete).setVisibility(answer == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -20,8 +20,8 @@ package eu.faircode.email;
|
||||
*/
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
@@ -56,19 +56,14 @@ import android.widget.Toast;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
@@ -81,6 +76,7 @@ import javax.mail.internet.InternetAddress;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.cursoradapter.widget.SimpleCursorAdapter;
|
||||
@@ -118,27 +114,6 @@ public class FragmentCompose extends FragmentEx {
|
||||
private boolean addresses;
|
||||
private boolean autosave = true;
|
||||
|
||||
private String encrypted = null;
|
||||
private OpenPgpServiceConnection openPgpConnection = null;
|
||||
|
||||
private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
openPgpConnection = new OpenPgpServiceConnection(getContext(), "org.sufficientlysecure.keychain");
|
||||
openPgpConnection.bindToService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (openPgpConnection != null) {
|
||||
openPgpConnection.unbindFromService();
|
||||
openPgpConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
@@ -253,8 +228,21 @@ public class FragmentCompose extends FragmentEx {
|
||||
bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
onAction(item.getItemId());
|
||||
int action = item.getItemId();
|
||||
if (action == R.id.action_delete) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder
|
||||
.setMessage(R.string.title_ask_delete)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
onAction(R.id.action_delete);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null).show();
|
||||
|
||||
} else
|
||||
onAction(action);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -319,7 +307,7 @@ public class FragmentCompose extends FragmentEx {
|
||||
public CharSequence convertToString(Cursor cursor) {
|
||||
int colName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
|
||||
int colEmail = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
|
||||
return cursor.getString(colName) + "<" + cursor.getString(colEmail) + ">";
|
||||
return cursor.getString(colName) + " <" + cursor.getString(colEmail) + ">";
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -327,6 +315,7 @@ public class FragmentCompose extends FragmentEx {
|
||||
rvAttachment.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvAttachment.setLayoutManager(llm);
|
||||
rvAttachment.setItemAnimator(null);
|
||||
|
||||
adapter = new AdapterAttachment(getContext(), getViewLifecycleOwner(), false);
|
||||
rvAttachment.setAdapter(adapter);
|
||||
@@ -344,7 +333,6 @@ public class FragmentCompose extends FragmentEx {
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putLong("working", working);
|
||||
outState.putString("encrypted", encrypted);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -376,11 +364,10 @@ public class FragmentCompose extends FragmentEx {
|
||||
draftLoader.load(this, args);
|
||||
}
|
||||
} else {
|
||||
encrypted = savedInstanceState.getString("encrypted");
|
||||
|
||||
long id = savedInstanceState.getLong("working");
|
||||
Bundle args = new Bundle();
|
||||
args.putString("action", "edit");
|
||||
args.putLong("id", savedInstanceState.getLong("working"));
|
||||
args.putLong("id", id);
|
||||
args.putLong("account", -1);
|
||||
args.putLong("reference", -1);
|
||||
args.putLong("answer", -1);
|
||||
@@ -407,7 +394,6 @@ public class FragmentCompose extends FragmentEx {
|
||||
menu.findItem(R.id.menu_attachment).setVisible(!free && working >= 0);
|
||||
menu.findItem(R.id.menu_attachment).setEnabled(etBody.isEnabled());
|
||||
menu.findItem(R.id.menu_addresses).setVisible(!free && working >= 0);
|
||||
menu.findItem(R.id.menu_encrypt).setVisible(encrypted == null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -419,9 +405,6 @@ public class FragmentCompose extends FragmentEx {
|
||||
case R.id.menu_addresses:
|
||||
onMenuAddresses();
|
||||
return true;
|
||||
case R.id.menu_encrypt:
|
||||
onMenuEncrypt();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -438,81 +421,6 @@ public class FragmentCompose extends FragmentEx {
|
||||
grpAddresses.setVisibility(grpAddresses.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void onMenuEncrypt() {
|
||||
Log.i(Helper.TAG, "On encrypt");
|
||||
|
||||
if (!PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("pro", false)) {
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
|
||||
fragmentTransaction.commit();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (openPgpConnection == null || !openPgpConnection.isBound())
|
||||
throw new IllegalArgumentException(getString(R.string.title_no_openpgp));
|
||||
|
||||
EntityIdentity identity = (EntityIdentity) spFrom.getSelectedItem();
|
||||
if (identity == null)
|
||||
throw new IllegalArgumentException(getString(R.string.title_from_missing));
|
||||
|
||||
Intent data = new Intent();
|
||||
data.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
||||
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{identity.email});
|
||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
String plain = etBody.getText().toString();
|
||||
final InputStream is = new ByteArrayInputStream(plain.getBytes("UTF-8"));
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(getContext(), openPgpConnection.getService());
|
||||
api.executeApiAsync(data, is, os, new OpenPgpApi.IOpenPgpCallback() {
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
try {
|
||||
int code = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||
switch (code) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
||||
Log.i(Helper.TAG, "Encrypted");
|
||||
FragmentCompose.this.encrypted = os.toString("UTF-8");
|
||||
getActivity().invalidateOptionsMenu();
|
||||
etBody.setText(FragmentCompose.this.encrypted);
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
|
||||
Log.i(Helper.TAG, "User interaction");
|
||||
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
startIntentSenderForResult(
|
||||
pi.getIntentSender(),
|
||||
ActivityCompose.REQUEST_OPENPGP,
|
||||
null, 0, 0, 0,
|
||||
new Bundle());
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_ERROR: {
|
||||
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
||||
throw new IllegalArgumentException(error.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
Log.i(Helper.TAG, "Compose onActivityResult request=" + requestCode + " result=" + resultCode + " data=" + data);
|
||||
@@ -520,11 +428,10 @@ public class FragmentCompose extends FragmentEx {
|
||||
if (requestCode == ActivityCompose.REQUEST_ATTACHMENT) {
|
||||
if (data != null)
|
||||
handleAddAttachment(data);
|
||||
} else if (requestCode == ActivityCompose.REQUEST_OPENPGP) {
|
||||
Log.i(Helper.TAG, "User interacted");
|
||||
onMenuEncrypt();
|
||||
} else
|
||||
handlePickContact(requestCode, data);
|
||||
} else {
|
||||
if (data != null)
|
||||
handlePickContact(requestCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -679,7 +586,7 @@ public class FragmentCompose extends FragmentEx {
|
||||
os = new BufferedOutputStream(new FileOutputStream(file));
|
||||
|
||||
int size = 0;
|
||||
byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
|
||||
byte[] buffer = new byte[Helper.ATTACHMENT_BUFFER_SIZE];
|
||||
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
|
||||
size += len;
|
||||
os.write(buffer, 0, len);
|
||||
@@ -715,11 +622,11 @@ public class FragmentCompose extends FragmentEx {
|
||||
protected EntityMessage onLoad(Context context, Bundle args) throws IOException {
|
||||
String action = args.getString("action");
|
||||
long id = args.getLong("id", -1);
|
||||
long account = args.getLong("account", -1);
|
||||
long reference = args.getLong("reference", -1);
|
||||
long answer = args.getLong("answer", -1);
|
||||
boolean pro = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("pro", false);
|
||||
|
||||
Log.i(Helper.TAG, "Load draft action=" + action + " id=" + id + " account=" + account + " reference=" + reference);
|
||||
Log.i(Helper.TAG, "Load draft action=" + action + " id=" + id + " reference=" + reference);
|
||||
|
||||
EntityMessage draft;
|
||||
|
||||
@@ -734,34 +641,39 @@ public class FragmentCompose extends FragmentEx {
|
||||
} else
|
||||
return draft;
|
||||
|
||||
EntityAccount account;
|
||||
EntityMessage ref = db.message().getMessage(reference);
|
||||
if (ref == null) {
|
||||
if (account < 0) {
|
||||
EntityAccount a = db.account().getPrimaryAccount();
|
||||
if (a == null)
|
||||
long aid = args.getLong("account", -1);
|
||||
if (aid < 0) {
|
||||
account = db.account().getPrimaryAccount();
|
||||
if (account == null)
|
||||
throw new IllegalArgumentException(context.getString(R.string.title_no_account));
|
||||
account = a.id;
|
||||
}
|
||||
} else
|
||||
account = db.account().getAccount(aid);
|
||||
} else {
|
||||
account = ref.account;
|
||||
account = db.account().getAccount(ref.account);
|
||||
|
||||
// Reply to sender, not to known self
|
||||
if (ref.from != null && ref.from.length == 1) {
|
||||
// All identities, synchronized or not
|
||||
// Reply to recipient, not to known self
|
||||
if (ref.from != null && ref.from.length > 0) {
|
||||
String from = Helper.canonicalAddress(((InternetAddress) ref.from[0]).getAddress());
|
||||
List<EntityIdentity> identities = db.identity().getIdentities();
|
||||
for (EntityIdentity identity : identities)
|
||||
if (((InternetAddress) ref.from[0]).getAddress().equals(identity.email)) {
|
||||
for (EntityIdentity identity : identities) {
|
||||
String email = Helper.canonicalAddress(identity.email);
|
||||
if (from.equals(email)) {
|
||||
Log.i(Helper.TAG, "Swapping from/to");
|
||||
Address[] tmp = ref.to;
|
||||
ref.to = ref.from;
|
||||
ref.reply = null;
|
||||
ref.from = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EntityFolder drafts;
|
||||
drafts = db.folder().getFolderByType(account, EntityFolder.DRAFTS);
|
||||
drafts = db.folder().getFolderByType(account.id, EntityFolder.DRAFTS);
|
||||
if (drafts == null)
|
||||
drafts = db.folder().getPrimaryDrafts();
|
||||
if (drafts == null)
|
||||
@@ -770,7 +682,7 @@ public class FragmentCompose extends FragmentEx {
|
||||
String body = "";
|
||||
|
||||
draft = new EntityMessage();
|
||||
draft.account = account;
|
||||
draft.account = account.id;
|
||||
draft.folder = drafts.id;
|
||||
draft.msgid = EntityMessage.generateMessageId(); // for multiple appends
|
||||
|
||||
@@ -798,8 +710,13 @@ public class FragmentCompose extends FragmentEx {
|
||||
|
||||
draft.subject = args.getString("subject");
|
||||
body = args.getString("body");
|
||||
if (!TextUtils.isEmpty(body))
|
||||
body = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
if (body == null)
|
||||
body = "";
|
||||
else
|
||||
body = body.replaceAll("\\r?\\n", "<br />");
|
||||
|
||||
if (pro && !TextUtils.isEmpty(account.signature))
|
||||
body = "<br>" + account.signature.replaceAll("\\r?\\n", "<br />") + "<br>" + body;
|
||||
|
||||
} else {
|
||||
draft.thread = ref.thread;
|
||||
@@ -808,8 +725,24 @@ public class FragmentCompose extends FragmentEx {
|
||||
draft.replying = ref.id;
|
||||
draft.to = (ref.reply == null || ref.reply.length == 0 ? ref.from : ref.reply);
|
||||
draft.from = ref.to;
|
||||
if ("reply_all".equals(action))
|
||||
draft.cc = ref.cc;
|
||||
|
||||
if ("reply_all".equals(action)) {
|
||||
List<Address> addresses = new ArrayList<>();
|
||||
if (ref.to != null)
|
||||
addresses.addAll(Arrays.asList(ref.to));
|
||||
if (ref.cc != null)
|
||||
addresses.addAll(Arrays.asList(ref.cc));
|
||||
List<EntityIdentity> identities = db.identity().getIdentities();
|
||||
for (Address address : new ArrayList<>(addresses)) {
|
||||
String cc = Helper.canonicalAddress(((InternetAddress) address).getAddress());
|
||||
for (EntityIdentity identity : identities) {
|
||||
String email = Helper.canonicalAddress(identity.email);
|
||||
if (cc.equals(email))
|
||||
addresses.remove(address);
|
||||
}
|
||||
}
|
||||
draft.cc = addresses.toArray(new Address[0]);
|
||||
}
|
||||
|
||||
} else if ("forward".equals(action)) {
|
||||
//msg.replying = ref.id;
|
||||
@@ -818,8 +751,14 @@ public class FragmentCompose extends FragmentEx {
|
||||
|
||||
if ("reply".equals(action) || "reply_all".equals(action)) {
|
||||
String text = "";
|
||||
if (answer > 0)
|
||||
if (answer > 0) {
|
||||
text = db.answer().getAnswer(answer).text;
|
||||
|
||||
String name = "";
|
||||
if (draft.to != null && draft.to.length > 0)
|
||||
name = ((InternetAddress) draft.to[0]).getPersonal();
|
||||
text = text.replace("$name$", name);
|
||||
}
|
||||
draft.subject = context.getString(R.string.title_subject_reply, ref.subject);
|
||||
body = String.format("%s<br><br>%s %s:<br><br>%s",
|
||||
text.replaceAll("\\r?\\n", "<br />"),
|
||||
@@ -833,12 +772,18 @@ public class FragmentCompose extends FragmentEx {
|
||||
Html.escapeHtml(MessageHelper.getFormattedAddresses(ref.from, true)),
|
||||
HtmlHelper.sanitize(context, ref.read(context), true));
|
||||
}
|
||||
|
||||
if (pro && !TextUtils.isEmpty(account.signature))
|
||||
body = "<br>" + account.signature.replaceAll("\\r?\\n", "<br />") + "<br>" + body;
|
||||
}
|
||||
|
||||
draft.received = new Date().getTime();
|
||||
draft.seen = false;
|
||||
draft.ui_seen = false;
|
||||
draft.flagged = false;
|
||||
draft.ui_flagged = false;
|
||||
draft.ui_hide = false;
|
||||
draft.ui_found = false;
|
||||
|
||||
draft.id = db.message().insertMessage(draft);
|
||||
draft.write(context, body == null ? "" : body);
|
||||
@@ -881,14 +826,11 @@ public class FragmentCompose extends FragmentEx {
|
||||
@Override
|
||||
protected Spanned onLoad(Context context, Bundle args) throws Throwable {
|
||||
String body = EntityMessage.read(context, args.getLong("id"));
|
||||
if (body != null && body.startsWith("-----BEGIN PGP MESSAGE-----"))
|
||||
args.putString("encrypted", body);
|
||||
return Html.fromHtml(body);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Spanned body) {
|
||||
FragmentCompose.this.encrypted = args.getString("encrypted");
|
||||
getActivity().invalidateOptionsMenu();
|
||||
etBody.setText(body);
|
||||
etBody.setSelection(0);
|
||||
@@ -942,9 +884,9 @@ public class FragmentCompose extends FragmentEx {
|
||||
// Select identity matching from address
|
||||
if (!found && draft.from != null && draft.from.length > 0) {
|
||||
String from = Helper.canonicalAddress(((InternetAddress) draft.from[0]).getAddress());
|
||||
|
||||
for (int pos = 0; pos < identities.size(); pos++) {
|
||||
if (Helper.canonicalAddress(identities.get(pos).email).equals(from)) {
|
||||
String email = Helper.canonicalAddress(identities.get(pos).email);
|
||||
if (email.equals(from)) {
|
||||
spFrom.setSelection(pos);
|
||||
found = true;
|
||||
break;
|
||||
@@ -1025,10 +967,10 @@ public class FragmentCompose extends FragmentEx {
|
||||
Log.i(Helper.TAG, "Load action id=" + draft.id + " action=" + action);
|
||||
|
||||
// Convert data
|
||||
Address afrom[] = (identity == null ? null : new Address[]{new InternetAddress(identity.email, identity.name)});
|
||||
Address ato[] = (TextUtils.isEmpty(to) ? null : InternetAddress.parse(to));
|
||||
Address acc[] = (TextUtils.isEmpty(cc) ? null : InternetAddress.parse(cc));
|
||||
Address abcc[] = (TextUtils.isEmpty(bcc) ? null : InternetAddress.parse(bcc));
|
||||
InternetAddress afrom[] = (identity == null ? null : new InternetAddress[]{new InternetAddress(identity.email, identity.name)});
|
||||
InternetAddress ato[] = (TextUtils.isEmpty(to) ? null : InternetAddress.parse(to));
|
||||
InternetAddress acc[] = (TextUtils.isEmpty(cc) ? null : InternetAddress.parse(cc));
|
||||
InternetAddress abcc[] = (TextUtils.isEmpty(bcc) ? null : InternetAddress.parse(bcc));
|
||||
|
||||
// Update draft
|
||||
draft.identity = (identity == null ? null : identity.id);
|
||||
@@ -1039,31 +981,17 @@ public class FragmentCompose extends FragmentEx {
|
||||
draft.subject = subject;
|
||||
draft.received = new Date().getTime();
|
||||
|
||||
String pbody;
|
||||
if (encrypted == null)
|
||||
pbody = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
else
|
||||
pbody = encrypted;
|
||||
String pbody = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
|
||||
// Execute action
|
||||
if (action == R.id.action_trash) {
|
||||
draft.ui_seen = true;
|
||||
if (action == R.id.action_delete) {
|
||||
draft.msgid = null;
|
||||
draft.ui_hide = true;
|
||||
db.message().updateMessage(draft);
|
||||
draft.write(context, pbody);
|
||||
|
||||
EntityFolder trash = db.folder().getFolderByType(draft.account, EntityFolder.TRASH);
|
||||
EntityOperation.queue(db, draft, EntityOperation.MOVE, trash.id);
|
||||
EntityOperation.queue(db, draft, EntityOperation.DELETE);
|
||||
|
||||
} else if (action == R.id.action_save) {
|
||||
EntityIdentity primary = db.identity().getPrimaryIdentity(draft.account);
|
||||
if ((primary == null || draft.identity == primary.id) &&
|
||||
ato == null && acc == null && abcc == null &&
|
||||
TextUtils.isEmpty(subject) &&
|
||||
TextUtils.isEmpty(body) &&
|
||||
db.attachment().getAttachmentCount(draft.id) == 0)
|
||||
return null;
|
||||
|
||||
db.message().updateMessage(draft);
|
||||
draft.write(context, pbody);
|
||||
|
||||
@@ -1134,10 +1062,10 @@ public class FragmentCompose extends FragmentEx {
|
||||
Helper.setViewsEnabled(view, true);
|
||||
getActivity().invalidateOptionsMenu();
|
||||
|
||||
if (action == R.id.action_trash) {
|
||||
if (action == R.id.action_delete) {
|
||||
autosave = false;
|
||||
getFragmentManager().popBackStack();
|
||||
Toast.makeText(getContext(), R.string.title_draft_trashed, Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(getContext(), R.string.title_draft_deleted, Toast.LENGTH_LONG).show();
|
||||
|
||||
} else if (action == R.id.action_save) {
|
||||
if (draft != null)
|
||||
@@ -1166,7 +1094,7 @@ public class FragmentCompose extends FragmentEx {
|
||||
private Context context;
|
||||
private List<EntityIdentity> identities;
|
||||
|
||||
public IdentityAdapter(@NonNull Context context, List<EntityIdentity> identities) {
|
||||
IdentityAdapter(@NonNull Context context, List<EntityIdentity> identities) {
|
||||
super(context, 0, identities);
|
||||
this.context = context;
|
||||
this.identities = identities;
|
||||
@@ -1183,10 +1111,8 @@ public class FragmentCompose extends FragmentEx {
|
||||
return getLayout(position, convertView, parent);
|
||||
}
|
||||
|
||||
public View getLayout(int position, View convertView, ViewGroup parent) {
|
||||
View view = convertView;
|
||||
if (view == convertView)
|
||||
view = LayoutInflater.from(context).inflate(R.layout.spinner_item2, parent, false);
|
||||
View getLayout(int position, View convertView, ViewGroup parent) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.spinner_item2, parent, false);
|
||||
|
||||
EntityIdentity identity = identities.get(position);
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import androidx.lifecycle.Observer;
|
||||
public class FragmentFolder extends FragmentEx {
|
||||
private ViewGroup view;
|
||||
private CheckBox cbSynchronize;
|
||||
private CheckBox cbUnified;
|
||||
private EditText etAfter;
|
||||
private Button btnSave;
|
||||
private ProgressBar pbSave;
|
||||
@@ -63,6 +64,7 @@ public class FragmentFolder extends FragmentEx {
|
||||
|
||||
// Get controls
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
cbUnified = view.findViewById(R.id.cbUnified);
|
||||
etAfter = view.findViewById(R.id.etAfter);
|
||||
pbSave = view.findViewById(R.id.pbSave);
|
||||
btnSave = view.findViewById(R.id.btnSave);
|
||||
@@ -78,6 +80,7 @@ public class FragmentFolder extends FragmentEx {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
args.putBoolean("unified", cbUnified.isChecked());
|
||||
args.putString("after", etAfter.getText().toString());
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@@ -85,6 +88,7 @@ public class FragmentFolder extends FragmentEx {
|
||||
protected Void onLoad(Context context, Bundle args) {
|
||||
long id = args.getLong("id");
|
||||
boolean synchronize = args.getBoolean("synchronize");
|
||||
boolean unified = args.getBoolean("unified");
|
||||
String after = args.getString("after");
|
||||
int days = (TextUtils.isEmpty(after) ? 7 : Integer.parseInt(after));
|
||||
|
||||
@@ -92,7 +96,7 @@ public class FragmentFolder extends FragmentEx {
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
db.folder().setFolderProperties(id, synchronize, days);
|
||||
db.folder().setFolderProperties(id, synchronize, unified, days);
|
||||
if (!synchronize)
|
||||
db.folder().setFolderError(id, null);
|
||||
|
||||
@@ -142,7 +146,7 @@ public class FragmentFolder extends FragmentEx {
|
||||
|
||||
// Observe
|
||||
DB.getInstance(getContext()).folder().liveFolder(id).observe(getViewLifecycleOwner(), new Observer<EntityFolder>() {
|
||||
boolean once = false;
|
||||
private boolean once = false;
|
||||
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityFolder folder) {
|
||||
@@ -151,12 +155,13 @@ public class FragmentFolder extends FragmentEx {
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (once)
|
||||
return;
|
||||
once = true;
|
||||
if (once)
|
||||
return;
|
||||
once = true;
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
cbSynchronize.setChecked(folder.synchronize);
|
||||
cbUnified.setChecked(folder.unified);
|
||||
etAfter.setText(Integer.toString(folder.after));
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,18 @@ public class FragmentFolders extends FragmentEx {
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private long account;
|
||||
private AdapterFolder adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
account = (args == null ? -1 : args.getLong("account"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
@@ -89,10 +99,6 @@ public class FragmentFolders extends FragmentEx {
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
long account = (args == null ? -1 : args.getLong("account"));
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
|
||||
// Observe account
|
||||
|
||||
@@ -22,11 +22,7 @@ package eu.faircode.email;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -39,7 +35,6 @@ import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
@@ -60,16 +55,16 @@ import androidx.lifecycle.Observer;
|
||||
public class FragmentIdentity extends FragmentEx {
|
||||
private ViewGroup view;
|
||||
private EditText etName;
|
||||
private Spinner spAccount;
|
||||
private Button btnAdvanced;
|
||||
private EditText etEmail;
|
||||
private EditText etReplyTo;
|
||||
private Spinner spProvider;
|
||||
private Spinner spAccount;
|
||||
private EditText etHost;
|
||||
private CheckBox cbStartTls;
|
||||
private EditText etPort;
|
||||
private EditText etUser;
|
||||
private TextInputLayout tilPassword;
|
||||
private TextView tvLink;
|
||||
private CheckBox cbSynchronize;
|
||||
private CheckBox cbPrimary;
|
||||
private CheckBox cbStoreSent;
|
||||
@@ -77,7 +72,7 @@ public class FragmentIdentity extends FragmentEx {
|
||||
private ProgressBar pbSave;
|
||||
private ImageButton ibDelete;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpInstructions;
|
||||
private Group grpAdvanced;
|
||||
|
||||
private long id = -1;
|
||||
|
||||
@@ -99,16 +94,16 @@ public class FragmentIdentity extends FragmentEx {
|
||||
|
||||
// Get controls
|
||||
etName = view.findViewById(R.id.etName);
|
||||
spAccount = view.findViewById(R.id.spAccount);
|
||||
btnAdvanced = view.findViewById(R.id.btnAdvanced);
|
||||
etEmail = view.findViewById(R.id.etEmail);
|
||||
etReplyTo = view.findViewById(R.id.etReplyTo);
|
||||
spProvider = view.findViewById(R.id.spProvider);
|
||||
spAccount = view.findViewById(R.id.spAccount);
|
||||
etHost = view.findViewById(R.id.etHost);
|
||||
cbStartTls = view.findViewById(R.id.cbStartTls);
|
||||
etPort = view.findViewById(R.id.etPort);
|
||||
etUser = view.findViewById(R.id.etUser);
|
||||
tilPassword = view.findViewById(R.id.tilPassword);
|
||||
tvLink = view.findViewById(R.id.tvLink);
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
cbPrimary = view.findViewById(R.id.cbPrimary);
|
||||
cbStoreSent = view.findViewById(R.id.cbStoreSent);
|
||||
@@ -116,28 +111,19 @@ public class FragmentIdentity extends FragmentEx {
|
||||
pbSave = view.findViewById(R.id.pbSave);
|
||||
ibDelete = view.findViewById(R.id.ibDelete);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpInstructions = view.findViewById(R.id.grpInstructions);
|
||||
grpAdvanced = view.findViewById(R.id.grpAdvanced);
|
||||
|
||||
// Wire controls
|
||||
|
||||
etEmail.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
etUser.setText(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
spAccount.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
btnAdvanced.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
|
||||
if (position == 0)
|
||||
grpAdvanced.setVisibility(View.GONE);
|
||||
tilPassword.setPasswordVisibilityToggleEnabled(position == 0);
|
||||
btnSave.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
|
||||
|
||||
Integer tag = (Integer) adapterView.getTag();
|
||||
if (tag != null && tag.equals(position))
|
||||
return;
|
||||
@@ -146,20 +132,36 @@ public class FragmentIdentity extends FragmentEx {
|
||||
EntityAccount account = (EntityAccount) adapterView.getAdapter().getItem(position);
|
||||
|
||||
// Select associated provider
|
||||
for (int pos = 1; pos < spProvider.getAdapter().getCount(); pos++) {
|
||||
Provider provider = (Provider) spProvider.getItemAtPosition(pos);
|
||||
if (provider.imap_host.equals(account.host) && provider.imap_port == account.port) {
|
||||
spProvider.setSelection(pos);
|
||||
break;
|
||||
if (position == 0)
|
||||
spProvider.setSelection(0);
|
||||
else {
|
||||
boolean found = false;
|
||||
for (int pos = 1; pos < spProvider.getAdapter().getCount(); pos++) {
|
||||
Provider provider = (Provider) spProvider.getItemAtPosition(pos);
|
||||
if (provider.imap_host.equals(account.host) &&
|
||||
provider.imap_port == account.port) {
|
||||
found = true;
|
||||
|
||||
spProvider.setSelection(pos);
|
||||
|
||||
// This is needed because the spinner might be invisible
|
||||
etHost.setText(provider.smtp_host);
|
||||
etPort.setText(Integer.toString(provider.smtp_port));
|
||||
cbStartTls.setChecked(provider.starttls);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
grpAdvanced.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// Copy account user name
|
||||
etEmail.setText(account.user);
|
||||
etUser.setText(account.user);
|
||||
|
||||
// Copy account password
|
||||
tilPassword.getEditText().setText(account.password);
|
||||
tilPassword.setPasswordVisibilityToggleEnabled(position == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -167,6 +169,14 @@ public class FragmentIdentity extends FragmentEx {
|
||||
}
|
||||
});
|
||||
|
||||
btnAdvanced.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int visibility = (grpAdvanced.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
|
||||
grpAdvanced.setVisibility(visibility);
|
||||
}
|
||||
});
|
||||
|
||||
spProvider.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
@@ -181,10 +191,6 @@ public class FragmentIdentity extends FragmentEx {
|
||||
etHost.setText(provider.smtp_host);
|
||||
etPort.setText(position == 0 ? null : Integer.toString(provider.smtp_port));
|
||||
cbStartTls.setChecked(provider.starttls);
|
||||
|
||||
// Show link to instructions
|
||||
tvLink.setText(Html.fromHtml("<a href=\"" + provider.link + "\">" + provider.link + "</a>"));
|
||||
grpInstructions.setVisibility(provider.link == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -236,9 +242,9 @@ public class FragmentIdentity extends FragmentEx {
|
||||
protected Void onLoad(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
String name = args.getString("name");
|
||||
long account = args.getLong("account");
|
||||
String email = args.getString("email");
|
||||
String replyto = args.getString("replyto");
|
||||
long account = args.getLong("account");
|
||||
String host = args.getString("host");
|
||||
boolean starttls = args.getBoolean("starttls");
|
||||
String port = args.getString("port");
|
||||
@@ -253,8 +259,6 @@ public class FragmentIdentity extends FragmentEx {
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.title_no_name));
|
||||
if (TextUtils.isEmpty(email))
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.title_no_email));
|
||||
if (account < 0)
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.title_no_account));
|
||||
if (TextUtils.isEmpty(host))
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.title_no_host));
|
||||
if (TextUtils.isEmpty(port))
|
||||
@@ -267,10 +271,6 @@ public class FragmentIdentity extends FragmentEx {
|
||||
if (TextUtils.isEmpty(replyto))
|
||||
replyto = null;
|
||||
|
||||
// Refresh token
|
||||
if (id >= 0 && auth_type == Helper.AUTH_TYPE_GMAIL)
|
||||
password = Helper.refreshToken(getContext(), "com.google", user, password);
|
||||
|
||||
// Check SMTP server
|
||||
if (synchronize) {
|
||||
Properties props = MessageHelper.getSessionProperties(context, auth_type);
|
||||
@@ -293,9 +293,9 @@ public class FragmentIdentity extends FragmentEx {
|
||||
if (identity == null)
|
||||
identity = new EntityIdentity();
|
||||
identity.name = name;
|
||||
identity.account = account;
|
||||
identity.email = email;
|
||||
identity.replyto = replyto;
|
||||
identity.account = account;
|
||||
identity.host = host;
|
||||
identity.port = Integer.parseInt(port);
|
||||
identity.starttls = starttls;
|
||||
@@ -388,8 +388,9 @@ public class FragmentIdentity extends FragmentEx {
|
||||
// Initialize
|
||||
Helper.setViewsEnabled(view, false);
|
||||
tilPassword.setPasswordVisibilityToggleEnabled(id < 0);
|
||||
tvLink.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
btnSave.setEnabled(false);
|
||||
btnSave.setVisibility(View.GONE);
|
||||
btnAdvanced.setVisibility(View.GONE);
|
||||
grpAdvanced.setVisibility(View.GONE);
|
||||
pbSave.setVisibility(View.GONE);
|
||||
ibDelete.setVisibility(View.GONE);
|
||||
|
||||
@@ -402,6 +403,7 @@ public class FragmentIdentity extends FragmentEx {
|
||||
outState.putInt("account", spAccount.getSelectedItemPosition());
|
||||
outState.putInt("provider", spProvider.getSelectedItemPosition());
|
||||
outState.putString("password", tilPassword.getEditText().getText().toString());
|
||||
outState.putInt("advanced", grpAdvanced.getVisibility());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -412,15 +414,15 @@ public class FragmentIdentity extends FragmentEx {
|
||||
|
||||
// Observe identity
|
||||
db.identity().liveIdentity(id).observe(getViewLifecycleOwner(), new Observer<EntityIdentity>() {
|
||||
boolean once = false;
|
||||
private boolean once = false;
|
||||
|
||||
@Override
|
||||
public void onChanged(@Nullable final EntityIdentity identity) {
|
||||
if (savedInstanceState == null) {
|
||||
if (once)
|
||||
return;
|
||||
once = true;
|
||||
if (once)
|
||||
return;
|
||||
once = true;
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
etName.setText(identity == null ? null : identity.name);
|
||||
etEmail.setText(identity == null ? null : identity.email);
|
||||
etReplyTo.setText(identity == null ? null : identity.replyto);
|
||||
@@ -434,23 +436,42 @@ public class FragmentIdentity extends FragmentEx {
|
||||
cbStoreSent.setChecked(identity == null ? false : identity.store_sent);
|
||||
|
||||
etName.requestFocus();
|
||||
} else
|
||||
|
||||
if (identity == null)
|
||||
new SimpleTask<Integer>() {
|
||||
@Override
|
||||
protected Integer onLoad(Context context, Bundle args) {
|
||||
return DB.getInstance(context).identity().getSynchronizingIdentityCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Integer count) {
|
||||
cbPrimary.setChecked(count == 0);
|
||||
}
|
||||
}.load(FragmentIdentity.this, new Bundle());
|
||||
} else {
|
||||
tilPassword.getEditText().setText(savedInstanceState.getString("password"));
|
||||
grpAdvanced.setVisibility(savedInstanceState.getInt("advanced"));
|
||||
}
|
||||
|
||||
Helper.setViewsEnabled(view, true);
|
||||
|
||||
grpInstructions.setVisibility(View.GONE);
|
||||
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
||||
|
||||
// Consider previous save/delete as cancelled
|
||||
ibDelete.setVisibility(identity == null ? View.GONE : View.VISIBLE);
|
||||
btnSave.setEnabled(true);
|
||||
pbWait.setVisibility(View.GONE);
|
||||
|
||||
db.account().liveAccounts().removeObservers(getViewLifecycleOwner());
|
||||
db.account().liveAccounts().observe(getViewLifecycleOwner(), new Observer<List<EntityAccount>>() {
|
||||
private boolean once = false;
|
||||
|
||||
@Override
|
||||
public void onChanged(List<EntityAccount> accounts) {
|
||||
if (once)
|
||||
return;
|
||||
once = true;
|
||||
|
||||
if (accounts == null)
|
||||
accounts = new ArrayList<>();
|
||||
|
||||
@@ -503,10 +524,6 @@ public class FragmentIdentity extends FragmentEx {
|
||||
spAccount.setTag(account);
|
||||
spAccount.setSelection(account);
|
||||
}
|
||||
|
||||
Provider provider = (Provider) spProvider.getSelectedItem();
|
||||
tvLink.setText(Html.fromHtml("<a href=\"" + provider.link + "\">" + provider.link + "</a>"));
|
||||
grpInstructions.setVisibility(provider.link == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
94
app/src/main/java/eu/faircode/email/FragmentLogs.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
NetGuard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class FragmentLogs extends FragmentEx {
|
||||
private RecyclerView rvLog;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
|
||||
private AdapterLog adapter;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
setSubtitle(R.string.title_log);
|
||||
|
||||
View view = inflater.inflate(R.layout.fragment_logs, container, false);
|
||||
|
||||
// Get controls
|
||||
rvLog = view.findViewById(R.id.rvLog);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvLog.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvLog.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterLog(getContext());
|
||||
rvLog.setAdapter(adapter);
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
long from = new Date().getTime() - 24 * 3600 * 1000L;
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
db.log().liveLogs(from).observe(getViewLifecycleOwner(), new Observer<List<EntityLog>>() {
|
||||
@Override
|
||||
public void onChanged(List<EntityLog> logs) {
|
||||
if (logs == null)
|
||||
logs = new ArrayList<>();
|
||||
|
||||
adapter.set(logs);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ package eu.faircode.email;
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
@@ -37,8 +36,10 @@ import android.text.Html;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.SpannedString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -49,23 +50,16 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -82,8 +76,6 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -97,13 +89,11 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class FragmentMessage extends FragmentEx {
|
||||
private ViewGroup view;
|
||||
private View vwAnswerAnchor;
|
||||
private ImageView ivFlagged;
|
||||
private TextView tvFrom;
|
||||
private TextView tvSize;
|
||||
private TextView tvTime;
|
||||
private TextView tvTo;
|
||||
private TextView tvSubject;
|
||||
@@ -111,6 +101,8 @@ public class FragmentMessage extends FragmentEx {
|
||||
private TextView tvReplyTo;
|
||||
private TextView tvCc;
|
||||
private TextView tvBcc;
|
||||
private TextView tvRawHeaders;
|
||||
private ProgressBar pbRawHeaders;
|
||||
private RecyclerView rvAttachment;
|
||||
private TextView tvError;
|
||||
private View vSeparatorBody;
|
||||
@@ -121,18 +113,19 @@ public class FragmentMessage extends FragmentEx {
|
||||
private BottomNavigationView bottom_navigation;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpHeader;
|
||||
private Group grpThread;
|
||||
private Group grpAddresses;
|
||||
private Group grpRawHeaders;
|
||||
private Group grpAttachments;
|
||||
private Group grpError;
|
||||
private Group grpMessage;
|
||||
|
||||
private TupleMessageEx message = null;
|
||||
private boolean free = false;
|
||||
private boolean addresses = false;
|
||||
private boolean headers = false;
|
||||
private AdapterAttachment adapter;
|
||||
|
||||
private String decrypted = null;
|
||||
private OpenPgpServiceConnection openPgpConnection = null;
|
||||
|
||||
private boolean debug;
|
||||
private DateFormat df = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
|
||||
|
||||
@@ -146,18 +139,6 @@ public class FragmentMessage extends FragmentEx {
|
||||
message = (TupleMessageEx) getArguments().getSerializable("message");
|
||||
else
|
||||
message = (TupleMessageEx) savedInstanceState.getSerializable("message");
|
||||
|
||||
openPgpConnection = new OpenPgpServiceConnection(getContext(), "org.sufficientlysecure.keychain");
|
||||
openPgpConnection.bindToService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (openPgpConnection != null) {
|
||||
openPgpConnection.unbindFromService();
|
||||
openPgpConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,8 +151,8 @@ public class FragmentMessage extends FragmentEx {
|
||||
|
||||
// Get controls
|
||||
vwAnswerAnchor = view.findViewById(R.id.vwAnswerAnchor);
|
||||
ivFlagged = view.findViewById(R.id.ivFlagged);
|
||||
tvFrom = view.findViewById(R.id.tvFrom);
|
||||
tvSize = view.findViewById(R.id.tvSize);
|
||||
tvTime = view.findViewById(R.id.tvTime);
|
||||
tvTo = view.findViewById(R.id.tvTo);
|
||||
tvSubject = view.findViewById(R.id.tvSubject);
|
||||
@@ -179,6 +160,8 @@ public class FragmentMessage extends FragmentEx {
|
||||
tvReplyTo = view.findViewById(R.id.tvReplyTo);
|
||||
tvCc = view.findViewById(R.id.tvCc);
|
||||
tvBcc = view.findViewById(R.id.tvBcc);
|
||||
tvRawHeaders = view.findViewById(R.id.tvRawHeaders);
|
||||
pbRawHeaders = view.findViewById(R.id.pbRawHeaders);
|
||||
rvAttachment = view.findViewById(R.id.rvAttachment);
|
||||
tvError = view.findViewById(R.id.tvError);
|
||||
vSeparatorBody = view.findViewById(R.id.vSeparatorBody);
|
||||
@@ -189,24 +172,46 @@ public class FragmentMessage extends FragmentEx {
|
||||
bottom_navigation = view.findViewById(R.id.bottom_navigation);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpHeader = view.findViewById(R.id.grpHeader);
|
||||
grpThread = view.findViewById(R.id.grpThread);
|
||||
grpAddresses = view.findViewById(R.id.grpAddresses);
|
||||
grpRawHeaders = view.findViewById(R.id.grpRawHeaders);
|
||||
grpAttachments = view.findViewById(R.id.grpAttachments);
|
||||
grpError = view.findViewById(R.id.grpError);
|
||||
grpMessage = view.findViewById(R.id.grpMessage);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
tvCount.setOnClickListener(new View.OnClickListener() {
|
||||
ivFlagged.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
onMenuThread();
|
||||
public void onClick(View v) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("account", message.account);
|
||||
args.putString("thread", message.thread);
|
||||
args.putBoolean("flagged", !message.ui_flagged);
|
||||
Log.i(Helper.TAG, "Set message id=" + message.id + " flagged=" + !message.ui_flagged);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onLoad(Context context, Bundle args) throws Throwable {
|
||||
long account = args.getLong("account");
|
||||
String thread = args.getString("thread");
|
||||
boolean flagged = args.getBoolean("flagged");
|
||||
DB db = DB.getInstance(context);
|
||||
for (EntityMessage message : db.message().getMessageByThread(account, thread)) {
|
||||
db.message().setMessageUiFlagged(message.id, flagged);
|
||||
EntityOperation.queue(db, message, EntityOperation.FLAG, flagged);
|
||||
}
|
||||
EntityOperation.process(context);
|
||||
return null;
|
||||
}
|
||||
}.load(FragmentMessage.this, args);
|
||||
}
|
||||
});
|
||||
|
||||
tvBody.setMovementMethod(new LinkMovementMethod() {
|
||||
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
|
||||
if (event.getAction() != MotionEvent.ACTION_UP)
|
||||
return super.onTouchEvent(widget, buffer, event);
|
||||
return false;
|
||||
|
||||
int x = (int) event.getX();
|
||||
int y = (int) event.getY();
|
||||
@@ -239,7 +244,7 @@ public class FragmentMessage extends FragmentEx {
|
||||
|
||||
} else if (prefs.getBoolean("webview", false)) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("link", url);
|
||||
args.putString("url", url);
|
||||
|
||||
FragmentWebView fragment = new FragmentWebView();
|
||||
fragment.setArguments(args);
|
||||
@@ -273,14 +278,12 @@ public class FragmentMessage extends FragmentEx {
|
||||
vSeparatorBody.setVisibility(View.GONE);
|
||||
fab.setVisibility(View.GONE);
|
||||
|
||||
tvCount.setVisibility(View.GONE);
|
||||
grpThread.setVisibility(View.GONE);
|
||||
grpAddresses.setVisibility(View.GONE);
|
||||
pbRawHeaders.setVisibility(View.GONE);
|
||||
grpRawHeaders.setVisibility(View.GONE);
|
||||
grpAttachments.setVisibility(View.GONE);
|
||||
grpError.setVisibility(View.GONE);
|
||||
|
||||
tvCount.setTag(tvCount.getVisibility());
|
||||
tvCc.setTag(grpAddresses.getVisibility());
|
||||
tvError.setTag(grpError.getVisibility());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -297,10 +300,11 @@ public class FragmentMessage extends FragmentEx {
|
||||
|
||||
RecyclerView.Adapter adapter = rvAttachment.getAdapter();
|
||||
|
||||
tvCount.setVisibility((int) tvCount.getTag());
|
||||
grpAddresses.setVisibility((int) tvCc.getTag());
|
||||
grpThread.setVisibility(View.VISIBLE);
|
||||
grpAddresses.setVisibility(addresses ? View.VISIBLE : View.GONE);
|
||||
pbRawHeaders.setVisibility(headers && message.headers == null ? View.VISIBLE : View.GONE);
|
||||
grpRawHeaders.setVisibility(headers ? View.VISIBLE : View.GONE);
|
||||
grpAttachments.setVisibility(adapter != null && adapter.getItemCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
grpError.setVisibility((int) tvError.getTag());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -315,7 +319,7 @@ public class FragmentMessage extends FragmentEx {
|
||||
case R.id.action_spam:
|
||||
onActionSpam();
|
||||
return true;
|
||||
case R.id.action_trash:
|
||||
case R.id.action_delete:
|
||||
onActionDelete();
|
||||
return true;
|
||||
case R.id.action_move:
|
||||
@@ -335,21 +339,22 @@ public class FragmentMessage extends FragmentEx {
|
||||
// Initialize
|
||||
grpHeader.setVisibility(View.GONE);
|
||||
grpAddresses.setVisibility(View.GONE);
|
||||
pbRawHeaders.setVisibility(View.GONE);
|
||||
grpRawHeaders.setVisibility(View.GONE);
|
||||
grpAttachments.setVisibility(View.GONE);
|
||||
btnImages.setVisibility(View.GONE);
|
||||
grpMessage.setVisibility(View.GONE);
|
||||
pbBody.setVisibility(View.GONE);
|
||||
bottom_navigation.setVisibility(View.GONE);
|
||||
tvCount.setVisibility(View.GONE);
|
||||
grpThread.setVisibility(View.GONE);
|
||||
grpError.setVisibility(View.GONE);
|
||||
fab.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
tvSize.setText(null);
|
||||
|
||||
rvAttachment.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvAttachment.setLayoutManager(llm);
|
||||
rvAttachment.setItemAnimator(null);
|
||||
|
||||
adapter = new AdapterAttachment(getContext(), getViewLifecycleOwner(), true);
|
||||
rvAttachment.setAdapter(adapter);
|
||||
@@ -357,17 +362,19 @@ public class FragmentMessage extends FragmentEx {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
adapter = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable("message", message);
|
||||
outState.putBoolean("free", free);
|
||||
if (free) {
|
||||
outState.putInt("tag_count", (int) tvCount.getTag());
|
||||
outState.putInt("tag_cc", (int) tvCc.getTag());
|
||||
outState.putInt("tag_error", (int) tvError.getTag());
|
||||
}
|
||||
outState.putString("decrypted", decrypted);
|
||||
outState.putBoolean("headers", headers);
|
||||
outState.putBoolean("addresses", addresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -377,6 +384,7 @@ public class FragmentMessage extends FragmentEx {
|
||||
if (savedInstanceState == null) {
|
||||
setSubtitle(Helper.localizeFolderName(getContext(), message.folderName));
|
||||
|
||||
ivFlagged.setImageResource(message.ui_flagged ? R.drawable.baseline_star_24 : R.drawable.baseline_star_border_24);
|
||||
tvFrom.setText(MessageHelper.getFormattedAddresses(message.from, true));
|
||||
tvTime.setText(message.sent == null ? null : df.format(new Date(message.sent)));
|
||||
tvTo.setText(MessageHelper.getFormattedAddresses(message.to, true));
|
||||
@@ -388,137 +396,22 @@ public class FragmentMessage extends FragmentEx {
|
||||
tvCc.setText(MessageHelper.getFormattedAddresses(message.cc, true));
|
||||
tvBcc.setText(MessageHelper.getFormattedAddresses(message.bcc, true));
|
||||
|
||||
tvRawHeaders.setText(message.headers);
|
||||
|
||||
tvError.setText(message.error);
|
||||
} else {
|
||||
free = savedInstanceState.getBoolean("free");
|
||||
if (free) {
|
||||
tvCount.setTag(savedInstanceState.getInt("tag_count"));
|
||||
tvCc.setTag(savedInstanceState.getInt("tag_cc"));
|
||||
rvAttachment.setTag(savedInstanceState.getInt("tag_attachment"));
|
||||
tvError.setTag(savedInstanceState.getInt("tag_error"));
|
||||
}
|
||||
decrypted = savedInstanceState.getString("decrypted");
|
||||
headers = savedInstanceState.getBoolean("headers");
|
||||
addresses = savedInstanceState.getBoolean("addresses");
|
||||
}
|
||||
|
||||
if (tvBody.getTag() == null) {
|
||||
// Spanned text needs to be loaded after recreation too
|
||||
final Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
args.putBoolean("has_images", false);
|
||||
args.putBoolean("show_images", false);
|
||||
|
||||
pbBody.setVisibility(View.VISIBLE);
|
||||
final SimpleTask<Spanned> bodyTask = new SimpleTask<Spanned>() {
|
||||
@Override
|
||||
protected Spanned onLoad(final Context context, final Bundle args) throws Throwable {
|
||||
final long id = args.getLong("id");
|
||||
final boolean show_images = args.getBoolean("show_images");
|
||||
String body = (decrypted == null ? message.read(context) : decrypted);
|
||||
args.putInt("size", body.length());
|
||||
|
||||
return Html.fromHtml(HtmlHelper.sanitize(getContext(), body, false), new Html.ImageGetter() {
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
float scale = context.getResources().getDisplayMetrics().density;
|
||||
int px = (int) (24 * scale + 0.5f);
|
||||
|
||||
if (show_images) {
|
||||
// Get cache folder
|
||||
File dir = new File(context.getCacheDir(), "images");
|
||||
dir.mkdir();
|
||||
|
||||
// Cleanup cache
|
||||
long now = new Date().getTime();
|
||||
File[] images = dir.listFiles();
|
||||
if (images != null)
|
||||
for (File image : images)
|
||||
if (image.isFile() && image.lastModified() + CACHE_IMAGE_DURATION < now) {
|
||||
Log.i(Helper.TAG, "Deleting from image cache " + image.getName());
|
||||
image.delete();
|
||||
}
|
||||
|
||||
// Create unique file name
|
||||
File file = new File(dir, id + "_" + source.hashCode());
|
||||
|
||||
InputStream is = null;
|
||||
FileOutputStream os = null;
|
||||
try {
|
||||
// Get input stream
|
||||
if (file.exists()) {
|
||||
Log.i(Helper.TAG, "Using cached " + file);
|
||||
is = new FileInputStream(file);
|
||||
} else {
|
||||
Log.i(Helper.TAG, "Downloading " + source);
|
||||
is = new URL(source).openStream();
|
||||
}
|
||||
|
||||
// Decode image from stream
|
||||
Bitmap bm = BitmapFactory.decodeStream(is);
|
||||
if (bm == null)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
// Cache bitmap
|
||||
if (!file.exists()) {
|
||||
os = new FileOutputStream(file);
|
||||
bm.compress(Bitmap.CompressFormat.PNG, 100, os);
|
||||
}
|
||||
|
||||
// Create drawable from bitmap
|
||||
Drawable d = new BitmapDrawable(context.getResources(), bm);
|
||||
d.setBounds(0, 0, bm.getWidth(), bm.getHeight());
|
||||
return d;
|
||||
} catch (Throwable ex) {
|
||||
// Show warning icon
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Drawable d = context.getResources().getDrawable(R.drawable.baseline_warning_24, context.getTheme());
|
||||
d.setBounds(0, 0, px, px);
|
||||
return d;
|
||||
} finally {
|
||||
// Close streams
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Show placeholder icon
|
||||
args.putBoolean("has_images", true);
|
||||
Drawable d = context.getResources().getDrawable(R.drawable.baseline_image_24, context.getTheme());
|
||||
d.setBounds(0, 0, px, px);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}, new Html.TagHandler() {
|
||||
@Override
|
||||
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
|
||||
Log.i(Helper.TAG, "HTML tag=" + tag + " opening=" + opening);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Spanned body) {
|
||||
boolean has_images = args.getBoolean("has_images");
|
||||
boolean show_images = args.getBoolean("show_images");
|
||||
tvSize.setText(Helper.humanReadableByteCount(args.getInt("size"), false));
|
||||
tvBody.setText(body);
|
||||
tvBody.setTag(true);
|
||||
btnImages.setVisibility(has_images && !show_images ? View.VISIBLE : View.GONE);
|
||||
grpMessage.setVisibility(View.VISIBLE);
|
||||
fab.setVisibility(free ? View.GONE : View.VISIBLE);
|
||||
pbBody.setVisibility(View.GONE);
|
||||
}
|
||||
};
|
||||
|
||||
bodyTask.load(FragmentMessage.this, args);
|
||||
|
||||
@@ -539,89 +432,94 @@ public class FragmentMessage extends FragmentEx {
|
||||
grpHeader.setVisibility(free ? View.GONE : View.VISIBLE);
|
||||
vSeparatorBody.setVisibility(free ? View.GONE : View.VISIBLE);
|
||||
|
||||
if (free) {
|
||||
tvCount.setVisibility((int) tvCount.getTag());
|
||||
grpAddresses.setVisibility((int) tvCc.getTag());
|
||||
grpError.setVisibility((int) tvError.getTag());
|
||||
} else {
|
||||
tvCount.setVisibility(!free && message.count > 1 ? View.VISIBLE : View.GONE);
|
||||
grpError.setVisibility(free || message.error == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
grpAddresses.setVisibility(!free && addresses ? View.VISIBLE : View.GONE);
|
||||
grpThread.setVisibility(free ? View.GONE : View.VISIBLE);
|
||||
pbRawHeaders.setVisibility(!free && headers && message.headers == null ? View.VISIBLE : View.GONE);
|
||||
grpRawHeaders.setVisibility(free || !headers ? View.GONE : View.VISIBLE);
|
||||
grpError.setVisibility(message.error == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
final DB db = DB.getInstance(getContext());
|
||||
|
||||
if (!message.virtual) {
|
||||
// Observe message
|
||||
db.message().liveMessage(message.id).observe(getViewLifecycleOwner(), new Observer<TupleMessageEx>() {
|
||||
// Observe message
|
||||
db.message().liveMessage(message.id).observe(getViewLifecycleOwner(), new Observer<TupleMessageEx>() {
|
||||
|
||||
@Override
|
||||
public void onChanged(@Nullable final TupleMessageEx message) {
|
||||
if (message == null || (!(debug && BuildConfig.DEBUG) && message.ui_hide)) {
|
||||
// Message gone (moved, deleted)
|
||||
finish();
|
||||
return;
|
||||
@Override
|
||||
public void onChanged(@Nullable final TupleMessageEx message) {
|
||||
if (message == null || (!(debug && BuildConfig.DEBUG) && message.ui_hide)) {
|
||||
// Message gone (moved, deleted)
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Messages are immutable except for flags
|
||||
FragmentMessage.this.message = message;
|
||||
setSeen();
|
||||
ivFlagged.setImageResource(message.ui_flagged ? R.drawable.baseline_star_24 : R.drawable.baseline_star_border_24);
|
||||
|
||||
// Headers can be downloaded
|
||||
tvRawHeaders.setText(message.headers);
|
||||
pbRawHeaders.setVisibility(!free && headers && message.headers == null ? View.VISIBLE : View.GONE);
|
||||
|
||||
// Message count can be changed
|
||||
getActivity().invalidateOptionsMenu();
|
||||
|
||||
// Messages can be moved to another folder
|
||||
setSubtitle(Helper.localizeFolderName(getContext(), message.folderName));
|
||||
|
||||
// Observe folders
|
||||
db.folder().liveFolders(message.account).removeObservers(getViewLifecycleOwner());
|
||||
db.folder().liveFolders(message.account).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<TupleFolderEx> folders) {
|
||||
boolean hasTrash = false;
|
||||
boolean hasJunk = false;
|
||||
boolean hasArchive = false;
|
||||
boolean hasUser = false;
|
||||
|
||||
if (folders != null)
|
||||
for (EntityFolder folder : folders) {
|
||||
if (EntityFolder.TRASH.equals(folder.type))
|
||||
hasTrash = true;
|
||||
else if (EntityFolder.JUNK.equals(folder.type))
|
||||
hasJunk = true;
|
||||
else if (EntityFolder.ARCHIVE.equals(folder.type))
|
||||
hasArchive = true;
|
||||
else if (EntityFolder.USER.equals(folder.type))
|
||||
hasUser = true;
|
||||
}
|
||||
|
||||
boolean inInbox = EntityFolder.INBOX.equals(message.folderType);
|
||||
boolean inOutbox = EntityFolder.OUTBOX.equals(message.folderType);
|
||||
boolean inArchive = EntityFolder.ARCHIVE.equals(message.folderType);
|
||||
boolean inTrash = EntityFolder.TRASH.equals(message.folderType);
|
||||
boolean inJunk = EntityFolder.JUNK.equals(message.folderType);
|
||||
|
||||
bottom_navigation.setTag(inTrash || !hasTrash || inOutbox);
|
||||
|
||||
bottom_navigation.getMenu().findItem(R.id.action_spam).setVisible(message.uid != null && !inArchive && !inJunk && hasJunk);
|
||||
bottom_navigation.getMenu().findItem(R.id.action_delete).setVisible((message.uid != null && hasTrash) || (inOutbox && !TextUtils.isEmpty(message.error)));
|
||||
bottom_navigation.getMenu().findItem(R.id.action_move).setVisible(message.uid != null && (!inInbox || hasUser));
|
||||
bottom_navigation.getMenu().findItem(R.id.action_archive).setVisible(message.uid != null && !inArchive && hasArchive);
|
||||
bottom_navigation.getMenu().findItem(R.id.action_reply).setVisible(!inOutbox);
|
||||
bottom_navigation.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Messages are immutable except for flags
|
||||
FragmentMessage.this.message.seen = message.seen;
|
||||
FragmentMessage.this.message.ui_seen = message.ui_seen;
|
||||
setSeen();
|
||||
}
|
||||
});
|
||||
// Observe attachments
|
||||
db.attachment().liveAttachments(message.id).observe(getViewLifecycleOwner(),
|
||||
new Observer<List<EntityAttachment>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAttachment> attachments) {
|
||||
if (attachments == null)
|
||||
attachments = new ArrayList<>();
|
||||
|
||||
// Observe attachments
|
||||
db.attachment().liveAttachments(message.id).observe(getViewLifecycleOwner(),
|
||||
new Observer<List<EntityAttachment>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAttachment> attachments) {
|
||||
if (attachments == null)
|
||||
attachments = new ArrayList<>();
|
||||
adapter.set(attachments);
|
||||
grpAttachments.setVisibility(!free && attachments.size() > 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
adapter.set(attachments);
|
||||
grpAttachments.setVisibility(!free && attachments.size() > 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
// Observe folders
|
||||
db.folder().liveFolders(message.account).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<TupleFolderEx> folders) {
|
||||
if (folders == null)
|
||||
folders = new ArrayList<>();
|
||||
|
||||
boolean inInbox = EntityFolder.INBOX.equals(message.folderType);
|
||||
boolean inOutbox = EntityFolder.OUTBOX.equals(message.folderType);
|
||||
boolean inArchive = EntityFolder.ARCHIVE.equals(message.folderType);
|
||||
boolean inTrash = EntityFolder.TRASH.equals(message.folderType);
|
||||
boolean inJunk = EntityFolder.JUNK.equals(message.folderType);
|
||||
|
||||
boolean hasTrash = false;
|
||||
boolean hasJunk = false;
|
||||
boolean hasArchive = false;
|
||||
boolean hasUser = false;
|
||||
if (folders != null)
|
||||
for (EntityFolder folder : folders) {
|
||||
if (EntityFolder.TRASH.equals(folder.type))
|
||||
hasTrash = true;
|
||||
else if (EntityFolder.JUNK.equals(folder.type))
|
||||
hasJunk = true;
|
||||
else if (EntityFolder.ARCHIVE.equals(folder.type))
|
||||
hasArchive = true;
|
||||
else if (EntityFolder.USER.equals(folder.type))
|
||||
hasUser = true;
|
||||
}
|
||||
|
||||
bottom_navigation.setTag(inTrash || !hasTrash || inOutbox);
|
||||
|
||||
bottom_navigation.getMenu().findItem(R.id.action_spam).setVisible(message.uid != null && !inArchive && !inJunk && hasJunk);
|
||||
bottom_navigation.getMenu().findItem(R.id.action_trash).setVisible((message.uid != null && hasTrash) || (inOutbox && !TextUtils.isEmpty(message.error)));
|
||||
bottom_navigation.getMenu().findItem(R.id.action_move).setVisible(message.uid != null && (!inInbox || hasUser));
|
||||
bottom_navigation.getMenu().findItem(R.id.action_archive).setVisible(message.uid != null && !inArchive && hasArchive);
|
||||
bottom_navigation.getMenu().findItem(R.id.action_reply).setVisible(!inOutbox);
|
||||
bottom_navigation.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setSeen() {
|
||||
@@ -650,17 +548,12 @@ public class FragmentMessage extends FragmentEx {
|
||||
boolean inOutbox = EntityFolder.OUTBOX.equals(message.folderType);
|
||||
|
||||
menu.findItem(R.id.menu_addresses).setVisible(!free);
|
||||
menu.findItem(R.id.menu_thread).setVisible(!free && !message.virtual && message.count > 1);
|
||||
menu.findItem(R.id.menu_seen).setVisible(!free && !message.virtual && !inOutbox);
|
||||
menu.findItem(R.id.menu_forward).setVisible(!free && !message.virtual && !inOutbox);
|
||||
menu.findItem(R.id.menu_reply_all).setVisible(!free && !message.virtual && message.cc != null && !inOutbox);
|
||||
menu.findItem(R.id.menu_decrypt).setVisible(decrypted == null);
|
||||
|
||||
MenuItem menuSeen = menu.findItem(R.id.menu_seen);
|
||||
menuSeen.setIcon(message.ui_seen
|
||||
? R.drawable.baseline_visibility_off_24
|
||||
: R.drawable.baseline_visibility_24);
|
||||
menuSeen.setTitle(message.ui_seen ? R.string.title_unseen : R.string.title_seen);
|
||||
menu.findItem(R.id.menu_thread).setVisible(message.count > 1);
|
||||
menu.findItem(R.id.menu_forward).setVisible(!inOutbox);
|
||||
menu.findItem(R.id.menu_show_headers).setChecked(headers);
|
||||
menu.findItem(R.id.menu_show_headers).setEnabled(message.uid != null);
|
||||
menu.findItem(R.id.menu_show_headers).setVisible(!free);
|
||||
menu.findItem(R.id.menu_reply_all).setVisible(!inOutbox);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -672,28 +565,29 @@ public class FragmentMessage extends FragmentEx {
|
||||
case R.id.menu_thread:
|
||||
onMenuThread();
|
||||
return true;
|
||||
case R.id.menu_seen:
|
||||
onMenuSeen();
|
||||
return true;
|
||||
case R.id.menu_forward:
|
||||
onMenuForward();
|
||||
return true;
|
||||
case R.id.menu_reply_all:
|
||||
onMenuReplyAll();
|
||||
return true;
|
||||
case R.id.menu_show_html:
|
||||
onMenuShowHtml();
|
||||
return true;
|
||||
case R.id.menu_show_headers:
|
||||
onMenuShowHeaders();
|
||||
return true;
|
||||
case R.id.menu_answer:
|
||||
onMenuAnswer();
|
||||
return true;
|
||||
case R.id.menu_decrypt:
|
||||
onMenuDecrypt();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMenuAddresses() {
|
||||
grpAddresses.setVisibility(grpAddresses.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
|
||||
addresses = !addresses;
|
||||
grpAddresses.setVisibility(addresses ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void onMenuThread() {
|
||||
@@ -710,49 +604,6 @@ public class FragmentMessage extends FragmentEx {
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuSeen() {
|
||||
Helper.setViewsEnabled(view, false);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onLoad(Context context, Bundle args) {
|
||||
long id = args.getLong("id");
|
||||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
for (EntityMessage tmessage : db.message().getMessageByThread(message.account, message.thread)) {
|
||||
db.message().setMessageUiSeen(tmessage.id, !message.ui_seen);
|
||||
EntityOperation.queue(db, tmessage, EntityOperation.SEEN, !tmessage.ui_seen);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
EntityOperation.process(context);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Void data) {
|
||||
Helper.setViewsEnabled(view, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Bundle args, Throwable ex) {
|
||||
Helper.setViewsEnabled(view, true);
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}.load(this, args);
|
||||
}
|
||||
|
||||
private void onMenuForward() {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class)
|
||||
.putExtra("action", "forward")
|
||||
@@ -765,6 +616,53 @@ public class FragmentMessage extends FragmentEx {
|
||||
.putExtra("reference", message.id));
|
||||
}
|
||||
|
||||
private void onMenuShowHeaders() {
|
||||
headers = !headers;
|
||||
getActivity().invalidateOptionsMenu();
|
||||
pbRawHeaders.setVisibility(headers && message.headers == null ? View.VISIBLE : View.GONE);
|
||||
grpRawHeaders.setVisibility(headers ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (headers && message.headers == null) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onLoad(Context context, Bundle args) {
|
||||
Long id = args.getLong("id");
|
||||
DB db = DB.getInstance(context);
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
EntityOperation.queue(db, message, EntityOperation.HEADERS);
|
||||
EntityOperation.process(context);
|
||||
return null;
|
||||
}
|
||||
}.load(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMenuShowHtml() {
|
||||
new SimpleTask<String>() {
|
||||
@Override
|
||||
protected String onLoad(Context context, Bundle args) throws Throwable {
|
||||
return message.read(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle a, String html) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("html", html);
|
||||
args.putString("from", MessageHelper.getFormattedAddresses(message.from, true));
|
||||
|
||||
FragmentWebView fragment = new FragmentWebView();
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("webview");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}.load(this, new Bundle());
|
||||
}
|
||||
|
||||
private void onMenuAnswer() {
|
||||
DB.getInstance(getContext()).answer().liveAnswers().observe(getViewLifecycleOwner(), new Observer<List<EntityAnswer>>() {
|
||||
@Override
|
||||
@@ -808,100 +706,6 @@ public class FragmentMessage extends FragmentEx {
|
||||
});
|
||||
}
|
||||
|
||||
private void onMenuDecrypt() {
|
||||
Log.i(Helper.TAG, "On decrypt");
|
||||
|
||||
if (!PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("pro", false)) {
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
|
||||
fragmentTransaction.commit();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (openPgpConnection == null || !openPgpConnection.isBound())
|
||||
throw new IllegalArgumentException(getString(R.string.title_no_openpgp));
|
||||
|
||||
if (message.to == null || message.to.length == 0)
|
||||
throw new IllegalArgumentException(getString(R.string.title_to_missing));
|
||||
|
||||
// Find encrypted message
|
||||
String begin = "-----BEGIN PGP MESSAGE-----";
|
||||
String end = "-----END PGP MESSAGE-----";
|
||||
Document document = Jsoup.parse(message.read(getContext()));
|
||||
String encrypted = document.text();
|
||||
int efrom = encrypted.indexOf(begin) + begin.length();
|
||||
int eto = encrypted.indexOf(end);
|
||||
if (efrom < 0 || eto < 0)
|
||||
throw new IllegalArgumentException(getString(R.string.title_not_encrypted));
|
||||
encrypted = begin + "\n" + encrypted.substring(efrom, eto).replace(" ", "\n") + end + "\n";
|
||||
final InputStream is = new ByteArrayInputStream(encrypted.getBytes("UTF-8"));
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
InternetAddress to = (InternetAddress) message.to[0];
|
||||
|
||||
Intent data = new Intent();
|
||||
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{to.getAddress()});
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(getContext(), openPgpConnection.getService());
|
||||
api.executeApiAsync(data, is, os, new OpenPgpApi.IOpenPgpCallback() {
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
try {
|
||||
int code = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||
switch (code) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
||||
Log.i(Helper.TAG, "Decrypted");
|
||||
FragmentMessage.this.decrypted = os.toString("UTF-8");
|
||||
getActivity().invalidateOptionsMenu();
|
||||
tvBody.setText(decrypted);
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
|
||||
Log.i(Helper.TAG, "User interaction");
|
||||
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
startIntentSenderForResult(
|
||||
pi.getIntentSender(),
|
||||
ActivityView.REQUEST_OPENPGP,
|
||||
null, 0, 0, 0,
|
||||
new Bundle());
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_ERROR: {
|
||||
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
||||
throw new IllegalArgumentException(error.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
Log.i(Helper.TAG, "Message onActivityResult request=" + requestCode + " result=" + resultCode + " data=" + data);
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (requestCode == ActivityView.REQUEST_OPENPGP) {
|
||||
Log.i(Helper.TAG, "User interacted");
|
||||
onMenuDecrypt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onActionSpam() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder
|
||||
@@ -1221,4 +1025,123 @@ public class FragmentMessage extends FragmentEx {
|
||||
.putExtra("action", "reply")
|
||||
.putExtra("reference", message.id));
|
||||
}
|
||||
|
||||
private SimpleTask<Spanned> bodyTask = new SimpleTask<Spanned>() {
|
||||
@Override
|
||||
protected Spanned onLoad(final Context context, final Bundle args) throws Throwable {
|
||||
final long id = args.getLong("id");
|
||||
final boolean show_images = args.getBoolean("show_images");
|
||||
String body = message.read(context);
|
||||
args.putInt("size", body.length());
|
||||
return decodeHtml(context, id, body, show_images);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Spanned body) {
|
||||
boolean show_images = args.getBoolean("show_images");
|
||||
|
||||
SpannedString ss = new SpannedString(body);
|
||||
boolean has_images = (ss.getSpans(0, ss.length(), ImageSpan.class).length > 0);
|
||||
|
||||
tvBody.setText(body);
|
||||
tvBody.setTag(true);
|
||||
btnImages.setVisibility(has_images && !show_images ? View.VISIBLE : View.GONE);
|
||||
grpMessage.setVisibility(View.VISIBLE);
|
||||
fab.setVisibility(free ? View.GONE : View.VISIBLE);
|
||||
pbBody.setVisibility(View.GONE);
|
||||
}
|
||||
};
|
||||
|
||||
private static Spanned decodeHtml(final Context context, final long id, String body, final boolean show_images) {
|
||||
return Html.fromHtml(HtmlHelper.sanitize(context, body, false), new Html.ImageGetter() {
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
float scale = context.getResources().getDisplayMetrics().density;
|
||||
int px = (int) (24 * scale + 0.5f);
|
||||
|
||||
if (show_images) {
|
||||
// Get cache folder
|
||||
File dir = new File(context.getCacheDir(), "images");
|
||||
dir.mkdir();
|
||||
|
||||
// Cleanup cache
|
||||
long now = new Date().getTime();
|
||||
File[] images = dir.listFiles();
|
||||
if (images != null)
|
||||
for (File image : images)
|
||||
if (image.isFile() && image.lastModified() + CACHE_IMAGE_DURATION < now) {
|
||||
Log.i(Helper.TAG, "Deleting from image cache " + image.getName());
|
||||
image.delete();
|
||||
}
|
||||
|
||||
InputStream is = null;
|
||||
FileOutputStream os = null;
|
||||
try {
|
||||
if (source == null)
|
||||
throw new IllegalArgumentException("Html.ImageGetter.getDrawable(source == null)");
|
||||
|
||||
// Create unique file name
|
||||
File file = new File(dir, id + "_" + source.hashCode());
|
||||
|
||||
// Get input stream
|
||||
if (file.exists()) {
|
||||
Log.i(Helper.TAG, "Using cached " + file);
|
||||
is = new FileInputStream(file);
|
||||
} else {
|
||||
Log.i(Helper.TAG, "Downloading " + source);
|
||||
is = new URL(source).openStream();
|
||||
}
|
||||
|
||||
// Decode image from stream
|
||||
Bitmap bm = BitmapFactory.decodeStream(is);
|
||||
if (bm == null)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
// Cache bitmap
|
||||
if (!file.exists()) {
|
||||
os = new FileOutputStream(file);
|
||||
bm.compress(Bitmap.CompressFormat.PNG, 100, os);
|
||||
}
|
||||
|
||||
// Create drawable from bitmap
|
||||
Drawable d = new BitmapDrawable(context.getResources(), bm);
|
||||
d.setBounds(0, 0, bm.getWidth(), bm.getHeight());
|
||||
return d;
|
||||
} catch (Throwable ex) {
|
||||
// Show warning icon
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Drawable d = context.getResources().getDrawable(R.drawable.baseline_warning_24, context.getTheme());
|
||||
d.setBounds(0, 0, px, px);
|
||||
return d;
|
||||
} finally {
|
||||
// Close streams
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Show placeholder icon
|
||||
Drawable d = context.getResources().getDrawable(R.drawable.baseline_image_24, context.getTheme());
|
||||
d.setBounds(0, 0, px, px);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}, new Html.TagHandler() {
|
||||
@Override
|
||||
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
|
||||
Log.i(Helper.TAG, "HTML tag=" + tag + " opening=" + opening);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ package eu.faircode.email;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
@@ -40,6 +42,7 @@ import android.widget.Toast;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -49,7 +52,6 @@ import androidx.constraintlayout.widget.Group;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.paging.DataSource;
|
||||
import androidx.paging.LivePagedListBuilder;
|
||||
import androidx.paging.PagedList;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
@@ -58,7 +60,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class FragmentMessages extends FragmentEx {
|
||||
private ViewGroup view;
|
||||
private Button btnHintSwipe;
|
||||
private Button btnHintActions;
|
||||
private RecyclerView rvMessage;
|
||||
private TextView tvNoEmail;
|
||||
private ProgressBar pbWait;
|
||||
@@ -70,14 +72,17 @@ public class FragmentMessages extends FragmentEx {
|
||||
private long thread = -1;
|
||||
private String search = null;
|
||||
|
||||
private SearchDataSource sds = null;
|
||||
|
||||
private long primary = -1;
|
||||
private AdapterMessage adapter;
|
||||
|
||||
private SearchState searchState = SearchState.Reset;
|
||||
private BoundaryCallbackMessages searchCallback = null;
|
||||
|
||||
private static final int MESSAGES_PAGE_SIZE = 50;
|
||||
private static final int SEARCH_PAGE_SIZE = 10;
|
||||
|
||||
private enum SearchState {Reset, Database, Boundary}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -99,7 +104,7 @@ public class FragmentMessages extends FragmentEx {
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
// Get controls
|
||||
btnHintSwipe = view.findViewById(R.id.btnHintSwipe);
|
||||
btnHintActions = view.findViewById(R.id.btnHintActions);
|
||||
rvMessage = view.findViewById(R.id.rvFolder);
|
||||
tvNoEmail = view.findViewById(R.id.tvNoEmail);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
@@ -110,10 +115,10 @@ public class FragmentMessages extends FragmentEx {
|
||||
// Wire controls
|
||||
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
btnHintSwipe.setOnClickListener(new View.OnClickListener() {
|
||||
btnHintActions.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
prefs.edit().putBoolean("understood_swipe", true).apply();
|
||||
prefs.edit().putBoolean("understood_actions", true).apply();
|
||||
grpHintSwipe.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
@@ -156,65 +161,101 @@ public class FragmentMessages extends FragmentEx {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||
int pos = viewHolder.getAdapterPosition();
|
||||
if (pos == RecyclerView.NO_POSITION)
|
||||
return;
|
||||
|
||||
TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
|
||||
boolean inbox = (EntityFolder.ARCHIVE.equals(message.folderType) || EntityFolder.TRASH.equals(message.folderType));
|
||||
|
||||
View itemView = viewHolder.itemView;
|
||||
int margin = Math.round(12 * (getResources().getDisplayMetrics().density));
|
||||
|
||||
if (dX > margin) {
|
||||
// Right swipe
|
||||
Drawable d = getResources().getDrawable(inbox ? R.drawable.baseline_inbox_24 : R.drawable.baseline_archive_24, getContext().getTheme());
|
||||
d.setBounds(
|
||||
itemView.getLeft() + margin,
|
||||
itemView.getTop() + d.getIntrinsicHeight() / 2,
|
||||
itemView.getLeft() + margin + d.getIntrinsicWidth(),
|
||||
itemView.getTop() + (itemView.getHeight() - d.getIntrinsicHeight() / 2));
|
||||
d.draw(canvas);
|
||||
} else if (dX < -margin) {
|
||||
// Left swipe
|
||||
Drawable d = getResources().getDrawable(inbox ? R.drawable.baseline_inbox_24 : R.drawable.baseline_delete_24, getContext().getTheme());
|
||||
d.setBounds(
|
||||
itemView.getLeft() + itemView.getWidth() - d.getIntrinsicWidth() - margin,
|
||||
itemView.getTop() + d.getIntrinsicHeight() / 2,
|
||||
itemView.getLeft() + itemView.getWidth() - margin,
|
||||
itemView.getTop() + (itemView.getHeight() - d.getIntrinsicHeight() / 2));
|
||||
d.draw(canvas);
|
||||
}
|
||||
|
||||
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
int pos = viewHolder.getAdapterPosition();
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
|
||||
Log.i(Helper.TAG, "Swiped dir=" + direction + " message=" + message.id);
|
||||
if (pos == RecyclerView.NO_POSITION)
|
||||
return;
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
args.putInt("direction", direction);
|
||||
new SimpleTask<String>() {
|
||||
@Override
|
||||
protected String onLoad(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
int direction = args.getInt("direction");
|
||||
EntityFolder target = null;
|
||||
TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
|
||||
Log.i(Helper.TAG, "Swiped dir=" + direction + " message=" + message.id);
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
EntityFolder folder = db.folder().getFolder(message.folder);
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
args.putInt("direction", direction);
|
||||
new SimpleTask<String>() {
|
||||
@Override
|
||||
protected String onLoad(Context context, Bundle args) {
|
||||
long id = args.getLong("id");
|
||||
int direction = args.getInt("direction");
|
||||
EntityFolder target = null;
|
||||
|
||||
if (EntityFolder.ARCHIVE.equals(folder.type) || EntityFolder.TRASH.equals(folder.type))
|
||||
target = db.folder().getFolderByType(message.account, EntityFolder.INBOX);
|
||||
else {
|
||||
if (direction == ItemTouchHelper.RIGHT)
|
||||
target = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
|
||||
if (direction == ItemTouchHelper.LEFT || target == null)
|
||||
target = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
|
||||
}
|
||||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
EntityFolder folder = db.folder().getFolder(message.folder);
|
||||
|
||||
db.message().setMessageUiHide(message.id, true);
|
||||
EntityOperation.queue(db, message, EntityOperation.MOVE, target.id);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
if (EntityFolder.ARCHIVE.equals(folder.type) || EntityFolder.TRASH.equals(folder.type))
|
||||
target = db.folder().getFolderByType(message.account, EntityFolder.INBOX);
|
||||
else {
|
||||
if (direction == ItemTouchHelper.RIGHT)
|
||||
target = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
|
||||
if (direction == ItemTouchHelper.LEFT || target == null)
|
||||
target = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
|
||||
}
|
||||
|
||||
EntityOperation.process(context);
|
||||
db.message().setMessageUiHide(message.id, true);
|
||||
EntityOperation.queue(db, message, EntityOperation.MOVE, target.id);
|
||||
|
||||
return target.name;
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, String folder) {
|
||||
Snackbar.make(
|
||||
view,
|
||||
getString(R.string.title_moving, Helper.localizeFolderName(getContext(), folder)),
|
||||
Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
EntityOperation.process(context);
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}.load(FragmentMessages.this, args);
|
||||
}
|
||||
return target.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, String folder) {
|
||||
Snackbar.make(
|
||||
view,
|
||||
getString(R.string.title_moving, Helper.localizeFolderName(getContext(), folder)),
|
||||
Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}.load(FragmentMessages.this, args);
|
||||
}
|
||||
}).attachToRecyclerView(rvMessage);
|
||||
|
||||
@@ -243,7 +284,7 @@ public class FragmentMessages extends FragmentEx {
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
|
||||
grpHintSwipe.setVisibility(prefs.getBoolean("understood_swipe", false) ? View.GONE : View.VISIBLE);
|
||||
grpHintSwipe.setVisibility(prefs.getBoolean("understood_actions", false) ? View.GONE : View.VISIBLE);
|
||||
|
||||
final DB db = DB.getInstance(getContext());
|
||||
|
||||
@@ -294,61 +335,131 @@ public class FragmentMessages extends FragmentEx {
|
||||
}
|
||||
});
|
||||
|
||||
messages = new LivePagedListBuilder<>(db.message().pagedFolder(folder, debug), MESSAGES_PAGE_SIZE).build();
|
||||
messages = new LivePagedListBuilder<>(db.message().pagedFolder(folder, false, debug), MESSAGES_PAGE_SIZE).build();
|
||||
}
|
||||
else {
|
||||
setSubtitle(R.string.title_folder_thread);
|
||||
messages = new LivePagedListBuilder<>(db.message().pagedThread(thread, debug), MESSAGES_PAGE_SIZE).build();
|
||||
}
|
||||
|
||||
messages.observe(getViewLifecycleOwner(), new Observer<PagedList<TupleMessageEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable PagedList<TupleMessageEx> messages) {
|
||||
if (messages == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(Helper.TAG, "Submit messages=" + messages.size());
|
||||
adapter.submitList(messages);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
if (messages.size() == 0) {
|
||||
tvNoEmail.setVisibility(View.VISIBLE);
|
||||
rvMessage.setVisibility(View.GONE);
|
||||
} else {
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
rvMessage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.i(Helper.TAG, "Search state=" + searchState);
|
||||
setSubtitle(getString(R.string.title_searching, search));
|
||||
|
||||
// Searching is expensive:
|
||||
// - reuse existing data source
|
||||
// - use fragment lifecycle (instead of getViewLifecycleOwner)
|
||||
// - saving state is not feasible
|
||||
if (sds == null)
|
||||
sds = new SearchDataSource(getContext(), this, folder, search);
|
||||
if (searchCallback == null)
|
||||
searchCallback = new BoundaryCallbackMessages(
|
||||
getContext(), FragmentMessages.this,
|
||||
folder, search,
|
||||
new BoundaryCallbackMessages.IBoundaryCallbackMessages() {
|
||||
@Override
|
||||
public void onLoading() {
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
messages = new LivePagedListBuilder<>(
|
||||
new DataSource.Factory<Integer, TupleMessageEx>() {
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Context context, Throwable ex) {
|
||||
Toast.makeText(context, ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("folder", folder);
|
||||
args.putString("search", search);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onLoad(Context context, Bundle args) {
|
||||
if (searchState == SearchState.Reset) {
|
||||
long folder = args.getLong("folder");
|
||||
DB.getInstance(context).message().resetFound(folder);
|
||||
searchState = SearchState.Database;
|
||||
Log.i(Helper.TAG, "Search reset done");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(final Bundle args, Void data) {
|
||||
LivePagedListBuilder<Integer, TupleMessageEx> builder = new LivePagedListBuilder<>(db.message().pagedFolder(folder, true, false), SEARCH_PAGE_SIZE);
|
||||
builder.setBoundaryCallback(searchCallback);
|
||||
LiveData<PagedList<TupleMessageEx>> messages = builder.build();
|
||||
messages.observe(getViewLifecycleOwner(), new Observer<PagedList<TupleMessageEx>>() {
|
||||
@Override
|
||||
public DataSource<Integer, TupleMessageEx> create() {
|
||||
return sds;
|
||||
public void onChanged(PagedList<TupleMessageEx> messages) {
|
||||
Log.i(Helper.TAG, "Submit found messages=" + messages.size());
|
||||
adapter.submitList(messages);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
},
|
||||
new PagedList.Config.Builder()
|
||||
.setEnablePlaceholders(true)
|
||||
.setInitialLoadSizeHint(SEARCH_PAGE_SIZE)
|
||||
.setPageSize(SEARCH_PAGE_SIZE)
|
||||
.build()
|
||||
).build();
|
||||
});
|
||||
|
||||
new SimpleTask<Long>() {
|
||||
@Override
|
||||
protected Long onLoad(Context context, Bundle args) throws Throwable {
|
||||
long last = 0;
|
||||
if (searchState == SearchState.Database) {
|
||||
last = new Date().getTime();
|
||||
long folder = args.getLong("folder");
|
||||
String search = args.getString("search").toLowerCase();
|
||||
DB db = DB.getInstance(context);
|
||||
for (long id : db.message().getMessageIDs(folder)) {
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message != null) { // Message could be removed in the meantime
|
||||
String from = MessageHelper.getFormattedAddresses(message.from, true);
|
||||
if (from.toLowerCase().contains(search) ||
|
||||
message.subject.toLowerCase().contains(search) ||
|
||||
message.read(context).toLowerCase().contains(search)) {
|
||||
Log.i(Helper.TAG, "Search found id=" + id);
|
||||
db.message().setMessageFound(message.id, true);
|
||||
last = message.received;
|
||||
}
|
||||
}
|
||||
}
|
||||
searchState = SearchState.Boundary;
|
||||
Log.i(Helper.TAG, "Search database done");
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Long last) {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
searchCallback.setEnabled(true);
|
||||
if (last > 0)
|
||||
searchCallback.load(last);
|
||||
}
|
||||
}.load(FragmentMessages.this, args);
|
||||
}
|
||||
}.load(this, args);
|
||||
}
|
||||
|
||||
messages.observe(getViewLifecycleOwner(), new Observer<PagedList<TupleMessageEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable PagedList<TupleMessageEx> messages) {
|
||||
if (messages == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(Helper.TAG, "Submit messages=" + messages.size());
|
||||
adapter.submitList(messages);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
if (messages.size() == 0) {
|
||||
tvNoEmail.setVisibility(View.VISIBLE);
|
||||
rvMessage.setVisibility(View.GONE);
|
||||
} else {
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
rvMessage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("folder", folder);
|
||||
args.putLong("thread", thread);
|
||||
|
||||
@@ -20,45 +20,60 @@ package eu.faircode.email;
|
||||
*/
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
public class FragmentSetup extends FragmentEx {
|
||||
private Button btnAccount;
|
||||
private ProgressBar pbAccount;
|
||||
private TextView tvAccountDone;
|
||||
|
||||
private Button btnIdentity;
|
||||
private ProgressBar pbIdentity;
|
||||
private TextView tvIdentityDone;
|
||||
|
||||
private Button btnPermissions;
|
||||
private TextView tvPermissionsDone;
|
||||
|
||||
private CheckBox cbDarkTheme;
|
||||
private Button btnDoze;
|
||||
private TextView tvDozeDone;
|
||||
|
||||
private Button btnData;
|
||||
|
||||
private ToggleButton tbDarkTheme;
|
||||
|
||||
private Button btnOptions;
|
||||
|
||||
private Drawable check;
|
||||
|
||||
private static final String[] permissions = new String[]{
|
||||
Manifest.permission.READ_CONTACTS
|
||||
};
|
||||
@@ -68,21 +83,26 @@ public class FragmentSetup extends FragmentEx {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
setSubtitle(R.string.title_setup);
|
||||
|
||||
check = getResources().getDrawable(R.drawable.baseline_check_24, getContext().getTheme());
|
||||
|
||||
View view = inflater.inflate(R.layout.fragment_setup, container, false);
|
||||
|
||||
// Get controls
|
||||
btnAccount = view.findViewById(R.id.btnAccount);
|
||||
pbAccount = view.findViewById(R.id.pbAccount);
|
||||
tvAccountDone = view.findViewById(R.id.tvAccountDone);
|
||||
|
||||
btnIdentity = view.findViewById(R.id.btnIdentity);
|
||||
pbIdentity = view.findViewById(R.id.pbIdentity);
|
||||
tvIdentityDone = view.findViewById(R.id.tvIdentityDone);
|
||||
|
||||
btnPermissions = view.findViewById(R.id.btnPermissions);
|
||||
tvPermissionsDone = view.findViewById(R.id.tvPermissionsDone);
|
||||
|
||||
cbDarkTheme = view.findViewById(R.id.cbDarkTheme);
|
||||
btnDoze = view.findViewById(R.id.btnDoze);
|
||||
tvDozeDone = view.findViewById(R.id.tvDozeDone);
|
||||
|
||||
btnData = view.findViewById(R.id.btnData);
|
||||
|
||||
tbDarkTheme = view.findViewById(R.id.tbDarkTheme);
|
||||
btnOptions = view.findViewById(R.id.btnOptions);
|
||||
|
||||
// Wire controls
|
||||
@@ -113,18 +133,51 @@ public class FragmentSetup extends FragmentEx {
|
||||
}
|
||||
});
|
||||
|
||||
btnDoze.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(R.string.title_setup_doze_instructions)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try {
|
||||
startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
btnData.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
startActivity(new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS,
|
||||
Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
|
||||
String theme = prefs.getString("theme", "light");
|
||||
boolean dark = "dark".equals(theme);
|
||||
cbDarkTheme.setTag(dark);
|
||||
cbDarkTheme.setChecked(dark);
|
||||
cbDarkTheme.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
tbDarkTheme.setTag(dark);
|
||||
tbDarkTheme.setChecked(dark);
|
||||
tbDarkTheme.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton button, boolean checked) {
|
||||
if (checked != (Boolean) button.getTag()) {
|
||||
button.setTag(checked);
|
||||
cbDarkTheme.setChecked(checked);
|
||||
tbDarkTheme.setChecked(checked);
|
||||
prefs.edit().putString("theme", checked ? "dark" : "light").apply();
|
||||
}
|
||||
}
|
||||
@@ -141,11 +194,21 @@ public class FragmentSetup extends FragmentEx {
|
||||
|
||||
// Initialize
|
||||
|
||||
pbAccount.setVisibility(View.GONE);
|
||||
pbIdentity.setVisibility(View.GONE);
|
||||
tvAccountDone.setText(R.string.title_setup_to_do);
|
||||
tvIdentityDone.setText(R.string.title_setup_to_do);
|
||||
tvPermissionsDone.setText(R.string.title_setup_to_do);
|
||||
tvAccountDone.setText(null);
|
||||
tvAccountDone.setCompoundDrawables(null, null, null, null);
|
||||
|
||||
btnIdentity.setEnabled(false);
|
||||
tvIdentityDone.setText(null);
|
||||
tvIdentityDone.setCompoundDrawables(null, null, null, null);
|
||||
|
||||
tvPermissionsDone.setText(null);
|
||||
tvPermissionsDone.setCompoundDrawables(null, null, null, null);
|
||||
|
||||
btnDoze.setEnabled(false);
|
||||
tvDozeDone.setText(null);
|
||||
tvDozeDone.setCompoundDrawables(null, null, null, null);
|
||||
|
||||
btnData.setVisibility(View.GONE);
|
||||
|
||||
int[] grantResults = new int[permissions.length];
|
||||
for (int i = 0; i < permissions.length; i++)
|
||||
@@ -197,18 +260,40 @@ public class FragmentSetup extends FragmentEx {
|
||||
db.account().liveAccounts(true).observe(getViewLifecycleOwner(), new Observer<List<EntityAccount>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAccount> accounts) {
|
||||
tvAccountDone.setText(accounts != null && accounts.size() > 0 ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||
boolean done = (accounts != null && accounts.size() > 0);
|
||||
btnIdentity.setEnabled(done);
|
||||
tvAccountDone.setText(done ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||
tvAccountDone.setCompoundDrawablesWithIntrinsicBounds(done ? check : null, null, null, null);
|
||||
}
|
||||
});
|
||||
|
||||
db.identity().liveIdentities(true).observe(getViewLifecycleOwner(), new Observer<List<EntityIdentity>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityIdentity> identities) {
|
||||
tvIdentityDone.setText(identities != null && identities.size() > 0 ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||
boolean done = (identities != null && identities.size() > 0);
|
||||
tvIdentityDone.setText(done ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||
tvIdentityDone.setCompoundDrawablesWithIntrinsicBounds(done ? check : null, null, null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
PowerManager pm = getContext().getSystemService(PowerManager.class);
|
||||
boolean ignoring = pm.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID);
|
||||
btnDoze.setEnabled(!ignoring);
|
||||
tvDozeDone.setText(ignoring ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||
tvDozeDone.setCompoundDrawablesWithIntrinsicBounds(ignoring ? check : null, null, null, null);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
ConnectivityManager cm = getContext().getSystemService(ConnectivityManager.class);
|
||||
boolean saving = (cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
btnData.setVisibility(saving ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
boolean has = (grantResults.length > 0);
|
||||
@@ -220,5 +305,6 @@ public class FragmentSetup extends FragmentEx {
|
||||
|
||||
btnPermissions.setEnabled(!has);
|
||||
tvPermissionsDone.setText(has ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||
tvPermissionsDone.setCompoundDrawablesWithIntrinsicBounds(has ? check : null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ package eu.faircode.email;
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -31,34 +34,43 @@ import android.widget.ProgressBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
|
||||
// https://developer.android.com/reference/android/webkit/WebView
|
||||
|
||||
public class FragmentWebView extends FragmentEx {
|
||||
private String url = null;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_webview, container, false);
|
||||
|
||||
final ProgressBar progressBar = view.findViewById(R.id.progressbar);
|
||||
WebView webview = view.findViewById(R.id.webview);
|
||||
final WebView webview = view.findViewById(R.id.webview);
|
||||
|
||||
progressBar.setProgress(0);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
WebSettings settings = webview.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setLoadWithOverviewMode(true);
|
||||
settings.setUseWideViewPort(true);
|
||||
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
|
||||
webview.setWebViewClient(new WebViewClient() {
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
view.loadUrl(url);
|
||||
setSubtitle(url);
|
||||
return false;
|
||||
if (prefs.getBoolean("webview", false)) {
|
||||
view.loadUrl(url);
|
||||
setSubtitle(url);
|
||||
return false;
|
||||
} else {
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor(Helper.resolveColor(getContext(), R.attr.colorPrimary));
|
||||
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
customTabsIntent.launchUrl(getContext(), Uri.parse(url));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -71,9 +83,31 @@ public class FragmentWebView extends FragmentEx {
|
||||
});
|
||||
|
||||
Bundle args = getArguments();
|
||||
url = (args == null ? null : args.getString("link"));
|
||||
webview.loadUrl(url);
|
||||
setSubtitle(url);
|
||||
if (args.containsKey("url")) {
|
||||
String url = args.getString("url");
|
||||
webview.loadUrl(url);
|
||||
setSubtitle(url);
|
||||
} else if (args.containsKey("html")) {
|
||||
String html = args.getString("html");
|
||||
String from = args.getString("from");
|
||||
webview.loadDataWithBaseURL("email://", html, "text/html", "UTF-8", null);
|
||||
setSubtitle(from);
|
||||
}
|
||||
|
||||
((ActivityBase) getActivity()).addBackPressedListener(new ActivityBase.IBackPressedListener() {
|
||||
@Override
|
||||
public boolean onBackPressed() {
|
||||
boolean can = webview.canGoBack();
|
||||
if (can)
|
||||
webview.goBack();
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args.containsKey("from") && !webview.canGoBack())
|
||||
setSubtitle(args.getString("from"));
|
||||
|
||||
return can;
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
@@ -47,16 +48,32 @@ import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
|
||||
|
||||
public class Helper {
|
||||
static final String TAG = "fairemail";
|
||||
|
||||
static final int JOB_DAILY = 1001;
|
||||
|
||||
static final int AUTH_TYPE_PASSWORD = 1;
|
||||
static final int AUTH_TYPE_GMAIL = 2;
|
||||
|
||||
static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
|
||||
|
||||
static ThreadFactory backgroundThreadFactory = new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(@NonNull Runnable runnable) {
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.setPriority(THREAD_PRIORITY_BACKGROUND);
|
||||
return thread;
|
||||
}
|
||||
};
|
||||
|
||||
static int resolveColor(Context context, int attr) {
|
||||
int[] attrs = new int[]{attr};
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs);
|
||||
@@ -92,10 +109,10 @@ public class Helper {
|
||||
|
||||
static String formatThrowable(Throwable ex) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(ex.getMessage());
|
||||
sb.append(ex.getMessage() == null ? ex.getClass().getName() : ex.getMessage());
|
||||
Throwable cause = ex.getCause();
|
||||
while (cause != null) {
|
||||
sb.append(" ").append(cause.getMessage());
|
||||
sb.append(" ").append(cause.getMessage() == null ? cause.getClass().getName() : cause.getMessage());
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return sb.toString();
|
||||
@@ -118,7 +135,7 @@ public class Helper {
|
||||
String[] cmd = new String[]{"logcat",
|
||||
"-d",
|
||||
"-v", "threadtime",
|
||||
"-t", "500",
|
||||
"-t", "1000",
|
||||
TAG + ":I"};
|
||||
proc = Runtime.getRuntime().exec(cmd);
|
||||
br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
|
||||
|
||||
@@ -45,7 +45,8 @@ public class HtmlHelper implements NodeVisitor {
|
||||
private String newline;
|
||||
private List<String> refs = new ArrayList<>();
|
||||
private StringBuilder sb = new StringBuilder();
|
||||
private Pattern pattern = Pattern.compile("([http|https]+://[\\w\\S(\\.|:|/)]+)");
|
||||
|
||||
private static Pattern pattern = Pattern.compile("([http|https]+://[\\w\\S(\\.|:|/)]+)");
|
||||
|
||||
private HtmlHelper(Context context, boolean reply) {
|
||||
this.context = context;
|
||||
@@ -56,7 +57,6 @@ public class HtmlHelper implements NodeVisitor {
|
||||
String name = node.nodeName();
|
||||
if (node instanceof TextNode) {
|
||||
String text = ((TextNode) node).text();
|
||||
text = Html.escapeHtml(text);
|
||||
Matcher matcher = pattern.matcher(text);
|
||||
while (matcher.find()) {
|
||||
String ref = matcher.group();
|
||||
@@ -125,6 +125,25 @@ public class HtmlHelper implements NodeVisitor {
|
||||
Document document = Jsoup.parse(Jsoup.clean(html, Whitelist.relaxed()));
|
||||
for (Element tr : document.select("tr"))
|
||||
tr.after("<br>");
|
||||
NodeTraversor.traverse(new NodeVisitor() {
|
||||
@Override
|
||||
public void head(Node node, int depth) {
|
||||
if (node instanceof TextNode) {
|
||||
String text = ((TextNode) node).text();
|
||||
Matcher matcher = pattern.matcher(text);
|
||||
while (matcher.find()) {
|
||||
String ref = matcher.group();
|
||||
text = text.replace(ref, String.format("<a href=\"%s\">%s</a>", ref, ref));
|
||||
}
|
||||
node.before(text);
|
||||
((TextNode) node).text("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tail(Node node, int depth) {
|
||||
}
|
||||
}, document.body());
|
||||
return document.body().html();
|
||||
}
|
||||
}
|
||||
|
||||
115
app/src/main/java/eu/faircode/email/JobDaily.java
Normal file
@@ -0,0 +1,115 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
NetGuard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.job.JobService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class JobDaily extends JobService {
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
|
||||
|
||||
public static void schedule(Context context) {
|
||||
Log.i(Helper.TAG, "Scheduling daily job");
|
||||
|
||||
JobInfo.Builder job = new JobInfo.Builder(Helper.JOB_DAILY, new ComponentName(context, JobDaily.class))
|
||||
.setPeriodic(24 * 3600 * 1000L)
|
||||
.setRequiresDeviceIdle(true);
|
||||
|
||||
JobScheduler scheduler = context.getSystemService(JobScheduler.class);
|
||||
scheduler.cancel(Helper.JOB_DAILY);
|
||||
if (scheduler.schedule(job.build()) == JobScheduler.RESULT_SUCCESS)
|
||||
Log.i(Helper.TAG, "Scheduled daily job");
|
||||
else
|
||||
Log.e(Helper.TAG, "Failed to schedule daily job");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters args) {
|
||||
Log.i(Helper.TAG, "Starting daily job");
|
||||
EntityLog.log(this, "Daily cleanup");
|
||||
|
||||
final DB db = DB.getInstance(this);
|
||||
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(Helper.TAG, "Start daily job");
|
||||
|
||||
// Cleanup message files
|
||||
Log.i(Helper.TAG, "Cleanup message files");
|
||||
File[] messages = new File(getFilesDir(), "messages").listFiles();
|
||||
if (messages != null)
|
||||
for (File file : messages)
|
||||
if (file.isFile()) {
|
||||
long id = Long.parseLong(file.getName());
|
||||
if (db.message().countMessage(id) == 0) {
|
||||
Log.i(Helper.TAG, "Cleanup message id=" + id);
|
||||
if (!file.delete())
|
||||
Log.w(Helper.TAG, "Error deleting " + file);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup attachment files
|
||||
Log.i(Helper.TAG, "Cleanup attachment files");
|
||||
File[] attachments = new File(getFilesDir(), "attachments").listFiles();
|
||||
if (attachments != null)
|
||||
for (File file : attachments)
|
||||
if (file.isFile()) {
|
||||
long id = Long.parseLong(file.getName());
|
||||
if (db.attachment().countAttachment(id) == 0) {
|
||||
Log.i(Helper.TAG, "Cleanup attachment id=" + id);
|
||||
if (!file.delete())
|
||||
Log.w(Helper.TAG, "Error deleting " + file);
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(Helper.TAG, "Cleanup log");
|
||||
long before = new Date().getTime() - 24 * 3600 * 1000L;
|
||||
int logs = db.log().deleteLogs(before);
|
||||
Log.i(Helper.TAG, "Deleted logs=" + logs);
|
||||
|
||||
// Cleanup found messages
|
||||
Log.i(Helper.TAG, "Cleanup found messages");
|
||||
int found = db.message().deleteFoundMessages();
|
||||
Log.i(Helper.TAG, "Deleted found messages=" + found);
|
||||
|
||||
Log.i(Helper.TAG, "End daily job");
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters args) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -54,6 +55,7 @@ import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import javax.mail.internet.ParseException;
|
||||
|
||||
public class MessageHelper {
|
||||
private MimeMessage imessage;
|
||||
@@ -212,6 +214,10 @@ public class MessageHelper {
|
||||
return imessage.isSet(Flags.Flag.SEEN);
|
||||
}
|
||||
|
||||
boolean getFlagged() throws MessagingException {
|
||||
return imessage.isSet(Flags.Flag.FLAGGED);
|
||||
}
|
||||
|
||||
String getMessageID() throws MessagingException {
|
||||
return imessage.getHeader("Message-ID", null);
|
||||
}
|
||||
@@ -268,10 +274,13 @@ public class MessageHelper {
|
||||
String personal = a.getPersonal();
|
||||
if (TextUtils.isEmpty(personal))
|
||||
formatted.add(address.toString());
|
||||
else if (full)
|
||||
formatted.add(personal + " <" + a.getAddress() + ">");
|
||||
else
|
||||
formatted.add(personal);
|
||||
else {
|
||||
personal = personal.replaceAll("[\\,\\<\\>]", "");
|
||||
if (full)
|
||||
formatted.add(personal + " <" + a.getAddress() + ">");
|
||||
else
|
||||
formatted.add(personal);
|
||||
}
|
||||
} else
|
||||
formatted.add(address.toString());
|
||||
return TextUtils.join(", ", formatted);
|
||||
@@ -281,7 +290,7 @@ public class MessageHelper {
|
||||
return getHtml(imessage);
|
||||
}
|
||||
|
||||
private String getHtml(Part part) throws MessagingException, IOException {
|
||||
private static String getHtml(Part part) throws MessagingException, IOException {
|
||||
if (part.isMimeType("text/*")) {
|
||||
String s;
|
||||
try {
|
||||
@@ -296,6 +305,10 @@ public class MessageHelper {
|
||||
for (int len = is.read(buffer); len != -1; len = is.read(buffer))
|
||||
os.write(buffer, 0, len);
|
||||
s = new String(os.toByteArray(), "US-ASCII");
|
||||
} catch (IOException ex) {
|
||||
// IOException; Unknown encoding: none
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
s = ex.toString();
|
||||
}
|
||||
|
||||
if (part.isMimeType("text/plain"))
|
||||
@@ -319,15 +332,15 @@ public class MessageHelper {
|
||||
} else
|
||||
return getHtml(bp);
|
||||
}
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
} catch (ParseException ex) {
|
||||
// ParseException: In parameter list boundary="...">, expected parameter name, got ";"
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
text = ex.toString();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
if (part.isMimeType("multipart/*")) {
|
||||
if (part.isMimeType("multipart/*"))
|
||||
try {
|
||||
Multipart mp = (Multipart) part.getContent();
|
||||
for (int i = 0; i < mp.getCount(); i++) {
|
||||
@@ -335,12 +348,10 @@ public class MessageHelper {
|
||||
if (s != null)
|
||||
return s;
|
||||
}
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
} catch (ParseException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -348,20 +359,24 @@ public class MessageHelper {
|
||||
public List<EntityAttachment> getAttachments() throws IOException, MessagingException {
|
||||
List<EntityAttachment> result = new ArrayList<>();
|
||||
|
||||
Object content = imessage.getContent();
|
||||
if (content instanceof String)
|
||||
return result;
|
||||
try {
|
||||
Object content = imessage.getContent();
|
||||
if (content instanceof String)
|
||||
return result;
|
||||
|
||||
if (content instanceof Multipart) {
|
||||
Multipart multipart = (Multipart) content;
|
||||
for (int i = 0; i < multipart.getCount(); i++)
|
||||
result.addAll(getAttachments(multipart.getBodyPart(i)));
|
||||
if (content instanceof Multipart) {
|
||||
Multipart multipart = (Multipart) content;
|
||||
for (int i = 0; i < multipart.getCount(); i++)
|
||||
result.addAll(getAttachments(multipart.getBodyPart(i)));
|
||||
}
|
||||
} catch (ParseException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<EntityAttachment> getAttachments(BodyPart part) throws
|
||||
private static List<EntityAttachment> getAttachments(BodyPart part) throws
|
||||
IOException, MessagingException {
|
||||
List<EntityAttachment> result = new ArrayList<>();
|
||||
|
||||
@@ -371,12 +386,17 @@ public class MessageHelper {
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
Log.w(Helper.TAG, "attachment content type=" + part.getContentType());
|
||||
content = part.getInputStream();
|
||||
} catch (ParseException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
content = null;
|
||||
}
|
||||
|
||||
if (content instanceof InputStream || content instanceof String) {
|
||||
String disposition;
|
||||
try {
|
||||
disposition = part.getDisposition();
|
||||
} catch (MessagingException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
disposition = null;
|
||||
}
|
||||
|
||||
@@ -384,6 +404,7 @@ public class MessageHelper {
|
||||
try {
|
||||
filename = part.getFileName();
|
||||
} catch (MessagingException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
filename = null;
|
||||
}
|
||||
|
||||
@@ -394,8 +415,23 @@ public class MessageHelper {
|
||||
attachment.type = ct.getBaseType();
|
||||
attachment.size = part.getSize();
|
||||
attachment.part = part;
|
||||
|
||||
// Try to guess a better content type
|
||||
// Sometimes PDF files are sent using the wrong type
|
||||
if ("application/octet-stream".equals(attachment.type) && attachment.name != null) {
|
||||
String extension = MimeTypeMap.getFileExtensionFromUrl(attachment.name.toLowerCase());
|
||||
if (extension != null) {
|
||||
String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (type != null) {
|
||||
Log.w(Helper.TAG, "Guessing file=" + attachment.name + " type=" + type);
|
||||
attachment.type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attachment.size < 0)
|
||||
attachment.size = null;
|
||||
|
||||
result.add(attachment);
|
||||
}
|
||||
} else if (content instanceof Multipart) {
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.FetchProfile;
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.UIDFolder;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.search.BodyTerm;
|
||||
import javax.mail.search.FromStringTerm;
|
||||
import javax.mail.search.OrTerm;
|
||||
import javax.mail.search.SubjectTerm;
|
||||
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
import androidx.paging.PositionalDataSource;
|
||||
|
||||
public class SearchDataSource extends PositionalDataSource<TupleMessageEx> implements LifecycleObserver {
|
||||
private Context context;
|
||||
private LifecycleOwner owner;
|
||||
private long fid;
|
||||
private String search;
|
||||
|
||||
private EntityFolder folder;
|
||||
private EntityAccount account;
|
||||
private IMAPStore istore = null;
|
||||
private IMAPFolder ifolder;
|
||||
private Message[] imessages;
|
||||
|
||||
private SparseArray<TupleMessageEx> cache = new SparseArray<>();
|
||||
|
||||
private static final float MAX_MEMORY_USAGE = 0.6f; // percent
|
||||
|
||||
SearchDataSource(Context context, LifecycleOwner owner, long folder, String search) {
|
||||
Log.i(Helper.TAG, "SDS create");
|
||||
|
||||
this.context = context;
|
||||
this.owner = owner;
|
||||
this.fid = folder;
|
||||
this.search = search;
|
||||
|
||||
owner.getLifecycle().addObserver(this);
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
public void onDestroyed() {
|
||||
Log.i(Helper.TAG, "SDS destroy");
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (istore != null)
|
||||
istore.close();
|
||||
} catch (MessagingException ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
} finally {
|
||||
istore = null;
|
||||
ifolder = null;
|
||||
imessages = null;
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
owner.getLifecycle().removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadInitial(LoadInitialParams params, LoadInitialCallback<TupleMessageEx> callback) {
|
||||
Log.i(Helper.TAG, "SDS load initial");
|
||||
try {
|
||||
SearchResult result = search(search, params.requestedStartPosition, params.requestedLoadSize);
|
||||
callback.onResult(result.messages, params.requestedStartPosition, result.total);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
reportError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRange(LoadRangeParams params, LoadRangeCallback<TupleMessageEx> callback) {
|
||||
Log.i(Helper.TAG, "SDS load range");
|
||||
try {
|
||||
SearchResult result = search(search, params.startPosition, params.loadSize);
|
||||
callback.onResult(result.messages);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
reportError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private SearchResult search(String term, int from, int count) throws MessagingException, IOException {
|
||||
Log.i(Helper.TAG, "SDS search from=" + from + " count=" + count);
|
||||
|
||||
if (istore == null) {
|
||||
DB db = DB.getInstance(context);
|
||||
folder = db.folder().getFolder(fid);
|
||||
account = db.account().getAccount(folder.account);
|
||||
|
||||
// Refresh token
|
||||
if (account.auth_type == Helper.AUTH_TYPE_GMAIL) {
|
||||
account.password = Helper.refreshToken(context, "com.google", account.user, account.password);
|
||||
db.account().setAccountPassword(account.id, account.password);
|
||||
}
|
||||
|
||||
Properties props = MessageHelper.getSessionProperties(context, account.auth_type);
|
||||
props.setProperty("mail.imap.throwsearchexception", "true");
|
||||
Session isession = Session.getInstance(props, null);
|
||||
|
||||
Log.i(Helper.TAG, "SDS connecting account=" + account.name);
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
|
||||
Log.i(Helper.TAG, "SDS opening folder=" + folder.name);
|
||||
ifolder = (IMAPFolder) istore.getFolder(folder.name);
|
||||
ifolder.open(Folder.READ_WRITE);
|
||||
|
||||
Log.i(Helper.TAG, "SDS searching term=" + term);
|
||||
imessages = ifolder.search(
|
||||
new OrTerm(
|
||||
new FromStringTerm(term),
|
||||
new OrTerm(
|
||||
new SubjectTerm(term),
|
||||
new BodyTerm(term))));
|
||||
Log.i(Helper.TAG, "SDS found messages=" + imessages.length);
|
||||
}
|
||||
|
||||
SearchResult result = new SearchResult();
|
||||
result.total = imessages.length;
|
||||
result.messages = new ArrayList<>();
|
||||
|
||||
List<Message> selected = new ArrayList<>();
|
||||
int base = imessages.length - 1 - from;
|
||||
for (int i = base; i >= 0 && i >= base - count + 1; i--)
|
||||
selected.add(imessages[i]);
|
||||
Log.i(Helper.TAG, "SDS selected messages=" + selected.size());
|
||||
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(UIDFolder.FetchProfileItem.UID);
|
||||
fp.add(IMAPFolder.FetchProfileItem.FLAGS);
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
fp.add(FetchProfile.Item.CONTENT_INFO);
|
||||
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
|
||||
fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
|
||||
ifolder.fetch(selected.toArray(new Message[0]), fp);
|
||||
|
||||
for (int s = 0; s < selected.size(); s++) {
|
||||
int pos = from + s;
|
||||
if (cache.get(pos) != null) {
|
||||
Log.i(Helper.TAG, "SDS from cache pos=" + pos);
|
||||
result.messages.add(cache.get(pos));
|
||||
continue;
|
||||
}
|
||||
|
||||
Message imessage = selected.get(s);
|
||||
|
||||
long uid = ifolder.getUID(imessage);
|
||||
|
||||
MessageHelper helper = new MessageHelper((MimeMessage) imessage);
|
||||
boolean seen = helper.getSeen();
|
||||
|
||||
TupleMessageEx message = new TupleMessageEx();
|
||||
message.id = uid;
|
||||
message.account = folder.account;
|
||||
message.folder = folder.id;
|
||||
message.uid = uid;
|
||||
message.msgid = helper.getMessageID();
|
||||
message.references = TextUtils.join(" ", helper.getReferences());
|
||||
message.inreplyto = helper.getInReplyTo();
|
||||
message.thread = helper.getThreadId(uid);
|
||||
message.from = helper.getFrom();
|
||||
message.to = helper.getTo();
|
||||
message.cc = helper.getCc();
|
||||
message.bcc = helper.getBcc();
|
||||
message.reply = helper.getReply();
|
||||
message.subject = imessage.getSubject();
|
||||
message.received = imessage.getReceivedDate().getTime();
|
||||
message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime());
|
||||
message.seen = seen;
|
||||
message.ui_seen = seen;
|
||||
message.ui_hide = false;
|
||||
|
||||
message.accountName = account.name;
|
||||
message.folderName = folder.name;
|
||||
message.folderType = folder.type;
|
||||
message.count = 1;
|
||||
message.unseen = (seen ? 0 : 1);
|
||||
message.attachments = 0;
|
||||
|
||||
message.body = helper.getHtml();
|
||||
message.virtual = true;
|
||||
|
||||
result.messages.add(message);
|
||||
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
float used = (float) (rt.totalMemory() - rt.freeMemory()) / rt.maxMemory();
|
||||
if (used < MAX_MEMORY_USAGE)
|
||||
cache.put(pos, message);
|
||||
else
|
||||
Log.i(Helper.TAG, "SDS memory used=" + used);
|
||||
}
|
||||
|
||||
Log.i(Helper.TAG, "SDS result=" + result.messages.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void reportError(final Throwable ex) {
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(context, Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class SearchResult {
|
||||
int total;
|
||||
List<TupleMessageEx> messages;
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,15 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -79,12 +82,14 @@ import javax.mail.Flags;
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.FolderClosedException;
|
||||
import javax.mail.FolderNotFoundException;
|
||||
import javax.mail.Header;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessageRemovedException;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.NoSuchProviderException;
|
||||
import javax.mail.SendFailedException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.StoreClosedException;
|
||||
import javax.mail.Transport;
|
||||
import javax.mail.UIDFolder;
|
||||
import javax.mail.event.ConnectionAdapter;
|
||||
@@ -99,7 +104,6 @@ import javax.mail.event.StoreEvent;
|
||||
import javax.mail.event.StoreListener;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.ParseException;
|
||||
import javax.mail.search.ComparisonTerm;
|
||||
import javax.mail.search.ReceivedDateTerm;
|
||||
import javax.net.ssl.SSLException;
|
||||
@@ -119,10 +123,10 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
private static final int NOTIFICATION_SYNCHRONIZE = 1;
|
||||
private static final int NOTIFICATION_UNSEEN = 2;
|
||||
|
||||
private static final int CONNECT_BACKOFF_START = 32; // seconds
|
||||
private static final int CONNECT_BACKOFF_START = 8; // seconds
|
||||
private static final int CONNECT_BACKOFF_MAX = 1024; // seconds (1024 sec ~ 17 min)
|
||||
private static final long STORE_NOOP_INTERVAL = 9 * 60 * 1000L; // ms
|
||||
private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
|
||||
private static final int ATTACHMENT_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes
|
||||
|
||||
static final String ACTION_SYNCHRONIZE_FOLDER = BuildConfig.APPLICATION_ID + ".SYNCHRONIZE_FOLDER";
|
||||
static final String ACTION_PROCESS_OPERATIONS = BuildConfig.APPLICATION_ID + ".PROCESS_OPERATIONS";
|
||||
@@ -305,7 +309,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
if (!TextUtils.isEmpty(message.subject))
|
||||
sb.append(": ").append(message.subject);
|
||||
sb.append(" ").append(df.format(new Date(message.sent)));
|
||||
sb.append("\n");
|
||||
sb.append("<br>");
|
||||
}
|
||||
|
||||
builder.setStyle(new Notification.BigTextStyle().bigText(Html.fromHtml(sb.toString())));
|
||||
@@ -339,6 +343,8 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
.setCategory(Notification.CATEGORY_ERROR)
|
||||
.setVisibility(Notification.VISIBILITY_SECRET);
|
||||
|
||||
builder.setStyle(new Notification.BigTextStyle().bigText(ex.toString()));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -355,37 +361,44 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
// MailConnectException
|
||||
// - on connectity problems when connecting to store
|
||||
|
||||
String action;
|
||||
if (TextUtils.isEmpty(account))
|
||||
action = folder;
|
||||
else if (TextUtils.isEmpty(folder))
|
||||
action = account;
|
||||
else
|
||||
action = account + "/" + folder;
|
||||
|
||||
EntityLog.log(this, action + "\n" + ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
|
||||
if (!(ex instanceof MailConnectException) &&
|
||||
!(ex instanceof FolderClosedException) &&
|
||||
!(ex instanceof IllegalStateException) &&
|
||||
!(ex instanceof AuthenticationFailedException) && // Also: Too many simultaneous connections
|
||||
!(ex instanceof StoreClosedException) &&
|
||||
!(ex instanceof MessagingException && ex.getCause() instanceof UnknownHostException) &&
|
||||
!(ex instanceof MessagingException && ex.getCause() instanceof ConnectionException) &&
|
||||
!(ex instanceof MessagingException && ex.getCause() instanceof SocketException) &&
|
||||
!(ex instanceof MessagingException && ex.getCause() instanceof SocketTimeoutException) &&
|
||||
!(ex instanceof MessagingException && ex.getCause() instanceof SSLException)) {
|
||||
String action;
|
||||
if (TextUtils.isEmpty(account))
|
||||
action = folder;
|
||||
else if (TextUtils.isEmpty(folder))
|
||||
action = account;
|
||||
else
|
||||
action = account + "/" + folder;
|
||||
|
||||
!(ex instanceof MessagingException && ex.getCause() instanceof SSLException) &&
|
||||
!(ex instanceof MessagingException && "connection failure".equals(ex.getMessage()))) {
|
||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||
nm.notify(action, 1, getNotificationError(action, ex).build());
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorAccount(final EntityAccount account, final ServiceState state) throws NoSuchProviderException {
|
||||
Log.i(Helper.TAG, account.name + " start");
|
||||
|
||||
final DB db = DB.getInstance(this);
|
||||
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
final ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
|
||||
|
||||
int backoff = CONNECT_BACKOFF_START;
|
||||
while (state.running) {
|
||||
Log.i(Helper.TAG, account.name + " run");
|
||||
EntityLog.log(this, account.name + " run");
|
||||
|
||||
// Debug
|
||||
boolean debug = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("debug", false);
|
||||
if (debug)
|
||||
System.setProperty("mail.socket.debug", "true");
|
||||
System.setProperty("mail.socket.debug", Boolean.toString(debug));
|
||||
|
||||
// Refresh token
|
||||
if (account.auth_type == Helper.AUTH_TYPE_GMAIL) {
|
||||
@@ -393,6 +406,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
db.account().setAccountPassword(account.id, account.password);
|
||||
}
|
||||
|
||||
// Create session
|
||||
Properties props = MessageHelper.getSessionProperties(this, account.auth_type);
|
||||
final Session isession = Session.getInstance(props, null);
|
||||
isession.setDebug(debug);
|
||||
@@ -459,19 +473,15 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
db.folder().setFolderState(folder.id, null);
|
||||
db.account().setAccountState(account.id, "connecting");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
boolean hasIdle = istore.hasCapability("IDLE");
|
||||
|
||||
backoff = CONNECT_BACKOFF_START;
|
||||
db.account().setAccountState(account.id, "connected");
|
||||
db.account().setAccountError(account.id, null);
|
||||
|
||||
EntityLog.log(this, account.name + " connected");
|
||||
|
||||
// Update folder list
|
||||
try {
|
||||
synchronizeFolders(account, istore, state);
|
||||
} catch (MessagingException ex) {
|
||||
// Don't show to user
|
||||
throw new IllegalStateException("synchronize folders", ex);
|
||||
}
|
||||
synchronizeFolders(account, istore, state);
|
||||
|
||||
// Synchronize folders
|
||||
for (final EntityFolder folder : db.folder().getFolders(account.id, true)) {
|
||||
@@ -480,7 +490,12 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
db.folder().setFolderState(folder.id, "connecting");
|
||||
|
||||
final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name);
|
||||
ifolder.open(Folder.READ_WRITE);
|
||||
try {
|
||||
ifolder.open(Folder.READ_WRITE);
|
||||
} catch (Throwable ex) {
|
||||
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
|
||||
throw ex;
|
||||
}
|
||||
folders.put(folder, ifolder);
|
||||
|
||||
db.folder().setFolderState(folder.id, "connected");
|
||||
@@ -491,6 +506,9 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Process pending operations
|
||||
processOperations(folder, isession, istore, ifolder);
|
||||
|
||||
// Listen for new and deleted messages
|
||||
ifolder.addMessageCountListener(new MessageCountAdapter() {
|
||||
@Override
|
||||
@@ -500,10 +518,16 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
Log.i(Helper.TAG, folder.name + " messages added");
|
||||
for (Message imessage : e.getMessages())
|
||||
try {
|
||||
synchronizeMessage(folder, ifolder, (IMAPMessage) imessage);
|
||||
synchronizeMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, false);
|
||||
} catch (MessageRemovedException ex) {
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} catch (IOException ex) {
|
||||
if (ex.getCause() instanceof MessageRemovedException)
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
EntityOperation.process(ServiceSynchronize.this); // download small attachments
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
reportError(account.name, folder.name, ex);
|
||||
@@ -560,9 +584,15 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
try {
|
||||
try {
|
||||
Log.i(Helper.TAG, folder.name + " message changed");
|
||||
synchronizeMessage(folder, ifolder, (IMAPMessage) e.getMessage());
|
||||
synchronizeMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), false);
|
||||
EntityOperation.process(ServiceSynchronize.this); // download small attachments
|
||||
} catch (MessageRemovedException ex) {
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} catch (IOException ex) {
|
||||
if (ex.getCause() instanceof MessageRemovedException)
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
@@ -583,18 +613,15 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
try {
|
||||
Thread.sleep(account.poll_interval * 60 * 1000L);
|
||||
|
||||
if (istore.hasCapability("IDLE")) {
|
||||
Log.i(Helper.TAG, folder.name + " request NOOP");
|
||||
ifolder.doCommand(new IMAPFolder.ProtocolCommand() {
|
||||
public Object doCommand(IMAPProtocol p) throws ProtocolException {
|
||||
Log.i(Helper.TAG, ifolder.getName() + " start NOOP");
|
||||
p.simpleCommand("NOOP", null);
|
||||
Log.i(Helper.TAG, ifolder.getName() + " end NOOP");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} else
|
||||
synchronizeMessages(account, folder, ifolder, state);
|
||||
Log.i(Helper.TAG, folder.name + " request NOOP");
|
||||
ifolder.doCommand(new IMAPFolder.ProtocolCommand() {
|
||||
public Object doCommand(IMAPProtocol p) throws ProtocolException {
|
||||
Log.i(Helper.TAG, ifolder.getName() + " start NOOP");
|
||||
p.simpleCommand("NOOP", null);
|
||||
Log.i(Helper.TAG, ifolder.getName() + " end NOOP");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (InterruptedException ex) {
|
||||
Log.w(Helper.TAG, folder.name + " noop " + ex.toString());
|
||||
@@ -618,34 +645,32 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
noops.add(noop);
|
||||
|
||||
// Receive folder events
|
||||
if (hasIdle) {
|
||||
Thread idle = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Log.i(Helper.TAG, folder.name + " start idle");
|
||||
while (state.running && ifolder.isOpen()) {
|
||||
Log.i(Helper.TAG, folder.name + " do idle");
|
||||
ifolder.idle(false);
|
||||
Log.i(Helper.TAG, folder.name + " done idle");
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
reportError(account.name, folder.name, ex);
|
||||
|
||||
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
|
||||
|
||||
synchronized (state) {
|
||||
state.notifyAll();
|
||||
}
|
||||
} finally {
|
||||
Log.i(Helper.TAG, folder.name + " end idle");
|
||||
Thread idle = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Log.i(Helper.TAG, folder.name + " start idle");
|
||||
while (state.running && ifolder.isOpen()) {
|
||||
Log.i(Helper.TAG, folder.name + " do idle");
|
||||
ifolder.idle(false);
|
||||
Log.i(Helper.TAG, folder.name + " done idle");
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
reportError(account.name, folder.name, ex);
|
||||
|
||||
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
|
||||
|
||||
synchronized (state) {
|
||||
state.notifyAll();
|
||||
}
|
||||
} finally {
|
||||
Log.i(Helper.TAG, folder.name + " end idle");
|
||||
}
|
||||
}, "sync.idle." + folder.id);
|
||||
idle.start();
|
||||
idlers.add(idle);
|
||||
}
|
||||
}
|
||||
}, "sync.idle." + folder.id);
|
||||
idle.start();
|
||||
idlers.add(idle);
|
||||
}
|
||||
|
||||
BroadcastReceiver processFolder = new BroadcastReceiver() {
|
||||
@@ -673,10 +698,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
if (folder == null)
|
||||
folder = db.folder().getFolder(fid);
|
||||
|
||||
if (shouldClose)
|
||||
Log.i(Helper.TAG, folder.name + " run offline=" + shouldClose);
|
||||
else
|
||||
Log.i(Helper.TAG, folder.name + " run online");
|
||||
Log.i(Helper.TAG, folder.name + " run " + (shouldClose ? "offline" : "online"));
|
||||
|
||||
if (ifolder == null) {
|
||||
// Prevent unnecessary folder connections
|
||||
@@ -684,28 +706,37 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
if (db.operation().getOperationCount(fid) == 0)
|
||||
return;
|
||||
|
||||
db.folder().setFolderState(folder.id, "connecting");
|
||||
|
||||
ifolder = (IMAPFolder) istore.getFolder(folder.name);
|
||||
ifolder.open(Folder.READ_WRITE);
|
||||
|
||||
db.folder().setFolderState(folder.id, "connected");
|
||||
db.folder().setFolderError(folder.id, null);
|
||||
}
|
||||
|
||||
if (ACTION_PROCESS_OPERATIONS.equals(intent.getAction()))
|
||||
processOperations(folder, isession, istore, ifolder);
|
||||
|
||||
else if (ACTION_SYNCHRONIZE_FOLDER.equals(intent.getAction()))
|
||||
synchronizeMessages(account, folder, ifolder, state);
|
||||
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
reportError(account.name, folder.name, ex);
|
||||
|
||||
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
|
||||
} finally {
|
||||
if (shouldClose)
|
||||
if (shouldClose) {
|
||||
if (ifolder != null && ifolder.isOpen()) {
|
||||
db.folder().setFolderState(folder.id, "closing");
|
||||
try {
|
||||
ifolder.close(false);
|
||||
} catch (MessagingException ex) {
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
db.folder().setFolderState(folder.id, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -721,14 +752,6 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
lbm.registerReceiver(processFolder, f);
|
||||
|
||||
try {
|
||||
// Process pending folder operations
|
||||
Log.i(Helper.TAG, "listen process folder");
|
||||
for (final EntityFolder folder : folders.keySet())
|
||||
if (!EntityFolder.OUTBOX.equals(folder.type))
|
||||
lbm.sendBroadcast(new Intent(ACTION_PROCESS_OPERATIONS)
|
||||
.setType("account/" + account.id)
|
||||
.putExtra("folder", folder.id));
|
||||
|
||||
// Keep store alive
|
||||
while (state.running && istore.isConnected()) {
|
||||
Log.i(Helper.TAG, "Checking folders");
|
||||
@@ -753,8 +776,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
if (!(ex instanceof AuthenticationFailedException)) // Also: Too many simultaneous connections
|
||||
reportError(account.name, null, ex);
|
||||
reportError(account.name, null, ex);
|
||||
db.account().setAccountError(account.id, Helper.formatThrowable(ex));
|
||||
} finally {
|
||||
// Close store
|
||||
@@ -762,6 +784,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
db.account().setAccountState(account.id, "closing");
|
||||
for (EntityFolder folder : folders.keySet())
|
||||
db.folder().setFolderState(folder.id, "closing");
|
||||
EntityLog.log(this, account.name + " closing");
|
||||
try {
|
||||
// This can take some time
|
||||
istore.close();
|
||||
@@ -769,6 +792,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
Log.w(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} finally {
|
||||
Log.i(Helper.TAG, account.name + " closed");
|
||||
EntityLog.log(this, account.name + " closed");
|
||||
db.account().setAccountState(account.id, null);
|
||||
for (EntityFolder folder : folders.keySet())
|
||||
db.folder().setFolderState(folder.id, null);
|
||||
@@ -789,7 +813,8 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
|
||||
if (state.running) {
|
||||
try {
|
||||
Log.i(Helper.TAG, "Backoff seconds=" + backoff);
|
||||
Log.i(Helper.TAG, account.name + " backoff=" + backoff);
|
||||
EntityLog.log(this, account.name + " backoff=" + backoff);
|
||||
Thread.sleep(backoff * 1000L);
|
||||
|
||||
if (backoff < CONNECT_BACKOFF_MAX)
|
||||
@@ -801,6 +826,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
}
|
||||
|
||||
Log.i(Helper.TAG, account.name + " stopped");
|
||||
EntityLog.log(this, account.name + " stopped");
|
||||
}
|
||||
|
||||
private void processOperations(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder) throws MessagingException, JSONException, IOException {
|
||||
@@ -828,7 +854,8 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
if (message.uid == null &&
|
||||
(EntityOperation.SEEN.equals(op.name) ||
|
||||
EntityOperation.DELETE.equals(op.name) ||
|
||||
EntityOperation.MOVE.equals(op.name)))
|
||||
EntityOperation.MOVE.equals(op.name) ||
|
||||
EntityOperation.HEADERS.equals(op.name)))
|
||||
throw new IllegalArgumentException(op.name + " without uid");
|
||||
|
||||
JSONArray jargs = new JSONArray(op.args);
|
||||
@@ -836,6 +863,9 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
if (EntityOperation.SEEN.equals(op.name))
|
||||
doSeen(folder, ifolder, message, jargs, db);
|
||||
|
||||
else if (EntityOperation.FLAG.equals(op.name))
|
||||
doFlag(folder, ifolder, message, jargs, db);
|
||||
|
||||
else if (EntityOperation.ADD.equals(op.name))
|
||||
doAdd(folder, isession, ifolder, message, jargs, db);
|
||||
|
||||
@@ -851,6 +881,9 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
else if (EntityOperation.ATTACHMENT.equals(op.name))
|
||||
doAttachment(folder, op, ifolder, message, jargs, db);
|
||||
|
||||
else if (EntityOperation.HEADERS.equals(op.name))
|
||||
doHeaders(folder, ifolder, message, db);
|
||||
|
||||
else
|
||||
throw new MessagingException("Unknown operation name=" + op.name);
|
||||
|
||||
@@ -903,24 +936,34 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
db.message().setMessageSeen(message.id, seen);
|
||||
}
|
||||
|
||||
private void doFlag(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException {
|
||||
// Star/unstar message
|
||||
boolean flagged = jargs.getBoolean(0);
|
||||
Message imessage = ifolder.getMessageByUID(message.uid);
|
||||
if (imessage == null)
|
||||
throw new MessageRemovedException();
|
||||
|
||||
imessage.setFlag(Flags.Flag.FLAGGED, flagged);
|
||||
|
||||
db.message().setMessageFlagged(message.id, flagged);
|
||||
}
|
||||
|
||||
private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException {
|
||||
// Append message
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
|
||||
|
||||
MimeMessage imessage = MessageHelper.from(this, message, attachments, isession);
|
||||
AppendUID[] uid = ifolder.appendUIDMessages(new Message[]{imessage});
|
||||
db.message().setMessageUid(message.id, uid[0].uid);
|
||||
Log.i(Helper.TAG, "Appended uid=" + uid[0].uid);
|
||||
|
||||
if (message.uid != null) {
|
||||
Message iprev = ifolder.getMessageByUID(message.uid);
|
||||
if (iprev != null) {
|
||||
Log.i(Helper.TAG, "Deleting existing id=" + message.id);
|
||||
Log.i(Helper.TAG, "Deleting existing uid=" + message.uid);
|
||||
iprev.setFlag(Flags.Flag.DELETED, true);
|
||||
ifolder.expunge();
|
||||
}
|
||||
}
|
||||
|
||||
db.message().setMessageUid(message.id, uid[0].uid);
|
||||
Log.i(Helper.TAG, "Appended uid=" + message.uid);
|
||||
}
|
||||
|
||||
private void doMove(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException, IOException {
|
||||
@@ -1080,7 +1123,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
os = new BufferedOutputStream(new FileOutputStream(file));
|
||||
|
||||
int size = 0;
|
||||
byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
|
||||
byte[] buffer = new byte[Helper.ATTACHMENT_BUFFER_SIZE];
|
||||
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
|
||||
size += len;
|
||||
os.write(buffer, 0, len);
|
||||
@@ -1113,11 +1156,23 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
}
|
||||
}
|
||||
|
||||
private void synchronizeFolders(EntityAccount account, IMAPStore istore, ServiceState state) throws MessagingException {
|
||||
try {
|
||||
Log.v(Helper.TAG, "Start sync folders");
|
||||
private void doHeaders(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, DB db) throws MessagingException {
|
||||
Message imessage = ifolder.getMessageByUID(message.uid);
|
||||
Enumeration<Header> headers = imessage.getAllHeaders();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (headers.hasMoreElements()) {
|
||||
Header header = headers.nextElement();
|
||||
sb.append(header.getName()).append(": ").append(header.getValue()).append("\n");
|
||||
}
|
||||
db.message().setMessageHeaders(message.id, sb.toString());
|
||||
}
|
||||
|
||||
DB db = DB.getInstance(this);
|
||||
private void synchronizeFolders(EntityAccount account, IMAPStore istore, ServiceState state) throws MessagingException {
|
||||
DB db = DB.getInstance(this);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
Log.v(Helper.TAG, "Start sync folders");
|
||||
|
||||
List<String> names = new ArrayList<>();
|
||||
for (EntityFolder folder : db.folder().getUserFolders(account.id))
|
||||
@@ -1128,9 +1183,6 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
Log.i(Helper.TAG, "Remote folder count=" + ifolders.length);
|
||||
|
||||
for (Folder ifolder : ifolders) {
|
||||
if (!state.running)
|
||||
return;
|
||||
|
||||
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
|
||||
boolean selectable = true;
|
||||
for (String attr : attrs) {
|
||||
@@ -1165,7 +1217,10 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
Log.i(Helper.TAG, "Delete local folder=" + names.size());
|
||||
for (String name : names)
|
||||
db.folder().deleteFolder(account.id, name);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
Log.v(Helper.TAG, "End sync folder");
|
||||
}
|
||||
}
|
||||
@@ -1243,41 +1298,23 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
Log.i(Helper.TAG, folder.name + " add=" + imessages.length);
|
||||
for (int i = imessages.length - 1; i >= 0; i--)
|
||||
try {
|
||||
int status = synchronizeMessage(folder, ifolder, (IMAPMessage) imessages[i]);
|
||||
int status = synchronizeMessage(this, folder, ifolder, (IMAPMessage) imessages[i], false);
|
||||
if (status > 0)
|
||||
added++;
|
||||
else if (status < 0)
|
||||
updated++;
|
||||
else
|
||||
unchanged++;
|
||||
} catch (ParseException ex) {
|
||||
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
reportError(account.name, folder.name, ex);
|
||||
} catch (MessageRemovedException ex) {
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} catch (IOException ex) {
|
||||
// Getting attachments
|
||||
if (ex.getCause() instanceof MessageRemovedException)
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Cleanup files
|
||||
File[] messages = new File(getFilesDir(), "messages").listFiles();
|
||||
if (messages != null)
|
||||
for (File file : messages)
|
||||
if (file.isFile()) {
|
||||
long id = Long.parseLong(file.getName());
|
||||
if (db.message().countMessage(id) == 0) {
|
||||
Log.i(Helper.TAG, "Cleanup message id=" + id);
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
File[] attachments = new File(getFilesDir(), "attachments").listFiles();
|
||||
if (attachments != null)
|
||||
for (File file : attachments)
|
||||
if (file.isFile()) {
|
||||
long id = Long.parseLong(file.getName());
|
||||
if (db.attachment().countAttachment(id) == 0) {
|
||||
Log.i(Helper.TAG, "Cleanup attachment id=" + id);
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
EntityOperation.process(this); // download small attachments
|
||||
|
||||
Log.w(Helper.TAG, folder.name + " statistics added=" + added + " updated=" + updated + " unchanged=" + unchanged);
|
||||
} finally {
|
||||
@@ -1286,7 +1323,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
}
|
||||
}
|
||||
|
||||
private int synchronizeMessage(EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage) throws MessagingException, IOException {
|
||||
static int synchronizeMessage(Context context, EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage, boolean found) throws MessagingException, IOException {
|
||||
long uid;
|
||||
try {
|
||||
FetchProfile fp = new FetchProfile();
|
||||
@@ -1299,17 +1336,20 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
|
||||
if (imessage.isExpunged()) {
|
||||
Log.i(Helper.TAG, folder.name + " expunged uid=" + uid);
|
||||
imessage.invalidateHeaders();
|
||||
return 0;
|
||||
}
|
||||
if (imessage.isSet(Flags.Flag.DELETED)) {
|
||||
Log.i(Helper.TAG, folder.name + " deleted uid=" + uid);
|
||||
imessage.invalidateHeaders();
|
||||
return 0;
|
||||
}
|
||||
|
||||
MessageHelper helper = new MessageHelper(imessage);
|
||||
boolean seen = helper.getSeen();
|
||||
boolean flagged = helper.getFlagged();
|
||||
|
||||
DB db = DB.getInstance(this);
|
||||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
int result = 0;
|
||||
|
||||
@@ -1351,6 +1391,13 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen);
|
||||
result = -1;
|
||||
}
|
||||
if (message.flagged != flagged || message.flagged != message.ui_flagged) {
|
||||
message.flagged = flagged;
|
||||
message.ui_flagged = flagged;
|
||||
db.message().updateMessage(message);
|
||||
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " flagged=" + flagged);
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
@@ -1386,12 +1433,18 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime());
|
||||
message.seen = seen;
|
||||
message.ui_seen = seen;
|
||||
message.flagged = false;
|
||||
message.ui_flagged = false;
|
||||
message.ui_hide = false;
|
||||
message.ui_found = found;
|
||||
|
||||
message.id = db.message().insertMessage(message);
|
||||
message.write(this, helper.getHtml());
|
||||
message.write(context, helper.getHtml());
|
||||
Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid);
|
||||
|
||||
// Free memory
|
||||
imessage.invalidateHeaders();
|
||||
|
||||
int sequence = 0;
|
||||
for (EntityAttachment attachment : helper.getAttachments()) {
|
||||
sequence++;
|
||||
@@ -1400,6 +1453,9 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
attachment.message = message.id;
|
||||
attachment.sequence = sequence;
|
||||
attachment.id = db.attachment().insertAttachment(attachment);
|
||||
|
||||
if (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE)
|
||||
EntityOperation.queue(db, message, EntityOperation.ATTACHMENT, sequence);
|
||||
}
|
||||
|
||||
result = 1;
|
||||
@@ -1420,12 +1476,15 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
private boolean running = false;
|
||||
private Thread main;
|
||||
private EntityFolder outbox = null;
|
||||
private ExecutorService lifecycle = Executors.newSingleThreadExecutor();
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private ExecutorService lifecycle = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
|
||||
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
Log.i(Helper.TAG, "Network available " + network);
|
||||
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
|
||||
NetworkInfo ni = cm.getNetworkInfo(network);
|
||||
Log.i(Helper.TAG, "Network available " + network + " " + ni);
|
||||
EntityLog.log(ServiceSynchronize.this, "Network available " + network + " " + ni);
|
||||
|
||||
if (running)
|
||||
Log.i(Helper.TAG, "Service already running");
|
||||
@@ -1445,14 +1504,16 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
Log.i(Helper.TAG, "Network lost " + network);
|
||||
EntityLog.log(ServiceSynchronize.this, "Network lost " + network);
|
||||
|
||||
if (running) {
|
||||
Log.i(Helper.TAG, "Service running");
|
||||
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
|
||||
NetworkInfo ni = cm.getActiveNetworkInfo();
|
||||
Log.i(Helper.TAG, "Network active=" + (ni == null ? null : ni.toString()));
|
||||
if (ni == null || !ni.isConnected()) {
|
||||
Log.i(Helper.TAG, "Network disconnected=" + ni);
|
||||
NetworkInfo ani = cm.getActiveNetworkInfo();
|
||||
Log.i(Helper.TAG, "Network active=" + (ani == null ? null : ani.toString()));
|
||||
if (ani == null || !ani.isConnected()) {
|
||||
EntityLog.log(ServiceSynchronize.this, "Network disconnected=" + ani);
|
||||
Log.i(Helper.TAG, "Network disconnected=" + ani);
|
||||
running = false;
|
||||
lifecycle.submit(new Runnable() {
|
||||
@Override
|
||||
@@ -1467,6 +1528,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
}
|
||||
|
||||
private void start() {
|
||||
EntityLog.log(ServiceSynchronize.this, "Main start");
|
||||
state = new ServiceState();
|
||||
|
||||
main = new Thread(new Runnable() {
|
||||
@@ -1522,15 +1584,29 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
threads.add(t);
|
||||
}
|
||||
|
||||
EntityLog.log(ServiceSynchronize.this, "Main started");
|
||||
|
||||
synchronized (state) {
|
||||
try {
|
||||
state.wait();
|
||||
} catch (InterruptedException ex) {
|
||||
Log.w(Helper.TAG, "main wait " + ex.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Stop monitoring accounts
|
||||
for (Thread t : threads)
|
||||
for (Thread t : threads) {
|
||||
t.interrupt();
|
||||
join(t);
|
||||
}
|
||||
threads.clear();
|
||||
|
||||
// Stop monitoring outbox
|
||||
lbm.unregisterReceiver(outboxReceiver);
|
||||
Log.i(Helper.TAG, outbox.name + " unlisten operations");
|
||||
db.folder().setFolderState(outbox.id, null);
|
||||
|
||||
EntityLog.log(ServiceSynchronize.this, "Main exited");
|
||||
} catch (Throwable ex) {
|
||||
// Fail-safe
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
@@ -1543,6 +1619,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
|
||||
private void stop(boolean disconnected) {
|
||||
if (main != null) {
|
||||
EntityLog.log(ServiceSynchronize.this, "Main stop disconnected=" + disconnected);
|
||||
synchronized (state) {
|
||||
state.running = false;
|
||||
state.disconnected = disconnected;
|
||||
@@ -1554,6 +1631,8 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
join(main);
|
||||
|
||||
main = null;
|
||||
|
||||
EntityLog.log(ServiceSynchronize.this, "Main stopped");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1592,7 +1671,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
reportError(null, outbox.name, ex);
|
||||
} finally {
|
||||
Log.i(Helper.TAG, outbox.name + " end operations");
|
||||
db.folder().setFolderState(outbox.id, "connected");
|
||||
db.folder().setFolderState(outbox.id, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1610,12 +1689,12 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
Log.i(Helper.TAG, "Joined " + thread.getName());
|
||||
} catch (InterruptedException ex) {
|
||||
Log.e(Helper.TAG, thread.getName() + " join " + ex.toString());
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public static void start(Context context) {
|
||||
ContextCompat.startForegroundService(context, new Intent(context, ServiceSynchronize.class));
|
||||
JobDaily.schedule(context);
|
||||
}
|
||||
|
||||
public static void reload(Context context, String reason) {
|
||||
|
||||
@@ -22,9 +22,11 @@ package eu.faircode.email;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
@@ -33,8 +35,6 @@ import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LifecycleService;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
|
||||
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
|
||||
|
||||
//
|
||||
// This simple task is simple to use, but it is also simple to cause bugs that can easily lead to crashes
|
||||
// Make sure to not access any member in any outer scope from onLoad
|
||||
@@ -47,15 +47,7 @@ public abstract class SimpleTask<T> implements LifecycleObserver {
|
||||
private Bundle args = null;
|
||||
private Result stored = null;
|
||||
|
||||
private static HandlerThread handlerThread;
|
||||
private static Handler handler;
|
||||
|
||||
static {
|
||||
handlerThread = new HandlerThread("SimpleTask");
|
||||
handlerThread.start();
|
||||
handlerThread.setPriority(THREAD_PRIORITY_BACKGROUND);
|
||||
handler = new Handler(handlerThread.getLooper());
|
||||
}
|
||||
private ExecutorService executor = Executors.newCachedThreadPool(Helper.backgroundThreadFactory);
|
||||
|
||||
public void load(Context context, LifecycleOwner owner, Bundle args) {
|
||||
run(context, owner, args);
|
||||
@@ -70,7 +62,11 @@ public abstract class SimpleTask<T> implements LifecycleObserver {
|
||||
}
|
||||
|
||||
public void load(final Fragment fragment, Bundle args) {
|
||||
run(fragment.getContext(), fragment.getViewLifecycleOwner(), args);
|
||||
try {
|
||||
run(fragment.getContext(), fragment.getViewLifecycleOwner(), args);
|
||||
} catch (IllegalStateException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
@@ -120,7 +116,7 @@ public abstract class SimpleTask<T> implements LifecycleObserver {
|
||||
owner.getLifecycle().addObserver(this);
|
||||
|
||||
// Run in background thread
|
||||
handler.post(new Runnable() {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Result result = new Result();
|
||||
|
||||
10
app/src/main/res/drawable/baseline_check_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/baseline_folder_special_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM17.94,17L15,15.28 12.06,17l0.78,-3.33 -2.59,-2.24 3.41,-0.29L15,8l1.34,3.14 3.41,0.29 -2.59,2.24 0.78,3.33z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/baseline_inbox_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,3L4.99,3c-1.11,0 -1.98,0.89 -1.98,2L3,19c0,1.1 0.88,2 1.99,2L19,21c1.1,0 2,-0.9 2,-2L21,5c0,-1.11 -0.9,-2 -2,-2zM19,15h-4c0,1.66 -1.35,3 -3,3s-3,-1.34 -3,-3L4.99,15L4.99,5L19,5v10z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/baseline_move_to_inbox_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,3L4.99,3c-1.11,0 -1.98,0.9 -1.98,2L3,19c0,1.1 0.88,2 1.99,2L19,21c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,15h-4c0,1.66 -1.35,3 -3,3s-3,-1.34 -3,-3L4.99,15L4.99,5L19,5v10zM16,10h-2L14,7h-4v3L8,10l4,4 4,-4z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/baseline_star_border_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
|
||||
</vector>
|
||||
@@ -47,6 +47,18 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvCopyright" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnLog"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/title_log"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvEula" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDebugInfo"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
@@ -56,8 +68,7 @@
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/title_debug_info"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvEula" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -30,28 +30,6 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvProvider" />
|
||||
|
||||
<!-- name -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_account_name"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spProvider" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/title_account_name_hint"
|
||||
android:inputType="text"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvName" />
|
||||
|
||||
<!-- IMAP -->
|
||||
|
||||
<TextView
|
||||
@@ -62,7 +40,27 @@
|
||||
android:text="@string/title_imap"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etName" />
|
||||
app:layout_constraintTop_toBottomOf="@id/spProvider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPop"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_pop"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textStyle="italic"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvImap" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInsecure"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_insecure"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textStyle="italic"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvPop" />
|
||||
|
||||
<!-- host -->
|
||||
|
||||
@@ -74,7 +72,7 @@
|
||||
android:text="@string/title_host"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvImap" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tvInsecure" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etHost"
|
||||
@@ -167,27 +165,70 @@
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInstructions"
|
||||
<Button
|
||||
android:id="@+id/btnAdvanced"
|
||||
style="@style/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:freezesText="true"
|
||||
android:text="@string/title_instructions"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/title_setup_advanced"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
|
||||
|
||||
<!-- name -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLink"
|
||||
android:id="@+id/tvName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:freezesText="true"
|
||||
android:text="link"
|
||||
android:text="@string/title_account_name"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvInstructions" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnAdvanced" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/title_account_name_hint"
|
||||
android:inputType="text"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSignature"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_account_signature"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etName" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSignature"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/title_optional"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ibPro"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvSignature" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ibPro"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/baseline_info_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/etSignature"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/etSignature" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbSynchronize"
|
||||
@@ -196,7 +237,7 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_synchronize_account"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvLink" />
|
||||
app:layout_constraintTop_toBottomOf="@id/etSignature" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbPrimary"
|
||||
@@ -207,26 +248,6 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSynchronize" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInterval"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_interval"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbPrimary" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInterval"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:text="9"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvInterval" />
|
||||
|
||||
<!-- check -->
|
||||
|
||||
<Button
|
||||
@@ -236,7 +257,7 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_check"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etInterval" />
|
||||
app:layout_constraintTop_toBottomOf="@id/cbPrimary" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbCheck"
|
||||
@@ -256,19 +277,7 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:src="@drawable/baseline_delete_24"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etInterval" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvIdle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:minWidth="100dp"
|
||||
android:text="@string/title_no_idle"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/btnCheck" />
|
||||
app:layout_constraintTop_toBottomOf="@id/cbPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDrafts"
|
||||
@@ -288,7 +297,7 @@
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/tvDrafts"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvIdle" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnCheck" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSent"
|
||||
@@ -404,10 +413,22 @@
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpInstructions"
|
||||
android:id="@+id/grpServer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvInstructions,tvLink" />
|
||||
app:constraint_referenced_ids="tvImap,tvPop,tvInsecure,tvHost,etHost,tvPort,etPort" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpAuthorize"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvUser,etUser,tvPassword,tilPassword" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpAdvanced"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvName,etName,tvSignature,etSignature,ibPro,cbSynchronize,cbPrimary" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpFolders"
|
||||
|
||||
@@ -22,6 +22,15 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbUnified"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_unified_folder"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSynchronize" />
|
||||
|
||||
<!-- after -->
|
||||
|
||||
<TextView
|
||||
@@ -32,7 +41,7 @@
|
||||
android:text="@string/title_after"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSynchronize" />
|
||||
app:layout_constraintTop_toBottomOf="@id/cbUnified" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAfter"
|
||||
|
||||
@@ -32,6 +32,37 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvName" />
|
||||
|
||||
<!--- linked account -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAccount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_account_linked"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etName" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spAccount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAccount" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAdvanced"
|
||||
style="@style/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/title_setup_advanced"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spAccount" />
|
||||
|
||||
<!-- email -->
|
||||
|
||||
<TextView
|
||||
@@ -42,7 +73,7 @@
|
||||
android:text="@string/title_identity_email"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etName" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnAdvanced" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etEmail"
|
||||
@@ -75,26 +106,6 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvReplyTo" />
|
||||
|
||||
|
||||
<!--- linked account -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAccount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_account_linked"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etReplyTo" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spAccount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAccount" />
|
||||
|
||||
<!--- provider -->
|
||||
|
||||
<TextView
|
||||
@@ -105,7 +116,7 @@
|
||||
android:text="@string/title_provider"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spAccount" />
|
||||
app:layout_constraintTop_toBottomOf="@id/etReplyTo" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spProvider"
|
||||
@@ -126,6 +137,16 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spProvider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInsecure"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_insecure"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textStyle="italic"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvSmtp" />
|
||||
|
||||
<!-- host -->
|
||||
|
||||
<TextView
|
||||
@@ -136,7 +157,7 @@
|
||||
android:text="@string/title_host"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvSmtp" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tvInsecure" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etHost"
|
||||
@@ -230,28 +251,6 @@
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInstructions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:freezesText="true"
|
||||
android:text="@string/title_instructions"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLink"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:freezesText="true"
|
||||
android:text="link"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvInstructions" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbSynchronize"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -259,7 +258,7 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_synchronize_identity"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvLink" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbPrimary"
|
||||
@@ -320,9 +319,9 @@
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpInstructions"
|
||||
android:id="@+id/grpAdvanced"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvInstructions,tvLink" />
|
||||
app:constraint_referenced_ids="tvEmail,etEmail,tvReplyTo,etReplyTo,tvProvider,spProvider,tvSmtp,tvInsecure,tvHost,etHost,cbStartTls,tvPort,etPort,tvUser,etUser,tvPassword,tilPassword,cbSynchronize,cbPrimary,cbStoreSent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -48,6 +48,26 @@
|
||||
app:layout_constraintStart_toEndOf="@id/ivAttachment"
|
||||
app:layout_constraintTop_toTopOf="@id/ivAttachment" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivThread"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:src="@drawable/baseline_message_24"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ivAttachment" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvThread"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:text="@string/title_legend_thread"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintBottom_toBottomOf="@id/ivThread"
|
||||
app:layout_constraintStart_toEndOf="@id/ivThread"
|
||||
app:layout_constraintTop_toTopOf="@id/ivThread" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivSynchronize"
|
||||
android:layout_width="24dp"
|
||||
@@ -55,7 +75,7 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:src="@drawable/baseline_sync_24"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ivAttachment" />
|
||||
app:layout_constraintTop_toBottomOf="@id/ivThread" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSynchronize"
|
||||
@@ -88,6 +108,26 @@
|
||||
app:layout_constraintStart_toEndOf="@id/ivPrimary"
|
||||
app:layout_constraintTop_toTopOf="@id/ivPrimary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivUnified"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:src="@drawable/baseline_folder_special_24"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ivPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvUnified"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:text="@string/title_legend_unified"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintBottom_toBottomOf="@id/ivUnified"
|
||||
app:layout_constraintStart_toEndOf="@id/ivUnified"
|
||||
app:layout_constraintTop_toTopOf="@id/ivUnified" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivDisconnected"
|
||||
android:layout_width="24dp"
|
||||
@@ -95,7 +135,7 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:src="@drawable/baseline_cloud_off_24"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ivPrimary" />
|
||||
app:layout_constraintTop_toBottomOf="@id/ivUnified" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDisconnected"
|
||||
|
||||
36
app/src/main/res/layout/fragment_logs.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ActivityView">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvLog"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbWait"
|
||||
style="@style/Base.Widget.AppCompat.ProgressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpReady"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="rvLog" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -14,6 +14,17 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivFlagged"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/baseline_star_24"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvFrom"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/tvFrom" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFrom"
|
||||
android:layout_width="0dp"
|
||||
@@ -25,21 +36,9 @@
|
||||
android:text="From"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textIsSelectable="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvSize"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:maxLines="1"
|
||||
android:text="123 K"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textIsSelectable="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvTime"
|
||||
app:layout_constraintTop_toTopOf="@id/tvFrom" />
|
||||
app:layout_constraintStart_toEndOf="@id/ivFlagged"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTime"
|
||||
@@ -98,10 +97,21 @@
|
||||
android:id="@+id/tvCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginEnd="3dp"
|
||||
android:maxLines="1"
|
||||
android:text="3"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvSubject"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ivThread"
|
||||
app:layout_constraintTop_toTopOf="@id/tvSubject" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivThread"
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:src="@drawable/baseline_message_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvSubject"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/tvSubject" />
|
||||
|
||||
@@ -186,6 +196,45 @@
|
||||
app:layout_constraintStart_toEndOf="@id/tvBccTitle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvCc" />
|
||||
|
||||
|
||||
<View
|
||||
android:id="@+id/vSeparatorRawHeaders"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvBcc" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRawHeaders"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:fontFamily="monospace"
|
||||
|
||||
android:freezesText="true"
|
||||
android:maxHeight="120sp"
|
||||
android:text="H1\nH2\nH3\nH4\nH5\nH6\nH7\nH8\nH9\n"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorRawHeaders" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbRawHeaders"
|
||||
style="@style/Base.Widget.AppCompat.ProgressBar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvRawHeaders"
|
||||
app:layout_constraintEnd_toEndOf="@id/tvRawHeaders"
|
||||
app:layout_constraintStart_toStartOf="@id/tvRawHeaders"
|
||||
app:layout_constraintTop_toTopOf="@id/tvRawHeaders" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vSeparatorAttachments"
|
||||
android:layout_width="match_parent"
|
||||
@@ -193,7 +242,7 @@
|
||||
android:layout_marginTop="3dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvBcc" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tvRawHeaders" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvAttachment"
|
||||
@@ -253,7 +302,6 @@
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/title_show_images"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorBody" />
|
||||
|
||||
@@ -285,11 +333,10 @@
|
||||
style="@style/Base.Widget.AppCompat.ProgressBar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/scroll"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/scroll"
|
||||
app:layout_constraintStart_toStartOf="@id/scroll"
|
||||
app:layout_constraintTop_toTopOf="@id/scroll" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
@@ -335,12 +382,24 @@
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvFrom,tvToTitle,tvTo,tvSize,tvTime,tvSubject" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpThread"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvCount,ivThread" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpAddresses"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="vSeparatorAddress,tvReplyToTitle,tvReplyTo,tvCcTitle,tvCc,tvBccTitle,tvBcc" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpRawHeaders"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="vSeparatorRawHeaders,tvRawHeaders" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpAttachments"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -18,28 +18,28 @@
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHintSwipe"
|
||||
android:id="@+id/tvHintActions"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/title_hint_swipe"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnHintSwipe"
|
||||
android:text="@string/title_hint_actions"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnHintActions"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnHintSwipe"
|
||||
android:id="@+id/btnHintActions"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/title_understood"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvHintSwipe"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvHintActions"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/tvHintSwipe" />
|
||||
app:layout_constraintTop_toTopOf="@id/tvHintActions" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vSeparator"
|
||||
@@ -48,7 +48,7 @@
|
||||
android:layout_marginTop="6dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnHintSwipe" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnHintActions" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvFolder"
|
||||
@@ -88,7 +88,7 @@
|
||||
android:id="@+id/grpHintSwipe"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvHintSwipe,btnHintSwipe,vSeparator" />
|
||||
app:constraint_referenced_ids="tvHintActions,btnHintActions,vSeparator" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpReady"
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_advanced_webview"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
@@ -40,8 +38,6 @@
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_advanced_sanitize"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvCustomTabs" />
|
||||
|
||||
@@ -52,8 +48,6 @@
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_advanced_compress_imap"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSanitize" />
|
||||
|
||||
@@ -64,8 +58,6 @@
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_advanced_debug"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbCompressImap" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/title_pro_purchase"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvList" />
|
||||
|
||||
|
||||
@@ -19,22 +19,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/title_setup_account"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbAccount"
|
||||
style="@style/Base.Widget.AppCompat.ProgressBar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/btnAccount"
|
||||
app:layout_constraintStart_toEndOf="@id/btnAccount"
|
||||
app:layout_constraintTop_toTopOf="@id/btnAccount" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAccount"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -49,6 +37,7 @@
|
||||
android:id="@+id/tvAccountDone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/baseline_check_24"
|
||||
android:text="@string/title_setup_done"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
@@ -56,29 +45,26 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAccount" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vSeparatorAccount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="9dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAccountDone" />
|
||||
|
||||
<!-- identity -->
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnIdentity"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="9dp"
|
||||
android:text="@string/title_setup_identity"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAccountDone" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbIdentity"
|
||||
style="@style/Base.Widget.AppCompat.ProgressBar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/btnIdentity"
|
||||
app:layout_constraintStart_toEndOf="@id/btnIdentity"
|
||||
app:layout_constraintTop_toTopOf="@id/btnIdentity" />
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorAccount" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvIdentity"
|
||||
@@ -94,6 +80,7 @@
|
||||
android:id="@+id/tvIdentityDone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/baseline_check_24"
|
||||
android:text="@string/title_setup_done"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
@@ -101,18 +88,26 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvIdentity" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vSeparatorIdentity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="9dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvIdentityDone" />
|
||||
|
||||
<!-- permissions -->
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPermissions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="9dp"
|
||||
android:text="@string/title_setup_permissions"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvIdentityDone" />
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorIdentity" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPermissions"
|
||||
@@ -128,37 +123,103 @@
|
||||
android:id="@+id/tvPermissionsDone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_setup_to_do"
|
||||
android:drawableStart="@drawable/baseline_check_24"
|
||||
android:text="@string/title_setup_done"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvPermissions" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbDarkTheme"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/title_setup_dark_theme"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
<View
|
||||
android:id="@+id/vSeparatorPermissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="9dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvPermissionsDone" />
|
||||
|
||||
<!-- doze -->
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDoze"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="9dp"
|
||||
android:text="@string/title_setup_doze"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorPermissions" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDoze"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_setup_doze_remark"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnDoze" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDozeDone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/baseline_check_24"
|
||||
android:text="@string/title_setup_done"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvDoze" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vSeparatorDoze"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="9dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvDozeDone" />
|
||||
|
||||
<!-- data saver -->
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnData"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="9dp"
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/title_setup_data"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorDoze" />
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/tbDarkTheme"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="18dp"
|
||||
android:textOff="@string/title_setup_dark_theme"
|
||||
android:textOn="@string/title_setup_light_theme"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnData" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOptions"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="18dp"
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/title_advanced"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbDarkTheme" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tbDarkTheme" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -84,7 +84,7 @@
|
||||
android:layout_marginStart="6dp"
|
||||
android:text="error"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?attr/colorWarning"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvHost" />
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:background="?attr/colorButtonNormal"
|
||||
android:src="@drawable/baseline_edit_24"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/ivMessages"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -70,6 +71,16 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/ivState"
|
||||
app:layout_constraintTop_toTopOf="@id/ivState" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivUnified"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/baseline_folder_special_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/ivState"
|
||||
app:layout_constraintStart_toEndOf="@id/tvType"
|
||||
app:layout_constraintTop_toTopOf="@id/ivState" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAfter"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -98,7 +109,7 @@
|
||||
android:layout_marginStart="6dp"
|
||||
android:text="error"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?attr/colorWarning"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ivState" />
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
android:layout_marginStart="6dp"
|
||||
android:text="error"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?attr/colorWarning"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvHost" />
|
||||
|
||||
28
app/src/main/res/layout/item_log.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTime"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="start"
|
||||
android:singleLine="true"
|
||||
android:text="12:34:56"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/ivEdit" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvData"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:text="log"
|
||||
android:textAppearance="@android:style/TextAppearance.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/tvTime"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -5,6 +5,17 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/drawableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivFlagged"
|
||||
android:layout_width="21dp"
|
||||
android:layout_height="21dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/baseline_star_24"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvFrom"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/tvFrom" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFrom"
|
||||
android:layout_width="0dp"
|
||||
@@ -15,21 +26,10 @@
|
||||
android:maxLines="1"
|
||||
android:text="From"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvSize"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvTime"
|
||||
app:layout_constraintStart_toEndOf="@id/ivFlagged"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:maxLines="1"
|
||||
android:text="123K"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvFrom"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvTime" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTime"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -80,12 +80,22 @@
|
||||
android:id="@+id/tvCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginEnd="3dp"
|
||||
android:maxLines="1"
|
||||
android:text="3"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvSubject"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
app:layout_constraintEnd_toStartOf="@+id/ivThread" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivThread"
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:src="@drawable/baseline_message_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvSubject"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/tvSubject" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvError"
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_trash"
|
||||
android:id="@+id/action_delete"
|
||||
android:icon="@drawable/baseline_delete_24"
|
||||
android:title="@string/title_trash"
|
||||
android:title="@string/title_delete"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_trash"
|
||||
android:id="@+id/action_delete"
|
||||
android:icon="@drawable/baseline_delete_24"
|
||||
android:title="@string/title_trash"
|
||||
android:title="@string/title_delete"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_trash"
|
||||
android:id="@+id/action_delete"
|
||||
android:icon="@drawable/baseline_delete_24"
|
||||
android:title="@string/title_trash"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
@@ -13,9 +13,4 @@
|
||||
android:icon="@drawable/baseline_people_24"
|
||||
android:title="@string/title_show_addresses"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_encrypt"
|
||||
android:title="@string/title_encrypt"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
||||
@@ -12,13 +12,7 @@
|
||||
android:id="@+id/menu_thread"
|
||||
android:icon="@drawable/baseline_message_24"
|
||||
android:title="@string/title_thread"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_seen"
|
||||
android:icon="@drawable/baseline_visibility_off_24"
|
||||
android:title="@string/title_seen"
|
||||
app:showAsAction="never" />
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_forward"
|
||||
@@ -32,14 +26,22 @@
|
||||
android:title="@string/title_reply_all"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_show_headers"
|
||||
android:checkable="true"
|
||||
android:icon="@drawable/baseline_visibility_24"
|
||||
android:title="@string/title_show_headers"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_show_html"
|
||||
android:icon="@drawable/baseline_visibility_24"
|
||||
android:title="@string/title_show_html"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_answer"
|
||||
android:icon="@drawable/baseline_reply_24"
|
||||
android:title="@string/title_answer_reply"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_decrypt"
|
||||
android:title="@string/title_decrypt"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -26,10 +26,11 @@
|
||||
<string name="menu_answers">Standard replies</string>
|
||||
<string name="menu_operations">Operations</string>
|
||||
<string name="menu_legend">Legend</string>
|
||||
<string name="menu_faq">FAQ</string>
|
||||
<string name="menu_faq">FAQ/support</string>
|
||||
<string name="menu_pro">Pro features</string>
|
||||
<string name="menu_privacy">Privacy</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="menu_rate">Rate this app</string>
|
||||
<string name="menu_other">Other apps</string>
|
||||
<string name="title_eula">End-user license agreement</string>
|
||||
<string name="title_agree">I agree</string>
|
||||
@@ -45,10 +46,15 @@
|
||||
<string name="title_setup_account_remark">To receive email</string>
|
||||
<string name="title_setup_identity">Manage identities</string>
|
||||
<string name="title_setup_identity_remark">To send email</string>
|
||||
<string name="title_setup_doze">Disable battery optimizations</string>
|
||||
<string name="title_setup_doze_remark">To continuously receive email (optional)</string>
|
||||
<string name="title_setup_doze_instructions">In the next dialog, select \"All apps\" at the top, select this app and select and confirm \"Don\'t optimize\"</string>
|
||||
<string name="title_setup_data">Disable data saving</string>
|
||||
<string name="title_setup_permissions">Grant permissions</string>
|
||||
<string name="title_setup_permissions_remark">To autocomplete addresses (optional)</string>
|
||||
<string name="title_setup_to_do">To do</string>
|
||||
<string name="title_setup_done">Done</string>
|
||||
<string name="title_setup_light_theme">Light theme</string>
|
||||
<string name="title_setup_dark_theme">Dark theme</string>
|
||||
<string name="title_advanced">Advanced options</string>
|
||||
<string name="title_advanced_webview">Use WebView to show external links</string>
|
||||
@@ -64,6 +70,7 @@
|
||||
<string name="title_account_linked">Linked account</string>
|
||||
<string name="title_account_name">Account name</string>
|
||||
<string name="title_account_name_hint">Used to differentiate folders</string>
|
||||
<string name="title_account_signature">Signature text</string>
|
||||
<string name="title_imap">IMAP</string>
|
||||
<string name="title_smtp">SMTP</string>
|
||||
<string name="title_provider">Provider</string>
|
||||
@@ -74,9 +81,9 @@
|
||||
<string name="title_user">User name</string>
|
||||
<string name="title_password">Password</string>
|
||||
<string name="title_authorize">Select account</string>
|
||||
<string name="title_instructions">Instructions</string>
|
||||
<string name="title_authorizing">Authorizing account …</string>
|
||||
<string name="title_setup_advanced">Advanced</string>
|
||||
<string name="title_store_sent">Store sent messages (enable if needed only)</string>
|
||||
<string name="title_interval">Poll/keep-alive interval (minutes)</string>
|
||||
<string name="title_synchronize_account">Synchronize (receive messages)</string>
|
||||
<string name="title_synchronize_identity">Synchronize (send messages)</string>
|
||||
<string name="title_primary_account">Primary (default account)</string>
|
||||
@@ -90,11 +97,14 @@
|
||||
<string name="title_no_user">User name missing</string>
|
||||
<string name="title_no_password">Password missing</string>
|
||||
<string name="title_no_drafts">Drafts folder missing</string>
|
||||
<string name="title_no_idle">IDLE not supported</string>
|
||||
<string name="title_no_uidplus">UIDPLUS not supported</string>
|
||||
<string name="title_no_idle">IMAP IDLE not supported</string>
|
||||
<string name="title_no_uidplus">IMAP UIDPLUS not supported</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
<string name="title_identity_delete">Delete this identity permanently?</string>
|
||||
<string name="title_pop">POP is not supported</string>
|
||||
<string name="title_insecure">Insecure connections are not supported</string>
|
||||
<string name="title_synchronize_folder">Synchronize (receive messages)</string>
|
||||
<string name="title_unified_folder">Show in unified inbox</string>
|
||||
<string name="title_after">Synchronize (days)</string>
|
||||
<string name="title_folder_unified">Unified inbox</string>
|
||||
<string name="title_folder_inbox">Inbox</string>
|
||||
@@ -106,19 +116,24 @@
|
||||
<string name="title_folder_sent">Sent</string>
|
||||
<string name="title_folder_user">User</string>
|
||||
<string name="title_folder_primary">Folders primary account</string>
|
||||
<string name="title_folder_thread">Message thread</string>
|
||||
<string name="title_folder_thread">Conversation</string>
|
||||
<string name="title_no_messages">No messages</string>
|
||||
<string name="title_link">link</string>
|
||||
<string name="title_image">image</string>
|
||||
<string name="title_show_images">Show images</string>
|
||||
<string name="title_subject_reply">Re: %1$s</string>
|
||||
<string name="title_subject_forward">Fwd: %1$s</string>
|
||||
<string name="title_thread">Show thread</string>
|
||||
<string name="title_thread">Show conversation</string>
|
||||
<string name="title_seen">Mark read</string>
|
||||
<string name="title_unseen">Mark unread</string>
|
||||
<string name="title_flag">Add star</string>
|
||||
<string name="title_unflag">Remove star</string>
|
||||
<string name="title_forward">Forward</string>
|
||||
<string name="title_reply_all">Reply to all</string>
|
||||
<string name="title_show_headers">Show headers</string>
|
||||
<string name="title_show_html">Show original</string>
|
||||
<string name="title_trash">Trash</string>
|
||||
<string name="title_delete">Delete</string>
|
||||
<string name="title_spam">Spam</string>
|
||||
<string name="title_move">Move</string>
|
||||
<string name="title_archive">Archive</string>
|
||||
@@ -140,33 +155,34 @@
|
||||
<string name="title_send">Send</string>
|
||||
<string name="title_show_addresses">Show CC/BCC</string>
|
||||
<string name="title_add_attachment">Add attachment</string>
|
||||
<string name="title_no_openpgp">OpenPGP not available</string>
|
||||
<string name="title_not_encrypted">Encrypted message not found</string>
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
<string name="title_from_missing">Sender missing</string>
|
||||
<string name="title_to_missing">Recipient missing</string>
|
||||
<string name="title_attachments_missing">Attachments still loading</string>
|
||||
<string name="title_draft_trashed">Draft trashed</string>
|
||||
<string name="title_draft_deleted">Draft deleted</string>
|
||||
<string name="title_draft_saved">Draft saved</string>
|
||||
<string name="title_queued">Sending message</string>
|
||||
<string name="title_search">Search</string>
|
||||
<string name="title_search_hint">Search on server</string>
|
||||
<string name="title_search_hint">Search sender/subject/text</string>
|
||||
<string name="title_searching">Searching \'%1$s\'</string>
|
||||
<string name="title_answer_reply">Standard reply</string>
|
||||
<string name="title_answer_name">Answer name</string>
|
||||
<string name="title_answer_text">Answer text</string>
|
||||
<string name="title_legend_cc">CC/BCC</string>
|
||||
<string name="title_legend_attachment">Attachment</string>
|
||||
<string name="title_legend_thread">Conversation</string>
|
||||
<string name="title_legend_synchronize">Synchronize</string>
|
||||
<string name="title_legend_primary">Primary</string>
|
||||
<string name="title_legend_unified">Unified inbox</string>
|
||||
<string name="title_legend_disconnected">Disconnected</string>
|
||||
<string name="title_legend_connecting">Connecting</string>
|
||||
<string name="title_legend_connected">Connected</string>
|
||||
<string name="title_legend_synchronizing">Synchronizing</string>
|
||||
<string name="title_legend_closing">Closing</string>
|
||||
<string name="title_hint_swipe">Swipe left to trash and swipe right to archive (if available)</string>
|
||||
<string name="title_hint_actions">Swipe left to trash; swipe right to archive (if available); long press to mark read/unread</string>
|
||||
<string name="title_understood">Understood</string>
|
||||
<string name="title_issue">Do you have a question or problem?</string>
|
||||
<string name="title_yes">Yes</string>
|
||||
<string name="title_no">No</string>
|
||||
<string name="title_pro_feature">This is a pro feature</string>
|
||||
<string name="title_pro_list">List of pro features</string>
|
||||
<string name="title_pro_purchase">Buy</string>
|
||||
@@ -174,6 +190,7 @@
|
||||
<string name="title_pro_activated">All pro features are activated</string>
|
||||
<string name="title_pro_valid">All pro features activated</string>
|
||||
<string name="title_pro_invalid">Invalid response</string>
|
||||
<string name="title_log">Log</string>
|
||||
<string name="title_debug_info">Debug info</string>
|
||||
<string name="title_debug_info_remark">Please describe the problem and indicate the time of the problem:</string>
|
||||
</resources>
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
<string name="menu_answers">Standard replies</string>
|
||||
<string name="menu_operations">Operations</string>
|
||||
<string name="menu_legend">Legend</string>
|
||||
<string name="menu_faq">FAQ</string>
|
||||
<string name="menu_faq">FAQ/support</string>
|
||||
<string name="menu_pro">Pro features</string>
|
||||
<string name="menu_privacy">Privacy</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="menu_rate">Rate this app</string>
|
||||
<string name="menu_other">Other apps</string>
|
||||
<string name="title_eula">End-user license agreement</string>
|
||||
<string name="title_agree">I agree</string>
|
||||
@@ -61,10 +62,15 @@
|
||||
<string name="title_setup_account_remark">To receive email</string>
|
||||
<string name="title_setup_identity">Manage identities</string>
|
||||
<string name="title_setup_identity_remark">To send email</string>
|
||||
<string name="title_setup_doze">Disable battery optimizations</string>
|
||||
<string name="title_setup_doze_remark">To continuously receive email (optional)</string>
|
||||
<string name="title_setup_doze_instructions">In the next dialog, select \"All apps\" at the top, select this app and select and confirm \"Don\'t optimize\"</string>
|
||||
<string name="title_setup_data">Disable data saving</string>
|
||||
<string name="title_setup_permissions">Grant permissions</string>
|
||||
<string name="title_setup_permissions_remark">To autocomplete addresses (optional)</string>
|
||||
<string name="title_setup_to_do">To do</string>
|
||||
<string name="title_setup_done">Done</string>
|
||||
<string name="title_setup_light_theme">Light theme</string>
|
||||
<string name="title_setup_dark_theme">Dark theme</string>
|
||||
<string name="title_advanced">Advanced options</string>
|
||||
<string name="title_advanced_webview">Use WebView to show external links</string>
|
||||
@@ -80,6 +86,7 @@
|
||||
<string name="title_account_linked">Linked account</string>
|
||||
<string name="title_account_name">Account name</string>
|
||||
<string name="title_account_name_hint">Used to differentiate folders</string>
|
||||
<string name="title_account_signature">Signature text</string>
|
||||
<string name="title_imap">IMAP</string>
|
||||
<string name="title_smtp">SMTP</string>
|
||||
<string name="title_provider">Provider</string>
|
||||
@@ -90,9 +97,9 @@
|
||||
<string name="title_user">User name</string>
|
||||
<string name="title_password">Password</string>
|
||||
<string name="title_authorize">Select account</string>
|
||||
<string name="title_instructions">Instructions</string>
|
||||
<string name="title_authorizing">Authorizing account …</string>
|
||||
<string name="title_setup_advanced">Advanced</string>
|
||||
<string name="title_store_sent">Store sent messages (enable if needed only)</string>
|
||||
<string name="title_interval">Poll/keep-alive interval (minutes)</string>
|
||||
<string name="title_synchronize_account">Synchronize (receive messages)</string>
|
||||
<string name="title_synchronize_identity">Synchronize (send messages)</string>
|
||||
<string name="title_primary_account">Primary (default account)</string>
|
||||
@@ -106,11 +113,14 @@
|
||||
<string name="title_no_user">User name missing</string>
|
||||
<string name="title_no_password">Password missing</string>
|
||||
<string name="title_no_drafts">Drafts folder missing</string>
|
||||
<string name="title_no_idle">IDLE not supported</string>
|
||||
<string name="title_no_uidplus">UIDPLUS not supported</string>
|
||||
<string name="title_no_idle">IMAP IDLE not supported</string>
|
||||
<string name="title_no_uidplus">IMAP UIDPLUS not supported</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
<string name="title_identity_delete">Delete this identity permanently?</string>
|
||||
<string name="title_pop">POP is not supported</string>
|
||||
<string name="title_insecure">Insecure connections are not supported</string>
|
||||
<string name="title_synchronize_folder">Synchronize (receive messages)</string>
|
||||
<string name="title_unified_folder">Show in unified inbox</string>
|
||||
<string name="title_after">Synchronize (days)</string>
|
||||
<string name="title_folder_unified">Unified inbox</string>
|
||||
<string name="title_folder_inbox">Inbox</string>
|
||||
@@ -122,19 +132,24 @@
|
||||
<string name="title_folder_sent">Sent</string>
|
||||
<string name="title_folder_user">User</string>
|
||||
<string name="title_folder_primary">Folders primary account</string>
|
||||
<string name="title_folder_thread">Message thread</string>
|
||||
<string name="title_folder_thread">Conversation</string>
|
||||
<string name="title_no_messages">No messages</string>
|
||||
<string name="title_link">link</string>
|
||||
<string name="title_image">image</string>
|
||||
<string name="title_show_images">Show images</string>
|
||||
<string name="title_subject_reply">Re: %1$s</string>
|
||||
<string name="title_subject_forward">Fwd: %1$s</string>
|
||||
<string name="title_thread">Show thread</string>
|
||||
<string name="title_thread">Show conversation</string>
|
||||
<string name="title_seen">Mark read</string>
|
||||
<string name="title_unseen">Mark unread</string>
|
||||
<string name="title_flag">Add star</string>
|
||||
<string name="title_unflag">Remove star</string>
|
||||
<string name="title_forward">Forward</string>
|
||||
<string name="title_reply_all">Reply to all</string>
|
||||
<string name="title_show_headers">Show headers</string>
|
||||
<string name="title_show_html">Show original</string>
|
||||
<string name="title_trash">Trash</string>
|
||||
<string name="title_delete">Delete</string>
|
||||
<string name="title_spam">Spam</string>
|
||||
<string name="title_move">Move</string>
|
||||
<string name="title_archive">Archive</string>
|
||||
@@ -156,33 +171,34 @@
|
||||
<string name="title_send">Send</string>
|
||||
<string name="title_show_addresses">Show CC/BCC</string>
|
||||
<string name="title_add_attachment">Add attachment</string>
|
||||
<string name="title_no_openpgp">OpenPGP not available</string>
|
||||
<string name="title_not_encrypted">Encrypted message not found</string>
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
<string name="title_from_missing">Sender missing</string>
|
||||
<string name="title_to_missing">Recipient missing</string>
|
||||
<string name="title_attachments_missing">Attachments still loading</string>
|
||||
<string name="title_draft_trashed">Draft trashed</string>
|
||||
<string name="title_draft_deleted">Draft deleted</string>
|
||||
<string name="title_draft_saved">Draft saved</string>
|
||||
<string name="title_queued">Sending message</string>
|
||||
<string name="title_search">Search</string>
|
||||
<string name="title_search_hint">Search on server</string>
|
||||
<string name="title_search_hint">Search sender/subject/text</string>
|
||||
<string name="title_searching">Searching \'%1$s\'</string>
|
||||
<string name="title_answer_reply">Standard reply</string>
|
||||
<string name="title_answer_name">Answer name</string>
|
||||
<string name="title_answer_text">Answer text</string>
|
||||
<string name="title_legend_cc">CC/BCC</string>
|
||||
<string name="title_legend_attachment">Attachment</string>
|
||||
<string name="title_legend_thread">Conversation</string>
|
||||
<string name="title_legend_synchronize">Synchronize</string>
|
||||
<string name="title_legend_primary">Primary</string>
|
||||
<string name="title_legend_unified">Unified inbox</string>
|
||||
<string name="title_legend_disconnected">Disconnected</string>
|
||||
<string name="title_legend_connecting">Connecting</string>
|
||||
<string name="title_legend_connected">Connected</string>
|
||||
<string name="title_legend_synchronizing">Synchronizing</string>
|
||||
<string name="title_legend_closing">Closing</string>
|
||||
<string name="title_hint_swipe">Swipe left to trash and swipe right to archive (if available)</string>
|
||||
<string name="title_hint_actions">Swipe left to trash; swipe right to archive (if available); long press to mark read/unread</string>
|
||||
<string name="title_understood">Understood</string>
|
||||
<string name="title_issue">Do you have a question or problem?</string>
|
||||
<string name="title_yes">Yes</string>
|
||||
<string name="title_no">No</string>
|
||||
<string name="title_pro_feature">This is a pro feature</string>
|
||||
<string name="title_pro_list">List of pro features</string>
|
||||
<string name="title_pro_purchase">Buy</string>
|
||||
@@ -190,6 +206,7 @@
|
||||
<string name="title_pro_activated">All pro features are activated</string>
|
||||
<string name="title_pro_valid">All pro features activated</string>
|
||||
<string name="title_pro_invalid">Invalid response</string>
|
||||
<string name="title_log">Log</string>
|
||||
<string name="title_debug_info">Debug info</string>
|
||||
<string name="title_debug_info_remark">Please describe the problem and indicate the time of the problem:</string>
|
||||
</resources>
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
<string name="menu_answers">Standard replies</string>
|
||||
<string name="menu_operations">Operations</string>
|
||||
<string name="menu_legend">Legend</string>
|
||||
<string name="menu_faq">FAQ</string>
|
||||
<string name="menu_faq">FAQ/support</string>
|
||||
<string name="menu_pro">Pro features</string>
|
||||
<string name="menu_privacy">Privacy</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="menu_rate">Rate this app</string>
|
||||
<string name="menu_other">Other apps</string>
|
||||
<string name="title_eula">End-user license agreement</string>
|
||||
<string name="title_agree">I agree</string>
|
||||
@@ -61,10 +62,15 @@
|
||||
<string name="title_setup_account_remark">To receive email</string>
|
||||
<string name="title_setup_identity">Manage identities</string>
|
||||
<string name="title_setup_identity_remark">To send email</string>
|
||||
<string name="title_setup_doze">Disable battery optimizations</string>
|
||||
<string name="title_setup_doze_remark">To continuously receive email (optional)</string>
|
||||
<string name="title_setup_doze_instructions">In the next dialog, select \"All apps\" at the top, select this app and select and confirm \"Don\'t optimize\"</string>
|
||||
<string name="title_setup_data">Disable data saving</string>
|
||||
<string name="title_setup_permissions">Grant permissions</string>
|
||||
<string name="title_setup_permissions_remark">To autocomplete addresses (optional)</string>
|
||||
<string name="title_setup_to_do">To do</string>
|
||||
<string name="title_setup_done">Done</string>
|
||||
<string name="title_setup_light_theme">Light theme</string>
|
||||
<string name="title_setup_dark_theme">Dark theme</string>
|
||||
<string name="title_advanced">Advanced options</string>
|
||||
<string name="title_advanced_webview">Use WebView to show external links</string>
|
||||
@@ -80,6 +86,7 @@
|
||||
<string name="title_account_linked">Linked account</string>
|
||||
<string name="title_account_name">Account name</string>
|
||||
<string name="title_account_name_hint">Used to differentiate folders</string>
|
||||
<string name="title_account_signature">Signature text</string>
|
||||
<string name="title_imap">IMAP</string>
|
||||
<string name="title_smtp">SMTP</string>
|
||||
<string name="title_provider">Provider</string>
|
||||
@@ -90,9 +97,9 @@
|
||||
<string name="title_user">User name</string>
|
||||
<string name="title_password">Password</string>
|
||||
<string name="title_authorize">Select account</string>
|
||||
<string name="title_instructions">Instructions</string>
|
||||
<string name="title_authorizing">Authorizing account …</string>
|
||||
<string name="title_setup_advanced">Advanced</string>
|
||||
<string name="title_store_sent">Store sent messages (enable if needed only)</string>
|
||||
<string name="title_interval">Poll/keep-alive interval (minutes)</string>
|
||||
<string name="title_synchronize_account">Synchronize (receive messages)</string>
|
||||
<string name="title_synchronize_identity">Synchronize (send messages)</string>
|
||||
<string name="title_primary_account">Primary (default account)</string>
|
||||
@@ -106,11 +113,14 @@
|
||||
<string name="title_no_user">User name missing</string>
|
||||
<string name="title_no_password">Password missing</string>
|
||||
<string name="title_no_drafts">Drafts folder missing</string>
|
||||
<string name="title_no_idle">IDLE not supported</string>
|
||||
<string name="title_no_uidplus">UIDPLUS not supported</string>
|
||||
<string name="title_no_idle">IMAP IDLE not supported</string>
|
||||
<string name="title_no_uidplus">IMAP UIDPLUS not supported</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
<string name="title_identity_delete">Delete this identity permanently?</string>
|
||||
<string name="title_pop">POP is not supported</string>
|
||||
<string name="title_insecure">Insecure connections are not supported</string>
|
||||
<string name="title_synchronize_folder">Synchronize (receive messages)</string>
|
||||
<string name="title_unified_folder">Show in unified inbox</string>
|
||||
<string name="title_after">Synchronize (days)</string>
|
||||
<string name="title_folder_unified">Unified inbox</string>
|
||||
<string name="title_folder_inbox">Inbox</string>
|
||||
@@ -122,19 +132,24 @@
|
||||
<string name="title_folder_sent">Sent</string>
|
||||
<string name="title_folder_user">User</string>
|
||||
<string name="title_folder_primary">Folders primary account</string>
|
||||
<string name="title_folder_thread">Message thread</string>
|
||||
<string name="title_folder_thread">Conversation</string>
|
||||
<string name="title_no_messages">No messages</string>
|
||||
<string name="title_link">link</string>
|
||||
<string name="title_image">image</string>
|
||||
<string name="title_show_images">Show images</string>
|
||||
<string name="title_subject_reply">Re: %1$s</string>
|
||||
<string name="title_subject_forward">Fwd: %1$s</string>
|
||||
<string name="title_thread">Show thread</string>
|
||||
<string name="title_thread">Show conversation</string>
|
||||
<string name="title_seen">Mark read</string>
|
||||
<string name="title_unseen">Mark unread</string>
|
||||
<string name="title_flag">Add star</string>
|
||||
<string name="title_unflag">Remove star</string>
|
||||
<string name="title_forward">Forward</string>
|
||||
<string name="title_reply_all">Reply to all</string>
|
||||
<string name="title_show_headers">Show headers</string>
|
||||
<string name="title_show_html">Show original</string>
|
||||
<string name="title_trash">Trash</string>
|
||||
<string name="title_delete">Delete</string>
|
||||
<string name="title_spam">Spam</string>
|
||||
<string name="title_move">Move</string>
|
||||
<string name="title_archive">Archive</string>
|
||||
@@ -156,33 +171,34 @@
|
||||
<string name="title_send">Send</string>
|
||||
<string name="title_show_addresses">Show CC/BCC</string>
|
||||
<string name="title_add_attachment">Add attachment</string>
|
||||
<string name="title_no_openpgp">OpenPGP not available</string>
|
||||
<string name="title_not_encrypted">Encrypted message not found</string>
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
<string name="title_from_missing">Sender missing</string>
|
||||
<string name="title_to_missing">Recipient missing</string>
|
||||
<string name="title_attachments_missing">Attachments still loading</string>
|
||||
<string name="title_draft_trashed">Draft trashed</string>
|
||||
<string name="title_draft_deleted">Draft deleted</string>
|
||||
<string name="title_draft_saved">Draft saved</string>
|
||||
<string name="title_queued">Sending message</string>
|
||||
<string name="title_search">Search</string>
|
||||
<string name="title_search_hint">Search on server</string>
|
||||
<string name="title_search_hint">Search sender/subject/text</string>
|
||||
<string name="title_searching">Searching \'%1$s\'</string>
|
||||
<string name="title_answer_reply">Standard reply</string>
|
||||
<string name="title_answer_name">Answer name</string>
|
||||
<string name="title_answer_text">Answer text</string>
|
||||
<string name="title_legend_cc">CC/BCC</string>
|
||||
<string name="title_legend_attachment">Attachment</string>
|
||||
<string name="title_legend_thread">Conversation</string>
|
||||
<string name="title_legend_synchronize">Synchronize</string>
|
||||
<string name="title_legend_primary">Primary</string>
|
||||
<string name="title_legend_unified">Unified inbox</string>
|
||||
<string name="title_legend_disconnected">Disconnected</string>
|
||||
<string name="title_legend_connecting">Connecting</string>
|
||||
<string name="title_legend_connected">Connected</string>
|
||||
<string name="title_legend_synchronizing">Synchronizing</string>
|
||||
<string name="title_legend_closing">Closing</string>
|
||||
<string name="title_hint_swipe">Swipe left to trash and swipe right to archive (if available)</string>
|
||||
<string name="title_hint_actions">Swipe left to trash; swipe right to archive (if available); long press to mark read/unread</string>
|
||||
<string name="title_understood">Understood</string>
|
||||
<string name="title_issue">Do you have a question or problem?</string>
|
||||
<string name="title_yes">Yes</string>
|
||||
<string name="title_no">No</string>
|
||||
<string name="title_pro_feature">This is a pro feature</string>
|
||||
<string name="title_pro_list">List of pro features</string>
|
||||
<string name="title_pro_purchase">Buy</string>
|
||||
@@ -190,6 +206,7 @@
|
||||
<string name="title_pro_activated">All pro features are activated</string>
|
||||
<string name="title_pro_valid">All pro features activated</string>
|
||||
<string name="title_pro_invalid">Invalid response</string>
|
||||
<string name="title_log">Log</string>
|
||||
<string name="title_debug_info">Debug info</string>
|
||||
<string name="title_debug_info_remark">Please describe the problem and indicate the time of the problem:</string>
|
||||
</resources>
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
<string name="menu_answers">Standard replies</string>
|
||||
<string name="menu_operations">Operations</string>
|
||||
<string name="menu_legend">Legend</string>
|
||||
<string name="menu_faq">FAQ</string>
|
||||
<string name="menu_faq">FAQ/support</string>
|
||||
<string name="menu_pro">Pro features</string>
|
||||
<string name="menu_privacy">Privacy</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="menu_rate">Rate this app</string>
|
||||
<string name="menu_other">Other apps</string>
|
||||
<string name="title_eula">End-user license agreement</string>
|
||||
<string name="title_agree">I agree</string>
|
||||
@@ -61,10 +62,15 @@
|
||||
<string name="title_setup_account_remark">To receive email</string>
|
||||
<string name="title_setup_identity">Manage identities</string>
|
||||
<string name="title_setup_identity_remark">To send email</string>
|
||||
<string name="title_setup_doze">Disable battery optimizations</string>
|
||||
<string name="title_setup_doze_remark">To continuously receive email (optional)</string>
|
||||
<string name="title_setup_doze_instructions">In the next dialog, select \"All apps\" at the top, select this app and select and confirm \"Don\'t optimize\"</string>
|
||||
<string name="title_setup_data">Disable data saving</string>
|
||||
<string name="title_setup_permissions">Grant permissions</string>
|
||||
<string name="title_setup_permissions_remark">To autocomplete addresses (optional)</string>
|
||||
<string name="title_setup_to_do">To do</string>
|
||||
<string name="title_setup_done">Done</string>
|
||||
<string name="title_setup_light_theme">Light theme</string>
|
||||
<string name="title_setup_dark_theme">Dark theme</string>
|
||||
<string name="title_advanced">Advanced options</string>
|
||||
<string name="title_advanced_webview">Use WebView to show external links</string>
|
||||
@@ -80,6 +86,7 @@
|
||||
<string name="title_account_linked">Linked account</string>
|
||||
<string name="title_account_name">Account name</string>
|
||||
<string name="title_account_name_hint">Used to differentiate folders</string>
|
||||
<string name="title_account_signature">Signature text</string>
|
||||
<string name="title_imap">IMAP</string>
|
||||
<string name="title_smtp">SMTP</string>
|
||||
<string name="title_provider">Provider</string>
|
||||
@@ -90,9 +97,9 @@
|
||||
<string name="title_user">User name</string>
|
||||
<string name="title_password">Password</string>
|
||||
<string name="title_authorize">Select account</string>
|
||||
<string name="title_instructions">Instructions</string>
|
||||
<string name="title_authorizing">Authorizing account …</string>
|
||||
<string name="title_setup_advanced">Advanced</string>
|
||||
<string name="title_store_sent">Store sent messages (enable if needed only)</string>
|
||||
<string name="title_interval">Poll/keep-alive interval (minutes)</string>
|
||||
<string name="title_synchronize_account">Synchronize (receive messages)</string>
|
||||
<string name="title_synchronize_identity">Synchronize (send messages)</string>
|
||||
<string name="title_primary_account">Primary (default account)</string>
|
||||
@@ -106,11 +113,14 @@
|
||||
<string name="title_no_user">User name missing</string>
|
||||
<string name="title_no_password">Password missing</string>
|
||||
<string name="title_no_drafts">Drafts folder missing</string>
|
||||
<string name="title_no_idle">IDLE not supported</string>
|
||||
<string name="title_no_uidplus">UIDPLUS not supported</string>
|
||||
<string name="title_no_idle">IMAP IDLE not supported</string>
|
||||
<string name="title_no_uidplus">IMAP UIDPLUS not supported</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
<string name="title_identity_delete">Delete this identity permanently?</string>
|
||||
<string name="title_pop">POP is not supported</string>
|
||||
<string name="title_insecure">Insecure connections are not supported</string>
|
||||
<string name="title_synchronize_folder">Synchronize (receive messages)</string>
|
||||
<string name="title_unified_folder">Show in unified inbox</string>
|
||||
<string name="title_after">Synchronize (days)</string>
|
||||
<string name="title_folder_unified">Unified inbox</string>
|
||||
<string name="title_folder_inbox">Inbox</string>
|
||||
@@ -122,19 +132,24 @@
|
||||
<string name="title_folder_sent">Sent</string>
|
||||
<string name="title_folder_user">User</string>
|
||||
<string name="title_folder_primary">Folders primary account</string>
|
||||
<string name="title_folder_thread">Message thread</string>
|
||||
<string name="title_folder_thread">Conversation</string>
|
||||
<string name="title_no_messages">No messages</string>
|
||||
<string name="title_link">link</string>
|
||||
<string name="title_image">image</string>
|
||||
<string name="title_show_images">Show images</string>
|
||||
<string name="title_subject_reply">Re: %1$s</string>
|
||||
<string name="title_subject_forward">Fwd: %1$s</string>
|
||||
<string name="title_thread">Show thread</string>
|
||||
<string name="title_thread">Show conversation</string>
|
||||
<string name="title_seen">Mark read</string>
|
||||
<string name="title_unseen">Mark unread</string>
|
||||
<string name="title_flag">Add star</string>
|
||||
<string name="title_unflag">Remove star</string>
|
||||
<string name="title_forward">Forward</string>
|
||||
<string name="title_reply_all">Reply to all</string>
|
||||
<string name="title_show_headers">Show headers</string>
|
||||
<string name="title_show_html">Show original</string>
|
||||
<string name="title_trash">Trash</string>
|
||||
<string name="title_delete">Delete</string>
|
||||
<string name="title_spam">Spam</string>
|
||||
<string name="title_move">Move</string>
|
||||
<string name="title_archive">Archive</string>
|
||||
@@ -156,33 +171,34 @@
|
||||
<string name="title_send">Send</string>
|
||||
<string name="title_show_addresses">Show CC/BCC</string>
|
||||
<string name="title_add_attachment">Add attachment</string>
|
||||
<string name="title_no_openpgp">OpenPGP not available</string>
|
||||
<string name="title_not_encrypted">Encrypted message not found</string>
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
<string name="title_from_missing">Sender missing</string>
|
||||
<string name="title_to_missing">Recipient missing</string>
|
||||
<string name="title_attachments_missing">Attachments still loading</string>
|
||||
<string name="title_draft_trashed">Draft trashed</string>
|
||||
<string name="title_draft_deleted">Draft deleted</string>
|
||||
<string name="title_draft_saved">Draft saved</string>
|
||||
<string name="title_queued">Sending message</string>
|
||||
<string name="title_search">Search</string>
|
||||
<string name="title_search_hint">Search on server</string>
|
||||
<string name="title_search_hint">Search sender/subject/text</string>
|
||||
<string name="title_searching">Searching \'%1$s\'</string>
|
||||
<string name="title_answer_reply">Standard reply</string>
|
||||
<string name="title_answer_name">Answer name</string>
|
||||
<string name="title_answer_text">Answer text</string>
|
||||
<string name="title_legend_cc">CC/BCC</string>
|
||||
<string name="title_legend_attachment">Attachment</string>
|
||||
<string name="title_legend_thread">Conversation</string>
|
||||
<string name="title_legend_synchronize">Synchronize</string>
|
||||
<string name="title_legend_primary">Primary</string>
|
||||
<string name="title_legend_unified">Unified inbox</string>
|
||||
<string name="title_legend_disconnected">Disconnected</string>
|
||||
<string name="title_legend_connecting">Connecting</string>
|
||||
<string name="title_legend_connected">Connected</string>
|
||||
<string name="title_legend_synchronizing">Synchronizing</string>
|
||||
<string name="title_legend_closing">Closing</string>
|
||||
<string name="title_hint_swipe">Swipe left to trash and swipe right to archive (if available)</string>
|
||||
<string name="title_hint_actions">Swipe left to trash; swipe right to archive (if available); long press to mark read/unread</string>
|
||||
<string name="title_understood">Understood</string>
|
||||
<string name="title_issue">Do you have a question or problem?</string>
|
||||
<string name="title_yes">Yes</string>
|
||||
<string name="title_no">No</string>
|
||||
<string name="title_pro_feature">This is a pro feature</string>
|
||||
<string name="title_pro_list">List of pro features</string>
|
||||
<string name="title_pro_purchase">Buy</string>
|
||||
@@ -190,6 +206,7 @@
|
||||
<string name="title_pro_activated">All pro features are activated</string>
|
||||
<string name="title_pro_valid">All pro features activated</string>
|
||||
<string name="title_pro_invalid">Invalid response</string>
|
||||
<string name="title_log">Log</string>
|
||||
<string name="title_debug_info">Debug info</string>
|
||||
<string name="title_debug_info_remark">Please describe the problem and indicate the time of the problem:</string>
|
||||
</resources>
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
<string name="menu_answers">Standard replies</string>
|
||||
<string name="menu_operations">Operations</string>
|
||||
<string name="menu_legend">Legend</string>
|
||||
<string name="menu_faq">FAQ</string>
|
||||
<string name="menu_faq">FAQ/support</string>
|
||||
<string name="menu_pro">Pro features</string>
|
||||
<string name="menu_privacy">Privacy</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="menu_rate">Rate this app</string>
|
||||
<string name="menu_other">Other apps</string>
|
||||
<string name="title_eula">End-user license agreement</string>
|
||||
<string name="title_agree">I agree</string>
|
||||
@@ -61,10 +62,15 @@
|
||||
<string name="title_setup_account_remark">To receive email</string>
|
||||
<string name="title_setup_identity">Manage identities</string>
|
||||
<string name="title_setup_identity_remark">To send email</string>
|
||||
<string name="title_setup_doze">Disable battery optimizations</string>
|
||||
<string name="title_setup_doze_remark">To continuously receive email (optional)</string>
|
||||
<string name="title_setup_doze_instructions">In the next dialog, select \"All apps\" at the top, select this app and select and confirm \"Don\'t optimize\"</string>
|
||||
<string name="title_setup_data">Disable data saving</string>
|
||||
<string name="title_setup_permissions">Grant permissions</string>
|
||||
<string name="title_setup_permissions_remark">To autocomplete addresses (optional)</string>
|
||||
<string name="title_setup_to_do">To do</string>
|
||||
<string name="title_setup_done">Done</string>
|
||||
<string name="title_setup_light_theme">Light theme</string>
|
||||
<string name="title_setup_dark_theme">Dark theme</string>
|
||||
<string name="title_advanced">Advanced options</string>
|
||||
<string name="title_advanced_webview">Use WebView to show external links</string>
|
||||
@@ -80,6 +86,7 @@
|
||||
<string name="title_account_linked">Linked account</string>
|
||||
<string name="title_account_name">Account name</string>
|
||||
<string name="title_account_name_hint">Used to differentiate folders</string>
|
||||
<string name="title_account_signature">Signature text</string>
|
||||
<string name="title_imap">IMAP</string>
|
||||
<string name="title_smtp">SMTP</string>
|
||||
<string name="title_provider">Provider</string>
|
||||
@@ -90,9 +97,9 @@
|
||||
<string name="title_user">User name</string>
|
||||
<string name="title_password">Password</string>
|
||||
<string name="title_authorize">Select account</string>
|
||||
<string name="title_instructions">Instructions</string>
|
||||
<string name="title_authorizing">Authorizing account …</string>
|
||||
<string name="title_setup_advanced">Advanced</string>
|
||||
<string name="title_store_sent">Store sent messages (enable if needed only)</string>
|
||||
<string name="title_interval">Poll/keep-alive interval (minutes)</string>
|
||||
<string name="title_synchronize_account">Synchronize (receive messages)</string>
|
||||
<string name="title_synchronize_identity">Synchronize (send messages)</string>
|
||||
<string name="title_primary_account">Primary (default account)</string>
|
||||
@@ -106,11 +113,14 @@
|
||||
<string name="title_no_user">User name missing</string>
|
||||
<string name="title_no_password">Password missing</string>
|
||||
<string name="title_no_drafts">Drafts folder missing</string>
|
||||
<string name="title_no_idle">IDLE not supported</string>
|
||||
<string name="title_no_uidplus">UIDPLUS not supported</string>
|
||||
<string name="title_no_idle">IMAP IDLE not supported</string>
|
||||
<string name="title_no_uidplus">IMAP UIDPLUS not supported</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
<string name="title_identity_delete">Delete this identity permanently?</string>
|
||||
<string name="title_pop">POP is not supported</string>
|
||||
<string name="title_insecure">Insecure connections are not supported</string>
|
||||
<string name="title_synchronize_folder">Synchronize (receive messages)</string>
|
||||
<string name="title_unified_folder">Show in unified inbox</string>
|
||||
<string name="title_after">Synchronize (days)</string>
|
||||
<string name="title_folder_unified">Unified inbox</string>
|
||||
<string name="title_folder_inbox">Inbox</string>
|
||||
@@ -122,19 +132,24 @@
|
||||
<string name="title_folder_sent">Sent</string>
|
||||
<string name="title_folder_user">User</string>
|
||||
<string name="title_folder_primary">Folders primary account</string>
|
||||
<string name="title_folder_thread">Message thread</string>
|
||||
<string name="title_folder_thread">Conversation</string>
|
||||
<string name="title_no_messages">No messages</string>
|
||||
<string name="title_link">link</string>
|
||||
<string name="title_image">image</string>
|
||||
<string name="title_show_images">Show images</string>
|
||||
<string name="title_subject_reply">Re: %1$s</string>
|
||||
<string name="title_subject_forward">Fwd: %1$s</string>
|
||||
<string name="title_thread">Show thread</string>
|
||||
<string name="title_thread">Show conversation</string>
|
||||
<string name="title_seen">Mark read</string>
|
||||
<string name="title_unseen">Mark unread</string>
|
||||
<string name="title_flag">Add star</string>
|
||||
<string name="title_unflag">Remove star</string>
|
||||
<string name="title_forward">Forward</string>
|
||||
<string name="title_reply_all">Reply to all</string>
|
||||
<string name="title_show_headers">Show headers</string>
|
||||
<string name="title_show_html">Show original</string>
|
||||
<string name="title_trash">Trash</string>
|
||||
<string name="title_delete">Delete</string>
|
||||
<string name="title_spam">Spam</string>
|
||||
<string name="title_move">Move</string>
|
||||
<string name="title_archive">Archive</string>
|
||||
@@ -156,33 +171,34 @@
|
||||
<string name="title_send">Send</string>
|
||||
<string name="title_show_addresses">Show CC/BCC</string>
|
||||
<string name="title_add_attachment">Add attachment</string>
|
||||
<string name="title_no_openpgp">OpenPGP not available</string>
|
||||
<string name="title_not_encrypted">Encrypted message not found</string>
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
<string name="title_from_missing">Sender missing</string>
|
||||
<string name="title_to_missing">Recipient missing</string>
|
||||
<string name="title_attachments_missing">Attachments still loading</string>
|
||||
<string name="title_draft_trashed">Draft trashed</string>
|
||||
<string name="title_draft_deleted">Draft deleted</string>
|
||||
<string name="title_draft_saved">Draft saved</string>
|
||||
<string name="title_queued">Sending message</string>
|
||||
<string name="title_search">Search</string>
|
||||
<string name="title_search_hint">Search on server</string>
|
||||
<string name="title_search_hint">Search sender/subject/text</string>
|
||||
<string name="title_searching">Searching \'%1$s\'</string>
|
||||
<string name="title_answer_reply">Standard reply</string>
|
||||
<string name="title_answer_name">Answer name</string>
|
||||
<string name="title_answer_text">Answer text</string>
|
||||
<string name="title_legend_cc">CC/BCC</string>
|
||||
<string name="title_legend_attachment">Attachment</string>
|
||||
<string name="title_legend_thread">Conversation</string>
|
||||
<string name="title_legend_synchronize">Synchronize</string>
|
||||
<string name="title_legend_primary">Primary</string>
|
||||
<string name="title_legend_unified">Unified inbox</string>
|
||||
<string name="title_legend_disconnected">Disconnected</string>
|
||||
<string name="title_legend_connecting">Connecting</string>
|
||||
<string name="title_legend_connected">Connected</string>
|
||||
<string name="title_legend_synchronizing">Synchronizing</string>
|
||||
<string name="title_legend_closing">Closing</string>
|
||||
<string name="title_hint_swipe">Swipe left to trash and swipe right to archive (if available)</string>
|
||||
<string name="title_hint_actions">Swipe left to trash; swipe right to archive (if available); long press to mark read/unread</string>
|
||||
<string name="title_understood">Understood</string>
|
||||
<string name="title_issue">Do you have a question or problem?</string>
|
||||
<string name="title_yes">Yes</string>
|
||||
<string name="title_no">No</string>
|
||||
<string name="title_pro_feature">This is a pro feature</string>
|
||||
<string name="title_pro_list">List of pro features</string>
|
||||
<string name="title_pro_purchase">Buy</string>
|
||||
@@ -190,6 +206,7 @@
|
||||
<string name="title_pro_activated">All pro features are activated</string>
|
||||
<string name="title_pro_valid">All pro features activated</string>
|
||||
<string name="title_pro_invalid">Invalid response</string>
|
||||
<string name="title_log">Log</string>
|
||||
<string name="title_debug_info">Debug info</string>
|
||||
<string name="title_debug_info_remark">Please describe the problem and indicate the time of the problem:</string>
|
||||
</resources>
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
<string name="menu_answers">Standard replies</string>
|
||||
<string name="menu_operations">Operations</string>
|
||||
<string name="menu_legend">Legend</string>
|
||||
<string name="menu_faq">FAQ</string>
|
||||
<string name="menu_faq">FAQ/support</string>
|
||||
<string name="menu_pro">Pro features</string>
|
||||
<string name="menu_privacy">Privacy</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="menu_rate">Rate this app</string>
|
||||
<string name="menu_other">Other apps</string>
|
||||
<string name="title_eula">End-user license agreement</string>
|
||||
<string name="title_agree">I agree</string>
|
||||
@@ -61,10 +62,15 @@
|
||||
<string name="title_setup_account_remark">To receive email</string>
|
||||
<string name="title_setup_identity">Manage identities</string>
|
||||
<string name="title_setup_identity_remark">To send email</string>
|
||||
<string name="title_setup_doze">Disable battery optimizations</string>
|
||||
<string name="title_setup_doze_remark">To continuously receive email (optional)</string>
|
||||
<string name="title_setup_doze_instructions">In the next dialog, select \"All apps\" at the top, select this app and select and confirm \"Don\'t optimize\"</string>
|
||||
<string name="title_setup_data">Disable data saving</string>
|
||||
<string name="title_setup_permissions">Grant permissions</string>
|
||||
<string name="title_setup_permissions_remark">To autocomplete addresses (optional)</string>
|
||||
<string name="title_setup_to_do">To do</string>
|
||||
<string name="title_setup_done">Done</string>
|
||||
<string name="title_setup_light_theme">Light theme</string>
|
||||
<string name="title_setup_dark_theme">Dark theme</string>
|
||||
<string name="title_advanced">Advanced options</string>
|
||||
<string name="title_advanced_webview">Use WebView to show external links</string>
|
||||
@@ -80,6 +86,7 @@
|
||||
<string name="title_account_linked">Linked account</string>
|
||||
<string name="title_account_name">Account name</string>
|
||||
<string name="title_account_name_hint">Used to differentiate folders</string>
|
||||
<string name="title_account_signature">Signature text</string>
|
||||
<string name="title_imap">IMAP</string>
|
||||
<string name="title_smtp">SMTP</string>
|
||||
<string name="title_provider">Provider</string>
|
||||
@@ -90,9 +97,9 @@
|
||||
<string name="title_user">User name</string>
|
||||
<string name="title_password">Password</string>
|
||||
<string name="title_authorize">Select account</string>
|
||||
<string name="title_instructions">Instructions</string>
|
||||
<string name="title_authorizing">Authorizing account …</string>
|
||||
<string name="title_setup_advanced">Advanced</string>
|
||||
<string name="title_store_sent">Store sent messages (enable if needed only)</string>
|
||||
<string name="title_interval">Poll/keep-alive interval (minutes)</string>
|
||||
<string name="title_synchronize_account">Synchronize (receive messages)</string>
|
||||
<string name="title_synchronize_identity">Synchronize (send messages)</string>
|
||||
<string name="title_primary_account">Primary (default account)</string>
|
||||
@@ -106,11 +113,14 @@
|
||||
<string name="title_no_user">User name missing</string>
|
||||
<string name="title_no_password">Password missing</string>
|
||||
<string name="title_no_drafts">Drafts folder missing</string>
|
||||
<string name="title_no_idle">IDLE not supported</string>
|
||||
<string name="title_no_uidplus">UIDPLUS not supported</string>
|
||||
<string name="title_no_idle">IMAP IDLE not supported</string>
|
||||
<string name="title_no_uidplus">IMAP UIDPLUS not supported</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
<string name="title_identity_delete">Delete this identity permanently?</string>
|
||||
<string name="title_pop">POP is not supported</string>
|
||||
<string name="title_insecure">Insecure connections are not supported</string>
|
||||
<string name="title_synchronize_folder">Synchronize (receive messages)</string>
|
||||
<string name="title_unified_folder">Show in unified inbox</string>
|
||||
<string name="title_after">Synchronize (days)</string>
|
||||
<string name="title_folder_unified">Unified inbox</string>
|
||||
<string name="title_folder_inbox">Inbox</string>
|
||||
@@ -122,19 +132,24 @@
|
||||
<string name="title_folder_sent">Sent</string>
|
||||
<string name="title_folder_user">User</string>
|
||||
<string name="title_folder_primary">Folders primary account</string>
|
||||
<string name="title_folder_thread">Message thread</string>
|
||||
<string name="title_folder_thread">Conversation</string>
|
||||
<string name="title_no_messages">No messages</string>
|
||||
<string name="title_link">link</string>
|
||||
<string name="title_image">image</string>
|
||||
<string name="title_show_images">Show images</string>
|
||||
<string name="title_subject_reply">Re: %1$s</string>
|
||||
<string name="title_subject_forward">Fwd: %1$s</string>
|
||||
<string name="title_thread">Show thread</string>
|
||||
<string name="title_thread">Show conversation</string>
|
||||
<string name="title_seen">Mark read</string>
|
||||
<string name="title_unseen">Mark unread</string>
|
||||
<string name="title_flag">Add star</string>
|
||||
<string name="title_unflag">Remove star</string>
|
||||
<string name="title_forward">Forward</string>
|
||||
<string name="title_reply_all">Reply to all</string>
|
||||
<string name="title_show_headers">Show headers</string>
|
||||
<string name="title_show_html">Show original</string>
|
||||
<string name="title_trash">Trash</string>
|
||||
<string name="title_delete">Delete</string>
|
||||
<string name="title_spam">Spam</string>
|
||||
<string name="title_move">Move</string>
|
||||
<string name="title_archive">Archive</string>
|
||||
@@ -156,33 +171,34 @@
|
||||
<string name="title_send">Send</string>
|
||||
<string name="title_show_addresses">Show CC/BCC</string>
|
||||
<string name="title_add_attachment">Add attachment</string>
|
||||
<string name="title_no_openpgp">OpenPGP not available</string>
|
||||
<string name="title_not_encrypted">Encrypted message not found</string>
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
<string name="title_from_missing">Sender missing</string>
|
||||
<string name="title_to_missing">Recipient missing</string>
|
||||
<string name="title_attachments_missing">Attachments still loading</string>
|
||||
<string name="title_draft_trashed">Draft trashed</string>
|
||||
<string name="title_draft_deleted">Draft deleted</string>
|
||||
<string name="title_draft_saved">Draft saved</string>
|
||||
<string name="title_queued">Sending message</string>
|
||||
<string name="title_search">Search</string>
|
||||
<string name="title_search_hint">Search on server</string>
|
||||
<string name="title_search_hint">Search sender/subject/text</string>
|
||||
<string name="title_searching">Searching \'%1$s\'</string>
|
||||
<string name="title_answer_reply">Standard reply</string>
|
||||
<string name="title_answer_name">Answer name</string>
|
||||
<string name="title_answer_text">Answer text</string>
|
||||
<string name="title_legend_cc">CC/BCC</string>
|
||||
<string name="title_legend_attachment">Attachment</string>
|
||||
<string name="title_legend_thread">Conversation</string>
|
||||
<string name="title_legend_synchronize">Synchronize</string>
|
||||
<string name="title_legend_primary">Primary</string>
|
||||
<string name="title_legend_unified">Unified inbox</string>
|
||||
<string name="title_legend_disconnected">Disconnected</string>
|
||||
<string name="title_legend_connecting">Connecting</string>
|
||||
<string name="title_legend_connected">Connected</string>
|
||||
<string name="title_legend_synchronizing">Synchronizing</string>
|
||||
<string name="title_legend_closing">Closing</string>
|
||||
<string name="title_hint_swipe">Swipe left to trash and swipe right to archive (if available)</string>
|
||||
<string name="title_hint_actions">Swipe left to trash; swipe right to archive (if available); long press to mark read/unread</string>
|
||||
<string name="title_understood">Understood</string>
|
||||
<string name="title_issue">Do you have a question or problem?</string>
|
||||
<string name="title_yes">Yes</string>
|
||||
<string name="title_no">No</string>
|
||||
<string name="title_pro_feature">This is a pro feature</string>
|
||||
<string name="title_pro_list">List of pro features</string>
|
||||
<string name="title_pro_purchase">Buy</string>
|
||||
@@ -190,6 +206,7 @@
|
||||
<string name="title_pro_activated">All pro features are activated</string>
|
||||
<string name="title_pro_valid">All pro features activated</string>
|
||||
<string name="title_pro_invalid">Invalid response</string>
|
||||
<string name="title_log">Log</string>
|
||||
<string name="title_debug_info">Debug info</string>
|
||||
<string name="title_debug_info_remark">Please describe the problem and indicate the time of the problem:</string>
|
||||
</resources>
|
||||
|
||||
@@ -26,10 +26,11 @@
|
||||
<string name="menu_answers">Standard replies</string>
|
||||
<string name="menu_operations">Operations</string>
|
||||
<string name="menu_legend">Legend</string>
|
||||
<string name="menu_faq">FAQ</string>
|
||||
<string name="menu_faq">FAQ/support</string>
|
||||
<string name="menu_pro">Pro features</string>
|
||||
<string name="menu_privacy">Privacy</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="menu_rate">Rate this app</string>
|
||||
<string name="menu_other">Other apps</string>
|
||||
<string name="title_eula">End-user license agreement</string>
|
||||
<string name="title_agree">I agree</string>
|
||||
@@ -45,10 +46,15 @@
|
||||
<string name="title_setup_account_remark">To receive email</string>
|
||||
<string name="title_setup_identity">Manage identities</string>
|
||||
<string name="title_setup_identity_remark">To send email</string>
|
||||
<string name="title_setup_doze">Disable battery optimizations</string>
|
||||
<string name="title_setup_doze_remark">To continuously receive email (optional)</string>
|
||||
<string name="title_setup_doze_instructions">In the next dialog, select \"All apps\" at the top, select this app and select and confirm \"Don\'t optimize\"</string>
|
||||
<string name="title_setup_data">Disable data saving</string>
|
||||
<string name="title_setup_permissions">Grant permissions</string>
|
||||
<string name="title_setup_permissions_remark">To autocomplete addresses (optional)</string>
|
||||
<string name="title_setup_to_do">To do</string>
|
||||
<string name="title_setup_done">Done</string>
|
||||
<string name="title_setup_light_theme">Light theme</string>
|
||||
<string name="title_setup_dark_theme">Dark theme</string>
|
||||
<string name="title_advanced">Advanced options</string>
|
||||
<string name="title_advanced_webview">Use WebView to show external links</string>
|
||||
@@ -64,6 +70,7 @@
|
||||
<string name="title_account_linked">Linked account</string>
|
||||
<string name="title_account_name">Account name</string>
|
||||
<string name="title_account_name_hint">Used to differentiate folders</string>
|
||||
<string name="title_account_signature">Signature text</string>
|
||||
<string name="title_imap">IMAP</string>
|
||||
<string name="title_smtp">SMTP</string>
|
||||
<string name="title_provider">Provider</string>
|
||||
@@ -74,9 +81,9 @@
|
||||
<string name="title_user">User name</string>
|
||||
<string name="title_password">Password</string>
|
||||
<string name="title_authorize">Select account</string>
|
||||
<string name="title_instructions">Instructions</string>
|
||||
<string name="title_authorizing">Authorizing account …</string>
|
||||
<string name="title_setup_advanced">Advanced</string>
|
||||
<string name="title_store_sent">Store sent messages (enable if needed only)</string>
|
||||
<string name="title_interval">Poll/keep-alive interval (minutes)</string>
|
||||
<string name="title_synchronize_account">Synchronize (receive messages)</string>
|
||||
<string name="title_synchronize_identity">Synchronize (send messages)</string>
|
||||
<string name="title_primary_account">Primary (default account)</string>
|
||||
@@ -90,11 +97,14 @@
|
||||
<string name="title_no_user">User name missing</string>
|
||||
<string name="title_no_password">Password missing</string>
|
||||
<string name="title_no_drafts">Drafts folder missing</string>
|
||||
<string name="title_no_idle">IDLE not supported</string>
|
||||
<string name="title_no_uidplus">UIDPLUS not supported</string>
|
||||
<string name="title_no_idle">IMAP IDLE not supported</string>
|
||||
<string name="title_no_uidplus">IMAP UIDPLUS not supported</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
<string name="title_identity_delete">Delete this identity permanently?</string>
|
||||
<string name="title_pop">POP is not supported</string>
|
||||
<string name="title_insecure">Insecure connections are not supported</string>
|
||||
<string name="title_synchronize_folder">Synchronize (receive messages)</string>
|
||||
<string name="title_unified_folder">Show in unified inbox</string>
|
||||
<string name="title_after">Synchronize (days)</string>
|
||||
<string name="title_folder_unified">Unified inbox</string>
|
||||
<string name="title_folder_inbox">Inbox</string>
|
||||
@@ -106,19 +116,24 @@
|
||||
<string name="title_folder_sent">Sent</string>
|
||||
<string name="title_folder_user">User</string>
|
||||
<string name="title_folder_primary">Folders primary account</string>
|
||||
<string name="title_folder_thread">Message thread</string>
|
||||
<string name="title_folder_thread">Conversation</string>
|
||||
<string name="title_no_messages">No messages</string>
|
||||
<string name="title_link">link</string>
|
||||
<string name="title_image">image</string>
|
||||
<string name="title_show_images">Show images</string>
|
||||
<string name="title_subject_reply">Re: %1$s</string>
|
||||
<string name="title_subject_forward">Fwd: %1$s</string>
|
||||
<string name="title_thread">Show thread</string>
|
||||
<string name="title_thread">Show conversation</string>
|
||||
<string name="title_seen">Mark read</string>
|
||||
<string name="title_unseen">Mark unread</string>
|
||||
<string name="title_flag">Add star</string>
|
||||
<string name="title_unflag">Remove star</string>
|
||||
<string name="title_forward">Forward</string>
|
||||
<string name="title_reply_all">Reply to all</string>
|
||||
<string name="title_show_headers">Show headers</string>
|
||||
<string name="title_show_html">Show original</string>
|
||||
<string name="title_trash">Trash</string>
|
||||
<string name="title_delete">Delete</string>
|
||||
<string name="title_spam">Spam</string>
|
||||
<string name="title_move">Move</string>
|
||||
<string name="title_archive">Archive</string>
|
||||
@@ -140,33 +155,34 @@
|
||||
<string name="title_send">Send</string>
|
||||
<string name="title_show_addresses">Show CC/BCC</string>
|
||||
<string name="title_add_attachment">Add attachment</string>
|
||||
<string name="title_no_openpgp">OpenPGP not available</string>
|
||||
<string name="title_not_encrypted">Encrypted message not found</string>
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
<string name="title_from_missing">Sender missing</string>
|
||||
<string name="title_to_missing">Recipient missing</string>
|
||||
<string name="title_attachments_missing">Attachments still loading</string>
|
||||
<string name="title_draft_trashed">Draft trashed</string>
|
||||
<string name="title_draft_deleted">Draft deleted</string>
|
||||
<string name="title_draft_saved">Draft saved</string>
|
||||
<string name="title_queued">Sending message</string>
|
||||
<string name="title_search">Search</string>
|
||||
<string name="title_search_hint">Search on server</string>
|
||||
<string name="title_search_hint">Search sender/subject/text</string>
|
||||
<string name="title_searching">Searching \'%1$s\'</string>
|
||||
<string name="title_answer_reply">Standard reply</string>
|
||||
<string name="title_answer_name">Answer name</string>
|
||||
<string name="title_answer_text">Answer text</string>
|
||||
<string name="title_legend_cc">CC/BCC</string>
|
||||
<string name="title_legend_attachment">Attachment</string>
|
||||
<string name="title_legend_thread">Conversation</string>
|
||||
<string name="title_legend_synchronize">Synchronize</string>
|
||||
<string name="title_legend_primary">Primary</string>
|
||||
<string name="title_legend_unified">Unified inbox</string>
|
||||
<string name="title_legend_disconnected">Disconnected</string>
|
||||
<string name="title_legend_connecting">Connecting</string>
|
||||
<string name="title_legend_connected">Connected</string>
|
||||
<string name="title_legend_synchronizing">Synchronizing</string>
|
||||
<string name="title_legend_closing">Closing</string>
|
||||
<string name="title_hint_swipe">Swipe left to trash and swipe right to archive (if available)</string>
|
||||
<string name="title_hint_actions">Swipe left to trash; swipe right to archive (if available); long press to mark read/unread</string>
|
||||
<string name="title_understood">Understood</string>
|
||||
<string name="title_issue">Do you have a question or problem?</string>
|
||||
<string name="title_yes">Yes</string>
|
||||
<string name="title_no">No</string>
|
||||
<string name="title_pro_feature">This is a pro feature</string>
|
||||
<string name="title_pro_list">List of pro features</string>
|
||||
<string name="title_pro_purchase">Buy</string>
|
||||
@@ -174,6 +190,7 @@
|
||||
<string name="title_pro_activated">All pro features are activated</string>
|
||||
<string name="title_pro_valid">All pro features activated</string>
|
||||
<string name="title_pro_invalid">Invalid response</string>
|
||||
<string name="title_log">Log</string>
|
||||
<string name="title_debug_info">Debug info</string>
|
||||
<string name="title_debug_info_remark">Please describe the problem and indicate the time of the problem:</string>
|
||||
</resources>
|
||||
|
||||