mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-18 01:27:25 +00:00
refactor: make use of StatusQ StatusAppLayout
This first step in leveraging StatusQ components. This sets up the overall app layout, allowing us to use other sub view layouts provided by StatusQ, such as `StatusAppTwoPanelLayout`. This commit primarily touches the application navbar, leaving layout changes in dedicated views for future commits. Partially closes #2688
This commit is contained in:
parent
57e835114b
commit
f581f49dcf
@ -1,75 +0,0 @@
|
|||||||
import QtQuick 2.13
|
|
||||||
import QtQuick.Controls 2.13
|
|
||||||
import "../../../../shared"
|
|
||||||
import "../../../../shared/status"
|
|
||||||
import "../../../../imports"
|
|
||||||
import "../components"
|
|
||||||
|
|
||||||
StatusIconTabButton {
|
|
||||||
property string communityId: ""
|
|
||||||
property string name: "channelName"
|
|
||||||
property int unviewedMessagesCount: 0
|
|
||||||
property string image
|
|
||||||
property bool hasMentions: false
|
|
||||||
|
|
||||||
id: communityButton
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
iconSource: communityButton.image
|
|
||||||
anchors.topMargin: 0
|
|
||||||
|
|
||||||
section: Constants.community
|
|
||||||
|
|
||||||
checked: chatsModel.communities.activeCommunity.active && chatsModel.communities.activeCommunity.id === communityId
|
|
||||||
|
|
||||||
borderOnChecked: true
|
|
||||||
doNotHandleClick: true
|
|
||||||
onClicked: {
|
|
||||||
appMain.changeAppSection(Constants.chat)
|
|
||||||
chatsModel.communities.setActiveCommunity(communityId)
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusToolTip {
|
|
||||||
visible: communityButton.hovered
|
|
||||||
text: communityButton.name
|
|
||||||
delay: 50
|
|
||||||
orientation: "right"
|
|
||||||
x: communityButton.width + Style.current.padding
|
|
||||||
y: communityButton.height / 2 - height / 2 + 4
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: chatBadge
|
|
||||||
visible: unviewedMessagesCount > 0
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.right
|
|
||||||
anchors.leftMargin: -17
|
|
||||||
anchors.topMargin: 1
|
|
||||||
radius: height / 2
|
|
||||||
color: Style.current.blue
|
|
||||||
border.color: Style.current.background
|
|
||||||
border.width: 2
|
|
||||||
width: unviewedMessagesCount < 10 ? 22 : messageCount.width + 14
|
|
||||||
height: 22
|
|
||||||
Text {
|
|
||||||
id: messageCount
|
|
||||||
font.pixelSize: chatsModel.messageView.unreadMessagesCount > 99 ? 10 : 12
|
|
||||||
color: Style.current.white
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: unviewedMessagesCount > 99 ? "99+" : unviewedMessagesCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: function (mouse) {
|
|
||||||
if (mouse.button === Qt.RightButton) {
|
|
||||||
commnunityMenu.communityId = communityButton.communityId
|
|
||||||
commnunityMenu.popup()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
communityButton.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
import QtQuick 2.13
|
|
||||||
import QtQuick.Controls 2.13
|
|
||||||
import QtQuick.Layouts 1.13
|
|
||||||
import "../../../../shared"
|
|
||||||
import "../../../../imports"
|
|
||||||
import "../components"
|
|
||||||
import "./"
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
id: communityListView
|
|
||||||
spacing: 12
|
|
||||||
height: contentHeight
|
|
||||||
visible: height > 10
|
|
||||||
width:parent.width
|
|
||||||
interactive: false
|
|
||||||
verticalLayoutDirection: ListView.BottomToTop
|
|
||||||
|
|
||||||
model: chatsModel.communities.joinedCommunities
|
|
||||||
delegate: CommunityButton {
|
|
||||||
communityId: model.id
|
|
||||||
name: model.name
|
|
||||||
image: model.thumbnailImage
|
|
||||||
unviewedMessagesCount: model.unviewedMessagesCount
|
|
||||||
iconColor: model.communityColor || Style.current.blue
|
|
||||||
useLetterIdenticon: model.thumbnailImage === ""
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupMenu {
|
|
||||||
property string communityId
|
|
||||||
|
|
||||||
onAboutToShow: {
|
|
||||||
chatsModel.communities.setObservedCommunity(commnunityMenu.communityId)
|
|
||||||
}
|
|
||||||
|
|
||||||
id: commnunityMenu
|
|
||||||
Action {
|
|
||||||
text: qsTr("Invite People")
|
|
||||||
enabled: chatsModel.communities.observedCommunity.canManageUsers
|
|
||||||
icon.source: "../../../img/export.svg"
|
|
||||||
icon.width: 20
|
|
||||||
icon.height: 20
|
|
||||||
onTriggered: openPopup(inviteFriendsToCommunityPopup, {communityId: commnunityMenu.communityId})
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
text: qsTr("View Community")
|
|
||||||
icon.source: "../../../img/group.svg"
|
|
||||||
icon.width: 20
|
|
||||||
icon.height: 20
|
|
||||||
onTriggered: openPopup(communityMembersPopup, {community: chatsModel.communities.observedCommunity})
|
|
||||||
}
|
|
||||||
Separator {
|
|
||||||
height: 10
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
text: qsTr("Edit Community")
|
|
||||||
// TODO reenable this option once the edit feature is done
|
|
||||||
enabled: false//chatsModel.communities.observedCommunity.admin
|
|
||||||
icon.source: "../../../img/edit.svg"
|
|
||||||
icon.width: 20
|
|
||||||
icon.height: 20
|
|
||||||
onTriggered: openPopup(editCommunityPopup, {community: chatsModel.communities.observedCommunity})
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
text: qsTr("Leave Community")
|
|
||||||
icon.source: "../../../img/arrow-left.svg"
|
|
||||||
icon.width: 12
|
|
||||||
icon.height: 9
|
|
||||||
onTriggered: chatsModel.communities.leaveCommunity(commnunityMenu.communityId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -163,7 +163,7 @@ Rectangle {
|
|||||||
leftPadding: Style.current.halfPadding
|
leftPadding: Style.current.halfPadding
|
||||||
rightPadding: Style.current.halfPadding
|
rightPadding: Style.current.halfPadding
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
contentHeight: communitiesListLoader.height + channelList.height + 2 * Style.current.padding + emptyViewAndSuggestions.height
|
contentHeight: channelList.height + 2 * Style.current.padding + emptyViewAndSuggestions.height
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
ChannelList {
|
ChannelList {
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
import QtQuick 2.3
|
|
||||||
import QtQuick.Controls 2.3
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import Qt.labs.platform 1.1
|
|
||||||
import "../../../../imports"
|
|
||||||
import "../../../../shared"
|
|
||||||
import "../../../../shared/status"
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 12
|
|
||||||
width: parent.width
|
|
||||||
height: childrenRect.height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 40
|
|
||||||
height: 1
|
|
||||||
color: Style.current.appBarDividerColor
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusIconTabButton {
|
|
||||||
id: walletBtn
|
|
||||||
enabled: isExperimental === "1" || appSettings.isWalletEnabled
|
|
||||||
icon.name: "wallet"
|
|
||||||
icon.width: 20
|
|
||||||
icon.height: 20
|
|
||||||
section: Constants.wallet
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusIconTabButton {
|
|
||||||
id: browserBtn
|
|
||||||
enabled: isExperimental === "1" || appSettings.isBrowserEnabled
|
|
||||||
icon.name: "compass"
|
|
||||||
icon.width: 22
|
|
||||||
icon.height: 22
|
|
||||||
section: Constants.browser
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusIconTabButton {
|
|
||||||
id: timelineBtn
|
|
||||||
enabled: isExperimental === "1" || appSettings.timelineEnabled
|
|
||||||
icon.name: "timeline"
|
|
||||||
icon.width: 22
|
|
||||||
icon.height: 22
|
|
||||||
section: Constants.timeline
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusIconTabButton {
|
|
||||||
id: profileBtn
|
|
||||||
icon.name: "profile"
|
|
||||||
icon.width: 22
|
|
||||||
icon.height: 22
|
|
||||||
section: Constants.profile
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: profileBadge
|
|
||||||
visible: !profileModel.mnemonic.isBackedUp && sLayout.children[sLayout.currentIndex] !== profileLayoutContainer
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 4
|
|
||||||
anchors.topMargin: 5
|
|
||||||
radius: height / 2
|
|
||||||
color: Style.current.blue
|
|
||||||
border.color: profileBtn.hovered ? Style.current.secondaryBackground : Style.current.mainMenuBackground
|
|
||||||
border.width: 2
|
|
||||||
width: 14
|
|
||||||
height: 14
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusIconTabButton {
|
|
||||||
id: nodeBtn
|
|
||||||
enabled: isExperimental === "1" && appSettings.nodeManagementEnabled
|
|
||||||
icon.name: "node"
|
|
||||||
section: Constants.node
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusIconTabButton {
|
|
||||||
id: uiComponentBtn
|
|
||||||
enabled: isExperimental === "1"
|
|
||||||
icon.name: "node"
|
|
||||||
section: Constants.ui
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,16 +13,22 @@ import "./AppLayouts/Chat/components"
|
|||||||
import "./AppLayouts/Chat/CommunityComponents"
|
import "./AppLayouts/Chat/CommunityComponents"
|
||||||
import Qt.labs.settings 1.0
|
import Qt.labs.settings 1.0
|
||||||
|
|
||||||
RowLayout {
|
import StatusQ.Core.Theme 0.1
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Layout 0.1
|
||||||
|
import StatusQ.Popups 0.1
|
||||||
|
|
||||||
|
StatusAppLayout {
|
||||||
id: appMain
|
id: appMain
|
||||||
property int currentView: sLayout.currentIndex
|
anchors.fill: parent
|
||||||
property bool popupOpened: false
|
|
||||||
spacing: 0
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
property alias appSettings: appSettings
|
property alias appSettings: appSettings
|
||||||
|
signal settingsLoaded()
|
||||||
|
|
||||||
|
function changeAppSection(section) {
|
||||||
|
chatsModel.communities.activeCommunity.active = false
|
||||||
|
appView.currentIndex = Utils.getAppSectionIndex(section)
|
||||||
|
}
|
||||||
|
|
||||||
function getProfileImage(pubkey, isCurrentUser, useLargeImage) {
|
function getProfileImage(pubkey, isCurrentUser, useLargeImage) {
|
||||||
if (isCurrentUser || (isCurrentUser === undefined && pubkey === profileModel.profile.pubKey)) {
|
if (isCurrentUser || (isCurrentUser === undefined && pubkey === profileModel.profile.pubKey)) {
|
||||||
@ -86,7 +92,6 @@ RowLayout {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function openLink(link) {
|
function openLink(link) {
|
||||||
if (appSettings.showBrowserSelector) {
|
if (appSettings.showBrowserSelector) {
|
||||||
appMain.openPopup(chooseBrowserPopupComponent, {link: link})
|
appMain.openPopup(chooseBrowserPopupComponent, {link: link})
|
||||||
@ -100,401 +105,132 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signal settingsLoaded()
|
|
||||||
|
|
||||||
Settings {
|
appNavBar: StatusAppNavBar {
|
||||||
id: appSettings
|
height: appMain.height
|
||||||
fileName: profileModel.profileSettingsFile
|
|
||||||
property var chatSplitView
|
|
||||||
property var walletSplitView
|
|
||||||
property var profileSplitView
|
|
||||||
property bool communitiesEnabled: false
|
|
||||||
property bool isWalletEnabled: false
|
|
||||||
property bool nodeManagementEnabled: false
|
|
||||||
property bool isBrowserEnabled: false
|
|
||||||
property bool isActivityCenterEnabled: false
|
|
||||||
property bool displayChatImages: false
|
|
||||||
property bool useCompactMode: true
|
|
||||||
property bool timelineEnabled: true
|
|
||||||
property var recentEmojis: []
|
|
||||||
property var hiddenCommunityWelcomeBanners: []
|
|
||||||
property var hiddenCommunityBackUpBanners: []
|
|
||||||
property real volume: 0.2
|
|
||||||
property int notificationSetting: Constants.notifyAllMessages
|
|
||||||
property bool notificationSoundsEnabled: true
|
|
||||||
property bool useOSNotifications: true
|
|
||||||
property int notificationMessagePreviewSetting: Constants.notificationPreviewNameAndMessage
|
|
||||||
property bool notifyOnNewRequests: true
|
|
||||||
property var whitelistedUnfurlingSites: ({})
|
|
||||||
property bool neverAskAboutUnfurlingAgain: false
|
|
||||||
property bool hideChannelSuggestions: false
|
|
||||||
property int fontSize: Constants.fontSizeM
|
|
||||||
property bool hideSignPhraseModal: false
|
|
||||||
property bool onlyShowContactsProfilePics: true
|
|
||||||
property bool quitOnClose: false
|
|
||||||
|
|
||||||
// Browser settings
|
navBarChatButton: StatusNavBarTabButton {
|
||||||
property bool showBrowserSelector: true
|
icon.name: "chat"
|
||||||
property bool openLinksInStatus: true
|
checked: !chatsModel.communities.activeCommunity.active && appView.currentIndex === Utils.getAppSectionIndex(Constants.chat)
|
||||||
property bool shouldShowFavoritesBar: true
|
tooltip.text: qsTr("Chat")
|
||||||
property string browserHomepage: ""
|
badge.value: chatsModel.messageView.unreadMessagesCount + profileModel.contacts.contactRequests.count
|
||||||
property int shouldShowBrowserSearchEngine: Constants.browserSearchEngineDuckDuckGo
|
badge.visible: badge.value > 0
|
||||||
property int useBrowserEthereumExplorer: Constants.browserEthereumExplorerEtherscan
|
onClicked: {
|
||||||
property bool autoLoadImages: true
|
if (chatsModel.communities.activeCommunity.active) {
|
||||||
property bool javaScriptEnabled: true
|
chatLayoutContainer.chatColumn.input.hideExtendedArea();
|
||||||
property bool errorPageEnabled: true
|
chatsModel.communities.activeCommunity.active = false
|
||||||
property bool pluginsEnabled: true
|
}
|
||||||
property bool autoLoadIconsForPage: true
|
appMain.changeAppSection(Constants.chat)
|
||||||
property bool touchIconsEnabled: true
|
}
|
||||||
property bool webRTCPublicInterfacesOnly: false
|
}
|
||||||
property bool devToolsEnabled: false
|
|
||||||
property bool pdfViewerEnabled: true
|
|
||||||
property bool compatibilityMode: true
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorSound {
|
navBarCommunityTabButtons.model: chatsModel.communities.joinedCommunities
|
||||||
id: errorSound
|
navBarCommunityTabButtons.delegate: StatusNavBarTabButton {
|
||||||
}
|
onClicked: {
|
||||||
|
appMain.changeAppSection(Constants.chat)
|
||||||
Audio {
|
chatsModel.communities.setActiveCommunity(model.id)
|
||||||
id: sendMessageSound
|
|
||||||
audioRole: Audio.NotificationRole
|
|
||||||
source: "../../../../sounds/send_message.wav"
|
|
||||||
volume: appSettings.volume
|
|
||||||
muted: !appSettings.notificationSoundsEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
Audio {
|
|
||||||
id: notificationSound
|
|
||||||
audioRole: Audio.NotificationRole
|
|
||||||
source: "../../../../sounds/notification.wav"
|
|
||||||
volume: appSettings.volume
|
|
||||||
muted: !appSettings.notificationSoundsEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: profileModel
|
|
||||||
onProfileSettingsFileChanged: {
|
|
||||||
profileModel.changeLocale(globalSettings.locale)
|
|
||||||
|
|
||||||
|
|
||||||
// Since https://github.com/status-im/status-desktop/commit/93668ff75
|
|
||||||
// we're hiding the setting to change appearance for compact normal mode
|
|
||||||
// of the UI. For now, compact mode is the new default.
|
|
||||||
//
|
|
||||||
// Prior to this change, most likely many users are still using the
|
|
||||||
// normal mode configuration, so we have to enforce compact mode for
|
|
||||||
// those.
|
|
||||||
if (!appSettings.useCompactMode) {
|
|
||||||
appSettings.useCompactMode = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const whitelist = profileModel.getLinkPreviewWhitelist()
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
try {
|
|
||||||
const whiteListedSites = JSON.parse(whitelist)
|
|
||||||
let settingsUpdated = false
|
|
||||||
|
|
||||||
// Add Status links to whitelist
|
checked: chatsModel.communities.activeCommunity.active && chatsModel.communities.activeCommunity.id === model.id
|
||||||
whiteListedSites.push({title: "Status", address: Constants.deepLinkPrefix, imageSite: false})
|
name: model.name
|
||||||
whiteListedSites.push({title: "Status", address: Constants.joinStatusLink, imageSite: false})
|
tooltip.text: model.name
|
||||||
|
icon.color: model.communityColor
|
||||||
|
icon.source: model.thumbnailImage
|
||||||
|
|
||||||
const settings = appSettings.whitelistedUnfurlingSites
|
badge.visible: model.unviewedMessagesCount > 0
|
||||||
|
badge.value: model.unviewedMessagesCount
|
||||||
|
badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusBadge.borderColor
|
||||||
|
badge.border.width: 2
|
||||||
|
|
||||||
// Set Status links as true. We intercept thoseURLs so it is privacy-safe
|
popupMenu: StatusPopupMenu {
|
||||||
if (!settings[Constants.deepLinkPrefix] || !settings[Constants.joinStatusLink]) {
|
id: communityContextMenu
|
||||||
settings[Constants.deepLinkPrefix] = true
|
|
||||||
settings[Constants.joinStatusLink] = true
|
openHandler: function () {
|
||||||
settingsUpdated = true
|
chatsModel.communities.setObservedCommunity(model.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const whitelistedHostnames = []
|
StatusMenuItem {
|
||||||
|
text: qsTr("Invite People")
|
||||||
// Add whitelisted sites in to app settings that are not already there
|
icon.name: "share-ios"
|
||||||
whiteListedSites.forEach(site => {
|
enabled: chatsModel.communities.observedCommunity.canManageUsers
|
||||||
if (!settings.hasOwnProperty(site.address)) {
|
onTriggered: openPopup(inviteFriendsToCommunityPopup, { communityId: model.id })
|
||||||
settings[site.address] = false
|
|
||||||
settingsUpdated = true
|
|
||||||
}
|
|
||||||
whitelistedHostnames.push(site.address)
|
|
||||||
})
|
|
||||||
// Remove any whitelisted sites from app settings that don't exist in the
|
|
||||||
// whitelist from status-go
|
|
||||||
Object.keys(settings).forEach(settingsHostname => {
|
|
||||||
if (!whitelistedHostnames.includes(settingsHostname)) {
|
|
||||||
delete settings[settingsHostname]
|
|
||||||
settingsUpdated = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (settingsUpdated) {
|
|
||||||
appSettings.whitelistedUnfurlingSites = settings
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Could not parse the whitelist for sites', e)
|
|
||||||
}
|
|
||||||
appMain.settingsLoaded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
target: profileModel
|
|
||||||
ignoreUnknownSignals: true
|
|
||||||
enabled: removeMnemonicAfterLogin
|
|
||||||
onInitialized: {
|
|
||||||
profileModel.mnemonic.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: chooseBrowserPopupComponent
|
|
||||||
ChooseBrowserPopup {
|
|
||||||
onClosed: {
|
|
||||||
destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: inviteFriendsToCommunityPopup
|
|
||||||
InviteFriendsToCommunityPopup {
|
|
||||||
onClosed: {
|
|
||||||
destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: communityMembersPopup
|
|
||||||
CommunityMembersPopup {
|
|
||||||
onClosed: {
|
|
||||||
destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: editCommunityPopup
|
|
||||||
CreateCommunityPopup {
|
|
||||||
isEdit: true
|
|
||||||
onClosed: {
|
|
||||||
destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: editChannelPopup
|
|
||||||
CreateChannelPopup {
|
|
||||||
isEdit: true
|
|
||||||
pinnedMessagesPopupComponent: chatLayoutContainer.chatColumn.pinnedMessagesPopupComponent
|
|
||||||
onClosed: {
|
|
||||||
destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToastMessage {
|
|
||||||
id: toastMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add SendModal here as it is used by the Wallet as well as the Browser
|
|
||||||
Loader {
|
|
||||||
id: sendModal
|
|
||||||
active: false
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
this.active = true
|
|
||||||
this.item.open()
|
|
||||||
}
|
|
||||||
function closed() {
|
|
||||||
// this.sourceComponent = undefined // kill an opened instance
|
|
||||||
this.active = false
|
|
||||||
}
|
|
||||||
sourceComponent: SendModal {
|
|
||||||
onOpened: {
|
|
||||||
walletModel.gasView.getGasPricePredictions()
|
|
||||||
}
|
|
||||||
onClosed: {
|
|
||||||
sendModal.closed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
shortcut: "Ctrl+1"
|
|
||||||
onTriggered: changeAppSection(Constants.chat)
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
shortcut: "Ctrl+2"
|
|
||||||
onTriggered: changeAppSection(Constants.browser)
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
shortcut: "Ctrl+3"
|
|
||||||
onTriggered: changeAppSection(Constants.wallet)
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
shortcut: "Ctrl+4, Ctrl+,"
|
|
||||||
onTriggered: changeAppSection(Constants.profile)
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
shortcut: "Ctrl+K"
|
|
||||||
onTriggered: {
|
|
||||||
if (channelPicker.opened) {
|
|
||||||
channelPicker.close()
|
|
||||||
} else {
|
|
||||||
channelPicker.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: statusIdenticonComponent
|
|
||||||
StatusIdenticon {}
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusInputListPopup {
|
|
||||||
id: channelPicker
|
|
||||||
//% "Where do you want to go?"
|
|
||||||
title: qsTrId("where-do-you-want-to-go-")
|
|
||||||
showSearchBox: true
|
|
||||||
width: 350
|
|
||||||
x: parent.width / 2 - width / 2
|
|
||||||
y: parent.height / 2 - height / 2
|
|
||||||
modelList: chatsModel.channelView.chats
|
|
||||||
getText: function (modelData) {
|
|
||||||
return modelData.name
|
|
||||||
}
|
|
||||||
getImageComponent: function (parent, modelData) {
|
|
||||||
return statusIdenticonComponent.createObject(parent, {
|
|
||||||
width: channelPicker.imageWidth,
|
|
||||||
height: channelPicker.imageHeight,
|
|
||||||
chatName: modelData.name,
|
|
||||||
chatType: modelData.chatType,
|
|
||||||
identicon: modelData.identicon
|
|
||||||
});
|
|
||||||
}
|
|
||||||
onClicked: function (index) {
|
|
||||||
chatsModel.channelView.setActiveChannelByIndex(index)
|
|
||||||
appMain.changeAppSection(Constants.chat)
|
|
||||||
channelPicker.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeAppSection(section) {
|
|
||||||
chatsModel.communities.activeCommunity.active = false
|
|
||||||
sLayout.currentIndex = Utils.getAppSectionIndex(section)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: leftTab
|
|
||||||
Layout.maximumWidth: 78
|
|
||||||
Layout.minimumWidth: 78
|
|
||||||
Layout.preferredWidth: 78
|
|
||||||
Layout.fillHeight: true
|
|
||||||
height: parent.height
|
|
||||||
color: Style.current.mainMenuBackground
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
id: scrollView
|
|
||||||
width: leftTab.width
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 50
|
|
||||||
anchors.bottom: leftTabButtons.visible ? leftTabButtons.top : parent.bottom
|
|
||||||
anchors.bottomMargin: tabBar.spacing
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: tabBar
|
|
||||||
spacing: 12
|
|
||||||
width: scrollView.width
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: communitiesListLoader
|
|
||||||
active: appSettings.communitiesEnabled
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
width: parent.width
|
|
||||||
height: {
|
|
||||||
if (item && active) {
|
|
||||||
return item.height
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
sourceComponent: Component {
|
|
||||||
CommunityList {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusIconTabButton {
|
StatusMenuItem {
|
||||||
id: chatBtn
|
text: qsTr("View Community")
|
||||||
icon.name: "message"
|
icon.name: "group-chat"
|
||||||
icon.width: 20
|
onTriggered: openPopup(communityMembersPopup, {community: chatsModel.communities.observedCommunity})
|
||||||
icon.height: 20
|
|
||||||
section: Constants.chat
|
|
||||||
doNotHandleClick: true
|
|
||||||
onClicked: {
|
|
||||||
if (chatsModel.communities.activeCommunity.active) {
|
|
||||||
chatLayoutContainer.chatColumn.input.hideExtendedArea();
|
|
||||||
chatsModel.communities.activeCommunity.active = false
|
|
||||||
}
|
|
||||||
appMain.changeAppSection(Constants.chat)
|
|
||||||
}
|
|
||||||
|
|
||||||
checked: !chatsModel.communities.activeCommunity.active && sLayout.currentIndex === Utils.getAppSectionIndex(Constants.chat)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
property int badgeCount: chatsModel.messageView.unreadMessagesCount + profileModel.contacts.contactRequests.count
|
|
||||||
|
|
||||||
id: chatBadge
|
|
||||||
visible: chatBadge.badgeCount > 0
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.right
|
|
||||||
anchors.leftMargin: -17
|
|
||||||
anchors.topMargin: 1
|
|
||||||
radius: height / 2
|
|
||||||
color: Style.current.blue
|
|
||||||
border.color: chatBtn.hovered ? Style.current.secondaryBackground : Style.current.mainMenuBackground
|
|
||||||
border.width: 2
|
|
||||||
width: chatBadge.badgeCount < 10 ? 22 : messageCount.width + 14
|
|
||||||
height: 22
|
|
||||||
Text {
|
|
||||||
id: messageCount
|
|
||||||
font.pixelSize: chatBadge.badgeCount > 99 ? 10 : 12
|
|
||||||
color: Style.current.white
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: chatBadge.badgeCount > 99 ? "99+" : chatBadge.badgeCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
StatusMenuItem {
|
||||||
active: !leftTabButtons.visible
|
enabled: false//chatsModel.communities.observedCommunity.admin
|
||||||
width: parent.width
|
text: qsTr("Edit Community")
|
||||||
height: {
|
icon.name: "edit"
|
||||||
if (item && active) {
|
onTriggered: openPopup(editCommunityPopup, {community: chatsModel.communities.observedCommunity})
|
||||||
return item.height
|
}
|
||||||
}
|
|
||||||
return 0
|
StatusMenuSeparator {}
|
||||||
}
|
|
||||||
sourceComponent: LeftTabBottomButtons {}
|
StatusMenuItem {
|
||||||
|
text: qsTr("Leave Community")
|
||||||
|
icon.name: "arrow-right"
|
||||||
|
icon.width: 14
|
||||||
|
iconRotation: 180
|
||||||
|
type: StatusMenuItem.Type.Danger
|
||||||
|
onTriggered: chatsModel.communities.leaveCommunity(model.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LeftTabBottomButtons {
|
navBarTabButtons: [
|
||||||
id: leftTabButtons
|
StatusNavBarTabButton {
|
||||||
visible: scrollView.contentHeight > leftTab.height
|
icon.name: "wallet"
|
||||||
anchors.bottom: parent.bottom
|
tooltip.text: qsTr("Wallet")
|
||||||
anchors.bottomMargin: Style.current.padding
|
visible: enabled
|
||||||
}
|
enabled: isExperimental === "1" || appSettings.isWalletEnabled
|
||||||
|
checked: appView.currentIndex == Utils.getAppSectionIndex(Constants.wallet)
|
||||||
|
onClicked: appMain.changeAppSection(Constants.wallet)
|
||||||
|
},
|
||||||
|
|
||||||
|
StatusNavBarTabButton {
|
||||||
|
enabled: isExperimental === "1" || appSettings.isBrowserEnabled
|
||||||
|
visible: enabled
|
||||||
|
tooltip.text: qsTr("Browser")
|
||||||
|
icon.name: "browser"
|
||||||
|
checked: appView.currentIndex == Utils.getAppSectionIndex(Constants.browser)
|
||||||
|
onClicked: appMain.changeAppSection(Constants.browser)
|
||||||
|
},
|
||||||
|
|
||||||
|
StatusNavBarTabButton {
|
||||||
|
enabled: isExperimental === "1" || appSettings.timelineEnabled
|
||||||
|
visible: enabled
|
||||||
|
tooltip.text: qsTr("Timeline")
|
||||||
|
icon.name: "status-update"
|
||||||
|
checked: appView.currentIndex == Utils.getAppSectionIndex(Constants.timeline)
|
||||||
|
onClicked: appMain.changeAppSection(Constants.timeline)
|
||||||
|
},
|
||||||
|
|
||||||
|
StatusNavBarTabButton {
|
||||||
|
id: profileBtn
|
||||||
|
tooltip.text: qsTr("Profile")
|
||||||
|
icon.name: "profile"
|
||||||
|
checked: appView.currentIndex == Utils.getAppSectionIndex(Constants.profile)
|
||||||
|
onClicked: appMain.changeAppSection(Constants.profile)
|
||||||
|
|
||||||
|
badge.visible: !profileModel.mnemonic.isBackedUp && appView.children[appView.currentIndex] !== profileLayoutContainer
|
||||||
|
badge.anchors.rightMargin: 4
|
||||||
|
badge.anchors.topMargin: 5
|
||||||
|
badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusAppNavBar.backgroundColor
|
||||||
|
badge.border.width: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
StackLayout {
|
appView: StackLayout {
|
||||||
id: sLayout
|
id: appView
|
||||||
Layout.fillWidth: true
|
anchors.fill: parent
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
|
||||||
Layout.fillHeight: true
|
|
||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
onCurrentIndexChanged: {
|
onCurrentIndexChanged: {
|
||||||
if (typeof this.children[currentIndex].onActivated === "function") {
|
if (typeof this.children[currentIndex].onActivated === "function") {
|
||||||
@ -578,6 +314,281 @@ RowLayout {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Settings {
|
||||||
|
id: appSettings
|
||||||
|
fileName: profileModel.profileSettingsFile
|
||||||
|
property var chatSplitView
|
||||||
|
property var walletSplitView
|
||||||
|
property var profileSplitView
|
||||||
|
property bool communitiesEnabled: false
|
||||||
|
property bool isWalletEnabled: false
|
||||||
|
property bool nodeManagementEnabled: false
|
||||||
|
property bool isBrowserEnabled: false
|
||||||
|
property bool isActivityCenterEnabled: false
|
||||||
|
property bool displayChatImages: false
|
||||||
|
property bool useCompactMode: true
|
||||||
|
property bool timelineEnabled: true
|
||||||
|
property var recentEmojis: []
|
||||||
|
property var hiddenCommunityWelcomeBanners: []
|
||||||
|
property var hiddenCommunityBackUpBanners: []
|
||||||
|
property real volume: 0.2
|
||||||
|
property int notificationSetting: Constants.notifyAllMessages
|
||||||
|
property bool notificationSoundsEnabled: true
|
||||||
|
property bool useOSNotifications: true
|
||||||
|
property int notificationMessagePreviewSetting: Constants.notificationPreviewNameAndMessage
|
||||||
|
property bool notifyOnNewRequests: true
|
||||||
|
property var whitelistedUnfurlingSites: ({})
|
||||||
|
property bool neverAskAboutUnfurlingAgain: false
|
||||||
|
property bool hideChannelSuggestions: false
|
||||||
|
property int fontSize: Constants.fontSizeM
|
||||||
|
property bool hideSignPhraseModal: false
|
||||||
|
property bool onlyShowContactsProfilePics: true
|
||||||
|
property bool quitOnClose: false
|
||||||
|
|
||||||
|
// Browser settings
|
||||||
|
property bool showBrowserSelector: true
|
||||||
|
property bool openLinksInStatus: true
|
||||||
|
property bool shouldShowFavoritesBar: true
|
||||||
|
property string browserHomepage: ""
|
||||||
|
property int shouldShowBrowserSearchEngine: Constants.browserSearchEngineDuckDuckGo
|
||||||
|
property int useBrowserEthereumExplorer: Constants.browserEthereumExplorerEtherscan
|
||||||
|
property bool autoLoadImages: true
|
||||||
|
property bool javaScriptEnabled: true
|
||||||
|
property bool errorPageEnabled: true
|
||||||
|
property bool pluginsEnabled: true
|
||||||
|
property bool autoLoadIconsForPage: true
|
||||||
|
property bool touchIconsEnabled: true
|
||||||
|
property bool webRTCPublicInterfacesOnly: false
|
||||||
|
property bool devToolsEnabled: false
|
||||||
|
property bool pdfViewerEnabled: true
|
||||||
|
property bool compatibilityMode: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorSound {
|
||||||
|
id: errorSound
|
||||||
|
}
|
||||||
|
|
||||||
|
Audio {
|
||||||
|
id: sendMessageSound
|
||||||
|
audioRole: Audio.NotificationRole
|
||||||
|
source: "../../../../sounds/send_message.wav"
|
||||||
|
volume: appSettings.volume
|
||||||
|
muted: !appSettings.notificationSoundsEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
Audio {
|
||||||
|
id: notificationSound
|
||||||
|
audioRole: Audio.NotificationRole
|
||||||
|
source: "../../../../sounds/notification.wav"
|
||||||
|
volume: appSettings.volume
|
||||||
|
muted: !appSettings.notificationSoundsEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: profileModel
|
||||||
|
onProfileSettingsFileChanged: {
|
||||||
|
profileModel.changeLocale(globalSettings.locale)
|
||||||
|
|
||||||
|
// Since https://github.com/status-im/status-desktop/commit/93668ff75
|
||||||
|
// we're hiding the setting to change appearance for compact normal mode
|
||||||
|
// of the UI. For now, compact mode is the new default.
|
||||||
|
//
|
||||||
|
// Prior to this change, most likely many users are still using the
|
||||||
|
// normal mode configuration, so we have to enforce compact mode for
|
||||||
|
// those.
|
||||||
|
if (!appSettings.useCompactMode) {
|
||||||
|
appSettings.useCompactMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const whitelist = profileModel.getLinkPreviewWhitelist()
|
||||||
|
try {
|
||||||
|
const whiteListedSites = JSON.parse(whitelist)
|
||||||
|
let settingsUpdated = false
|
||||||
|
|
||||||
|
// Add Status links to whitelist
|
||||||
|
whiteListedSites.push({title: "Status", address: Constants.deepLinkPrefix, imageSite: false})
|
||||||
|
whiteListedSites.push({title: "Status", address: Constants.joinStatusLink, imageSite: false})
|
||||||
|
|
||||||
|
const settings = appSettings.whitelistedUnfurlingSites
|
||||||
|
|
||||||
|
// Set Status links as true. We intercept thoseURLs so it is privacy-safe
|
||||||
|
if (!settings[Constants.deepLinkPrefix] || !settings[Constants.joinStatusLink]) {
|
||||||
|
settings[Constants.deepLinkPrefix] = true
|
||||||
|
settings[Constants.joinStatusLink] = true
|
||||||
|
settingsUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const whitelistedHostnames = []
|
||||||
|
|
||||||
|
// Add whitelisted sites in to app settings that are not already there
|
||||||
|
whiteListedSites.forEach(site => {
|
||||||
|
if (!settings.hasOwnProperty(site.address)) {
|
||||||
|
settings[site.address] = false
|
||||||
|
settingsUpdated = true
|
||||||
|
}
|
||||||
|
whitelistedHostnames.push(site.address)
|
||||||
|
})
|
||||||
|
// Remove any whitelisted sites from app settings that don't exist in the
|
||||||
|
// whitelist from status-go
|
||||||
|
Object.keys(settings).forEach(settingsHostname => {
|
||||||
|
if (!whitelistedHostnames.includes(settingsHostname)) {
|
||||||
|
delete settings[settingsHostname]
|
||||||
|
settingsUpdated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (settingsUpdated) {
|
||||||
|
appSettings.whitelistedUnfurlingSites = settings
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Could not parse the whitelist for sites', e)
|
||||||
|
}
|
||||||
|
appMain.settingsLoaded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: profileModel
|
||||||
|
ignoreUnknownSignals: true
|
||||||
|
enabled: removeMnemonicAfterLogin
|
||||||
|
onInitialized: {
|
||||||
|
profileModel.mnemonic.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: chooseBrowserPopupComponent
|
||||||
|
ChooseBrowserPopup {
|
||||||
|
onClosed: {
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: inviteFriendsToCommunityPopup
|
||||||
|
InviteFriendsToCommunityPopup {
|
||||||
|
onClosed: {
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: communityMembersPopup
|
||||||
|
CommunityMembersPopup {
|
||||||
|
onClosed: {
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: editCommunityPopup
|
||||||
|
CreateCommunityPopup {
|
||||||
|
isEdit: true
|
||||||
|
onClosed: {
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: editChannelPopup
|
||||||
|
CreateChannelPopup {
|
||||||
|
isEdit: true
|
||||||
|
pinnedMessagesPopupComponent: chatLayoutContainer.chatColumn.pinnedMessagesPopupComponent
|
||||||
|
onClosed: {
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToastMessage {
|
||||||
|
id: toastMessage
|
||||||
|
}
|
||||||
|
// Add SendModal here as it is used by the Wallet as well as the Browser
|
||||||
|
Loader {
|
||||||
|
id: sendModal
|
||||||
|
active: false
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
this.active = true
|
||||||
|
this.item.open()
|
||||||
|
}
|
||||||
|
function closed() {
|
||||||
|
// this.sourceComponent = undefined // kill an opened instance
|
||||||
|
this.active = false
|
||||||
|
}
|
||||||
|
sourceComponent: SendModal {
|
||||||
|
onOpened: {
|
||||||
|
walletModel.gasView.getGasPricePredictions()
|
||||||
|
}
|
||||||
|
onClosed: {
|
||||||
|
sendModal.closed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+1"
|
||||||
|
onTriggered: changeAppSection(Constants.chat)
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+2"
|
||||||
|
onTriggered: changeAppSection(Constants.browser)
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+3"
|
||||||
|
onTriggered: changeAppSection(Constants.wallet)
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+4, Ctrl+,"
|
||||||
|
onTriggered: changeAppSection(Constants.profile)
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+K"
|
||||||
|
onTriggered: {
|
||||||
|
if (channelPicker.opened) {
|
||||||
|
channelPicker.close()
|
||||||
|
} else {
|
||||||
|
channelPicker.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: statusIdenticonComponent
|
||||||
|
StatusIdenticon {}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusInputListPopup {
|
||||||
|
id: channelPicker
|
||||||
|
//% "Where do you want to go?"
|
||||||
|
title: qsTrId("where-do-you-want-to-go-")
|
||||||
|
showSearchBox: true
|
||||||
|
width: 350
|
||||||
|
x: parent.width / 2 - width / 2
|
||||||
|
y: parent.height / 2 - height / 2
|
||||||
|
modelList: chatsModel.channelView.chats
|
||||||
|
getText: function (modelData) {
|
||||||
|
return modelData.name
|
||||||
|
}
|
||||||
|
getImageComponent: function (parent, modelData) {
|
||||||
|
return statusIdenticonComponent.createObject(parent, {
|
||||||
|
width: channelPicker.imageWidth,
|
||||||
|
height: channelPicker.imageHeight,
|
||||||
|
chatName: modelData.name,
|
||||||
|
chatType: modelData.chatType,
|
||||||
|
identicon: modelData.identicon
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClicked: function (index) {
|
||||||
|
chatsModel.channelView.setActiveChannelByIndex(index)
|
||||||
|
appMain.changeAppSection(Constants.chat)
|
||||||
|
channelPicker.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*##^##
|
/*##^##
|
||||||
|
Loading…
x
Reference in New Issue
Block a user