Files
pallectrum/electrum/gui/qml/components/Preferences.qml
SomberNight 0dae17339d qml: add config setting to allow screenshots
On Android, we disallow screenshots on screens where the seed is visible.
(The seed is extremely sensitive data that should not be stored digitally without
significant precautions but it's also cumbersome to write down or memorise, so
some people instinctively just try to take a screenshot of it when creating a wallet.)
We do this by using the built-in OS mechanism of setting FLAG_SECURE on the window.
However, on some devices with custom ROMs (one report from LineageOS, one from /e/OS),
unsetting FLAG_SECURE crashes the application for some reason.

As a workaround, this commit adds a config setting into the Preferences,
to disable this mechanism and just always allow screenshots.
(note that you can get into the qml Preferences before creating/opening any wallet)

ref https://github.com/spesmilo/electrum/issues/8522
2023-12-27 07:28:39 +00:00

418 lines
16 KiB
QML

import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
import org.electrum 1.0
import "controls"
Pane {
id: preferences
objectName: 'Properties'
property string title: qsTr("Preferences")
padding: 0
property var _baseunits: ['BTC','mBTC','bits','sat']
ColumnLayout {
anchors.fill: parent
Flickable {
Layout.fillHeight: true
Layout.fillWidth: true
contentHeight: prefsPane.height
interactive: height < contentHeight
clip: true
Pane {
id: prefsPane
width: parent.width
GridLayout {
columns: 2
width: parent.width
PrefsHeading {
Layout.columnSpan: 2
text: qsTr('User Interface')
}
Label {
text: qsTr('Language')
}
ElComboBox {
id: language
textRole: 'text'
valueRole: 'value'
model: Config.languagesAvailable
onCurrentValueChanged: {
if (activeFocus) {
if (Config.language != currentValue) {
Config.language = currentValue
var dialog = app.messageDialog.createObject(app, {
text: qsTr('Please restart Electrum to activate the new GUI settings')
})
dialog.open()
}
}
}
}
Label {
text: qsTr('Base unit')
}
ElComboBox {
id: baseUnit
model: _baseunits
onCurrentValueChanged: {
if (activeFocus)
Config.baseUnit = currentValue
}
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
Switch {
id: thousands
onCheckedChanged: {
if (activeFocus)
Config.thousandsSeparator = checked
}
}
Label {
Layout.fillWidth: true
text: qsTr('Add thousands separators to bitcoin amounts')
wrapMode: Text.Wrap
}
}
RowLayout {
spacing: 0
Switch {
id: fiatEnable
onCheckedChanged: {
if (activeFocus)
Daemon.fx.enabled = checked
}
}
Label {
Layout.fillWidth: true
text: qsTr('Fiat Currency')
wrapMode: Text.Wrap
}
}
ElComboBox {
id: currencies
model: Daemon.fx.currencies
enabled: Daemon.fx.enabled
onCurrentValueChanged: {
if (activeFocus)
Daemon.fx.fiatCurrency = currentValue
}
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
Switch {
id: historicRates
enabled: Daemon.fx.enabled
onCheckedChanged: {
if (activeFocus)
Daemon.fx.historicRates = checked
}
}
Label {
Layout.fillWidth: true
text: qsTr('Historic rates')
wrapMode: Text.Wrap
}
}
Label {
text: qsTr('Exchange rate provider')
enabled: Daemon.fx.enabled
}
ElComboBox {
id: rateSources
enabled: Daemon.fx.enabled
model: Daemon.fx.rateSources
onModelChanged: {
currentIndex = rateSources.indexOfValue(Daemon.fx.rateSource)
}
onCurrentValueChanged: {
if (activeFocus)
Daemon.fx.rateSource = currentValue
}
}
RowLayout {
Layout.fillWidth: true
spacing: 0
Switch {
id: usePin
checked: Config.pinCode
onCheckedChanged: {
if (activeFocus) {
console.log('PIN active ' + checked)
if (checked) {
var dialog = pinSetup.createObject(preferences, {mode: 'enter'})
dialog.accepted.connect(function() {
Config.pinCode = dialog.pincode
dialog.close()
})
dialog.rejected.connect(function() {
checked = false
})
dialog.open()
} else {
focus = false
Config.pinCode = ''
// re-add binding, pincode still set if auth failed
checked = Qt.binding(function () { return Config.pinCode })
}
}
}
}
Label {
Layout.fillWidth: true
text: qsTr('PIN protect payments')
wrapMode: Text.Wrap
}
}
Pane {
background: Rectangle { color: Material.dialogColor }
padding: 0
visible: Config.pinCode != ''
FlatButton {
text: qsTr('Modify')
onClicked: {
var dialog = pinSetup.createObject(preferences, {
mode: 'change',
pincode: Config.pinCode
})
dialog.accepted.connect(function() {
Config.pinCode = dialog.pincode
dialog.close()
})
dialog.open()
}
}
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
Switch {
id: syncLabels
onCheckedChanged: {
if (activeFocus)
AppController.setPluginEnabled('labels', checked)
}
}
Label {
Layout.fillWidth: true
text: qsTr('Synchronize labels')
wrapMode: Text.Wrap
}
}
PrefsHeading {
Layout.columnSpan: 2
text: qsTr('Wallet behavior')
}
RowLayout {
Layout.columnSpan: 2
spacing: 0
Switch {
id: spendUnconfirmed
onCheckedChanged: {
if (activeFocus)
Config.spendUnconfirmed = checked
}
}
Label {
Layout.fillWidth: true
text: qsTr('Spend unconfirmed')
wrapMode: Text.Wrap
}
}
PrefsHeading {
Layout.columnSpan: 2
text: qsTr('Lightning')
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
Switch {
id: useTrampolineRouting
onCheckedChanged: {
if (activeFocus) {
if (!checked) {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Are you sure?'),
text: qsTr('Electrum will have to download the Lightning Network graph, which is not recommended on mobile.'),
yesno: true
})
dialog.accepted.connect(function() {
Config.useGossip = true
})
dialog.rejected.connect(function() {
checked = true // revert
})
dialog.open()
} else {
Config.useGossip = !checked
}
}
}
}
Label {
Layout.fillWidth: true
text: qsTr('Trampoline routing')
wrapMode: Text.Wrap
}
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
Switch {
id: useRecoverableChannels
onCheckedChanged: {
if (activeFocus) {
if (!checked) {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Are you sure?'),
text: qsTr('This option allows you to recover your lightning funds if you lose your device, or if you uninstall this app while lightning channels are active. Do not disable it unless you know how to recover channels from backups.'),
yesno: true
})
dialog.accepted.connect(function() {
Config.useRecoverableChannels = false
})
dialog.rejected.connect(function() {
checked = true // revert
})
dialog.open()
} else {
Config.useRecoverableChannels = checked
}
}
}
}
Label {
Layout.fillWidth: true
text: qsTr('Create recoverable channels')
wrapMode: Text.Wrap
}
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
Switch {
id: useFallbackAddress
onCheckedChanged: {
if (activeFocus)
Config.useFallbackAddress = checked
}
}
Label {
Layout.fillWidth: true
text: qsTr('Create lightning invoices with on-chain fallback address')
wrapMode: Text.Wrap
}
}
PrefsHeading {
Layout.columnSpan: 2
text: qsTr('Advanced')
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
Switch {
id: enableDebugLogs
onCheckedChanged: {
if (activeFocus)
Config.enableDebugLogs = checked
}
enabled: Config.canToggleDebugLogs
}
Label {
Layout.fillWidth: true
text: qsTr('Enable debug logs (for developers)')
wrapMode: Text.Wrap
}
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
Switch {
id: alwaysAllowScreenshots
onCheckedChanged: {
if (activeFocus)
Config.alwaysAllowScreenshots = checked
}
}
Label {
Layout.fillWidth: true
text: qsTr('Always allow screenshots')
wrapMode: Text.Wrap
}
}
}
}
}
}
Component {
id: pinSetup
Pin {}
}
Component.onCompleted: {
language.currentIndex = language.indexOfValue(Config.language)
baseUnit.currentIndex = _baseunits.indexOf(Config.baseUnit)
thousands.checked = Config.thousandsSeparator
currencies.currentIndex = currencies.indexOfValue(Daemon.fx.fiatCurrency)
historicRates.checked = Daemon.fx.historicRates
rateSources.currentIndex = rateSources.indexOfValue(Daemon.fx.rateSource)
fiatEnable.checked = Daemon.fx.enabled
spendUnconfirmed.checked = Config.spendUnconfirmed
useTrampolineRouting.checked = !Config.useGossip
useFallbackAddress.checked = Config.useFallbackAddress
enableDebugLogs.checked = Config.enableDebugLogs
alwaysAllowScreenshots.checked = Config.alwaysAllowScreenshots
useRecoverableChannels.checked = Config.useRecoverableChannels
syncLabels.checked = AppController.isPluginEnabled('labels')
}
}