diff --git a/atomic_defi_design/Dex/Exchange/Trade/FeeIcon.qml b/atomic_defi_design/Dex/Exchange/Trade/FeeIcon.qml index a5f65a9ba9..883e5e13d1 100644 --- a/atomic_defi_design/Dex/Exchange/Trade/FeeIcon.qml +++ b/atomic_defi_design/Dex/Exchange/Trade/FeeIcon.qml @@ -24,12 +24,12 @@ DefaultText { visible: mouse_area.containsMouse contentItem: ColumnLayout { - DefaultText { + DexLabel { id: tx_fee_text text_value: General.txFeeText(trade_info, base, false) font.pixelSize: Style.textSizeSmall4 } - DefaultText { + DexLabel { text_value: General.tradingFeeText(trade_info, base, false) font.pixelSize: tx_fee_text.font.pixelSize } diff --git a/atomic_defi_design/Dex/Sidebar/Center.qml b/atomic_defi_design/Dex/Sidebar/Center.qml index 89ab94df97..a3e2586d90 100644 --- a/atomic_defi_design/Dex/Sidebar/Center.qml +++ b/atomic_defi_design/Dex/Sidebar/Center.qml @@ -15,6 +15,25 @@ MouseArea height: lineHeight * 5 hoverEnabled: true + Connections + { + target: API.app.timesyncCheckerService + + function onTimesyncInfoChanged() + { + if (!API.app.timesyncCheckerService.timesyncInfo) + { + _dexLine.timesyncInfo = false + if (currentLineType === Main.LineType.DEX) currentLineType = Main.LineType.Portfolio + root.lineSelected(Main.LineType.Portfolio); + } + else + { + _dexLine.timesyncInfo = true + } + } + } + Connections { target: parent.parent @@ -84,12 +103,15 @@ MouseArea FigurativeLine { id: _dexLine + property var timesyncInfo: API.app.timesyncCheckerService.timesyncInfo Layout.fillWidth: true type: Main.LineType.DEX + label.color: timesyncInfo ? Dex.CurrentTheme.foregroundColor : Dex.CurrentTheme.textDisabledColor label.text: qsTr("DEX") // isExpanded ? qsTr("DEX") : "" icon.source: General.image_path + "menu-exchange-white.svg" - onClicked: lineSelected(type) + onClicked: timesyncInfo ? lineSelected(type) : null + disabled_tt_text: timesyncInfo ? "" : qsTr("DEX is disabled due to system clock synchronization issues. Please check your device time settings.") } FigurativeLine diff --git a/atomic_defi_design/Dex/Sidebar/FigurativeLine.qml b/atomic_defi_design/Dex/Sidebar/FigurativeLine.qml index 540ca6e11b..a747a36e7f 100644 --- a/atomic_defi_design/Dex/Sidebar/FigurativeLine.qml +++ b/atomic_defi_design/Dex/Sidebar/FigurativeLine.qml @@ -1,12 +1,14 @@ import QtQuick 2.12 import "../Components" +import "../Constants" import Dex.Themes 1.0 as Dex // FigurativeLine acts the same as Line but contains a figurative icon on the left of its label Line { property alias icon: _icon + property string disabled_tt_text: "" DefaultImage { @@ -26,4 +28,13 @@ Line currentLineType === type && type != Main.LineType.Support ? Dex.CurrentTheme.sidebarLineTextSelected : Dex.CurrentTheme.foregroundColor } + + DexTooltip + { + visible: mouseArea.containsMouse && disabled_tt_text + delay: 500 + timeout: 5000 + text: disabled_tt_text + font.pixelSize: Style.textSizeSmall4 + } } diff --git a/src/app/app.cpp b/src/app/app.cpp index 8036d57e47..f2bf63ef11 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -44,6 +44,7 @@ #include "atomicdex/services/price/komodo_prices/komodo.prices.provider.hpp" #include "atomicdex/services/price/coingecko/coingecko.wallet.charts.hpp" #include "atomicdex/services/price/orderbook.scanner.service.hpp" +#include "atomicdex/services/sync/timesync.checker.service.hpp" namespace { @@ -498,6 +499,7 @@ namespace atomic_dex system_manager_.create_system(system_manager_); system_manager_.create_system(); system_manager_.create_system(); + system_manager_.create_system(); system_manager_.create_system(system_manager_); system_manager_.create_system(system_manager_); system_manager_.create_system( @@ -910,7 +912,18 @@ namespace atomic_dex } } // namespace atomic_dex -//! update checker +//! time sync checker +namespace atomic_dex +{ + timesync_checker_service* application::get_timesync_checker_service() const + { + auto ptr = const_cast(std::addressof(system_manager_.get_system())); + assert(ptr != nullptr); + return ptr; + } +} // namespace atomic_dex + +//! zcash_params checker namespace atomic_dex { zcash_params_service* application::get_zcash_params_service() const diff --git a/src/app/app.hpp b/src/app/app.hpp index e6d26b7fd1..6e1e93a9fe 100644 --- a/src/app/app.hpp +++ b/src/app/app.hpp @@ -50,6 +50,7 @@ #include "atomicdex/services/price/global.provider.hpp" #include "atomicdex/services/update/update.checker.service.hpp" #include "atomicdex/services/update/zcash.params.service.hpp" +#include "atomicdex/services/sync/timesync.checker.service.hpp" #include "atomicdex/utilities/qt.utilities.hpp" namespace ag = antara::gaming; @@ -75,6 +76,7 @@ namespace atomic_dex Q_PROPERTY(settings_page* settings_pg READ get_settings_page NOTIFY settingsPageChanged) Q_PROPERTY(qt_wallet_manager* wallet_mgr READ get_wallet_mgr NOTIFY walletMgrChanged) Q_PROPERTY(update_checker_service* updateCheckerService READ get_update_checker_service NOTIFY updateCheckerServiceChanged) + Q_PROPERTY(timesync_checker_service* timesyncCheckerService READ get_timesync_checker_service NOTIFY timesyncCheckerServiceChanged) Q_PROPERTY(zcash_params_service* zcash_params READ get_zcash_params_service NOTIFY zcashParamsServiceChanged) //! Private function @@ -135,6 +137,7 @@ namespace atomic_dex qt_wallet_manager* get_wallet_mgr() const; internet_service_checker* get_internet_checker() const; update_checker_service* get_update_checker_service() const; + timesync_checker_service* get_timesync_checker_service() const; [[nodiscard]] zcash_params_service* get_zcash_params_service() const; exporter_service* get_exporter_service() const; @@ -180,6 +183,7 @@ namespace atomic_dex void walletPageChanged(); void ordersChanged(); void updateCheckerServiceChanged(); + void timesyncCheckerServiceChanged(); void zcashParamsServiceChanged(); void tradingPageChanged(); void settingsPageChanged(); diff --git a/src/core/atomicdex/services/sync/timesync.checker.service.cpp b/src/core/atomicdex/services/sync/timesync.checker.service.cpp new file mode 100644 index 0000000000..2909368a4e --- /dev/null +++ b/src/core/atomicdex/services/sync/timesync.checker.service.cpp @@ -0,0 +1,118 @@ +/****************************************************************************** + * Copyright © 2013-2024 The Komodo Platform Developers. * + * * + * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * + * the top-level directory of this distribution for the individual copyright * + * holder information and the developer policies on copyright and licensing. * + * * + * Unless otherwise agreed in a custom licensing agreement, no part of the * + * Komodo Platform software, including this file may be copied, modified, * + * propagated or distributed except according to the terms contained in the * + * LICENSE file * + * * + * Removal or modification of this copyright notice is prohibited. * + * * + ******************************************************************************/ + +#include +#include "atomicdex/services/sync/timesync.checker.service.hpp" +#include "atomicdex/utilities/cpprestsdk.utilities.hpp" + +namespace +{ + constexpr const char* g_timesync_endpoint = "https://worldtimeapi.org"; + web::http::client::http_client_config g_timesync_cfg{[]() + { + web::http::client::http_client_config cfg; + cfg.set_validate_certificates(false); + cfg.set_timeout(std::chrono::seconds(5)); + return cfg; + }()}; + t_http_client_ptr g_timesync_client = std::make_unique(FROM_STD_STR(g_timesync_endpoint), g_timesync_cfg); + pplx::cancellation_token_source g_synctoken_source; + + pplx::task + async_fetch_timesync() + { + web::http::http_request req; + req.set_method(web::http::methods::GET); + req.set_request_uri(FROM_STD_STR("api/timezone/UTC")); + return g_timesync_client->request(req, g_synctoken_source.get_token()); + } + + bool get_timesync_info_rpc(web::http::http_response resp_http) + { + using namespace std::string_literals; + nlohmann::json resp; + bool sync_ok = false; + std::string resp_str = TO_STD_STR(resp_http.extract_string(true).get()); + if (resp_http.status_code() != 200) + { + SPDLOG_ERROR("Cannot reach the endpoint [{}]: {}", g_timesync_endpoint); + } + else + { + resp = nlohmann::json::parse(resp_str); + int64_t epoch_ts = resp["unixtime"]; + int64_t current_ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + int64_t ts_diff = epoch_ts - current_ts; + if (abs(ts_diff) < 60) + { + sync_ok = true; + } + } + return sync_ok; + } +} // namespace + + +namespace atomic_dex +{ + timesync_checker_service::timesync_checker_service(entt::registry& registry, QObject* parent) : QObject(parent), system(registry) + { + m_timesync_clock = std::chrono::high_resolution_clock::now(); + m_timesync_status = true; + fetch_timesync_status(); + } + + void timesync_checker_service::update() + { + using namespace std::chrono_literals; + + int64_t m_timesync_clock_ts = std::chrono::duration_cast(m_timesync_clock.time_since_epoch()).count(); + int64_t now_ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + int64_t ts_diff = now_ts - m_timesync_clock_ts; + if (abs(ts_diff) >= 60) + { + fetch_timesync_status(); + m_timesync_clock = std::chrono::high_resolution_clock::now(); + } + } + + void timesync_checker_service::fetch_timesync_status() + { + if (is_timesync_fetching) + { + return; + } + is_timesync_fetching = true; + emit isTimesyncFetchingChanged(); + async_fetch_timesync() + .then([this](web::http::http_response resp) { + this->m_timesync_status = get_timesync_info_rpc(resp); + emit timesyncInfoChanged(); + }) + .then(&handle_exception_pplx_task); + is_timesync_fetching = false; + emit isTimesyncFetchingChanged(); + + } + + bool timesync_checker_service::get_timesync_info() const + { + return *m_timesync_status; + } + +} // namespace atomic_dex + + diff --git a/src/core/atomicdex/services/sync/timesync.checker.service.hpp b/src/core/atomicdex/services/sync/timesync.checker.service.hpp new file mode 100644 index 0000000000..54b3f73bf5 --- /dev/null +++ b/src/core/atomicdex/services/sync/timesync.checker.service.hpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * Copyright © 2013-2024 The Komodo Platform Developers. * + * * + * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * + * the top-level directory of this distribution for the individual copyright * + * holder information and the developer policies on copyright and licensing. * + * * + * Unless otherwise agreed in a custom licensing agreement, no part of the * + * Komodo Platform software, including this file may be copied, modified, * + * propagated or distributed except according to the terms contained in the * + * LICENSE file * + * * + * Removal or modification of this copyright notice is prohibited. * + * * + ******************************************************************************/ + +#pragma once + +#include +#include + +#include +#include + +#include + +namespace atomic_dex +{ + class timesync_checker_service final : public QObject, public ag::ecs::pre_update_system + { + Q_OBJECT + + Q_PROPERTY(QVariant timesyncInfo READ get_timesync_info NOTIFY timesyncInfoChanged) + Q_PROPERTY(bool isTimesyncFetching READ get_is_timesync_fetching NOTIFY isTimesyncFetchingChanged) + + using t_timesync_time_point = std::chrono::high_resolution_clock::time_point; + using t_bool_synchronized = boost::synchronized_value; + + t_bool_synchronized m_timesync_status; + t_timesync_time_point m_timesync_clock; + t_bool_synchronized is_timesync_fetching; + + void fetch_timesync_status(); + + public: + explicit timesync_checker_service(entt::registry& registry, QObject* parent = nullptr); + ~timesync_checker_service() final = default; + + void update() final; + + [[nodiscard]] bool get_timesync_info() const; + [[nodiscard]] bool get_is_timesync_fetching() const noexcept { return *is_timesync_fetching; } + + signals: + void timesyncInfoChanged(); + void isTimesyncFetchingChanged(); + }; +} // namespace atomic_dex + +REFL_AUTO(type(atomic_dex::timesync_checker_service))