mirror of
https://github.com/status-im/status-app.git
synced 2026-08-27 07:01:14 +00:00
feat(browser): mobile webview (#20178)
* fix(browser): fix pr comments * fix(browser): fix offset between navigation and webview * fix(browser): split logic into contexts * dialogs * favorites * webview * fix(browser): crash when webview tries to render and destroy at the same time * fix(browser): opened dialogs counter * fix(browser): load platform-specific webview * fix(wc): unused wc files * fix(browser): separate dapps and websocket logic * fix(browser): fix pr comments * fix(browser): fix popup tracker * fix(browser): fix pr * fix(browser): fix PR * fix(browser): fix PR
This commit is contained in:
@@ -38,6 +38,12 @@ fi
|
||||
if [[ "${OS}" == "android" ]]; then
|
||||
[[ -z "${JAVA_HOME}" ]] && { echo "JAVA_HOME is not set"; exit 1; }
|
||||
|
||||
STATUSQ_DIR=${STATUSQ:-"${STATUS_DESKTOP}/ui/StatusQ"}
|
||||
MOBILEWEBVIEW_JAVA_DIR_FILE="${STATUSQ_DIR}/build/${OS}/StatusQ/mobilewebview_java_dir.txt"
|
||||
if [[ -z "${MOBILEWEBVIEW_ANDROID_JAVA_SRC}" && -f "${MOBILEWEBVIEW_JAVA_DIR_FILE}" ]]; then
|
||||
MOBILEWEBVIEW_ANDROID_JAVA_SRC="$(<"${MOBILEWEBVIEW_JAVA_DIR_FILE}")"
|
||||
fi
|
||||
|
||||
# Export BUILD_VARIANT for build.gradle to pick up
|
||||
export BUILD_VARIANT
|
||||
|
||||
@@ -62,6 +68,12 @@ if [[ "${OS}" == "android" ]]; then
|
||||
rsync -a --exclude='libs.xml' "$CWD/../android/qt${QT_MAJOR}/res/" "$BUILD_DIR/android-build/res/" 2>/dev/null || true
|
||||
rsync -a "$CWD/../android/qt${QT_MAJOR}/src/" "$BUILD_DIR/android-build/src/" 2>/dev/null || true
|
||||
|
||||
if [[ -n "${MOBILEWEBVIEW_ANDROID_JAVA_SRC}" && \
|
||||
-f "${MOBILEWEBVIEW_ANDROID_JAVA_SRC}/org/mobilewebview/MobileWebView.java" ]]; then
|
||||
echo "Including MobileWebView Java sources from: ${MOBILEWEBVIEW_ANDROID_JAVA_SRC}"
|
||||
rsync -a "${MOBILEWEBVIEW_ANDROID_JAVA_SRC}/org/" "$BUILD_DIR/android-build/src/org/"
|
||||
fi
|
||||
|
||||
cd "$BUILD_DIR/android-build"
|
||||
|
||||
BIN_DIR=${BIN_DIR:-"$CWD/../bin/android/qt6"}
|
||||
|
||||
@@ -34,6 +34,12 @@ cmake -S "${STATUSQ}" -B "${BUILD_DIR}" \
|
||||
make -C "${BUILD_DIR}" SCodes -j "$(nproc)"
|
||||
make -C "${BUILD_DIR}" StatusQ -j "$(nproc)"
|
||||
|
||||
MOBILEWEBVIEW_JAVA_DIR_FILE="${BUILD_DIR}/mobilewebview_java_dir.txt"
|
||||
if [[ -f "${MOBILEWEBVIEW_JAVA_DIR_FILE}" ]]; then
|
||||
export MOBILEWEBVIEW_ANDROID_JAVA_SRC
|
||||
MOBILEWEBVIEW_ANDROID_JAVA_SRC="$(<"${MOBILEWEBVIEW_JAVA_DIR_FILE}")"
|
||||
fi
|
||||
|
||||
mkdir -p "${LIB_DIR}"
|
||||
|
||||
STATUSQ_LIB=$(find "${BUILD_DIR}" -name "libStatusQ${LIB_SUFFIX}${LIB_EXT}")
|
||||
|
||||
@@ -68,6 +68,16 @@ proc determineStatusAppIconPath(): string =
|
||||
|
||||
return "/../status-dev.png"
|
||||
|
||||
proc ensureQmlSelector(selector: string) =
|
||||
let current = getEnv("QT_FILE_SELECTORS")
|
||||
if current.len == 0:
|
||||
putEnv("QT_FILE_SELECTORS", selector)
|
||||
return
|
||||
|
||||
let selectors = current.split(",")
|
||||
if selector notin selectors:
|
||||
putEnv("QT_FILE_SELECTORS", current & "," & selector)
|
||||
|
||||
proc prepareLogging() =
|
||||
# Outputs logs in the node tab
|
||||
for output in defaultChroniclesStream.outputs.fields():
|
||||
@@ -180,6 +190,10 @@ proc mainProc() =
|
||||
|
||||
ensureDirectories(DATADIR, TMPDIR, LOGDIR)
|
||||
|
||||
# Mobile builds use the mobile selector for browser WebView adapter replacement.
|
||||
when main_constants.IS_MOBILE:
|
||||
ensureQmlSelector("mobile")
|
||||
|
||||
let isExperimental = isExperimental()
|
||||
let resourcesPath = determineResourcePath()
|
||||
let openUri = determineOpenUri()
|
||||
|
||||
@@ -33,6 +33,7 @@ SplitView {
|
||||
userUID: "0xdeadbeef"
|
||||
transactionStore: TransactionStoreMock {}
|
||||
thirdpartyServicesEnabled: true
|
||||
dappsEnabled: true
|
||||
connectorController: null
|
||||
platformOS: ctrlPlatformOS.currentValue
|
||||
|
||||
|
||||
@@ -13,10 +13,6 @@ int main(int argc, char *argv[])
|
||||
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
// FIXME: revert when full integration is done
|
||||
// (https://github.com/status-im/status-app/pull/20178)
|
||||
qputenv("QT_FILE_SELECTORS", "noWebEngine");
|
||||
|
||||
QGuiApplication::setOrganizationName(u"Status"_s);
|
||||
QGuiApplication::setOrganizationDomain(u"status.im"_s);
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ if(IOS OR ANDROID OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
MobileWebView
|
||||
|
||||
GIT_REPOSITORY https://github.com/status-im/mobilewebview.git
|
||||
GIT_TAG 551b1610deceec99b5b99e9612ab4947faa8c332
|
||||
GIT_TAG feb2db5a5374ef75c1c11162b6892189c199f954
|
||||
SOURCE_SUBDIR mobilewebview
|
||||
)
|
||||
FetchContent_MakeAvailable(MobileWebView)
|
||||
@@ -302,6 +302,10 @@ if(IOS OR ANDROID OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
if(TARGET MobileWebView)
|
||||
target_link_libraries(StatusQ PRIVATE MobileWebView)
|
||||
target_compile_definitions(StatusQ PRIVATE STATUSQ_HAS_MOBILEWEBVIEW=1)
|
||||
if(ANDROID AND DEFINED mobilewebview_SOURCE_DIR)
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/mobilewebview_java_dir.txt"
|
||||
"${mobilewebview_SOURCE_DIR}/mobilewebview/android/src")
|
||||
endif()
|
||||
install(TARGETS MobileWebView
|
||||
RUNTIME DESTINATION StatusQ
|
||||
LIBRARY DESTINATION StatusQ
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
import QtWebEngine
|
||||
import QtWebChannel
|
||||
|
||||
import StatusQ
|
||||
|
||||
// Helper to load and setup an instance of \c WebEngineView
|
||||
//
|
||||
// The \c webChannelObjects property is used to register specific objects
|
||||
//
|
||||
// Loading qrc:/StatusQ/Components/private/qwebchannel/qwebchannel.js and
|
||||
// qrc:/StatusQ/Components/private/qwebchannel/helpers.js will provide
|
||||
// access to window.statusq APIs used to exchange data between the internal
|
||||
// web engine and the QML application
|
||||
//
|
||||
// It doesn't load the web engine until NetworkChecker detects and active internet
|
||||
// connection to avoid the corner case of initializing the web engine without
|
||||
// network connectivity. If the web engine is initialized without network connectivity
|
||||
// it won't restore the connectivity when it's available on Mac OS
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property url url
|
||||
required property var webChannelObjects
|
||||
property string profileName: "Default"
|
||||
|
||||
// Used to control the loading of the web engine
|
||||
property bool active: false
|
||||
// Useful to monitor the loading state of the web engine (depends on active and internet connectivity)
|
||||
readonly property bool isActive: loader.active
|
||||
property alias instance: loader.item
|
||||
property bool waitForInternet: true
|
||||
|
||||
signal engineLoaded(WebEngineView instance)
|
||||
signal engineUnloaded()
|
||||
signal pageLoaded()
|
||||
signal pageLoadingError(string errorString)
|
||||
|
||||
Component {
|
||||
id: webEngineViewComponent
|
||||
|
||||
WebEngineView {
|
||||
id: webEngineView
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
visible: false
|
||||
|
||||
url: root.url
|
||||
webChannel: statusChannel
|
||||
profile.storageName: root.profileName
|
||||
|
||||
onLoadingChanged: function(loadRequest) {
|
||||
switch(loadRequest.status) {
|
||||
case WebEngineView.LoadSucceededStatus:
|
||||
root.pageLoaded()
|
||||
break
|
||||
case WebEngineView.LoadFailedStatus:
|
||||
root.pageLoadingError(loadRequest.errorString)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
WebChannel {
|
||||
id: statusChannel
|
||||
registeredObjects: root.webChannelObjects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
active: root.active && (!root.waitForInternet || (d.passedFirstTimeInitialization || networkChecker.isOnline))
|
||||
|
||||
onStatusChanged: function() {
|
||||
if (status === Loader.Ready) {
|
||||
root.engineLoaded(loader.item)
|
||||
d.passedFirstTimeInitialization = true
|
||||
} else if (status === Loader.Null) {
|
||||
root.engineUnloaded()
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: webEngineViewComponent
|
||||
}
|
||||
|
||||
NetworkChecker {
|
||||
id: networkChecker
|
||||
|
||||
// Deactivate searching for network connectivity after the web engine is loaded
|
||||
active: !d.passedFirstTimeInitialization
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
// Used to hold the loading of the web engine until internet connectivity is available
|
||||
property bool passedFirstTimeInitialization: false
|
||||
}
|
||||
}
|
||||
@@ -68,5 +68,4 @@ StatusToastMessage 0.1 StatusToastMessage.qml
|
||||
StatusToolBar 0.1 StatusToolBar.qml
|
||||
StatusUserImage 0.1 StatusUserImage.qml
|
||||
StatusVideo 0.1 StatusVideo.qml
|
||||
WebEngineLoader 0.1 WebEngineLoader.qml
|
||||
StatusLoadingPageIndicator 0.1 StatusLoadingPageIndicator.qml
|
||||
|
||||
@@ -332,4 +332,17 @@ QtObject {
|
||||
|
||||
return content.length > 0 ? (content + tidSuffix) : tidSuffix
|
||||
}
|
||||
|
||||
function hasPopups(overlayChildren) {
|
||||
if (!overlayChildren)
|
||||
return false
|
||||
|
||||
return overlayChildren.filter(
|
||||
item => {
|
||||
if (!item)
|
||||
return false
|
||||
const str = item.toString()
|
||||
return str.includes("QQuickPopupItem") && !str.includes("StatusToolTip")
|
||||
}).length > 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
<file>StatusQ/Components/StatusToolBar.qml</file>
|
||||
<file>StatusQ/Components/StatusUserImage.qml</file>
|
||||
<file>StatusQ/Components/StatusVideo.qml</file>
|
||||
<file>StatusQ/Components/WebEngineLoader.qml</file>
|
||||
<file>StatusQ/Components/private/LoadingDotItem.qml</file>
|
||||
<file>StatusQ/Components/private/StatusComboboxBackground.qml</file>
|
||||
<file>StatusQ/Components/private/StatusComboboxIndicator.qml</file>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Page</title>
|
||||
<script src="../../../src/StatusQ/Components/private/qwebchannel/qwebchannel.js" defer></script>
|
||||
<script src="../../../src/StatusQ/Components/private/qwebchannel/helpers.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test Page</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,102 +0,0 @@
|
||||
import QtQuick
|
||||
import QtTest
|
||||
|
||||
import QtWebEngine
|
||||
import QtWebChannel
|
||||
|
||||
import StatusQ.Components
|
||||
import StatusQ.TestHelpers
|
||||
|
||||
TestCase {
|
||||
id: root
|
||||
name: "TestWebEngineLoader"
|
||||
|
||||
QtObject {
|
||||
id: testObject
|
||||
|
||||
WebChannel.id: "testObject"
|
||||
|
||||
signal webChannelInitOk()
|
||||
signal webChannelError()
|
||||
function signalWebChannelInitResult(error) {
|
||||
if(error) {
|
||||
webChannelError()
|
||||
} else {
|
||||
webChannelInitOk()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
active: false
|
||||
|
||||
sourceComponent: WebEngineLoader {
|
||||
url: Qt.resolvedUrl("./WebEngineLoader/test.html")
|
||||
webChannelObjects: [testObject]
|
||||
|
||||
waitForInternet: false
|
||||
}
|
||||
}
|
||||
SignalSpy { id: loadedSpy; target: loader; signalName: "loaded" }
|
||||
|
||||
SignalSpy { id: webEngineLoadedSpy; target: loader.item; signalName: "engineLoaded" }
|
||||
SignalSpy { id: pageLoadedSpy; target: loader.item; signalName: "pageLoaded" }
|
||||
SignalSpy { id: engineUnloadedSpy; target: loader.item; signalName: "engineUnloaded" }
|
||||
SignalSpy { id: pageLoadingErrorSpy; target: loader.item; signalName: "onPageLoadingError" }
|
||||
|
||||
function init() {
|
||||
for (var i = 0; i < root.children.length; i++) {
|
||||
const child = root.children[i]
|
||||
if(child.hasOwnProperty("signalName")) {
|
||||
child.clear()
|
||||
}
|
||||
}
|
||||
loader.active = true
|
||||
loadedSpy.wait(1000);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
loader.active = false
|
||||
}
|
||||
|
||||
function test_loadUnload() {
|
||||
const webEngine = loader.item
|
||||
compare(webEngine.instance, null, "By default the engine is not loaded")
|
||||
webEngine.active = true
|
||||
|
||||
webEngineLoadedSpy.wait(1000)
|
||||
verify(webEngine.instance !== null , "The WebEngineView should be available")
|
||||
|
||||
pageLoadedSpy.wait(1000)
|
||||
webEngine.active = false
|
||||
engineUnloadedSpy.wait(1000);
|
||||
|
||||
verify(webEngine.instance === null , "The WebEngineView should be unavailable")
|
||||
}
|
||||
|
||||
SignalSpy { id: wcInitOkSpy; target: testObject; signalName: "webChannelInitOk" }
|
||||
SignalSpy { id: wcInitErrorSpy; target: testObject; signalName: "webChannelError" }
|
||||
function test_executeCode() {
|
||||
const webEngine = loader.item
|
||||
webEngine.active = true
|
||||
pageLoadedSpy.wait(1000);
|
||||
|
||||
let errorResult = null
|
||||
webEngine.instance.runJavaScript(`
|
||||
window.testError = window.statusq.error;
|
||||
try {
|
||||
window.statusq.channel.objects.testObject.signalWebChannelInitResult("");
|
||||
} catch (e) {
|
||||
window.testError = e.message;
|
||||
}
|
||||
window.testError
|
||||
`, function(result) {
|
||||
errorResult = result
|
||||
})
|
||||
|
||||
wcInitOkSpy.wait(1000);
|
||||
compare(errorResult, "", "Expected empty error string if all good")
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtWebEngine
|
||||
|
||||
import QtModelsToolkit
|
||||
|
||||
@@ -26,6 +25,7 @@ import AppLayouts.Browser.popups
|
||||
import AppLayouts.Browser.controls
|
||||
import AppLayouts.Browser.views
|
||||
import AppLayouts.Browser.panels
|
||||
import AppLayouts.Browser.webview
|
||||
|
||||
// Code based on https://code.qt.io/cgit/qt/qtwebengine.git/tree/examples/webengine/quicknanobrowser/BrowserWindow.qml?h=5.15
|
||||
// Licensed under BSD
|
||||
@@ -35,6 +35,7 @@ StatusSectionLayout {
|
||||
required property bool isMobile
|
||||
required property string userUID
|
||||
required property bool thirdpartyServicesEnabled
|
||||
required property bool dappsEnabled
|
||||
|
||||
required property TransactionStore transactionStore
|
||||
|
||||
@@ -49,23 +50,20 @@ StatusSectionLayout {
|
||||
|
||||
property bool isDebugEnabled: false
|
||||
property string platformOS: Qt.platform.os
|
||||
// FIXME: restore in the next PR
|
||||
property bool useWebViewAdapter: false
|
||||
readonly property Component webViewAdapterComponent: webEngineAdapterComponent
|
||||
|
||||
signal sendToRecipientRequested(string address)
|
||||
|
||||
function openUrlInNewTab(url) {
|
||||
var tab = _internal.addNewTab()
|
||||
tab.url = _internal.determineRealURL(url)
|
||||
tab.url = root.browserRootStore.determineRealURL(url)
|
||||
}
|
||||
|
||||
function reloadCurrentTab() {
|
||||
_internal.currentWebView?.reload()
|
||||
webViewContext.reloadCurrent()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
var tab = webStackView.createEmptyTab(connectorBridge.defaultProfileParams, true);
|
||||
var tab = webViewContext.createEmptyTab(connectorBridge.defaultProfileParams, true);
|
||||
// For Devs: Uncomment the next line if you want to use the simpledapp on first load
|
||||
// tab.url = root.browserRootStore.determineRealURL("https://simpledapp.eth");
|
||||
}
|
||||
@@ -96,91 +94,25 @@ StatusSectionLayout {
|
||||
QtObject {
|
||||
id: _internal
|
||||
|
||||
property Item currentWebView: tabs.currentIndex < tabs.count ? webStackView.getCurrentWebView() : null
|
||||
readonly property bool currentTabIncognito: webStackView.getCurrentWebView()?.offTheRecord ?? false
|
||||
property Item currentWebView: webViewContext.currentWebView
|
||||
readonly property bool currentTabIncognito: currentWebView?.offTheRecord ?? false
|
||||
property bool webViewHidden: false
|
||||
|
||||
property Component jsDialogComponent: JSDialogWindow {}
|
||||
|
||||
property Component accessDialogComponent: BrowserConnectionModal {
|
||||
browserRootStore: root.browserRootStore
|
||||
browserWalletStore: root.browserWalletStore
|
||||
|
||||
parent: mainView
|
||||
x: mainView.width - width - Theme.halfPadding
|
||||
y: mainView.y + browserToolbar.height + Theme.halfPadding
|
||||
}
|
||||
|
||||
property Component sendTransactionModalComponent: SendModal {
|
||||
anchors.centerIn: parent
|
||||
preSelectedHoldingID: "ETH"
|
||||
preSelectedHoldingType: Constants.TokenType.ERC20
|
||||
store: root.transactionStore
|
||||
}
|
||||
|
||||
property Component signMessageModalComponent: SignMessageModal {
|
||||
browserRootStore: root.browserRootStore
|
||||
signingPhrase: root.browserWalletStore.signingPhrase
|
||||
}
|
||||
|
||||
property StatusMessageDialog sendingError: StatusMessageDialog {
|
||||
title: qsTr("Error sending the transaction")
|
||||
icon: StatusMessageDialog.StandardIcon.Critical
|
||||
standardButtons: Dialog.Ok
|
||||
}
|
||||
|
||||
property StatusMessageDialog signingError: StatusMessageDialog {
|
||||
title: qsTr("Error signing message")
|
||||
icon: StatusMessageDialog.StandardIcon.Critical
|
||||
standardButtons: Dialog.Ok
|
||||
}
|
||||
|
||||
function addNewDownloadTab() {
|
||||
webStackView.createDownloadTab(tabs.count !== 0 ? currentWebView.profileParams : connectorBridge.defaultProfileParams);
|
||||
webViewContext.createDownloadTab(tabs.count !== 0 ? currentWebView.profileParams : connectorBridge.defaultProfileParams);
|
||||
tabs.currentIndex = tabs.count - 1;
|
||||
}
|
||||
|
||||
function addNewTab() {
|
||||
var tab = webStackView.createEmptyTab(tabs.count !== 0 ? currentWebView.profileParams : connectorBridge.defaultProfileParams);
|
||||
var tab = webViewContext.createEmptyTab(tabs.count !== 0 ? currentWebView.profileParams : connectorBridge.defaultProfileParams);
|
||||
browserToolbar.activateAddressBar()
|
||||
return tab;
|
||||
}
|
||||
|
||||
function onDownloadRequested(download) {
|
||||
download.accept();
|
||||
root.downloadsStore.addDownload(download)
|
||||
root.showFooter = true
|
||||
|
||||
// close the tab launched only for starting download
|
||||
if (!download.view)
|
||||
return
|
||||
|
||||
// find tab for this view
|
||||
for (var i = 0; i < tabs.count; ++i) {
|
||||
var tab = webStackView.getWebView(i)
|
||||
// close the “download-only” tab
|
||||
if (tab === download.view &&
|
||||
!tab.htmlPageLoaded &&
|
||||
tab.title === "") {
|
||||
webStackView.removeView(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function determineRealURL(url) {
|
||||
return root.browserRootStore.determineRealURL(url)
|
||||
}
|
||||
|
||||
onCurrentWebViewChanged: () => findBar.reset()
|
||||
|
||||
readonly property var currentViewBookmarkEntry: ModelEntry {
|
||||
sourceModel: root.bookmarksStore.bookmarksModel
|
||||
key: "url"
|
||||
value: (_internal.currentWebView && _internal.currentWebView.url)
|
||||
? _internal.currentWebView.url.toString()
|
||||
: ""
|
||||
}
|
||||
}
|
||||
|
||||
invertedLayout: root.isMobile
|
||||
@@ -188,6 +120,34 @@ StatusSectionLayout {
|
||||
headerPadding: 0
|
||||
backgroundColor: Theme.palette.statusAppNavBar.backgroundColor
|
||||
|
||||
BrowserFavoritesContext {
|
||||
id: favoritesContext
|
||||
currentWebView: _internal.currentWebView
|
||||
bookmarksStore: root.bookmarksStore
|
||||
shouldShowFavoritesBar: localAccountSensitiveSettings.shouldShowFavoritesBar
|
||||
openPopupFn: (popup, params) => Global.openPopup(popup, params)
|
||||
addFavoriteModal: addFavoriteModal
|
||||
}
|
||||
|
||||
BrowserDialogsContext {
|
||||
id: dialogsContext
|
||||
networksStore: root.networksStore
|
||||
browserActivityStore: root.browserActivityStore
|
||||
browserWalletStore: root.browserWalletStore
|
||||
openPopupFn: (popup, params) => Global.openPopup(popup, params)
|
||||
jsDialogComponent: _internal.jsDialogComponent
|
||||
dialogParent: root
|
||||
}
|
||||
|
||||
BrowserDownloadsContext {
|
||||
id: downloadsContext
|
||||
downloadsStore: root.downloadsStore
|
||||
tabsModel: tabs
|
||||
getWebViewFn: (index) => webViewContext.getWebView(index)
|
||||
removeViewFn: (index) => webViewContext.removeView(index)
|
||||
setFooterVisibleFn: (visible) => root.showFooter = visible
|
||||
}
|
||||
|
||||
// TODO: move this to a single browser header qml file
|
||||
headerContent: ColumnLayout {
|
||||
spacing: 0
|
||||
@@ -200,14 +160,14 @@ StatusSectionLayout {
|
||||
|
||||
currentTabIncognito: _internal.currentTabIncognito
|
||||
determineRealURL: function(url) {
|
||||
return _internal.determineRealURL(url)
|
||||
return root.browserRootStore.determineRealURL(url)
|
||||
}
|
||||
onOpenNewTabTriggered: _internal.addNewTab()
|
||||
fnGetWebView: (index) => {
|
||||
return webStackView.getWebView(index)
|
||||
return webViewContext.getWebView(index)
|
||||
}
|
||||
onRemoveView: (index) => {
|
||||
webStackView.removeView(index)
|
||||
webViewContext.removeView(index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,87 +186,61 @@ StatusSectionLayout {
|
||||
walletAccountsBtnAvailable: !root.isMobile
|
||||
|
||||
openTabsCount: tabs.count
|
||||
currentTabIncognito: _internal.currentWebView?.profile.offTheRecord ?? false
|
||||
currentTabIsBookmark: _internal.currentViewBookmarkEntry.available &&_internal.currentViewBookmarkEntry.item
|
||||
currentTabIncognito: _internal.currentWebView?.offTheRecord ?? false
|
||||
currentTabIsBookmark: favoritesContext.currentTabIsBookmark
|
||||
currentTabLoading: (!!_internal.currentWebView && _internal.currentWebView.loading)
|
||||
browserDappsModel: browserDappsProvider.model
|
||||
|
||||
onRequestHistoryPopup: () => historyMenu.open()
|
||||
onRequestGoBack: () => _internal.currentWebView.goBack()
|
||||
onRequestGoForward: () => _internal.currentWebView.goForward()
|
||||
onRequestReloadPage: () => _internal.currentWebView.reload()
|
||||
onRequestStopLoadingPage: () => _internal.currentWebView.stop()
|
||||
onRequestHistoryPopup: () => dialogsContext.openHistoryMenu(historyMenu)
|
||||
onRequestGoBack: () => webViewContext.goBackCurrent()
|
||||
onRequestGoForward: () => webViewContext.goForwardCurrent()
|
||||
onRequestReloadPage: () => webViewContext.reloadCurrent()
|
||||
onRequestStopLoadingPage: () => webViewContext.stopCurrent()
|
||||
onRequestOpenDapp: (url) => {
|
||||
if (_internal.currentWebView) {
|
||||
_internal.currentWebView.url = _internal.determineRealURL(url)
|
||||
webViewContext.setCurrentWebUrl(url)
|
||||
}
|
||||
}
|
||||
onRequestDisconnectDapp: (dappUrl) => {
|
||||
connectorBridge.disconnect(dappUrl)
|
||||
}
|
||||
onAddBookmarkRequested: () => {
|
||||
Global.openPopup(addFavoriteModal,
|
||||
{
|
||||
modifiyModal: !!browserToolbar.currentTabIsBookmark,
|
||||
toolbarMode: true,
|
||||
ogUrl: _internal.currentViewBookmarkEntry.item && _internal.currentViewBookmarkEntry.available ?
|
||||
_internal.currentViewBookmarkEntry.item.url : _internal.currentWebView.url,
|
||||
ogName: _internal.currentViewBookmarkEntry.item && _internal.currentViewBookmarkEntry.available ?
|
||||
_internal.currentViewBookmarkEntry.item.name : _internal.currentWebView.title
|
||||
})
|
||||
favoritesContext.openAddFavoritePopup(!!browserToolbar.currentTabIsBookmark)
|
||||
}
|
||||
onRequestLaunchInBrowser: (url) => {
|
||||
if (localAccountSensitiveSettings.useBrowserEthereumExplorer !== Constants.browserEthereumExplorerNone && url.startsWith("0x")) {
|
||||
_internal.currentWebView.url = root.browserRootStore.get0xFormedUrl(localAccountSensitiveSettings.useBrowserEthereumExplorer, url)
|
||||
webViewContext.setCurrentWebUrl(root.browserRootStore.get0xFormedUrl(localAccountSensitiveSettings.useBrowserEthereumExplorer, url))
|
||||
return
|
||||
}
|
||||
if (localAccountSensitiveSettings.selectedBrowserSearchEngineId !== SearchEnginesConfig.browserSearchEngineNone && !Utils.isURL(url) && !Utils.isURLWithOptionalProtocol(url)) {
|
||||
_internal.currentWebView.url = root.browserRootStore.getFormedUrl(localAccountSensitiveSettings.selectedBrowserSearchEngineId, url)
|
||||
webViewContext.setCurrentWebUrl(root.browserRootStore.getFormedUrl(localAccountSensitiveSettings.selectedBrowserSearchEngineId, url))
|
||||
return
|
||||
} else if (Utils.isURLWithOptionalProtocol(url)) {
|
||||
url = "https://" + url
|
||||
}
|
||||
_internal.currentWebView.url = _internal.determineRealURL(url);
|
||||
webViewContext.setCurrentWebUrl(url);
|
||||
}
|
||||
onRequestWalletMenu: () => {
|
||||
// Initialize activity filters before opening popup
|
||||
const activeChainIds = SQUtils.ModelUtils.modelToFlatArray(
|
||||
root.networksStore.activeNetworks, "chainId")
|
||||
if (activeChainIds.length > 0) {
|
||||
root.browserActivityStore.activityController.setFilterChainsJson(
|
||||
JSON.stringify(activeChainIds), true)
|
||||
}
|
||||
const currentAddress = root.browserWalletStore.dappBrowserAccount.address
|
||||
root.browserActivityStore.activityController.setFilterAddressesJson(
|
||||
JSON.stringify([currentAddress]))
|
||||
|
||||
Global.openPopup(browserWalletMenu)
|
||||
}
|
||||
onRequestWalletMenu: () => dialogsContext.openWalletMenu(browserWalletMenu)
|
||||
onRequestAllOpenTabsView: () => {
|
||||
// TODO: Launch All Tabs View
|
||||
// https://github.com/status-im/status-app/issues/19569
|
||||
}
|
||||
onOpenSettingMenu: () => {
|
||||
settingsMenu.open()
|
||||
}
|
||||
onOpenSettingMenu: () => dialogsContext.openSettingsMenu(settingsMenu)
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: favoritesBarLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: active ? 38: 0
|
||||
active: localAccountSensitiveSettings.shouldShowFavoritesBar &&
|
||||
root.bookmarksStore.bookmarksModel.ModelCount.count > 0
|
||||
Layout.preferredHeight: favoritesContext.favoritesBarActive ? 38 : 0
|
||||
active: favoritesContext.favoritesBarActive
|
||||
sourceComponent: FavoritesBar {
|
||||
currentTabIncognito: _internal.currentTabIncognito
|
||||
bookmarkModel: root.bookmarksStore.bookmarksModel
|
||||
favoritesMenu: favoriteMenu
|
||||
onSetAsCurrentWebUrl: (url) => _internal.currentWebView.url = _internal.determineRealURL(url)
|
||||
onSetAsCurrentWebUrl: (url) => webViewContext.setCurrentWebUrl(url)
|
||||
onOpenInNewTab: (url) => root.openUrlInNewTab(url)
|
||||
onAddFavModalRequested: {
|
||||
Global.openPopup(addFavoriteModal, {toolbarMode: true,
|
||||
ogUrl: _internal.currentViewBookmarkEntry.item ? _internal.currentViewBookmarkEntry.item.url : _internal.currentWebView.url,
|
||||
ogName: _internal.currentViewBookmarkEntry.item ? _internal.currentViewBookmarkEntry.item.name : _internal.currentWebView.title})
|
||||
favoritesContext.openAddFavoritePopup(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,13 +257,13 @@ StatusSectionLayout {
|
||||
|
||||
onFindNext: {
|
||||
if (text)
|
||||
_internal.currentWebView && _internal.currentWebView.findText(text);
|
||||
webViewContext.findTextCurrent(text)
|
||||
else if (!visible)
|
||||
visible = true;
|
||||
}
|
||||
onFindPrevious: {
|
||||
if (text)
|
||||
_internal.currentWebView && _internal.currentWebView.findText(text, WebEngineView.FindBackward);
|
||||
webViewContext.findTextCurrent(text, true)
|
||||
else if (!visible)
|
||||
visible = true;
|
||||
}
|
||||
@@ -340,6 +274,33 @@ StatusSectionLayout {
|
||||
sourceComponent: downloadBar
|
||||
}
|
||||
|
||||
BrowserWebViewContext {
|
||||
id: webViewContext
|
||||
thirdpartyServicesEnabled: root.thirdpartyServicesEnabled
|
||||
isDebugEnabled: root.isDebugEnabled
|
||||
isMobile: root.isMobile
|
||||
browserSettings: localAccountSensitiveSettings
|
||||
webChannel: connectorBridge.channel
|
||||
hostStackLayout: webStackView
|
||||
tabsModel: tabs
|
||||
defaultProfileParams: connectorBridge.defaultProfileParams
|
||||
bookmarksStore: root.bookmarksStore
|
||||
downloadsStore: root.downloadsStore
|
||||
determineRealURLFn: (url) => root.browserRootStore.determineRealURL(url)
|
||||
downloadRequestHandler: (download) => downloadsContext.handleDownloadRequest(download)
|
||||
sslErrorHandler: (error) => {
|
||||
error.defer()
|
||||
sslDialog.enqueue(error)
|
||||
}
|
||||
jsDialogHandler: (request) => dialogsContext.openJsDialog(request)
|
||||
findTextFinishedHandler: (result) => {
|
||||
if (!findBar.visible)
|
||||
findBar.visible = true
|
||||
findBar.numberOfMatches = result.numberOfMatches
|
||||
findBar.activeMatch = result.activeMatch
|
||||
}
|
||||
}
|
||||
|
||||
centerPanel: ColumnLayout {
|
||||
id: mainView
|
||||
spacing: 0
|
||||
@@ -350,54 +311,6 @@ StatusSectionLayout {
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
function createEmptyTab(profileParams, createAsStartPage = false, focusOnNewTab = true, url = undefined) {
|
||||
focusOnNewTab = focusOnNewTab && !createAsStartPage
|
||||
|
||||
var webview = root.webViewAdapterComponent.createObject(webStackView, {
|
||||
profileParams: profileParams,
|
||||
isDownloadView: false
|
||||
})
|
||||
|
||||
tabs.createEmptyTab(createAsStartPage, focusOnNewTab, webview)
|
||||
|
||||
if (createAsStartPage && root.thirdpartyServicesEnabled) {
|
||||
webview.url = Constants.browserDefaultHomepage
|
||||
} else if (url !== undefined) {
|
||||
webview.url = url;
|
||||
} else if (!!localAccountSensitiveSettings.browserHomepage) {
|
||||
webview.url = _internal.determineRealURL(localAccountSensitiveSettings.browserHomepage)
|
||||
}
|
||||
|
||||
return webview;
|
||||
}
|
||||
|
||||
function createDownloadTab(profileParams) {
|
||||
var webview = root.webViewAdapterComponent.createObject(webStackView, {
|
||||
profileParams: profileParams,
|
||||
isDownloadView: true
|
||||
})
|
||||
tabs.createDownloadTab()
|
||||
return webview;
|
||||
}
|
||||
|
||||
function getCurrentWebView() { // -> WebEngineView/WebView
|
||||
return getWebView(tabs.currentIndex)
|
||||
}
|
||||
|
||||
function getWebView(index) { // -> WebEngineView/WebView
|
||||
return webStackView.children[index]
|
||||
}
|
||||
|
||||
function removeView(index) {
|
||||
if (tabs.count <= 1) {
|
||||
createEmptyTab(_internal.currentWebView.profileParams, true)
|
||||
}
|
||||
tabs.removeTab(index)
|
||||
var view = getWebView(index)
|
||||
view.stop()
|
||||
view.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay for DownloadView and EmptyWebPage
|
||||
@@ -405,12 +318,13 @@ StatusSectionLayout {
|
||||
id: overlayLoader
|
||||
anchors.fill: parent
|
||||
z: 53
|
||||
|
||||
readonly property bool showDownloadView: _internal.currentWebView?.isDownloadView ?? false
|
||||
readonly property bool showEmptyPage: !showDownloadView && (!_internal.currentWebView?.url?.toString())
|
||||
|
||||
active: showDownloadView || showEmptyPage
|
||||
sourceComponent: showDownloadView ? downloadViewComponent : emptyPageComponent
|
||||
|
||||
readonly property int contentMode: webViewContext.currentContentMode
|
||||
|
||||
active: contentMode !== BrowserWebViewContext.ContentMode.WebContent
|
||||
sourceComponent: contentMode === BrowserWebViewContext.ContentMode.DownloadContent
|
||||
? downloadViewComponent
|
||||
: emptyPageComponent
|
||||
}
|
||||
|
||||
// Non UI component
|
||||
@@ -439,52 +353,13 @@ StatusSectionLayout {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: webEngineAdapterComponent
|
||||
WebEngineAdapter {
|
||||
id: webEngineAdapterItem
|
||||
anchors.fill: parent
|
||||
webChannel: connectorBridge.channel
|
||||
enableJsLogs: root.isDebugEnabled
|
||||
devToolsEnabled: localAccountSensitiveSettings.devToolsEnabled
|
||||
|
||||
onWindowCloseRequested: webStackView.removeView(StackLayout.index)
|
||||
onNewWindowRequested: (makeCurrent, requestedUrl, callback) => {
|
||||
var tab = webStackView.createEmptyTab(_internal.currentWebView.profileParams, false, makeCurrent, requestedUrl);
|
||||
callback(tab);
|
||||
}
|
||||
onDownloadRequested: (download) => {
|
||||
_internal.onDownloadRequested(download)
|
||||
}
|
||||
onCertificateError: (error) => {
|
||||
error.defer()
|
||||
sslDialog.enqueue(error)
|
||||
}
|
||||
onJavaScriptDialogRequested: (request) => {
|
||||
request.accepted = true;
|
||||
var dialog = _internal.jsDialogComponent.createObject(root, {"request": request})
|
||||
dialog.open()
|
||||
}
|
||||
onFindTextFinished: (result) => {
|
||||
if (!findBar.visible)
|
||||
findBar.visible = true
|
||||
findBar.numberOfMatches = result.numberOfMatches;
|
||||
findBar.activeMatch = result.activeMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: downloadViewComponent
|
||||
DownloadView {
|
||||
downloadsModel: root.downloadsStore.downloadModel
|
||||
downloadsMenu: downloadMenuInst
|
||||
onOpenDownloadClicked: function(downloadComplete, index) {
|
||||
if (downloadComplete) {
|
||||
return root.downloadsStore.openFile(index)
|
||||
}
|
||||
root.downloadsStore.openDirectory(index)
|
||||
downloadsContext.openDownloadFromList(downloadComplete, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,11 +371,9 @@ StatusSectionLayout {
|
||||
favMenu: favoriteMenu
|
||||
addFavModal: addFavoriteModal
|
||||
determineRealURLFn: function(url) {
|
||||
return _internal.determineRealURL(url)
|
||||
return root.browserRootStore.determineRealURL(url)
|
||||
}
|
||||
onSetCurrentWebUrl: (url) => {
|
||||
_internal.currentWebView.url = url
|
||||
}
|
||||
onSetCurrentWebUrl: (url) => webViewContext.setCurrentWebUrl(url)
|
||||
Component.onCompleted: {
|
||||
// Add fav button at the end of the grid
|
||||
var index = root.bookmarksStore.getBookmarkIndexByUrl(Constants.newBookmark)
|
||||
@@ -513,6 +386,8 @@ StatusSectionLayout {
|
||||
Component {
|
||||
id: browserWalletMenu
|
||||
BrowserWalletMenu {
|
||||
id: walletMenu
|
||||
|
||||
parent: browserToolbar
|
||||
x: browserToolbar.width - width - Theme.halfPadding
|
||||
y: browserToolbar.height + 4
|
||||
@@ -528,7 +403,7 @@ StatusSectionLayout {
|
||||
onAccountChanged: (newAddress) => connectorBridge.connectorManager.changeAccount(newAddress)
|
||||
onReload: {
|
||||
for (let i = 0; i < tabs.count; ++i){
|
||||
webStackView.getWebView(i).reload();
|
||||
webViewContext.getWebView(i).reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,6 +425,7 @@ StatusSectionLayout {
|
||||
root.browserActivityStore.currentActivityFiltersStore.updateRecipientsModel()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,20 +440,10 @@ StatusSectionLayout {
|
||||
zoomFactor: _internal.currentWebView ? _internal.currentWebView.zoomFactor : 1
|
||||
onAddNewTab: _internal.addNewTab()
|
||||
onAddNewDownloadTab: _internal.addNewDownloadTab()
|
||||
onGoIncognito: function (checked) {
|
||||
if (_internal.currentWebView) {
|
||||
_internal.currentWebView.offTheRecord = checked;
|
||||
}
|
||||
}
|
||||
onZoomIn: {
|
||||
const newZoom = _internal.currentWebView.zoomFactor + 0.1
|
||||
_internal.currentWebView.changeZoomFactor(newZoom)
|
||||
}
|
||||
onZoomOut: {
|
||||
const newZoom = _internal.currentWebView.zoomFactor - 0.1
|
||||
_internal.currentWebView.changeZoomFactor(newZoom)
|
||||
}
|
||||
onResetZoomFactor: _internal.currentWebView.changeZoomFactor(1.0)
|
||||
onGoIncognito: (checked) => webViewContext.setIncognitoCurrent(checked)
|
||||
onZoomIn: webViewContext.changeZoomCurrent(0.1)
|
||||
onZoomOut: webViewContext.changeZoomCurrent(-0.1)
|
||||
onResetZoomFactor: webViewContext.resetZoomCurrent()
|
||||
onLaunchFindBar: {
|
||||
if (!findBar.visible) {
|
||||
findBar.visible = true;
|
||||
@@ -586,18 +452,19 @@ StatusSectionLayout {
|
||||
}
|
||||
onToggleCompatibilityMode: function(checked) {
|
||||
for (let i = 0; i < tabs.count; ++i){
|
||||
webStackView.getWebView(i).stop() // Stop all loading tabs
|
||||
webViewContext.getWebView(i).stop() // Stop all loading tabs
|
||||
}
|
||||
|
||||
localAccountSensitiveSettings.compatibilityMode = checked;
|
||||
|
||||
for (let i = 0; i < tabs.count; ++i){
|
||||
webStackView.getWebView(i).reload() // Reload them with new user agent
|
||||
webViewContext.getWebView(i).reload() // Reload them with new user agent
|
||||
}
|
||||
}
|
||||
onLaunchBrowserSettings: {
|
||||
Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.browserSettings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -649,11 +516,9 @@ StatusSectionLayout {
|
||||
bookmarksStore: root.bookmarksStore
|
||||
onOpenInNewTab: (url) => root.openUrlInNewTab(url)
|
||||
onEditFavoriteTriggered: {
|
||||
Global.openPopup(addFavoriteModal, {
|
||||
modifiyModal: true,
|
||||
ogUrl: favoriteMenu.currentFavorite ? favoriteMenu.currentFavorite.url : _internal.currentWebView.url,
|
||||
ogName: favoriteMenu.currentFavorite ? favoriteMenu.currentFavorite.name : _internal.currentWebView.title})
|
||||
favoritesContext.openAddFavoritePopup(true, favoriteMenu.currentFavorite)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StatusMenu {
|
||||
@@ -680,6 +545,7 @@ StatusSectionLayout {
|
||||
historyMenu.removeItem(object)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -687,23 +553,10 @@ StatusSectionLayout {
|
||||
FavoritesBar {
|
||||
bookmarkModel: root.bookmarksStore.bookmarksModel
|
||||
favoritesMenu: favoriteMenu
|
||||
onSetAsCurrentWebUrl: (url) => {
|
||||
if (!_internal.currentWebView) {
|
||||
console.error("[Browser] currentWebView is null, cannot set URL")
|
||||
return
|
||||
}
|
||||
const newUrl = _internal.determineRealURL(url)
|
||||
Qt.callLater(function() {
|
||||
if (_internal.currentWebView) {
|
||||
_internal.currentWebView.url = newUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
onSetAsCurrentWebUrl: (url) => webViewContext.setCurrentWebUrl(url)
|
||||
onOpenInNewTab: (url) => root.openUrlInNewTab(url)
|
||||
onAddFavModalRequested: {
|
||||
Global.openPopup(addFavoriteModal, {toolbarMode: true,
|
||||
ogUrl: browserHeader.currentFavorite ? browserHeader.currentFavorite.url : _internal.currentWebView.url,
|
||||
ogName: browserHeader.currentFavorite ? browserHeader.currentFavorite.name : _internal.currentWebView.title})
|
||||
favoritesContext.openAddFavoritePopup(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -712,7 +565,8 @@ StatusSectionLayout {
|
||||
id: connectorBridge
|
||||
|
||||
userUID: root.userUID
|
||||
connectorController: root.connectorController
|
||||
featureEnabled: root.dappsEnabled
|
||||
connectorController: root.dappsEnabled ? root.connectorController : null
|
||||
httpUserAgent: {
|
||||
if (localAccountSensitiveSettings.compatibilityMode) {
|
||||
// Google doesn't let you connect if the user agent is Chrome-ish and doesn't satisfy some sort of hidden requirement
|
||||
@@ -739,7 +593,7 @@ StatusSectionLayout {
|
||||
|
||||
BCBrowserDappsProvider {
|
||||
id: browserDappsProvider
|
||||
connectorController: root.connectorController
|
||||
connectorController: root.dappsEnabled ? root.connectorController : null
|
||||
clientId: connectorBridge.clientId
|
||||
clientIdFilter: connectorBridge.clientId
|
||||
}
|
||||
@@ -750,39 +604,13 @@ StatusSectionLayout {
|
||||
downloadsModel: root.downloadsStore.downloadModel
|
||||
downloadsMenu: downloadMenuInst
|
||||
onOpenDownloadClicked: function (downloadComplete, index) {
|
||||
if (downloadComplete) {
|
||||
return root.downloadsStore.openFile(index)
|
||||
}
|
||||
root.downloadsStore.openDirectory(index)
|
||||
downloadsContext.openDownloadFromList(downloadComplete, index)
|
||||
}
|
||||
onAddNewDownloadTab: _internal.addNewDownloadTab()
|
||||
onClose: root.showFooter = false
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: _internal.currentWebView
|
||||
function onUrlChanged() {
|
||||
browserHeader.addressBar.text = root.browserRootStore.obtainAddress(_internal.currentWebView.url)
|
||||
|
||||
// Update ConnectorBridge with current dApp metadata
|
||||
if (_internal.currentWebView && _internal.currentWebView.url) {
|
||||
connectorBridge.connectorManager.updateDAppUrl(
|
||||
_internal.currentWebView.url,
|
||||
_internal.currentWebView.title,
|
||||
_internal.currentWebView.icon
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.bookmarksStore.bookmarksModel
|
||||
function onModelChanged() {
|
||||
browserHeader.currentFavorite = Qt.binding(function () {return root.bookmarksStore.getCurrentFavorite(_internal.currentWebView.url)})
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: typeof browserSection !== "undefined" ? browserSection : null
|
||||
function onOpenUrl(url: string) {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
function getProfile(profileParams) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
+10
-6
@@ -9,6 +9,7 @@ AbstractWebView {
|
||||
|
||||
required property BrowserStores.BookmarksStore bookmarksStore
|
||||
required property BrowserStores.DownloadsStore downloadsStore
|
||||
required property var localAccountSensitiveSettings
|
||||
|
||||
property var findBarComp
|
||||
property var favMenu
|
||||
@@ -52,15 +53,15 @@ AbstractWebView {
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
console.warn("MobileWebViewAdapter: goBack not supported yet")
|
||||
console.warn("WebViewAdapter: goBack not supported yet")
|
||||
}
|
||||
|
||||
function goForward() {
|
||||
console.warn("MobileWebViewAdapter: goForward not supported yet")
|
||||
console.warn("WebViewAdapter: goForward not supported yet")
|
||||
}
|
||||
|
||||
function goBackOrForward(offset) {
|
||||
console.warn("MobileWebViewAdapter: goBackOrForward not supported yet")
|
||||
console.warn("WebViewAdapter: goBackOrForward not supported yet")
|
||||
}
|
||||
|
||||
function reload() {
|
||||
@@ -70,7 +71,7 @@ AbstractWebView {
|
||||
}
|
||||
|
||||
function stop() {
|
||||
console.warn("MobileWebViewAdapter: stop not supported yet")
|
||||
console.warn("WebViewAdapter: stop not supported yet")
|
||||
}
|
||||
|
||||
function findText(text, flags) {
|
||||
@@ -82,7 +83,10 @@ AbstractWebView {
|
||||
}
|
||||
|
||||
function acceptAsNewWindow(request) {
|
||||
console.warn("MobileWebViewAdapter: acceptAsNewWindow not supported")
|
||||
console.warn("WebViewAdapter: acceptAsNewWindow not supported")
|
||||
}
|
||||
|
||||
function detachView() {
|
||||
}
|
||||
|
||||
function triggerWebAction(action) {
|
||||
@@ -100,7 +104,7 @@ AbstractWebView {
|
||||
reload()
|
||||
break
|
||||
default:
|
||||
console.warn("MobileWebViewAdapter: Web action not supported:", action)
|
||||
console.warn("WebViewAdapter: Web action not supported:", action)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,25 @@ Item {
|
||||
RequestClose = 35
|
||||
}
|
||||
|
||||
// === Download States (constants for cross-platform compatibility) ===
|
||||
// These map to WebEngineDownloadRequest.DownloadState enum on desktop
|
||||
enum DownloadState {
|
||||
DownloadRequested = 0,
|
||||
DownloadInProgress = 1,
|
||||
DownloadCompleted = 2,
|
||||
DownloadCancelled = 3,
|
||||
DownloadInterrupted = 4
|
||||
}
|
||||
|
||||
// === JavaScript Dialog Types (constants for cross-platform compatibility) ===
|
||||
// These map to JavaScriptDialogRequest.DialogType enum on desktop
|
||||
enum JavaScriptDialogType {
|
||||
DialogTypeAlert = 0,
|
||||
DialogTypeConfirm = 1,
|
||||
DialogTypePrompt = 2,
|
||||
DialogTypeUnload = 3
|
||||
}
|
||||
|
||||
signal linkHovered(string hoveredUrl)
|
||||
signal windowCloseRequested()
|
||||
signal downloadRequested(var download)
|
||||
@@ -76,6 +95,7 @@ Item {
|
||||
function findText(text, flags) {}
|
||||
function changeZoomFactor(factor) {}
|
||||
function acceptAsNewWindow(request) {}
|
||||
function detachView() {}
|
||||
|
||||
function triggerWebAction(action) { console.warn("AbstractWebView: triggerWebAction not implemented") }
|
||||
}
|
||||
|
||||
+5
-2
@@ -20,11 +20,14 @@ QtObject {
|
||||
sourceUrl: path,
|
||||
injectionPoint: WebEngineScript.DocumentCreation,
|
||||
worldId: WebEngineScript.MainWorld,
|
||||
runsOnSubframes: runOnSubFrames
|
||||
runsOnSubFrames: runOnSubFrames
|
||||
}
|
||||
}
|
||||
|
||||
function _getProfilePrototype(storageName, offTheRecord) {
|
||||
const storageNameProp = offTheRecord
|
||||
? ""
|
||||
: `storageName: "${storageName.replace(/"/g, '\\"')}"`
|
||||
const persistentCookiesPolicy = offTheRecord
|
||||
? "persistentCookiesPolicy: WebEngineProfile.NoPersistentCookies"
|
||||
: ""
|
||||
@@ -32,7 +35,7 @@ QtObject {
|
||||
return Qt.createQmlObject(`
|
||||
import QtWebEngine
|
||||
WebEngineProfilePrototype {
|
||||
storageName: "${storageName.replace(/"/g, '\\"')}"
|
||||
${storageNameProp}
|
||||
${persistentCookiesPolicy}
|
||||
}
|
||||
`, root, "ProfilePrototype_" + storageName)
|
||||
+109
-14
@@ -4,16 +4,18 @@ import QtWebEngine
|
||||
import StatusQ.Core.Theme
|
||||
|
||||
import AppLayouts.Browser.views
|
||||
import AppLayouts.Browser.provider.qml
|
||||
|
||||
AbstractWebView {
|
||||
id: root
|
||||
|
||||
property bool enableJsLogs: false
|
||||
required property var localAccountSensitiveSettings
|
||||
property var bookmarksStore
|
||||
property var downloadsStore
|
||||
|
||||
property var profile: ProfileManager.getProfile(root.profileParams)
|
||||
|
||||
// Expose BrowserWebEngineView properties
|
||||
// Expose internal WebEngineView properties
|
||||
property alias url: webView.url
|
||||
readonly property alias title: webView.title
|
||||
readonly property alias loading: webView.loading
|
||||
@@ -42,6 +44,16 @@ AbstractWebView {
|
||||
function findText(text, flags) { webView.findText(text, flags) }
|
||||
function changeZoomFactor(factor) { webView.changeZoomFactor(factor) }
|
||||
function acceptAsNewWindow(request) { request.openIn(webView) }
|
||||
function detachView() {
|
||||
// Detach internal views from scene graph before destroy.
|
||||
webView.webChannel = null
|
||||
devToolsView.inspectedView = null
|
||||
webView.stop()
|
||||
webView.visible = false
|
||||
webView.parent = null
|
||||
devToolsView.visible = false
|
||||
devToolsView.parent = null
|
||||
}
|
||||
function triggerWebAction(action) {
|
||||
// Map AbstractWebView.WebAction to WebEngineView.WebAction
|
||||
switch (action) {
|
||||
@@ -72,26 +84,95 @@ AbstractWebView {
|
||||
case AbstractWebView.WebAction.PasteAndMatchStyle:
|
||||
webView.triggerWebAction(WebEngineView.PasteAndMatchStyle); break
|
||||
default:
|
||||
console.warn("WebEngineAdapter: Unknown web action:", action)
|
||||
console.warn("WebViewAdapter: Unknown web action:", action)
|
||||
}
|
||||
}
|
||||
|
||||
BrowserWebEngineView {
|
||||
WebEngineView {
|
||||
id: webView
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: root.devToolsEnabled ? devToolsView.top : parent.bottom
|
||||
focus: true
|
||||
|
||||
property bool htmlPageLoaded: false
|
||||
backgroundColor: Theme.palette.background
|
||||
|
||||
settings.autoLoadImages: root.localAccountSensitiveSettings.autoLoadImages
|
||||
settings.javascriptEnabled: root.localAccountSensitiveSettings.javaScriptEnabled
|
||||
settings.errorPageEnabled: root.localAccountSensitiveSettings.errorPageEnabled
|
||||
settings.pluginsEnabled: root.localAccountSensitiveSettings.pluginsEnabled
|
||||
settings.autoLoadIconsForPage: root.localAccountSensitiveSettings.autoLoadIconsForPage
|
||||
settings.touchIconsEnabled: root.localAccountSensitiveSettings.touchIconsEnabled
|
||||
settings.webRTCPublicInterfacesOnly: root.localAccountSensitiveSettings.webRTCPublicInterfacesOnly
|
||||
settings.pdfViewerEnabled: root.localAccountSensitiveSettings.pdfViewerEnabled
|
||||
settings.focusOnNavigationEnabled: true
|
||||
settings.forceDarkMode: Application.styleHints.colorScheme === Qt.ColorScheme.Dark
|
||||
|
||||
webChannel: root.webChannel
|
||||
profile: root.profile
|
||||
enableJsLogs: root.enableJsLogs
|
||||
|
||||
onQuotaRequested: function(request) {
|
||||
if (request.requestedSize <= 5 * 1024 * 1024)
|
||||
request.accept()
|
||||
else
|
||||
request.reject()
|
||||
}
|
||||
onRegisterProtocolHandlerRequested: function(request) {
|
||||
console.log("accepting registerProtocolHandler request for "
|
||||
+ request.scheme + " from " + request.origin)
|
||||
request.accept()
|
||||
}
|
||||
onRenderProcessTerminated: function(terminationStatus, exitCode) {
|
||||
var status = ""
|
||||
switch (terminationStatus) {
|
||||
case WebEngineView.NormalTerminationStatus:
|
||||
status = "(normal exit)"
|
||||
break
|
||||
case WebEngineView.AbnormalTerminationStatus:
|
||||
status = "(abnormal exit)"
|
||||
break
|
||||
case WebEngineView.CrashedTerminationStatus:
|
||||
status = "(crashed)"
|
||||
break
|
||||
case WebEngineView.KilledTerminationStatus:
|
||||
status = "(killed)"
|
||||
break
|
||||
}
|
||||
console.warn("Render process exited with code " + exitCode + " " + status)
|
||||
}
|
||||
onSelectClientCertificate: function(selection) {
|
||||
selection.certificates[0].select()
|
||||
}
|
||||
onLoadingChanged: function(loadRequest) {
|
||||
if (loadRequest.status === WebEngineView.LoadStartedStatus) {
|
||||
webView.htmlPageLoaded = false
|
||||
}
|
||||
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
||||
webView.htmlPageLoaded = true
|
||||
}
|
||||
}
|
||||
onLoadProgressChanged: function(progress) {
|
||||
if (progress >= 10)
|
||||
webView.htmlPageLoaded = true
|
||||
}
|
||||
onNavigationRequested: function(request) {
|
||||
if (request.url.toString().startsWith("file:/")) {
|
||||
console.log("Local file browsing is disabled")
|
||||
request.reject()
|
||||
}
|
||||
}
|
||||
onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceID) {
|
||||
const isOurScript = ScriptUtils.isOurInjectedScript(sourceID, root.profile)
|
||||
if (isOurScript || root.enableJsLogs)
|
||||
console.log("[WebEngine]", sourceID + ":" + lineNumber, message)
|
||||
}
|
||||
onLinkHovered: (hoveredUrl) => root.linkHovered(hoveredUrl)
|
||||
onWindowCloseRequested: root.windowCloseRequested()
|
||||
onNewWindowRequested: (request) => {
|
||||
if (!request.userInitiated) {
|
||||
console.warn("Warning: Blocked a popup window.");
|
||||
console.warn("Warning: Blocked a popup window.")
|
||||
} else {
|
||||
const makeCurrent = request.destination !== WebEngineNewWindowRequest.InNewBackgroundTab
|
||||
root.newWindowRequested(makeCurrent, request.requestedUrl, (tab) => tab.acceptAsNewWindow(request))
|
||||
@@ -100,7 +181,19 @@ AbstractWebView {
|
||||
onCertificateError: (error) => root.certificateError(error)
|
||||
onJavaScriptDialogRequested: (request) => root.javaScriptDialogRequested(request)
|
||||
onFindTextFinished: (result) => root.findTextFinished(result)
|
||||
onShowFindBar: (numberOfMatches, activeMatch) => root.findTextFinished({numberOfMatches, activeMatch})
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.profile
|
||||
function onDownloadRequested(download) {
|
||||
// Profile emits for all tabs sharing it; forward only owner view.
|
||||
if (download?.view && download.view !== webView)
|
||||
return
|
||||
// For viewless downloads, only visible adapter forwards to avoid fan-out.
|
||||
if (!download?.view && !root.visible)
|
||||
return
|
||||
root.downloadRequested(download)
|
||||
}
|
||||
}
|
||||
|
||||
WebEngineView {
|
||||
@@ -118,18 +211,20 @@ AbstractWebView {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.profile
|
||||
function onDownloadRequested(download) {
|
||||
root.downloadRequested(download)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
// This connection is needed because changing profileParams.offTheRecord doesn't trigger the root.profile update
|
||||
target: root.profileParams
|
||||
function onOffTheRecordChanged() {
|
||||
root.profile = ProfileManager.getProfile(root.profileParams)
|
||||
}
|
||||
function onUserAgentChanged() {
|
||||
root.profile = ProfileManager.getProfile(root.profileParams)
|
||||
}
|
||||
function onScriptsChanged() {
|
||||
root.profile = ProfileManager.getProfile(root.profileParams)
|
||||
}
|
||||
function onUserIdChanged() {
|
||||
root.profile = ProfileManager.getProfile(root.profileParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
singleton ProfileManager 1.0 ProfileManager.qml
|
||||
AbstractWebView 1.0 AbstractWebView.qml
|
||||
ProfileParams 1.0 ProfileParams.qml
|
||||
WebEngineAdapter 1.0 WebEngineAdapter.qml
|
||||
MobileWebViewAdapter 1.0 MobileWebViewAdapter.qml
|
||||
WebViewAdapter 1.0 WebViewAdapter.qml
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtWebEngine
|
||||
|
||||
import StatusQ.Core
|
||||
import StatusQ.Core.Theme
|
||||
@@ -8,6 +7,7 @@ import StatusQ.Controls
|
||||
|
||||
import utils
|
||||
|
||||
import AppLayouts.Browser.adapters
|
||||
import AppLayouts.Browser.controls
|
||||
|
||||
Rectangle {
|
||||
@@ -44,8 +44,8 @@ Rectangle {
|
||||
readonly property var downloadItem: downloadsModel.downloads[index]
|
||||
|
||||
isPaused: downloadItem?.isPaused ?? false
|
||||
isCanceled: downloadItem?.state === WebEngineDownloadRequest.DownloadCancelled ?? false
|
||||
downloadComplete: downloadItem?.state === WebEngineDownloadRequest.DownloadCompleted ?? false
|
||||
isCanceled: downloadItem?.state === AbstractWebView.DownloadState.DownloadCancelled ?? false
|
||||
downloadComplete: downloadItem?.state === AbstractWebView.DownloadState.DownloadCompleted ?? false
|
||||
primaryText: downloadItem?.downloadFileName ?? ""
|
||||
downloadText: {
|
||||
if (isCanceled) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import QtQuick
|
||||
import QtWebEngine
|
||||
|
||||
import StatusQ.Core
|
||||
import StatusQ.Core.Theme
|
||||
|
||||
import utils
|
||||
|
||||
import AppLayouts.Browser.adapters
|
||||
import AppLayouts.Browser.controls
|
||||
|
||||
Rectangle {
|
||||
@@ -40,7 +40,7 @@ Rectangle {
|
||||
|
||||
width: parent.width
|
||||
isPaused: downloadItem?.isPaused ?? false
|
||||
isCanceled: downloadItem?.state === WebEngineDownloadRequest.DownloadCancelled ?? false
|
||||
isCanceled: downloadItem?.state === AbstractWebView.DownloadState.DownloadCancelled ?? false
|
||||
primaryText: downloadItem?.downloadFileName ?? ""
|
||||
downloadText: {
|
||||
if (isCanceled) {
|
||||
@@ -52,7 +52,7 @@ Rectangle {
|
||||
return "%1/%2".arg(Qt.locale().formattedDataSize(downloadItem?.receivedBytes ?? 0, 2, Locale.DataSizeTraditionalFormat)) //e.g. 14.4/109 MB
|
||||
.arg(Qt.locale().formattedDataSize(downloadItem?.totalBytes ?? 0, 2, Locale.DataSizeTraditionalFormat))
|
||||
}
|
||||
downloadComplete: downloadItem?.state === WebEngineDownloadRequest.DownloadCompleted ?? false
|
||||
downloadComplete: downloadItem?.state === AbstractWebView.DownloadState.DownloadCompleted ?? false
|
||||
onItemClicked: {
|
||||
openDownloadClicked(downloadComplete, index)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtWebEngine
|
||||
|
||||
import StatusQ.Popups
|
||||
|
||||
import AppLayouts.Browser.adapters
|
||||
import AppLayouts.Browser.stores as BrowserStores
|
||||
|
||||
StatusMenu {
|
||||
@@ -14,8 +14,8 @@ StatusMenu {
|
||||
property int index: -1
|
||||
property var download: root.downloadsStore.getDownload(index)
|
||||
|
||||
readonly property bool downloadCancelled: download?.state === WebEngineDownloadRequest.DownloadCancelled ?? false
|
||||
readonly property bool downloadComplete: download?.state === WebEngineDownloadRequest.DownloadCompleted ?? false
|
||||
readonly property bool downloadCancelled: download?.state === AbstractWebView.DownloadState.DownloadCancelled ?? false
|
||||
readonly property bool downloadComplete: download?.state === AbstractWebView.DownloadState.DownloadCompleted ?? false
|
||||
|
||||
signal cancelClicked()
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtWebEngine
|
||||
|
||||
import StatusQ.Core
|
||||
import StatusQ.Core.Theme
|
||||
@@ -12,6 +11,8 @@ import shared.popups
|
||||
|
||||
import utils
|
||||
|
||||
import AppLayouts.Browser.adapters
|
||||
|
||||
// TODO: replace with StatusDialog
|
||||
ModalPopup {
|
||||
id: root
|
||||
@@ -28,10 +29,10 @@ ModalPopup {
|
||||
Component.onCompleted: {
|
||||
root.title = request.securityOrigin;
|
||||
message.text = request.message;
|
||||
if(request.type === JavaScriptDialogRequest.DialogTypeAlert) {
|
||||
if(request.type === AbstractWebView.JavaScriptDialogType.DialogTypeAlert) {
|
||||
cancelButton.visible = false;
|
||||
}
|
||||
if(request.type === JavaScriptDialogRequest.DialogTypePrompt) {
|
||||
if(request.type === AbstractWebView.JavaScriptDialogType.DialogTypePrompt) {
|
||||
prompt.text = request.defaultText;
|
||||
prompt.visible = true;
|
||||
svMessage.height = 75;
|
||||
|
||||
@@ -18,7 +18,9 @@ const EthereumWrapper = (function() {
|
||||
|
||||
this.listeners = new Map(); // event -> Set<handler>
|
||||
this.nativeEthereum = nativeEthereum;
|
||||
this.requestIdCounter = 1; // async requests
|
||||
// Start from a per-instance random base to avoid requestId collisions
|
||||
// between different frames/tabs that share connector callbacks.
|
||||
this.requestIdCounter = Math.floor(Math.random() * 1000000000) + 1;
|
||||
this.pendingRequests = new Map(); // requestId -> { resolve, reject, timestamp }
|
||||
this.requestTimeout = 600000; // 10min timeout for pending requests. (nim side has it's own timeouts)
|
||||
this.timeoutCheckInterval = 10000;
|
||||
|
||||
@@ -17,6 +17,7 @@ QtObject {
|
||||
id: root
|
||||
|
||||
required property string userUID
|
||||
property bool featureEnabled: true
|
||||
required property var connectorController
|
||||
property string httpUserAgent: "" // Custom user agent for web profiles
|
||||
|
||||
@@ -34,12 +35,12 @@ QtObject {
|
||||
offTheRecord: true
|
||||
}
|
||||
|
||||
readonly property var scriptPaths: [
|
||||
readonly property var scriptPaths: root.featureEnabled ? [
|
||||
{ path: Qt.resolvedUrl("../js/qwebchannel.js"), runOnSubFrames: true },
|
||||
{ path: Qt.resolvedUrl("../js/ethereum_wrapper.js"), runOnSubFrames: true },
|
||||
{ path: Qt.resolvedUrl("../js/eip6963_announcer.js"), runOnSubFrames: false },
|
||||
{ path: Qt.resolvedUrl("../js/ethereum_injector.js"), runOnSubFrames: true }
|
||||
]
|
||||
] : []
|
||||
|
||||
readonly property alias dappUrl: connectorManager.dappUrl
|
||||
readonly property alias dappOrigin: connectorManager.dappOrigin
|
||||
|
||||
@@ -133,12 +133,12 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
function changeAccount(newAccount) {
|
||||
if (connectorController) {
|
||||
connectorController.disconnect(dappOrigin, clientId)
|
||||
connectorController.changeAccount(dappOrigin, clientId, newAccount)
|
||||
}
|
||||
}
|
||||
function changeAccount(newAccount) {
|
||||
if (connectorController) {
|
||||
connectorController.disconnect(dappOrigin, clientId)
|
||||
connectorController.changeAccount(dappOrigin, clientId, newAccount)
|
||||
}
|
||||
}
|
||||
|
||||
function updateDAppUrl(url, name, iconUrl) {
|
||||
if (!url) return
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
singleton ProfileManager 1.0 ProfileManager.qml
|
||||
ConnectorBridge 1.0 ConnectorBridge.qml
|
||||
ConnectorManager 1.0 ConnectorManager.qml
|
||||
Eip1193ProviderAdapter 1.0 Eip1193ProviderAdapter.qml
|
||||
|
||||
@@ -58,6 +58,11 @@ QObject {
|
||||
property var currentWebView
|
||||
property var findBarComponent
|
||||
property var browserHeaderComponent
|
||||
function triggerWebAction(action) {
|
||||
if (!currentWebView)
|
||||
return
|
||||
currentWebView.triggerWebAction(action)
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Ctrl+L", "F6"]
|
||||
@@ -68,16 +73,13 @@ QObject {
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Refresh]
|
||||
onActivated: {
|
||||
if (currentWebView)
|
||||
currentWebView.triggerWebAction(AbstractWebView.WebAction.Reload)
|
||||
triggerWebAction(AbstractWebView.WebAction.Reload)
|
||||
}
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Close]
|
||||
onActivated: {
|
||||
if (currentWebView) {
|
||||
currentWebView.triggerWebAction(AbstractWebView.WebAction.RequestClose)
|
||||
}
|
||||
triggerWebAction(AbstractWebView.WebAction.RequestClose)
|
||||
}
|
||||
}
|
||||
Shortcut {
|
||||
@@ -85,45 +87,44 @@ QObject {
|
||||
onActivated: {
|
||||
if (findBarComponent.visible)
|
||||
findBarComponent.visible = false;
|
||||
if (currentWebView)
|
||||
currentWebView.triggerWebAction(AbstractWebView.WebAction.Stop)
|
||||
triggerWebAction(AbstractWebView.WebAction.Stop)
|
||||
}
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Copy]
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Copy)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.Copy)
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Cut]
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Cut)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.Cut)
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Paste]
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Paste)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.Paste)
|
||||
}
|
||||
Shortcut {
|
||||
sequence: "Shift+"+StandardKey.Paste
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.PasteAndMatchStyle)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.PasteAndMatchStyle)
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.SelectAll]
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.SelectAll)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.SelectAll)
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Undo]
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Undo)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.Undo)
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Redo]
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Redo)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.Redo)
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Back]
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Back)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.Back)
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Forward]
|
||||
onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Forward)
|
||||
onActivated: triggerWebAction(AbstractWebView.WebAction.Forward)
|
||||
}
|
||||
Shortcut {
|
||||
sequences: [StandardKey.FindNext]
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import QtQuick
|
||||
import QtWebEngine
|
||||
|
||||
import StatusQ.Core.Theme
|
||||
|
||||
import "ScriptUtils.js" as ScriptUtils
|
||||
|
||||
WebEngineView {
|
||||
id: root
|
||||
|
||||
property bool enableJsLogs: false
|
||||
property bool htmlPageLoaded: false
|
||||
|
||||
signal showFindBar(int numberOfMatches, int activeMatch)
|
||||
signal resetFindBar()
|
||||
|
||||
focus: true
|
||||
|
||||
function changeZoomFactor(newFactor) {
|
||||
zoomFactor = newFactor
|
||||
}
|
||||
|
||||
backgroundColor: Theme.palette.background
|
||||
|
||||
settings.autoLoadImages: localAccountSensitiveSettings.autoLoadImages
|
||||
settings.javascriptEnabled: localAccountSensitiveSettings.javaScriptEnabled
|
||||
settings.errorPageEnabled: localAccountSensitiveSettings.errorPageEnabled
|
||||
settings.pluginsEnabled: localAccountSensitiveSettings.pluginsEnabled
|
||||
settings.autoLoadIconsForPage: localAccountSensitiveSettings.autoLoadIconsForPage
|
||||
settings.touchIconsEnabled: localAccountSensitiveSettings.touchIconsEnabled
|
||||
settings.webRTCPublicInterfacesOnly: localAccountSensitiveSettings.webRTCPublicInterfacesOnly
|
||||
settings.pdfViewerEnabled: localAccountSensitiveSettings.pdfViewerEnabled
|
||||
settings.focusOnNavigationEnabled: true
|
||||
settings.forceDarkMode: Application.styleHints.colorScheme === Qt.ColorScheme.Dark
|
||||
|
||||
onQuotaRequested: function(request) {
|
||||
if (request.requestedSize <= 5 * 1024 * 1024)
|
||||
request.accept();
|
||||
else
|
||||
request.reject();
|
||||
}
|
||||
|
||||
onRegisterProtocolHandlerRequested: function(request) {
|
||||
console.log("accepting registerProtocolHandler request for "
|
||||
+ request.scheme + " from " + request.origin);
|
||||
request.accept();
|
||||
}
|
||||
|
||||
onRenderProcessTerminated: function(terminationStatus, exitCode) {
|
||||
var status = "";
|
||||
switch (terminationStatus) {
|
||||
case WebEngineView.NormalTerminationStatus:
|
||||
status = "(normal exit)";
|
||||
break;
|
||||
case WebEngineView.AbnormalTerminationStatus:
|
||||
status = "(abnormal exit)";
|
||||
break;
|
||||
case WebEngineView.CrashedTerminationStatus:
|
||||
status = "(crashed)";
|
||||
break;
|
||||
case WebEngineView.KilledTerminationStatus:
|
||||
status = "(killed)";
|
||||
break;
|
||||
}
|
||||
|
||||
console.warn("Render process exited with code " + exitCode + " " + status);
|
||||
}
|
||||
|
||||
onSelectClientCertificate: function(selection) {
|
||||
selection.certificates[0].select();
|
||||
}
|
||||
|
||||
onFindTextFinished: function(result) {
|
||||
root.showFindBar(result.numberOfMatches, result.activeMatch)
|
||||
}
|
||||
|
||||
onLoadingChanged: function(loadRequest) {
|
||||
if (loadRequest.status === WebEngineView.LoadStartedStatus) {
|
||||
root.htmlPageLoaded = false
|
||||
root.resetFindBar()
|
||||
}
|
||||
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
||||
root.htmlPageLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
onLoadProgressChanged: function(progress) {
|
||||
if (progress >= 10) {
|
||||
// Some real content rendered
|
||||
htmlPageLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
onNavigationRequested: function (request) {
|
||||
if(request.url.toString().startsWith("file:/")){
|
||||
console.log("Local file browsing is disabled" )
|
||||
request.reject()
|
||||
}
|
||||
}
|
||||
|
||||
onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceID) {
|
||||
// Check if the message is from our injected scripts
|
||||
const isOurScript = ScriptUtils.isOurInjectedScript(sourceID, root.profile);
|
||||
if (isOurScript || root.enableJsLogs) {
|
||||
console.log("[WebEngine]", sourceID + ":" + lineNumber, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
BrowserWebEngineView 1.0 BrowserWebEngineView.qml
|
||||
WebViewContainer 1.0 WebViewContainer.qml
|
||||
BrowserShortcutActions 1.0 BrowserShortcutActions.qml
|
||||
EmptyWebPage 1.0 EmptyWebPage.qml
|
||||
ScriptUtils 1.0 ScriptUtils.js
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import QtQuick
|
||||
import StatusQ.Core.Utils as SQUtils
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
required property var networksStore
|
||||
required property var browserActivityStore
|
||||
required property var browserWalletStore
|
||||
required property var openPopupFn
|
||||
required property Component jsDialogComponent
|
||||
required property Item dialogParent
|
||||
|
||||
function openHistoryMenu(historyMenu) {
|
||||
historyMenu.open()
|
||||
}
|
||||
|
||||
function openSettingsMenu(settingsMenu) {
|
||||
settingsMenu.open()
|
||||
}
|
||||
|
||||
function openWalletMenu(browserWalletMenu) {
|
||||
// Initialize activity filters before opening popup.
|
||||
const activeChainIds = SQUtils.ModelUtils.modelToFlatArray(
|
||||
networksStore.activeNetworks, "chainId")
|
||||
if (activeChainIds.length > 0) {
|
||||
browserActivityStore.activityController.setFilterChainsJson(
|
||||
JSON.stringify(activeChainIds), true)
|
||||
}
|
||||
|
||||
const currentAddress = browserWalletStore.dappBrowserAccount.address
|
||||
browserActivityStore.activityController.setFilterAddressesJson(
|
||||
JSON.stringify([currentAddress]))
|
||||
openPopupFn(browserWalletMenu)
|
||||
}
|
||||
|
||||
function openJsDialog(request) {
|
||||
request.accepted = true
|
||||
var dialog = jsDialogComponent.createObject(dialogParent, {"request": request})
|
||||
if (dialog)
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
required property var downloadsStore
|
||||
required property var tabsModel
|
||||
required property var getWebViewFn
|
||||
required property var removeViewFn
|
||||
required property var setFooterVisibleFn
|
||||
|
||||
function handleDownloadRequest(download) {
|
||||
if (!download)
|
||||
return
|
||||
|
||||
downloadsStore.addDownload(download)
|
||||
download.accept()
|
||||
setFooterVisibleFn(true)
|
||||
|
||||
// Close tabs that were opened only to trigger a download.
|
||||
if (!download.view)
|
||||
return
|
||||
|
||||
for (var i = 0; i < tabsModel.count; ++i) {
|
||||
var tab = getWebViewFn(i)
|
||||
if (tab === download.view && !tab.htmlPageLoaded && tab.title === "") {
|
||||
removeViewFn(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openDownloadFromList(downloadComplete, index) {
|
||||
if (downloadComplete)
|
||||
return downloadsStore.openFile(index)
|
||||
|
||||
downloadsStore.openDirectory(index)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import QtQuick
|
||||
|
||||
import QtModelsToolkit
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
required property var currentWebView
|
||||
required property var bookmarksStore
|
||||
required property bool shouldShowFavoritesBar
|
||||
required property var openPopupFn
|
||||
required property var addFavoriteModal
|
||||
|
||||
readonly property bool favoritesBarActive: shouldShowFavoritesBar &&
|
||||
bookmarksStore.bookmarksModel.ModelCount.count > 0
|
||||
readonly property var currentUrl: (currentWebView && currentWebView.url)
|
||||
? currentWebView.url
|
||||
: ""
|
||||
readonly property string currentTitle: currentWebView ? currentWebView.title : ""
|
||||
|
||||
readonly property var currentViewBookmarkEntry: ModelEntry {
|
||||
sourceModel: root.bookmarksStore.bookmarksModel
|
||||
key: "url"
|
||||
value: root.currentUrl ? root.currentUrl.toString() : ""
|
||||
}
|
||||
|
||||
readonly property bool currentTabIsBookmark: currentViewBookmarkEntry.available &&
|
||||
!!currentViewBookmarkEntry.item
|
||||
|
||||
function buildAddFavoritePopupParams(modifyModal = false, fallbackItem = null) {
|
||||
var bookmarkItem = currentViewBookmarkEntry.available ? currentViewBookmarkEntry.item : null
|
||||
var sourceItem = fallbackItem || bookmarkItem
|
||||
var sourceUrl = sourceItem ? sourceItem.url : currentUrl
|
||||
var sourceName = sourceItem ? sourceItem.name : currentTitle
|
||||
return {
|
||||
modifiyModal: modifyModal,
|
||||
toolbarMode: true,
|
||||
ogUrl: sourceUrl,
|
||||
ogName: sourceName
|
||||
}
|
||||
}
|
||||
|
||||
function openAddFavoritePopup(modifyModal = false, fallbackItem = null) {
|
||||
openPopupFn(addFavoriteModal, buildAddFavoritePopupParams(modifyModal, fallbackItem))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import utils
|
||||
import StatusQ.Core.Utils as SQUtils
|
||||
|
||||
import AppLayouts.Browser.adapters
|
||||
|
||||
Item {
|
||||
id: root
|
||||
visible: false
|
||||
|
||||
required property bool thirdpartyServicesEnabled
|
||||
required property bool isDebugEnabled
|
||||
required property bool isMobile
|
||||
|
||||
required property var browserSettings
|
||||
required property var webChannel
|
||||
|
||||
required property Item hostStackLayout
|
||||
required property var tabsModel
|
||||
required property ProfileParams defaultProfileParams
|
||||
|
||||
required property var bookmarksStore
|
||||
required property var downloadsStore
|
||||
|
||||
required property var determineRealURLFn
|
||||
required property var downloadRequestHandler
|
||||
required property var sslErrorHandler
|
||||
required property var jsDialogHandler
|
||||
required property var findTextFinishedHandler
|
||||
|
||||
enum ContentMode {
|
||||
WebContent = 0,
|
||||
DownloadContent,
|
||||
EmptyContent
|
||||
}
|
||||
|
||||
readonly property Item currentWebView: tabsModel.currentIndex < tabsModel.count ? getCurrentWebView() : null
|
||||
readonly property int currentContentMode: {
|
||||
if (!currentWebView)
|
||||
return BrowserWebViewContext.ContentMode.EmptyContent
|
||||
if (currentWebView.isDownloadView)
|
||||
return BrowserWebViewContext.ContentMode.DownloadContent
|
||||
if (!currentWebView.url?.toString())
|
||||
return BrowserWebViewContext.ContentMode.EmptyContent
|
||||
return BrowserWebViewContext.ContentMode.WebContent
|
||||
}
|
||||
|
||||
function createEmptyTab(profileParams, createAsStartPage = false, focusOnNewTab = true, url = undefined) {
|
||||
focusOnNewTab = focusOnNewTab && !createAsStartPage
|
||||
|
||||
var webview = webViewAdapterComponent.createObject(hostStackLayout, {
|
||||
profileParams: profileParams,
|
||||
isDownloadView: false
|
||||
})
|
||||
|
||||
tabsModel.createEmptyTab(createAsStartPage, focusOnNewTab, webview)
|
||||
|
||||
if (createAsStartPage && thirdpartyServicesEnabled) {
|
||||
webview.url = Constants.browserDefaultHomepage
|
||||
} else if (url !== undefined) {
|
||||
webview.url = url
|
||||
} else if (!!browserSettings.browserHomepage) {
|
||||
webview.url = determineRealURLFn(browserSettings.browserHomepage)
|
||||
}
|
||||
|
||||
return webview
|
||||
}
|
||||
|
||||
function createDownloadTab(profileParams) {
|
||||
var webview = webViewAdapterComponent.createObject(hostStackLayout, {
|
||||
profileParams: profileParams,
|
||||
isDownloadView: true
|
||||
})
|
||||
tabsModel.createDownloadTab()
|
||||
return webview
|
||||
}
|
||||
|
||||
function getCurrentWebView() { // -> WebEngineView/WebView
|
||||
return getWebView(tabsModel.currentIndex)
|
||||
}
|
||||
|
||||
function getWebView(index) { // -> WebEngineView/WebView
|
||||
return hostStackLayout.children[index]
|
||||
}
|
||||
|
||||
function setCurrentWebUrl(url) {
|
||||
if (!currentWebView) {
|
||||
console.error("[Browser] currentWebView is null, cannot set URL")
|
||||
return
|
||||
}
|
||||
|
||||
const newUrl = determineRealURLFn(url)
|
||||
Qt.callLater(function() {
|
||||
if (currentWebView)
|
||||
currentWebView.url = newUrl
|
||||
})
|
||||
}
|
||||
|
||||
function goBackCurrent() {
|
||||
if (!currentWebView)
|
||||
return
|
||||
currentWebView.goBack()
|
||||
}
|
||||
|
||||
function goForwardCurrent() {
|
||||
if (!currentWebView)
|
||||
return
|
||||
currentWebView.goForward()
|
||||
}
|
||||
|
||||
function reloadCurrent() {
|
||||
if (!currentWebView)
|
||||
return
|
||||
currentWebView.reload()
|
||||
}
|
||||
|
||||
function stopCurrent() {
|
||||
if (!currentWebView)
|
||||
return
|
||||
currentWebView.stop()
|
||||
}
|
||||
|
||||
function findTextCurrent(text, backward = false) {
|
||||
if (!currentWebView || !text)
|
||||
return
|
||||
|
||||
if (backward) {
|
||||
currentWebView.findText(text, currentWebView.findBackward)
|
||||
return
|
||||
}
|
||||
|
||||
currentWebView.findText(text)
|
||||
}
|
||||
|
||||
function setIncognitoCurrent(checked) {
|
||||
if (!currentWebView)
|
||||
return
|
||||
currentWebView.profileParams.offTheRecord = checked
|
||||
}
|
||||
|
||||
function changeZoomCurrent(delta) {
|
||||
if (!currentWebView)
|
||||
return
|
||||
currentWebView.changeZoomFactor(currentWebView.zoomFactor + delta)
|
||||
}
|
||||
|
||||
function resetZoomCurrent() {
|
||||
if (!currentWebView)
|
||||
return
|
||||
currentWebView.changeZoomFactor(1.0)
|
||||
}
|
||||
|
||||
function removeView(index) {
|
||||
if (index < 0 || index >= tabsModel.count)
|
||||
return
|
||||
|
||||
var view = getWebView(index)
|
||||
if (tabsModel.count <= 1) {
|
||||
var fallbackProfileParams = currentWebView ? currentWebView.profileParams : defaultProfileParams
|
||||
createEmptyTab(fallbackProfileParams, true)
|
||||
}
|
||||
tabsModel.removeTab(index)
|
||||
if (!view)
|
||||
return
|
||||
view.visible = false
|
||||
view.enabled = false
|
||||
view.focus = false
|
||||
view.detachView()
|
||||
view.parent = null
|
||||
view.destroy()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: webViewAdapterComponent
|
||||
WebViewAdapter {
|
||||
visible: !SQUtils.Utils.hasPopups(Overlay.overlay.children) || !root.isMobile
|
||||
enabled: visible
|
||||
|
||||
bookmarksStore: root.bookmarksStore
|
||||
downloadsStore: root.downloadsStore
|
||||
webChannel: root.webChannel
|
||||
enableJsLogs: root.isDebugEnabled
|
||||
localAccountSensitiveSettings: root.browserSettings
|
||||
devToolsEnabled: root.browserSettings.devToolsEnabled
|
||||
|
||||
onWindowCloseRequested: root.removeView(StackLayout.index)
|
||||
onNewWindowRequested: (makeCurrent, requestedUrl, callback) => {
|
||||
var profileParams = root.currentWebView ? root.currentWebView.profileParams : root.defaultProfileParams
|
||||
var tab = root.createEmptyTab(profileParams, false, makeCurrent, requestedUrl)
|
||||
callback(tab)
|
||||
}
|
||||
onDownloadRequested: (download) => root.downloadRequestHandler(download)
|
||||
onCertificateError: (error) => root.sslErrorHandler(error)
|
||||
onJavaScriptDialogRequested: (request) => root.jsDialogHandler(request)
|
||||
onFindTextFinished: (result) => root.findTextFinishedHandler(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
BrowserWebViewContext 1.0 BrowserWebViewContext.qml
|
||||
BrowserOverlayContext 1.0 BrowserOverlayContext.qml
|
||||
BrowserFavoritesContext 1.0 BrowserFavoritesContext.qml
|
||||
BrowserDialogsContext 1.0 BrowserDialogsContext.qml
|
||||
BrowserDownloadsContext 1.0 BrowserDownloadsContext.qml
|
||||
@@ -1504,7 +1504,12 @@ Item {
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: sidebar.alwaysVisible ? sidebar.width : undefined
|
||||
readonly property bool offsetBySidebar: sidebar.alwaysVisible
|
||||
|| d.activeSectionType === Constants.appSection.browser
|
||||
readonly property real sidebarOffset: offsetBySidebar
|
||||
? sidebar.width * (sidebar.alwaysVisible ? 1.0 : sidebar.position)
|
||||
: 0
|
||||
anchors.leftMargin: sidebarOffset
|
||||
|
||||
StackLayout {
|
||||
id: appView
|
||||
@@ -1851,9 +1856,7 @@ Item {
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
sourceComponent: (appMain.rootStore.thirdpartyServicesEnabled && !SQUtils.Utils.isMobile)
|
||||
? browserLayout
|
||||
: browserPrivacyWall
|
||||
sourceComponent: appMain.rootStore.thirdpartyServicesEnabled ? browserLayout: browserPrivacyWall
|
||||
|
||||
Component {
|
||||
id: browserPrivacyWall
|
||||
@@ -1873,6 +1876,7 @@ Item {
|
||||
isMobile: SQUtils.Utils.isMobile
|
||||
userUID: appMain.profileStore.pubKey
|
||||
thirdpartyServicesEnabled: appMain.rootStore.thirdpartyServicesEnabled
|
||||
dappsEnabled: featureFlagsStore.dappsEnabled
|
||||
bookmarksStore: BrowserStores.BookmarksStore {}
|
||||
downloadsStore: BrowserStores.DownloadsStore {}
|
||||
browserRootStore: BrowserStores.BrowserRootStore {}
|
||||
@@ -2932,7 +2936,7 @@ Item {
|
||||
accountsModel: WalletStores.RootStore.nonWatchAccounts
|
||||
}
|
||||
bcSdk: DappsConnectorSDK {
|
||||
enabled: featureFlagsStore.connectorEnabled && WalletStores.RootStore.walletSectionInst.walletReady
|
||||
enabled: featureFlagsStore.dappsEnabled && WalletStores.RootStore.walletSectionInst.walletReady
|
||||
excludeClientIds: ["walletconnect"]
|
||||
store: SharedStores.BrowserConnectStore {
|
||||
controller: WalletStores.RootStore.dappsConnectorController
|
||||
|
||||
@@ -109,11 +109,7 @@ Control {
|
||||
: root.Theme.palette.privacyColors.primary
|
||||
readonly property int containerBgRadius: root.Theme.defaultPadding
|
||||
|
||||
readonly property bool hasPopups: root.Overlay.overlay.children.filter(
|
||||
item => {
|
||||
const str = item.toString()
|
||||
return str.includes("QQuickPopupItem") && !str.includes("StatusToolTip")
|
||||
}).length
|
||||
readonly property bool hasPopups: SQUtils.Utils.hasPopups(root.Overlay.overlay.children)
|
||||
|
||||
onHasPopupsChanged: {
|
||||
if (d.hasPopups) {
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import StatusQ.Components
|
||||
import StatusQ.Core
|
||||
import StatusQ.Core.Theme
|
||||
|
||||
import utils
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 8
|
||||
|
||||
required property string dappName
|
||||
required property url dappIcon
|
||||
required property var account
|
||||
|
||||
property string userDisplayNaming
|
||||
|
||||
// Icons
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
StatusRoundedImage {
|
||||
width: height
|
||||
height: parent.height
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.horizontalCenterOffset: -16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
image.source: root.dappIcon
|
||||
}
|
||||
StatusRoundIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.horizontalCenterOffset: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
asset: StatusAssetSettings {
|
||||
width: 24
|
||||
height: 24
|
||||
color: root.Theme.palette.primaryColor1
|
||||
bgWidth: 40
|
||||
bgHeight: 40
|
||||
bgColor: root.Theme.palette.desktopBlue10
|
||||
bgRadius: bgWidth / 2
|
||||
bgBorderWidth: 2
|
||||
bgBorderColor: root.Theme.palette.statusAppLayout.backgroundColor
|
||||
source: Assets.svg("sign")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Names and intentions
|
||||
StatusBaseText {
|
||||
text: qsTr("%1 wants you to %2 with %3").arg(dappName).arg(root.userDisplayNaming).arg(account.name)
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
font.weight: Font.DemiBold
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
// TODO #14762: externalize as a InfoPill and merge base implementation with
|
||||
// the existing IssuePill reusable component
|
||||
Rectangle {
|
||||
Layout.preferredWidth: operationStatusLayout.implicitWidth + 24
|
||||
Layout.preferredHeight: operationStatusLayout.implicitHeight + 14
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
visible: true
|
||||
|
||||
border.color: Theme.palette.successColor2
|
||||
border.width: 1
|
||||
color: "transparent"
|
||||
radius: height / 2
|
||||
|
||||
RowLayout {
|
||||
id: operationStatusLayout
|
||||
|
||||
spacing: 8
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
StatusIcon {
|
||||
Layout.preferredWidth: 16
|
||||
Layout.preferredHeight: 16
|
||||
|
||||
visible: true
|
||||
|
||||
color: Theme.palette.directColor1
|
||||
icon: "info"
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Only sign if you trust the dApp")
|
||||
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import StatusQ.Core
|
||||
import StatusQ.Core.Theme
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property alias maxFeesText: maxFeesDisplay.text
|
||||
property alias feesTextColor: maxFeesDisplay.color
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Max fees:")
|
||||
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
StatusBaseText {
|
||||
id: maxFeesDisplay
|
||||
text: root.maxFeesText
|
||||
|
||||
visible: !!text
|
||||
|
||||
font.pixelSize: Theme.fontSize(16)
|
||||
|
||||
}
|
||||
StatusBaseText {
|
||||
text: qsTr("No fees")
|
||||
|
||||
visible: !maxFeesDisplay.visible
|
||||
|
||||
font.pixelSize: maxFeesDisplay.font.pixelSize
|
||||
font.weight: maxFeesDisplay.font.weight
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1 @@
|
||||
MaxFeesDisplay 1.0 MaxFeesDisplay.qml
|
||||
IntentionPanel 1.0 IntentionPanel.qml
|
||||
ContentPanel 1.0 ContentPanel.qml
|
||||
ContentPanel 1.0 ContentPanel.qml
|
||||
|
||||
Reference in New Issue
Block a user