mirror of
https://github.com/flectar/mail.git
synced 2026-11-14 02:53:32 +00:00
268 lines
22 KiB
Plaintext
268 lines
22 KiB
Plaintext
import { VerticalBox, HorizontalBox, ScrollView, TextEdit } from "std-widgets.slint";
|
||
import { AccountRow } from "../models.slint";
|
||
import { AppTheme } from "../design-system.slint";
|
||
import { TypeScale } from "../typography.slint";
|
||
import { Button } from "button.slint";
|
||
import { LineEdit } from "controls.slint";
|
||
import { OnScreenSurface } from "on-screen-surface.slint";
|
||
import { SettingsSelectRow, SettingsSwitchRow } from "settings-components.slint";
|
||
|
||
export struct SignatureChoice { id: string, name: string, text: string }
|
||
export struct SenderIdentityChoice { email: string, label: string }
|
||
export struct SenderIdentityRow {
|
||
email: string,
|
||
name: string,
|
||
reply-to: string,
|
||
primary: bool,
|
||
manual: bool,
|
||
pending: bool,
|
||
wildcard: bool,
|
||
// "unknown" | "confirmed" | "rejected"
|
||
delivery: string,
|
||
error: string,
|
||
}
|
||
export global AccountMailPreferences {
|
||
in-out property <int> account-id: -1;
|
||
in-out property <string> account-label;
|
||
in-out property <[SignatureChoice]> signatures;
|
||
in-out property <[string]> signature-names;
|
||
in-out property <[string]> default-names;
|
||
in-out property <int> editing-index: -1;
|
||
in-out property <string> editing-id;
|
||
in-out property <string> signature-name;
|
||
in-out property <string> signature-text;
|
||
in-out property <int> new-index: 0;
|
||
in-out property <int> reply-index: 0;
|
||
in-out property <string> fingerprint;
|
||
in-out property <bool> sign: false;
|
||
in-out property <bool> encrypt: false;
|
||
in-out property <string> recipient-keys;
|
||
in-out property <string> key-inventory;
|
||
in-out property <string> status;
|
||
in-out property <bool> busy: false;
|
||
in-out property <bool> desktop-supported: true;
|
||
in-out property <[SenderIdentityChoice]> sender-identities;
|
||
in-out property <[string]> sender-identity-names;
|
||
in-out property <int> default-sender-index: 0;
|
||
in-out property <string> sender-identity-status;
|
||
in-out property <[SenderIdentityRow]> sender-identity-rows;
|
||
// "any": the server judges at send time; "wildcard": only addresses inside
|
||
// a server *@domain identity; "never": the provider is authoritative.
|
||
in-out property <string> sender-identity-policy: "never";
|
||
in-out property <string> sender-form-email;
|
||
in-out property <string> sender-form-name;
|
||
in-out property <string> sender-form-reply-to;
|
||
in-out property <bool> sender-form-editing: false;
|
||
callback load(int);
|
||
callback edit-signature(int);
|
||
callback save-signature;
|
||
callback delete-signature;
|
||
callback save-defaults;
|
||
callback refresh-sender-identities;
|
||
callback save-default-sender;
|
||
callback save-sender-identity;
|
||
callback edit-sender-identity(string);
|
||
callback remove-sender-identity(string);
|
||
callback clear-sender-form;
|
||
callback save-security;
|
||
callback key-action(string);
|
||
callback composer-reset;
|
||
callback composer-account-changed(int);
|
||
callback composer-signature(int);
|
||
in-out property <[string]> composer-signature-names;
|
||
in-out property <int> composer-signature-index: 0;
|
||
in-out property <string> composer-security;
|
||
in-out property <bool> composer-preferences-ready: false;
|
||
in-out property <[SenderIdentityChoice]> composer-sender-identities;
|
||
in-out property <[string]> composer-sender-names;
|
||
in-out property <int> composer-sender-index: 0;
|
||
in-out property <string> composer-sender-email;
|
||
callback composer-sender(int);
|
||
callback read-message;
|
||
callback export-message;
|
||
callback close-reader;
|
||
callback context-changed;
|
||
in-out property <bool> reader-open: false;
|
||
in-out property <string> reader-status;
|
||
in-out property <string> reader-text;
|
||
}
|
||
|
||
export component AccountMailPreferencesPanel inherits OnScreenSurface {
|
||
in property <[AccountRow]> accounts;
|
||
in property <bool> compact: false;
|
||
in-out property <bool> security-tab: false;
|
||
background: AppTheme.surface;
|
||
border-radius: 10px;
|
||
border-width: 1px;
|
||
border-color: AppTheme.input-border;
|
||
vertical-stretch: 0;
|
||
pure function identity-detail(identity: SenderIdentityRow) -> string {
|
||
if identity.wildcard {
|
||
return @tr("Server allows any address at this domain");
|
||
}
|
||
if identity.pending {
|
||
return @tr("Awaiting verification by the provider; cannot be selected yet");
|
||
}
|
||
if identity.delivery == "rejected" {
|
||
return identity.error != "" ? @tr("The server refused the last message from this address: {}", identity.error) : @tr("The server refused the last message from this address");
|
||
}
|
||
if identity.primary {
|
||
return identity.reply-to != "" ? @tr("Sign-in address · replies go to {}", identity.reply-to) : @tr("Sign-in address");
|
||
}
|
||
if identity.manual {
|
||
if identity.delivery == "confirmed" {
|
||
return identity.reply-to != "" ? @tr("Added by you · accepted by the server · replies go to {}", identity.reply-to) : @tr("Added by you · accepted by the server");
|
||
}
|
||
return identity.reply-to != "" ? @tr("Added by you · not used yet · replies go to {}", identity.reply-to) : @tr("Added by you · not used yet");
|
||
}
|
||
return identity.reply-to != "" ? @tr("Provided by the server · replies go to {}", identity.reply-to) : @tr("Provided by the server");
|
||
}
|
||
content := VerticalBox {
|
||
padding: 20px; spacing: 12px; alignment: start;
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Sender identities, signatures & OpenPGP"); color: AppTheme.heading; font-size: TypeScale.subtitle; font-weight: 700; wrap: word-wrap; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Choose an account to manage its sender identity, signature, and message protection."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
for account in root.accounts : Button {
|
||
text: account.email;
|
||
primary: AccountMailPreferences.account-id == account.id;
|
||
enabled: !AccountMailPreferences.busy;
|
||
clicked => { AccountMailPreferences.load(account.id); }
|
||
}
|
||
if AccountMailPreferences.account-id > 0 : VerticalBox {
|
||
spacing: 12px;
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: AccountMailPreferences.account-label; color: AppTheme.heading; font-weight: 600; overflow: elide; }
|
||
HorizontalBox {
|
||
Button { text: @tr("Signatures"); primary: !root.security-tab; clicked => { root.security-tab = false; } }
|
||
Button { text: @tr("OpenPGP"); primary: root.security-tab; clicked => { root.security-tab = true; } }
|
||
}
|
||
if !root.security-tab : VerticalBox {
|
||
spacing: 10px;
|
||
SettingsSelectRow {
|
||
compact: root.compact; title: @tr("Default sender identity"); description: @tr("Preselected for new messages. Your sign-in address is unchanged.");
|
||
choices: AccountMailPreferences.sender-identity-names; selected-index <=> AccountMailPreferences.default-sender-index;
|
||
title-color: AppTheme.heading; description-color: AppTheme.secondary;
|
||
selected(index, value) => { AccountMailPreferences.default-sender-index = index; }
|
||
}
|
||
HorizontalBox {
|
||
Button { text: @tr("Save sender default"); primary: true; enabled: !AccountMailPreferences.busy && AccountMailPreferences.sender-identities.length > 0; clicked => { AccountMailPreferences.save-default-sender(); } }
|
||
Button { text: @tr("Refresh sender identities"); enabled: !AccountMailPreferences.busy; clicked => { AccountMailPreferences.refresh-sender-identities(); } }
|
||
}
|
||
if AccountMailPreferences.sender-identity-status != "" : Text { min-height: self.preferred-height; vertical-stretch: 0; text: AccountMailPreferences.sender-identity-status; color: AppTheme.secondary; wrap: word-wrap; }
|
||
for identity in AccountMailPreferences.sender-identity-rows : VerticalBox {
|
||
padding: 0px; spacing: 4px;
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: identity.name != "" ? identity.name + " <" + identity.email + ">" : identity.email; color: AppTheme.heading; overflow: elide; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: root.identity-detail(identity); color: identity.delivery == "rejected" ? AppTheme.destructive : AppTheme.secondary; wrap: word-wrap; }
|
||
if identity.manual : HorizontalBox {
|
||
padding: 0px;
|
||
alignment: start;
|
||
Button { text: @tr("Edit"); enabled: !AccountMailPreferences.busy; clicked => { AccountMailPreferences.edit-sender-identity(identity.email); } }
|
||
Button { text: @tr("Remove"); destructive: true; enabled: !AccountMailPreferences.busy; clicked => { AccountMailPreferences.remove-sender-identity(identity.email); } }
|
||
}
|
||
}
|
||
if AccountMailPreferences.sender-identity-policy == "never" : Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Gmail decides which addresses you can send from. Add an address in Gmail settings under Accounts › Send mail as, then refresh sender identities."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
if AccountMailPreferences.sender-identity-policy != "never" : VerticalBox {
|
||
padding: 0px; spacing: 8px;
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: AccountMailPreferences.sender-form-editing ? @tr("Edit sender address") : @tr("Add a sender address"); color: AppTheme.heading; font-weight: 600; }
|
||
Text {
|
||
min-height: self.preferred-height; vertical-stretch: 0; color: AppTheme.secondary; wrap: word-wrap;
|
||
text: AccountMailPreferences.sender-identity-policy == "wildcard"
|
||
? @tr("Your server lets you send from any address at the domains shown as wildcards. Add the addresses you want to choose from.")
|
||
: @tr("Add an alias or another address your mail server lets you send from. The server decides when you send; Flectar shows whether it accepted the last message.");
|
||
}
|
||
LineEdit { text <=> AccountMailPreferences.sender-form-email; input-label: @tr("Email address"); placeholder-text: @tr("alias@example.com"); read-only: AccountMailPreferences.sender-form-editing; enabled: !AccountMailPreferences.busy; }
|
||
LineEdit { text <=> AccountMailPreferences.sender-form-name; input-label: @tr("Sender name"); placeholder-text: @tr("Name shown to recipients (optional)"); enabled: !AccountMailPreferences.busy; }
|
||
LineEdit { text <=> AccountMailPreferences.sender-form-reply-to; input-label: @tr("Reply-to address"); placeholder-text: @tr("Where replies should go (optional)"); enabled: !AccountMailPreferences.busy; }
|
||
HorizontalBox {
|
||
padding: 0px;
|
||
alignment: start;
|
||
Button { text: AccountMailPreferences.sender-form-editing ? @tr("Save sender address") : @tr("Add sender address"); primary: true; enabled: !AccountMailPreferences.busy && AccountMailPreferences.sender-form-email != ""; clicked => { AccountMailPreferences.save-sender-identity(); } }
|
||
if AccountMailPreferences.sender-form-editing : Button { text: @tr("Cancel"); enabled: !AccountMailPreferences.busy; clicked => { AccountMailPreferences.clear-sender-form(); } }
|
||
}
|
||
}
|
||
Rectangle { height: 1px; background: AppTheme.input-border; }
|
||
SettingsSelectRow {
|
||
compact: root.compact; title: @tr("Edit signature"); description: @tr("Saved separately for each account.");
|
||
choices: AccountMailPreferences.signature-names; selected-index: AccountMailPreferences.editing-index;
|
||
title-color: AppTheme.heading; description-color: AppTheme.secondary;
|
||
selected(index, value) => { AccountMailPreferences.edit-signature(index); }
|
||
}
|
||
Button { text: @tr("New signature"); enabled: !AccountMailPreferences.busy; clicked => { AccountMailPreferences.edit-signature(-1); } }
|
||
LineEdit { text <=> AccountMailPreferences.signature-name; input-label: @tr("Signature name"); placeholder-text: @tr("Signature name, for example Work"); enabled: !AccountMailPreferences.busy; }
|
||
TextEdit { wrap: word-wrap; height: 160px; text <=> AccountMailPreferences.signature-text; placeholder-text: @tr("Your name\nRole · Company\nPhone · Website"); enabled: !AccountMailPreferences.busy; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Plain text works in every email client. You can format the inserted signature in the composer."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
HorizontalBox {
|
||
Button { text: @tr("Save signature"); primary: true; enabled: !AccountMailPreferences.busy; clicked => { AccountMailPreferences.save-signature(); } }
|
||
Button { text: @tr("Delete"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.editing-id != ""; clicked => { AccountMailPreferences.delete-signature(); } }
|
||
}
|
||
SettingsSelectRow {
|
||
compact: root.compact; title: @tr("New messages"); description: @tr("Signature inserted when you start a message.");
|
||
choices: AccountMailPreferences.default-names; selected-index <=> AccountMailPreferences.new-index;
|
||
title-color: AppTheme.heading; description-color: AppTheme.secondary;
|
||
selected(index, value) => { AccountMailPreferences.new-index = index; }
|
||
}
|
||
SettingsSelectRow {
|
||
compact: root.compact; title: @tr("Replies & forwards"); description: @tr("Placed above the quoted message.");
|
||
choices: AccountMailPreferences.default-names; selected-index <=> AccountMailPreferences.reply-index;
|
||
title-color: AppTheme.heading; description-color: AppTheme.secondary;
|
||
selected(index, value) => { AccountMailPreferences.reply-index = index; }
|
||
}
|
||
Button { text: @tr("Save signature defaults"); enabled: !AccountMailPreferences.busy; clicked => { AccountMailPreferences.save-defaults(); } }
|
||
}
|
||
if root.security-tab : VerticalBox {
|
||
spacing: 10px;
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("OpenPGP protects message bodies and attachments. Digital signatures let recipients check who signed a message."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: AccountMailPreferences.desktop-supported ? @tr("Uses installed GnuPG 2.x and its secure key storage. Passphrases are entered in GnuPG's unlock dialog.") : @tr("OpenPGP is unavailable on this mobile build. Protected accounts cannot send without a supported encryption engine."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
Button { text: @tr("Create a new key"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; clicked => { AccountMailPreferences.key-action("generate"); } }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("New keys expire in two years. GnuPG asks you to protect the private key with a passphrase."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
LineEdit { text <=> AccountMailPreferences.fingerprint; input-label: @tr("Your full OpenPGP fingerprint"); placeholder-text: @tr("Your full OpenPGP key fingerprint"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; }
|
||
SettingsSwitchRow {
|
||
title: @tr("Digitally sign messages"); description: @tr("Proves possession of your private key; does not encrypt the message.");
|
||
compact: root.compact; checked: AccountMailPreferences.sign; enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; toggled(value) => { AccountMailPreferences.sign = value; }
|
||
title-color: AppTheme.heading; description-color: AppTheme.secondary;
|
||
}
|
||
SettingsSwitchRow {
|
||
title: @tr("Require encryption"); description: @tr("Blocks sending unless every recipient has a verified, usable key.");
|
||
compact: root.compact; checked: AccountMailPreferences.encrypt; enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; toggled(value) => { AccountMailPreferences.encrypt = value; }
|
||
title-color: AppTheme.heading; description-color: AppTheme.secondary;
|
||
}
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Verified recipient keys"); color: AppTheme.heading; font-weight: 600; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Compare fingerprints with recipients through a separate trusted channel, then enter one email=fingerprint per line. Importing a key alone does not verify it."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
TextEdit { wrap: word-wrap; height: 130px; text <=> AccountMailPreferences.recipient-keys; placeholder-text: @tr("person@example.org=FULL_FINGERPRINT"); enabled: !AccountMailPreferences.busy; }
|
||
Button { text: @tr("Save OpenPGP settings"); primary: true; enabled: !AccountMailPreferences.busy; clicked => { AccountMailPreferences.save-security(); } }
|
||
if !root.compact : HorizontalBox {
|
||
Button { text: @tr("Import key"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; clicked => { AccountMailPreferences.key-action("import"); } }
|
||
Button { text: @tr("Export public key"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; clicked => { AccountMailPreferences.key-action("export"); } }
|
||
Button { text: @tr("Show keys"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; clicked => { AccountMailPreferences.key-action("list"); } }
|
||
}
|
||
if root.compact : VerticalBox {
|
||
spacing: 8px;
|
||
Button { text: @tr("Import key"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; clicked => { AccountMailPreferences.key-action("import"); } }
|
||
Button { text: @tr("Export public key"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; clicked => { AccountMailPreferences.key-action("export"); } }
|
||
Button { text: @tr("Show keys"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; clicked => { AccountMailPreferences.key-action("list"); } }
|
||
}
|
||
Button { text: @tr("Open message file (.eml)"); enabled: !AccountMailPreferences.busy && AccountMailPreferences.desktop-supported; clicked => { AccountMailPreferences.key-action("open"); } }
|
||
if AccountMailPreferences.key-inventory != "" : TextEdit { wrap: word-wrap; height: 150px; read-only: true; text: AccountMailPreferences.key-inventory; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Manage expiry, revocation and private-key backups in GnuPG. Only public keys are exported here."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Subjects and addresses remain visible. Protected drafts stay on this device. Existing server drafts are not retroactively protected. S/MIME certificates are not supported."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
}
|
||
if AccountMailPreferences.busy || AccountMailPreferences.status != "" : Text {
|
||
text: AccountMailPreferences.busy ? @tr("Working…") : AccountMailPreferences.status;
|
||
color: AppTheme.secondary; wrap: word-wrap;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
export component OpenPgpReader inherits Rectangle {
|
||
visible: AccountMailPreferences.reader-open;
|
||
background: AppTheme.surface;
|
||
VerticalBox {
|
||
padding: 24px; spacing: 12px;
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("OpenPGP message"); color: AppTheme.heading; font-size: TypeScale.subtitle; font-weight: 700; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: AccountMailPreferences.reader-status; color: AppTheme.secondary; wrap: word-wrap; }
|
||
TextEdit { wrap: word-wrap; vertical-stretch: 1; read-only: true; text: AccountMailPreferences.reader-text; }
|
||
Text { min-height: self.preferred-height; vertical-stretch: 0; text: @tr("Decrypted content is shown as text, without remote content. It is not added to search or saved to the mail database."); color: AppTheme.secondary; wrap: word-wrap; }
|
||
Button { text: @tr("Export decrypted message"); enabled: AccountMailPreferences.reader-text != ""; clicked => { AccountMailPreferences.export-message(); } }
|
||
Button { text: @tr("Close"); clicked => { AccountMailPreferences.close-reader(); AccountMailPreferences.reader-open = false; AccountMailPreferences.reader-text = ""; AccountMailPreferences.reader-status = ""; } }
|
||
}
|
||
}
|