mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-22 11:38:57 +00:00
* feat(StatusQ): Adding numberToLocaleStringInCompactForm function to LocaleUtils This function will format the number in a compact form E.g: 1000 -> 1K; 1000000 -> 1M; 1100000 -> 1.1M + adding tests fix(statusQ): Update numberToLocaleStringInCompactForm to return the locale number when greter than 999T fix(StatusQ): extend the test_numberToLocaleStringInCompactForm with new data * feat(LinkPreviews): Update the link preview area in StatusChatInput to use the new model Changes: 1. Create a new component `LinkPreviewMiniCardDelegate.qml` that filters the model data to properly fill the link preview card with the needed data based on the preview type 2. Update storybook pages 3. Small updates to LinkPreviewMiniCard * feat(LinkPreviews): Update the link previews in message history to use the new backend Changes: 1. Create delegate items for LinkPreviewCard and gif link preview to clean the LinksMessageView component and filter the model data based on the preview type 2. Remove UserProfileCard and reuse the LinkPreviewCard to display contacts link previews 3. Update LinkPreviewCard so that it can accommodate status link previews (communities, channels, contacts). The generic properties (title, description, footer) have been dropped and replaced with specialised properties for each preview type. 4. Fix LinkPreviewCard layout to better accommodate different content variants (missing properties, long/short title, missing description, missing icon) 5. Fixing the link preview context menu and click actions fix: Move inline components to separate files Fixing the linux builds using Qt 5.15.2 affected by this bug: https://bugreports.qt.io/browse/QTBUG-89180 * fix: Align LinkPreviewMiniCard implementation with LinkPreviewCard and remove state based model filtering
1650 lines
70 KiB
QML
1650 lines
70 KiB
QML
import QtQuick 2.15
|
||
import QtQuick.Controls 2.13
|
||
import QtQuick.Layouts 1.13
|
||
import QtMultimedia 5.13
|
||
import Qt.labs.platform 1.1
|
||
import Qt.labs.settings 1.1
|
||
import QtQml.Models 2.14
|
||
import QtQml 2.15
|
||
|
||
import AppLayouts.Wallet 1.0
|
||
import AppLayouts.Node 1.0
|
||
import AppLayouts.Browser 1.0
|
||
import AppLayouts.Chat 1.0
|
||
import AppLayouts.Chat.views 1.0
|
||
import AppLayouts.Profile 1.0
|
||
import AppLayouts.Communities 1.0
|
||
|
||
import utils 1.0
|
||
import shared 1.0
|
||
import shared.controls 1.0
|
||
import shared.controls.chat.menuItems 1.0
|
||
import shared.panels 1.0
|
||
import shared.popups 1.0
|
||
import shared.popups.keycard 1.0
|
||
import shared.status 1.0
|
||
import shared.stores 1.0
|
||
import shared.popups.send 1.0
|
||
import shared.popups.send.views 1.0
|
||
|
||
import StatusQ.Core.Theme 0.1
|
||
import StatusQ.Components 0.1
|
||
import StatusQ.Controls 0.1
|
||
import StatusQ.Layout 0.1
|
||
import StatusQ.Popups 0.1
|
||
import StatusQ.Popups.Dialog 0.1
|
||
import StatusQ.Core 0.1
|
||
|
||
import AppLayouts.Browser.stores 1.0 as BrowserStores
|
||
import AppLayouts.stores 1.0
|
||
import AppLayouts.Chat.stores 1.0 as ChatStores
|
||
import AppLayouts.Communities.stores 1.0
|
||
|
||
import mainui.activitycenter.stores 1.0
|
||
import mainui.activitycenter.popups 1.0
|
||
|
||
import SortFilterProxyModel 0.2
|
||
|
||
import "panels"
|
||
|
||
Item {
|
||
id: appMain
|
||
|
||
property alias appLayout: appLayout
|
||
property RootStore rootStore: RootStore {}
|
||
property var rootChatStore: ChatStores.RootStore {
|
||
contactsStore: appMain.rootStore.contactStore
|
||
communityTokensStore: appMain.communityTokensStore
|
||
emojiReactionsModel: appMain.rootStore.emojiReactionsModel
|
||
openCreateChat: createChatView.opened
|
||
networkConnectionStore: appMain.networkConnectionStore
|
||
}
|
||
property var createChatPropertiesStore: ChatStores.CreateChatPropertiesStore {}
|
||
property ActivityCenterStore activityCenterStore: ActivityCenterStore {}
|
||
property NetworkConnectionStore networkConnectionStore: NetworkConnectionStore {}
|
||
property CommunityTokensStore communityTokensStore: CommunityTokensStore {}
|
||
property CommunitiesStore communitiesStore: CommunitiesStore {}
|
||
// set from main.qml
|
||
property var sysPalette
|
||
|
||
Connections {
|
||
target: rootStore.mainModuleInst
|
||
|
||
function onDisplayUserProfile(publicKey: string) {
|
||
popups.openProfilePopup(publicKey)
|
||
}
|
||
|
||
function onDisplayKeycardSharedModuleForAuthentication() {
|
||
keycardPopupForAuthentication.active = true
|
||
}
|
||
|
||
function onDestroyKeycardSharedModuleForAuthentication() {
|
||
keycardPopupForAuthentication.active = false
|
||
}
|
||
|
||
function onDisplayKeycardSharedModuleFlow() {
|
||
keycardPopup.active = true
|
||
}
|
||
|
||
function onDestroyKeycardSharedModuleFlow() {
|
||
keycardPopup.active = false
|
||
}
|
||
|
||
function onMailserverWorking() {
|
||
mailserverConnectionBanner.hide()
|
||
}
|
||
|
||
function onMailserverNotWorking() {
|
||
mailserverConnectionBanner.show()
|
||
}
|
||
|
||
function onActiveSectionChanged() {
|
||
createChatView.opened = false
|
||
}
|
||
|
||
function onOpenActivityCenter() {
|
||
d.openActivityCenterPopup()
|
||
}
|
||
|
||
function onShowToastAccountAdded(name: string) {
|
||
Global.displayToastMessage(
|
||
qsTr("\"%1\" successfully added").arg(name),
|
||
"",
|
||
"checkmark-circle",
|
||
false,
|
||
Constants.ephemeralNotificationType.success,
|
||
""
|
||
)
|
||
}
|
||
|
||
function onShowToastKeypairRenamed(oldName: string, newName: string) {
|
||
Global.displayToastMessage(
|
||
qsTr("You successfully renamed your keypair\nfrom \"%1\" to \"%2\"").arg(oldName).arg(newName),
|
||
"",
|
||
"checkmark-circle",
|
||
false,
|
||
Constants.ephemeralNotificationType.success,
|
||
""
|
||
)
|
||
}
|
||
|
||
function onShowNetworkEndpointUpdated(name: string, isTest: bool, revertToDefault: bool) {
|
||
let mainText = revertToDefault ?
|
||
(isTest ? qsTr("Test network settings for %1 reverted to default").arg(name): qsTr("Live network settings for %1 reverted to default").arg(name)):
|
||
(isTest ? qsTr("Test network settings for %1 updated").arg(name): qsTr("Live network settings for %1 updated").arg(name))
|
||
Global.displayToastMessage(
|
||
mainText,
|
||
"",
|
||
"checkmark-circle",
|
||
false,
|
||
Constants.ephemeralNotificationType.success,
|
||
""
|
||
)
|
||
}
|
||
|
||
function onShowToastKeypairRemoved(keypairName: string) {
|
||
Global.displayToastMessage(
|
||
qsTr("“%1” keypair and its derived accounts were successfully removed from all devices").arg(keypairName),
|
||
"",
|
||
"checkmark-circle",
|
||
false,
|
||
Constants.ephemeralNotificationType.success,
|
||
""
|
||
)
|
||
}
|
||
|
||
function onShowToastKeypairsImported(keypairName: string, keypairsCount: int, error: string) {
|
||
let notification = qsTr("Please re-generate QR code and try importing again")
|
||
if (error !== "") {
|
||
if (error.startsWith("one or more expected keystore files are not found among the sent files")) {
|
||
notification = qsTr("Make sure you're importing the exported keypair on paired device")
|
||
}
|
||
}
|
||
else {
|
||
notification = qsTr("%1 keypair successfully imported").arg(keypairName)
|
||
if (keypairsCount > 1) {
|
||
notification = qsTr("%n keypair(s) successfully imported", "", keypairsCount)
|
||
}
|
||
}
|
||
Global.displayToastMessage(
|
||
notification,
|
||
"",
|
||
error!==""? "info" : "checkmark-circle",
|
||
false,
|
||
error!==""? Constants.ephemeralNotificationType.normal : Constants.ephemeralNotificationType.success,
|
||
""
|
||
)
|
||
}
|
||
|
||
function onShowToastTransactionSent(chainId: int, txHash: string, uuid: string, error: string) {
|
||
if (!error) {
|
||
Global.displayToastMessage(qsTr("Transaction pending..."),
|
||
qsTr("View on etherscan"),
|
||
"",
|
||
true,
|
||
Constants.ephemeralNotificationType.normal,
|
||
"%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash))
|
||
}
|
||
}
|
||
}
|
||
|
||
QtObject {
|
||
id: d
|
||
|
||
property var activityCenterPopupObj: null
|
||
|
||
function openActivityCenterPopup() {
|
||
if (!activityCenterPopupObj) {
|
||
activityCenterPopupObj = activityCenterPopupComponent.createObject(appMain)
|
||
}
|
||
|
||
if (activityCenterPopupObj.opened) {
|
||
activityCenterPopupObj.close()
|
||
} else {
|
||
activityCenterPopupObj.open()
|
||
}
|
||
}
|
||
}
|
||
|
||
Settings {
|
||
id: appMainLocalSettings
|
||
property var whitelistedUnfurledDomains: []
|
||
}
|
||
|
||
Popups {
|
||
id: popups
|
||
popupParent: appMain
|
||
rootStore: appMain.rootStore
|
||
communitiesStore: appMain.communitiesStore
|
||
devicesStore: appMain.rootStore.profileSectionStore.devicesStore
|
||
isDevBuild: !production
|
||
|
||
onOpenExternalLink: globalConns.onOpenLink(link)
|
||
onSaveDomainToUnfurledWhitelist: {
|
||
const whitelistedHostnames = appMainLocalSettings.whitelistedUnfurledDomains || []
|
||
if (!whitelistedHostnames.includes(domain)) {
|
||
whitelistedHostnames.push(domain)
|
||
appMainLocalSettings.whitelistedUnfurledDomains = whitelistedHostnames
|
||
}
|
||
}
|
||
}
|
||
|
||
Connections {
|
||
id: globalConns
|
||
target: Global
|
||
|
||
function onOpenLinkInBrowser(link: string) {
|
||
changeAppSectionBySectionId(Constants.appSection.browser)
|
||
Qt.callLater(() => browserLayoutContainer.item.openUrlInNewTab(link));
|
||
}
|
||
|
||
function onOpenCreateChatView() {
|
||
createChatView.opened = true
|
||
}
|
||
|
||
function onCloseCreateChatView() {
|
||
createChatView.opened = false
|
||
}
|
||
|
||
function onOpenActivityCenterPopupRequested() {
|
||
d.openActivityCenterPopup()
|
||
}
|
||
|
||
function onDisplayToastMessage(title: string, subTitle: string, icon: string, loading: bool, ephNotifType: int, url: string) {
|
||
appMain.rootStore.mainModuleInst.displayEphemeralNotification(title, subTitle, icon, loading, ephNotifType, url)
|
||
}
|
||
|
||
function onOpenLink(link: string) {
|
||
// Qt sometimes inserts random HTML tags; and this will break on invalid URL inside QDesktopServices::openUrl(link)
|
||
link = appMain.rootStore.plainText(link)
|
||
|
||
if (appMain.rootStore.showBrowserSelector) {
|
||
popups.openChooseBrowserPopup(link)
|
||
} else {
|
||
if (appMain.rootStore.openLinksInStatus) {
|
||
globalConns.onAppSectionBySectionTypeChanged(Constants.appSection.browser)
|
||
globalConns.onOpenLinkInBrowser(link)
|
||
} else {
|
||
Qt.openUrlExternally(link)
|
||
}
|
||
}
|
||
}
|
||
|
||
function onOpenLinkWithConfirmation(link: string, domain: string) {
|
||
if (appMainLocalSettings.whitelistedUnfurledDomains.includes(domain))
|
||
globalConns.onOpenLink(link)
|
||
else
|
||
popups.openConfirmExternalLinkPopup(link, domain)
|
||
}
|
||
|
||
function onActivateDeepLink(link: string) {
|
||
appMain.rootStore.mainModuleInst.activateStatusDeepLink(link)
|
||
}
|
||
|
||
function onPlaySendMessageSound() {
|
||
sendMessageSound.stop()
|
||
sendMessageSound.play()
|
||
}
|
||
|
||
function onPlayNotificationSound() {
|
||
notificationSound.stop()
|
||
notificationSound.play()
|
||
}
|
||
|
||
function onPlayErrorSound() {
|
||
errorSound.stop()
|
||
errorSound.play()
|
||
}
|
||
|
||
function onSetNthEnabledSectionActive(nthSection: int) {
|
||
if(!appMain.rootStore.mainModuleInst)
|
||
return
|
||
appMain.rootStore.mainModuleInst.setNthEnabledSectionActive(nthSection)
|
||
}
|
||
|
||
function onAppSectionBySectionTypeChanged(sectionType: int, subsection: int) {
|
||
if(!appMain.rootStore.mainModuleInst)
|
||
return
|
||
|
||
appMain.rootStore.mainModuleInst.setActiveSectionBySectionType(sectionType)
|
||
if (sectionType === Constants.appSection.profile) {
|
||
Global.settingsSubsection = subsection;
|
||
}
|
||
}
|
||
|
||
function onOpenSendModal(address: string) {
|
||
sendModal.open(address)
|
||
}
|
||
|
||
function onSwitchToCommunity(communityId: string) {
|
||
appMain.communitiesStore.setActiveCommunity(communityId)
|
||
}
|
||
}
|
||
|
||
Connections {
|
||
target: appMain.communitiesStore
|
||
|
||
function onImportingCommunityStateChanged(communityId, state, errorMsg) {
|
||
let title = ""
|
||
let subTitle = ""
|
||
let loading = false
|
||
let notificationType = Constants.ephemeralNotificationType.normal
|
||
let icon = ""
|
||
|
||
switch (state)
|
||
{
|
||
case Constants.communityImported:
|
||
const community = appMain.communitiesStore.getCommunityDetailsAsJson(communityId)
|
||
if(community.isControlNode) {
|
||
title = qsTr("This device is now the control node for the %1 Community").arg(community.name)
|
||
notificationType = Constants.ephemeralNotificationType.success
|
||
icon = "checkmark-circle"
|
||
} else {
|
||
title = qsTr("'%1' community imported").arg(community.name)
|
||
}
|
||
break
|
||
case Constants.communityImportingInProgress:
|
||
title = qsTr("Importing community is in progress")
|
||
loading = true
|
||
break
|
||
case Constants.communityImportingError:
|
||
title = qsTr("Failed to import community '%1'").arg(communityId)
|
||
subTitle = errorMsg
|
||
break
|
||
default:
|
||
console.error("unknown state while importing community: %1").arg(state)
|
||
return
|
||
}
|
||
|
||
Global.displayToastMessage(title,
|
||
subTitle,
|
||
icon,
|
||
loading,
|
||
notificationType,
|
||
"")
|
||
}
|
||
|
||
function onCommunityInfoAlreadyRequested() {
|
||
Global.displayToastMessage(qsTr("Community data not loaded yet."),
|
||
qsTr("Please wait for the unfurl to show"),
|
||
"",
|
||
true,
|
||
Constants.ephemeralNotificationType.normal,
|
||
"")
|
||
}
|
||
}
|
||
|
||
Connections {
|
||
target: Global.applicationWindow
|
||
|
||
function onActiveChanged() {
|
||
if (Global.applicationWindow.active) appMain.rootStore.windowActivated()
|
||
else appMain.rootStore.windowDeactivated()
|
||
}
|
||
}
|
||
|
||
function changeAppSectionBySectionId(sectionId) {
|
||
appMain.rootStore.mainModuleInst.setActiveSectionById(sectionId)
|
||
}
|
||
|
||
Audio {
|
||
id: sendMessageSound
|
||
store: rootStore
|
||
source: "qrc:/imports/assets/audio/send_message.wav"
|
||
}
|
||
|
||
Audio {
|
||
id: notificationSound
|
||
store: rootStore
|
||
source: "qrc:/imports/assets/audio/notification.wav"
|
||
}
|
||
|
||
Audio {
|
||
id: errorSound
|
||
source: "qrc:/imports/assets/audio/error.mp3"
|
||
store: rootStore
|
||
}
|
||
|
||
Loader {
|
||
id: appSearch
|
||
active: false
|
||
asynchronous: true
|
||
|
||
function openSearchPopup() {
|
||
if (!active)
|
||
active = true
|
||
item.openSearchPopup()
|
||
}
|
||
|
||
function closeSearchPopup() {
|
||
if (item)
|
||
item.closeSearchPopup()
|
||
|
||
active = false
|
||
}
|
||
|
||
sourceComponent: AppSearch {
|
||
store: appMain.rootStore.appSearchStore
|
||
onClosed: appSearch.active = false
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: statusEmojiPopup
|
||
active: appMain.rootStore.mainModuleInst.sectionsLoaded
|
||
sourceComponent: StatusEmojiPopup {
|
||
width: 360
|
||
height: 440
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: statusStickersPopupLoader
|
||
active: appMain.rootStore.mainModuleInst.sectionsLoaded
|
||
sourceComponent: StatusStickersPopup {
|
||
id: statusStickersPopup
|
||
store: appMain.rootChatStore
|
||
}
|
||
}
|
||
|
||
StatusMainLayout {
|
||
id: appLayout
|
||
|
||
anchors.fill: parent
|
||
|
||
leftPanel: StatusAppNavBar {
|
||
chatItemsModel: SortFilterProxyModel {
|
||
sourceModel: appMain.rootStore.mainModuleInst.sectionsModel
|
||
filters: [
|
||
ValueFilter {
|
||
roleName: "sectionType"
|
||
value: Constants.appSection.chat
|
||
},
|
||
ValueFilter {
|
||
roleName: "enabled"
|
||
value: true
|
||
}
|
||
]
|
||
}
|
||
chatItemDelegate: navbarButton
|
||
|
||
communityItemsModel: SortFilterProxyModel {
|
||
sourceModel: appMain.rootStore.mainModuleInst.sectionsModel
|
||
filters: [
|
||
ValueFilter {
|
||
roleName: "sectionType"
|
||
value: Constants.appSection.community
|
||
},
|
||
ValueFilter {
|
||
roleName: "enabled"
|
||
value: true
|
||
}
|
||
]
|
||
}
|
||
communityItemDelegate: StatusNavBarTabButton {
|
||
objectName: "CommunityNavBarButton"
|
||
anchors.horizontalCenter: parent.horizontalCenter
|
||
name: model.icon.length > 0? "" : model.name
|
||
icon.name: model.icon
|
||
icon.source: model.image
|
||
identicon.asset.color: (hovered || identicon.highlighted || checked) ? model.color : icon.color
|
||
tooltip.text: model.name
|
||
checked: model.active
|
||
badge.value: model.notificationsCount
|
||
badge.visible: model.hasNotification
|
||
badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusBadge.borderColor
|
||
badge.border.width: 2
|
||
onClicked: {
|
||
changeAppSectionBySectionId(model.id)
|
||
}
|
||
|
||
popupMenu: Component {
|
||
StatusMenu {
|
||
id: communityContextMenu
|
||
property var chatCommunitySectionModule
|
||
|
||
readonly property bool isSpectator: model.spectated && !model.joined
|
||
|
||
openHandler: function () {
|
||
// we cannot return QVariant if we pass another parameter in a function call
|
||
// that's why we're using it this way
|
||
appMain.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(model.id)
|
||
communityContextMenu.chatCommunitySectionModule = appMain.rootStore.mainModuleInst.getCommunitySectionModule()
|
||
}
|
||
|
||
StatusAction {
|
||
text: qsTr("Invite People")
|
||
icon.name: "share-ios"
|
||
enabled: model.canManageUsers
|
||
objectName: "invitePeople"
|
||
onTriggered: {
|
||
popups.openInviteFriendsToCommunityPopup(model,
|
||
communityContextMenu.chatCommunitySectionModule,
|
||
null)
|
||
}
|
||
}
|
||
|
||
StatusAction {
|
||
text: qsTr("View Community")
|
||
icon.name: "group-chat"
|
||
onTriggered: popups.openCommunityProfilePopup(appMain.rootStore, model, communityContextMenu.chatCommunitySectionModule)
|
||
}
|
||
|
||
StatusMenuSeparator {}
|
||
|
||
MuteChatMenuItem {
|
||
enabled: !model.muted
|
||
title: qsTr("Mute Community")
|
||
onMuteTriggered: {
|
||
communityContextMenu.chatCommunitySectionModule.setCommunityMuted(interval)
|
||
communityContextMenu.close()
|
||
}
|
||
}
|
||
|
||
StatusAction {
|
||
enabled: model.muted
|
||
text: qsTr("Unmute Community")
|
||
icon.name: "notification"
|
||
onTriggered: {
|
||
communityContextMenu.chatCommunitySectionModule.setCommunityMuted(Constants.MutingVariations.Unmuted)
|
||
}
|
||
}
|
||
|
||
StatusAction {
|
||
text: qsTr("Edit Shared Addresses")
|
||
icon.name: "wallet"
|
||
enabled: {
|
||
if (model.memberRole === Constants.memberRole.owner)
|
||
return false
|
||
if (communityContextMenu.isSpectator && !appMain.rootStore.isCommunityRequestPending(model.id))
|
||
return false
|
||
return true
|
||
}
|
||
onTriggered: {
|
||
communityContextMenu.close()
|
||
Global.openEditSharedAddressesFlow(model.id)
|
||
}
|
||
}
|
||
|
||
StatusMenuSeparator { visible: leaveCommunityMenuItem.enabled }
|
||
|
||
StatusAction {
|
||
id: leaveCommunityMenuItem
|
||
enabled: model.memberRole !== Constants.memberRole.owner
|
||
text: {
|
||
if (communityContextMenu.isSpectator)
|
||
return qsTr("Close Community")
|
||
return qsTr("Leave Community")
|
||
}
|
||
icon.name: communityContextMenu.isSpectator ? "close-circle" : "arrow-left"
|
||
type: StatusAction.Type.Danger
|
||
onTriggered: communityContextMenu.isSpectator ? communityContextMenu.chatCommunitySectionModule.leaveCommunity()
|
||
: popups.openLeaveCommunityPopup(model.name, model.id, model.outroMessage)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
regularItemsModel: SortFilterProxyModel {
|
||
sourceModel: appMain.rootStore.mainModuleInst.sectionsModel
|
||
filters: [
|
||
RangeFilter {
|
||
roleName: "sectionType"
|
||
minimumValue: Constants.appSection.wallet
|
||
maximumValue: Constants.appSection.communitiesPortal
|
||
},
|
||
ValueFilter {
|
||
roleName: "enabled"
|
||
value: true
|
||
}
|
||
]
|
||
}
|
||
regularItemDelegate: navbarButton
|
||
|
||
delegateHeight: 40
|
||
|
||
profileComponent: StatusNavBarTabButton {
|
||
id: profileButton
|
||
objectName: "statusProfileNavBarTabButton"
|
||
property bool opened: false
|
||
|
||
name: appMain.rootStore.userProfileInst.name
|
||
icon.source: appMain.rootStore.userProfileInst.icon
|
||
implicitWidth: 32
|
||
implicitHeight: 32
|
||
identicon.asset.width: width
|
||
identicon.asset.height: height
|
||
identicon.asset.charactersLen: 2
|
||
identicon.asset.color: Utils.colorForPubkey(appMain.rootStore.userProfileInst.pubKey)
|
||
identicon.ringSettings.ringSpecModel: Utils.getColorHashAsJson(appMain.rootStore.userProfileInst.pubKey,
|
||
appMain.rootStore.userProfileInst.preferredName)
|
||
|
||
badge.visible: true
|
||
badge.anchors {
|
||
left: undefined
|
||
top: undefined
|
||
right: profileButton.right
|
||
bottom: profileButton.bottom
|
||
margins: 0
|
||
rightMargin: -badge.border.width
|
||
bottomMargin: -badge.border.width
|
||
}
|
||
badge.implicitHeight: 12
|
||
badge.implicitWidth: 12
|
||
badge.border.width: 2
|
||
badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusAppNavBar.backgroundColor
|
||
badge.color: {
|
||
switch(appMain.rootStore.userProfileInst.currentUserStatus){
|
||
case Constants.currentUserStatus.automatic:
|
||
case Constants.currentUserStatus.alwaysOnline:
|
||
return Style.current.green;
|
||
default:
|
||
return Style.current.midGrey;
|
||
}
|
||
}
|
||
|
||
onClicked: userStatusContextMenu.opened ? userStatusContextMenu.close() : userStatusContextMenu.open()
|
||
|
||
UserStatusContextMenu {
|
||
id: userStatusContextMenu
|
||
y: profileButton.y - userStatusContextMenu.height + profileButton.height
|
||
x: profileButton.x + profileButton.width + 5
|
||
store: appMain.rootStore
|
||
}
|
||
}
|
||
|
||
Component {
|
||
id: navbarButton
|
||
StatusNavBarTabButton {
|
||
id: navbar
|
||
objectName: model.name + "-navbar"
|
||
anchors.horizontalCenter: parent.horizontalCenter
|
||
name: model.icon.length > 0? "" : model.name
|
||
icon.name: model.icon
|
||
icon.source: model.image
|
||
tooltip.text: Utils.translatedSectionName(model.sectionType, model.name)
|
||
checked: model.active
|
||
badge.value: model.notificationsCount
|
||
badge.visible: model.hasNotification
|
||
badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusBadge.borderColor
|
||
badge.border.width: 2
|
||
onClicked: {
|
||
changeAppSectionBySectionId(model.id)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
rightPanel: ColumnLayout {
|
||
spacing: 0
|
||
objectName: "mainRightView"
|
||
|
||
ColumnLayout {
|
||
id: bannersLayout
|
||
|
||
enabled: !localAppSettings.testEnvironment
|
||
visible: enabled
|
||
|
||
property var updateBanner: null
|
||
property var connectedBanner: null
|
||
readonly property bool isConnected: appMain.rootStore.mainModuleInst.isOnline
|
||
|
||
function processUpdateAvailable() {
|
||
if (!updateBanner)
|
||
updateBanner = updateBannerComponent.createObject(this)
|
||
}
|
||
|
||
function processConnected() {
|
||
if (!connectedBanner)
|
||
connectedBanner = connectedBannerComponent.createObject(this)
|
||
}
|
||
|
||
Layout.fillWidth: true
|
||
Layout.maximumHeight: implicitHeight
|
||
spacing: 1
|
||
|
||
onIsConnectedChanged: {
|
||
processConnected()
|
||
}
|
||
|
||
Component.onCompleted: {
|
||
if (!isConnected)
|
||
processConnected()
|
||
}
|
||
|
||
Connections {
|
||
target: rootStore.aboutModuleInst
|
||
function onAppVersionFetched(available: bool, version: string, url: string) {
|
||
rootStore.setLatestVersionInfo(available, version, url);
|
||
// TODO when we re-implement check for updates, uncomment this
|
||
// bannersLayout.processUpdateAvailable()
|
||
}
|
||
}
|
||
|
||
ModuleWarning {
|
||
id: testnetBanner
|
||
objectName: "testnetBanner"
|
||
Layout.fillWidth: true
|
||
text: qsTr("Testnet mode enabled. All balances, transactions and dApp interactions will be on testnets.")
|
||
buttonText: qsTr("Turn off")
|
||
type: ModuleWarning.Warning
|
||
iconName: "warning"
|
||
active: appMain.rootStore.profileSectionStore.walletStore.areTestNetworksEnabled
|
||
|
||
onClicked: Global.openTestnetPopup()
|
||
onCloseClicked: hide()
|
||
}
|
||
|
||
ModuleWarning {
|
||
id: secureYourSeedPhrase
|
||
objectName: "secureYourSeedPhraseBanner"
|
||
Layout.fillWidth: true
|
||
active: !appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner
|
||
&& !appMain.rootStore.profileSectionStore.profileStore.privacyStore.mnemonicBackedUp
|
||
type: ModuleWarning.Danger
|
||
text: qsTr("Secure your seed phrase")
|
||
buttonText: qsTr("Back up now")
|
||
|
||
onClicked: popups.openBackUpSeedPopup()
|
||
|
||
onCloseClicked: {
|
||
appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner = true
|
||
}
|
||
}
|
||
|
||
|
||
ModuleWarning {
|
||
Layout.fillWidth: true
|
||
readonly property int progress: appMain.communitiesStore.discordImportProgress
|
||
readonly property bool inProgress: (progress > 0 && progress < 100) || appMain.communitiesStore.discordImportInProgress
|
||
readonly property bool finished: progress >= 100
|
||
readonly property bool cancelled: appMain.communitiesStore.discordImportCancelled
|
||
readonly property bool stopped: appMain.communitiesStore.discordImportProgressStopped
|
||
readonly property int errors: appMain.communitiesStore.discordImportErrorsCount
|
||
readonly property int warnings: appMain.communitiesStore.discordImportWarningsCount
|
||
readonly property string communityId: appMain.communitiesStore.discordImportCommunityId
|
||
readonly property string communityName: appMain.communitiesStore.discordImportCommunityName
|
||
readonly property string channelId: appMain.communitiesStore.discordImportChannelId
|
||
readonly property string channelName: appMain.communitiesStore.discordImportChannelName
|
||
readonly property string channelOrCommunityName: channelName || communityName
|
||
|
||
active: !cancelled && (inProgress || finished || stopped)
|
||
type: errors ? ModuleWarning.Type.Danger : ModuleWarning.Type.Success
|
||
text: {
|
||
if (finished || stopped) {
|
||
if (errors)
|
||
return qsTr("The import of ‘%1’ from Discord to Status was stopped: <a href='#'>Critical issues found</a>").arg(channelOrCommunityName)
|
||
|
||
let result = qsTr("‘%1’ was successfully imported from Discord to Status").arg(channelOrCommunityName) + " <a href='#'>"
|
||
if (warnings)
|
||
result += qsTr("Details (%1)").arg(qsTr("%n issue(s)", "", warnings))
|
||
else
|
||
result += qsTr("Details")
|
||
result += "</a>"
|
||
return result
|
||
}
|
||
if (inProgress) {
|
||
let result = qsTr("Importing ‘%1’ from Discord to Status").arg(channelOrCommunityName) + " <a href='#'>"
|
||
if (warnings)
|
||
result += qsTr("Check progress (%1)").arg(qsTr("%n issue(s)", "", warnings))
|
||
else
|
||
result += qsTr("Check progress")
|
||
result += "</a>"
|
||
return result
|
||
}
|
||
|
||
return ""
|
||
}
|
||
onLinkActivated: popups.openDiscordImportProgressPopup(!!channelId)
|
||
progressValue: progress
|
||
closeBtnVisible: finished || stopped
|
||
buttonText: finished && !errors ? !!channelId ? qsTr("Visit your new channel") : qsTr("Visit your Community") : ""
|
||
onClicked: function() {
|
||
if (!!channelId)
|
||
rootStore.setActiveSectionChat(communityId, channelId)
|
||
else
|
||
appMain.communitiesStore.setActiveCommunity(communityId)
|
||
}
|
||
onCloseClicked: hide()
|
||
}
|
||
|
||
ModuleWarning {
|
||
id: downloadingArchivesBanner
|
||
Layout.fillWidth: true
|
||
active: appMain.communitiesStore.downloadingCommunityHistoryArchives
|
||
type: ModuleWarning.Danger
|
||
text: qsTr("Downloading message history archives, DO NOT CLOSE THE APP until this banner disappears.")
|
||
closeBtnVisible: false
|
||
}
|
||
|
||
ModuleWarning {
|
||
id: mailserverConnectionBanner
|
||
type: ModuleWarning.Warning
|
||
text: qsTr("Can not connect to store node. Retrying automatically")
|
||
onCloseClicked: hide()
|
||
Layout.fillWidth: true
|
||
}
|
||
|
||
Component {
|
||
id: connectedBannerComponent
|
||
|
||
ModuleWarning {
|
||
id: connectedBanner
|
||
property bool isConnected: true
|
||
|
||
objectName: "connectionInfoBanner"
|
||
Layout.fillWidth: true
|
||
text: isConnected ? qsTr("You are back online") : qsTr("Internet connection lost. Reconnect to ensure everything is up to date.")
|
||
type: isConnected ? ModuleWarning.Success : ModuleWarning.Danger
|
||
|
||
function updateState() {
|
||
if (isConnected)
|
||
showFor()
|
||
else
|
||
show();
|
||
}
|
||
|
||
Component.onCompleted: {
|
||
connectedBanner.isConnected = Qt.binding(() => bannersLayout.isConnected);
|
||
}
|
||
onIsConnectedChanged: {
|
||
updateState();
|
||
}
|
||
onCloseClicked: {
|
||
hide();
|
||
}
|
||
onHideFinished: {
|
||
destroy()
|
||
bannersLayout.connectedBanner = null
|
||
}
|
||
}
|
||
}
|
||
|
||
Component {
|
||
id: updateBannerComponent
|
||
|
||
ModuleWarning {
|
||
readonly property string version: appMain.rootStore.latestVersion
|
||
readonly property bool updateAvailable: appMain.rootStore.newVersionAvailable
|
||
|
||
objectName: "appVersionUpdateBanner"
|
||
Layout.fillWidth: true
|
||
type: ModuleWarning.Success
|
||
text: updateAvailable ? qsTr("A new version of Status (%1) is available").arg(version)
|
||
: qsTr("Your version is up to date")
|
||
|
||
buttonText: updateAvailable ? qsTr("Update")
|
||
: qsTr("Close")
|
||
|
||
function updateState() {
|
||
if (updateAvailable)
|
||
show()
|
||
else
|
||
showFor(5000)
|
||
}
|
||
|
||
Component.onCompleted: {
|
||
updateState()
|
||
}
|
||
onUpdateAvailableChanged: {
|
||
updateState();
|
||
}
|
||
onClicked: {
|
||
if (updateAvailable)
|
||
Global.openDownloadModal(appMain.rootStore.newVersionAvailable,
|
||
appMain.rootStore.latestVersion,
|
||
appMain.rootStore.downloadURL)
|
||
else
|
||
close()
|
||
}
|
||
onCloseClicked: {
|
||
if (updateAvailable)
|
||
appMain.rootStore.resetLastVersion();
|
||
hide()
|
||
}
|
||
onHideFinished: {
|
||
destroy()
|
||
bannersLayout.updateBanner = null
|
||
}
|
||
}
|
||
}
|
||
|
||
ConnectionWarnings {
|
||
id: walletBlockchainConnectionBanner
|
||
objectName: "walletBlockchainConnectionBanner"
|
||
Layout.fillWidth: true
|
||
websiteDown: Constants.walletConnections.blockchains
|
||
withCache: networkConnectionStore.balanceCache
|
||
networkConnectionStore: appMain.networkConnectionStore
|
||
tooltipMessage: qsTr("Pocket Network (POKT) & Infura are currently both unavailable for %1. Balances for those chains are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
|
||
toastText: {
|
||
switch(connectionState) {
|
||
case Constants.ConnectionStatus.Success:
|
||
return qsTr("Pocket Network (POKT) connection successful")
|
||
case Constants.ConnectionStatus.Failure:
|
||
if(completelyDown) {
|
||
if(withCache)
|
||
return qsTr("POKT & Infura down. Token balances are as of %1.").arg(lastCheckedAt)
|
||
else
|
||
return qsTr("POKT & Infura down. Token balances cannot be retrieved.")
|
||
}
|
||
else if(chainIdsDown.length > 0) {
|
||
if(chainIdsDown.length > 2) {
|
||
return qsTr("POKT & Infura down for <a href='#'>multiple chains </a>. Token balances for those chains cannot be retrieved.")
|
||
}
|
||
else if(chainIdsDown.length === 1) {
|
||
return qsTr("POKT & Infura down for %1. %1 token balances are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
|
||
}
|
||
else {
|
||
return qsTr("POKT & Infura down for %1. %1 token balances cannot be retrieved.").arg(jointChainIdString)
|
||
}
|
||
}
|
||
else
|
||
return ""
|
||
case Constants.ConnectionStatus.Retrying:
|
||
return qsTr("Retrying connection to Pocket Network (POKT).")
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
}
|
||
|
||
ConnectionWarnings {
|
||
id: walletCollectiblesConnectionBanner
|
||
objectName: "walletCollectiblesConnectionBanner"
|
||
Layout.fillWidth: true
|
||
websiteDown: Constants.walletConnections.collectibles
|
||
withCache: lastCheckedAtUnix > 0
|
||
networkConnectionStore: appMain.networkConnectionStore
|
||
tooltipMessage: {
|
||
if(withCache)
|
||
return qsTr("Collectibles providers are currently unavailable for %1. Collectibles for those chains are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
|
||
else
|
||
return qsTr("Collectibles providers are currently unavailable for %1.").arg(jointChainIdString)
|
||
}
|
||
toastText: {
|
||
switch(connectionState) {
|
||
case Constants.ConnectionStatus.Success:
|
||
return qsTr("Collectibles providers connection successful")
|
||
case Constants.ConnectionStatus.Failure:
|
||
if(completelyDown) {
|
||
if(withCache)
|
||
return qsTr("Collectibles providers down. Collectibles are as of %1.").arg(lastCheckedAt)
|
||
else
|
||
return qsTr("Collectibles providers down. Collectibles cannot be retrieved.")
|
||
}
|
||
else if(chainIdsDown.length > 0) {
|
||
if(chainIdsDown.length > 2) {
|
||
if(withCache)
|
||
return qsTr("Collectibles providers down for <a href='#'>multiple chains</a>. Collectibles for these chains are as of %1.".arg(lastCheckedAt))
|
||
else
|
||
return qsTr("Collectibles providers down for <a href='#'>multiple chains</a>. Collectibles for these chains cannot be retrieved.")
|
||
}
|
||
else if(chainIdsDown.length === 1) {
|
||
if(withCache)
|
||
return qsTr("Collectibles providers down for %1. Collectibles for this chain are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
|
||
else
|
||
return qsTr("Collectibles providers down for %1. Collectibles for this chain cannot be retrieved.").arg(jointChainIdString)
|
||
}
|
||
else {
|
||
if(withCache)
|
||
return qsTr("Collectibles providers down for %1. Collectibles for these chains are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
|
||
else
|
||
return qsTr("Collectibles providers down for %1. Collectibles for these chains cannot be retrieved.").arg(jointChainIdString)
|
||
}
|
||
}
|
||
else
|
||
return ""
|
||
case Constants.ConnectionStatus.Retrying:
|
||
return qsTr("Retrying connection to collectibles providers...")
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
}
|
||
|
||
ConnectionWarnings {
|
||
id: walletMarketConnectionBanner
|
||
objectName: "walletMarketConnectionBanner"
|
||
Layout.fillWidth: true
|
||
websiteDown: Constants.walletConnections.market
|
||
withCache: networkConnectionStore.marketValuesCache
|
||
networkConnectionStore: appMain.networkConnectionStore
|
||
toastText: {
|
||
switch(connectionState) {
|
||
case Constants.ConnectionStatus.Success:
|
||
return qsTr("CryptoCompare and CoinGecko connection successful")
|
||
case Constants.ConnectionStatus.Failure: {
|
||
if(withCache) {
|
||
return qsTr("CryptoCompare and CoinGecko down. Market values are as of %1.").arg(lastCheckedAt)
|
||
}
|
||
else {
|
||
return qsTr("CryptoCompare and CoinGecko down. Market values cannot be retrieved.")
|
||
}
|
||
}
|
||
case Constants.ConnectionStatus.Retrying:
|
||
return qsTr("Retrying connection to CryptoCompare and CoinGecko...")
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Item {
|
||
Layout.fillWidth: true
|
||
Layout.fillHeight: true
|
||
|
||
StackLayout {
|
||
id: appView
|
||
anchors.fill: parent
|
||
|
||
currentIndex: {
|
||
const activeSectionType = appMain.rootStore.mainModuleInst.activeSection.sectionType
|
||
|
||
if (activeSectionType === Constants.appSection.chat)
|
||
return Constants.appViewStackIndex.chat
|
||
if (activeSectionType === Constants.appSection.community) {
|
||
for (let i = this.children.length - 1; i >=0; i--) {
|
||
var obj = this.children[i]
|
||
if (obj && obj.sectionId && obj.sectionId === appMain.rootStore.mainModuleInst.activeSection.id) {
|
||
return i
|
||
}
|
||
}
|
||
|
||
// Should never be here, correct index must be returned from the for loop above
|
||
console.error("Wrong section type:", appMain.rootStore.mainModuleInst.activeSection.sectionType,
|
||
"or section id: ", appMain.rootStore.mainModuleInst.activeSection.id)
|
||
return Constants.appViewStackIndex.community
|
||
}
|
||
if (activeSectionType === Constants.appSection.communitiesPortal)
|
||
return Constants.appViewStackIndex.communitiesPortal
|
||
if (activeSectionType === Constants.appSection.wallet)
|
||
return Constants.appViewStackIndex.wallet
|
||
if (activeSectionType === Constants.appSection.browser)
|
||
return Constants.appViewStackIndex.browser
|
||
if (activeSectionType === Constants.appSection.profile)
|
||
return Constants.appViewStackIndex.profile
|
||
if (activeSectionType === Constants.appSection.node)
|
||
return Constants.appViewStackIndex.node
|
||
|
||
// We should never end up here
|
||
console.error("AppMain: Unknown section type")
|
||
}
|
||
|
||
// NOTE:
|
||
// If we ever change stack layout component order we need to updade
|
||
// Constants.appViewStackIndex accordingly
|
||
|
||
Loader {
|
||
id: personalChatLayoutLoader
|
||
asynchronous: true
|
||
active: false
|
||
sourceComponent: {
|
||
if (appMain.rootStore.mainModuleInst.chatsLoadingFailed) {
|
||
return errorStateComponent
|
||
}
|
||
if (appMain.rootStore.mainModuleInst.sectionsLoaded) {
|
||
return personalChatLayoutComponent
|
||
}
|
||
return loadingStateComponent
|
||
}
|
||
|
||
// Do not unload section data from the memory in order not
|
||
// to reset scroll, not send text input and etc during the
|
||
// sections switching
|
||
Binding on active {
|
||
when: appView.currentIndex === Constants.appViewStackIndex.chat
|
||
value: true
|
||
restoreMode: Binding.RestoreNone
|
||
}
|
||
|
||
Component {
|
||
id: loadingStateComponent
|
||
Item {
|
||
anchors.fill: parent
|
||
|
||
Row {
|
||
anchors.centerIn: parent
|
||
spacing: 6
|
||
StatusBaseText {
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
text: qsTr("Loading sections...")
|
||
}
|
||
LoadingAnimation { anchors.verticalCenter: parent.verticalCenter }
|
||
}
|
||
}
|
||
}
|
||
|
||
Component {
|
||
id: errorStateComponent
|
||
Item {
|
||
anchors.fill: parent
|
||
StatusBaseText {
|
||
text: qsTr("Error loading chats, try closing the app and restarting")
|
||
anchors.centerIn: parent
|
||
}
|
||
}
|
||
}
|
||
|
||
Component {
|
||
id: personalChatLayoutComponent
|
||
|
||
ChatLayout {
|
||
id: chatLayoutContainer
|
||
|
||
Binding {
|
||
target: rootDropAreaPanel
|
||
property: "enabled"
|
||
value: chatLayoutContainer.currentIndex === 0 // Meaning: Chats / channels view
|
||
when: visible
|
||
restoreMode: Binding.RestoreBindingOrValue
|
||
}
|
||
|
||
rootStore: ChatStores.RootStore {
|
||
contactsStore: appMain.rootStore.contactStore
|
||
communityTokensStore: appMain.communityTokensStore
|
||
emojiReactionsModel: appMain.rootStore.emojiReactionsModel
|
||
openCreateChat: createChatView.opened
|
||
chatCommunitySectionModule: appMain.rootStore.mainModuleInst.getChatSectionModule()
|
||
networkConnectionStore: appMain.networkConnectionStore
|
||
}
|
||
createChatPropertiesStore: appMain.createChatPropertiesStore
|
||
emojiPopup: statusEmojiPopup.item
|
||
stickersPopup: statusStickersPopupLoader.item
|
||
|
||
onProfileButtonClicked: {
|
||
Global.changeAppSectionBySectionType(Constants.appSection.profile);
|
||
}
|
||
|
||
onOpenAppSearch: {
|
||
appSearch.openSearchPopup()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
active: appView.currentIndex === Constants.appViewStackIndex.communitiesPortal
|
||
asynchronous: true
|
||
CommunitiesPortalLayout {
|
||
anchors.fill: parent
|
||
communitiesStore: appMain.communitiesStore
|
||
assetsModel: SortFilterProxyModel {
|
||
sourceModel: appMain.communitiesStore.communitiesModuleInst.tokenList
|
||
|
||
proxyRoles: ExpressionRole {
|
||
function tokenIcon(symbol) {
|
||
return Constants.tokenIcon(symbol)
|
||
}
|
||
name: "iconSource"
|
||
expression: !!model.icon ? model.icon : tokenIcon(model.symbol)
|
||
}
|
||
}
|
||
collectiblesModel: SortFilterProxyModel {
|
||
sourceModel: appMain.communitiesStore.communitiesModuleInst.collectiblesModel
|
||
|
||
proxyRoles: ExpressionRole {
|
||
function icon(icon) {
|
||
return !!icon ? icon : Style.png("tokens/DEFAULT-TOKEN")
|
||
}
|
||
name: "iconSource"
|
||
expression: icon(model.icon)
|
||
}
|
||
}
|
||
notificationCount: appMain.activityCenterStore.unreadNotificationsCount
|
||
hasUnseenNotifications: activityCenterStore.hasUnseenNotifications
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
active: appView.currentIndex === Constants.appViewStackIndex.wallet
|
||
asynchronous: true
|
||
sourceComponent: WalletLayout {
|
||
store: appMain.rootStore
|
||
contactsStore: appMain.rootStore.profileSectionStore.contactsStore
|
||
emojiPopup: statusEmojiPopup.item
|
||
sendModalPopup: sendModal
|
||
networkConnectionStore: appMain.networkConnectionStore
|
||
}
|
||
onLoaded: item.showSigningPhrasePopup()
|
||
}
|
||
|
||
Loader {
|
||
id: browserLayoutContainer
|
||
active: appView.currentIndex === Constants.appViewStackIndex.browser
|
||
asynchronous: true
|
||
sourceComponent: BrowserLayout {
|
||
globalStore: appMain.rootStore
|
||
sendTransactionModal: sendModal
|
||
}
|
||
// Loaders do not have access to the context, so props need to be set
|
||
// Adding a "_" to avoid a binding loop
|
||
// Not Refactored Yet
|
||
// property var _chatsModel: chatsModel.messageView
|
||
// Not Refactored Yet
|
||
// property var _walletModel: walletModel
|
||
// Not Refactored Yet
|
||
// property var _utilsModel: utilsModel
|
||
// property var _web3Provider: BrowserStores.Web3ProviderStore.web3ProviderInst
|
||
}
|
||
|
||
Loader {
|
||
active: appView.currentIndex === Constants.appViewStackIndex.profile
|
||
asynchronous: true
|
||
sourceComponent: ProfileLayout {
|
||
store: appMain.rootStore.profileSectionStore
|
||
globalStore: appMain.rootStore
|
||
systemPalette: appMain.sysPalette
|
||
emojiPopup: statusEmojiPopup.item
|
||
networkConnectionStore: appMain.networkConnectionStore
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
active: appView.currentIndex === Constants.appViewStackIndex.node
|
||
asynchronous: true
|
||
sourceComponent: NodeLayout {}
|
||
}
|
||
|
||
Repeater {
|
||
model: SortFilterProxyModel {
|
||
sourceModel: appMain.rootStore.mainModuleInst.sectionsModel
|
||
filters: ValueFilter {
|
||
roleName: "sectionType"
|
||
value: Constants.appSection.community
|
||
}
|
||
}
|
||
|
||
delegate: Loader {
|
||
id: communityLoader
|
||
|
||
readonly property string sectionId: model.id
|
||
|
||
Layout.fillWidth: true
|
||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||
Layout.fillHeight: true
|
||
|
||
asynchronous: true
|
||
active: false
|
||
|
||
// Do not unload section data from the memory in order not
|
||
// to reset scroll, not send text input and etc during the
|
||
// sections switching
|
||
Binding on active {
|
||
when: sectionId === appMain.rootStore.mainModuleInst.activeSection.id
|
||
value: true
|
||
restoreMode: Binding.RestoreNone
|
||
}
|
||
|
||
sourceComponent: ChatLayout {
|
||
id: chatLayoutComponent
|
||
|
||
readonly property bool isManageCommunityEnabledInAdvanced: appMain.rootStore.profileSectionStore.advancedStore.isManageCommunityOnTestModeEnabled
|
||
|
||
Binding {
|
||
target: rootDropAreaPanel
|
||
property: "enabled"
|
||
value: chatLayoutComponent.currentIndex === 0 // Meaning: Chats / channels view
|
||
when: visible
|
||
restoreMode: Binding.RestoreBindingOrValue
|
||
}
|
||
|
||
Connections {
|
||
target: Global
|
||
function onSwitchToCommunitySettings(communityId: string) {
|
||
if (communityId !== model.id)
|
||
return
|
||
chatLayoutComponent.currentIndex = 1 // Settings
|
||
}
|
||
}
|
||
|
||
sendModalPopup: sendModal
|
||
emojiPopup: statusEmojiPopup.item
|
||
stickersPopup: statusStickersPopupLoader.item
|
||
sectionItemModel: model
|
||
createChatPropertiesStore: appMain.createChatPropertiesStore
|
||
communitiesStore: appMain.communitiesStore
|
||
communitySettingsDisabled: !chatLayoutComponent.isManageCommunityEnabledInAdvanced &&
|
||
(production && appMain.rootStore.profileSectionStore.walletStore.areTestNetworksEnabled)
|
||
rootStore: ChatStores.RootStore {
|
||
contactsStore: appMain.rootStore.contactStore
|
||
communityTokensStore: appMain.communityTokensStore
|
||
emojiReactionsModel: appMain.rootStore.emojiReactionsModel
|
||
openCreateChat: createChatView.opened
|
||
chatCommunitySectionModule: {
|
||
appMain.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(model.id)
|
||
return appMain.rootStore.mainModuleInst.getCommunitySectionModule()
|
||
}
|
||
}
|
||
|
||
onProfileButtonClicked: {
|
||
Global.changeAppSectionBySectionType(Constants.appSection.profile);
|
||
}
|
||
|
||
onOpenAppSearch: {
|
||
appSearch.openSearchPopup()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: createChatView
|
||
|
||
property bool opened: false
|
||
active: appMain.rootStore.mainModuleInst.sectionsLoaded && opened
|
||
|
||
asynchronous: true
|
||
anchors.top: parent.top
|
||
anchors.topMargin: 8
|
||
anchors.rightMargin: 8
|
||
anchors.bottom: parent.bottom
|
||
anchors.right: parent.right
|
||
width: active ?
|
||
parent.width - Constants.chatSectionLeftColumnWidth -
|
||
anchors.rightMargin - anchors.leftMargin : 0
|
||
|
||
sourceComponent: CreateChatView {
|
||
rootStore: ChatStores.RootStore {
|
||
contactsStore: appMain.rootStore.contactStore
|
||
communityTokensStore: appMain.communityTokensStore
|
||
emojiReactionsModel: appMain.rootStore.emojiReactionsModel
|
||
openCreateChat: createChatView.opened
|
||
chatCommunitySectionModule: appMain.rootStore.mainModuleInst.getChatSectionModule()
|
||
}
|
||
createChatPropertiesStore: appMain.createChatPropertiesStore
|
||
emojiPopup: statusEmojiPopup.item
|
||
stickersPopup: statusStickersPopupLoader.item
|
||
}
|
||
}
|
||
}
|
||
} // ColumnLayout
|
||
|
||
Component {
|
||
id: activityCenterPopupComponent
|
||
ActivityCenterPopup {
|
||
// TODO get screen size // Taken from old code top bar height was fixed there to 56
|
||
readonly property int _buttonSize: 56
|
||
|
||
x: parent.width - width - Style.current.smallPadding
|
||
y: parent.y + _buttonSize
|
||
height: appView.height - _buttonSize * 2
|
||
store: ChatStores.RootStore {
|
||
contactsStore: appMain.rootStore.contactStore
|
||
communityTokensStore: appMain.communityTokensStore
|
||
emojiReactionsModel: appMain.rootStore.emojiReactionsModel
|
||
openCreateChat: createChatView.opened
|
||
chatCommunitySectionModule: appMain.rootStore.mainModuleInst.getChatSectionModule()
|
||
}
|
||
activityCenterStore: appMain.activityCenterStore
|
||
}
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|
||
property var preSelectedAccount
|
||
property var preSelectedRecipient
|
||
property int preSelectedRecipientType
|
||
property string preSelectedHoldingID
|
||
property int preSelectedHoldingType
|
||
property int preSelectedSendType: Constants.SendType.Unknown
|
||
property string preDefinedAmountToSend
|
||
property bool onlyAssets: false
|
||
|
||
sourceComponent: SendModal {
|
||
onlyAssets: sendModal.onlyAssets
|
||
onClosed: {
|
||
sendModal.closed()
|
||
sendModal.preSelectedSendType = Constants.SendType.Unknown
|
||
sendModal.preSelectedHoldingID = ""
|
||
sendModal.preSelectedHoldingType = Constants.HoldingType.Unknown
|
||
sendModal.preSelectedAccount = undefined
|
||
sendModal.preSelectedRecipient = undefined
|
||
sendModal.preDefinedAmountToSend = ""
|
||
}
|
||
}
|
||
onLoaded: {
|
||
if (!!sendModal.preSelectedAccount) {
|
||
item.preSelectedAccount = sendModal.preSelectedAccount
|
||
}
|
||
if (!!sendModal.preSelectedRecipient) {
|
||
item.preSelectedRecipient = sendModal.preSelectedRecipient
|
||
item.preSelectedRecipientType = sendModal.preSelectedRecipientType
|
||
}
|
||
if(sendModal.preSelectedSendType !== Constants.SendType.Unknown) {
|
||
item.preSelectedSendType = sendModal.preSelectedSendType
|
||
}
|
||
if(preSelectedHoldingType !== Constants.HoldingType.Unknown) {
|
||
item.preSelectedHoldingID = sendModal.preSelectedHoldingID
|
||
item.preSelectedHoldingType = sendModal.preSelectedHoldingType
|
||
}
|
||
if(preDefinedAmountToSend != "") {
|
||
item.preDefinedAmountToSend = preDefinedAmountToSend
|
||
}
|
||
}
|
||
}
|
||
|
||
Action {
|
||
shortcut: "Ctrl+1"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(0)
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+2"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(1)
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+3"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(2)
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+4"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(3)
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+5"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(4)
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+6"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(5)
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+7"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(6)
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+8"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(7)
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+9"
|
||
onTriggered: {
|
||
Global.setNthEnabledSectionActive(8)
|
||
}
|
||
}
|
||
|
||
Action {
|
||
shortcut: "Ctrl+K"
|
||
onTriggered: {
|
||
// FIXME the focus is no longer on the AppMain when the popup is opened, so this does not work to close
|
||
if (!channelPickerLoader.active)
|
||
channelPickerLoader.active = true
|
||
|
||
if (channelPickerLoader.item.opened) {
|
||
channelPickerLoader.item.close()
|
||
channelPickerLoader.active = false
|
||
} else {
|
||
channelPickerLoader.item.open()
|
||
}
|
||
}
|
||
}
|
||
Action {
|
||
shortcut: "Ctrl+F"
|
||
onTriggered: {
|
||
// FIXME the focus is no longer on the AppMain when the popup is opened, so this does not work to close
|
||
if (appSearch.active) {
|
||
appSearch.closeSearchPopup()
|
||
} else {
|
||
appSearch.openSearchPopup()
|
||
}
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: channelPickerLoader
|
||
active: false
|
||
asynchronous: true
|
||
sourceComponent: StatusSearchListPopup {
|
||
searchBoxPlaceholder: qsTr("Where do you want to go?")
|
||
model: rootStore.chatSearchModel
|
||
delegate: StatusListItem {
|
||
property var modelData
|
||
property bool isCurrentItem: true
|
||
function filterAccepts(searchText) {
|
||
const lowerCaseSearchText = searchText.toLowerCase()
|
||
return title.toLowerCase().includes(lowerCaseSearchText) || label.toLowerCase().includes(lowerCaseSearchText)
|
||
}
|
||
|
||
title: modelData ? modelData.name : ""
|
||
label: modelData? modelData.sectionName : ""
|
||
highlighted: isCurrentItem
|
||
sensor.hoverEnabled: false
|
||
statusListItemIcon {
|
||
name: modelData ? modelData.name : ""
|
||
active: true
|
||
}
|
||
asset.width: 30
|
||
asset.height: 30
|
||
asset.color: modelData ? modelData.color ? modelData.color : Utils.colorForColorId(modelData.colorId) : ""
|
||
asset.name: modelData ? modelData.icon : ""
|
||
asset.charactersLen: 2
|
||
asset.letterSize: asset._twoLettersSize
|
||
ringSettings.ringSpecModel: modelData ? modelData.colorHash : undefined
|
||
}
|
||
|
||
onAboutToShow: rootStore.rebuildChatSearchModel()
|
||
onSelected: {
|
||
rootStore.setActiveSectionChat(modelData.sectionId, modelData.chatId)
|
||
close()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
StatusListView {
|
||
id: toastArea
|
||
objectName: "ephemeralNotificationList"
|
||
anchors.right: parent.right
|
||
anchors.rightMargin: 8
|
||
anchors.bottom: parent.bottom
|
||
anchors.bottomMargin: 60
|
||
width: 374
|
||
height: Math.min(parent.height - 120, toastArea.contentHeight)
|
||
spacing: 8
|
||
verticalLayoutDirection: ListView.BottomToTop
|
||
model: appMain.rootStore.mainModuleInst.ephemeralNotificationModel
|
||
clip: false
|
||
|
||
delegate: StatusToastMessage {
|
||
objectName: "statusToastMessage"
|
||
width: ListView.view.width
|
||
primaryText: model.title
|
||
secondaryText: model.subTitle
|
||
icon.name: model.icon
|
||
loading: model.loading
|
||
type: model.ephNotifType
|
||
linkUrl: model.url
|
||
duration: model.durationInMs
|
||
onClicked: {
|
||
appMain.rootStore.mainModuleInst.ephemeralNotificationClicked(model.timestamp)
|
||
this.open = false
|
||
}
|
||
onLinkActivated: {
|
||
if (link.startsWith("#") && link !== "#") { // internal link to section
|
||
const sectionArgs = link.substring(1).split("/")
|
||
const section = sectionArgs[0]
|
||
let subsection = sectionArgs.length > 1 ? sectionArgs[1] : 0
|
||
Global.changeAppSectionBySectionType(section, subsection)
|
||
}
|
||
else
|
||
Global.openLink(link)
|
||
}
|
||
|
||
onClose: {
|
||
appMain.rootStore.mainModuleInst.removeEphemeralNotification(model.timestamp)
|
||
}
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: keycardPopupForAuthentication
|
||
active: false
|
||
sourceComponent: KeycardPopup {
|
||
sharedKeycardModule: appMain.rootStore.mainModuleInst.keycardSharedModuleForAuthentication
|
||
}
|
||
|
||
onLoaded: {
|
||
keycardPopupForAuthentication.item.open()
|
||
}
|
||
}
|
||
|
||
Loader {
|
||
id: keycardPopup
|
||
active: false
|
||
sourceComponent: KeycardPopup {
|
||
sharedKeycardModule: appMain.rootStore.mainModuleInst.keycardSharedModule
|
||
}
|
||
|
||
onLoaded: {
|
||
keycardPopup.item.open()
|
||
}
|
||
}
|
||
|
||
DropAreaPanel {
|
||
id: rootDropAreaPanel
|
||
|
||
width: appMain.width
|
||
height: appMain.height
|
||
}
|
||
|
||
Loader {
|
||
id: userAgreementLoader
|
||
active: production && !localAppSettings.testEnvironment
|
||
sourceComponent: UserAgreementPopup {
|
||
visible: appMain.visible
|
||
onClosed: userAgreementLoader.active = false
|
||
}
|
||
}
|
||
}
|