feat(profile): add Messaging settings view in profile

Fixes #4931
This commit is contained in:
Jonathan Rainville 2022-03-07 15:34:59 -05:00
parent e244260c81
commit e1b7475884
23 changed files with 845 additions and 718 deletions

@ -1 +1 @@
Subproject commit 082bb8ef45b64986be1bf67f4d7c95b1a8ea1440
Subproject commit 8af7028efc61725e415518bea21a3c4de4bb6481

View File

@ -69,6 +69,12 @@ StatusAppTwoPanelLayout {
profileContentWidth: _internal.profileContentWidth
}
MessagingView {
id: messagingView
messagingStore: profileView.store.messagingStore
profileContentWidth: _internal.profileContentWidth
}
WalletView {
id: walletView
walletStore: profileView.store.walletStore
@ -100,11 +106,6 @@ StatusAppTwoPanelLayout {
profileContentWidth: _internal.profileContentWidth
}
SyncView {
syncStore: profileView.store.syncStore
profileContentWidth: _internal.profileContentWidth
}
DevicesView {
devicesStore: profileView.store.devicesStore
}

View File

@ -11,13 +11,14 @@ Column {
spacing: 4
property var privacyStore
property var messagingStore
property alias mainMenuItems: mainMenuItems.model
property alias settingsMenuItems: settingsMenuItems.model
property alias extraMenuItems: extraMenuItems.model
property alias appsMenuItems: appsMenuItems.model
property bool browserMenuItemEnabled: false
property bool appsMenuItemsEnabled: false
property bool walletMenuItemEnabled: false
signal menuItemClicked(var menu_item)
@ -34,7 +35,6 @@ Column {
StatusListSectionHeadline {
text: qsTr("Apps")
visible: root.appsMenuItemsEnabled || root.browserMenuItemEnabled
}
Repeater {
@ -47,8 +47,9 @@ Column {
selected: Global.settingsSubsection === model.subsection
onClicked: root.menuItemClicked(model)
visible: {
(model.subsection !== Constants.settingsSubsection.browserSettings && model.subsection !== Constants.settingsSubsection.wallet) ||
(model.subsection === Constants.settingsSubsection.browserSettings && root.browserMenuItemEnabled) ||
(model.subsection === Constants.settingsSubsection.wallet && root.appsMenuItemsEnabled)
(model.subsection === Constants.settingsSubsection.wallet && root.walletMenuItemEnabled)
}
}
}
@ -65,7 +66,15 @@ Column {
selected: Global.settingsSubsection === model.subsection
onClicked: root.menuItemClicked(model)
visible: model.subsection !== Constants.settingsSubsection.browserSettings || root.browserMenuItemEnabled
badge.value: !root.privacyStore.mnemonicBackedUp && settingsMenuDelegate.title === qsTr("Privacy and security")
badge.value: {
switch (model.subsection) {
case Constants.settingsSubsection.privacyAndSecurity:
return !root.privacyStore.mnemonicBackedUp
case Constants.settingsSubsection.messaging:
return root.messagingStore.contactRequestsModel.count
default: return ""
}
}
}
}

View File

@ -0,0 +1,82 @@
import QtQuick 2.12
import QtQuick.Controls 2.3
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1
import utils 1.0
StatusModal {
id: popup
anchors.centerIn: parent
height: 560
header.title: qsTr("Waku nodes")
property var messagingStore
onClosed: {
destroy()
}
onOpened: {
nameInput.text = "";
enodeInput.text = "";
}
contentItem: Item {
width: parent.width
height: parent.height
StatusInput {
id: nameInput
//% "Name"
label: qsTrId("name")
//% "Specify a name"
input.placeholderText: qsTrId("specify-name")
validators: [StatusMinLengthValidator {
minLength: 1
//% "You need to enter a name"
errorMessage: qsTrId("you-need-to-enter-a-name")
}]
validationMode: StatusInput.ValidationMode.OnlyWhenDirty
}
StatusInput {
id: enodeInput
//% "History node address"
label: qsTrId("history-node-address")
input.placeholderText: "enode://{enode-id}:{password}@{ip-address}:{port-number}"
validators: [StatusMinLengthValidator {
minLength: 1
//% "You need to enter the enode address"
errorMessage: qsTrId("you-need-to-enter-the-enode-address")
},
StatusRegularExpressionValidator {
errorMessage: qsTr("The format must be: enode://{enode-id}:{password}@{ip-address}:{port}")
regularExpression: /enode:\/\/[a-z0-9]+:[a-z0-9]+@(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}:[0-9]+/
}]
validationMode: StatusInput.ValidationMode.OnlyWhenDirty
anchors.top: nameInput.bottom
anchors.topMargin: Style.current.bigPadding
}
}
rightButtons: [
StatusButton {
//% "Save"
text: qsTrId("save")
enabled: nameInput.valid && enodeInput.valid
// enabled: nameInput.text !== "" && enodeInput.text !== ""
onClicked: {
root.messagingStore.saveNewMailserver(nameInput.text, enodeInput.text)
popup.close()
}
}
]
}

View File

@ -1,225 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import shared.panels 1.0
import shared.popups 1.0
import shared.status 1.0
import utils 1.0
// TODO: replace with StatusModal
ModalPopup {
id: popup
property var privacyStore
//% "Chat link previews"
title: qsTrId("chat-link-previews")
onClosed: {
destroy()
}
function populatePreviewableSites() {
let whitelistAsString = popup.privacyStore.getLinkPreviewWhitelist()
if(whitelistAsString == "")
return
let whitelist = JSON.parse(whitelistAsString)
if (!localAccountSensitiveSettings.whitelistedUnfurlingSites) {
localAccountSensitiveSettings.whitelistedUnfurlingSites = {}
}
whitelist.forEach(entry => {
entry.isWhitelisted = localAccountSensitiveSettings.whitelistedUnfurlingSites[entry.address] || false
previewableSites.append(entry)
})
}
onOpened: {
populatePreviewableSites()
}
Item {
anchors.fill: parent
StatusSectionHeadline {
id: labelWebsites
//% "Websites"
text: qsTrId("websites")
width: parent.width
StatusFlatButton {
//% "Enable all"
text: qsTrId("enable-all")
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const count = sitesListView.count
for (let i = 0; i < count; i++) {
sitesListView.itemAtIndex(i).toggleSetting(true)
}
}
}
}
ListModel {
id: previewableSites
}
Connections {
target: Global
onSettingsLoaded: {
popup.populatePreviewableSites()
}
}
ScrollView {
width: parent.width
anchors.top: labelWebsites.bottom
anchors.topMargin: Style.current.bigPadding
anchors.bottom: infoText.top
anchors.bottomMargin: Style.current.bigPadding
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
clip: true
ListView {
id: sitesListView
anchors.fill: parent
anchors.rightMargin: Style.current.padding
model: previewableSites
spacing: 0
delegate: Component {
Rectangle {
property bool isHovered: false
id: linkRectangle
width: parent.width
height: 64
color: isHovered ? Style.current.backgroundHover : Style.current.transparent
radius: Style.current.radius
border.width: 0
function toggleSetting(newState) {
if (newState !== undefined) {
settingSwitch.checked = newState
return
}
settingSwitch.checked = !settingSwitch.checked
}
SVGImage {
id: thumbnail
width: 40
height: 40
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
source: {
let filename;
switch (title.toLowerCase()) {
case "youtube":
case "youtube shortener":
filename = "youtube"; break;
case "github":
filename = "github"; break;
case "medium":
filename = "medium"; break;
case "tenor gifs":
filename = "tenor"; break;
case "giphy gifs":
case "giphy gifs shortener":
case "giphy gifs subdomain":
filename = "giphy"; break;
case "github":
filename = "github"; break;
case "status":
filename = "status"; break;
// TODO get a good default icon
default: filename = "../globe"
}
return Style.svg(`linkPreviewThumbnails/${filename}`)
}
Rectangle {
width: parent.width
height: parent.height
radius: width / 2
color: Style.current.transparent
border.color: Style.current.border
border.width: 1
}
}
StatusBaseText {
id: siteTitle
text: title
color: Theme.palette.directColor1
font.pixelSize: 15
font.weight: Font.Medium
anchors.top: thumbnail.top
anchors.left: thumbnail.right
anchors.leftMargin: Style.current.padding
}
StatusBaseText {
text: address
font.pixelSize: 15
font.weight: Font.Thin
color: Style.current.secondaryText
anchors.top: siteTitle.bottom
anchors.left: siteTitle.left
}
StatusSwitch {
id: settingSwitch
checked: !!isWhitelisted
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
onCheckedChanged: function () {
let settings = localAccountSensitiveSettings.whitelistedUnfurlingSites
if (!settings) {
settings = {}
}
if (settings[address] === this.checked) {
return
}
settings[address] = this.checked
localAccountSensitiveSettings.whitelistedUnfurlingSites = settings
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: linkRectangle.isHovered = true
onExited: linkRectangle.isHovered = false
onClicked: toggleSetting()
}
}
}
}
}
StatusBaseText {
id: infoText
//% "Previewing links from these websites may share your metadata with their owners."
text: qsTrId("previewing-links-from-these-websites-may-share-your-metadata-with-their-owners-")
width: parent.width
wrapMode: Text.WordWrap
font.weight: Font.Thin
color: Style.current.secondaryText
font.pixelSize: 15
anchors.bottom: parent.bottom
}
}
}

View File

@ -1,62 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import utils 1.0
import shared.popups 1.0
import shared.controls 1.0
// TODO: replace with StatusModal
ModalPopup {
id: popup
//% "Open links with..."
title: qsTrId("open-links-with---")
onClosed: {
destroy()
}
Column {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.leftMargin: Style.current.padding
spacing: 0
ButtonGroup {
id: openLinksWithGroup
}
RadioButtonSelector {
title: "Status"
buttonGroup: openLinksWithGroup
checked: localAccountSensitiveSettings.openLinksInStatus
onCheckedChanged: {
if (checked) {
localAccountSensitiveSettings.openLinksInStatus = true
}
}
}
RadioButtonSelector {
//% "My default browser"
title: qsTrId("my-default-browser")
buttonGroup: openLinksWithGroup
checked: !localAccountSensitiveSettings.openLinksInStatus
onCheckedChanged: {
if (checked) {
localAccountSensitiveSettings.openLinksInStatus = false
}
}
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -0,0 +1,146 @@
import QtQuick 2.12
import QtQuick.Controls 2.3
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1
import "."
import utils 1.0
import shared 1.0
import shared.panels 1.0
import shared.popups 1.0
import shared.status 1.0
import shared.controls 1.0
StatusModal {
id: popup
anchors.centerIn: parent
height: 560
header.title: qsTr("Waku nodes")
property var messagingStore
property string nameValidationError: ""
property string enodeValidationError: ""
onClosed: {
destroy()
}
contentItem: ScrollView {
height: parent.height
width: parent.width
contentHeight: nodesColumn.height
clip: true
Column {
id: nodesColumn
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
// TODO re-add that when the status-go API to disconnect from the mailserver is added
// StatusListItem {
// anchors.left: parent.left
// anchors.leftMargin: -Style.current.padding
// anchors.right: parent.right
// anchors.rightMargin: -Style.current.padding
// title: qsTr("Use Waku nodes")
// components: [
// StatusSwitch {
// // TODO find what this is
// checked: true
// }
// ]
// }
Separator {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
}
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
title: qsTr("Select node automatically")
components: [
StatusSwitch {
id: automaticSelectionSwitch
checked: root.messagingStore.automaticMailserverSelection
onCheckedChanged: root.messagingStore.enableAutomaticMailserverSelection(checked)
}
]
sensor.onClicked: {
automaticSelectionSwitch.checked = !automaticSelectionSwitch.checked
}
}
StatusSectionHeadline {
text: qsTr("Waku Nodes")
visible: !automaticSelectionSwitch.checked
width: parent.width
height: visible ? implicitHeight : 0
}
ButtonGroup {
id: nodesButtonGroup
}
Repeater {
id: mailServersListView
model: root.messagingStore.mailservers
delegate: Component {
StatusListItem {
title: qsTr("Node %1").arg(index)
subTitle: model.name
visible: !automaticSelectionSwitch.checked
height: visible ? implicitHeight : 0
components: [
StatusRadioButton {
id: nodeRadioBtn
ButtonGroup.group: nodesButtonGroup
checked: model.nodeAddress === root.messagingStore.activeMailserver
onCheckedChanged: {
if (checked) {
root.messagingStore.setActiveMailserver(model.nodeAddress)
}
}
}
]
sensor.onClicked: {
nodeRadioBtn.checked = true
}
}
}
}
StatusBaseText {
text: qsTr("Add a new node")
color: Theme.palette.primaryColor1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Global.openPopup(wakuNodeModalComponent)
}
}
}
}
Component {
id: wakuNodeModalComponent
AddWakuNodeModal {
messagingStore: root.messagingStore
}
}
}

View File

@ -0,0 +1,54 @@
import QtQuick 2.13
import utils 1.0
QtObject {
id: root
property var privacyModule
property var syncModule
property int profilePicturesVisibility: privacyModule.profilePicturesVisibility
property int profilePicturesShowTo: privacyModule.profilePicturesShowTo
// TODO move contact requests back to the contacts module since we need them in the Profile
// also, having them in the chat section creates some waste, since no community has it
property var chatSectionModule: mainModule.getChatSectionModule()
property var contactRequestsModel: chatSectionModule.contactRequestsModel
property var mailservers: syncModule.model
// Module Properties
property bool automaticMailserverSelection: syncModule.automaticSelection
property string activeMailserver: syncModule.activeMailserver
function getMailserverNameForNodeAddress(nodeAddress) {
return root.syncModule.getMailserverNameForNodeAddress(nodeAddress)
}
function setActiveMailserver(nodeAddress) {
root.syncModule.setActiveMailserver(nodeAddress)
}
function saveNewMailserver(name, nodeAddress) {
root.syncModule.saveNewMailserver(name, nodeAddress)
}
function enableAutomaticMailserverSelection(checked) {
if (automaticMailserverSelection === checked) {
return
}
root.syncModule.enableAutomaticSelection(checked)
}
function getLinkPreviewWhitelist() {
return root.privacyModule.getLinkPreviewWhitelist()
}
function setProfilePicturesVisibility(value) {
return root.privacyModule.setProfilePicturesVisibility(value)
}
function setProfilePicturesShowTo(value) {
return root.privacyModule.setProfilePicturesShowTo(value)
}
}

View File

@ -8,11 +8,6 @@ QtObject {
// Module Properties
property bool mnemonicBackedUp: privacyModule.mnemonicBackedUp
property int profilePicturesVisibility: privacyModule.profilePicturesVisibility
function getLinkPreviewWhitelist() {
return root.privacyModule.getLinkPreviewWhitelist()
}
function changePassword(password, newPassword) {
root.privacyModule.changePassword(password, newPassword)
@ -33,8 +28,4 @@ QtObject {
function validatePassword(password) {
return root.privacyModule.validatePassword(password)
}
function setProfilePicturesVisibility(value) {
return root.privacyModule.setProfilePicturesVisibility(value)
}
}

View File

@ -16,12 +16,13 @@ QtObject {
advancedModule: profileSectionModuleInst.advancedModule
}
property DevicesStore devicesStore: DevicesStore {
devicesModule: profileSectionModuleInst.devicesModule
property MessagingStore messagingStore: MessagingStore {
privacyModule: profileSectionModuleInst.privacyModule
syncModule: profileSectionModuleInst.syncModule
}
property SyncStore syncStore: SyncStore {
syncModule: profileSectionModuleInst.syncModule
property DevicesStore devicesStore: DevicesStore {
devicesModule: profileSectionModuleInst.devicesModule
}
property NotificationsStore notificationsStore: NotificationsStore {
@ -52,16 +53,13 @@ QtObject {
property bool browserMenuItemEnabled: localAccountSensitiveSettings.isBrowserEnabled
property bool appsMenuItemsEnabled: localAccountSensitiveSettings.isWalletEnabled
property bool walletMenuItemEnabled: localAccountSensitiveSettings.isWalletEnabled
property ListModel mainMenuItems: ListModel {
Component.onCompleted: {
append({subsection: Constants.settingsSubsection.profile,
text: qsTr("Profile"),
icon: "profile"})
append({subsection: Constants.settingsSubsection.contacts,
text: qsTr("Contacts"),
icon: "contact"})
append({subsection: Constants.settingsSubsection.ensUsernames,
text: qsTr("ENS usernames"),
icon: "username"})
@ -70,6 +68,9 @@ QtObject {
property ListModel appsMenuItems: ListModel {
Component.onCompleted: {
append({subsection: Constants.settingsSubsection.messaging,
text: qsTr("Messaging"),
icon: "chat"})
append({subsection: Constants.settingsSubsection.wallet,
text: qsTr("Wallet"),
icon: "wallet"})
@ -96,9 +97,6 @@ QtObject {
append({subsection: Constants.settingsSubsection.notifications,
text: qsTr("Notifications"),
icon: "notification"})
append({subsection: Constants.settingsSubsection.syncSettings,
text: qsTr("Sync settings"),
icon: "mobile"})
append({subsection: Constants.settingsSubsection.devicesSettings,
text: qsTr("Devices settings"),
icon: "mobile"})

View File

@ -1,30 +0,0 @@
import QtQuick 2.13
import utils 1.0
QtObject {
id: root
property var syncModule
property var mailservers: syncModule.model
// Module Properties
property bool automaticMailserverSelection: syncModule.automaticSelection
property string activeMailserver: syncModule.activeMailserver
function getMailserverNameForNodeAddress(nodeAddress) {
return root.syncModule.getMailserverNameForNodeAddress(nodeAddress)
}
function setActiveMailserver(nodeAddress) {
root.syncModule.setActiveMailserver(nodeAddress)
}
function saveNewMailserver(name, nodeAddress) {
root.syncModule.saveNewMailserver(name, nodeAddress)
}
function enableAutomaticMailserverSelection(checked) {
root.syncModule.enableAutomaticSelection(checked)
}
}

View File

@ -8,6 +8,7 @@ import StatusQ.Core.Theme 0.1
import utils 1.0
import StatusQ.Core 0.1
import shared.views 1.0
import shared.panels 1.0
import shared.popups 1.0
@ -30,16 +31,39 @@ Item {
clip: true
Item {
anchors.top: parent.top
anchors.topMargin: 32
anchors.bottom: parent.bottom
height: parent.height
width: profileContentWidth
anchors.horizontalCenter: parent.horizontalCenter
StatusFlatButton {
icon.name: "arrow-left"
icon.width: 20
icon.height: 20
text: qsTr("Messaging")
size: StatusBaseButton.Size.Large
anchors.top: parent.top
anchors.topMargin: 8
anchors.left: parent.left
anchors.leftMargin: -40
onClicked: Global.changeAppSectionBySectionType(Constants.appSection.profile,
Constants.settingsSubsection.messaging)
}
StatusBaseText {
id: titleText
text: qsTr("Contacts")
font.weight: Font.Bold
font.pixelSize: 28
color: Theme.palette.directColor1
anchors.top: parent.top
anchors.topMargin: 56
}
SearchBox {
id: searchBox
anchors.top: parent.top
anchors.top: titleText.bottom
anchors.topMargin: 32
fontPixelSize: 15
}

View File

@ -34,12 +34,13 @@ Item {
MenuPanel {
id: profileMenu
privacyStore: store.privacyStore
messagingStore: store.messagingStore
mainMenuItems: store.mainMenuItems
settingsMenuItems: store.settingsMenuItems
extraMenuItems: store.extraMenuItems
appsMenuItems: store.appsMenuItems
browserMenuItemEnabled: store.browserMenuItemEnabled
appsMenuItemsEnabled: store.appsMenuItemsEnabled
walletMenuItemEnabled: store.walletMenuItemEnabled
onMenuItemClicked: {
if (menu_item.subsection === Constants.settingsSubsection.signout) {

View File

@ -0,0 +1,444 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
import utils 1.0
import shared 1.0
import shared.panels 1.0
import shared.popups 1.0
import shared.status 1.0
import shared.controls 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import "../stores"
import "../controls"
import "../popups"
import "../panels"
ScrollView {
id: root
property int profileContentWidth
height: parent.height
width: parent.width
contentHeight: advancedContainer.height + 100
clip: true
property MessagingStore messagingStore
Item {
id: advancedContainer
width: profileContentWidth
anchors.horizontalCenter: parent.horizontalCenter
height: generalColumn.height
ButtonGroup {
id: showProfilePictureToGroup
}
ButtonGroup {
id: seeProfilePicturesFromGroup
}
ButtonGroup {
id: browserGroup
}
Column {
id: generalColumn
spacing: 18
anchors.top: parent.top
anchors.topMargin: 24
anchors.left: parent.left
anchors.right: parent.right
StatusBaseText {
id: titleText
text: qsTr("Messaging")
font.weight: Font.Bold
font.pixelSize: 28
color: Theme.palette.directColor1
}
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
//% "Allow new contact requests"
title: qsTrId("allow-new-contact-requests")
implicitHeight: 64
components: [
StatusSwitch {
id: switch3
checked: !root.messagingStore.privacyModule.messagesFromContactsOnly
onCheckedChanged: {
// messagesFromContactsOnly needs to be accessed from the module (view),
// because otherwise doing `messagesFromContactsOnly = value` only changes the bool property on QML
if (root.messagingStore.privacyModule.messagesFromContactsOnly === checked) {
root.messagingStore.privacyModule.messagesFromContactsOnly = !checked
}
}
}
]
sensor.onClicked: {
switch3.checked = !switch3.checked
}
}
// SHOW PROFILE PICTURE TO
StatusBaseText {
text: qsTr("Show My Profile Picture To")
font.pixelSize: 15
color: Theme.palette.directColor1
}
SettingsRadioButton {
label: qsTr("Everyone")
group: showProfilePictureToGroup
checked: root.messagingStore.profilePicturesShowTo ===
Constants.profilePicturesShowTo.everyone
onClicked: root.messagingStore.setProfilePicturesShowTo(
Constants.profilePicturesShowTo.everyone
)
}
SettingsRadioButton {
label: qsTr("Contacts")
group: showProfilePictureToGroup
checked: root.messagingStore.profilePicturesShowTo ===
Constants.profilePicturesShowTo.contactsOnly
onClicked: root.messagingStore.setProfilePicturesShowTo(
Constants.profilePicturesShowTo.contactsOnly
)
}
SettingsRadioButton {
label: qsTr("No One")
group: showProfilePictureToGroup
checked: root.messagingStore.profilePicturesShowTo ===
Constants.profilePicturesShowTo.noOne
onClicked: root.messagingStore.setProfilePicturesShowTo(
Constants.profilePicturesShowTo.noOne
)
}
Item {
id: spacer1
width: parent.width
height: 6
}
// SEE PROFILTE PICTURES FROM
StatusBaseText {
text: qsTr("See Profile Pictures From")
font.pixelSize: 15
color: Theme.palette.directColor1
}
SettingsRadioButton {
label: qsTr("Everyone")
group: seeProfilePicturesFromGroup
checked: root.messagingStore.profilePicturesVisibility ===
Constants.profilePicturesVisibility.everyone
onClicked: root.messagingStore.setProfilePicturesVisibility(
Constants.profilePicturesVisibility.everyone
)
}
SettingsRadioButton {
label: qsTr("Contacts")
group: seeProfilePicturesFromGroup
checked: root.messagingStore.profilePicturesVisibility ===
Constants.profilePicturesVisibility.contactsOnly
onClicked: root.messagingStore.setProfilePicturesVisibility(
Constants.profilePicturesVisibility.contactsOnly
)
}
SettingsRadioButton {
label: qsTr("No One")
group: seeProfilePicturesFromGroup
checked: root.messagingStore.profilePicturesVisibility ===
Constants.profilePicturesVisibility.noOne
onClicked: root.messagingStore.setProfilePicturesVisibility(
Constants.profilePicturesVisibility.noOne
)
}
Item {
id: spacer2
width: parent.width
height: 6
}
// Open Message Links With
StatusBaseText {
text: qsTr("Open Message Links With")
font.pixelSize: 15
color: Theme.palette.directColor1
}
SettingsRadioButton {
label: qsTr("Status Browser")
group: browserGroup
checked: localAccountSensitiveSettings.openLinksInStatus
onClicked: {
localAccountSensitiveSettings.openLinksInStatus = true
}
}
SettingsRadioButton {
label: qsTr("System Default Browser")
group: browserGroup
checked: !localAccountSensitiveSettings.openLinksInStatus
onClicked: {
localAccountSensitiveSettings.openLinksInStatus = false
}
}
Separator {
id: separator1
}
// CONTACTS SECTION
StatusContactRequestsIndicatorListItem {
title: qsTr("Contacts, Requests, and Blocked Users")
requestsCount: root.messagingStore.contactRequestsModel.count
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
sensor.onClicked: Global.changeAppSectionBySectionType(Constants.appSection.profile,
Constants.settingsSubsection.contacts)
}
Separator {
id: separator2
}
// MESSAGE LINK PREVIEWS
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
title: qsTr("Display Message Link Previews")
implicitHeight: 64
components: [
StatusSwitch {
id: showMessageLinksSwitch
checked: false
onCheckedChanged: {
if (checked === false) {
// Switch all the whitelists to false
imageSwitch.checked = false
for (let i = 0; i < sitesListView.count; i++) {
let item = sitesListView.itemAt(i)
item.whitelistSwitch.checked = false
}
}
}
}
]
sensor.onClicked: {
showMessageLinksSwitch.checked = !showMessageLinksSwitch.checked
}
}
function populatePreviewableSites() {
let whitelistAsString = root.messagingStore.getLinkPreviewWhitelist()
if(whitelistAsString == "")
return
let whitelist = JSON.parse(whitelistAsString)
if (!localAccountSensitiveSettings.whitelistedUnfurlingSites) {
localAccountSensitiveSettings.whitelistedUnfurlingSites = {}
}
previewableSites.clear()
var oneEntryIsActive = false
whitelist.forEach(entry => {
entry.isWhitelisted = localAccountSensitiveSettings.whitelistedUnfurlingSites[entry.address] || false
if (entry.isWhitelisted) {
oneEntryIsActive = true
}
previewableSites.append(entry)
})
if (oneEntryIsActive) {
showMessageLinksSwitch.checked = true
}
}
Component.onCompleted: {
populatePreviewableSites()
}
Column {
id: siteColumn
visible: showMessageLinksSwitch.checked
width: parent.width
StatusSectionHeadline {
id: labelWebsites
text: qsTr("Fine tune which sites to allow link previews")
width: parent.width
}
ListModel {
id: previewableSites
}
Connections {
target: Global
onSettingsLoaded: {
generalColumn.populatePreviewableSites()
}
}
// Manually add switch for the image unfurling
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
implicitHeight: 64
title: qsTr("Image unfurling")
subTitle: qsTr("All images (links that contain an image extension) will be downloaded and displayed")
// TODO find a better icon for this
image.source: Style.svg('globe')
Component.onCompleted: {
if (localAccountSensitiveSettings.displayChatImages) {
showMessageLinksSwitch.checked = true
}
}
components: [
StatusSwitch {
id: imageSwitch
checked: localAccountSensitiveSettings.displayChatImages
onCheckedChanged: {
if (localAccountSensitiveSettings.displayChatImages !== checked) {
localAccountSensitiveSettings.displayChatImages = checked
}
}
}
]
sensor.onClicked: {
imageSwitch.checked = !imageSwitch.checked
}
}
Repeater {
id: sitesListView
model: previewableSites
delegate: Component {
StatusListItem {
property alias whitelistSwitch: siteSwitch
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
implicitHeight: 64
title: model.title
subTitle: model.address
image.source: {
let filename;
switch (model.title.toLowerCase()) {
case "youtube":
case "youtube shortener":
filename = "youtube"; break;
case "github":
filename = "github"; break;
case "medium":
filename = "medium"; break;
case "tenor gifs":
filename = "tenor"; break;
case "giphy gifs":
case "giphy gifs shortener":
case "giphy gifs subdomain":
filename = "giphy"; break;
case "github":
filename = "github"; break;
case "status":
filename = "status"; break;
// TODO get a good default icon
default: filename = "../globe"
}
return Style.svg(`linkPreviewThumbnails/${filename}`)
}
components: [
StatusSwitch {
id: siteSwitch
checked: !!model.isWhitelisted
onCheckedChanged: {
let settings = localAccountSensitiveSettings.whitelistedUnfurlingSites
if (!settings) {
settings = {}
}
if (settings[address] === this.checked) {
return
}
settings[address] = this.checked
localAccountSensitiveSettings.whitelistedUnfurlingSites = settings
}
}
]
sensor.onClicked: {
siteSwitch.checked = !siteSwitch.checked
}
}
}
}
} // Site Column
Separator {
id: separator3
visible: siteColumn.visible
}
// SYNC WAKU SECTION
StatusSectionHeadline {
text: qsTr("Message syncing")
width: parent.width
}
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
title: qsTr("Waku nodes")
label: root.messagingStore.getMailserverNameForNodeAddress(root.messagingStore.activeMailserver)
components: [
StatusIcon {
icon: "chevron-down"
rotation: 270
color: Theme.palette.baseColor1
}
]
sensor.onClicked: Global.openPopup(wakuNodeModalComponent)
}
Component {
id: wakuNodeModalComponent
WakuNodesModal {
messagingStore: root.messagingStore
}
}
StatusSectionHeadline {
text: qsTr("For security reasons, private chat history won't be synced.")
width: parent.width
}
} // Column
} // Item
} // ScrollView

View File

@ -129,168 +129,5 @@ Item {
id: successPopup
anchors.centerIn: parent
}
Item {
id: spacer1
height: Style.current.bigPadding
width: parent.width
}
Separator {
id: separator
}
StatusSectionHeadline {
id: labelPrivacy
//% "Privacy"
text: qsTrId("privacy")
topPadding: Style.current.padding
bottomPadding: Style.current.halfPadding
}
// TODO change this component from a switch to a chooser between, everyone, contacts and no one
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
//% "Display all profile pictures (not only contacts)"
title: qsTrId("display-all-profile-pictures--not-only-contacts-")
implicitHeight: 52
components: [
StatusQControls.StatusSwitch {
id: switch1
checked: root.privacyStore.profilePicturesVisibility ===
Constants.profilePicturesVisibility.everyone
onCheckedChanged: {
if (checked && root.privacyStore.profilePicturesVisibility !==
Constants.profilePicturesVisibility.everyone) {
root.privacyStore.setProfilePicturesVisibility(
Constants.profilePicturesVisibility.everyone
)
} else if (!checked && root.privacyStore.profilePicturesVisibility ===
Constants.profilePicturesVisibility.everyone) {
root.privacyStore.setProfilePicturesVisibility(
Constants.profilePicturesVisibility.contacts
)
}
}
}
]
sensor.onClicked: {
switch1.checked = !switch1.checked
}
}
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
//% "Display images in chat automatically"
title: qsTrId("display-images-in-chat-automatically")
implicitHeight: 52
components: [
StatusQControls.StatusSwitch {
id: switch2
checked: localAccountSensitiveSettings.displayChatImages
onCheckedChanged: {
if (localAccountSensitiveSettings.displayChatImages !== checked) {
localAccountSensitiveSettings.displayChatImages = checked
}
}
}
]
sensor.onClicked: {
switch2.checked = !switch2.checked
}
}
StatusBaseText {
width: parent.width
//% "All images (links that contain an image extension) will be downloaded and displayed, regardless of the whitelist settings below"
text: qsTrId("all-images--links-that-contain-an-image-extension--will-be-downloaded-and-displayed--regardless-of-the-whitelist-settings-below")
font.pixelSize: 15
font.weight: Font.Thin
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
bottomPadding: Style.current.smallPadding
}
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
//% "Chat link previews"
title: qsTrId("chat-link-previews")
implicitHeight: 52
components: [
StatusIcon {
icon: "chevron-down"
rotation: 270
color: Theme.palette.baseColor1
}
]
sensor.onClicked: Global.openPopup(chatLinksPreviewModal)
}
Component {
id: chatLinksPreviewModal
ChatLinksPreviewModal {
privacyStore: root.privacyStore
}
}
Component {
id: openLinksWithModal
OpenLinksWithModal {}
}
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
//% "Open links with..."
title: qsTrId("open-links-with---")
implicitHeight: 52
//% "My default browser"
label: localAccountSensitiveSettings.openLinksInStatus ? "Status" : qsTrId("my-default-browser")
components: [
StatusIcon {
icon: "chevron-down"
rotation: 270
color: Theme.palette.baseColor1
}
]
sensor.onClicked: Global.openPopup(openLinksWithModal)
}
StatusListItem {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
//% "Allow new contact requests"
title: qsTrId("allow-new-contact-requests")
implicitHeight: 52
components: [
StatusQControls.StatusSwitch {
id: switch3
checked: !root.privacyStore.privacyModule.messagesFromContactsOnly
onCheckedChanged: {
// messagesFromContactsOnly needs to be accessed from the module (view),
// because otherwise doing `messagesFromContactsOnly = value` only changes the bool property on QML
if (root.privacyStore.privacyModule.messagesFromContactsOnly === checked) {
root.privacyStore.privacyModule.messagesFromContactsOnly = !checked
}
}
}
]
sensor.onClicked: {
switch3.checked = !switch3.checked
}
}
}
}

View File

@ -1,194 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import utils 1.0
import shared.popups 1.0
import shared.controls 1.0
import "../stores"
Item {
id: root
property SyncStore syncStore
property int profileContentWidth
Layout.fillHeight: true
Layout.fillWidth: true
Item {
width: profileContentWidth
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
Component {
id: mailserversList
// TODO: Replace with StatusQ component
StatusRadioButton {
id: rbSetMailsever
text: name
checked: nodeAddress === root.syncStore.activeMailserver
onClicked: {
if (checked) {
root.syncStore.setActiveMailserver(nodeAddress)
}
}
}
}
Item {
id: addMailserver
width: parent.width
height: addButton.height
anchors.top: parent.top
anchors.topMargin: 24
anchors.left: parent.left
anchors.leftMargin: 24
StatusFlatRoundButton {
id: addButton
icon.name: "add"
anchors.verticalCenter: parent.verticalCenter
}
StatusBaseText {
id: usernameText
//% "Add mailserver"
text: qsTrId("add-mailserver")
color: Theme.palette.primaryColor1
anchors.left: addButton.right
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: addButton.verticalCenter
font.pixelSize: 15
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: addMailserverPopup.open()
}
// TODO: replace with StatusModal
ModalPopup {
id: addMailserverPopup
//% "Add mailserver"
title: qsTrId("add-mailserver")
property string nameValidationError: ""
property string enodeValidationError: ""
function validate() {
nameValidationError = ""
enodeValidationError = ""
if (nameInput.text === "") {
//% "You need to enter a name"
nameValidationError = qsTrId("you-need-to-enter-a-name")
}
if (enodeInput.text === "") {
//% "You need to enter the enode address"
enodeValidationError = qsTrId("you-need-to-enter-the-enode-address")
}
return !nameValidationError && !enodeValidationError
}
onOpened: {
nameInput.text = "";
enodeInput.text = "";
nameValidationError = "";
enodeValidationError = "";
}
footer: StatusButton {
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
//% "Save"
text: qsTrId("save")
anchors.bottom: parent.bottom
enabled: nameInput.text !== "" && enodeInput.text !== ""
onClicked: {
if (!addMailserverPopup.validate()) {
return;
}
root.syncStore.saveNewMailserver(nameInput.text, enodeInput.text)
addMailserverPopup.close()
}
}
Input {
id: nameInput
//% "Name"
label: qsTrId("name")
//% "Specify a name"
placeholderText: qsTrId("specify-name")
validationError: addMailserverPopup.nameValidationError
}
Input {
id: enodeInput
//% "History node address"
label: qsTrId("history-node-address")
//% "enode://{enode-id}:{password}@{ip-address}:{port-number}"
placeholderText: qsTrId("enode----enode-id---password---ip-address---port-number-")
validationError: addMailserverPopup.enodeValidationError
anchors.top: nameInput.bottom
anchors.topMargin: Style.current.bigPadding
}
}
}
StatusBaseText {
id: switchLbl
//% "Automatic mailserver selection"
text: qsTrId("automatic-mailserver-selection")
anchors.left: parent.left
anchors.leftMargin: 24
anchors.top: addMailserver.bottom
anchors.topMargin: 24
color: Theme.palette.directColor1
}
StatusSwitch {
id: automaticSelectionSwitch
checked: root.syncStore.automaticMailserverSelection
onCheckedChanged: root.syncStore.enableAutomaticMailserverSelection(checked)
anchors.top: addMailserver.bottom
anchors.topMargin: Style.current.padding
anchors.left: switchLbl.right
anchors.leftMargin: Style.current.padding
}
StatusBaseText {
//% "..."
text: qsTr("Active mailserver: %1").arg(root.syncStore.getMailserverNameForNodeAddress(root.syncStore.activeMailserver))
anchors.left: parent.left
anchors.leftMargin: 24
anchors.top: switchLbl.bottom
anchors.topMargin: 24
visible: automaticSelectionSwitch.checked
color: Theme.palette.directColor1
}
ListView {
id: mailServersListView
anchors.topMargin: 20
anchors.top: automaticSelectionSwitch.bottom
anchors.bottom: parent.bottom
model: root.syncStore.mailservers
delegate: mailserversList
visible: !automaticSelectionSwitch.checked
}
}
}

View File

@ -42,6 +42,7 @@ QtObject {
property var contactStore: profileSectionStore.contactsStore
property var privacyStore: profileSectionStore.privacyStore
property var messagingStore: profileSectionStore.messagingStore
property bool hasAddedContacts: contactStore.myContactsModel.count > 0
property var assets: walletSectionAccountTokens.model

View File

@ -860,7 +860,7 @@ Item {
Component.onCompleted: {
Global.appMain = this;
const whitelist = appMain.rootStore.privacyStore.getLinkPreviewWhitelist()
const whitelist = appMain.rootStore.messagingStore.getLinkPreviewWhitelist()
try {
const whiteListedSites = JSON.parse(whitelist)
let settingsUpdated = false

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5 18.6667V12C20.5 7.30558 16.6944 3.5 12 3.5C7.30558 3.5 3.5 7.30558 3.5 12C3.5 16.6944 7.30558 20.5 12 20.5H18.6667C19.6792 20.5 20.5 19.6792 20.5 18.6667ZM12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22H18.6667C20.5076 22 22 20.5076 22 18.6667V12C22 6.47715 17.5228 2 12 2Z" fill="#4360DF"/>
</svg>

After

Width:  |  Height:  |  Size: 459 B

View File

@ -0,0 +1,40 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
Item {
property string label
property alias checked: radioButton.checked
property ButtonGroup group
signal clicked()
id: root
width: childrenRect.width
height: 24
StatusRadioButton {
id: radioButton
ButtonGroup.group: root.group
}
StatusBaseText {
text: root.label
font.pixelSize: 13
color: Theme.palette.directColor1
anchors.left: radioButton.right
anchors.leftMargin: 12
anchors.verticalCenter: radioButton.verticalCenter
}
MouseArea {
enabled: !radioButton.checked
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.clicked()
}
}

View File

@ -15,6 +15,7 @@ RecipientSelector 1.0 RecipientSelector.qml
SearchBox 1.0 SearchBox.qml
SeedPhraseTextArea 1.0 SeedPhraseTextArea.qml
SendToContractWarning 1.0 SendToContractWarning.qml
SettingsRadioButton 1.0 SettingsRadioButton.qml
StatusTabButton 1.0 StatusTabButton.qml
StyledButton 1.0 StyledButton.qml
StyledTextArea 1.0 StyledTextArea.qml

View File

@ -373,7 +373,7 @@ Column {
//% "Enable in Settings"
text: qsTrId("enable-in-settings")
onClicked: {
Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.privacyAndSecurity);
Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.messaging);
}
width: parent.width
anchors.top: sep1.bottom

View File

@ -31,13 +31,13 @@ QtObject {
property int profile: 0
property int contacts: 1
property int ensUsernames: 2
property int wallet: 3
property int privacyAndSecurity: 4
property int appearance: 5
property int sound: 6
property int language: 7
property int notifications: 8
property int syncSettings: 9
property int messaging: 3
property int wallet: 4
property int privacyAndSecurity: 5
property int appearance: 6
property int sound: 7
property int language: 8
property int notifications: 9
property int devicesSettings: 10
property int browserSettings: 11
property int advanced: 12
@ -85,6 +85,12 @@ QtObject {
readonly property int noOne: 3
}
readonly property QtObject profilePicturesShowTo: QtObject {
readonly property int contactsOnly: 1
readonly property int everyone: 2
readonly property int noOne: 3
}
readonly property int communityImported: 0
readonly property int communityImportingInProgress: 1
readonly property int communityImportingError: 2