diff --git a/.github/workflows/atomicdexpro_ci.yml b/.github/workflows/atomicdexpro_ci.yml index 42d06d7880..dae9711343 100644 --- a/.github/workflows/atomicdexpro_ci.yml +++ b/.github/workflows/atomicdexpro_ci.yml @@ -143,8 +143,8 @@ jobs: nimble build -y #./ci_tools_atomic_dex --install_dependencies - ./ci_tools_atomic_dex build debug - ./ci_tools_atomic_dex bundle debug + ./ci_tools_atomic_dex build release + ./ci_tools_atomic_dex bundle release - name: Running Tests (Linux) @@ -161,8 +161,8 @@ jobs: export CXX=clang++-9 export CC=clang-9 echo "Running tests" - ./ci_tools_atomic_dex tests debug - cd build-Debug/bin/AntaraAtomicDexTestsAppDir/usr/bin + ./ci_tools_atomic_dex tests release + cd build-Release/bin/AntaraAtomicDexTestsAppDir/usr/bin cat atomic-dex-tests-result.xml curl https://report.ci/upload.py --output upload.py ls @@ -172,8 +172,8 @@ jobs: - name: Upload bundle artifact (Linux) uses: actions/upload-artifact@v1 with: - name: dexpro-ubuntu-debug.tar.gz - path: ./ci_tools_atomic_dex/bundle-Debug/AntaraAtomicDexAppDir.tar.gz + name: dexpro-ubuntu-release.tar.gz + path: ./ci_tools_atomic_dex/bundle-Release/AntaraAtomicDexAppDir.tar.gz macos-build: @@ -265,8 +265,8 @@ jobs: export QT_ROOT=${{ github.workspace }}/Qt/5.15.0 cd ci_tools_atomic_dex nimble build -y - ./ci_tools_atomic_dex bundle debug --osx_sdk=$HOME/sdk/MacOSX10.13.sdk --compiler=/usr/local/opt/llvm@9/bin/clang++ - ls bundle-Debug/atomicDEX-Pro.dmg + ./ci_tools_atomic_dex bundle release --osx_sdk=$HOME/sdk/MacOSX10.13.sdk --compiler=/usr/local/opt/llvm@9/bin/clang++ + ls bundle-Release/atomicDEX-Pro.dmg - name: Running Tests (MacOS) env: # Or as an environment variable @@ -276,8 +276,8 @@ jobs: export QT_INSTALL_CMAKE_PATH=${{ github.workspace }}/Qt/5.15.0/clang_64/lib/cmake export QT_ROOT=${{ github.workspace }}/Qt/5.15.0 echo "Running tests" - ./ci_tools_atomic_dex tests debug - cd build-Debug/bin/atomic_qt_tests.app/Contents/MacOS + ./ci_tools_atomic_dex tests release + cd build-Release/bin/atomic_qt_tests.app/Contents/MacOS cat atomic-dex-tests-result.xml curl https://report.ci/upload.py --output upload.py ls @@ -288,8 +288,8 @@ jobs: - name: Upload artifacts (MacOS) uses: actions/upload-artifact@v1 with: - name: dexpro-mac-debug.dmg - path: ./ci_tools_atomic_dex/bundle-Debug/atomicDEX-Pro.dmg + name: dexpro-mac-release.dmg + path: ./ci_tools_atomic_dex/bundle-Release/atomicDEX-Pro.dmg windows-build: name: Win Build @@ -347,17 +347,17 @@ jobs: nimble build -y # downloading debug dlls because powershell build doesnt put it - $SHAORIG = "8B06E02CE77C48D6EE1993E2D532FE0B12650C77B24609BF4003ADE97826E8FE" - $DWFILE = ($PWD | select -exp Path) + '\debuglibs-win-dexpro.zip' - (New-Object System.Net.WebClient).DownloadFile('https://github.com/KomodoPlatform/depot/releases/download/0.1/debuglibs-win-atomicdexpro.zip', $DWFILE) - $SHADW = Get-FileHash -Algorithm SHA256 .\debuglibs-win-dexpro.zip | select -exp Hash - if ($SHADW -ne $SHAORIG) {Throw "Wrong hash: $SHADW =! $SHAORIG"} + #$SHAORIG = "8B06E02CE77C48D6EE1993E2D532FE0B12650C77B24609BF4003ADE97826E8FE" + #$DWFILE = ($PWD | select -exp Path) + '\debuglibs-win-dexpro.zip' + #(New-Object System.Net.WebClient).DownloadFile('https://github.com/KomodoPlatform/depot/releases/download/0.1/debuglibs-win-atomicdexpro.zip', $DWFILE) + #$SHADW = Get-FileHash -Algorithm SHA256 .\debuglibs-win-dexpro.zip | select -exp Hash + #if ($SHADW -ne $SHAORIG) {Throw "Wrong hash: $SHADW =! $SHAORIG"} #cmd /c '.\ci_tools_atomic_dex.exe --install_dependencies 2>&1' - cmd /c '.\ci_tools_atomic_dex.exe build debug 2>&1' - 7z e -o'build-Debug\bin\' .\debuglibs-win-dexpro.zip - cmd /c '.\ci_tools_atomic_dex.exe bundle debug 2>&1' - ls bundle-Debug/bundle.zip + cmd /c '.\ci_tools_atomic_dex.exe build release 2>&1' + #7z e -o'build-release\bin\' .\debuglibs-win-dexpro.zip + cmd /c '.\ci_tools_atomic_dex.exe bundle release 2>&1' + ls bundle-Release/bundle.zip - name: Running Tests (Windows) shell: powershell @@ -367,8 +367,8 @@ jobs: run: | echo "Running tests" $Env:QT_INSTALL_CMAKE_PATH = "C:\Qt\5.15.0\msvc2019_64" - cmd /c '.\ci_tools_atomic_dex.exe tests debug 2>&1' - cd build-Debug + cmd /c '.\ci_tools_atomic_dex.exe tests release 2>&1' + cd build-Release cd bin ls Invoke-WebRequest -Uri https://report.ci/upload.py -OutFile upload.py @@ -377,5 +377,5 @@ jobs: - name: Upload artifacts (Windows) uses: actions/upload-artifact@v1 with: - name: dexpro-win-debug.zip - path: ./ci_tools_atomic_dex/bundle-Debug/bundle.zip + name: dexpro-win-release.zip + path: ./ci_tools_atomic_dex/bundle-Release/bundle.zip diff --git a/.github/workflows/linux_response_file.txt b/.github/workflows/linux_response_file.txt index 37e39f3de7..6ad93852a0 100644 --- a/.github/workflows/linux_response_file.txt +++ b/.github/workflows/linux_response_file.txt @@ -2,6 +2,7 @@ entt folly boost-multiprecision boost-random +boost-lockfree doctest fmt curl diff --git a/.github/workflows/osx_response_file.txt b/.github/workflows/osx_response_file.txt index 37e39f3de7..6ad93852a0 100644 --- a/.github/workflows/osx_response_file.txt +++ b/.github/workflows/osx_response_file.txt @@ -2,6 +2,7 @@ entt folly boost-multiprecision boost-random +boost-lockfree doctest fmt curl diff --git a/.github/workflows/windows_response_file.txt b/.github/workflows/windows_response_file.txt index f9e65ed535..b3d65061dd 100644 --- a/.github/workflows/windows_response_file.txt +++ b/.github/workflows/windows_response_file.txt @@ -2,6 +2,7 @@ entt:x64-Windows folly:x64-Windows boost-multiprecision:x64-Windows boost-random:x64-Windows +boost-lockfree:x64-Windows doctest:x64-Windows fmt:x64-Windows curl:x64-Windows diff --git a/CMakeLists.txt b/CMakeLists.txt index 19a7a5c255..ef9b68da33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,10 @@ cmake_minimum_required(VERSION 3.5) -project(atomic_qt LANGUAGES CXX VERSION 0.1.5) +project(atomic_qt LANGUAGES CXX VERSION 0.2.0) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -50,13 +47,13 @@ endif () ##! We fetch our dependence if (APPLE) FetchContent_Declare(mm2 - URL https://github.com/KomodoPlatform/atomicDEX-API/releases/download/beta-2.0.2103/mm2-281d6ec86-Darwin-Release.zip) + URL https://github.com/KomodoPlatform/atomicDEX-API/releases/download/beta-2.0.2165/mm2-e51a128c6-Darwin-Release.zip) elseif (UNIX AND NOT APPLE) FetchContent_Declare(mm2 - URL https://github.com/KomodoPlatform/atomicDEX-API/releases/download/beta-2.0.2103/mm2-281d6ec86-Linux-Release.zip) + URL https://github.com/KomodoPlatform/atomicDEX-API/releases/download/beta-2.0.2165/mm2-e51a128c6-Linux-Release.zip) else () FetchContent_Declare(mm2 - URL https://github.com/KomodoPlatform/atomicDEX-API/releases/download/beta-2.0.2103/mm2-281d6ec86-Windows_NT-Release.zip) + URL https://github.com/KomodoPlatform/atomicDEX-API/releases/download/beta-2.0.2165/mm2-e51a128c6-Windows_NT-Release.zip) endif () FetchContent_Declare(jl777-coins @@ -123,13 +120,16 @@ else () endif () add_library(unofficial-btc::bitcoin ALIAS unofficial-bitcoin) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) set(TS_FILES ${CMAKE_SOURCE_DIR}/atomic_qt_design/assets/languages/atomic_qt_en.ts ${CMAKE_SOURCE_DIR}/atomic_qt_design/assets/languages/atomic_qt_fr.ts ${CMAKE_SOURCE_DIR}/atomic_qt_design/assets/languages/atomic_qt_tr.ts) set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_SOURCE_DIR}/atomic_qt_design/assets/languages/") qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR}/atomic_qt_design/qml ${TS_FILES}) -qt5_add_translation(qmFiles ${TS_FILES}) +#qt5_add_translation(qmFiles ${TS_FILES}) message(STATUS "${QM_FILES}") message(STATUS "${TS_FILES}") @@ -146,8 +146,22 @@ target_sources(atomic_qt_shared_deps INTERFACE ${CMAKE_SOURCE_DIR}/src/atomic.dex.provider.coinpaprika.api.cpp ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.bindings.cpp ${CMAKE_SOURCE_DIR}/src/atomic.dex.provider.coinpaprika.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.provider.cex.prices.api.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.provider.cex.prices.cpp ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.current.coin.infos.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.wallet.manager.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.utilities.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.wallet.config.cpp ${CMAKE_SOURCE_DIR}/src/atomic.dex.security.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.update.service.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.orders.model.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.orders.proxy.model.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.candlestick.charts.model.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.addressbook.model.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.addressbook.proxy.filter.model.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.contact.model.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.portfolio.model.cpp + ${CMAKE_SOURCE_DIR}/src/atomic.dex.qt.portfolio.proxy.filter.model.cpp $<$:${CMAKE_SOURCE_DIR}/src/osx/atomic.dex.osx.manager.mm> ${CMAKE_SOURCE_DIR}/qml.qrc ${qmFiles} @@ -160,6 +174,8 @@ target_compile_definitions(atomic_qt_shared_deps $<$:AUTO_DOWNLOAD> ) +#target_compile_options(atomic_qt_shared_deps INTERFACE -fstandalone-debug) + target_link_libraries(atomic_qt_shared_deps INTERFACE Qt5::Core @@ -180,6 +196,7 @@ target_link_libraries(atomic_qt_shared_deps spdlog::spdlog spdlog::spdlog_header_only ) + target_include_directories(atomic_qt_shared_deps INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/src @@ -196,7 +213,11 @@ add_executable(atomic_qt MACOSX_BUNDLE ${ICON} ##! Testing executable add_executable(atomic_qt_tests MACOSX_BUNDLE ${ICON} src/atomic.dex.tests.cpp - src/atomic.dex.utilities.tests.cpp) + src/atomic.dex.wallet.config.tests.cpp + src/atomic.dex.utilities.tests.cpp + src/atomic.dex.provider.cex.prices.tests.cpp + src/atomic.dex.qt.utilities.tests.cpp + src/atomic.dex.provider.cex.prices.api.tests.cpp) target_link_libraries(atomic_qt PRIVATE @@ -221,6 +242,9 @@ set_target_properties(${PROJECT_NAME}_tests RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/" ) +#target_enable_tsan(atomic_qt_tests) +#target_enable_asan(atomic_qt) + ##! Move assets if (LINUX) get_target_property(exe_runtime_directory_at ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY) diff --git a/assets/config/0.1.5-coins.json b/assets/config/0.2.0-coins.json similarity index 76% rename from assets/config/0.1.5-coins.json rename to assets/config/0.2.0-coins.json index 1fcf9b3121..c27ac34596 100644 --- a/assets/config/0.1.5-coins.json +++ b/assets/config/0.2.0-coins.json @@ -1,4 +1,28 @@ { + "AXE": { + "coin": "AXE", + "name": "Axe", + "coinpaprika_id": "axe-axe", + "coingecko_id": "axe", + "electrum": [ + { + "url": "electrum1.cipig.net:10057" + }, + { + "url": "electrum2.cipig.net:10057" + }, + { + "url": "electrum3.cipig.net:10057" + } + ], + "explorer_url": [ + "https://insight.axerunners.com/" + ], + "type": "UTXO", + "is_erc_20": false, + "active": false, + "currently_enabled": false + }, "BTC": { "coin": "BTC", "name": "Bitcoin", @@ -26,18 +50,18 @@ "BCH": { "active": false, "coin": "BCH", - "coingecko_id": "bitcoin", + "coingecko_id": "bitcoin-cash", "coinpaprika_id": "bch-bitcoin-cash", "currently_enabled": false, "electrum": [ { - "url": "bch.imaginary.cash:50001" + "url": "electrum1.cipig.net:10055" }, { - "url": "electrumx-bch.cryptonermal.net:50001" + "url": "electrum2.cipig.net:10055" }, { - "url": "bch0.kister.net:50001" + "url": "electrum3.cipig.net:10055" } ], "explorer_url": [ @@ -95,6 +119,48 @@ "active": false, "currently_enabled": false }, + "BUSD": { + "coin": "BUSD", + "name": "Binance USD", + "coinpaprika_id": "busd-binance-usd", + "coingecko_id": "binance-usd", + "eth_nodes": [ + "http://eth1.cipig.net:8555", + "http://eth2.cipig.net:8555", + "http://eth3.cipig.net:8555" + ], + "explorer_url": [ + "https://etherscan.io/" + ], + "type": "ERC-20", + "is_erc_20": true, + "active": false, + "currently_enabled": false + }, + "CCL": { + "coin": "CCL", + "name": "CoinCollect", + "coinpaprika_id": "test-coin", + "coingecko_id": "test-coin", + "electrum": [ + { + "url": "electrum1.cipig.net:10029" + }, + { + "url": "electrum2.cipig.net:10029" + }, + { + "url": "electrum3.cipig.net:10029" + } + ], + "explorer_url": [ + "https://ccl.explorer.dexstats.info/" + ], + "type": "Smart Chain", + "is_erc_20": false, + "active": false, + "currently_enabled": false + }, "CRYPTO": { "coin": "CRYPTO", "name": "CRYPTO", @@ -119,6 +185,24 @@ "active": false, "currently_enabled": false }, + "DAI": { + "coin": "DAI", + "name": "Dai", + "coinpaprika_id": "dai-dai", + "coingecko_id": "dai", + "eth_nodes": [ + "http://eth1.cipig.net:8555", + "http://eth2.cipig.net:8555", + "http://eth3.cipig.net:8555" + ], + "explorer_url": [ + "https://etherscan.io/" + ], + "type": "ERC-20", + "is_erc_20": true, + "active": false, + "currently_enabled": false + }, "DASH": { "active": false, "coin": "DASH", @@ -213,6 +297,53 @@ "active": false, "currently_enabled": false }, + "EMC2": { + "coin": "EMC2", + "name": "Einsteinium", + "coinpaprika_id": "emc2-einsteinium", + "coingecko_id": "einsteinium", + "electrum": [ + { + "url": "electrum1.cipig.net:10062" + }, + { + "url": "electrum2.cipig.net:10062" + }, + { + "url": "electrum3.cipig.net:10062" + } + ], + "explorer_url": [ + "https://chainz.cryptoid.info/emc2/" + ], + "type": "UTXO", + "is_erc_20": false, + "active": false, + "currently_enabled": false + }, + "FTC": { + "coin": "FTC", + "name": "Feathercoin", + "coinpaprika_id": "ftc-feathercoin", + "electrum": [ + { + "url": "electrum1.cipig.net:10054" + }, + { + "url": "electrum2.cipig.net:10054" + }, + { + "url": "electrum3.cipig.net:10054" + } + ], + "explorer_url": [ + "http://explorer.feathercoin.com/" + ], + "type": "UTXO", + "is_erc_20": false, + "active": false, + "currently_enabled": false + }, "JUMBLR": { "coin": "JUMBLR", "name": "JUMBLR", @@ -245,13 +376,13 @@ "currently_enabled": false, "electrum": [ { - "url": "electrum.ltc.xurious.com:50001" + "url": "electrum1.cipig.net:10063" }, { - "url": "ltc.rentonisk.com:50001" + "url": "electrum2.cipig.net:10063" }, { - "url": "electrum-ltc.bysh.me:50001" + "url": "electrum3.cipig.net:10063" } ], "explorer_url": [ @@ -285,6 +416,24 @@ "type": "Smart Chain", "name": "MCL" }, + "PAX": { + "coin": "PAX", + "name": "Paxos Standard", + "coinpaprika_id": "pax-paxos-standard-token", + "coingecko_id": "paxos-standard", + "eth_nodes": [ + "http://eth1.cipig.net:8555", + "http://eth2.cipig.net:8555", + "http://eth3.cipig.net:8555" + ], + "explorer_url": [ + "https://etherscan.io/" + ], + "type": "ERC-20", + "is_erc_20": true, + "active": false, + "currently_enabled": false + }, "ETH": { "active": false, "coin": "ETH", @@ -570,6 +719,30 @@ "active": false, "currently_enabled": false }, + "RFOX": { + "coin": "RFOX", + "name": "RedFOX Labs", + "coinpaprika_id": "rfox-redfox-labs", + "coingecko_id": "redfox-labs", + "electrum": [ + { + "url": "electrum1.cipig.net:10034" + }, + { + "url": "electrum2.cipig.net:10034" + }, + { + "url": "electrum3.cipig.net:10034" + } + ], + "explorer_url": [ + "https://rfox.explorer.dexstats.info/" + ], + "type": "Smart Chain", + "is_erc_20": false, + "active": false, + "currently_enabled": false + }, "CHIPS": { "coin": "CHIPS", "type": "UTXO", @@ -710,6 +883,30 @@ "active": false, "currently_enabled": false }, + "OOT": { + "coin": "OOT", + "name": "Utrum", + "coinpaprika_id": "oot-utrum", + "coingecko_id": "utrum", + "electrum": [ + { + "url": "electrum1.cipig.net:10021" + }, + { + "url": "electrum2.cipig.net:10021" + }, + { + "url": "electrum3.cipig.net:10021" + } + ], + "explorer_url": [ + "https://explorer.utrum.io/" + ], + "type": "Smart Chain", + "is_erc_20": false, + "active": false, + "currently_enabled": false + }, "QTUM": { "coin": "QTUM", "name": "Qtum", @@ -801,5 +998,29 @@ ], "is_erc_20": false, "type": "UTXO" + }, + "ZER": { + "coin": "ZER", + "name": "Zero", + "coinpaprika_id": "zer-zero", + "coingecko_id": "zero", + "electrum": [ + { + "url": "electrum1.cipig.net:10065" + }, + { + "url": "electrum2.cipig.net:10065" + }, + { + "url": "electrum3.cipig.net:10065" + } + ], + "explorer_url": [ + "https://insight.zerocurrency.io/insight/" + ], + "type": "UTXO", + "is_erc_20": false, + "active": false, + "currently_enabled": false } } diff --git a/assets/config/cfg.json b/assets/config/cfg.json index 4cee98220a..dd43e26fd6 100644 --- a/assets/config/cfg.json +++ b/assets/config/cfg.json @@ -4,5 +4,16 @@ "en", "fr", "tr" + ], + "current_currency": "USD", + "current_fiat": "USD", + "available_fiat": [ + "USD", + "EUR" + ], + "possible_currencies": [ + "USD", + "BTC", + "KMD" ] } \ No newline at end of file diff --git a/atomic_qt_design/assets/fonts/Montserrat-Light.ttf b/atomic_qt_design/assets/fonts/Montserrat-Light.ttf new file mode 100644 index 0000000000..990857de8e Binary files /dev/null and b/atomic_qt_design/assets/fonts/Montserrat-Light.ttf differ diff --git a/atomic_qt_design/assets/fonts/Montserrat-Medium.ttf b/atomic_qt_design/assets/fonts/Montserrat-Medium.ttf new file mode 100644 index 0000000000..6e079f6984 Binary files /dev/null and b/atomic_qt_design/assets/fonts/Montserrat-Medium.ttf differ diff --git a/atomic_qt_design/assets/fonts/Montserrat-SemiBold.ttf b/atomic_qt_design/assets/fonts/Montserrat-SemiBold.ttf new file mode 100644 index 0000000000..f8a43f2b20 Binary files /dev/null and b/atomic_qt_design/assets/fonts/Montserrat-SemiBold.ttf differ diff --git a/atomic_qt_design/assets/fonts/Montserrat-Thin.ttf b/atomic_qt_design/assets/fonts/Montserrat-Thin.ttf new file mode 100644 index 0000000000..b9858757eb Binary files /dev/null and b/atomic_qt_design/assets/fonts/Montserrat-Thin.ttf differ diff --git a/atomic_qt_design/assets/images/arrow_down.svg b/atomic_qt_design/assets/images/arrow_down.svg new file mode 100644 index 0000000000..7c35183e80 --- /dev/null +++ b/atomic_qt_design/assets/images/arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/atomic_qt_design/assets/images/arrow_up.svg b/atomic_qt_design/assets/images/arrow_up.svg new file mode 100644 index 0000000000..86a0c0025b --- /dev/null +++ b/atomic_qt_design/assets/images/arrow_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/atomic_qt_design/assets/images/atomicdex-logo-dark.svg b/atomic_qt_design/assets/images/atomicdex-logo-dark.svg new file mode 100644 index 0000000000..6922b5fee4 --- /dev/null +++ b/atomic_qt_design/assets/images/atomicdex-logo-dark.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/atomicdex-logo-large.svg b/atomic_qt_design/assets/images/atomicdex-logo-large.svg new file mode 100644 index 0000000000..1409b4772f --- /dev/null +++ b/atomic_qt_design/assets/images/atomicdex-logo-large.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/atomicdex-logo.svg b/atomic_qt_design/assets/images/atomicdex-logo.svg new file mode 100644 index 0000000000..4cb0f8766f --- /dev/null +++ b/atomic_qt_design/assets/images/atomicdex-logo.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/coins/axe.png b/atomic_qt_design/assets/images/coins/axe.png new file mode 100644 index 0000000000..0db893a9c7 Binary files /dev/null and b/atomic_qt_design/assets/images/coins/axe.png differ diff --git a/atomic_qt_design/assets/images/coins/busd.png b/atomic_qt_design/assets/images/coins/busd.png new file mode 100644 index 0000000000..ecd8009aa9 Binary files /dev/null and b/atomic_qt_design/assets/images/coins/busd.png differ diff --git a/atomic_qt_design/assets/images/coins/ccl.png b/atomic_qt_design/assets/images/coins/ccl.png new file mode 100644 index 0000000000..a717809115 Binary files /dev/null and b/atomic_qt_design/assets/images/coins/ccl.png differ diff --git a/atomic_qt_design/assets/images/coins/rfox.png b/atomic_qt_design/assets/images/coins/rfox.png new file mode 100644 index 0000000000..c22fe8c0bf Binary files /dev/null and b/atomic_qt_design/assets/images/coins/rfox.png differ diff --git a/atomic_qt_design/assets/images/coins/zer.png b/atomic_qt_design/assets/images/coins/zer.png new file mode 100644 index 0000000000..848c885401 Binary files /dev/null and b/atomic_qt_design/assets/images/coins/zer.png differ diff --git a/atomic_qt_design/assets/images/exchange-search-old.svg b/atomic_qt_design/assets/images/exchange-search-old.svg deleted file mode 100644 index 517bc41348..0000000000 --- a/atomic_qt_design/assets/images/exchange-search-old.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - search - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/atomic_qt_design/assets/images/exchange-search.svg b/atomic_qt_design/assets/images/exchange-search.svg index f41bbdd43b..ab77b95b02 100644 --- a/atomic_qt_design/assets/images/exchange-search.svg +++ b/atomic_qt_design/assets/images/exchange-search.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/atomic_qt_design/assets/images/menu-assets-portfolio.png b/atomic_qt_design/assets/images/menu-assets-portfolio.png deleted file mode 100644 index 672d550c2c..0000000000 Binary files a/atomic_qt_design/assets/images/menu-assets-portfolio.png and /dev/null differ diff --git a/atomic_qt_design/assets/images/menu-assets-portfolio.svg b/atomic_qt_design/assets/images/menu-assets-portfolio.svg new file mode 100644 index 0000000000..060b1c5f39 --- /dev/null +++ b/atomic_qt_design/assets/images/menu-assets-portfolio.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/menu-assets-white.svg b/atomic_qt_design/assets/images/menu-assets-white.svg index d06f1548a6..aae30dae16 100644 --- a/atomic_qt_design/assets/images/menu-assets-white.svg +++ b/atomic_qt_design/assets/images/menu-assets-white.svg @@ -1,37 +1,17 @@ - - - - assets-white - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/menu-dapp-white.svg b/atomic_qt_design/assets/images/menu-dapp-white.svg index 3474f4a604..8a5cc5c9ab 100644 --- a/atomic_qt_design/assets/images/menu-dapp-white.svg +++ b/atomic_qt_design/assets/images/menu-dapp-white.svg @@ -1,31 +1,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/menu-exchange-white.svg b/atomic_qt_design/assets/images/menu-exchange-white.svg index 883cd96855..3e84cc81c6 100644 --- a/atomic_qt_design/assets/images/menu-exchange-white.svg +++ b/atomic_qt_design/assets/images/menu-exchange-white.svg @@ -1,18 +1,20 @@ - - - - Exchange-white - Created with Sketch. - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/menu-news-white.svg b/atomic_qt_design/assets/images/menu-news-white.svg index 79e760b356..34d844cff0 100644 --- a/atomic_qt_design/assets/images/menu-news-white.svg +++ b/atomic_qt_design/assets/images/menu-news-white.svg @@ -1,19 +1,19 @@ - - - - news-white - Created with Sketch. - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/menu-settings-white.svg b/atomic_qt_design/assets/images/menu-settings-white.svg index 650c7cfcd5..98a833f874 100644 --- a/atomic_qt_design/assets/images/menu-settings-white.svg +++ b/atomic_qt_design/assets/images/menu-settings-white.svg @@ -1,16 +1,17 @@ - - - - settings-white - Created with Sketch. - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/shadowed_circle_blue.svg b/atomic_qt_design/assets/images/shadowed_circle_blue.svg new file mode 100644 index 0000000000..ba6a957a66 --- /dev/null +++ b/atomic_qt_design/assets/images/shadowed_circle_blue.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/shadowed_circle_green.svg b/atomic_qt_design/assets/images/shadowed_circle_green.svg new file mode 100644 index 0000000000..5d5e588663 --- /dev/null +++ b/atomic_qt_design/assets/images/shadowed_circle_green.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/atomic_qt_design/assets/images/trade_icon.svg b/atomic_qt_design/assets/images/trade_icon.svg new file mode 100644 index 0000000000..75bf725842 --- /dev/null +++ b/atomic_qt_design/assets/images/trade_icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/atomic_qt_design/assets/languages/atomic_qt_en.ts b/atomic_qt_design/assets/languages/atomic_qt_en.ts index db5478dfa6..0d854f65ea 100644 --- a/atomic_qt_design/assets/languages/atomic_qt_en.ts +++ b/atomic_qt_design/assets/languages/atomic_qt_en.ts @@ -2,34 +2,69 @@ - App + AddressBook - - gui version + + Back + + + + + Address Book + + + + + New Contact + + + + + Enter the contact name + + + + + Enter the address + + + + + Explorer + + + + + Send ClaimRewardsModal - + + Failed to prepare to claim rewards + + + + Claim your %1 reward? TICKER - + You will receive %1 AMT TICKER - + Cancel - + Confirm @@ -42,22 +77,22 @@ - + This swap request can not be undone and is a final event! - + This transaction can take up to 10 mins - DO NOT close this application! - + Cancel - + Confirm @@ -73,13 +108,23 @@ Dashboard - + News - - DApps + + Dapps + + + + + CEX Data + + + + + Markets data (prices, charts, etc.) marked with the ⓘ icon originates from third party sources. (<a href="https://coinpaprika.com">coinpaprika.com</a>) @@ -91,33 +136,33 @@ - + Are you sure you want to delete %1 wallet? WALLET_NAME - + If so, make sure you record your seed phrase in order to restore your wallet in future. - + Enter the password of your wallet - + Wrong Password - + Cancel - + Delete @@ -135,32 +180,32 @@ - + Select all UTXO coins - + Select all SmartChains - + Select all ERC tokens - + All coins are already enabled! - + Close - + Enable @@ -173,32 +218,32 @@ - + Accept EULA - + Accept Terms and Conditions - + Close - + Cancel - + Confirm - + <h2>This End-User License Agreement ('EULA') is a legal agreement between you and Komodo Platform.</h2> <p>This EULA agreement governs your acquisition and use of our AtomicDEX Pro software ('Software', 'Mobile Application', 'Application' or 'App') directly from Komodo Platform or indirectly through a Komodo Platform authorized entity, reseller or distributor (a 'Distributor').</p> @@ -239,42 +284,42 @@ Exchange - + Trade - + Orders - + History - + Order Matching - + Order Matched - + Swap Ongoing - + Swap Successful - + Swap Failed @@ -282,22 +327,22 @@ FirstLaunch - - Welcome! + + Welcome - + Recover Seed - + New User - + Wallets @@ -318,22 +363,22 @@ InitialLoading - + Loading, please wait - + Initializing MM2 - + Enabling coins - + Complete @@ -341,7 +386,7 @@ Languages - + Language @@ -354,17 +399,96 @@ - - + + Login - + Back + + Main + + + Wallet Balance + + + + + Price + + + + + Change 24h + + + + + Portfolio % + + + + + %1 / %2 Price + TICKER + + + + + Volume 24h + + + + + Address Book + + + + + Send + + + + + Receive + + + + + Swap + + + + + Claim Rewards + + + + + Loading + + + + + Scanning blocks for TX History... + + + + + Syncing TX History... + + + + + No transactions + + + NewUser @@ -378,58 +502,58 @@ - + New User - + Important: Back up your seed phrase before proceeding! - + We recommend storing it offline. - + Generated Seed - + Confirm Seed - + Enter the generated seed here - + Back - - + + Continue - + Let's double check your seed phrase - + Your seed phrase is important - that's why we like to make sure it's correct. We'll ask you three different questions about your seed phrase to make sure you'll be able to easily restore your wallet whenever you want. - + What's the %n. word in your seed phrase? @@ -437,7 +561,7 @@ - + Enter the %n. word @@ -445,7 +569,7 @@ - + Go back and check again @@ -466,32 +590,32 @@ OrderContent - + Swap ID - + UUID - + Maker Order - + Taker Order - + Cancel - + Recover Funds @@ -499,50 +623,75 @@ OrderForm - + Sell - + Receive - - MAX + + Amount - + Amount to sell - + Amount to receive - + Please fill the send amount - + + Min + + + + + Half + + + + + Max + + + + Transaction Fee - + Trading Fee + + + Fees will be calculated + + + + + Trade + + OrderList - + You don't have any orders. @@ -560,62 +709,82 @@ - + Maker Order - + Taker Order - + + Refund State + + + + + Your swap failed but the auto-refund process for your payment started already. Please wait and keep application opened until you receive your payment back + + + + Date - + Swap ID - + UUID - - Taker Payment ID + + Maker Payment Sent ID - - Maker Payment ID + + Maker Payment Spent ID - - Error ID + + Taker Payment Spent ID - - Error Log + + Taker Payment Sent ID - - Close + + Cancel Order - - Cancel + + Error ID + Error Log + + + + + Close + + + + View at Explorer @@ -633,12 +802,12 @@ - + Click to create an order - + Click to see %n order(s) @@ -646,7 +815,7 @@ - + Close @@ -659,27 +828,27 @@ - + Price - + Volume - + Receive - + Close - + Create your own order @@ -687,69 +856,90 @@ Orders - + + Show All Coins + + + + Cancel All Orders - + + Cancel All %1 Orders + TICKER + + + + All %1 Orders TICKER + + + All Orders + + PasswordField - + Password - + Enter a password for your wallet - + Enter the password of your wallet - + At least 1 lowercase alphabetical character - + At least 1 uppercase alphabetical character - + At least 1 numeric character - + At least 1 special character (eg. !@#$%) - + At least 16 characters + + + Password and Confirm Password have to be same + + PasswordForm - + Confirm Password - + Enter the same password to confirm @@ -757,47 +947,47 @@ Portfolio - + TOTAL - + Search - + Coin - + Balance - + Change 24h - + Trend 7d - + Price - + Loading - + Disable %1 TICKER @@ -806,13 +996,34 @@ PriceLine - - Price + + Exchange rate + + + + + Selected - - Selected Price + + Expensive + + + + + Expedient + + + + + %1 compared to CEX + PRICE_DIFF% + + + + + CEXchange rate @@ -842,34 +1053,34 @@ - + Recovery - - + + Seed - - + + Enter the seed - + Allow custom seed - + Back - + Confirm @@ -933,135 +1144,140 @@ SendModal - + Prepare to Send - - + + Recipient's address - + Enter address of the recipient - + + Address Book + + + + The address has to be mixed case. - + Fix - + Amount to send - + Enter the amount to send - + MAX - + Enable Custom Fees - + Only use custom fees if you know what you are doing! - + Custom Fee - + Enter the custom fee - + Gas Limit - + Enter the gas limit - + Gas Price - + Enter the gas price - + Custom Fee can't be higher than the amount - + Not enough funds. - + You have %1 AMT TICKER - + Close - + Prepare - - + + Send - + Amount - + Fees - + Date - + Back @@ -1112,42 +1328,37 @@ Settings - - Settings - - - - + Fiat - + Open Logs Folder - + View Seed - + Disclaimer and ToS - + Delete Wallet - + Log out - + mm2 version @@ -1155,33 +1366,50 @@ Sidebar - - Portfolio + + Disable %1 + TICKER + + + SidebarBottom - - Wallet + + Settings - - DEX + + Privacy + + + SidebarCenter - - News + + Dashboard - - DApps + + Wallet - - Settings + + DEX + + + + + News + + + + + Dapps @@ -1193,40 +1421,54 @@ + + Toast + + + Click here to see the details + + + Trade - + No balance available - + Please enable a coin with balance or deposit funds - - Trade + + Placed the order - - Failed to place the order. + + Failed to place the order - + + Not enough balance for the fees. Need at least %1 more + AMT TICKER + + + + Not enough ETH for the transaction fee - + Sell amount is lower than minimum trade amount - + Receive amount is lower than minimum trade amount @@ -1234,110 +1476,86 @@ TransactionDetailsModal - + Transaction Details - + Amount - + Fees - + Date - - Transaction Hash + + Unconfirmed + Transaction Hash + + + + Confirmations - + Block Height - + From - + To - + Close - + View at Explorer - Wallet - - - Send - - - - - Receive - - - - - Swap - - + Transactions - - Claim Rewards + + Incoming transaction - - No transactions + + Outgoing transaction - - Loading + + transaction fee - - - Syncing %n TX(s)... - - - - - - - Search - - - - - Disable %1 - TICKER + + Unconfirmed diff --git a/atomic_qt_design/assets/languages/atomic_qt_fr.ts b/atomic_qt_design/assets/languages/atomic_qt_fr.ts index ab75a4be01..5ba4de8418 100644 --- a/atomic_qt_design/assets/languages/atomic_qt_fr.ts +++ b/atomic_qt_design/assets/languages/atomic_qt_fr.ts @@ -1,37 +1,79 @@ + + AddressBook + + + Back + Retour + + + + Address Book + + + + + New Contact + + + + + Enter the contact name + + + + + Enter the address + + + + + Explorer + + + + + Send + Envoyez + + App - gui version - + version de la gui ClaimRewardsModal - + + Failed to prepare to claim rewards + + + + Claim your %1 reward? TICKER - Réclamer votre %1 récompense ? + Réclamer votre %1 récompense ? - + You will receive %1 AMT TICKER - Vous allez recevoir %1 + Vous allez recevoir %1 - + Cancel - Annuler + Annuler - + Confirm - Confirmer + Confirmer @@ -39,27 +81,27 @@ Confirm Exchange Details - + Détails de la confirmation de l'échange - + This swap request can not be undone and is a final event! - + La requête de ce swap ne peut pas être annulé, c'est irréversible ! - + This transaction can take up to 10 mins - DO NOT close this application! - + Cette transaction peut prendre jusqu'à 10 mins - NE fermez pas l'application ! - + Cancel - Annuler + Annuler - + Confirm - Confirmer + Confirmer @@ -67,20 +109,34 @@ Copied to Clipboard - + Copier dans le presse-papier Dashboard - + News - Actualités + Actualités + + + + Dapps + + + + + CEX Data + + + + + Markets data (prices, charts, etc.) marked with the ⓘ icon originates from third party sources. (<a href="https://coinpaprika.com">coinpaprika.com</a>) + - DApps - Applications Décentralisée + Applications Décentralisée @@ -88,38 +144,38 @@ Delete Wallet - + Supprimez votre portefeuille - + Are you sure you want to delete %1 wallet? WALLET_NAME - + Êtes-vous sûre de supprimez le portefeuille %1 ? - + If so, make sure you record your seed phrase in order to restore your wallet in future. - + Si c'est le cas, faite en sorte que votre phrase de récupération soit sauvegardée pour pouvoir restaurer votre portefeuille à l'avenir. - + Enter the password of your wallet - + Entrez le mot de passe de votre portefeuille - + Wrong Password - + Mauvais mot de passe - + Cancel - Annuler + Annuler - + Delete - + Supprimez @@ -127,42 +183,42 @@ Enable coins - Activer pièces de monnaie + Activer les pièces Search - Rechercher + Rechercher - + Select all UTXO coins - + Selectionnez toutes les pièces UTXO - + Select all SmartChains - + Selectionnez toutes les SmartChains - + Select all ERC tokens - + Selectionnez tous les jetons ERC - + All coins are already enabled! - Toutes les pièces de monnaie sont déjà activées ! + Toutes les pièces sont déjà activées ! - + Close - Fermer + Fermer - + Enable - Activer + Activer @@ -170,35 +226,35 @@ Disclaimer & Terms of Service - + Clause de non-responsabilité et conditions d'utilisation - + Accept EULA - + Acceptez l'EULA - + Accept Terms and Conditions - + Accepter les termes et conditions - + Close - Fermer + Fermer - + Cancel - Annuler + Annuler - + Confirm - Confirmer + Confirmer - + <h2>This End-User License Agreement ('EULA') is a legal agreement between you and Komodo Platform.</h2> <p>This EULA agreement governs your acquisition and use of our AtomicDEX Pro software ('Software', 'Mobile Application', 'Application' or 'App') directly from Komodo Platform or indirectly through a Komodo Platform authorized entity, reseller or distributor (a 'Distributor').</p> @@ -239,67 +295,71 @@ Exchange - + Trade - Échanger + Échanger - + Orders - Ordres + Ordres - + History - Historique + Historique - + Order Matching - Recherche d'un ordre + Recherche d'un ordre - + Order Matched - Ordre trouvé + Ordre trouvé - + Swap Ongoing - Échange en cours + Échange en cours - + Swap Successful - Échange terminé + Échange terminé - + Swap Failed - Erreur lors de l'échange + Erreur lors de l'échange FirstLaunch - Welcome! - Bienvenue ! + Bienvenue ! - + + Welcome + + + + Recover Seed - Récuperer son Seed + Récuperer son Seed - + New User - Nouvel Utilisateur + Nouvel Utilisateur - + Wallets - Portefeuilles + Portefeuilles @@ -307,43 +367,43 @@ Recent Swaps - Swaps récents + Swaps récents Recover Funds Result - + Le résultat de la récupération des fonds InitialLoading - + Loading, please wait - Chargement en cours, veuillez patienter + Chargement en cours, veuillez patienter - + Initializing MM2 - Initialisation de MM2 + Initialisation de MM2 - + Enabling coins - Activation des pièces de monnaies + Activation des pièces - + Complete - Terminer + Terminer Languages - + Language - + Langue @@ -351,18 +411,97 @@ Failed to login - + Erreur de connection - - + + Login - Connection + Connection - + Back - Retour + Retour + + + + Main + + + Wallet Balance + + + + + Price + Prix + + + + Change 24h + Changement 24H + + + + Portfolio % + + + + + %1 / %2 Price + TICKER + + + + + Volume 24h + + + + + Address Book + + + + + Send + Envoyez + + + + Receive + Recevoir + + + + Swap + Échange + + + + Claim Rewards + Réclamer des récompenses + + + + Loading + Chargement + + + + Scanning blocks for TX History... + + + + + Syncing TX History... + + + + + No transactions + Pas de transactions @@ -370,84 +509,84 @@ Wrong word, please check again - + Mauvais mot, veuillez vérifier à nouveau Failed to create a wallet - + Impossible de créer un portefeuille - + New User - Nouvel utilisateur + Nouvel utilisateur - + Important: Back up your seed phrase before proceeding! - + Important: sauvegardez votre phrase de recupération avant de continuer ! - + We recommend storing it offline. - + Nous vous recommandons de le stocker hors ligne. - + Generated Seed - Générer un Seed + Générer un Seed - + Confirm Seed - Confirmer le Seed + Confirmer la phrase de récupération - + Enter the generated seed here - Veuillez entrez le Seed généré ici + Veuillez entrez la phrase de récupération ici - + Back - Retour + Retour - - + + Continue - + Continuer - + Let's double check your seed phrase - + Vérifions à nouveau votre phrase de récupération - + Your seed phrase is important - that's why we like to make sure it's correct. We'll ask you three different questions about your seed phrase to make sure you'll be able to easily restore your wallet whenever you want. - + Votre phrase de récupération est importante - c'est pourquoi nous aimons nous assurer qu'elle est correcte. Nous vous poserons trois questions différentes au sujet de votre phrase source pour vous assurer que vous pourrez facilement restaurer votre portefeuille à tout moment. - + What's the %n. word in your seed phrase? - - - + + Quel est le mot numéro %n dans votre phrase de récupération ? + Quel est le mot numéro %n dans votre phrase de récupération ? - + Enter the %n. word - - - + + Entrez le mot numéro %n + Entrez le mot numéro %n - + Go back and check again - + Revenez en arrière et vérifiez à nouveau Create @@ -459,88 +598,117 @@ No connection - + Pas de connéction Please make sure you are connected to the internet - + Veuillez vous assurer que vous êtes connecté à Internet OrderContent - + Swap ID - Identifiant du Swap + Identifiant du Swap - + UUID - UUID + UUID - + Maker Order - Ordre du receuveur + Ordre de vente - + Taker Order - Ordre du prenneur + Ordre d'achat - + Cancel - Annuler + Annuler - + Recover Funds - + Récupérer des fonds OrderForm - + Sell - Vendre + Vendre - + Receive - Recevoir + Recevoir - MAX - MAXIMUM + MAX - + + Amount + Montant + + + Amount to sell - Montant à vendre + Montant à vendre - + Amount to receive - Montant à recevoir + Montant à recevoir - + Please fill the send amount - Veuillez remplir le montant de la vente + Veuillez remplir le montant de la vente + + + + Min + - + + Half + + + + + Max + + + + Transaction Fee - Frais de transactions + Frais de transactions - + Trading Fee - Frais d'échanges + Frais d'échanges + + + + Fees will be calculated + + + + + Trade + Échanger @@ -569,9 +737,9 @@ OrderList - + You don't have any orders. - Vous n'avez aucun ordre en cours. + Vous n'avez aucun ordre en cours. @@ -579,72 +747,104 @@ Swap Details - Détails de l'échange + Détails de l'échange Order Details - Détails de l'ordre + Détails de l'ordre - + Maker Order - Ordre du receuveur + Ordre de vente - + Taker Order - Ordre du prenneur + Ordre d'achat - - Date + + Refund State + + + + + Your swap failed but the auto-refund process for your payment started already. Please wait and keep application opened until you receive your payment back - + + Date + Date + + + Swap ID - Identifiant du Swap + Identifiant du Swap - + UUID - UUID + UUID - - Taker Payment ID + + Maker Payment Sent ID - - Maker Payment ID + + Maker Payment Spent ID - - Error ID + + Taker Payment Spent ID - - Error Log + + Taker Payment Sent ID - + + Cancel Order + + + + Taker Payment ID + ID du paiement de l'acheteur + + + Maker Payment ID + ID du paiement du vendeur + + + + Error ID + ID de l'erreur + + + + Error Log + Journal des erreurs + + + Close - Fermer + Fermer - Cancel - Annuler + Annuler - + View at Explorer - + Voir dans l'explorateur @@ -652,30 +852,30 @@ Receive - Recevoir + Recevoir Search - Rechercher + Rechercher - + Click to create an order - + Cliquez pour créer un ordre - + Click to see %n order(s) - - - + + Cliquez pour voir %n ordre (s) + Cliquez pour voir %n ordre (s) - + Close - Fermer + Fermer @@ -683,163 +883,213 @@ Orderbook - + Carnet d'ordres - + Price - + Prix - + Volume - + Volume - + Receive - Recevoir + Recevoir - + Close - Fermer + Fermer - + Create your own order - + Créez votre propre ordre Orders - + + Show All Coins + + + + Cancel All Orders + Annuler tous les ordres + + + + Cancel All %1 Orders + TICKER - + All %1 Orders TICKER + Tous les ordres %1 + + + + All Orders PasswordField - + Password - + Mot de passe - + Enter a password for your wallet - + Entrez un mot de passe pour votre portefeuille - + Enter the password of your wallet - + Entrez un mot de passe pour votre portefeuille - + At least 1 lowercase alphabetical character - + Au moins 1 caractère alphabétique en minuscule - + At least 1 uppercase alphabetical character - + Au moins 1 caractère alphabétique en majuscule - + At least 1 numeric character - + Au moins 1 caractère numérique - + At least 1 special character (eg. !@#$%) - + Au moins 1 caractère spécial (ex: ! @ # $%) - + At least 16 characters + Au moins 16 caractères + + + + Password and Confirm Password have to be same PasswordForm - + Confirm Password - + Confirmez le mot de passe - + Enter the same password to confirm - + Entrez le même mot de passe pour confirmer Portfolio - + TOTAL - + TOTAL - + Search - Rechercher + Rechercher - + Coin - + Pièce - + Balance - + Balance - + Change 24h - + Changement 24H - + Trend 7d - + Tendance 7j - + Price - + Prix - + Loading - + Chargement - + Disable %1 TICKER - + Désactivez %1 PriceLine - Price - + Prix - Selected Price + Prix ​​sélectionné + + + + Exchange rate + + + + + Selected + + + + + Expensive + + + + + Expedient + + + + + %1 compared to CEX + PRICE_DIFF% + + + + + CEXchange rate @@ -848,17 +1098,17 @@ Receive - Recevoir + Recevoir Share this address to receive coins - + Partagez cette adresse pour recevoir des pièces Close - Fermer + Fermer @@ -866,39 +1116,39 @@ Failed to recover the seed - + Impossible de récupérer la phrase de récupération - + Recovery - + Récupération - - + + Seed - + Phrase de récupération - - + + Enter the seed - + Entrez la phrase de récupération - + Allow custom seed - + Autoriser les phrases de récupération personnalisées - + Back - Retour + Retour - + Confirm - Confirmer + Confirmer @@ -906,37 +1156,37 @@ View Seed - + Voir la phrase de récupération Please enter your password to view the seed. - + Veuillez entrer votre mot de passe pour voir la phrase de récupération. Wrong Password - + Mauvais mot de passe Seed - + Phrase de récupération Cancel - Annuler + Annuler Close - Fermer + Fermer View - + Voir @@ -944,153 +1194,158 @@ Cut - + Couper Copy - + Copier Paste - + Coller SendModal - + Prepare to Send - + Préparez-pour l'envoie - - + + Recipient's address - + Adresse du destinataire - + Enter address of the recipient + Entrez l'adresse du destinataire + + + + Address Book - + The address has to be mixed case. - + L'adresse doit être mixte (case). - + Fix - + Réparer - + Amount to send - + Montant à envoyer - + Enter the amount to send - + Entrez le montant à envoyer - + MAX - MAXIMUM + MAX - + Enable Custom Fees - + Activer les frais personnalisés - + Only use custom fees if you know what you are doing! - + N'utilisez des frais personnalisés que si vous savez ce que vous faites ! - + Custom Fee - + Frais personnalisés - + Enter the custom fee - + Entrez les frais personnalisées - + Gas Limit - + Limite de gaz - + Enter the gas limit - + Entrez la limite de gaz - + Gas Price - + Prix ​​du gaz - + Enter the gas price - + Entrez le prix du gaz - + Custom Fee can't be higher than the amount - + Les frais personnalisées ne peuvent pas être supérieurs au montant - + Not enough funds. - + Pas assez de fonds. - + You have %1 AMT TICKER - + Vous avez %1 - + Close - Fermer + Fermer - + Prepare - + Préparer - - + + Send - + Envoyez - + Amount - + Montant - + Fees - + Frais - + Date - + Date - + Back - Retour + Retour @@ -1098,117 +1353,157 @@ Transaction Complete! - + Transaction terminée ! Recipient's address - + Adresse du destinataire Amount - + Montant Fees - + Frais Date - + Date Transaction Hash - + Hachage de la transaction Close - Fermer + Fermer View at Explorer - + Voir dans l'explorateur Settings - Settings - + Paramètres - + Fiat - + Monnaie fiduciaire - + Open Logs Folder - + Ouvrir le répertoire de logs - + View Seed - + Voir la phrase de récupération - + Disclaimer and ToS - + Clause de non-responsabilité et conditions d'utilisation - + Delete Wallet - + Supprimez le portefeuille - + Log out - + Déconnection - + mm2 version - + Version de mm2 Sidebar - Portfolio - + Portfolio - Wallet - + Portefeuille - DEX - + DEX - News - Actualités + Actualités - DApps - Applications Décentralisée + DApps - Settings + Réglages + + + + Disable %1 + TICKER + + + + + SidebarBottom + + + Settings + + + + + Privacy + + + + + SidebarCenter + + + Dashboard + + + + + Wallet + Portefeuille + + + + DEX + DEX + + + + News + Actualités + + + + Dapps @@ -1217,155 +1512,196 @@ You don't have recent orders. + Vous n'avez pas d'ordres récents. + + + + Toast + + + Click here to see the details Trade - + No balance available - + Aucun solde disponible - + Please enable a coin with balance or deposit funds - + Veuillez activer une pièce avec solde ou déposez des fonds - Trade - Échanger + Échanger - Failed to place the order. + Impossible de placer l'ordre. + + + + Placed the order - - Not enough ETH for the transaction fee + + Failed to place the order - - Sell amount is lower than minimum trade amount + + Not enough balance for the fees. Need at least %1 more + AMT TICKER - + + Not enough ETH for the transaction fee + Pas assez d'ETH pour les frais de transaction + + + + Sell amount is lower than minimum trade amount + Le montant de la vente est inférieur au montant minimum de l'échange + + + Receive amount is lower than minimum trade amount - + Le montant reçu est inférieur au montant minimum de l'échange TransactionDetailsModal - + Transaction Details - + Détails de la transaction - + Amount - + Montant - + Fees - + Frais - + Date - + Date - - Transaction Hash + + Unconfirmed + Transaction Hash + Hachage de la transaction + + + Confirmations - + Confirmations - + Block Height - + Hauteur de bloc - + From - + De - + To - + Vers - + Close - Fermer + Fermer - + View at Explorer + Voir dans l'explorateur + + + + Transactions + + + Incoming transaction + + + + + Outgoing transaction + + + + + transaction fee + + + + + Unconfirmed Wallet - Send - + Envoyez - Receive - Recevoir + Recevoir - Swap - + Échange - Claim Rewards - + Réclamer des récompenses - No transactions - + Pas de transactions - Loading - + Chargement - Syncing %n TX(s)... - - - + + Synchronisation de %n TX (s) ... + Synchronisation de %n TX (s) ... - Search - Rechercher + Rechercher - Disable %1 TICKER - + Désactiver %1 @@ -1373,12 +1709,12 @@ Wallet Name - + Nom du portefeuille Enter the name of your wallet here - + Entrez le nom du portefeuille ici @@ -1386,7 +1722,7 @@ AtomicDEX Pro - + AtomicDEX Pro diff --git a/atomic_qt_design/assets/languages/atomic_qt_tr.ts b/atomic_qt_design/assets/languages/atomic_qt_tr.ts index fae0026243..cb97a4d07d 100644 --- a/atomic_qt_design/assets/languages/atomic_qt_tr.ts +++ b/atomic_qt_design/assets/languages/atomic_qt_tr.ts @@ -1,6 +1,44 @@ + + AddressBook + + + Back + Geri + + + + Address Book + + + + + New Contact + + + + + Enter the contact name + + + + + Enter the address + + + + + Explorer + + + + + Send + Gönder + + App @@ -8,32 +46,36 @@ versiyon - gui version - gui versiyonu + gui versiyonu ClaimRewardsModal - + + Failed to prepare to claim rewards + + + + Claim your %1 reward? TICKER %1 ödülünüzü alacak mısınız? - + You will receive %1 AMT TICKER %1 alacaksınız - + Cancel İptal - + Confirm Onayla @@ -46,22 +88,22 @@ Al-Sat Detaylarını Onayla - + This swap request can not be undone and is a final event! Bu takas isteği geri döndürülemez! - + This transaction can take up to 10 mins - DO NOT close this application! Bu işlem 10 dakika kadar sürebilir - Programı KAPATMAYINIZ! - + Cancel İptal - + Confirm Onayla @@ -77,14 +119,28 @@ Dashboard - + News Haberler - + + Dapps + + + + + CEX Data + + + + + Markets data (prices, charts, etc.) marked with the ⓘ icon originates from third party sources. (<a href="https://coinpaprika.com">coinpaprika.com</a>) + + + DApps - DApps + DApps @@ -95,33 +151,33 @@ Cüzdanı Sil - + Are you sure you want to delete %1 wallet? WALLET_NAME %1 cüzdanınızı silmek istediğinizden emin misiniz? - + If so, make sure you record your seed phrase in order to restore your wallet in future. Öyleyse, cüzdanınızı gelecekte kurtarabilmeniz için seed satırınızı kaydettiğinizden emin olunuz. - + Enter the password of your wallet Cüzdanınızın şifresini giriniz - + Wrong Password Yanlış Parola - + Cancel İptal - + Delete Sil @@ -139,32 +195,32 @@ Ara - + Select all UTXO coins Tüm UTXO kriptoparaları seç - + Select all SmartChains Tüm SmartChain'leri seç - + Select all ERC tokens Tüm ERC tokenlarını seç - + All coins are already enabled! Tüm kriptoparalar zaten etkin! - + Close Kapat - + Enable Etkinleştir @@ -177,32 +233,32 @@ Sorumluluk Reddi & Kullanım Şartları - + Accept EULA Son Kullanıcı Lisans Sözleşmesi (EULA) 'ni kabul ediyorum - + Accept Terms and Conditions Şartları ve koşulları kabul ediyorum - + Close Kapat - + Cancel İptal - + Confirm Onayla - + <h2>This End-User License Agreement ('EULA') is a legal agreement between you and Komodo Platform.</h2> <p>This EULA agreement governs your acquisition and use of our AtomicDEX Pro software ('Software', 'Mobile Application', 'Application' or 'App') directly from Komodo Platform or indirectly through a Komodo Platform authorized entity, reseller or distributor (a 'Distributor').</p> @@ -243,42 +299,42 @@ Exchange - + Trade Al-Sat - + Orders Emirler - + History Geçmiş - + Order Matching Emir Eşleşiyor - + Order Matched Emir Eşleşti - + Swap Ongoing Takas Devam Ediyor - + Swap Successful Takas Başarılı - + Swap Failed Takas Başarısız @@ -286,22 +342,26 @@ FirstLaunch - Welcome! - Hoş geldiniz! + Hoş geldiniz! + + + + Welcome + - + Recover Seed Seed Kurtar - + New User Yeni Kullanıcı - + Wallets Cüzdanlar @@ -322,22 +382,22 @@ InitialLoading - + Loading, please wait Yükleniyor, lütfen bekleyiniz - + Initializing MM2 MM2 başlatılıyor - + Enabling coins Kriptoparalar etkinleştiriliyor - + Complete Tamamlandı @@ -345,7 +405,7 @@ Languages - + Language Dil @@ -358,17 +418,96 @@ Giriş yapılamadı - - + + Login Giriş - + Back Geri + + Main + + + Wallet Balance + + + + + Price + Fiyat + + + + Change 24h + Değişim 24sa + + + + Portfolio % + + + + + %1 / %2 Price + TICKER + + + + + Volume 24h + + + + + Address Book + + + + + Send + Gönder + + + + Receive + + + + + Swap + Takasla + + + + Claim Rewards + Ödül Al + + + + Loading + Yükleniyor + + + + Scanning blocks for TX History... + + + + + Syncing TX History... + + + + + No transactions + İşlem yok + + NewUser @@ -382,72 +521,72 @@ Cüzdan oluşturulamadı - + New User Yeni Kullanıcı - + Important: Back up your seed phrase before proceeding! Önemli: Devam etmeden önce seed kelimelerinizi yedekleyin! - + We recommend storing it offline. Çevrimdışı saklamanızı öneririz. - + Generated Seed Seed Oluştur - + Confirm Seed Seed'i Onayla - + Enter the generated seed here Oluşturulmuş Seed'i buraya giriniz - + Back Geri - - + + Continue Devam - + Let's double check your seed phrase Seed kelimelerinizi tekrar kontrol edelim - + Your seed phrase is important - that's why we like to make sure it's correct. We'll ask you three different questions about your seed phrase to make sure you'll be able to easily restore your wallet whenever you want. Seed kelimeleriniz önemlidir - bu yüzden doğru olduğundan emin olmak istiyoruz. Cüzdanınızı istediğiniz zaman kolayca kurtarabileceğinizden emin olmak için seed kelimeleriniz hakkında üç farklı soru soracağız. - + What's the %n. word in your seed phrase? Seed kelimelerinizden %n. kelime nedir? - + Enter the %n. word %n. kelimeyi giriniz - + Go back and check again Geri dönüp tekrar kontrol et @@ -472,32 +611,32 @@ OrderContent - + Swap ID Takas ID - + UUID UUID - + Maker Order Yapıcı Emir - + Taker Order Alıcı Emir - + Cancel İptal - + Recover Funds @@ -505,45 +644,74 @@ OrderForm - + Sell Satılacak - + Receive Alınacak - MAX - MAKS + MAKS + + + + Amount + Miktar - + Amount to sell Satılacak miktar - + Amount to receive Alınacak miktar - + Please fill the send amount Lütfen gönderilecek miktarı giriniz - + + Min + + + + + Half + + + + + Max + + + + Transaction Fee İşlem Ücreti - + Trading Fee Al-Sat Ücreti + + + Fees will be calculated + + + + + Trade + Al-Sat + OrderLine @@ -571,7 +739,7 @@ OrderList - + You don't have any orders. Hiç emriniz yok. @@ -589,62 +757,94 @@ Emir Detayları - + Maker Order Satıcı Emri - + Taker Order Alıcı Emri - + + Refund State + + + + + Your swap failed but the auto-refund process for your payment started already. Please wait and keep application opened until you receive your payment back + + + + Date Tarih - + Swap ID Takas ID - + UUID UUID - + + Maker Payment Sent ID + + + + + Maker Payment Spent ID + + + + + Taker Payment Spent ID + + + + + Taker Payment Sent ID + + + + + Cancel Order + + + Taker Payment ID - Alıcı Ödeme ID + Alıcı Ödeme ID - Maker Payment ID - Alıcı Ödeme ID + Alıcı Ödeme ID - + Error ID Hata ID - + Error Log Hata Kaydı - + Close Kapat - Cancel - İptal + İptal - + View at Explorer Explorer'da Görüntüle @@ -662,19 +862,19 @@ Ara - + Click to create an order Emir oluşturmak için tıkla - + Click to see %n order(s) %n emri görüntüle - + Close Kapat @@ -687,27 +887,27 @@ Emir Defteri - + Price Fiyat - + Volume Hacim - + Receive Alınacak - + Close Kapat - + Create your own order Kendi emrini oluştur @@ -715,69 +915,90 @@ Orders - + + Show All Coins + + + + Cancel All Orders Tüm Emirleri İptal Et - + + Cancel All %1 Orders + TICKER + + + + All %1 Orders TICKER Tüm %1 Emirleri + + + All Orders + + PasswordField - + Password Parola - + Enter a password for your wallet Cüzdanınız için bir parola giriniz - + Enter the password of your wallet Cüzdanınızın parolasını giriniz - + At least 1 lowercase alphabetical character En az 1 küçük harf - + At least 1 uppercase alphabetical character En az 1 büyük harf - + At least 1 numeric character En az 1 sayı - + At least 1 special character (eg. !@#$%) En az 1 özel karakter - + At least 16 characters En az 16 karakter uzunluğu + + + Password and Confirm Password have to be same + + PasswordForm - + Confirm Password Parola Doğrulaması - + Enter the same password to confirm Doğrulamak için aynı parolayı giriniz @@ -785,47 +1006,47 @@ Portfolio - + TOTAL TOPLAM - + Search Ara - + Coin Kriptopara - + Balance Bakiye - + Change 24h Değişim 24sa - + Trend 7d Trend 7g - + Price Fiyat - + Loading Yükleniyor - + Disable %1 TICKER %1'i Etkinsizleştir @@ -834,14 +1055,43 @@ PriceLine - Price - Fiyat + Fiyat - Selected Price - Seçilen Fiyat + Seçilen Fiyat + + + + Exchange rate + + + + + Selected + + + + + Expensive + + + + + Expedient + + + + + %1 compared to CEX + PRICE_DIFF% + + + + + CEXchange rate + @@ -870,34 +1120,34 @@ Seed kurtarılamadı - + Recovery Kurtarma - - + + Seed Seed - - + + Enter the seed Seed'i giriniz - + Allow custom seed - + Back Geri - + Confirm Onayla @@ -961,135 +1211,140 @@ SendModal - + Prepare to Send Gönderi Hazırlığı - - + + Recipient's address Alıcı adresi - + Enter address of the recipient Alıcının adresini giriniz - + + Address Book + + + + The address has to be mixed case. - + Fix - + Amount to send Gönderilecek miktar - + Enter the amount to send Gönderilecek miktarı giriniz - + MAX MAKS - + Enable Custom Fees Özel Ücretleri Etkinleştir - + Only use custom fees if you know what you are doing! Özel ücretler hakkında bilginiz yoksa kullanmayınız! - + Custom Fee Özel Ücret - + Enter the custom fee Özel ücreti giriniz - + Gas Limit Gas Limiti - + Enter the gas limit Gas limitini giriniz - + Gas Price Gas Fiyatı - + Enter the gas price Gas fiyatını giriniz - + Custom Fee can't be higher than the amount Özel Ücret miktardan daha yüksek olamaz - + Not enough funds. Yetersiz bakiye. - + You have %1 AMT TICKER %1'niz var - + Close Kapat - + Prepare Hazırla - - + + Send Gönder - + Amount Miktar - + Fees Ücret - + Date Tarih - + Back Geri @@ -1140,32 +1395,31 @@ Settings - Settings - Ayarlar + Ayarlar - + Fiat Döviz - + Open Logs Folder Log Klasörünü Aç - + View Seed Seed'i Gör - + Disclaimer and ToS Sorumluluk Reddi ve K.Ş. - + mm2 version mm2 versiyonu @@ -1174,12 +1428,12 @@ Dil - + Delete Wallet Cüzdanı Sil - + Log out Çıkış @@ -1187,34 +1441,75 @@ Sidebar - Portfolio - Portföy + Portföy - Wallet - Cüzdan + Cüzdan - DEX - DEX + DEX - News - Haberler + Haberler - DApps - DApps + DApps - Settings - Ayarlar + Ayarlar + + + + Disable %1 + TICKER + %1'i Etkinsizleştir + + + + SidebarBottom + + + Settings + Ayarlar + + + + Privacy + + + + + SidebarCenter + + + Dashboard + + + + + Wallet + Cüzdan + + + + DEX + DEX + + + + News + Haberler + + + + Dapps + @@ -1225,35 +1520,58 @@ Yakın zamanda bir emriniz yok. + + Toast + + + Click here to see the details + + + Trade - + No balance available Bakiye yok - + Please enable a coin with balance or deposit funds Lütfen bakiyeniz bulunan bir kriptopara etkinleştirin ya da mevcut bakiyenizi doldurun - Trade - Al-Sat + Al-Sat + + + + Placed the order + - + + Failed to place the order + + + + + Not enough balance for the fees. Need at least %1 more + AMT TICKER + + + + Not enough ETH for the transaction fee - + Sell amount is lower than minimum trade amount - + Receive amount is lower than minimum trade amount @@ -1266,118 +1584,136 @@ Seçilen Fiyat - Failed to place the order. - Emir verme başarısız oldu. + Emir verme başarısız oldu. TransactionDetailsModal - + Transaction Details İşlem Detayları - + Amount Miktar - + Fees Ücret - + Date Tarih - + + Unconfirmed + + + + Transaction Hash İşlem Hash'i - + Confirmations Onay Sayısı - + Block Height Blok Uzunluğu - + From Gönderen - + To Alan - + Close Kapat - + View at Explorer Explorer'da Görüntüle + + Transactions + + + Incoming transaction + + + + + Outgoing transaction + + + + + transaction fee + + + + + Unconfirmed + + + Wallet - Send - Gönder + Gönder - Receive - Al + Al - Swap - Takasla + Takasla - Claim Rewards - Ödül Al + Ödül Al - No transactions - İşlem yok + İşlem yok - Loading - Yükleniyor + Yükleniyor - Syncing %n TX(s)... - + %n işlem senkronize ediliyor... - Search - Ara + Ara - Disable %1 TICKER - %1'i Etkinsizleştir + %1'i Etkinsizleştir diff --git a/atomic_qt_design/qml/App.qml b/atomic_qt_design/qml/App.qml index 23f96f9690..1cd8999a1c 100644 --- a/atomic_qt_design/qml/App.qml +++ b/atomic_qt_design/qml/App.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "Screens" import "Constants" import "Components" @@ -98,16 +98,6 @@ Rectangle { } } - DefaultText { - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.bottomMargin: 10 - anchors.rightMargin: anchors.bottomMargin - text: API.get().empty_string + (qsTr("gui version") + ": " + API.get().get_version()) - font.pixelSize: Style.textSizeSmall - } - - // Error Modal LogModal { id: error_log_modal @@ -124,6 +114,16 @@ Rectangle { ToastManager { id: toast } + + // Update Modal + UpdateModal { + id: update_modal + } + + UpdateNotificationLine { + anchors.top: parent.top + anchors.right: parent.right + } } diff --git a/atomic_qt_design/qml/Components/AddressField.qml b/atomic_qt_design/qml/Components/AddressField.qml index e23fdab584..be7b279572 100644 --- a/atomic_qt_design/qml/Components/AddressField.qml +++ b/atomic_qt_design/qml/Components/AddressField.qml @@ -1,15 +1,20 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 -TextFieldWithTitle { - field.validator: RegExpValidator { - regExp: /[a-zA-Z0-9 \t]{25,100}/ + +DefaultTextField { + readonly property int max_length: 50 + + validator: RegExpValidator { + regExp: /[a-zA-Z0-9 \t]{25,50}/ } - field.onTextChanged: { - if(field.text.indexOf(' ') !== -1 || field.text.indexOf('\t') !== -1) { - field.text = field.text.replace(/[ \t]/, '') + onTextChanged: { + if(text.indexOf(' ') !== -1 || text.indexOf('\t') !== -1) { + text = text.replace(/[ \t]/, '') + } + if(text.length > max_length) { + text = text.substring(0, max_length) } } } diff --git a/atomic_qt_design/qml/Components/AddressFieldWithTitle.qml b/atomic_qt_design/qml/Components/AddressFieldWithTitle.qml new file mode 100644 index 0000000000..6954b27c43 --- /dev/null +++ b/atomic_qt_design/qml/Components/AddressFieldWithTitle.qml @@ -0,0 +1,21 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + + +TextFieldWithTitle { + readonly property int max_length: 50 + + field.validator: RegExpValidator { + regExp: /[a-zA-Z0-9 \t]{25,50}/ + } + field.onTextChanged: { + if(field.text.indexOf(' ') !== -1 || field.text.indexOf('\t') !== -1) { + field.text = field.text.replace(/[ \t]/, '') + } + if(field.text.length > max_length) { + console.log("too long! ", field.text.length) + field.text = field.text.substring(0, max_length) + } + } +} diff --git a/atomic_qt_design/qml/Components/AmountField.qml b/atomic_qt_design/qml/Components/AmountField.qml index 987815bb0c..03209ac46e 100644 --- a/atomic_qt_design/qml/Components/AmountField.qml +++ b/atomic_qt_design/qml/Components/AmountField.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + TextFieldWithTitle { field.validator: RegExpValidator { diff --git a/atomic_qt_design/qml/Components/AmountIntField.qml b/atomic_qt_design/qml/Components/AmountIntField.qml index a2b6b34268..f10319409f 100644 --- a/atomic_qt_design/qml/Components/AmountIntField.qml +++ b/atomic_qt_design/qml/Components/AmountIntField.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + TextFieldWithTitle { field.validator: RegExpValidator { diff --git a/atomic_qt_design/qml/Components/Arrow.qml b/atomic_qt_design/qml/Components/Arrow.qml new file mode 100644 index 0000000000..d0a579185f --- /dev/null +++ b/atomic_qt_design/qml/Components/Arrow.qml @@ -0,0 +1,33 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtGraphicalEffects 1.0 +import "../Constants" + +Item { + property bool up: true + property alias color: img_overlay.color + + width: img.width + height: img.height + + DefaultImage { + id: img + + source: General.image_path + "arrow_" + (up ? "up" : "down") + ".svg" + + width: 10; + fillMode: Image.PreserveAspectFit + + visible: false + } + + ColorOverlay { + id: img_overlay + + anchors.fill: img + source: img + color: Style.colorWhite1 + } +} + diff --git a/atomic_qt_design/qml/Components/CexInfoTrigger.qml b/atomic_qt_design/qml/Components/CexInfoTrigger.qml new file mode 100644 index 0000000000..e630eb7cbc --- /dev/null +++ b/atomic_qt_design/qml/Components/CexInfoTrigger.qml @@ -0,0 +1,10 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import "../Constants" + +MouseArea { + anchors.fill: parent + onClicked: cex_rates_modal.open() +} diff --git a/atomic_qt_design/qml/Components/Circle.qml b/atomic_qt_design/qml/Components/Circle.qml new file mode 100644 index 0000000000..af0836004d --- /dev/null +++ b/atomic_qt_design/qml/Components/Circle.qml @@ -0,0 +1,15 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtGraphicalEffects 1.0 +import "../Constants" + +Rectangle { + id: circle + + width: 10 + height: width + + color: Style.colorWhite1 + radius: 100 +} diff --git a/atomic_qt_design/qml/Components/ColumnHeader.qml b/atomic_qt_design/qml/Components/ColumnHeader.qml index b4a93ce4f4..02922bc996 100644 --- a/atomic_qt_design/qml/Components/ColumnHeader.qml +++ b/atomic_qt_design/qml/Components/ColumnHeader.qml @@ -1,18 +1,16 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import "../Constants" -Rectangle { +Item { property int sort_type - property alias text: title.text + property alias text: title.text_value - property bool hovered: false property bool icon_at_left - color: "transparent" width: text.length * title.font.pixelSize height: title.height @@ -20,14 +18,22 @@ Rectangle { MouseArea { anchors.fill: parent hoverEnabled: true - onHoveredChanged: hovered = containsMouse onClicked: { if(current_sort === sort_type) { - highest_first = !highest_first + ascending = !ascending } else { current_sort = sort_type - highest_first = true + ascending = false + } + + // Apply the sort + switch(current_sort) { + case sort_by_name: API.get().portfolio_mdl.portfolio_proxy_mdl.sort_by_name(ascending); break + case sort_by_value: API.get().portfolio_mdl.portfolio_proxy_mdl.sort_by_currency_balance(ascending); break + case sort_by_price: API.get().portfolio_mdl.portfolio_proxy_mdl.sort_by_currency_unit(ascending); break + case sort_by_trend: + case sort_by_change: API.get().portfolio_mdl.portfolio_proxy_mdl.sort_by_change_last24h(ascending); break } } } @@ -41,10 +47,10 @@ Rectangle { // Arrow icon - Image { + DefaultImage { id: arrow_icon - source: General.image_path + "arrow-" + (highest_first ? "down" : "up") + ".svg" + source: General.image_path + "arrow-" + (ascending ? "up" : "down") + ".svg" width: title.font.pixelSize * 0.5 fillMode: Image.PreserveAspectFit diff --git a/atomic_qt_design/qml/Components/ComboBoxWithTitle.qml b/atomic_qt_design/qml/Components/ComboBoxWithTitle.qml index 3de0c65869..0ec3f79b14 100644 --- a/atomic_qt_design/qml/Components/ComboBoxWithTitle.qml +++ b/atomic_qt_design/qml/Components/ComboBoxWithTitle.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" ColumnLayout { diff --git a/atomic_qt_design/qml/Components/CopyFieldButton.qml b/atomic_qt_design/qml/Components/CopyFieldButton.qml index 66ef2034d5..4548784a35 100644 --- a/atomic_qt_design/qml/Components/CopyFieldButton.qml +++ b/atomic_qt_design/qml/Components/CopyFieldButton.qml @@ -1,11 +1,11 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" // Copy button -Image { +DefaultImage { source: General.image_path + "dashboard-copy.svg" visible: copyable scale: 0.8 diff --git a/atomic_qt_design/qml/Components/DangerButton.qml b/atomic_qt_design/qml/Components/DangerButton.qml index c0dc34179b..15c6d1f556 100644 --- a/atomic_qt_design/qml/Components/DangerButton.qml +++ b/atomic_qt_design/qml/Components/DangerButton.qml @@ -1,11 +1,15 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" DefaultButton { - Material.background: Style.colorRed2 + colorDisabled: Style.colorButtonDangerDisabled + colorHovered: Style.colorButtonDangerHovered + colorEnabled: Style.colorButtonDangerEnabled + colorTextDisabled: Style.colorWhite8 + colorTextEnabled: Style.colorWhite1 } /*##^## diff --git a/atomic_qt_design/qml/Components/DefaultBusyIndicator.qml b/atomic_qt_design/qml/Components/DefaultBusyIndicator.qml new file mode 100644 index 0000000000..bf74ce482f --- /dev/null +++ b/atomic_qt_design/qml/Components/DefaultBusyIndicator.qml @@ -0,0 +1,12 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 +import "../Constants" + +BusyIndicator { + id: control + implicitWidth: 48 + implicitHeight: 48 +} diff --git a/atomic_qt_design/qml/Components/DefaultButton.qml b/atomic_qt_design/qml/Components/DefaultButton.qml index c1f1140eb5..6af6da47d6 100644 --- a/atomic_qt_design/qml/Components/DefaultButton.qml +++ b/atomic_qt_design/qml/Components/DefaultButton.qml @@ -1,18 +1,56 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 +import QtGraphicalEffects 1.0 + import "../Constants" -Button { - Material.background: Style.colorTheme5 - Material.foreground: Style.colorWhite - Material.elevation: Style.materialElevation -} +// Add button +FloatingBackground { + property alias containsMouse: mouse_area.containsMouse + property alias text: text_obj.text_value + property alias text_obj: text_obj + property bool text_left_align: false + property double text_offset: 0 + property alias font: text_obj.font + property string colorDisabled: Style.colorButtonDisabled + property string colorHovered: Style.colorButtonHovered + property string colorEnabled: Style.colorButtonEnabled + property string colorTextDisabled: Style.colorButtonTextDisabled + property string colorTextHovered: Style.colorButtonTextHovered + property string colorTextEnabled: Style.colorButtonTextEnabled + property int minWidth: 90 -/*##^## -Designer { - D{i:0;autoSize:true;height:480;width:640} -} -##^##*/ + signal clicked() + + id: button_bg + + implicitWidth: Math.max(minWidth, text_obj.width + 20 + Math.abs(text_offset)) + implicitHeight: 40 + radius: 100 + + color: !enabled ? colorDisabled : mouse_area.containsMouse ? colorHovered : colorEnabled + border.width: 0 + + MouseArea { + id: mouse_area + anchors.fill: parent + hoverEnabled: true + onClicked: { + if(parent.enabled) parent.clicked() + } + } + + DefaultText { + id: text_obj + anchors.horizontalCenter: text_left_align ? undefined : parent.horizontalCenter + anchors.horizontalCenterOffset: text_left_align ? 0 : text_offset + anchors.left: text_left_align ? parent.left : undefined + anchors.leftMargin: text_left_align ? -text_offset : 0 + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: Style.textSizeSmall1 + font.capitalization: Font.AllUppercase + color: !parent.enabled ? colorTextDisabled : mouse_area.containsMouse ? colorTextHovered : colorTextEnabled + } +} diff --git a/atomic_qt_design/qml/Components/DefaultCheckBox.qml b/atomic_qt_design/qml/Components/DefaultCheckBox.qml new file mode 100644 index 0000000000..6d356c0727 --- /dev/null +++ b/atomic_qt_design/qml/Components/DefaultCheckBox.qml @@ -0,0 +1,16 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Universal 2.12 +import "../Constants" + +CheckBox { + font.family: Style.font_family +} + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ + diff --git a/atomic_qt_design/qml/Components/DefaultComboBox.qml b/atomic_qt_design/qml/Components/DefaultComboBox.qml index 1c45c38ce3..888798b54d 100644 --- a/atomic_qt_design/qml/Components/DefaultComboBox.qml +++ b/atomic_qt_design/qml/Components/DefaultComboBox.qml @@ -1,13 +1,11 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 +import QtQuick.Controls.Universal 2.12 import "../Constants" ComboBox { - Material.background: Style.colorTheme8 - Material.foreground: Style.colorWhite - Material.elevation: Style.materialElevation + font.family: Style.font_family } /*##^## diff --git a/atomic_qt_design/qml/Components/DefaultFlickable.qml b/atomic_qt_design/qml/Components/DefaultFlickable.qml new file mode 100644 index 0000000000..12e1fd3d38 --- /dev/null +++ b/atomic_qt_design/qml/Components/DefaultFlickable.qml @@ -0,0 +1,14 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import "../Constants" + +Flickable { + id: root + + property bool scrollbar_visible: contentHeight > height + ScrollBar.vertical: DefaultScrollBar { } + + clip: true +} diff --git a/atomic_qt_design/qml/Components/DefaultImage.qml b/atomic_qt_design/qml/Components/DefaultImage.qml new file mode 100644 index 0000000000..9fedd8b4bf --- /dev/null +++ b/atomic_qt_design/qml/Components/DefaultImage.qml @@ -0,0 +1,16 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Universal 2.12 +import "../Constants" + +Image { + mipmap: true +} + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ + diff --git a/atomic_qt_design/qml/Components/DefaultInnerShadow.qml b/atomic_qt_design/qml/Components/DefaultInnerShadow.qml new file mode 100644 index 0000000000..f7c909a189 --- /dev/null +++ b/atomic_qt_design/qml/Components/DefaultInnerShadow.qml @@ -0,0 +1,16 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 +import "../Constants" + +InnerShadow { + cached: false + horizontalOffset: 0.7 + verticalOffset: 0.7 + radius: 13 + samples: 32 + color: Style.colorInnerShadow + smooth: true +} diff --git a/atomic_qt_design/qml/Components/DefaultListView.qml b/atomic_qt_design/qml/Components/DefaultListView.qml new file mode 100644 index 0000000000..867175a525 --- /dev/null +++ b/atomic_qt_design/qml/Components/DefaultListView.qml @@ -0,0 +1,18 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import "../Constants" + +ListView { + id: root + + readonly property bool scrollbar_visible: contentHeight > height + readonly property double scrollbar_margin: scrollbar_visible ? 8 : 0 + ScrollBar.vertical: DefaultScrollBar { } + + implicitWidth: contentItem.childrenRect.width + implicitHeight: contentItem.childrenRect.height + + clip: true +} diff --git a/atomic_qt_design/qml/Components/DefaultModal.qml b/atomic_qt_design/qml/Components/DefaultModal.qml index f96db6184b..e53b2b572b 100644 --- a/atomic_qt_design/qml/Components/DefaultModal.qml +++ b/atomic_qt_design/qml/Components/DefaultModal.qml @@ -1,10 +1,9 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" -// Open Enable Coin Modal Popup { id: root anchors.centerIn: Overlay.overlay @@ -12,7 +11,10 @@ Popup { focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - Material.background: Style.colorTheme6 + Overlay.modal: Rectangle { + color: "#AA000000" + } + background: FloatingBackground { } } /*##^## diff --git a/atomic_qt_design/qml/Components/DefaultRectangle.qml b/atomic_qt_design/qml/Components/DefaultRectangle.qml new file mode 100644 index 0000000000..638ee14347 --- /dev/null +++ b/atomic_qt_design/qml/Components/DefaultRectangle.qml @@ -0,0 +1,16 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 + +import "../Constants" + +Rectangle { + id: rect + radius: Style.rectangleCornerRadius + color: Style.colorRectangle + border.color: Style.colorBorder + border.width: 1 +} + diff --git a/atomic_qt_design/qml/Components/DefaultScrollBar.qml b/atomic_qt_design/qml/Components/DefaultScrollBar.qml new file mode 100644 index 0000000000..a7eae0bb99 --- /dev/null +++ b/atomic_qt_design/qml/Components/DefaultScrollBar.qml @@ -0,0 +1,49 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import "../Constants" + +ScrollBar { + id: control + + anchors.right: root.right + anchors.rightMargin: Style.scrollbarOffset + policy: scrollbar_visible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + + width: 6 + anchors.margins: 50 + contentItem: Item { + FloatingBackground { + width: parent.width + height: parent.height - 14 + anchors.verticalCenter: parent.verticalCenter + + radius: 100 + + color: Style.colorScrollbar + border_color_start: Style.colorScrollbarGradient1 + border_color_end: Style.colorScrollbarGradient2 + } + } + + background: Item { + width: 10 + x: -width/2 + 6/2 + InnerBackground { + width: parent.width + height: parent.height - 10 + anchors.verticalCenter: parent.verticalCenter + + radius: 100 + color: Style.colorScrollbarBackground + } + } +} + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ + diff --git a/atomic_qt_design/qml/Components/DefaultText.qml b/atomic_qt_design/qml/Components/DefaultText.qml index 4de64a5d4d..72be9cbc2d 100644 --- a/atomic_qt_design/qml/Components/DefaultText.qml +++ b/atomic_qt_design/qml/Components/DefaultText.qml @@ -1,14 +1,17 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" Text { - font.family: "Rubik" - font.pixelSize: Style.textSize - color: Style.colorWhite1 + property string text_value + property bool privacy: false + font.family: Style.font_family + font.pixelSize: Style.textSize + color: Style.colorText + text: privacy && General.privacy_mode ? "*****" : text_value wrapMode: Text.WordWrap } diff --git a/atomic_qt_design/qml/Components/DefaultTextArea.qml b/atomic_qt_design/qml/Components/DefaultTextArea.qml index 86e3f70295..1100c3923c 100644 --- a/atomic_qt_design/qml/Components/DefaultTextArea.qml +++ b/atomic_qt_design/qml/Components/DefaultTextArea.qml @@ -1,12 +1,15 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" TextArea { id: text_field + font.family: Style.font_family + placeholderTextColor: Style.colorPlaceholderText + property bool remove_newline: true wrapMode: TextEdit.Wrap @@ -36,9 +39,9 @@ TextArea { selectByMouse: true persistentSelection: true - RightClickMenu { + background: InnerBackground { } - } + RightClickMenu { } } /*##^## @@ -46,4 +49,3 @@ Designer { D{i:0;autoSize:true;height:480;width:640} } ##^##*/ - diff --git a/atomic_qt_design/qml/Components/DefaultTextField.qml b/atomic_qt_design/qml/Components/DefaultTextField.qml index 0ca20346ba..50540d0e4f 100644 --- a/atomic_qt_design/qml/Components/DefaultTextField.qml +++ b/atomic_qt_design/qml/Components/DefaultTextField.qml @@ -1,19 +1,24 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" TextField { id: text_field + font.family: Style.font_family + placeholderTextColor: Style.colorPlaceholderText + // Right click Context Menu selectByMouse: true persistentSelection: true - RightClickMenu { - + background: InnerBackground { + radius: 100 } + + RightClickMenu { } } /*##^## diff --git a/atomic_qt_design/qml/Components/EulaModal.qml b/atomic_qt_design/qml/Components/EulaModal.qml index 4471a4184b..f9e3289dc7 100644 --- a/atomic_qt_design/qml/Components/EulaModal.qml +++ b/atomic_qt_design/qml/Components/EulaModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" import ".." @@ -30,25 +30,22 @@ DefaultModal { } - Rectangle { + InnerBackground { id: eula_rect - color: Style.colorTheme7 - radius: Style.rectangleCornerRadius + height: 400 Layout.fillWidth: true - Flickable { - ScrollBar.vertical: ScrollBar { } + DefaultFlickable { anchors.fill: parent anchors.margins: 20 - clip: true contentWidth: eula_text.width contentHeight: eula_text.height DefaultText { id: eula_text - text: API.get().empty_string + (getEula()) + text_value: API.get().empty_string + (getEula()) width: eula_rect.width - 40 } @@ -56,13 +53,13 @@ DefaultModal { } // Checkboxes - CheckBox { + DefaultCheckBox { id: accept_eula visible: !close_only text: API.get().empty_string + (qsTr("Accept EULA")) } - CheckBox { + DefaultCheckBox { id: accept_tac visible: !close_only text: API.get().empty_string + (qsTr("Accept Terms and Conditions")) diff --git a/atomic_qt_design/qml/Components/FloatingBackground.qml b/atomic_qt_design/qml/Components/FloatingBackground.qml new file mode 100644 index 0000000000..daf9b1f23b --- /dev/null +++ b/atomic_qt_design/qml/Components/FloatingBackground.qml @@ -0,0 +1,100 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 +import "../Constants" + +Item { + id: root + property alias rect: rect + property alias color: rect.color + property double border_gradient_start_pos: 0.35 + property double border_gradient_end_pos: 0.65 + property color border_color_start: Style.colorRectangleBorderGradient1 + property color border_color_end: Style.colorRectangleBorderGradient2 + property alias radius: rect.radius + property alias border: rect.border + property alias inner_space: inner_space + property alias content: inner_space.sourceComponent + property alias mask: mask_loader.sourceComponent + property bool verticalShadow: false + property bool opacity_mask_enabled: false + property bool auto_set_size: true + + readonly property var visible_rect: opacity_mask_enabled ? mask_loader : rect + + implicitWidth: auto_set_size ? inner_space.width : 0 + implicitHeight: auto_set_size ? inner_space.height : 0 + + DefaultRectangle { + id: rect + anchors.fill: parent + border.color: "transparent" + + Loader { + anchors.centerIn: parent + id: inner_space + } + + visible: !opacity_mask_enabled + } + + Loader { + id: mask_loader + anchors.fill: rect + } + + LinearGradient { + visible: rect.border.width > 0 + source: visible_rect + width: parent.width + rect.border.width*2 + height: parent.height + rect.border.width*2 + anchors.centerIn: parent + + z: -1 + start: Qt.point(0, 0) + end: Qt.point(width, height) + + gradient: Gradient { + GradientStop { + position: border_gradient_start_pos + color: border_color_start + } + GradientStop { + position: border_gradient_end_pos + color: border_color_end + } + } + } + + DropShadow { + anchors.fill: visible_rect + source: visible_rect + cached: false + horizontalOffset: verticalShadow ? 0 : -6 + verticalOffset: verticalShadow ? -10 : -6 + radius: verticalShadow ? 25 : 15 + samples: 32 + spread: 0 + color: verticalShadow ? Style.colorDropShadowLight2 : Style.colorDropShadowLight + smooth: true + z: -2 + } + + DropShadow { + anchors.fill: visible_rect + source: visible_rect + cached: false + horizontalOffset: verticalShadow ? 0 : 6 + verticalOffset: verticalShadow ? 10 : 6 + radius: verticalShadow ? 25 : 20 + samples: 32 + spread: 0 + color: Style.colorDropShadowDark + smooth: true + z: -2 + } +} + + diff --git a/atomic_qt_design/qml/Components/GradientRectangle.qml b/atomic_qt_design/qml/Components/GradientRectangle.qml new file mode 100644 index 0000000000..882ed7fae9 --- /dev/null +++ b/atomic_qt_design/qml/Components/GradientRectangle.qml @@ -0,0 +1,35 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 + +import "../Constants" + +// Gradient +Rectangle { + property alias orientation: gradient.orientation + + property alias start_pos: g_start.position + property alias end_pos: g_end.position + + property color start_color: Style.colorGradient1 + property color end_color: Style.colorGradient2 + + gradient: Gradient { + id: gradient + orientation: Qt.Horizontal + + GradientStop { + id: g_start + position: 0.0 + color: start_color + } + + GradientStop { + id: g_end + position: 1.0 + color: end_color + } + } +} diff --git a/atomic_qt_design/qml/Components/HideFieldButton.qml b/atomic_qt_design/qml/Components/HideFieldButton.qml index 7d0c870bd5..5632641c43 100644 --- a/atomic_qt_design/qml/Components/HideFieldButton.qml +++ b/atomic_qt_design/qml/Components/HideFieldButton.qml @@ -1,19 +1,19 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" // Hide button -Image { +DefaultImage { property alias mouse_area: mouse_area property bool use_default_behaviour: true source: General.image_path + "dashboard-eye" + (hiding ? "" : "-hide") + ".svg" visible: hidable scale: 0.8 anchors.right: parent.right + anchors.rightMargin: 5 anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: input_field.height * -0.0625 antialiasing: true diff --git a/atomic_qt_design/qml/Components/HorizontalLine.qml b/atomic_qt_design/qml/Components/HorizontalLine.qml index e1aa89a515..67127c9013 100644 --- a/atomic_qt_design/qml/Components/HorizontalLine.qml +++ b/atomic_qt_design/qml/Components/HorizontalLine.qml @@ -1,11 +1,17 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + +import QtGraphicalEffects 1.0 import "../Constants" Rectangle { - // width: - height: 1 - color: Style.colorWhite5 + height: 2 + property bool light: false + + gradient: Gradient { + orientation: Qt.Vertical + GradientStop { position: 0.0; color: light ? Style.colorLineGradient3 : Style.colorLineGradient2 } + GradientStop { position: 1.0; color: light ? Style.colorLineGradient4 : Style.colorLineGradient1 } + } } diff --git a/atomic_qt_design/qml/Components/InnerBackground.qml b/atomic_qt_design/qml/Components/InnerBackground.qml new file mode 100644 index 0000000000..1ffcbb6be8 --- /dev/null +++ b/atomic_qt_design/qml/Components/InnerBackground.qml @@ -0,0 +1,72 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtGraphicalEffects 1.12 + +import "../Constants" + +Item { + property alias content: inner_space.sourceComponent + property alias color: rect.color + property alias radius: rect.radius + property alias border: rect.border + + + width: inner_space.width + height: inner_space.height + + + Item { + id: rect_with_shadow + anchors.fill: parent + + DefaultRectangle { + id: rect + anchors.fill: parent + border.color: "transparent" + color: Style.colorInnerBackground + + Loader { + anchors.centerIn: parent + id: inner_space + + layer.enabled: true + + layer.effect: OpacityMask { + maskSource: Rectangle { + width: inner_space.width + height: inner_space.height + radius: rect.radius + } + } + } + } + + layer.enabled: true + layer.effect: DefaultInnerShadow { } + } + + LinearGradient { + id: gradient + visible: rect.border.width > 0 + source: rect + width: rect.width + rect.border.width*2 + height: rect.height + rect.border.width*2 + anchors.centerIn: parent + + z: -1 + start: Qt.point(0, 0) + end: Qt.point(0, height) + + gradient: Gradient { + GradientStop { + position: 0.35 + color: Style.colorRectangleBorderGradient2 + } + GradientStop { + position: 0.65 + color: Style.colorRectangleBorderGradient1 + } + } + } +} diff --git a/atomic_qt_design/qml/Components/LogModal.qml b/atomic_qt_design/qml/Components/LogModal.qml index 20af259f48..0c27c731af 100644 --- a/atomic_qt_design/qml/Components/LogModal.qml +++ b/atomic_qt_design/qml/Components/LogModal.qml @@ -4,34 +4,23 @@ import QtQuick.Controls 2.12 import "../Constants" -Popup { +DefaultModal { property alias title: text_area.title property alias field: text_area.field id: root - anchors.centerIn: Overlay.overlay - modal: true - focus: true - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - - Overlay.modal: Rectangle { - color: "#AA000000" - } padding: 50 width: 900 height: Math.min(text_area.height + padding*2, 700) - Flickable { - clip: true + DefaultFlickable { anchors.fill: parent contentWidth: text_area.width contentHeight: text_area.height - ScrollBar.vertical: ScrollBar { } - TextAreaWithTitle { id: text_area width: root.width - root.padding*2 diff --git a/atomic_qt_design/qml/Components/ModalHeader.qml b/atomic_qt_design/qml/Components/ModalHeader.qml index 742c947eb7..10f34e4cc1 100644 --- a/atomic_qt_design/qml/Components/ModalHeader.qml +++ b/atomic_qt_design/qml/Components/ModalHeader.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" ColumnLayout { diff --git a/atomic_qt_design/qml/Components/PaneWithTitle.qml b/atomic_qt_design/qml/Components/PaneWithTitle.qml deleted file mode 100644 index 01601b8f4c..0000000000 --- a/atomic_qt_design/qml/Components/PaneWithTitle.qml +++ /dev/null @@ -1,42 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 -import "../Constants" - -Column { - id: column - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - spacing: Style.paneTitleOffset - - - property string title - property alias content: inner_space.sourceComponent - property string color: Style.colorTheme6 - DefaultText { - text: API.get().empty_string + (title) - } - - Pane { - id: pane - - background: Rectangle { - color: column.color - radius: Style.rectangleCornerRadius - } - - Loader { - id: inner_space - } - } -} - - - - - -/*##^## -Designer { - D{i:0;autoSize:true;height:480;width:640} -} -##^##*/ diff --git a/atomic_qt_design/qml/Components/PasswordField.qml b/atomic_qt_design/qml/Components/PasswordField.qml index 2b93bc1fa3..cc13e53d85 100644 --- a/atomic_qt_design/qml/Components/PasswordField.qml +++ b/atomic_qt_design/qml/Components/PasswordField.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" ColumnLayout { @@ -9,6 +9,7 @@ ColumnLayout { property alias field: pw.field property bool hide_hint: false property bool new_password: true + property string match_password function isValid() { return pw.field.acceptableInput && RegExp(General.reg_pass_valid).test(pw.field.text) @@ -34,6 +35,10 @@ ColumnLayout { return pw.field.acceptableInput && RegExp(General.reg_pass_count).test(pw.field.text) } + function passwordsDoMatch() { + return match_password !== "" && pw.field.acceptableInput && pw.field.text === match_password + } + function hintColor(valid) { return valid ? Style.colorGreen : Style.colorRed } @@ -51,36 +56,41 @@ ColumnLayout { } ColumnLayout { - spacing: -Style.textSizeSmall3*0.3 + spacing: -Style.textSizeSmall3*0.1 visible: !hide_hint Layout.fillWidth: true DefaultText { font.pixelSize: Style.textSizeSmall3 - text: API.get().empty_string + (hintPrefix(hasEnoughLowercaseCharacters()) + qsTr("At least 1 lowercase alphabetical character")) + text_value: API.get().empty_string + (hintPrefix(hasEnoughLowercaseCharacters()) + qsTr("At least 1 lowercase alphabetical character")) color: hintColor(hasEnoughLowercaseCharacters()) } DefaultText { font.pixelSize: Style.textSizeSmall3 - text: API.get().empty_string + (hintPrefix(hasEnoughUppercaseCharacters()) + qsTr("At least 1 uppercase alphabetical character")) + text_value: API.get().empty_string + (hintPrefix(hasEnoughUppercaseCharacters()) + qsTr("At least 1 uppercase alphabetical character")) color: hintColor(hasEnoughUppercaseCharacters()) } DefaultText { font.pixelSize: Style.textSizeSmall3 - text: API.get().empty_string + (hintPrefix(hasEnoughNumericCharacters()) + qsTr("At least 1 numeric character")) + text_value: API.get().empty_string + (hintPrefix(hasEnoughNumericCharacters()) + qsTr("At least 1 numeric character")) color: hintColor(hasEnoughNumericCharacters()) } DefaultText { font.pixelSize: Style.textSizeSmall3 - text: API.get().empty_string + (hintPrefix(hasEnoughSpecialCharacters()) + qsTr("At least 1 special character (eg. !@#$%)")) + text_value: API.get().empty_string + (hintPrefix(hasEnoughSpecialCharacters()) + qsTr("At least 1 special character (eg. !@#$%)")) color: hintColor(hasEnoughSpecialCharacters()) } DefaultText { font.pixelSize: Style.textSizeSmall3 - text: API.get().empty_string + (hintPrefix(hasEnoughCharacters()) + qsTr("At least 16 characters")) + text_value: API.get().empty_string + (hintPrefix(hasEnoughCharacters()) + qsTr("At least 16 characters")) color: hintColor(hasEnoughCharacters()) } + DefaultText { + font.pixelSize: Style.textSizeSmall3 + text_value: API.get().empty_string + (hintPrefix(passwordsDoMatch()) + qsTr("Password and Confirm Password have to be same")) + color: hintColor(passwordsDoMatch()) + } } } diff --git a/atomic_qt_design/qml/Components/PasswordForm.qml b/atomic_qt_design/qml/Components/PasswordForm.qml index c3bfde7001..a2345657d1 100644 --- a/atomic_qt_design/qml/Components/PasswordForm.qml +++ b/atomic_qt_design/qml/Components/PasswordForm.qml @@ -1,11 +1,12 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" ColumnLayout { id: form + spacing: Style.rowSpacing property alias field: input_password.field property alias confirm_field: input_confirm_password.field @@ -31,6 +32,7 @@ ColumnLayout { id: input_password new_password: form.new_password hide_hint: !confirm + match_password: input_confirm_password.field.text } PasswordField { diff --git a/atomic_qt_design/qml/Components/PlusButton.qml b/atomic_qt_design/qml/Components/PlusButton.qml index c4c69575b1..84db8781d0 100644 --- a/atomic_qt_design/qml/Components/PlusButton.qml +++ b/atomic_qt_design/qml/Components/PlusButton.qml @@ -1,44 +1,22 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + +import QtGraphicalEffects 1.0 import "../Constants" // Add button -Rectangle { - property alias mouse_area: mouse_area - - id: add_coin_button - - width: 50; height: width - property bool hovered: false - color: "transparent" - border.color: hovered ? Style.colorTheme0 : Style.colorTheme3 - border.width: 2 +DefaultButton { + width: 45 + height: width radius: 100 - Rectangle { - width: parent.border.width - height: parent.width * 0.5 - radius: parent.radius - color: parent.border.color - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - } + text: "+" + font.pixelSize: width * 0.65 + font.weight: Font.Light - Rectangle { - width: parent.width * 0.5 - height: parent.border.width - radius: parent.radius - color: parent.border.color - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - } + verticalShadow: true - MouseArea { - id: mouse_area - anchors.fill: parent - hoverEnabled: true - onHoveredChanged: add_coin_button.hovered = containsMouse - } + colorEnabled: Style.colorButtonHovered + colorHovered: Style.colorButtonEnabled } diff --git a/atomic_qt_design/qml/Components/PrimaryButton.qml b/atomic_qt_design/qml/Components/PrimaryButton.qml index fd5619416f..44d9454c41 100644 --- a/atomic_qt_design/qml/Components/PrimaryButton.qml +++ b/atomic_qt_design/qml/Components/PrimaryButton.qml @@ -1,11 +1,17 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" DefaultButton { - Material.background: Style.colorTheme4 + colorDisabled: Style.colorButtonPrimaryDisabled + colorHovered: Style.colorButtonPrimaryHovered + colorEnabled: Style.colorButtonPrimaryEnabled + colorTextDisabled: Style.colorWhite13 + colorTextEnabled: Style.colorWhite10 + colorTextHovered: Style.colorWhite10 + font.bold: true } /*##^## diff --git a/atomic_qt_design/qml/Components/RightClickMenu.qml b/atomic_qt_design/qml/Components/RightClickMenu.qml index 23ff8d2ff7..db86310fe6 100644 --- a/atomic_qt_design/qml/Components/RightClickMenu.qml +++ b/atomic_qt_design/qml/Components/RightClickMenu.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" MouseArea { diff --git a/atomic_qt_design/qml/Components/Separator.qml b/atomic_qt_design/qml/Components/Separator.qml new file mode 100644 index 0000000000..812ad1ea31 --- /dev/null +++ b/atomic_qt_design/qml/Components/Separator.qml @@ -0,0 +1,19 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 +import "../Constants" + +Rectangle { + height: 1 + width: 150 + + gradient: Gradient { + orientation: Qt.Horizontal + + GradientStop { position: 0.0; color: Style.colorGradientLine1 } + GradientStop { position: 0.5; color: Style.colorGradientLine2 } + GradientStop { position: 1.0; color: Style.colorGradientLine1 } + } +} diff --git a/atomic_qt_design/qml/Components/SetupPage.qml b/atomic_qt_design/qml/Components/SetupPage.qml index 5420c4c49a..c32615eb81 100644 --- a/atomic_qt_design/qml/Components/SetupPage.qml +++ b/atomic_qt_design/qml/Components/SetupPage.qml @@ -1,14 +1,15 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" Item { + property alias image: image property alias image_path: image.source property alias image_scale: image.scale - property alias title: pane.title - property alias content: pane.content + property alias content: inner_space.sourceComponent + property double image_margin: 5 ColumnLayout { id: window_layout @@ -16,26 +17,31 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter transformOrigin: Item.Center - spacing: 0 - - Rectangle { - id: rectangle - color: Style.colorTheme6 - radius: 100 - implicitWidth: image.implicitHeight - implicitHeight: image.implicitHeight + spacing: image_margin + + DefaultImage { + id: image Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - Image { - id: image - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - antialiasing: true - } + antialiasing: true } - PaneWithTitle { + Pane { id: pane + + leftPadding: 30 + rightPadding: leftPadding + topPadding: leftPadding * 0.5 + bottomPadding: topPadding + + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + background: FloatingBackground { + color: Style.colorTheme7 + } + + Loader { + id: inner_space + } } } } diff --git a/atomic_qt_design/qml/Components/SidebarPanel.qml b/atomic_qt_design/qml/Components/SidebarPanel.qml new file mode 100644 index 0000000000..f17d743af9 --- /dev/null +++ b/atomic_qt_design/qml/Components/SidebarPanel.qml @@ -0,0 +1,16 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 + +import "../Constants" + +GradientRectangle { + id: rect + radius: Style.rectangleCornerRadius + color: Style.colorRectangle + border.color: Style.colorBorder + border.width: 1 +} + diff --git a/atomic_qt_design/qml/Components/TextAreaWithTitle.qml b/atomic_qt_design/qml/Components/TextAreaWithTitle.qml index d77c25fd59..6a9f33bf2a 100644 --- a/atomic_qt_design/qml/Components/TextAreaWithTitle.qml +++ b/atomic_qt_design/qml/Components/TextAreaWithTitle.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" ColumnLayout { diff --git a/atomic_qt_design/qml/Components/TextFieldWithTitle.qml b/atomic_qt_design/qml/Components/TextFieldWithTitle.qml index e1df6b44fe..57f8125201 100644 --- a/atomic_qt_design/qml/Components/TextFieldWithTitle.qml +++ b/atomic_qt_design/qml/Components/TextFieldWithTitle.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" ColumnLayout { @@ -11,6 +11,7 @@ ColumnLayout { property alias hide_button_area: hide_button.mouse_area property bool copyable: false property bool hidable: false + property bool required: false property bool hiding: true @@ -19,9 +20,18 @@ ColumnLayout { input_field.text = '' } - DefaultText { - id: title_text - visible: text !== '' + RowLayout { + DefaultText { + id: title_text + visible: text !== '' + } + + DefaultText { + visible: required && input_field.text === '' + font.pixelSize: Style.textSizeSmall2 + text_value: API.get().empty_string + (qsTr("Required")) + color: Style.colorRed + } } DefaultTextField { diff --git a/atomic_qt_design/qml/Components/TextWithTitle.qml b/atomic_qt_design/qml/Components/TextWithTitle.qml index 5fca6b8418..bc0d4b94fc 100644 --- a/atomic_qt_design/qml/Components/TextWithTitle.qml +++ b/atomic_qt_design/qml/Components/TextWithTitle.qml @@ -1,13 +1,14 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" ColumnLayout { property alias title: title.text - property alias text: text.text + property alias text: text.text_value property alias value_color: text.color + property alias privacy: text.privacy DefaultText { id: title diff --git a/atomic_qt_design/qml/Components/Toast.qml b/atomic_qt_design/qml/Components/Toast.qml index 4ebd65783c..a8f72888fb 100644 --- a/atomic_qt_design/qml/Components/Toast.qml +++ b/atomic_qt_design/qml/Components/Toast.qml @@ -50,7 +50,7 @@ Rectangle { margins: margin / 2 } font.pixelSize: Style.textSizeSmall2 - text: title + (details !== undefined && details !== "" ? (" - " + qsTr("Click here to see the details")) : "") + text_value: title + (details !== undefined && details !== "" ? (" - " + qsTr("Click here to see the details")) : "") } SequentialAnimation on opacity { diff --git a/atomic_qt_design/qml/Components/UpdateModal.qml b/atomic_qt_design/qml/Components/UpdateModal.qml new file mode 100644 index 0000000000..d17081cd0f --- /dev/null +++ b/atomic_qt_design/qml/Components/UpdateModal.qml @@ -0,0 +1,84 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import "../Constants" + +DefaultModal { + readonly property bool status_good: API.get().update_status.rpc_code === 200 + readonly property bool update_needed: status_good && API.get().update_status.update_needed + readonly property bool required_update: update_needed && (API.get().update_status.status === "required") + readonly property bool suggest_update: update_needed && (required_update || API.get().update_status.status === "recommended") + + onSuggest_updateChanged: { + if(suggest_update) { + // Force-open if update is suggested + if(!root.opened) root.open() + } + } + + id: root + + padding: 50 + + closePolicy: suggest_update ? Popup.NoAutoClose : (Popup.CloseOnEscape | Popup.CloseOnPressOutside) + + width: 900 + height: Math.min(text_area.height + padding*2, 700) + + DefaultText { + anchors.top: parent.top + anchors.topMargin: padding + anchors.right: parent.right + anchors.rightMargin: padding + + font.pixelSize: Style.textSize2 + + text_value: API.get().empty_string + ("(" + (!suggest_update ? qsTr("Available") : required_update ? qsTr("Required") : qsTr("Recommended")) + ")") + color: !suggest_update ? Style.colorGreen : required_update ? Style.colorRed : Style.colorOrange + } + + ColumnLayout { + anchors.fill: parent + + ModalHeader { + id: header + title: API.get().empty_string + (General.download_icon + " " + qsTr("New Update!") + " " + (API.get().update_status.current_version + " " + General.right_arrow_icon + " " + API.get().update_status.new_version)) + } + + + DefaultFlickable { + Layout.fillWidth: true + Layout.fillHeight: true + + contentWidth: text_area.width + contentHeight: text_area.height + + DefaultTextArea { + id: text_area + width: root.width - root.padding*2 + readOnly: true + text: status_good ? API.get().update_status.changelog : (qsTr("Problem occured") + ": " + API.get().update_status.status) + textFormat: Text.MarkdownText + remove_newline: false + } + } + + RowLayout { + Layout.topMargin: root.padding * 0.5 + Layout.alignment: Qt.AlignHCenter + + DefaultButton { + text: API.get().empty_string + (qsTr("Skip")) + onClicked: root.close() + visible: !required_update + } + + PrimaryButton { + enabled: status_good + text: API.get().empty_string + (qsTr("Download")) + onClicked: Qt.openUrlExternally(API.get().update_status.download_url) + } + } + } +} diff --git a/atomic_qt_design/qml/Components/UpdateNotificationLine.qml b/atomic_qt_design/qml/Components/UpdateNotificationLine.qml new file mode 100644 index 0000000000..ada0f7e31e --- /dev/null +++ b/atomic_qt_design/qml/Components/UpdateNotificationLine.qml @@ -0,0 +1,34 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import "../Constants" + +Rectangle { + visible: update_modal.update_needed && update_modal.status_good + + color: Style.colorGreen + height: 30 + radius + width: text.width + 30 + radius + + anchors.topMargin: -radius + anchors.rightMargin: -radius + + radius: Style.rectangleCornerRadius + + DefaultText { + id: text + anchors.centerIn: parent + anchors.horizontalCenterOffset: -parent.radius * 0.5 + anchors.verticalCenterOffset: parent.radius * 0.4 + + text: API.get().empty_string + (General.download_icon + " " + qsTr("New update available!") + " " + qsTr("Version:") + " " + API.get().update_status.new_version + " - " + qsTr("Click here for the details.")) + font.pixelSize: Style.textSizeSmall3 + color: Style.colorWhite10 + } + + MouseArea { + anchors.fill: parent + onClicked: update_modal.open() + } +} diff --git a/atomic_qt_design/qml/Components/VerticalLine.qml b/atomic_qt_design/qml/Components/VerticalLine.qml index 022cdb7566..3f3f6939ac 100644 --- a/atomic_qt_design/qml/Components/VerticalLine.qml +++ b/atomic_qt_design/qml/Components/VerticalLine.qml @@ -1,10 +1,16 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + +import QtGraphicalEffects 1.0 import "../Constants" Rectangle { - width: 1 - color: Style.colorWhite5 + width: 2 + + gradient: Gradient { + orientation: Qt.Horizontal + GradientStop { position: 0.0; color: Style.colorLineGradient1 } + GradientStop { position: 1.0; color: Style.colorLineGradient2 } + } } diff --git a/atomic_qt_design/qml/Components/VerticalLineBasic.qml b/atomic_qt_design/qml/Components/VerticalLineBasic.qml new file mode 100644 index 0000000000..47f3afde81 --- /dev/null +++ b/atomic_qt_design/qml/Components/VerticalLineBasic.qml @@ -0,0 +1,11 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 +import "../Constants" + +Rectangle { + width: 1 + color: Style.colorLineBasic +} diff --git a/atomic_qt_design/qml/Components/WalletNameField.qml b/atomic_qt_design/qml/Components/WalletNameField.qml index 087819f0a1..d48965c93a 100644 --- a/atomic_qt_design/qml/Components/WalletNameField.qml +++ b/atomic_qt_design/qml/Components/WalletNameField.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Constants" TextFieldWithTitle { @@ -10,6 +10,8 @@ TextFieldWithTitle { field.placeholderText: API.get().empty_string + (qsTr("Enter the name of your wallet here")) field.validator: RegExpValidator { regExp: /[a-zA-Z0-9]+/ } + required: true + function reset() { field.text = '' } diff --git a/atomic_qt_design/qml/Constants/API.qml b/atomic_qt_design/qml/Constants/API.qml index 2c2ca8ee7b..23a307e776 100644 --- a/atomic_qt_design/qml/Constants/API.qml +++ b/atomic_qt_design/qml/Constants/API.qml @@ -5,14 +5,70 @@ QtObject { // Mock API property string saved_seed property string saved_password + property var update_info: ({ + "update_needed": true, + "new_version": "0.1.5", + "version_num": "015", + "changelog": "blabla", + "status": "available", + "download_url": "https://github.com/KomodoPlatform/atomicDEX-Pro/releases/tag/0.1.5-alpha" + }) + property var mockAPI: ({ empty_string: '', // Signals myOrdersUpdated: { connect: (func) => { console.log("Connecting function") } }, + OHLCDataUpdated: { + connect: (func) => { console.log("Connecting function") } + }, + // Other + addressbook_mdl: [ + { + name: "ca333", + addresses: [ + { type: "ERC-20", address: "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" }, + { type: "SmartChains", address: "RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" }, + { type: "BTC", address: "3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" } + ] + }, + { + name: "alice", + addresses: [ + { type: "ERC-20", address: "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" }, + { type: "SmartChains", address: "RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" }, + { type: "BTC", address: "3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" } + ] + }, + { + name: "bob", + addresses: [ + { type: "ERC-20", address: "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" }, + { type: "SmartChains", address: "RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" }, + { type: "BTC", address: "3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" } + ] + }, + ], + add_contact: (name) => { + address_book[name] = {} + + address_book = address_book + }, + update_contact: (old_name, new_name) => { + address_book[new_name] = General.clone(address_book[old_name]) + delete address_book[old_name] + + address_book = address_book + }, + insert_or_update_address: (name, ticker, address) => { + address_book[name][ticker] = address + + address_book = address_book + }, + to_eth_checksum_qt: (addr) => { return "0xA00bF635b2cD52F2b6B4D8cd9B9efd290B97838C" }, retrieve_seed: (wallet_name, password) => { return "this is a test seed gossip rubber flee just connect manual any salmon limb suffer now turkey essence naive daughter system begin quantum page" }, get_log_folder: () => { return "D:/Projects/atomicDEX-Pro/atomic_qt_design" }, @@ -24,13 +80,23 @@ QtObject { balance_fiat_all: "12345678.90", - fiat: "USD", + current_fiat: "EUR", + current_currency: "EUR", + get_available_fiats: () => ["USD", "EUR"], + get_available_currencies: () => ["EUR", "BTC", "KMD"], + lang: "en", get_available_langs: () => ["en", "fr", "tr"], + get_cex_rates: (base, rel) => { + if(rel === "USD") return "9531.53" + if(rel === "EUR") return "6234.152" + return "25" + }, + confirm_password: (wallet_name, password) => { return true }, - current_coin_info: {"objectName":"", "tx_state": "Finished", "tx_current_block": "1523412", "is_claimable": true, "minimal_balance_for_asking_rewards": "10", "ticker":"MORTY", "balance":"18.20118151","address":"RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","fiat_amount":"0.00","explorer_url":"https://morty.kmd.dev/","transactions":[{"objectName":"","received":true,"blockheight":394149,"confirmations":26896,"timestamp":1588257051,"amount":"2.49999","amount_fiat":"0.00","date":"30 Apr 2020, 02:30","tx_hash":"114fa37e99e50f22ffd1df35a7a61a461b0dd8d7526d8a75cfe83e83886f80bc","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bbtott43HtbWs3b5uQU2ZzerCGxF39pygg"]},{"objectName":"","received":false,"blockheight":372818,"confirmations":48227,"timestamp":1586961103,"amount":"8.12079281","amount_fiat":"0.00","date":"15 Apr 2020, 02:31","tx_hash":"3f914bfb681fad98490321a6571c9d7580707a80ff0b431366c1cb27abb10abd","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RWQr5oJ2h2ptVdpK54LVyfRvgfNLxezfkU"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":372618,"confirmations":48427,"timestamp":1586949021,"amount":"0.01555","amount_fiat":"0.00","date":"15 Apr 2020, 11:10","tx_hash":"4ae3ee0620e900bd7479ea67f7ffc4e57b7eef7247f11e92cc8d46d7a958e758","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bZ3jTPHYe9KTY5bdF85REPK4zof93ySjgo"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":372617,"confirmations":48428,"timestamp":1586948929,"amount":"0.00011","amount_fiat":"0.00","date":"15 Apr 2020, 11:08","tx_hash":"13efd1952af1c16330987fdeefd7e1b94d8c9231972bfa1eb0880bdbb4ffebd8","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":true,"blockheight":357205,"confirmations":63840,"timestamp":1586020878,"amount":"7.777","amount_fiat":"0.00","date":" 4 Apr 2020, 05:21","tx_hash":"5f68701a4e9cc111f80974c93e4c1abc54b9b1e7ccdc511afe0bb2c3a2813a54","fees":"0.0001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RNTv4xTLLm26p3SvsQCBy9qNK7s1RgGYSB"],"from":["RNTv4xTLLm26p3SvsQCBy9qNK7s1RgGYSB"]},{"objectName":"","received":true,"blockheight":357201,"confirmations":63844,"timestamp":1586020706,"amount":"0.37499","amount_fiat":"0.00","date":" 4 Apr 2020, 05:18","tx_hash":"341858a39a0e57f1765521e2b2840c6fe779a0d775a6edb91b1c68c46aba554a","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bHYEEj2AzeEoPvZpzwo3n5jfHWyTmCUhPe"]},{"objectName":"","received":false,"blockheight":356946,"confirmations":64099,"timestamp":1586004722,"amount":"279.03349774","amount_fiat":"0.00","date":" 4 Apr 2020, 12:52","tx_hash":"b8ff7552401d2a1eff64ae1321e2b35e87cf9505274fb5b251e11f822f0a398d","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bboaQjQCSVfJGVoFFnK49vGLkvF9gbTwzH"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":356941,"confirmations":64104,"timestamp":1586004562,"amount":"0.35912645","amount_fiat":"0.00","date":" 4 Apr 2020, 12:49","tx_hash":"7707759b0716d32fda767b36e290559b7cebc2940f7ea708f1974bf5b5b2d623","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":true,"blockheight":343937,"confirmations":77108,"timestamp":1585222233,"amount":"208.46342442","amount_fiat":"0.00","date":"26 Mar 2020, 11:30","tx_hash":"6a16374c2a31d3f1aa8c2587ec436fbc91c6ccbf816a12acdf57306070130fd4","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bG6Xp2uwpSxcrE7LeLwXnSabic2EkvZQia"]},{"objectName":"","received":true,"blockheight":335804,"confirmations":85241,"timestamp":1584732739,"amount":"0.49999","amount_fiat":"0.00","date":"20 Mar 2020, 07:32","tx_hash":"83fb9272cb48b2478bcd5c8dbb4bca3ff3b5250c888b6f597a1d514195da263d","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bJtf8K1cKWiCtsRvxFEYjmT6wTcTeTgSVL"]},{"objectName":"","received":true,"blockheight":331071,"confirmations":89974,"timestamp":1584447523,"amount":"49.99999","amount_fiat":"0.00","date":"17 Mar 2020, 12:18","tx_hash":"474ce15044007421bf5113d8a1a015b1e4085870aed4160e3f68067c6adb931c","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bHZp9sHmztNHBmWS5Amo4qh7LFx3bQJ4AC"]},{"objectName":"","received":true,"blockheight":316896,"confirmations":104149,"timestamp":1583594325,"amount":"0.62499","amount_fiat":"0.00","date":" 7 Mar 2020, 03:18","tx_hash":"134d35e535068610a5f5360cefca1c6422177d270efdada6d0972b935d4d0b60","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bNzURkEHwW4g7k4L7frzVmiaqfDKJo5dLf"]},{"objectName":"","received":true,"blockheight":307294,"confirmations":113751,"timestamp":1583018395,"amount":"0.49999","amount_fiat":"0.00","date":"29 Feb 2020, 11:19","tx_hash":"e8f8f44e23b4e8384419292ab621c04ed0535694f35cb9daf6c93ff208decf02","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bHFNaKNSXiyUr1zkUckHRG4daJhLAkA7oA"]},{"objectName":"","received":true,"blockheight":306389,"confirmations":114656,"timestamp":1582964370,"amount":"2.49999","amount_fiat":"0.00","date":"29 Feb 2020, 08:19","tx_hash":"e9533b6978a6a6849ea1b3caf47c3ff8b4cb2888540ec94bdbcd0e0bb9ab9d15","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bDw7g7t3C88x35omywMBiWpKo1yj6RG7KZ"]},{"objectName":"","received":true,"blockheight":304422,"confirmations":116623,"timestamp":1582846832,"amount":"0.06249","amount_fiat":"0.00","date":"27 Feb 2020, 11:40","tx_hash":"35f7cdcd2aecb76ddebced7715984e47a58a8645f7b24ac0f725b3e550483651","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bKjnSYyy8wwRzqMGr8BNARzyhTeDu75TVz"]},{"objectName":"","received":true,"blockheight":304422,"confirmations":116623,"timestamp":1582846832,"amount":"0.12499","amount_fiat":"0.00","date":"27 Feb 2020, 11:40","tx_hash":"5d5a54ab990d7f148294dc8642ccc1f0d819a040bed839b1e272732d66833e2f","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bawH92Hj21A7Acvo26LMJx5Cyz3ueY6pmd"]},{"objectName":"","received":true,"blockheight":304413,"confirmations":116632,"timestamp":1582846278,"amount":"0.62499","amount_fiat":"0.00","date":"27 Feb 2020, 11:31","tx_hash":"d6af51e62cb1895ed600a9faa7dd56657aef1c20a62805e3b844d75269bc936b","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bbCWTuYD9jWzXpyrN9BKxc4cQU2mxBJ9UF"]},{"objectName":"","received":true,"blockheight":304413,"confirmations":116632,"timestamp":1582846278,"amount":"0.24999","amount_fiat":"0.00","date":"27 Feb 2020, 11:31","tx_hash":"425f61e5ca294a7a72d2ed5ca5f54affd815e4eeb3c2019a9cd5f2771cef55f4","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bK4jSHvbYd3uL5RkgK8fWpBGHoe1QA8v75"]},{"objectName":"","received":true,"blockheight":304397,"confirmations":116648,"timestamp":1582845399,"amount":"0.49999","amount_fiat":"0.00","date":"27 Feb 2020, 11:16","tx_hash":"84ded5ee097bbef0422ca4214f9950910c83fdd2d211fafbdd880c77a5b5e0cf","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bFRDEhKbe9gueQMmHWaDRsLvM5qa7X9ZDv"]},{"objectName":"","received":false,"blockheight":304382,"confirmations":116663,"timestamp":1582844385,"amount":"1.00001","amount_fiat":"0.00","date":"27 Feb 2020, 10:59","tx_hash":"47fb5b14a1b2495bb77355103b05a47d9e2e5a7072411172a26ef96ff4580e55","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bVNVciWhNMTDB4aRL4srB777FiK4h7ek8x"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":304379,"confirmations":116666,"timestamp":1582844180,"amount":"0.001297","amount_fiat":"0.00","date":"27 Feb 2020, 10:56","tx_hash":"a5b316bda3d728d62f74f4a0396aae74f1a861e41d942c703f4d393475c4a376","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":true,"blockheight":303443,"confirmations":117602,"timestamp":1582787612,"amount":"2.49999","amount_fiat":"0.00","date":"27 Feb 2020, 07:13","tx_hash":"4be80949386b9cbfc55d4966a9865624a669c8a45bd7a69ecc6cfc95ae71b502","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bCwHH5yf8hPhpp3VMk5dbHJGLGDbsgZKRN"]},{"objectName":"","received":false,"blockheight":298148,"confirmations":122897,"timestamp":1582469282,"amount":"3.00001","amount_fiat":"0.00","date":"23 Feb 2020, 02:48","tx_hash":"eba7d29b1bb5a12d8846e3b00b2814e6aa2e15b97981bb1e1b1adb086c99d666","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bQBrqg3CWKbovhtVQUn5tb5ZAb5DqXiSqA"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":298146,"confirmations":122899,"timestamp":1582469129,"amount":"0.003871","amount_fiat":"0.00","date":"23 Feb 2020, 02:45","tx_hash":"b89646118b9c5145df64dc9fa5428fe16cb4671a2923df81ae035c7051d67cf1","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":298146,"confirmations":122899,"timestamp":1582469129,"amount":"0.003871","amount_fiat":"0.00","date":"23 Feb 2020, 02:45","tx_hash":"a7496a75ae395ee5b41128c6043e80d6a805a01b1703269e711ddd69953e77a3","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":288292,"confirmations":132753,"timestamp":1581874648,"amount":"3.00001","amount_fiat":"0.00","date":"16 Feb 2020, 05:37","tx_hash":"18bd775377111f1ce0ac389946432b42cb78758a46c37a25259650b6c3dbe0fe","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RJ3E5myrLSpzjuQyNur2K9FYAUgEHP6eKQ"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":true,"blockheight":288287,"confirmations":132758,"timestamp":1581874414,"amount":"1","amount_fiat":"0.00","date":"16 Feb 2020, 05:33","tx_hash":"27ec66c6add7b6e26548ec541854b67e02bc59f0372701b67b24d2bc3a128adc","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RJ3E5myrLSpzjuQyNur2K9FYAUgEHP6eKQ"],"from":["RJ3E5myrLSpzjuQyNur2K9FYAUgEHP6eKQ"]},{"objectName":"","received":true,"blockheight":288275,"confirmations":132770,"timestamp":1581873750,"amount":"1","amount_fiat":"0.00","date":"16 Feb 2020, 05:22","tx_hash":"b6c5ebd07fad2dd10b4f3c3478891aa4f399ffd67dcaf49056392ba7b36c04a5","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RJ3E5myrLSpzjuQyNur2K9FYAUgEHP6eKQ"],"from":["RJ3E5myrLSpzjuQyNur2K9FYAUgEHP6eKQ"]},{"objectName":"","received":true,"blockheight":288274,"confirmations":132771,"timestamp":1581873708,"amount":"1","amount_fiat":"0.00","date":"16 Feb 2020, 05:21","tx_hash":"937f8a023258a1b3768e06ed6d5257d7a09c24eaac01e1b61524150665862a73","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RJ3E5myrLSpzjuQyNur2K9FYAUgEHP6eKQ"],"from":["RJ3E5myrLSpzjuQyNur2K9FYAUgEHP6eKQ"]},{"objectName":"","received":false,"blockheight":285252,"confirmations":135793,"timestamp":1581691530,"amount":"0.00011","amount_fiat":"0.00","date":"14 Feb 2020, 02:45","tx_hash":"5057fb39e6207f5b89fc8889dd2077429f7ab3d86dd2f1de8e7675c5223c2e63","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":285249,"confirmations":135796,"timestamp":1581691339,"amount":"0.00101","amount_fiat":"0.00","date":"14 Feb 2020, 02:42","tx_hash":"e5e1ce1f6332bfd4806b02dcb4270a5895f80fae9ddb3d68115e87a10c04ed4a","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bQkuUDJUkUfNgRCRNoqXyT1yv1stwbtY3u"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":285227,"confirmations":135818,"timestamp":1581689930,"amount":"0.00011","amount_fiat":"0.00","date":"14 Feb 2020, 02:18","tx_hash":"c2fb78e7e1984a45a70096609a3e4940f910c207aec23ee0219f74f24cc3f060","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":285227,"confirmations":135818,"timestamp":1581689930,"amount":"0.00101","amount_fiat":"0.00","date":"14 Feb 2020, 02:18","tx_hash":"9bd46537f6e193620be9ba678e0648ed3249ed1c779af6de80e572ad562aa007","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bMsWMCQ4Sus2u8Y8wjZjbm9UWdkjkzbv9V"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":285227,"confirmations":135818,"timestamp":1581689930,"amount":"0.00011","amount_fiat":"0.00","date":"14 Feb 2020, 02:18","tx_hash":"2bf355b296c78b8692b8b8ea0fd9d918a76043e3889acd4c9ce06850eddd95a2","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":true,"blockheight":285212,"confirmations":135833,"timestamp":1581689230,"amount":"0.02221","amount_fiat":"0.00","date":"14 Feb 2020, 02:07","tx_hash":"557705b66ffed4a6ac86cd1165107a1f96dd6cb6f31edf6b6d59a9fc8f25b134","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bVrbSk34QcH28fq1PadLUrkpooP71SywVu"]},{"objectName":"","received":true,"blockheight":285205,"confirmations":135840,"timestamp":1581688538,"amount":"0.00624","amount_fiat":"0.00","date":"14 Feb 2020, 01:55","tx_hash":"2f0c58dfc6e976078fe33e0a95032a9c5b95df1b59007867e3c5dd69e5b54210","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bXVsJekByxLnNChqnvzBnq1QgpchcaNutx"]},{"objectName":"","received":true,"blockheight":285088,"confirmations":135957,"timestamp":1581681499,"amount":"0.06249","amount_fiat":"0.00","date":"14 Feb 2020, 11:58","tx_hash":"ec17d50f2e324273d4469866257f9c192cdcbde798b8352c3b1a05f166ca623d","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bLnkLVmjAyiVBQEnMJk3ZW7wLMypVE5VKQ"]},{"objectName":"","received":true,"blockheight":284981,"confirmations":136064,"timestamp":1581675158,"amount":"0.49999","amount_fiat":"0.00","date":"14 Feb 2020, 10:12","tx_hash":"dd5dd8ce923235807f7a9bdfa9ff03ae80472796a2689d97dd1152568b51b81b","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bQAJGwrCiGCBYmF83XnXDdcJYbcFkAgqQH"]},{"objectName":"","received":false,"blockheight":283807,"confirmations":137238,"timestamp":1581604668,"amount":"0.12501","amount_fiat":"0.00","date":"13 Feb 2020, 02:37","tx_hash":"e53e7b287d27761465cfe21fb9aa422a16d4ac2d4058569c5da0c1c2d8fd1dd6","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bMHNnbAv8eBYBgg4biM1iCpcMcVaN9QSJL"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":283804,"confirmations":137241,"timestamp":1581604390,"amount":"0.00017087","amount_fiat":"0.00","date":"13 Feb 2020, 02:33","tx_hash":"102e639a82b4538075d5e8aa0fa0104e28078fd5b9e5d8a97248ca028c24c372","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":283789,"confirmations":137256,"timestamp":1581603467,"amount":"1.00001","amount_fiat":"0.00","date":"13 Feb 2020, 02:17","tx_hash":"1dbf70f85eaa0ca96f2546f8c5298ab86ae87fe09947932d9bd47d11d9b21755","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bWqLHAWr4k5nb4AdiqHLnfJxYFWEXr8o3A"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":283788,"confirmations":137257,"timestamp":1581603383,"amount":"0.001297","amount_fiat":"0.00","date":"13 Feb 2020, 02:16","tx_hash":"d1850e37f342d5c7c81b44f96755e368b8fc929ffd475355a0f9c5a9c6b13f09","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":283499,"confirmations":137546,"timestamp":1581586106,"amount":"0.77701","amount_fiat":"0.00","date":"13 Feb 2020, 09:28","tx_hash":"a0119225328c3f406d6f1d1abfea69c46d28951c4a87cec113fc90bd300f73c5","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bCpYBxxAYqUCikZEbRmvyoWnXDCswJKCQN"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":283490,"confirmations":137555,"timestamp":1581585571,"amount":"0.13371","amount_fiat":"0.00","date":"13 Feb 2020, 09:19","tx_hash":"eae60e81b21577136edd9a6c28f70603d7acd6d7331253dc24f993ec38fe98a5","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bX74z9w8ncS9zPtUsLVSLFPTJe8egWazH4"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":283489,"confirmations":137556,"timestamp":1581585475,"amount":"0.00018207","amount_fiat":"0.00","date":"13 Feb 2020, 09:17","tx_hash":"6dd019b060fe3ae0c86abfc49c260fe5790deb9d1b8367944b3d00c302592f3a","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":283489,"confirmations":137556,"timestamp":1581585475,"amount":"0.00100999","amount_fiat":"0.00","date":"13 Feb 2020, 09:17","tx_hash":"304523670584066e1606796dde22a6a13504d67b0c93bf7a1f293e7afd3a1b90","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":276716,"confirmations":144329,"timestamp":1581177854,"amount":"0.00001","amount_fiat":"0.00","date":" 8 Feb 2020, 04:04","tx_hash":"c9809eec563205a5da410a38a0602828a11b367e14da5c64966f5495deea09ad","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":false,"blockheight":276712,"confirmations":144333,"timestamp":1581177692,"amount":"0.00001","amount_fiat":"0.00","date":" 8 Feb 2020, 04:01","tx_hash":"2b2edd46bf40a4164ad009b13440a668a83b6ab3154ebe113b15440835e4b3e7","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"]},{"objectName":"","received":true,"blockheight":265534,"confirmations":155511,"timestamp":1580504908,"amount":"7.49999","amount_fiat":"0.00","date":"31 Jan 2020, 09:08","tx_hash":"23f575a9b93a8149eb87b7a86675485ba646ab34f510ec5a26d4fc6ceee19e46","fees":"0.00001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"from":["bJPAVNUbZw9jhy5W2xd2WDe4RDpD2QM9Gw"]},{"objectName":"","received":true,"blockheight":0,"confirmations":0,"timestamp":0,"amount":"7.777","amount_fiat":"0.00","date":" 1 Jan 1970, 12:00","tx_hash":"4dbf394b0a5bdaba8824c5ad4efb5e0533ac221e2adc949291c8654a1eec3e20","fees":"0.0001","to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RNTv4xTLLm26p3SvsQCBy9qNK7s1RgGYSB"],"from":["RNTv4xTLLm26p3SvsQCBy9qNK7s1RgGYSB"]}]}, + current_coin_info: {"objectName":"","is_claimable":false,"minimal_balance_for_asking_rewards":"0","ticker":"ETH","name":"Ethereum","paprika_id":"dash-dash","type":"ERC-20","balance":"0.009","address":"0x6A0DFcC3442aB5B2A252cE028aC9516Ecd29b9fB","fiat_amount":"2.04","explorer_url":"https://explorer.dash.org/","transactions":[{"objectName":"","received":true,"blockheight":9722988,"confirmations":645576,"timestamp":1584902263,"amount":"0.009","amount_fiat":"2.04","date":"22 Mar 2020, 09:37","tx_hash":"0x8140a94a5701167c88a015ec6e4dc26ef256335b81310c8a1946f175b06a2248","fees":"0.000168","to":["0x6A0DFcC3442aB5B2A252cE028aC9516Ecd29b9fB"],"from":["0x6c5CB1014e292624afDD0C36eBE3f1A870D12f7e"]}],"tx_state":"InProgress","transactions_left":0,"blocks_left":4414770,"tx_current_block":10368563}, get_coin_info: (ticker) => { const data = { "MORTY": { explorer_url: "https://morty.kmd.dev/" }, "RICK": { explorer_url: "https://rick.kmd.dev/" }, "KMD": { explorer_url: "https://kmdexplorer.io/" } } @@ -178,8 +244,9 @@ QtObject { case "BTC": return "0" case "KMD": return "5.555" case "CHIPS": return "0" + case "ETH": return "4.44" case "RICK": return "3.33" - case "MORTY": return "5.555" + case "MORTY": return "49538.555" } }, @@ -207,7 +274,7 @@ QtObject { }, - get_my_orders: () => { return {"BTC":{"objectName":"","taker_orders":[],"maker_orders":[]},"CHIPS":{"objectName":"","taker_orders":[],"maker_orders":[]},"ETH":{"objectName":"","taker_orders":[],"maker_orders":[]},"KMD":{"objectName":"","taker_orders":[],"maker_orders":[]},"MORTY":{"objectName":"","taker_orders":[],"maker_orders":[{"objectName":"","price":"","date":"2020-02-07 09:31:50.430","base":"MORTY","rel":"RICK","cancellable":true,"am_i_maker":true,"base_amount":"7","rel_amount":"861","uuid":"dc13abf1-3262-4dcb-9891-e04d4cb63d33"},{"objectName":"","price":"","date":"2020-02-07 09:32:03.124","base":"RICK","rel":"MORTY","cancellable":true,"am_i_maker":true,"base_amount":"10","rel_amount":"50","uuid":"7cab68d5-2de5-4c29-980c-1a72620f38bf"},{"objectName":"","price":"","date":"2020-02-07 09:32:22.688","base":"RICK","rel":"MORTY","cancellable":true,"am_i_maker":true,"base_amount":"3","rel_amount":"9","uuid":"db5059db-94da-4a1e-b2e1-cef603f796c5"}]},"RICK":{"objectName":"","taker_orders":[],"maker_orders":[{"objectName":"","price":"","date":"2020-02-07 09:31:50.430","base":"MORTY","rel":"RICK","cancellable":true,"am_i_maker":true,"base_amount":"7","rel_amount":"861","uuid":"dc13abf1-3262-4dcb-9891-e04d4cb63d33"},{"objectName":"","price":"","date":"2020-02-07 09:32:03.124","base":"RICK","rel":"MORTY","cancellable":true,"am_i_maker":true,"base_amount":"10","rel_amount":"50","uuid":"7cab68d5-2de5-4c29-980c-1a72620f38bf"},{"objectName":"","price":"","date":"2020-02-07 09:32:22.688","base":"RICK","rel":"MORTY","cancellable":true,"am_i_maker":true,"base_amount":"3","rel_amount":"9","uuid":"db5059db-94da-4a1e-b2e1-cef603f796c5"}]}} }, + get_fiat_from_amount: (ticker, amount) => { return (parseFloat(amount) * 157.213).toString() }, is_claiming_ready: (ticker) => { return true }, claim_rewards: (ticker) => { return {"objectName":"","has_error":false,"error_message":"","tx_hex":"0400008085202f8902755d841b4ecff907a7f49633af202e29363e3ed71fdc1918c5541648fc20ff36010000006a4730440220418adfffe0bc798af13d0241e6e45c4a2d4080a3dcdd90b497c31d155a52666c0220690ca5e11d9ddd681aae7a2eca137104701621961a5ca21bdb593c8cc6d20198012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff2de18d8aefe1967610ac76547ac70a56a724b25cb855e1451f63a327a3976107000000006b483045022100d49b5a5d6a5b9fcb368208cb1fb29eeed0fe864d6ef62d78e82f1fdc150d115d022078e119b15e63a7b1e5f927d8497d2269b91dddb42de5ea18d74662ecbd2558a7012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff0108365b3f000000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac8dcd3e5e000000000000000000000000000000","date":" 8. Feb 2020 03:15","balance_change":"0.00147216","fees":"0.00001","explorer_url":"https://kmdexplorer.io/"} }, @@ -218,19 +285,15 @@ QtObject { return "abcdefghijklmnopqrstuvwxyz" }, - get_recent_swaps: () => { - return {"0854e514-ef00-4073-bb16-e6d820a66588":{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"data":{"lock_duration":7800,"maker":"15d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732","maker_amount":"50.25","maker_coin":"MORTY","maker_coin_start_block":423909,"maker_payment_confirmations":1,"maker_payment_requires_nota":false,"maker_payment_wait":1590078619,"my_persistent_pub":"03f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5","started_at":1590075499,"taker_amount":"100.5","taker_coin":"RICK","taker_coin_start_block":421557,"taker_payment_confirmations":1,"taker_payment_lock":1590083299,"taker_payment_requires_nota":false,"uuid":"0854e514-ef00-4073-bb16-e6d820a66588"},"human_timestamp":"2020-05-21 15:38:19.680","started_at":1590075499,"state":"Started","time_difference_gui":"0.680s","timestamp":1590075499680},{"data":{"maker_payment_locktime":1590091099,"maker_pubkey":"0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732","secret_hash":"813ffe2714ecb2389158b04f6803bdff4f5df477"},"human_timestamp":"2020-05-21 15:39:20.271","started_at":1590075499680,"state":"Negotiated","time_difference_gui":"60.591s","timestamp":1590075560271},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"a50def46ee9940c0293f44cf0eb6c26ccf58be012fb7c69d4fd6bde1159dba12","my_balance_change":"-0.12935362","received_by_me":"0.87064638","spent_by_me":"1","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"total_amount":"1","tx_hash":"a50def46ee9940c0293f44cf0eb6c26ccf58be012fb7c69d4fd6bde1159dba12","tx_hex":"0400008085202f8901993605ab9a6036c7f114d1ff9520262f0d3f88193d036adb2a1a07e271265b89000000006a47304402202314817f5f79360f896295cbb8916eb7eeef884cfaf29aec2c990be1d43ba5f4022056ec99c330f399d0d20b9fd6e38e98dee5b71f25d9e8a4f9edd00fbdf8c375b7012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff02da5cc500000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac3e803005000000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88aca8a0c65e000000000000000000000000000000"},"human_timestamp":"2020-05-21 15:39:22.623","started_at":1590075560271,"state":"TakerFeeSent","time_difference_gui":"2.352s","timestamp":1590075562623},{"data":{"block_height":0,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD"],"internal_id":"a71b43309d4dd429c4918347753de154162046e971b49ab054db90a91115ed46","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":0,"to":["RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD","ba6FGDonu9LhjCwHtsKHLVhTLWF7q3uQTu"],"total_amount":"136.26708232","tx_hash":"a71b43309d4dd429c4918347753de154162046e971b49ab054db90a91115ed46","tx_hex":"0400008085202f8911488f351e40c84f3b413cd18df4b1fce020a70fe3c98e90d7b96ee39fe1104e5d010000006a47304402202c9cbf5829b0022161c7b0d7d2aaebef6d92157f0d207e46ca545c1730d96a2e022031939a0ed3db593d5dcb073aa785ec3ba13b17ff1877df480578397bc1075d7c01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff9a3dcdafc49c546546b246bb72b8dfb8f2edb94acee1abd7fa7056631969db44000000006a47304402206506f0fca900bac6c4608c00aeeff5720dd31ec8391ea17d2b52578a6bfaef730220599e4960e6f448d06b918ced80cca00580ee6bf3aef8c275ff26efc95a3a527a01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff3356b82c00c9045bd33bf4f3f0ef6279ed364e40f108f20bbd07b51b53c241f5000000006b4830450221009a134274dbae77cf728d2e680ca99666e6c1da139915e82462d684ab4e49155202206bff91de009f5eaeb7d51763d540364a5adf38e86a3febbc13d9934822ca12cc01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff52ac41dcc0c6a3faed6f0c2c732c9231db1d27885623587c2a774b4d1288f332000000006b483045022100eb46e50a61e518ffb88fba9eccaf6806e3e20c382283fffb2da4801dac2e5e8702202c79af7bedb48d9ebc3db50381eb25f30c52f5fa01b0d191f3698892931ed6ee01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffffc1680bf1b674ae6330f08a3651fa61537e67f877f5a85b06b477df8915837033000000006a47304402206ed8d8d9cfc16c094a65e89368d532d431b959096619360c0b2d02e1a970082f0220344f5a85c054454833c1297f95156058ecc2a40eb87c7abd4af94690d62acb4201210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffffd8ede1e923ff92faee0c6516016f0b700a069990c8db4b389d65226cd5c17e05000000006a473044022017adfe21cd6b30c84d421839e8bdc2ce9ef57603dc03638df745800556011d3802204f6cb52d920eb9776143812536adec844103b01aea4322a2644c9f1eef10bba401210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff6e6944bc7f255618188f4ecd6ae06e1350138912437e9695a27e6448122b359c000000006a47304402206b68d0e38e31dcd516631bd52c8a457bb550aaa153ca622ae0d131ade22ce92f022067279ab548099973a585a02ccc92eed607740e283c37ab8aa019504427b88a3401210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff5679c4a61def0b979720fcf66e9c2cac70fbc5195c12163b7c90994c2fdf0bbc000000006a47304402200e1754021ac3744160836db4ce0d994832e68e68b9d390fa3c06ff772d626ecb0220232d0bf01e632b05a88f87aec41b1ad8000bb92bb7324c244dec40f01e6d6b1301210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffffcebf49bd88e380551290c48bb608ed0bcf47f9d70a4a4ecab2d13f60eca1bdd6000000006b483045022100e7c383a6575409509b43e316a5d75f4b937a3853e549b9564a2c9bf025465ba1022023a59473a5fb462ac9462d6ec27b8fff29c25c46dbe126d085a45233a364e36501210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff4d2ab2e4ccc5de2bee021ae86eff6821ba0be1fc3c52c2e126fbd7946161a4b1000000006b483045022100c44eb0e686cb1adc60a7f95c738b399b62320741fee628efdfc1d2aaa5070b8d022049f792ab49570ca906981e562d1e3579fda2ca70afa42d73d459409705f711f901210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffffadce51b0a06a9069560125ebc4e14ab85ef0ab60266cc0a332f59f5ef4c1d820000000006b483045022100e9d3bbe3659159a042b761686721cda33fb81cc26f8e195ff2a1dec73bc28986022058fca12f3b0fde33c8ad61ec06907a9d7829801bce2e24d2b67287f77dff47e301210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff47301828d5e4e5685e2c95e8270eac119471622401a6b748db8d09b3b80c889a000000006a47304402202b513a38047b13c81b5aee6dce58779e7630676bb93e40762a4b6c4265efc3bb022069c1cfed54ebe0284fb0ef9d6bebdd04c1ded916dbce3c30f64e5c6594e6300301210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff03f12cc9e7b40136856fd1649dba09c21a331a4960f8bbaee1f3f425d92a9080000000006a47304402207ba9281b82701a6b246cdb04cda886e01791aaa92ea476d4c078dcbe9deff09d022015a094cef7e81fcfb4c1bb2ecd516a1612ae1354b6de7ded243504ff62b0e41d01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff031ed4d304bb23c4392ab6342bc5b196c4d5f9397ac27fbcfe9047ce4fb2cf14000000006b4830450221009af6f0356f31cca76b5f61a2fe191bea21f13229b547fb25f9829bca9d53febd0220297c536965b33be5b78710064db0acba044eeb655c5b4e21b65cde05d1ebfbfd01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732fffffffffe7a974c6bcaff5074bd78349846c0562a58a5e4bf4717f166628f721acf3fbd000000006b483045022100ef69a94bb53fd59d2ded7d55db45e59b553eab81fb259fd07661c2e580e0264602203dc1b92d5912096a15d6ec6b0588c4c9a9f574852344974c65d2618c61fa29fd01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff43d6a2049725c73e0beec045387393d04cd407a8a071a9530cd783ea9ef3f022010000006b483045022100a164312c8a24e2e828617eec614c43b2be2a97f1e880862dd31c87e18438afed022070dbf26102663bf379f939c83ec421060181e9ea60a974b96b1f15cc9a0c1d9e01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff5c88915ac0b6332523cab1a71279f65978c77735f185f11c247628f7724b08d2000000006b483045022100fc38850f7de19ab3a27c8731d7e89f292e46184f9a3010401160d3581d46511d022019ccde0cde328dd1986fd6380104e017306f2d76cfd0de4a49fa65184115b51c01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff02406a832b0100000017a914ea522e94cf0cf54b3e21a9ba920e262ed4d4f04587e0a2b300020000001976a9141462c3dd3f936d595c9af55978003b27c250441f88acbda0c65e000000000000000000000000000000"},"human_timestamp":"2020-05-21 15:40:19.208","started_at":1590075562623,"state":"MakerPaymentReceived","time_difference_gui":"56.585s","timestamp":1590075619208},{"human_timestamp":"2020-05-21 15:40:19.209","started_at":1590075619208,"state":"MakerPaymentWaitConfirmStarted","time_difference_gui":"0.001s","timestamp":1590075619209},{"human_timestamp":"2020-05-21 15:42:06.579","started_at":1590075619209,"state":"MakerPaymentValidatedAndConfirmed","time_difference_gui":"107.370s","timestamp":1590075726579},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"6c206a9cabb8fcf306a3389eaa558b405a503bf7d53bee8010c577bd71a0aa81","my_balance_change":"-100.50001","received_by_me":"61.71442846","spent_by_me":"162.21443846","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","baRqBfKc41BVjwqRnSN1KHhiJeZraLYY12"],"total_amount":"162.21443846","tx_hash":"6c206a9cabb8fcf306a3389eaa558b405a503bf7d53bee8010c577bd71a0aa81","tx_hex":"0400008085202f890812ba9d15e1bdd64f9dc6b72f01be58cf6cc2b60ecf443f29c04099ee46ef0da5010000006a4730440220540d832aa5b09001eac579af4acf7a6a342c5f63236f8f668cf05c5ee4ddb058022006fa3591b07d4277f8bab0d09ef1706392173b78fff08addf8125fa17a6607e5012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffffb71fbd45a36f351b7113f46c6b7b05d451b5ca2863914848a97abcc8b5e9d65e000000006b483045022100d1a1cc4c35e35731bced51b80022d3846065a59cc5592d49db89bf1a317693f40220735841354740a71ae12d044f30c162cd1e75cae24ff5e26587d04505748cf3d1012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff993605ab9a6036c7f114d1ff9520262f0d3f88193d036adb2a1a07e271265b89010000006a47304402206fb81c7b813bfdd72eb55a86e7a9ae20d85f94ff4e2a38184434a7148105257602204a03ef7c6813993eb9c744d66d307ac1eb21eb140540a554d5527e6b153d59c2012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffffb71fbd45a36f351b7113f46c6b7b05d451b5ca2863914848a97abcc8b5e9d65e010000006b483045022100fadd344c27c60af1f1ee1f74c8d2d29292e6fef8ff7a7627bcad7bbb7e8d75ea0220752c73669576f7caa1227f518ac697082b5366c26914131bb1784d1688563d9b012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff993605ab9a6036c7f114d1ff9520262f0d3f88193d036adb2a1a07e271265b89020000006b483045022100dfaa83cdcdcfd2d7f44432a6b46d4e5abd5518faf339d429fc4593b2cf905f5e0220556633e6868cff329ac16f2f4ca9326da604c11798f30ec4adadd32371cc8a80012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffffb71fbd45a36f351b7113f46c6b7b05d451b5ca2863914848a97abcc8b5e9d65e020000006a473044022022714a8431936d76f795ba97857ac2d8d758fd09f8c7148517117dba567c493202207a6dba7418cf0370a38a6923b9a0cd73bf2f07ff92c7a8c04756642d78cfce90012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffffc024b694eaba5f8f29d8d4b15283e2606c3d04ecfc2efa6ff6c721a43c9241c0000000006b4830450221008b2a9efe94815b749edd14d6a1dc3ca6acfbe8d659323b2904499fe7fec8e8c0022046c0c2aabfe00388f4adfc84726e080e84a285f1789265aaea55da4631dacfc8012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff7ebe73a3540c7b057e991d1e3917dabdf6c9450b18bc2a75a628fd2529f054d0010000006a473044022004e7459f41e7927badc5c2a21fb9994b702e09778c326ab8da6b565eee63328d02202cf931eb91419716dd982b457d94ddae5a3538baf9104bb80080d8133f17ebc2012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff0280d406570200000017a914ee0666c0529960338aa9276ea81df7b70e28a25f879ebed86f010000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac4ea1c65e000000000000000000000000000000"},"human_timestamp":"2020-05-21 15:42:08.183","started_at":1590075726579,"state":"TakerPaymentSent","time_difference_gui":"1.604s","timestamp":1590075728183},{"data":{"secret":"b20bf01ac148fd1f400bc8231a7c77bcae02fdc4152f8f683a8ce6f3e1033edd","transaction":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["baRqBfKc41BVjwqRnSN1KHhiJeZraLYY12"],"internal_id":"ac5d3faf670995f9a66956e4391626e48ce602d5108fd1e70df87b49d9f0cf3e","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":0,"to":["RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD"],"total_amount":"100.5","tx_hash":"ac5d3faf670995f9a66956e4391626e48ce602d5108fd1e70df87b49d9f0cf3e","tx_hex":"0400008085202f890181aaa071bd77c51080ee3bd5f73b505a408b55aa9e38a306f3fcb8ab9c6a206c00000000d8483045022100e7c5702a4ecc108175bc414b9f0f188e4093cdcef5fa9430fd0c0f3f846085af02206bc29ec31fa468932de89dfe63aa9bdcb8ff5917db072ab3d6d2e42931468e3d0120b20bf01ac148fd1f400bc8231a7c77bcae02fdc4152f8f683a8ce6f3e1033edd004c6b6304e3bec65eb1752103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ac6782012088a914813ffe2714ecb2389158b04f6803bdff4f5df47788210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ac68ffffffff0198d00657020000001976a9141462c3dd3f936d595c9af55978003b27c250441f88ac9d93c65e000000000000000000000000000000"}},"human_timestamp":"2020-05-21 15:43:49.013","started_at":1590075728183,"state":"TakerPaymentSpent","time_difference_gui":"100.830s","timestamp":1590075829013},{"data":{"block_height":0,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["ba6FGDonu9LhjCwHtsKHLVhTLWF7q3uQTu"],"internal_id":"00d321febcd67e46d85d5b541ae4f58c0617155532ca4215fdaf188af15c0f2d","my_balance_change":"50.24999","received_by_me":"50.24999","spent_by_me":"0","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"total_amount":"50.25","tx_hash":"00d321febcd67e46d85d5b541ae4f58c0617155532ca4215fdaf188af15c0f2d","tx_hex":"0400008085202f890146ed1511a990db54b09ab471e946201654e13d75478391c429d44d9d30431ba700000000d74730440220511edd5571b0b0e227f5d60986a9dfe23824cf98284be7d6f75c2c97015683de022002529f94abd7876ba5b23e8900a65f87171ab5f1303fd7bb20af9dbf87ce763c0120b20bf01ac148fd1f400bc8231a7c77bcae02fdc4152f8f683a8ce6f3e1033edd004c6b63045bddc65eb175210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ac6782012088a914813ffe2714ecb2389158b04f6803bdff4f5df477882103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ac68ffffffff015866832b010000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88aca593c65e000000000000000000000000000000"},"human_timestamp":"2020-05-21 15:43:50.526","started_at":1590075829013,"state":"MakerPaymentSpent","time_difference_gui":"1.513s","timestamp":1590075830526},{"human_timestamp":"2020-05-21 15:43:50.527","started_at":1590075830526,"state":"Finished","time_difference_gui":"0.001s","timestamp":1590075830527}],"is_recoverable":false,"maker_amount":"50.25","maker_coin":"MORTY","my_info":{"my_amount":"100.5","my_coin":"RICK","other_amount":"50.25","other_coin":"MORTY","started_at":1590075499},"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"taker_amount":"100.5","taker_coin":"RICK","total_time_in_seconds":"331.527s","type":"Taker","uuid":"0854e514-ef00-4073-bb16-e6d820a66588","is_recent_swap":true,"am_i_maker":false},"10f3e602-27e8-40cf-a29f-3fda7a4738a8":{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"data":{"lock_duration":7800,"maker":"15d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732","maker_amount":"50.25","maker_coin":"RICK","maker_coin_start_block":422627,"maker_payment_confirmations":1,"maker_payment_requires_nota":false,"maker_payment_wait":1590142616,"my_persistent_pub":"03f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5","started_at":1590139496,"taker_amount":"100.5","taker_coin":"MORTY","taker_coin_start_block":425001,"taker_payment_confirmations":1,"taker_payment_lock":1590147296,"taker_payment_requires_nota":false,"uuid":"10f3e602-27e8-40cf-a29f-3fda7a4738a8"},"human_timestamp":"2020-05-22 09:24:56.941","started_at":1590139496,"state":"Started","time_difference_gui":"0.941s","timestamp":1590139496941},{"data":{"maker_payment_locktime":1590155096,"maker_pubkey":"0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732","secret_hash":"1a9e2a538446c70c47d3ddc44e0a7234378d4c51"},"human_timestamp":"2020-05-22 09:25:57.778","started_at":1590139496941,"state":"Negotiated","time_difference_gui":"60.837s","timestamp":1590139557778},{"data":{"block_height":0,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"5769f561b0d9f6aade3c2abc87cbd8226a5713b78e2dda8df95de993764f4fb9","my_balance_change":"-0.12935362","received_by_me":"0.88664638","spent_by_me":"1.016","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"total_amount":"1.016","tx_hash":"5769f561b0d9f6aade3c2abc87cbd8226a5713b78e2dda8df95de993764f4fb9","tx_hex":"0400008085202f8902bd0ab1ab27cbc16613430bff807a7080759d1c57a621034998ad1f68fb4b913f010000006a47304402203ba92a8fdc3456656e6485d62fe56b6ae5d8bb0cab3e1908b77c4646ebdef5c302200c575783ed8ab0a51bf36f6d75448e9b75fc5e9ba7e381b693f23246ac041d4d012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff203eec1e4a65c8919294dc2a1e22ac33055efb4eadc52488bada5b0a4b39bf4d000000006a473044022021dac790e347e757c6411015fcf2d1273be2d6e83430887de3ddddacdce2d45a0220236872c6215dff4e3cce437d873dfb3469a2008930e5072bccd3a81fee69f443012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff02da5cc500000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac3eea4805000000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88aca59ac75e000000000000000000000000000000"},"human_timestamp":"2020-05-22 09:26:02.570","started_at":1590139557778,"state":"TakerFeeSent","time_difference_gui":"4.792s","timestamp":1590139562570},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD"],"internal_id":"e9d5f08c66a51ae98c58fc3a1891d6db9c1d0a8f66f82dead9bc25e45cf8a9d6","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":0,"to":["RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD","bQ22zo6DUrgpnMWeBuYE8W7gQSBzQji7Dy"],"total_amount":"71.7738767","tx_hash":"e9d5f08c66a51ae98c58fc3a1891d6db9c1d0a8f66f82dead9bc25e45cf8a9d6","tx_hex":"0400008085202f890e66e90cbde56dc7bb05ca0bbf5570b0f54948f440630e81889ea03a3715e79b8c010000006b48304502210090b61a6d0bb6775a23c7e6f364057a68a778352f9e70b917468423aaede4cbd602205349bff56d9a05a5f573cb02116cfb7290ee5947cf877d7e70e7299de8d180da01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff29da99503c7a5d41f6ac2d0104ff94879c7883b1cd889f280685b35b1c6b2643000000006a473044022007751020a7ba0909386a332cc05ae6af2a9b8f780999b34a871e13b6cea09864022047bba40cb5df45bbd568b0dc772e8cdfb66aef3002d83e17a003abb39ed44fb901210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff00e9cfd9296e435673e2510116ae2f0cbb7961be5ceba807fbf4d6f1596474e4000000006b483045022100d30169a4375a5ede60f629907d2e29e9a6f8e744c81252b5c07eaad0531a51180220519defa0a549c63c966a3c0464cd658640c72c8ea1520b9e8b942795f08589e701210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff4ff91809104c647cc151ef8d6beb3d8019aa98ae3911edf2ec64b0fd91e8d62d000000006b483045022100dc07400a37c535ea4ab34a82d59549a624058c90e3038380f55f1923e396745b0220588bd3b5e2421187957ecff518a59076c36c91c07a4c94609a28ed1530f08c9501210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff185f5ee88bde1075814f3e42ccf76b61be96ecc8ac90e882dc7c37fa8ff6be24000000006b48304502210083554baea6dce2eda3cb2b1a5daeacd6caa0c8425d056553e4d5d7f5e726d8980220636544157e6598aee8f017a7131e3eed6aa4ed282cd1f436be5672fed2b3760601210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732fffffffff2c73dfe7a853fe54bcbf89fe90c70c86c62fb01f084f294d87e3582554d0036000000006b48304502210084425e8aaee03ab035e1aa3f03f739441291bbbf1dacfe063ff912f7d28295a2022028c09a935e27bd56fa6c9dc3a3a0fc012ac8ee6b5b07d25c5609361f0185577101210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732fffffffffc81b6b54305c213ede3fd0e98ea1e0c2832ef86e6dbc7f30874133db03cded2000000006b483045022100ac1e0391847462877b042c210bb694dbb1a8d9af0b5f79f4c9b21bd9d309c7e10220545965d82cbf5a9ca5a47acefde6c57d29f4474b7db9bf20e3c9899cccf98fb001210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff15f7cdb1a6895fb4540d111c34841c9d01a0248258c022432b00a8f292c7b9c6000000006a473044022073eeb606d93927dc3f7fe776390585805f2c914df2f9611aabfaeb13361d141f022001c65421ce99d12274a0135fcaf5c89008bdf9ee0b3134aad80bc158731333c601210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff2302421765d9e0103a80410a005f0d03dffe4437614b6b8acb0f3d196aec1fb6000000006b4830450221008c8db83c4f3acfd02684fe3e3bb45464ad49a4d2fffd5f24ed9a1fdeea3daa4602207d93de1531e5171219c3efdcf9fef54a9e7d8d07a8be15f36031fbdb7214f35001210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff29d9b5b0f3c9676e28a2cea832ba09b724df282f472693ce1c22f2319c79cd90000000006a473044022010dcdcbfc3b366f50a2efddf68e5379d9b93c61c83481602e3d54bd59f7ad6cb022074cf92557ddf2dbfecd03f35e71b3f80fd60153cccd4c7b86d7d571693bfe70501210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffffeb5d6883748882221c1d45549c8cd9a9c5773fe5c8ee07f23ad8259cf2d9ae45000000006b483045022100aa5a5b7e8e6b450c3bf73a7259c4fd20c3ed271877decd0da0b04b6de8484abe022022cb21f43cc0a6f7bf353cb1a2855ebc6bb38aece76656705661177fe466233501210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff93bc22a459b57522225e7e4bff61e77901ea0a0517eab2f248d809db7e721507000000006b48304502210092a1ffe2844aaca0b838e1122a8696b627e6e95e052a5ac4352b767ca0c9140b02203ccb366363d5aa3b70e3f13a0eeee6236cb9ef3a3f3692acee1c09a3877d7aed01210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff6c835e75105e6263b9714a603cac53d82b075e2148682f98a24617a7a9f8a71f010000006a47304402207f7979b426c9dcec14697894b1cfb946f999a065bf2c7da8e6b94b59e4e404d402203bd7b70c9c442e67a5aaa7d681bf927343851cb7ef460a570e6bbe96b4e5949401210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffffb4d4a717fd72245ce79c0864bc24ced4cb96f18b5c403b1fcee0ee206971e04d000000006b483045022100c9cff3f27f59e51cb3f58f9ef8fdc5dd8a6c931fe1c278914fdeae1b171e058202203f7706a64a3d2e89a8d385b993e1bd06d25dc084148ccb08a318d6d3c240a0d701210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff02406a832b0100000017a9147bd5056aa71a0343411567b523683a61890b306e876ed04a80000000001976a9141462c3dd3f936d595c9af55978003b27c250441f88acbc9ac75e000000000000000000000000000000"},"human_timestamp":"2020-05-22 09:26:44.999","started_at":1590139562570,"state":"MakerPaymentReceived","time_difference_gui":"42.429s","timestamp":1590139604999},{"human_timestamp":"2020-05-22 09:26:45.000","started_at":1590139604999,"state":"MakerPaymentWaitConfirmStarted","time_difference_gui":"0.001s","timestamp":1590139605000},{"human_timestamp":"2020-05-22 09:28:46.120","started_at":1590139605000,"state":"MakerPaymentValidatedAndConfirmed","time_difference_gui":"121.120s","timestamp":1590139726120},{"data":{"block_height":0,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"dc0e82fcccfab1b37c3c5dcf9ea65efd2418231142a16843f4d3fd7b77430407","my_balance_change":"-100.50001","received_by_me":"49975.46761638","spent_by_me":"50075.96762638","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bYvGeVL7rXpaw6jygRP1P8LsAy626TfZKC"],"total_amount":"50075.96762638","tx_hash":"dc0e82fcccfab1b37c3c5dcf9ea65efd2418231142a16843f4d3fd7b77430407","tx_hex":"0400008085202f890cb94f4f7693e95df98dda2d8eb713576a22d8cb87bc2a3cdeaaf6d9b061f56957010000006b483045022100aca6a2e47906f0c705bfe04bc0981d32a4e55d21e93138e9cdefd3673ca234a8022067e35370e5784b0c9b5bbe72e45d8121ad9190bb9ee5e221aea5ca43ebda37db012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff37359f82787aeef3a428041292ddd32a032b29f01edb2c2c991dec4b774a8937000000006b483045022100baeb25ac356cc93f7710a52fa85a321a27c402d7fb99ae1a18879ae6365ad3c2022038f46897b9df3458c3d3047c23f369bceed4ca7864103eea334e422b66e61dd4012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff5604c80ff1f240d5c5bfc0c04f9dfec298f59d283dfa071d3bfa7ecd977c2142000000006b4830450221009a3a616ce57d79b9c6965d85386e63c7b55e843df7dd4ae59fde78361e4d82200220172f9c12e94c444fffe3853975342d43528a9c3bbae321a8a386f91dad05e26e012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff203eec1e4a65c8919294dc2a1e22ac33055efb4eadc52488bada5b0a4b39bf4d010000006a47304402201c8862ab238ec406f53e1150689a5f5223ea58b9330cb272f94eb973307e8923022066ddc5d061ac07964aa9a8cd68affd77fa3c9549f1841819d6e1870767ddb2e4012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff37359f82787aeef3a428041292ddd32a032b29f01edb2c2c991dec4b774a8937010000006a4730440220505b82b59a1dd0d2409da3a1723c6b21c7105f94a6b9a3cd7fde3f8bcbf1c70e022020a63d261ca8596db7c5f4ef1b44635bb0bea42ca343369c92a7db9dc81ded4a012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff5604c80ff1f240d5c5bfc0c04f9dfec298f59d283dfa071d3bfa7ecd977c2142010000006b483045022100a3f128c363b9357942350ec71f3a6b93fe7ae97516b7813f97ac7ad677445dd402205a265145a2ee32c54786bf51d66e1210ce281f2a3e38f8b850648791d93c7a72012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffffbc806f88833ee8cf758a6d52d7d80d1b461aa6a735dfd1ff220fe5997ea34f11000000006a47304402205e902d8867e8105b7df99e2ca5610b0d977c4c0eb1da5701214c1f067c26caf802200b3569c4792c5eaadfc93d70a72a2f8a27553e6320c5021bcebf238ca6b0967f012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff203eec1e4a65c8919294dc2a1e22ac33055efb4eadc52488bada5b0a4b39bf4d020000006b483045022100c1bc2751e0373e84e03485b0588bc5589c6324b4f01f5221c91eeb6cadce33fa02207a1b6f2e20507594ad53cd38a1325295688dec694983a9ef2260578a0dd010ee012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff37359f82787aeef3a428041292ddd32a032b29f01edb2c2c991dec4b774a8937020000006a4730440220799b26c3ad94e63dfd55c01231b040a754716d888560a5d1a2aa545b4ad7ffdf022042f415c8ef18102ec496023e805c75472e0ddf8d3ad81f2e567493ef93ce7acd012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff5604c80ff1f240d5c5bfc0c04f9dfec298f59d283dfa071d3bfa7ecd977c2142020000006b483045022100f4bf8350a2a4d29fc6d42f504f11c6ec24f9ee0011ca7059e77f1762e255668e022018cea1bab626cbc320d4e9fea62656f219d94fffc0641bc5ba32954e2224fdb9012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff2d0f5cf18a18affd1542ca32551517068cf5e41a545b5dd8467ed6bcfe21d300000000006a47304402201765e90326f92e2f71bd2292e5dc2103c45d55cd9b8003b493c392a5d838372e02202481c192bcd61a1d1c48e243ae9c6b48aa7826c45e5cf0cf189de5acbc6bba21012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff1d501b53f13540d85b6377a947345d24303b0b0a47fa70b165120bd577c0ae07010000006a47304402202b0983d1905335781bb9fd2eff31a2e6e55eca174c1fa9d6c1bdd72097a769640220325da12dbea41a395ef1a930a0013643d3b2e4529d70d53f62f31024606caab2012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff0280d406570200000017a914dd770d42ada2ca2d7be1068bcdc0a60a47ec3ce687a6ddff948b0400001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac4e9bc75e000000000000000000000000000000"},"human_timestamp":"2020-05-22 09:28:48.869","started_at":1590139726120,"state":"TakerPaymentSent","time_difference_gui":"2.749s","timestamp":1590139728869},{"data":{"secret":"015e9c8973f7da9f98c05f74dd132a3e4cd58f9311734841dfbb1ff48ce79a9a","transaction":{"block_height":0,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["bYvGeVL7rXpaw6jygRP1P8LsAy626TfZKC"],"internal_id":"00f781781f9b92c772b3452fe6b522af5c29bf1dabc0bcdbe955ad726fe1e337","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":0,"to":["RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD"],"total_amount":"100.5","tx_hash":"00f781781f9b92c772b3452fe6b522af5c29bf1dabc0bcdbe955ad726fe1e337","tx_hex":"0400008085202f8901070443777bfdd3f44368a14211231824fd5ea69ecf5d3c7cb3b1faccfc820edc00000000d7473044022043428a4d30b24c27faddf95b3d3f221c547ffa6c490401e85cb7247d404f6a090220484219cfd0f98a370f076fd81e6b4799cd1bd4e6ff97e9e5f62e8c1858e4f5810120015e9c8973f7da9f98c05f74dd132a3e4cd58f9311734841dfbb1ff48ce79a9a004c6b6304e0b8c75eb1752103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ac6782012088a9141a9e2a538446c70c47d3ddc44e0a7234378d4c5188210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ac68ffffffff0198d00657020000001976a9141462c3dd3f936d595c9af55978003b27c250441f88ac658dc75e000000000000000000000000000000"}},"human_timestamp":"2020-05-22 09:29:34.427","started_at":1590139728869,"state":"TakerPaymentSpent","time_difference_gui":"45.558s","timestamp":1590139774427},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["bQ22zo6DUrgpnMWeBuYE8W7gQSBzQji7Dy"],"internal_id":"6f1bba09d51cfb1dd314c617a0b946471e5bdb54c200891b963724b420bad5c4","my_balance_change":"50.24999","received_by_me":"50.24999","spent_by_me":"0","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"total_amount":"50.25","tx_hash":"6f1bba09d51cfb1dd314c617a0b946471e5bdb54c200891b963724b420bad5c4","tx_hex":"0400008085202f8901d6a9f85ce425bcd9ea2df8668f0a1d9cdbd691183afc588ce91aa5668cf0d5e900000000d8483045022100b539ca2fd2e856f3cef57075915794674150ce21ac7c887b22004907225a05dc0220730a8e78e6a2390b1e57e0cf0a84cbf356e0e65acf44f746b78b2e390e3bceea0120015e9c8973f7da9f98c05f74dd132a3e4cd58f9311734841dfbb1ff48ce79a9a004c6b630458d7c75eb175210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ac6782012088a9141a9e2a538446c70c47d3ddc44e0a7234378d4c51882103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ac68ffffffff015866832b010000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac6e8dc75e000000000000000000000000000000"},"human_timestamp":"2020-05-22 09:29:35.677","started_at":1590139774427,"state":"MakerPaymentSpent","time_difference_gui":"1.250s","timestamp":1590139775677},{"human_timestamp":"2020-05-22 09:29:35.678","started_at":1590139775677,"state":"Finished","time_difference_gui":"0.001s","timestamp":1590139775678}],"is_recoverable":false,"maker_amount":"50.25","maker_coin":"RICK","my_info":{"my_amount":"100.5","my_coin":"MORTY","other_amount":"50.25","other_coin":"RICK","started_at":1590139496},"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"taker_amount":"100.5","taker_coin":"MORTY","total_time_in_seconds":"279.678s","type":"Taker","uuid":"10f3e602-27e8-40cf-a29f-3fda7a4738a8","is_recent_swap":true,"am_i_maker":false},"24c3b70b-cab9-490c-8edb-d95fcf198e7d":{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"data":{"lock_duration":7800,"maker":"683c77e807a47dcd559fa60a6510087e5c5aa0016c094cf5eb4d7e002db18e9f","maker_amount":"0.03306666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667","maker_coin":"DEX","maker_coin_start_block":1132583,"maker_payment_confirmations":2,"maker_payment_requires_nota":true,"maker_payment_wait":1590104402,"my_persistent_pub":"03f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5","started_at":1590101282,"taker_amount":"0.496","taker_coin":"KMD","taker_coin_start_block":1887916,"taker_payment_confirmations":2,"taker_payment_lock":1590109082,"taker_payment_requires_nota":true,"uuid":"24c3b70b-cab9-490c-8edb-d95fcf198e7d"},"human_timestamp":"2020-05-21 22:48:02.551","started_at":1590101282,"state":"Started","time_difference_gui":"0.551s","timestamp":1590101282551},{"data":{"maker_payment_locktime":1590116883,"maker_pubkey":"03683c77e807a47dcd559fa60a6510087e5c5aa0016c094cf5eb4d7e002db18e9f","secret_hash":"3c79aff4e91435d57241dd9702813fe6d1f63108"},"human_timestamp":"2020-05-21 22:49:03.769","started_at":1590101282551,"state":"Negotiated","time_difference_gui":"61.218s","timestamp":1590101343769},{"data":{"block_height":0,"coin":"KMD","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"9f0d5aa87e4155d45f4852560d7f61d136bd1a8a74c74c3353335ced19e5966a","my_balance_change":"-0.00058451","received_by_me":"2.49703889","spent_by_me":"2.4976234","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"total_amount":"2.4976234","tx_hash":"9f0d5aa87e4155d45f4852560d7f61d136bd1a8a74c74c3353335ced19e5966a","tx_hex":"0400008085202f8901ae23b8bc9466c8cd9c243613e52c46698f59676deca140d93c70f5416825f416010000006b483045022100f4fd9d5d4c1f63b3d30a124c0da28d377499c2d7bf57f6cdf3b7473e048acaf50220292440797f6f1c327e08fb0ca4ee6fd0eb8e037ea144bd7a315bceafc45c72da012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff026be00000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88acd12de20e000000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac61fdc65e000000000000000000000000000000"},"human_timestamp":"2020-05-21 22:49:07.242","started_at":1590101343769,"state":"TakerFeeSent","time_difference_gui":"3.473s","timestamp":1590101347242},{"data":{"block_height":1132584,"coin":"DEX","fee_details":{"amount":"0.00001"},"from":["RQq3CmCy7DzwSbT1RPZDfVPcC3RAjyR8DJ"],"internal_id":"8b324f60d68cdc8c5f75772a0924998b0c0f8bbd4404612bf918ecab76e39b2d","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":1590101376,"to":["RQq3CmCy7DzwSbT1RPZDfVPcC3RAjyR8DJ","bPGfDubsFzzQ5jctEfsqzhwJBdiwUTLruM"],"total_amount":"5.70198389","tx_hash":"8b324f60d68cdc8c5f75772a0924998b0c0f8bbd4404612bf918ecab76e39b2d","tx_hex":"0400008085202f8901ffb0f08d39c631989dbfa53558cfa4a457b14578c1c843d54780b6ded255c71c010000006a4730440220484651b2f34040346a45946ddf0e540184fab7d4697c30f9f52a6fd1344341f502202b08dc6b734a65656babae85cf1383835c6d082e6e6e6cbbc3fb749b55b4e3d1012103683c77e807a47dcd559fa60a6510087e5c5aa0016c094cf5eb4d7e002db18e9fffffffff02aa7432000000000017a91473a0f576bf4e51f506faedd668c155e955c7a12087e310ca21000000001976a914aa8fc9fcbf345510d419ca273a6e9897b488ee1688ac7705c75e000000000000000000000000000000"},"human_timestamp":"2020-05-21 22:49:47.934","started_at":1590101347242,"state":"MakerPaymentReceived","time_difference_gui":"40.692s","timestamp":1590101387934},{"human_timestamp":"2020-05-21 22:49:47.935","started_at":1590101387934,"state":"MakerPaymentWaitConfirmStarted","time_difference_gui":"0.001s","timestamp":1590101387935},{"data":{"error":"taker_swap:825] !wait for maker payment confirmations: rpc_clients:108] Waited too long until 1590104402 for transaction Transaction { version: 4, n_time: None, overwintered: true, version_group_id: 2301567109, inputs: [TransactionInput { previous_output: OutPoint { hash: ffb0f08d39c631989dbfa53558cfa4a457b14578c1c843d54780b6ded255c71c, index: 1 }, script_sig: 4730440220484651b2f34040346a45946ddf0e540184fab7d4697c30f9f52a6fd1344341f502202b08dc6b734a65656babae85cf1383835c6d082e6e6e6cbbc3fb749b55b4e3d1012103683c77e807a47dcd559fa60a6510087e5c5aa0016c094cf5eb4d7e002db18e9f, sequence: 4294967295, script_witness: [] }], outputs: [TransactionOutput { value: 3306666, script_pubkey: a91473a0f576bf4e51f506faedd668c155e955c7a12087 }, TransactionOutput { value: 566890723, script_pubkey: 76a914aa8fc9fcbf345510d419ca273a6e9897b488ee1688ac }], lock_time: 1590101367, expiry_height: 0, shielded_spends: [], shielded_outputs: [], join_splits: [], value_balance: 0, join_split_pubkey: 0000000000000000000000000000000000000000000000000000000000000000, join_split_sig: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, binding_sig: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, zcash: true, str_d_zeel: None } to be confirmed 2 times"},"human_timestamp":"2020-05-22 09:14:29.695","state":"MakerPaymentWaitConfirmFailed","timestamp":1590138869695},{"human_timestamp":"2020-05-22 09:14:29.696","state":"Finished","timestamp":1590138869696}],"is_recoverable":false,"maker_amount":"0.03306667","maker_coin":"DEX","my_info":{"my_amount":"0.496","my_coin":"KMD","other_amount":"0.03306667","other_coin":"DEX","started_at":1590101282},"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"taker_amount":"0.496","taker_coin":"KMD","total_time_in_seconds":"105.935s","type":"Taker","uuid":"24c3b70b-cab9-490c-8edb-d95fcf198e7d","is_recent_swap":true,"am_i_maker":false},"261b87f9-6b20-43ee-b43e-92c34160e90f":{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"data":{"lock_duration":7800,"maker":"d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8f","maker_amount":"10","maker_coin":"MORTY","maker_coin_start_block":426674,"maker_payment_confirmations":1,"maker_payment_requires_nota":false,"maker_payment_wait":1590243626,"my_persistent_pub":"03f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5","started_at":1590240506,"taker_amount":"15","taker_coin":"RICK","taker_coin_start_block":424294,"taker_payment_confirmations":1,"taker_payment_lock":1590248306,"taker_payment_requires_nota":false,"uuid":"261b87f9-6b20-43ee-b43e-92c34160e90f"},"human_timestamp":"2020-05-23 13:28:26.181","started_at":1590240506,"state":"Started","time_difference_gui":"0.181s","timestamp":1590240506181},{"data":{"maker_payment_locktime":1590256105,"maker_pubkey":"02d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8f","secret_hash":"a7ef2b3aa1a85304cc0220ee940a89b024341dbb"},"human_timestamp":"2020-05-23 13:29:27.211","started_at":1590240506181,"state":"Negotiated","time_difference_gui":"61.030s","timestamp":1590240567211},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"2131c9473eec5228c2b82b2c709fdd96045cbcb2d022b28a4347d6a6a282a34a","my_balance_change":"-0.01931501","received_by_me":"100.48067499","spent_by_me":"100.49999","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf"],"total_amount":"100.49999","tx_hash":"2131c9473eec5228c2b82b2c709fdd96045cbcb2d022b28a4347d6a6a282a34a","tx_hex":"0400008085202f890129766e40d8266a462b004d33155ef090cf99e7170c5d1c4cde172fe90ca5e0e4000000006a473044022058c15d091b73a014932e5855765960e9c0cc3a0fdcf815d1e54e95ebcf1490d8022001605424682896f568612d4816393947148af152bae3d56b12590b45be00d1fc012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff0205751d00000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88acab57e956020000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac3725c95e000000000000000000000000000000"},"human_timestamp":"2020-05-23 13:29:31.993","started_at":1590240567211,"state":"TakerFeeSent","time_difference_gui":"4.782s","timestamp":1590240571993},{"data":{"block_height":426679,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["RWQr5oJ2h2ptVdpK54LVyfRvgfNLxezfkU"],"internal_id":"2946c04a52fd086c509e24a0ab40edf26747363d268f43e9eac9a86b52e1195e","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":1590240616,"to":["RWQr5oJ2h2ptVdpK54LVyfRvgfNLxezfkU","bUnBb8f5DGBrvvfwNxKniGiQtuk3GnAGhb"],"total_amount":"5000.016","tx_hash":"2946c04a52fd086c509e24a0ab40edf26747363d268f43e9eac9a86b52e1195e","tx_hex":"0400008085202f890287e8b092e6661f17c168faed3b8caae93893bb5157d5c2a3e3f4ef117195aebb010000006a473044022038df27011c804a945752559804ac8f0b3dd27521f589388b8b808b2b7aefc74102207bc58db1391b101360841c1bc00e613a5ee5a822116cc5694616c86c72092d2e012102d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8fffffffffdc47c9206404e82141473710354a4cb595aea17a5e4b433fcd853dfd2f4cae2f000000006b4830450221009b764288a5eabdc5e8f759afeb672d3b7b9d2c5b9bd2a97e9648aec154b5a894022036169e69fde84fdf6e1749e339970682846bdd969bc4a2763a4ef401d4eae6fc012102d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8fffffffff0200ca9a3b0000000017a914b00effb61980bad05e41cc4b7ef913a40333e6d7871824d02e740000001976a914e7cd472f5a59ef7be2bd8de1caf98f9ec2514e6a88ac5525c95e000000000000000000000000000000"},"human_timestamp":"2020-05-23 13:30:23.162","started_at":1590240571993,"state":"MakerPaymentReceived","time_difference_gui":"51.169s","timestamp":1590240623162},{"human_timestamp":"2020-05-23 13:30:23.163","started_at":1590240623162,"state":"MakerPaymentWaitConfirmStarted","time_difference_gui":"0.001s","timestamp":1590240623163},{"human_timestamp":"2020-05-23 13:30:23.375","started_at":1590240623163,"state":"MakerPaymentValidatedAndConfirmed","time_difference_gui":"0.212s","timestamp":1590240623375},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"369539129ce2a8624a6b6b04f231164b7d2152a2bf40a41b6341934c7ff7bd03","my_balance_change":"-15.00001","received_by_me":"85.48066499","spent_by_me":"100.48067499","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bEdb9x7cpUB5hcehhBejaweLcMYbHCZVNy"],"total_amount":"100.48067499","tx_hash":"369539129ce2a8624a6b6b04f231164b7d2152a2bf40a41b6341934c7ff7bd03","tx_hex":"0400008085202f89014aa382a2a6d647438ab222d0b2bc5c0496dd9f702c2bb8c22852ec3e47c93121010000006b48304502210081959dadae4ac68670fb44103f3ff3518316c17d5cbd9783f79fffed99165575022032f6f765cb48fe01d33fcbf3d14bce60fdd514d877c56c27ff4ba13af059190f012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff02002f68590000000017a91414dd2a7f907bc06a3a74c7b114e1586d3cd696b987c32481fd010000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac6f25c95e000000000000000000000000000000"},"human_timestamp":"2020-05-23 13:30:26.869","started_at":1590240623375,"state":"TakerPaymentSent","time_difference_gui":"3.494s","timestamp":1590240626869},{"data":{"secret":"c9cd2ac8a96da987cc6e221474d6177d53c25da2c7b94f72fb1c7e8ed6b36d82","transaction":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["bEdb9x7cpUB5hcehhBejaweLcMYbHCZVNy"],"internal_id":"a12f78f057d49294aa5e919ea6cfd92e93efb18dca8208690c383721cdcd82e0","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":0,"to":["RWQr5oJ2h2ptVdpK54LVyfRvgfNLxezfkU"],"total_amount":"15","tx_hash":"a12f78f057d49294aa5e919ea6cfd92e93efb18dca8208690c383721cdcd82e0","tx_hex":"0400008085202f890103bdf77f4c9341631ba440bfa252217d4b1631f2046b6b4a62a8e29c1239953600000000d74730440220610d922d84db0191350256897e95a5d1c6f0805edf48dcf0da0add387c35eea202203abf36d1672794dbdde9011274210f2c0b3f0d4a79a242a7d95fd6e24abeb86a0120c9cd2ac8a96da987cc6e221474d6177d53c25da2c7b94f72fb1c7e8ed6b36d82004c6b63047243c95eb1752103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ac6782012088a914a7ef2b3aa1a85304cc0220ee940a89b024341dbb882102d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8fac68ffffffff01182b6859000000001976a914e7cd472f5a59ef7be2bd8de1caf98f9ec2514e6a88ac9917c95e000000000000000000000000000000"}},"human_timestamp":"2020-05-23 13:31:27.708","started_at":1590240626869,"state":"TakerPaymentSpent","time_difference_gui":"60.839s","timestamp":1590240687708},{"data":{"block_height":0,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["bUnBb8f5DGBrvvfwNxKniGiQtuk3GnAGhb"],"internal_id":"398575f8ce2bb7a68deae3e68782982d8caa4a06c27cc3becaffbec6a8f008b5","my_balance_change":"9.99999","received_by_me":"9.99999","spent_by_me":"0","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"total_amount":"10","tx_hash":"398575f8ce2bb7a68deae3e68782982d8caa4a06c27cc3becaffbec6a8f008b5","tx_hex":"0400008085202f89015e19e1526ba8c9eae9438f263d364767f2ed40aba0249e506c08fd524ac0462900000000d74730440220524051a7aedf7bca67924ec25b5f8d073d1c14bc8cce2731087376c7a67b756102205e7133f7ebe651a60ffeb7faf21a881aa0c8079fcb9071153a3855cd30e30b440120c9cd2ac8a96da987cc6e221474d6177d53c25da2c7b94f72fb1c7e8ed6b36d82004c6b6304e961c95eb1752102d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8fac6782012088a914a7ef2b3aa1a85304cc0220ee940a89b024341dbb882103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ac68ffffffff0118c69a3b000000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac9f17c95e000000000000000000000000000000"},"human_timestamp":"2020-05-23 13:31:29.166","started_at":1590240687708,"state":"MakerPaymentSpent","time_difference_gui":"1.458s","timestamp":1590240689166},{"human_timestamp":"2020-05-23 13:31:29.166","started_at":1590240689166,"state":"Finished","time_difference_gui":"0.000s","timestamp":1590240689166}],"is_recoverable":false,"maker_amount":"10","maker_coin":"MORTY","my_info":{"my_amount":"15","my_coin":"RICK","other_amount":"10","other_coin":"MORTY","started_at":1590240506},"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"taker_amount":"15","taker_coin":"RICK","total_time_in_seconds":"183.166s","type":"Taker","uuid":"261b87f9-6b20-43ee-b43e-92c34160e90f","is_recent_swap":true,"am_i_maker":false},"745c917d-6b1b-4c85-8587-eb8b4b802a86":{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"data":{"lock_duration":7800,"maker_amount":"77.77699999999999999999999999999999999999999999999991788915721393034825870646766169154228855721393035","maker_coin":"MORTY","maker_coin_start_block":425126,"maker_payment_confirmations":1,"maker_payment_lock":1590163539,"maker_payment_requires_nota":false,"my_persistent_pub":"03f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5","secret":"373697fdcaf6b9808a73d5619541566895674bc09a35e2b99bc3e92a06b5b726","secret_hash":"51d52af9e591651b68746b564c98687254c27a6f","started_at":1590147939,"taker":"d4acb3b1cc944b8b44836c6a4ef87bdee906ce268465bde8106cfee171b7f40d","taker_amount":"100.5","taker_coin":"RICK","taker_coin_start_block":422764,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"uuid":"745c917d-6b1b-4c85-8587-eb8b4b802a86"},"human_timestamp":"2020-05-22 11:45:39.864","started_at":1590147939,"state":"Started","time_difference_gui":"0.864s","timestamp":1590147939864},{"data":{"taker_payment_locktime":1590155739,"taker_pubkey":"02d4acb3b1cc944b8b44836c6a4ef87bdee906ce268465bde8106cfee171b7f40d"},"human_timestamp":"2020-05-22 11:46:20.297","started_at":1590147939864,"state":"Negotiated","time_difference_gui":"40.433s","timestamp":1590147980297},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RX43484BFZbfQiaaRZXDqskHQhwyCKwBk2"],"internal_id":"8283b4bca37b1104d38062afd6395fa37f3fa03fc47482574957c1d0ed5b721e","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":0,"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RX43484BFZbfQiaaRZXDqskHQhwyCKwBk2"],"total_amount":"0.50605501","tx_hash":"8283b4bca37b1104d38062afd6395fa37f3fa03fc47482574957c1d0ed5b721e","tx_hex":"0400008085202f890211e711f39df12574a61dd3fa28716cdc4c7bb8388fdc9dd8f6e9b7a92a9ad6eb010000006a47304402206a79aa19195b667be90dcb45d1bfaf451d08ef219c7cd13040f6ce33fe7c762d02207e150eb8f0e9b3ed07c42f570c454b64a349eaf4004b0e6ba3799c644a12fc72012102d4acb3b1cc944b8b44836c6a4ef87bdee906ce268465bde8106cfee171b7f40dffffffff72df8a200863cb404267995d4f7a7c333aeeb920e53a89411d78db07838efdc2000000006b4830450221009251829f5a59c9e19af248e2566984fb7c4a94502effa436e61d167aa8033bf60220482f139407097661b32d5165fe1c4428ce90c551ee6ccbe9eea97f5dc84b84a2012102d4acb3b1cc944b8b44836c6a4ef87bdee906ce268465bde8106cfee171b7f40dffffffff02da5cc500000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88acfbcc3e02000000001976a914eed5d3ad264ffc68fc0a6454e1696a30d8f405be88aca0bbc75e000000000000000000000000000000"},"human_timestamp":"2020-05-22 11:47:22.969","started_at":1590147980297,"state":"TakerFeeValidated","time_difference_gui":"62.672s","timestamp":1590148042969},{"data":{"block_height":0,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"0d637906aa05e148ca55a6cc1cf3e8f117dce26fe445d916e2b51792e0db446a","my_balance_change":"-77.77700999","received_by_me":"48560.69059639","spent_by_me":"48638.46760638","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bRznhhH51WLCy6xpEUFf44Wa78efHYs8Dg"],"total_amount":"48638.46760638","tx_hash":"0d637906aa05e148ca55a6cc1cf3e8f117dce26fe445d916e2b51792e0db446a","tx_hex":"0400008085202f8901e61a291705775fe2aebb9d73e4da80d4500d9fb5d0561cd4d46780acaaf26848010000006a47304402207c40f98fbdf70cf2d526977a10a9057f2319407f3a9c57b9830b162b4ca4d7750220092b5c83d848dbab27b88af0a983cef0dad32dcf72192c52aefd4f71c00062ba012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff029f4896cf0100000017a9149188ec8de3ebaa91cb8c3100e52dffc264e6f05487377445a46a0400001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88accbbbc75e000000000000000000000000000000"},"human_timestamp":"2020-05-22 11:47:25.353","started_at":1590148042969,"state":"MakerPaymentSent","time_difference_gui":"2.384s","timestamp":1590148045353},{"data":{"block_height":422769,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RX43484BFZbfQiaaRZXDqskHQhwyCKwBk2"],"internal_id":"bc771b3ccf129f0aa55c50329d033cdd7c0e1bd3496198e95ce19b34badd0a2d","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":1590148142,"to":["RX43484BFZbfQiaaRZXDqskHQhwyCKwBk2","bMY78roFvA3MM6yzQpqCiL3DZhiiyndreV"],"total_amount":"1337.87669139","tx_hash":"bc771b3ccf129f0aa55c50329d033cdd7c0e1bd3496198e95ce19b34badd0a2d","tx_hex":"0400008085202f89031e725bedd0c15749578274c43fa03f7fa35f39d6af6280d304117ba3bcb48382010000006b483045022100f9cc1f5ae6bbc3795cf875f84926b43834f8defba77faf9bec6b3372abb16b1502206d4eb23edc1a5d60a1ed666533aaedf8573392e214130b4d7b801b4df6425026012102d4acb3b1cc944b8b44836c6a4ef87bdee906ce268465bde8106cfee171b7f40dffffffffe9ddd2cab6b9448ec79ecdb73e45990cdcb560ff5eb0a376a80096485d768482000000006a473044022068a70667d4e65ff95aaa111ee959da02c1d687426fd89631b81102a74751506202207f472c1ec80c9ff0d78c9752c5ae1b74f874b13f7329cf2486e11e12df10158c012102d4acb3b1cc944b8b44836c6a4ef87bdee906ce268465bde8106cfee171b7f40dffffffff11e7d8e77e6287e7b9015a65a5183ceca5a96cddb57a49c8b07f19cde1dd78e1000000006a4730440220023a0949fb1cfb7e464e4e286423e77ad100c3bc7f731701c310c8ee24a30f7302200f066f37f75d5d743c33cb60654246ba6eddc0f28418fd75d29d1b2a498243e9012102d4acb3b1cc944b8b44836c6a4ef87bdee906ce268465bde8106cfee171b7f40dffffffff0280d406570200000017a914609c93e78c3d53a6744c8ba8bfae204c05dc78db872bfa56cf1c0000001976a914eed5d3ad264ffc68fc0a6454e1696a30d8f405be88ac1dbcc75e000000000000000000000000000000"},"human_timestamp":"2020-05-22 11:49:11.248","state":"TakerPaymentReceived","timestamp":1590148151248},{"human_timestamp":"2020-05-22 11:49:11.248","state":"TakerPaymentWaitConfirmStarted","timestamp":1590148151248},{"human_timestamp":"2020-05-22 11:49:11.363","state":"TakerPaymentValidatedAndConfirmed","timestamp":1590148151363},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["bMY78roFvA3MM6yzQpqCiL3DZhiiyndreV"],"internal_id":"e4e0a50ce92f17de4c1c5d0c17e799cf90f05e15334d002b466a26d8406e7629","my_balance_change":"100.49999","received_by_me":"100.49999","spent_by_me":"0","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"total_amount":"100.5","tx_hash":"e4e0a50ce92f17de4c1c5d0c17e799cf90f05e15334d002b466a26d8406e7629","tx_hex":"0400008085202f89012d0addba349be15ce9986149d31b0e7cdd3c039d32505ca50a9f12cf3c1b77bc00000000d8483045022100bdc5c1e423b6f181c5632298d0248b38ad3cc9f902fda795c108dc1986558a4a02202bb1daa29ed5fa5b74456cc1a58310154bdbf0fadc773a83fde81638c62293bd0120373697fdcaf6b9808a73d5619541566895674bc09a35e2b99bc3e92a06b5b726004c6b6304dbd9c75eb1752102d4acb3b1cc944b8b44836c6a4ef87bdee906ce268465bde8106cfee171b7f40dac6782012088a91451d52af9e591651b68746b564c98687254c27a6f882103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ac68ffffffff0198d00657020000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac27aec75e000000000000000000000000000000"},"human_timestamp":"2020-05-22 11:49:12.591","state":"TakerPaymentSpent","timestamp":1590148152591},{"human_timestamp":"2020-05-22 11:49:12.592","state":"Finished","timestamp":1590148152592}],"is_recoverable":false,"maker_amount":"77.777","maker_coin":"MORTY","my_info":{"my_amount":"77.777","my_coin":"MORTY","other_amount":"100.5","other_coin":"RICK","started_at":1590147939},"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","Finished"],"taker_amount":"100.5","taker_coin":"RICK","total_time_in_seconds":"106.353s","type":"Maker","uuid":"745c917d-6b1b-4c85-8587-eb8b4b802a86","is_recent_swap":true,"am_i_maker":true},"cc57bc15-983a-481c-b998-f6b6d9f01c8c":{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"data":{"lock_duration":7800,"maker_amount":"2","maker_coin":"MORTY","maker_coin_start_block":444090,"maker_payment_confirmations":1,"maker_payment_lock":1591311367,"maker_payment_requires_nota":false,"my_persistent_pub":"03f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5","secret":"c2b770fc8c6063c088087aee450b0fa9fc35deb77d2bad07309fe3dc665f89b2","secret_hash":"2cef0bb5853dc0bf7e10aed64785624b8aac3b91","started_at":1591295767,"taker":"d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8f","taker_amount":"1.75","taker_coin":"RICK","taker_coin_start_block":441681,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"uuid":"cc57bc15-983a-481c-b998-f6b6d9f01c8c"},"human_timestamp":"2020-06-04 18:36:07.647","started_at":1591295767,"state":"Started","time_difference_gui":"0.647s","timestamp":1591295767647},{"data":{"taker_payment_locktime":1591303566,"taker_pubkey":"02d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8f"},"human_timestamp":"2020-06-04 18:36:48.145","started_at":1591295767647,"state":"Negotiated","time_difference_gui":"40.498s","timestamp":1591295808145},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RWQr5oJ2h2ptVdpK54LVyfRvgfNLxezfkU"],"internal_id":"ee4de58ea3ca05b387f32f2f022c4adee8553e44694c5f1841bd12f21861fd09","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":0,"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RWQr5oJ2h2ptVdpK54LVyfRvgfNLxezfkU"],"total_amount":"14.99999","tx_hash":"ee4de58ea3ca05b387f32f2f022c4adee8553e44694c5f1841bd12f21861fd09","tx_hex":"0400008085202f8901e082cdcd2137380c690882ca8db1ef932ed9cfa69e915eaa9492d457f0782fa1000000006b483045022100f411398339e40583ac3d4ac2d0436a43eb365d760a5c6bf6c40f748e8057349902202f07bd7c1dd7aa8e7cba064aa5ce4e45285ad72725be08c3ce86274e4c5522ba012102d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8fffffffff02c96f0300000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac67b76459000000001976a914e7cd472f5a59ef7be2bd8de1caf98f9ec2514e6a88ac533fd95e000000000000000000000000000000"},"human_timestamp":"2020-06-04 18:37:38.847","started_at":1591295808145,"state":"TakerFeeValidated","time_difference_gui":"50.702s","timestamp":1591295858847},{"data":{"block_height":0,"coin":"MORTY","fee_details":{"amount":"0.00001"},"from":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"internal_id":"1b2751ec35cf3dbfac0311c02ae8b3e77f2b8f00a778c13e8527949b97fc3976","my_balance_change":"-2.00001","received_by_me":"7.99998","spent_by_me":"9.99999","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2","bPsLhKNyKxVncCSvh4KPjF8LnRqUeenjdQ"],"total_amount":"9.99999","tx_hash":"1b2751ec35cf3dbfac0311c02ae8b3e77f2b8f00a778c13e8527949b97fc3976","tx_hex":"0400008085202f8901b508f0a8c6beffcabec37cc2064aaa8c2d988287e6e3ea8da6b72bcef8758539000000006a473044022039eb20856d61c8d6f6bca763e0cb94935f7617a5ffd99d7f0dabc2617631bf2202200a1e27f3d3e578758316f6dd28ba0d40c92834fdfa6bdded796f0b15b160b1fe012103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ffffffff0300c2eb0b0000000017a9147a300d35988b4edcd976eb80830a48738fd68e20870000000000000000166a142cef0bb5853dc0bf7e10aed64785624b8aac3b913000af2f000000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac723fd95e000000000000000000000000000000"},"human_timestamp":"2020-06-04 18:37:42.333","started_at":1591295858847,"state":"MakerPaymentSent","time_difference_gui":"3.486s","timestamp":1591295862333},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["RWQr5oJ2h2ptVdpK54LVyfRvgfNLxezfkU"],"internal_id":"5f49bc3029354207655e997bc0449d6df1d4daa7f021093c990a3caaaecdce12","my_balance_change":"0","received_by_me":"0","spent_by_me":"0","timestamp":0,"to":["RWQr5oJ2h2ptVdpK54LVyfRvgfNLxezfkU","bUFDh3ksaQcSeJzFHPWDcDg2zFZGA3dhNg"],"total_amount":"14.99772775","tx_hash":"5f49bc3029354207655e997bc0449d6df1d4daa7f021093c990a3caaaecdce12","tx_hex":"0400008085202f890109fd6118f212bd41185f4c69443e55e8de4a2c022f2ff387b305caa38ee54dee010000006a473044022058099e3ae8a74b9ab8ca754bede91d40b100c30a4a01e453ebf88867d4afb9190220277379b203368045c684adebeed0c8a6e29274899e3bf2e8ea3f83d42363b99c012102d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8fffffffff02c0496e0a0000000017a914aa33dbd47f135454583c470d0b52104b8265b63887bf69f64e000000001976a914e7cd472f5a59ef7be2bd8de1caf98f9ec2514e6a88acd73fd95e000000000000000000000000000000"},"human_timestamp":"2020-06-04 18:39:48.454","state":"TakerPaymentReceived","timestamp":1591295988454},{"human_timestamp":"2020-06-04 18:39:48.455","state":"TakerPaymentWaitConfirmStarted","timestamp":1591295988455},{"human_timestamp":"2020-06-04 18:40:18.682","state":"TakerPaymentValidatedAndConfirmed","timestamp":1591296018682},{"data":{"block_height":0,"coin":"RICK","fee_details":{"amount":"0.00001"},"from":["bUFDh3ksaQcSeJzFHPWDcDg2zFZGA3dhNg"],"internal_id":"2027064ea76c67df3b12b648221ed300e90dcb9084497a158043f948b08f2fdc","my_balance_change":"1.74999","received_by_me":"1.74999","spent_by_me":"0","timestamp":0,"to":["RH8WgfYXbeMgF96vCqfKo47TkFApNXkQM2"],"total_amount":"1.75","tx_hash":"2027064ea76c67df3b12b648221ed300e90dcb9084497a158043f948b08f2fdc","tx_hex":"0400008085202f890112cecdaeaa3c0a993c0921f0a7dad4f16d9d44c07b995e650742352930bc495f00000000d74730440220304ea1637cdbd93d93c46cc5ab6dce28958967f13cc096b479440a407b0e9b5e022027b5affd08981aa67645602f1864064f053c9bcedbee42ca86ef100c752cebda0120c2b770fc8c6063c088087aee450b0fa9fc35deb77d2bad07309fe3dc665f89b2004c6b63048e5dd95eb1752102d9c3939bd66f761abbe365395a318acdef6dd37021d3d019c6c0beb635dc8b8fac6782012088a9142cef0bb5853dc0bf7e10aed64785624b8aac3b91882103f813f322d82167027f635f08686fdc615f2f98d8f5f49091dafc62d520666aa5ac68ffffffff01d8456e0a000000001976a914561ccb4cb9159a1ba1b81c133507ea950e065edc88ac0232d95e000000000000000000000000000000"},"human_timestamp":"2020-06-04 18:40:19.907","state":"TakerPaymentSpent","timestamp":1591296019907},{"human_timestamp":"2020-06-04 18:40:19.907","state":"Finished","timestamp":1591296019907}],"is_recoverable":false,"maker_amount":"2","maker_coin":"MORTY","my_info":{"my_amount":"2","my_coin":"MORTY","other_amount":"1.75","other_coin":"RICK","started_at":1591295767},"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","Finished"],"taker_amount":"1.75","taker_coin":"RICK","total_time_in_seconds":"95.333s","type":"Maker","uuid":"cc57bc15-983a-481c-b998-f6b6d9f01c8c","is_recent_swap":true,"am_i_maker":true,"date":"2020-06-04 18:40:19.907"}} - }, - - refresh_infos: () => { - console.log("refresh infos!") - }, - refresh_orders_and_swaps: () => { console.log("refresh_orders_and_swaps!") }, - get_wallets: () => { return ["encrypted"] }, + find_closest_ohlc_data: () => { return {"close":0.0000654,"high":0.0000655,"low":0.0000654,"open":0.0000655,"quote_volume":0.006986865,"timestamp":1593740820,"volume":106.83} }, + + get_ohlc_data: (range) => [{"close":0.0000654,"high":0.0000655,"low":0.0000654,"open":0.0000655,"quote_volume":0.006986865,"timestamp":1593740820,"volume":106.83},{"close":0.0000653,"high":0.0000653,"low":0.0000653,"open":0.0000653,"quote_volume":0.0068565,"timestamp":1593740880,"volume":105},{"close":0.0000653,"high":0.0000653,"low":0.0000653,"open":0.0000653,"quote_volume":0.007420692,"timestamp":1593741000,"volume":113.64}], + + get_wallets: () => { return ["naezith", "slyris", "ca333", "tony"] }, get_trade_infos: (ticker, receive_ticker, amount) => { return {"input_final_value":"3332.99997961","is_ticker_of_fees_eth":false,"trade_fee":"0.00000039","tx_fee":"0.0001", "not_enough_balance_to_pay_the_fees": false, "amount_needed":"0.01"} diff --git a/atomic_qt_design/qml/Constants/General.qml b/atomic_qt_design/qml/Constants/General.qml index 975e214bc6..ac770af3b5 100644 --- a/atomic_qt_design/qml/Constants/General.qml +++ b/atomic_qt_design/qml/Constants/General.qml @@ -13,12 +13,20 @@ QtObject { return ticker === "" ? "" : coin_icons_path + ticker.toLowerCase() + ".png" } + readonly property string cex_icon: 'ⓘ' + readonly property string download_icon: '📥' + readonly property string right_arrow_icon: "⮕" + + property bool privacy_mode: false + readonly property int idx_dashboard_portfolio: 0 readonly property int idx_dashboard_wallet: 1 readonly property int idx_dashboard_exchange: 2 readonly property int idx_dashboard_news: 3 readonly property int idx_dashboard_dapps: 4 readonly property int idx_dashboard_settings: 5 + readonly property int idx_dashboard_light_ui: 6 + readonly property int idx_dashboard_privacy_mode: 7 readonly property int idx_exchange_trade: 0 readonly property int idx_exchange_orders: 1 @@ -31,18 +39,35 @@ QtObject { readonly property var reg_pass_numeric: /(?=.*[0-9])/ readonly property var reg_pass_special: /(?=.*[@#$%{}[\]()\/\\'"`~,;:.<>+\-_=!^&*|?])/ readonly property var reg_pass_count: /(?=.{16,})/ - + readonly property double time_toast_important_error: 10000 readonly property double time_toast_basic_info: 3000 - function prettifyJSON(j) { - return JSON.stringify(JSON.parse(j), null, 4) + readonly property var chart_times: (["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "1d", "3d"/*, "1w"*/]) + readonly property var time_seconds: ({ "1m": 60, "3m": 180, "5m": 300, "15m": 900, "30m": 1800, "1h": 3600, "2h": 7200, "4h": 14400, "6h": 21600, "12h": 43200, "1d": 86400, "3d": 259200, "1w": 604800 }) + + property var all_coins + + function timestampToDouble(timestamp) { + return (new Date(timestamp)).getTime() + } + + function timestampToString(timestamp) { + return (new Date(timestamp)).getUTCDate() } - + + function timestampToDate(timestamp) { + return (new Date(timestamp * 1000)) + } + function clone(obj) { return JSON.parse(JSON.stringify(obj)); } + function prettifyJSON(j) { + return JSON.stringify(JSON.parse(j), null, 4) + } + function viewTxAtExplorer(ticker, id, add_0x=false) { if(id !== '') { const coin_info = API.get().get_coin_info(ticker) @@ -51,6 +76,13 @@ QtObject { } } + function viewAddressAtExplorer(ticker, address) { + if(address !== '') { + const coin_info = API.get().get_coin_info(ticker) + Qt.openUrlExternally(coin_info.explorer_url + 'address/' + address) + } + } + function diffPrefix(received) { return received === "" ? "" : received === true ? "+ " : "- " } @@ -60,19 +92,22 @@ QtObject { (type === undefined || c.type === type)) } + function validFiatRates(data, fiat) { + return data && data.rates && data.rates[fiat] + } + function formatFiat(received, amount, fiat) { const symbols = { "USD": "$", - "EUR": "€" + "EUR": "€", + "BTC": "₿", + "KMD": "KMD", } return diffPrefix(received) + symbols[fiat] + " " + amount } function formatPercent(value, show_prefix=true) { - const result = value + ' %' - if(!show_prefix) return result - let prefix = '' if(value > 0) prefix = '+ ' else if(value < 0) { @@ -80,14 +115,28 @@ QtObject { value *= -1 } - return prefix + result + return (show_prefix ? prefix : '') + value + ' %' } readonly property int amountPrecision: 8 + readonly property int sliderDigitLimit: 9 + readonly property int recommendedPrecision: -1337 + + function getDigitCount(v) { + return v.toString().replace("-", "").split(".")[0].length + } + + function getRecommendedPrecision(v) { + return Math.min(Math.max(sliderDigitLimit - getDigitCount(v), 0), amountPrecision) + } + + function formatDouble(v, precision) { + if(precision === recommendedPrecision) precision = getRecommendedPrecision(v) + + if(precision === 0) return parseInt(v).toString() - function formatDouble(v) { // Remove more than n decimals, then convert to string without trailing zeros - return parseFloat(v).toFixed(amountPrecision).replace(/\.?0+$/,"") + return parseFloat(v).toFixed(precision || amountPrecision).replace(/\.?0+$/,"") } function formatCrypto(received, amount, ticker, fiat_amount, fiat) { @@ -99,11 +148,15 @@ QtObject { } function fullNamesOfCoins(coins) { - return coins.map(c => fullCoinName(c.name, c.ticker)) + return coins.map(c => { + return { value: c.ticker, text: fullCoinName(c.name, c.ticker) } + }) } function getTickers(coins) { - return coins.map(c => c.ticker) + return coins.map(c => { + return { value: c.ticker, text: c.ticker } + }) } @@ -112,7 +165,9 @@ QtObject { } function getTickersAndBalances(coins) { - return coins.map(c => c.ticker + " (" + c.balance + ")") + return coins.map(c => { + return { value: c.ticker, text: c.ticker + " (" + c.balance + ")" } + }) } function getMinTradeAmount() { @@ -139,50 +194,6 @@ QtObject { return v !== undefined && v !== "" } - function filterRecentSwaps(all_orders, finished_option, ticker) { - let orders = all_orders - - Object.keys(orders).map((key, index) => { - orders[key].uuid = key - orders[key].is_recent_swap = true - orders[key].am_i_maker = orders[key].type.toLowerCase() === 'maker' - }) - - let arr = Object.values(orders).sort((a, b) => b.events[b.events.length-1].timestamp - a.events[a.events.length-1].timestamp) - - // Filter by finished - if(finished_option !== undefined && finished_option !== "") - arr = arr.filter(o => { - for(let e of o.events) { - if(e.state === "Finished") - return finished_option === "include" - } - - return finished_option === "exclude" - }) - - // Filter by ticker - if(ticker) - arr = arr.filter(o => o.my_info.my_coin === ticker || o.my_info.other_coin === ticker) - - return arr - } - - function formatOrder(o) { - if(o.is_recent_swap) { - o.date = o.events[o.events.length-1].human_timestamp - } - else { - o.my_info = { - my_coin: o.base, - my_amount: o.base_amount, - other_coin: o.rel, - other_amount: o.rel_amount - } - } - return o - } - function isEthNeeded() { for(const c of API.get().enabled_coins) if(c.type === "ERC-20" && c.ticker !== "ETH") return true @@ -190,8 +201,15 @@ QtObject { return false } + function txFeeTicker(info) { + return info.type === "ERC-20" ? "ETH" : info.ticker + } + function canDisable(ticker) { - if(API.get().enabled_coins.length <= 2) return false + if(API.get().enabled_coins.length <= 2 || + ticker === "KMD" || + ticker === "BTC") return false + if(ticker === "ETH") return !General.isEthNeeded() return true diff --git a/atomic_qt_design/qml/Constants/Style.qml b/atomic_qt_design/qml/Constants/Style.qml index 640f9d26ad..a51259c54e 100644 --- a/atomic_qt_design/qml/Constants/Style.qml +++ b/atomic_qt_design/qml/Constants/Style.qml @@ -2,18 +2,28 @@ pragma Singleton import QtQuick 2.10 QtObject { - readonly property FontLoader mySystemFont: FontLoader { source: "../../assets/fonts/Rubik-Regular.ttf" } - readonly property font font: Qt.font({ - family: mySystemFont.name, - pixelSize: Qt.application.font.pixelSize - }) + readonly property FontLoader mySystemFontThin: FontLoader { source: "../../assets/fonts/Montserrat-Thin.ttf" } + readonly property FontLoader mySystemFontLight: FontLoader { source: "../../assets/fonts/Montserrat-Light.ttf" } + readonly property FontLoader mySystemFont: FontLoader { source: "../../assets/fonts/Montserrat-Regular.ttf" } + readonly property FontLoader mySystemFontMedium: FontLoader { source: "../../assets/fonts/Montserrat-Medium.ttf" } + readonly property FontLoader mySystemFontSemiBold: FontLoader { source: "../../assets/fonts/Montserrat-SemiBold.ttf" } + readonly property string font_family: "Montserrat" readonly property string listItemPrefix: " ⚬ " readonly property string successCharacter: "✓" readonly property string failureCharacter: "✘" - readonly property int materialElevation: 1 + readonly property int materialElevation: 5 + readonly property int textSizeVerySmall1: 1 + readonly property int textSizeVerySmall2: 2 + readonly property int textSizeVerySmall3: 3 + readonly property int textSizeVerySmall4: 4 + readonly property int textSizeVerySmall5: 5 + readonly property int textSizeVerySmall6: 6 + readonly property int textSizeVerySmall7: 7 + readonly property int textSizeVerySmall8: 8 + readonly property int textSizeVerySmall9: 9 readonly property int textSizeSmall: 10 readonly property int textSizeSmall1: 11 readonly property int textSizeSmall2: 12 @@ -21,6 +31,10 @@ QtObject { readonly property int textSizeSmall4: 14 readonly property int textSizeSmall5: 15 readonly property int textSize: 16 + readonly property int textSizeMid: 17 + readonly property int textSizeMid1: 18 + readonly property int textSizeMid2: 19 + readonly property int textSize1: 20 readonly property int textSize2: 24 readonly property int textSize3: 36 readonly property int textSize4: 48 @@ -33,42 +47,142 @@ QtObject { readonly property int textSize11: 132 readonly property int textSize12: 144 - readonly property int rectangleCornerRadius: 12 + readonly property int rectangleCornerRadius: 11 readonly property int itemPadding: 12 - readonly property int paneTitleOffset: 6 + readonly property int buttonSpacing: 12 + readonly property int rowSpacing: 12 readonly property int iconTextMargin: 5 + readonly property int sidebarLineHeight: 44 + readonly property int scrollbarOffset: 5 - readonly property string colorRed: "#DC0333" - readonly property string colorRed2: "#891931" - readonly property string colorRed3: "#661224" - readonly property string colorYellow: "#FFC305" - readonly property string colorOrange: "#F7931A" - readonly property string colorGreen: "#2FEA8B" - - readonly property string colorWhite1: "#FFFFFF" - readonly property string colorWhite2: "#F9F9F9" - readonly property string colorWhite3: "#F0F0F0" - readonly property string colorWhite4: "#C9C9C9" - readonly property string colorWhite5: "#8E9293" - readonly property string colorWhite6: "#777777" - readonly property string colorWhite7: "#666666" - readonly property string colorWhite8: "#555555" - readonly property string colorWhite9: "#444444" - readonly property string colorWhite10: "#333333" - readonly property string colorWhite11: "#222222" - readonly property string colorWhite12: "#111111" - readonly property string colorWhite13: "#000000" - - readonly property string colorTheme0: "#41EAD4" - readonly property string colorTheme1: "#3CC9BF" - readonly property string colorTheme2: "#36A8AA" - readonly property string colorTheme3: "#318795" - readonly property string colorTheme4: "#2B6680" - readonly property string colorTheme5: "#3a4c66" - readonly property string colorTheme6: "#283547" - readonly property string colorTheme7: "#232F40" - readonly property string colorTheme8: "#1E2938" + property bool dark_theme: true + + readonly property string sidebar_atomicdex_logo: dark_theme ? "atomicdex-logo.svg" : "atomicdex-logo-dark.svg" + readonly property string colorRed: dark_theme ? "#D13990" : "#D13990" + readonly property string colorRed2: dark_theme ? "#b61477" : "#b61477" + readonly property string colorRed3: dark_theme ? "#41072a" : "#41072a" + readonly property string colorYellow: dark_theme ? "#FFC305" : "#FFC305" + readonly property string colorOrange: dark_theme ? "#F7931A" : "#F7931A" + readonly property string colorBlue: dark_theme ? "#3B78D1" : "#3B78D1" + readonly property string colorGreen: dark_theme ? "#74FBEE" : "#74FBEE" + readonly property string colorGreen2: dark_theme ? "#14bca6" : "#14bca6" + readonly property string colorGreen3: dark_theme ? "#07433b" : "#07433b" + + readonly property string colorWhite1: dark_theme ? "#FFFFFF" : "#FFFFFF" + readonly property string colorWhite2: dark_theme ? "#F9F9F9" : "#F9F9F9" + readonly property string colorWhite3: dark_theme ? "#F0F0F0" : "#F0F0F0" + readonly property string colorWhite4: dark_theme ? "#C9C9C9" : "#C9C9C9" + readonly property string colorWhite5: dark_theme ? "#8E9293" : "#8E9293" + readonly property string colorWhite6: dark_theme ? "#777777" : "#777777" + readonly property string colorWhite7: dark_theme ? "#666666" : "#666666" + readonly property string colorWhite8: dark_theme ? "#555555" : "#555555" + readonly property string colorWhite9: dark_theme ? "#444444" : "#444444" + readonly property string colorWhite10: dark_theme ? "#333333" : "#333333" + readonly property string colorWhite11: dark_theme ? "#222222" : "#222222" + readonly property string colorWhite12: dark_theme ? "#111111" : "#111111" + readonly property string colorWhite13: dark_theme ? "#000000" : "#000000" + + readonly property string colorTheme0: dark_theme ? "#41EAD4" : "#41EAD4" + readonly property string colorTheme1: dark_theme ? "#3CC9BF" : "#3CC9BF" + readonly property string colorTheme2: dark_theme ? "#36A8AA" : "#36A8AA" + readonly property string colorTheme3: dark_theme ? "#318795" : "#318795" + readonly property string colorTheme4: dark_theme ? "#2B6680" : "#2B6680" + readonly property string colorTheme5: dark_theme ? "#23273C" : "#F2F3F7" + readonly property string colorTheme6: dark_theme ? "#22263A" : "#F2F3F7" + readonly property string colorTheme7: dark_theme ? "#15182A" : "#F9F9FB" + readonly property string colorTheme8: dark_theme ? "#171A2C" : "#F2F3F7" + readonly property string colorTheme9: dark_theme ? "#0E1021" : "#F2F3F7" + readonly property string colorTheme10: dark_theme ? "#2579E0" : "#2579E0" + readonly property string colorTheme11: dark_theme ? "#00A3FF" : "#00A3FF" + readonly property string colorThemeLine: dark_theme ? "#1D1F23" : "#1D1F23" + readonly property string colorThemePassive: dark_theme ? "#777F8C" : "#777F8C" + readonly property string colorThemePassiveLight: dark_theme ? "#CCCDD0" : "#CCCDD0" + readonly property string colorThemeDark: dark_theme ? "#26282C" : "#26282C" + readonly property string colorThemeDark2: dark_theme ? "#3C4150" : "#E6E8ED" + readonly property string colorThemeDark3: dark_theme ? "#78808D" : "#78808D" + readonly property string colorThemeDarkLight: dark_theme ? "#78808D" : "#456078" + + readonly property string colorRectangle: dark_theme ? colorTheme7 : colorTheme8 + readonly property string colorInnerBackground: dark_theme ? colorTheme7 : colorTheme7 + + readonly property string colorGradient1: dark_theme ? colorTheme9 : colorTheme9 + readonly property string colorGradient2: dark_theme ? colorTheme5 : colorTheme5 + readonly property string colorGradient3: dark_theme ? "#24283D" : "#24283D" + readonly property string colorGradient4: dark_theme ? "#0D0F21" : "#0D0F21" + readonly property string colorLineGradient1: dark_theme ? "#2c2f3c" : "#EEF1F7" + readonly property string colorLineGradient2: dark_theme ? "#06070c" : "#DCE1E8" + readonly property string colorLineGradient3: dark_theme ? "#090910" : "#EEF1F7" + readonly property string colorLineGradient4: dark_theme ? "#24283b" : "#DCE1E8" + readonly property string colorDropShadowLight: dark_theme ? "#216975a4" : "#21FFFFFF" + readonly property string colorDropShadowLight2: dark_theme ? "#606975a4" : "#60FFFFFF" + readonly property string colorDropShadowDark: dark_theme ? "#FF050615" : "#BECDE2" + readonly property string colorBorder: dark_theme ? "#23273B" : "#DAE1EC" + readonly property string colorBorder2: dark_theme ? "#1C1F32" : "#DAE1EC" + + readonly property string colorInnerShadow: dark_theme ? "#A0000000" : "#BECDE2" + + readonly property string colorGradientLine1: dark_theme ? "#00FFFFFF" : "#00CFD4DB" + readonly property string colorGradientLine2: dark_theme ? "#0FFFFFFF" : "#FFCFD4DB" + + readonly property string colorWalletsHighlightGradient1: dark_theme ? "#801B5E7D" : "#801B5E7D" + readonly property string colorWalletsHighlightGradient2: dark_theme ? "#001B5E7D" : "#001B5E7D" + readonly property string colorWalletsSidebarDropShadow: dark_theme ? "#B0000000" : "#BECDE2" + + readonly property string colorScrollbar: dark_theme ? "#202339" : "#C4CCDA" + readonly property string colorScrollbarBackground: dark_theme ? "#10121F" : "#EFF1F6" + readonly property string colorScrollbarGradient1: dark_theme ? "#33395A" : "#C4CCDA" + readonly property string colorScrollbarGradient2: dark_theme ? "#292D48" : "#C4CCDA" + + readonly property string colorSidebarIconHighlighted: dark_theme ? "#2BBEF2" : "#FFFFFF" + readonly property string colorSidebarHighlightGradient1: dark_theme ? "#FF1B5E7D" : "#8b95ed" + readonly property string colorSidebarHighlightGradient2: dark_theme ? "#BA1B5E7D" : "#AD7faaf0" + readonly property string colorSidebarHighlightGradient3: dark_theme ? "#5F1B5E7D" : "#A06dc9f3" + readonly property string colorSidebarHighlightGradient4: dark_theme ? "#001B5E7D" : "#006bcef4" + readonly property string colorSidebarDropShadow: dark_theme ? "#90000000" : "#BECDE2" + + readonly property string colorCoinListHighlightGradient1: dark_theme ? "#002C2E40" : "#00E0E6F0" + readonly property string colorCoinListHighlightGradient2: dark_theme ? "#FF2C2E40" : "#FFE0E6F0" + + readonly property string colorRectangleBorderGradient1: dark_theme ? "#2A2F48" : "#00FFFFFF" + readonly property string colorRectangleBorderGradient2: dark_theme ? "#0D1021" : "#00FFFFFF" + + readonly property string colorChartText: dark_theme ? "#405366" : "#B5B9C1" + readonly property string colorChartLegendLine: dark_theme ? "#3F5265" : "#BDC0C8" + readonly property string colorChartGrid: dark_theme ? "#202333" : "#E6E8ED" + readonly property string colorChartLineText: dark_theme ? "#405366" : "#FFFFFF" + + readonly property string colorChartMA1: dark_theme ? "#5BC6FA" : "#5BC6FA" + readonly property string colorChartMA2: dark_theme ? "#F1D17F" : "#F1D17F" + + readonly property string colorLineBasic: dark_theme ? "#303344" : "#303344" + + readonly property string colorText: dark_theme ? Style.colorWhite1 : "#405366" + readonly property string colorText2: dark_theme ? "#79808C" : "#3C5368" + readonly property string colorTextDisabled: dark_theme ? Style.colorWhite8 : "#B5B9C1" + readonly property string colorButtonDisabled: Style.colorTheme9 + readonly property string colorButtonHovered: Style.colorTheme6 + readonly property string colorButtonEnabled: Style.colorRectangle + readonly property string colorButtonTextDisabled: Style.colorWhite8 + readonly property string colorButtonTextHovered: Style.colorText + readonly property string colorButtonTextEnabled: Style.colorText + readonly property string colorPlaceholderText: Style.colorWhite9 + + property string colorButtonDangerDisabled: Style.colorRed3 + property string colorButtonDangerHovered: Style.colorRed + property string colorButtonDangerEnabled: Style.colorRed2 + + property string colorButtonPrimaryDisabled: Style.colorGreen3 + property string colorButtonPrimaryHovered: Style.colorGreen + property string colorButtonPrimaryEnabled: Style.colorGreen2 readonly property int modalTitleMargin: 10 - readonly property string modalValueColor: Style.colorWhite4 + readonly property string modalValueColor: colorWhite4 + + function getValueColor(v) { + v = parseFloat(v) + if(v !== 0) + return v > 0 ? Style.colorGreen : Style.colorRed + + return Style.colorWhite4 + } } diff --git a/atomic_qt_design/qml/Exchange/Exchange.qml b/atomic_qt_design/qml/Exchange/Exchange.qml index a4faa70405..9adbc4b2a3 100644 --- a/atomic_qt_design/qml/Exchange/Exchange.qml +++ b/atomic_qt_design/qml/Exchange/Exchange.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" import "./Trade" @@ -10,10 +10,14 @@ import "./History" Item { id: exchange + readonly property int layout_margin: 30 + + property int prev_page: -1 property int current_page: API.design_editor ? General.idx_exchange_trade : General.idx_exchange_trade function reset() { current_page = General.idx_exchange_trade + prev_page = -1 exchange_trade.fullReset() exchange_history.reset() exchange_orders.reset() @@ -33,15 +37,26 @@ Item { } function onOpened() { - if(current_page === General.idx_exchange_trade) { - exchange_trade.onOpened() - } - else if(current_page === General.idx_exchange_orders) { - exchange_orders.onOpened() - } - else if(current_page === General.idx_exchange_history) { - exchange_history.onOpened() + if(prev_page !== current_page) { + // Handle DEX enter/exit + if(current_page === General.idx_exchange_trade) { + API.get().on_gui_enter_dex() + exchange_trade.onOpened() + } + else if(prev_page === General.idx_exchange_trade) { + API.get().on_gui_leave_dex() + } + + // Opening of other pages + if(current_page === General.idx_exchange_orders) { + exchange_orders.onOpened() + } + else if(current_page === General.idx_exchange_history) { + exchange_history.onOpened() + } } + + prev_page = current_page } onCurrent_pageChanged: { @@ -54,41 +69,63 @@ Item { anchors.fill: parent - spacing: 20 + spacing: layout_margin // Top tabs - RowLayout { - id: tabs + FloatingBackground { + id: balance_box Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - Layout.topMargin: 30 - spacing: 40 - - ExchangeTab { - dashboard_index: General.idx_exchange_trade - text: API.get().empty_string + (qsTr("Trade")) - } - - ExchangeTab { - dashboard_index: General.idx_exchange_orders - text: API.get().empty_string + (qsTr("Orders")) - } - - ExchangeTab { - dashboard_index: General.idx_exchange_history - text: API.get().empty_string + (qsTr("History")) + Layout.fillWidth: true + Layout.topMargin: layout_margin + Layout.leftMargin: layout_margin + Layout.rightMargin: layout_margin + + content: Item { + id: content + width: balance_box.width + height: 62 + + RowLayout { + anchors.left: parent.left + anchors.leftMargin: 20 + anchors.verticalCenter: parent.verticalCenter + + spacing: 30 + + ExchangeTab { + dashboard_index: General.idx_exchange_trade + text_value: API.get().empty_string + (qsTr("Trade")) + } + + VerticalLineBasic { + id: vline + height: content.height * 0.5 + color: Style.colorTheme5 + } + + ExchangeTab { + dashboard_index: General.idx_exchange_orders + text_value: API.get().empty_string + (qsTr("Orders")) + } + + VerticalLineBasic { + height: vline.height + color: vline.color + } + + ExchangeTab { + dashboard_index: General.idx_exchange_history + text_value: API.get().empty_string + (qsTr("History")) + } + } } } - HorizontalLine { - width: tabs.width * 1.25 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } - // Bottom content StackLayout { Layout.fillWidth: true Layout.fillHeight: true - Layout.bottomMargin: 15 + Layout.bottomMargin: layout_margin Layout.leftMargin: Layout.bottomMargin Layout.rightMargin: Layout.bottomMargin @@ -114,91 +151,61 @@ Item { } } - - - // Status Info - readonly property int status_swap_not_swap: -1 - readonly property int status_swap_matching: 0 - readonly property int status_swap_matched: 1 - readonly property int status_swap_ongoing: 2 - readonly property int status_swap_successful: 3 - readonly property int status_swap_failed: 4 - - function getSwapError(swap) { - if(swap.is_recent_swap) { - for(let i = swap.events.length - 1; i > 0; --i) { - const e = swap.events[i] - if(e.data && e.data.error && swap.error_events.indexOf(e.state) !== -1) { - return e - } - } + function getStatusColor(status) { + switch(status) { + case "matching": + return Style.colorYellow + case "matched": + case "ongoing": + case "refunding": + return Style.colorOrange + case "successful": + return Style.colorGreen + case "failed": + default: + return Style.colorRed } - - return { state: '', data: { error: '' } } } - function getLastEvent(swap) { - if(swap.is_recent_swap && swap.events.length > 0) { - return swap.events[swap.events.length-1] + function getStatusText(status) { + switch(status) { + case "matching": + return qsTr("Order Matching") + case "matched": + return qsTr("Order Matched") + case "ongoing": + return qsTr("Swap Ongoing") + case "successful": + return qsTr("Swap Successful") + case "refunding": + return qsTr("Refunding") + case "failed": + return qsTr("Swap Failed") + default: + return qsTr("Unknown State") } - - return { state: '', data: { error: '' } } - } - - function getStatus(swap) { - if(!swap.is_recent_swap && !swap.am_i_maker) return status_swap_matching - if(!swap.is_recent_swap) return status_swap_not_swap - - const last_state = swap.events[swap.events.length-1].state - - if(last_state === "Started") return status_swap_matched - if(last_state === "Finished") return getSwapError(swap).state === '' ? status_swap_successful : status_swap_failed - - return status_swap_ongoing } - function getStatusColor(swap) { - const status = getStatus(swap) - return status === status_swap_matching ? Style.colorYellow : - status === status_swap_matched ? Style.colorOrange : - status === status_swap_ongoing ? Style.colorOrange : - status === status_swap_successful ? Style.colorGreen : Style.colorRed - } - - function getStatusText(swap) { - const status = getStatus(swap) - return status === status_swap_matching ? qsTr("Order Matching") : - status === status_swap_matched ? qsTr("Order Matched") : - status === status_swap_ongoing ? qsTr("Swap Ongoing") : - status === status_swap_successful ? qsTr("Swap Successful") : - qsTr("Swap Failed") - } - - function getStatusStep(swap) { - const status = getStatus(swap) - return status === status_swap_matching ? "0/3": - status === status_swap_matched ? "1/3": - status === status_swap_ongoing ? "2/3": - status === status_swap_successful ? Style.successCharacter : Style.failureCharacter - } - - function getStatusTextWithPrefix(swap) { - return getStatusStep(swap) + " " + getStatusText(swap) - } - - function getSwapPaymentID(swap, is_taker) { - if(swap.events !== undefined) { - const search_name = swap.am_i_maker ? - (is_taker ? "TakerPaymentSpent" : "MakerPaymentSent") : - (is_taker ? "TakerPaymentSent" : "MakerPaymentSpent") - for(const e of swap.events) { - if(e.state === search_name) { - return e.data.tx_hash - } - } + function getStatusStep(status) { + switch(status) { + case "matching": + return "0/3" + case "matched": + return "1/3" + case "ongoing": + return "2/3" + case "successful": + return Style.successCharacter + case "refunding": + case "failed": + return Style.failureCharacter + default: + return "?" } + } - return '' + function getStatusTextWithPrefix(status) { + return getStatusStep(status) + " " + getStatusText(status) } } diff --git a/atomic_qt_design/qml/Exchange/ExchangeTab.qml b/atomic_qt_design/qml/Exchange/ExchangeTab.qml index eb7f4e209e..a81ed16416 100644 --- a/atomic_qt_design/qml/Exchange/ExchangeTab.qml +++ b/atomic_qt_design/qml/Exchange/ExchangeTab.qml @@ -1,28 +1,27 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import "../Components" import "../Constants" DefaultText { property int dashboard_index - - property bool hovered: false + property bool highlight: exchange.current_page === dashboard_index // Override property var onClick: () => {} id: txt - font.pixelSize: Style.textSize2 - font.family: "Montserrat" - font.bold: exchange.current_page === dashboard_index - color: font.bold ? Style.colorWhite1 : hovered ? Style.colorWhite4 : Style.colorWhite5 + font.pixelSize: Style.textSizeMid2 + font.family: Style.font_family + font.weight: highlight ? Font.Medium : Font.Light + color: highlight ? Style.colorWhite1 : mouse_area.containsMouse ? Style.colorWhite4 : Style.colorWhite5 MouseArea { + id: mouse_area hoverEnabled: true - onHoveredChanged: hovered = containsMouse width: parent.width height: parent.height onClicked: function() { diff --git a/atomic_qt_design/qml/Exchange/History/History.qml b/atomic_qt_design/qml/Exchange/History/History.qml index 3caaa73e13..63fd603da4 100644 --- a/atomic_qt_design/qml/Exchange/History/History.qml +++ b/atomic_qt_design/qml/Exchange/History/History.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" import ".." @@ -9,49 +9,27 @@ import ".." Item { id: exchange_history - property var all_recent_swaps: ({}) - function inCurrentPage() { return exchange.inCurrentPage() && exchange.current_page === General.idx_exchange_history } function reset() { - update_timer.restart() - update_timer.running = inCurrentPage() - all_recent_swaps = {} } function onOpened() { - updateRecentSwaps() - } - - function updateRecentSwaps() { - all_recent_swaps = API.get().get_recent_swaps() - } - - function getRecentSwaps() { - return General.filterRecentSwaps(all_recent_swaps, "include") + API.get().orders_mdl.orders_proxy_mdl.setFilterFixedString("") + API.get().orders_mdl.orders_proxy_mdl.is_history = true + API.get().refresh_orders_and_swaps() } property string recover_funds_result: '{}' - function onRecoverFunds(uuid) { - const result = API.get().recover_fund(uuid) - console.log(result) + function onRecoverFunds(order_id) { + const result = API.get().recover_fund(order_id) + console.log("Refund result: ", result) recover_funds_result = result recover_funds_modal.open() - updateRecentSwaps() - } - - Timer { - id: update_timer - running: inCurrentPage() - repeat: true - interval: 5000 - onTriggered: { - if(inCurrentPage()) updateRecentSwaps() - } } ColumnLayout { @@ -63,18 +41,12 @@ Item { SwapList { title: API.get().empty_string + (qsTr("Recent Swaps")) - items: getRecentSwaps() + items: API.get().orders_mdl } } OrderModal { id: order_modal - details: General.formatOrder(getRecentSwaps().map(o => o.uuid).indexOf(order_modal.current_item_uuid) !== -1 ? - getRecentSwaps()[getRecentSwaps().map(o => o.uuid).indexOf(order_modal.current_item_uuid)] : default_details) - - onDetailsChanged: { - if(details.is_default) close() - } } LogModal { diff --git a/atomic_qt_design/qml/Exchange/History/SwapList.qml b/atomic_qt_design/qml/Exchange/History/SwapList.qml index 64a4d775b6..319cdd160b 100644 --- a/atomic_qt_design/qml/Exchange/History/SwapList.qml +++ b/atomic_qt_design/qml/Exchange/History/SwapList.qml @@ -1,21 +1,21 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" import ".." -Rectangle { +InnerBackground { property string title - property alias items: list.model + property var items // Override property var postCancelOrder: () => {} // Local - function onCancelOrder(uuid) { - API.get().cancel_order(uuid) + function onCancelOrder(order_id) { + API.get().cancel_order(order_id) postCancelOrder() } @@ -29,7 +29,7 @@ Rectangle { height: parent.height DefaultText { - text: API.get().empty_string + (title + " (" + items.length + ")") + text_value: API.get().empty_string + (title + " (" + items.length + ")") Layout.alignment: Qt.AlignHCenter | Qt.AlignTop Layout.topMargin: 10 @@ -50,21 +50,20 @@ Rectangle { Layout.topMargin: 20 color: Style.colorWhite5 - text: API.get().empty_string + (qsTr("You don't have recent orders.")) + text_value: API.get().empty_string + (qsTr("You don't have recent orders.")) } // List - ListView { + DefaultListView { id: list - ScrollBar.vertical: ScrollBar {} Layout.fillWidth: true Layout.fillHeight: true - clip: true + model: items.orders_proxy_mdl // Row delegate: OrderLine { - item: General.formatOrder(model.modelData) + details: model } } } diff --git a/atomic_qt_design/qml/Exchange/OrderContent.qml b/atomic_qt_design/qml/Exchange/OrderContent.qml index 380306ca95..2fe257736d 100644 --- a/atomic_qt_design/qml/Exchange/OrderContent.qml +++ b/atomic_qt_design/qml/Exchange/OrderContent.qml @@ -1,49 +1,57 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import "../Components" import "../Constants" // Content -Rectangle { - property var item +Item { + property var details property bool in_modal: false - color: "transparent" + readonly property bool is_placed_order: !details ? false : + details.order_id !== '' // Base Icon - Image { + DefaultImage { id: base_icon - source: General.coinIcon(item.my_info.my_coin) + source: General.coinIcon(!details ? "KMD" : + details.base_coin) fillMode: Image.PreserveAspectFit width: in_modal ? Style.textSize5 : Style.textSize3 - anchors.horizontalCenter: base_amount.horizontalCenter + + anchors.left: parent.left + anchors.leftMargin: parent.width * 0.2 } // Rel Icon - Image { + DefaultImage { id: rel_icon - source: General.coinIcon(item.my_info.other_coin) + source: General.coinIcon(!details ? "KMD" : + details.rel_coin) fillMode: Image.PreserveAspectFit width: base_icon.width - anchors.horizontalCenter: rel_amount.horizontalCenter + anchors.right: parent.right + anchors.rightMargin: base_icon.anchors.leftMargin } // Base Amount DefaultText { id: base_amount - text: API.get().empty_string + ("~ " + General.formatCrypto("", item.my_info.my_amount, item.my_info.my_coin)) + text_value: API.get().empty_string + (!details ? "" : + "~ " + General.formatCrypto("", details.base_amount, details.base_coin)) font.pixelSize: in_modal ? Style.textSize2 : Style.textSize - anchors.left: parent.left + anchors.horizontalCenter: base_icon.horizontalCenter anchors.top: base_icon.bottom anchors.topMargin: 10 + privacy: is_placed_order } // Swap icon - Image { + DefaultImage { source: General.image_path + "exchange-exchange.svg" width: base_amount.font.pixelSize height: width @@ -54,65 +62,75 @@ Rectangle { // Rel Amount DefaultText { id: rel_amount - text: API.get().empty_string + ("~ " + General.formatCrypto("", item.my_info.other_amount, item.my_info.other_coin)) + text_value: API.get().empty_string + (!details ? "" : + "~ " + General.formatCrypto("", details.rel_amount, details.rel_coin)) font.pixelSize: base_amount.font.pixelSize - anchors.right: parent.right + + anchors.horizontalCenter: rel_icon.horizontalCenter anchors.top: base_amount.top + privacy: is_placed_order } - // UUID + // Order ID DefaultText { - id: uuid - visible: !in_modal && item.uuid !== '' - text: API.get().empty_string + ((item.is_recent_swap ? qsTr("Swap ID") : qsTr("UUID")) + ": " + item.uuid) + id: order_id + visible: !in_modal && is_placed_order + text_value: API.get().empty_string + (!details ? "" : + qsTr("ID") + ": " + details.order_id) color: Style.colorTheme2 anchors.top: base_amount.bottom anchors.topMargin: base_amount.anchors.topMargin + privacy: is_placed_order } // Status Text DefaultText { - visible: !in_modal && (item.events !== undefined || item.am_i_maker === false) - color: visible ? getStatusColor(item) : '' + visible: !details ? false : !in_modal && (details.is_swap || !details.is_maker) + color: !details ? "white" : visible ? getStatusColor(details.order_status) : '' anchors.horizontalCenter: parent.horizontalCenter anchors.top: base_icon.top - text: API.get().empty_string + (visible ? getStatusTextWithPrefix(item) : '') + text_value: API.get().empty_string + (!details ? "" : + visible ? getStatusTextWithPrefix(details.order_status) : '') } // Date DefaultText { id: date - visible: !in_modal && item.date !== '' - text: API.get().empty_string + (item.date) + visible: !details ? false : !in_modal && details.date !== '' + text_value: API.get().empty_string + (!details ? "" : + details.date) color: Style.colorTheme2 - anchors.top: uuid.bottom + anchors.top: order_id.bottom anchors.topMargin: base_amount.anchors.topMargin } // Maker/Taker DefaultText { - visible: !in_modal && item.uuid !== '' - text: API.get().empty_string + (item.am_i_maker ? qsTr("Maker Order"): qsTr("Taker Order")) - color: Style.colorWhite6 + visible: !in_modal && is_placed_order + text_value: API.get().empty_string + (!details ? "" : + details.is_maker ? qsTr("Maker Order"): qsTr("Taker Order")) + color: Style.colorThemeDarkLight anchors.verticalCenter: date.verticalCenter anchors.horizontalCenter: parent.horizontalCenter } // Cancel button DangerButton { - visible: !in_modal && item.cancellable !== undefined && item.cancellable + visible: !details ? false : + !in_modal && details.cancellable anchors.right: parent.right anchors.bottom: date.bottom text: API.get().empty_string + (qsTr("Cancel")) - onClicked: onCancelOrder(item.uuid) + onClicked: onCancelOrder(details.order_id) } // Recover Funds button PrimaryButton { - visible: !in_modal && item.is_recoverable !== undefined && item.is_recoverable + visible: !details ? false : + !in_modal && details.recoverable anchors.right: parent.right anchors.bottom: date.bottom text: API.get().empty_string + (qsTr("Recover Funds")) - onClicked: onRecoverFunds(item.uuid) + onClicked: { if(details) onRecoverFunds(details.order_id) } } } diff --git a/atomic_qt_design/qml/Exchange/OrderLine.qml b/atomic_qt_design/qml/Exchange/OrderLine.qml index 793c13d4c3..d4d95779b5 100644 --- a/atomic_qt_design/qml/Exchange/OrderLine.qml +++ b/atomic_qt_design/qml/Exchange/OrderLine.qml @@ -1,26 +1,24 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import "../Components" import "../Constants" Rectangle { - property var item + property var details width: list.width height: 175 - property bool hovered: false - - color: hovered ? Style.colorTheme8 : "transparent" + color: mouse_area.containsMouse ? Style.colorTheme8 : "transparent" MouseArea { + id: mouse_area anchors.fill: parent hoverEnabled: true - onHoveredChanged: hovered = containsMouse onClicked: { - order_modal.current_item_uuid = item.uuid + order_modal.details = details order_modal.open() } } @@ -32,7 +30,7 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 20 - item: parent.item + details: parent.details } HorizontalLine { diff --git a/atomic_qt_design/qml/Exchange/OrderModal.qml b/atomic_qt_design/qml/Exchange/OrderModal.qml index 742eced82a..9c44d4d80e 100644 --- a/atomic_qt_design/qml/Exchange/OrderModal.qml +++ b/atomic_qt_design/qml/Exchange/OrderModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -9,10 +9,12 @@ import "../Constants" DefaultModal { id: root - width: 650 - readonly property var default_details: ({"is_default": true, "price":"","date":"","base":"","rel":"","cancellable":true,"am_i_maker":true,"base_amount":"1","rel_amount":"1","uuid":""}) + width: 900 property var details - property string current_item_uuid: "" + + onDetailsChanged: { + if(!details) root.close() + } // Inside modal ColumnLayout { @@ -21,20 +23,24 @@ DefaultModal { anchors.horizontalCenter: parent.horizontalCenter ModalHeader { - title: API.get().empty_string + (details.is_recent_swap ? qsTr("Swap Details") : qsTr("Order Details")) + title: API.get().empty_string + (!details ? "" : + details.is_swap ? qsTr("Swap Details") : qsTr("Order Details")) } // Complete image - Image { - visible: details.is_recent_swap !== undefined && getStatus(details) === status_swap_successful + DefaultImage { + visible: !details ? false : + details.is_swap && details.order_status === "successful" Layout.alignment: Qt.AlignHCenter source: General.image_path + "exchange-trade-complete.svg" } - BusyIndicator { - visible: details.is_recent_swap !== undefined && - getStatus(details) !== status_swap_successful && - getStatus(details) !== status_swap_failed + // Loading symbol + DefaultBusyIndicator { + visible: !details ? false : + details.is_swap && + details.order_status !== "successful" && + details.order_status !== "failed" Layout.alignment: Qt.AlignHCenter } @@ -43,11 +49,12 @@ DefaultModal { Layout.alignment: Qt.AlignHCenter Layout.topMargin: 20 font.pixelSize: Style.textSize3 - visible: getStatus(details) !== status_swap_not_swap && // Is order - (details.events !== undefined || // Has events, ongoing or - details.am_i_maker === false) // Taker order with no events - color: visible ? getStatusColor(details) : '' - text: API.get().empty_string + (visible ? getStatusTextWithPrefix(details) : '') + visible: !details ? false : + details.is_swap || !details.is_maker + color: !details ? "white" : + visible ? getStatusColor(details.order_status) : '' + text_value: API.get().empty_string + (!details ? "" : + visible ? getStatusTextWithPrefix(details.order_status) : '') } OrderContent { @@ -57,7 +64,7 @@ DefaultModal { Layout.rightMargin: Layout.leftMargin height: 120 Layout.alignment: Qt.AlignHCenter - item: details + details: root.details in_modal: true } @@ -69,8 +76,9 @@ DefaultModal { // Maker/Taker DefaultText { - text: API.get().empty_string + (details.am_i_maker ? qsTr("Maker Order"): qsTr("Taker Order")) - color: Style.colorWhite6 + text_value: API.get().empty_string + (!details ? "" : + details.is_maker ? qsTr("Maker Order"): qsTr("Taker Order")) + color: Style.colorThemeDarkLight Layout.alignment: Qt.AlignRight } @@ -79,17 +87,8 @@ DefaultModal { Layout.topMargin: -20 title: API.get().empty_string + (qsTr("Refund State")) - field.text: { - let str = API.get().empty_string - const e = getLastEvent(details) - - if(e.state === "TakerPaymentWaitRefundStarted" || - e.state === "MakerPaymentWaitRefundStarted") { - str += qsTr("Your swap failed but the auto-refund process for your payment started already. Please wait and keep application opened until you receive your payment back") - } - - return str - } + field.text: !details ? "" : + details.order_status === "refunding" ? qsTr("Your swap failed but the auto-refund process for your payment started already. Please wait and keep application opened until you receive your payment back") : "" field.readOnly: true visible: field.text !== '' @@ -98,42 +97,53 @@ DefaultModal { // Date TextWithTitle { title: API.get().empty_string + (qsTr("Date")) - text: API.get().empty_string + (details.date) + text: API.get().empty_string + (!details ? "" : + details.date) visible: text !== '' } - // Swap ID / UUID + // ID TextWithTitle { - title: API.get().empty_string + (details.is_recent_swap ? qsTr("Swap ID") : qsTr("UUID")) - text: API.get().empty_string + (details.uuid) + title: API.get().empty_string + (qsTr("ID")) + text: API.get().empty_string + (!details ? "" : + details.order_id) visible: text !== '' + privacy: true } // Payment ID TextWithTitle { - title: API.get().empty_string + (details.am_i_maker ? qsTr("Maker Payment Sent ID") : qsTr("Maker Payment Spent ID")) - text: API.get().empty_string + (getSwapPaymentID(details, false)) + title: API.get().empty_string + (!details ? "" : + details.is_maker ? qsTr("Maker Payment Sent ID") : qsTr("Maker Payment Spent ID")) + text: API.get().empty_string + (!details ? "" : + details.maker_payment_id) visible: text !== '' + privacy: true } // Payment ID TextWithTitle { - title: API.get().empty_string + (details.am_i_maker ? qsTr("Taker Payment Spent ID") : qsTr("Taker Payment Sent ID")) - text: API.get().empty_string + (getSwapPaymentID(details, true)) + title: API.get().empty_string + (!details ? "" : + details.is_maker ? qsTr("Taker Payment Spent ID") : qsTr("Taker Payment Sent ID")) + text: API.get().empty_string + (!details ? "" : + details.taker_payment_id) visible: text !== '' + privacy: true } // Error ID TextWithTitle { title: API.get().empty_string + (qsTr("Error ID")) - text: API.get().empty_string + (getSwapError(details).state) + text: API.get().empty_string + (!details ? "" : + details.order_error_state) visible: text !== '' } // Error Details TextFieldWithTitle { title: API.get().empty_string + (qsTr("Error Log")) - field.text: API.get().empty_string + (getSwapError(details).data.error) + field.text: API.get().empty_string + (!details ? "" : + details.order_error_message) field.readOnly: true copyable: true @@ -150,21 +160,26 @@ DefaultModal { // Cancel button DangerButton { - visible: details.cancellable !== undefined && details.cancellable + visible: !details ? false : + details.cancellable Layout.fillWidth: true - text: API.get().empty_string + (qsTr("Cancel")) - onClicked: onCancelOrder(details.uuid) + text: API.get().empty_string + (qsTr("Cancel Order")) + onClicked: { if(details) onCancelOrder(details.order_id) } } PrimaryButton { text: API.get().empty_string + (qsTr("View at Explorer")) Layout.fillWidth: true - visible: getSwapPaymentID(details, false) !== '' || getSwapPaymentID(details, true) !== '' + visible: !details ? false : + details.maker_payment_id !== '' || details.taker_payment_id !== '' onClicked: { - const maker_id = getSwapPaymentID(details, false) - const taker_id = getSwapPaymentID(details, true) - if(maker_id !== '') General.viewTxAtExplorer(details.maker_coin, maker_id, true) - if(taker_id !== '') General.viewTxAtExplorer(details.taker_coin, taker_id, true) + if(!details) return + + const maker_id = details.maker_payment_id + const taker_id = details.taker_payment_id + + if(maker_id !== '') General.viewTxAtExplorer(details.is_maker ? details.base_coin : details.rel_coin, maker_id, true) + if(taker_id !== '') General.viewTxAtExplorer(details.is_maker ? details.rel_coin : details.base_coin, taker_id, true) } } } diff --git a/atomic_qt_design/qml/Exchange/Orders/OrderList.qml b/atomic_qt_design/qml/Exchange/Orders/OrderList.qml index 4112969b6b..80ccb988a8 100644 --- a/atomic_qt_design/qml/Exchange/Orders/OrderList.qml +++ b/atomic_qt_design/qml/Exchange/Orders/OrderList.qml @@ -1,26 +1,24 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" import ".." -Rectangle { +InnerBackground { property string title - property alias items: list.model + property var items Layout.fillWidth: true Layout.fillHeight: true - color: Style.colorTheme7 - radius: Style.rectangleCornerRadius ColumnLayout { width: parent.width height: parent.height DefaultText { - text: API.get().empty_string + (title + " (" + items.length + ")") + text_value: API.get().empty_string + (title + " (" + items.length + ")") Layout.alignment: Qt.AlignHCenter | Qt.AlignTop Layout.topMargin: 10 @@ -41,21 +39,20 @@ Rectangle { Layout.topMargin: 20 color: Style.colorWhite5 - text: API.get().empty_string + (qsTr("You don't have any orders.")) + text_value: API.get().empty_string + (qsTr("You don't have any orders.")) } // List - ListView { + DefaultListView { id: list - ScrollBar.vertical: ScrollBar {} Layout.fillWidth: true Layout.fillHeight: true - clip: true + model: items.orders_proxy_mdl // Row delegate: OrderLine { - item: General.formatOrder(model.modelData) + details: model } } } diff --git a/atomic_qt_design/qml/Exchange/Orders/Orders.qml b/atomic_qt_design/qml/Exchange/Orders/Orders.qml index 1976e742a0..ba2524970c 100644 --- a/atomic_qt_design/qml/Exchange/Orders/Orders.qml +++ b/atomic_qt_design/qml/Exchange/Orders/Orders.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" import ".." @@ -10,14 +10,11 @@ Item { id: exchange_orders property string base - property var all_orders: ({}) - property var all_recent_swaps: ({}) - property var all_orders_merged: ([]) + property var orders_model: API.get().orders_mdl // Local - function onCancelOrder(uuid) { - API.get().cancel_order(uuid) - updateOrders() + function onCancelOrder(order_id) { + API.get().cancel_order(order_id) } @@ -26,101 +23,22 @@ Item { exchange.current_page === General.idx_exchange_orders } - onBaseChanged: updateOrders() - - function reset() { - all_orders = {} - all_recent_swaps = {} - all_orders_merged = [] - update_timer.running = false - } - - function onOpened() { - // Force a refresh, myOrdersUpdated will call updateOrders once it's done - API.get().refresh_infos() - } - - Component.onCompleted: { - API.get().myOrdersUpdated.connect(updateOrders) - } - - function getRecentSwaps(ticker) { - return General.filterRecentSwaps(all_recent_swaps, "exclude", ticker) - } - - function updateOrders() { - all_orders = API.get().get_my_orders() - all_recent_swaps = API.get().get_recent_swaps() - all_orders_merged = getAllOrders() - update_timer.running = true - } - - function baseCoins() { - return API.get().enabled_coins - } - - function mergeOrders(a, b) { - a.taker_orders = a.taker_orders.concat(b.taker_orders) - a.maker_orders = a.maker_orders.concat(b.maker_orders) - return a - } - - readonly property var empty_orders: ({ maker_orders: [], taker_orders: [] }) - function getOrders(ticker) { - let mixed_orders = General.clone(empty_orders) - - if(ticker !== "" && all_orders[ticker] !== undefined) { - // Add recent swaps - getRecentSwaps(ticker).map(s => { - mixed_orders[s.type === "Taker" ? "taker_orders" : "maker_orders"].push(s) - }) - - // Add normal orders - mixed_orders = mergeOrders(mixed_orders, all_orders[ticker]) - } - - return mixed_orders + function applyFilter() { + orders_model.orders_proxy_mdl.setFilterFixedString(show_all_coins.checked ? "" : base) } - function getAllOrders() { - let orders = General.clone(empty_orders) + onBaseChanged: applyFilter() - if(show_all_coins.checked) { - for(const c of baseCoins()) { - orders = mergeOrders(orders, getOrders(c.ticker)) - } - } - else orders = getOrders(base) + function reset() { } - // Merge two lists - let array = orders.taker_orders.concat(orders.maker_orders) - - // Remove duplicates - return array.filter((o, index, self) => index === self.findIndex(t => t.uuid === o.uuid)) + function onOpened() { + applyFilter() + API.get().orders_mdl.orders_proxy_mdl.is_history = false + API.get().refresh_orders_and_swaps() } function changeTicker(ticker) { - combo_base.currentIndex = baseCoins().map(c => c.ticker).indexOf(ticker) - } - - function cancellableOrderExists() { - for(const i in all_orders_merged) { - const o = all_orders_merged[i] - if(o.cancellable !== undefined && o.cancellable) - return true - } - - return false - } - - Timer { - id: update_timer - running: false - repeat: true - interval: 5000 - onTriggered: { - if(inCurrentPage()) updateOrders() - } + combo_base.currentIndex = combo_base.model.map(c => c.value).indexOf(ticker) } // Orders page quick refresher, used right after a fresh successful trade @@ -132,7 +50,7 @@ Item { Timer { id: refresh_timer repeat: true - interval: refresh_faster.running ? 500 : 5000 + interval: 1000 triggeredOnStart: true onTriggered: { if(inCurrentPage()) { @@ -143,9 +61,10 @@ Item { Timer { id: refresh_faster - interval: 10000 + interval: 2000 onTriggered: { console.log("Refreshing faster for " + interval + " ms!") + refresh_timer.stop() } } @@ -157,24 +76,25 @@ Item { spacing: 15 // Select coins row - Rectangle { + FloatingBackground { Layout.alignment: Qt.AlignHCenter - implicitWidth: childrenRect.width - implicitHeight: childrenRect.height + width: layout.width + height: layout.height - color: Style.colorTheme7 - radius: Style.rectangleCornerRadius - - RowLayout { + RowLayout { + id: layout + Switch { id: show_all_coins Layout.leftMargin: 15 text: API.get().empty_string + (qsTr("Show All Coins")) - onCheckedChanged: updateOrders() + + checked: true + onCheckedChanged: applyFilter() } // Base - Image { + DefaultImage { Layout.leftMargin: 15 source: General.coinIcon(base) Layout.preferredWidth: 32 @@ -189,15 +109,17 @@ Item { Layout.bottomMargin: 10 Layout.rightMargin: 15 - model: General.fullNamesOfCoins(baseCoins()) + textRole: "text" + + model: General.fullNamesOfCoins(API.get().enabled_coins) onCurrentTextChanged: { - base = baseCoins()[currentIndex].ticker + base = model[currentIndex].value } } DangerButton { text: API.get().empty_string + (show_all_coins.checked ? qsTr("Cancel All Orders") : qsTr("Cancel All %1 Orders", "TICKER").arg(base)) - enabled: cancellableOrderExists() + enabled: orders_model.length > 0 onClicked: { if(show_all_coins.checked) API.get().cancel_all_orders() else API.get().cancel_all_orders_by_ticker(base) @@ -216,18 +138,12 @@ Item { OrderList { title: API.get().empty_string + (show_all_coins.checked ? qsTr("All Orders") : qsTr("All %1 Orders", "TICKER").arg(base)) - items: all_orders_merged + items: orders_model } } OrderModal { id: order_modal - details: General.formatOrder(all_orders_merged.map(o => o.uuid).indexOf(order_modal.current_item_uuid) !== -1 ? - all_orders_merged[all_orders_merged.map(o => o.uuid).indexOf(order_modal.current_item_uuid)] : default_details) - - onDetailsChanged: { - if(details.is_default) close() - } } } } diff --git a/atomic_qt_design/qml/Exchange/Trade/CandleStickChart.qml b/atomic_qt_design/qml/Exchange/Trade/CandleStickChart.qml new file mode 100644 index 0000000000..6f1edfc71f --- /dev/null +++ b/atomic_qt_design/qml/Exchange/Trade/CandleStickChart.qml @@ -0,0 +1,641 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtCharts 2.3 +import "../../Components" +import "../../Constants" + +// List +Item { + id: root + readonly property double y_margin: 0.02 + + readonly property bool pair_supported: cs_mapper.model.is_current_pair_supported + + function getChartSeconds() { + const idx = combo_time.currentIndex + const timescale = General.chart_times[idx] + return General.time_seconds[timescale] + } + + Component.onCompleted: { + API.get().candlestick_charts_mdl.modelReset.connect(chartUpdated) + API.get().candlestick_charts_mdl.chartFullyModelReset.connect(chartFullyReset) + } + + function chartFullyReset() { + updater.locked_min_max_value = true + update_last_value_y_timer.restart() + chartUpdated() + } + + function chartUpdated() { + const mapper = cs_mapper + const model = mapper.model + + // Update last value line + const last_idx = series.count - 1 + const last_open = model.data(model.index(last_idx, mapper.openColumn), 0) + const last_close = model.data(model.index(last_idx, mapper.closeColumn), 0) + if(last_close === undefined) return + + series.last_value = last_close + series.last_value_green = last_close >= last_open + + // Get timestamp caps + first_value_timestamp = model.data(model.index(0, mapper.timestampColumn), 0) + last_value_timestamp = model.data(model.index(last_idx, mapper.timestampColumn), 0) + global_min_value = model.global_min_value + global_max_value = model.global_max_value + + // Update other stuff + updater.updateChart(true) + } + + property double first_value_timestamp + property double last_value_timestamp + property double global_min_value + property double global_max_value + + ChartView { + id: volume_chart + + visible: chart.visible + anchors.top: chart.bottom + anchors.bottom: parent.bottom + anchors.left: chart.left + anchors.right: chart.right + + margins.top: 0 + margins.left: 0 + margins.bottom: 0 + margins.right: 0 + + antialiasing: chart.antialiasing + legend.visible: chart.legend.visible + backgroundColor: chart.backgroundColor + plotArea: Qt.rect(chart.plotArea.x, 0, chart.plotArea.width, height) + + CandlestickSeries { + id: series_area + + HCandlestickModelMapper { + model: cs_mapper.model + + timestampColumn: 0 + openColumn: 6 + highColumn: 7 + lowColumn: 8 + closeColumn: 9 + + firstSetRow: 0 + lastSetRow: model.series_size + } + + increasingColor: Style.colorGreen3 + decreasingColor: Style.colorRed3 + bodyOutlineVisible: false + + property double visible_max: cs_mapper.model.visible_max_volume + onVisible_maxChanged: value_axis_area.updateAxes() + + axisX: DateTimeAxis { + min: cs_mapper.model.series_from + max: cs_mapper.model.series_to + + tickCount: 10 + titleVisible: false + lineVisible: true + labelsFont.family: Style.font_family + labelsFont.weight: Font.Bold + gridLineColor: Style.colorChartGrid + labelsColor: Style.colorChartText + color: Style.colorChartLegendLine + format: "MMM d" + } + axisY: ValueAxis { + id: value_axis_area + + function updateAxes() { + // This will be always same, small size at bottom + min = 0 + max = series_area.visible_max + } + + visible: false + onRangeChanged: updateAxes() + } + } + } + + ChartView { + id: chart + + visible: pair_supported && series.count > 0 && series.count === cs_mapper.model.series_size && !cs_mapper.model.is_fetching + + height: parent.height * 0.9 + width: parent.width + + margins.top: 0 + margins.left: 0 + margins.bottom: 0 + margins.right: 0 + + antialiasing: true + legend.visible: false + backgroundColor: "transparent" + + + Timer { + id: update_last_value_y_timer + interval: 50 + repeat: false + running: false + onTriggered: series.updateLastValueY() + } + + // Moving Average 1 + LineSeries { + id: series_ma1 + + VXYModelMapper { + model: cs_mapper.model + xColumn: 0 + yColumn: 10 + } + + readonly property int num: 20 + + color: Style.colorChartMA1 + + width: 1 + + pointsVisible: false + + axisX: series.axisX + axisYRight: series.axisYRight + } + + // Moving Average 2 + LineSeries { + id: series_ma2 + + VXYModelMapper { + model: cs_mapper.model + xColumn: 0 + yColumn: 11 + } + + readonly property int num: 50 + + color: Style.colorChartMA2 + + width: series_ma1.width + + pointsVisible: false + + axisX: series.axisX + axisYRight: series.axisYRight + } + + // Price, front + CandlestickSeries { + id: series + + HCandlestickModelMapper { + id: cs_mapper + model: API.get().candlestick_charts_mdl + + timestampColumn: 0 + openColumn: 1 + highColumn: 2 + lowColumn: 3 + closeColumn: 4 + + firstSetRow: 0 + lastSetRow: model.series_size + } + + property double global_max: 0 + property double last_value: 0 + property bool last_value_green: true + + function updateLastValueY() { + const area = chart.plotArea + horizontal_line.y = Math.max(Math.min(chart.mapToPosition(Qt.point(0, series.last_value), series).y, area.y + area.height), area.y) + } + + increasingColor: Style.colorGreen + decreasingColor: Style.colorRed + bodyOutlineVisible: false + + axisX: DateTimeAxis { + id: date_time_axis + min: cs_mapper.model.series_from + max: cs_mapper.model.series_to + + tickCount: 10 + titleVisible: false + lineVisible: true + labelsFont.family: Style.font_family + labelsFont.weight: Font.Bold + gridLineColor: Style.colorChartGrid + labelsColor: Style.colorChartText + color: Style.colorChartLegendLine + format: "MMM d" + } + axisYRight: ValueAxis { + id: value_axis + + min: cs_mapper.model.min_value + max: cs_mapper.model.max_value + + titleVisible: series.axisX.titleVisible + lineVisible: series.axisX.lineVisible + labelsFont: series.axisX.labelsFont + gridLineColor: series.axisX.gridLineColor + labelsColor: series.axisX.labelsColor + color: series.axisX.color + + labelFormat: "%llf" + + onRangeChanged: { + // if(min < 0) value_axis.min = 0 + + // const max_val = value_axis.global_max * (1 + y_margin) + // if(max > max_val) value_axis.max = max_val + } + } + } + + // Horizontal line + Canvas { + id: horizontal_line + readonly property color color: series.last_value_green ? Style.colorGreen : Style.colorRed + onColorChanged: requestPaint() + + anchors.left: parent.left + width: parent.width + height: 1 + + onPaint: { + var ctx = getContext("2d"); + + ctx.setLineDash([1, 1]); + ctx.lineWidth = 1.5; + ctx.strokeStyle = color + + ctx.beginPath() + ctx.moveTo(0, 0) + ctx.lineTo(width, 0) + ctx.stroke() + } + + Rectangle { + color: parent.color + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + width: Math.max(value_y_text.width, 30) + height: value_y_text.height + DefaultText { + id: value_y_text + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + text_value: General.formatDouble(series.last_value, General.recommendedPrecision) + font.pixelSize: series.axisYRight.labelsFont.pixelSize + color: Style.colorChartLineText + } + } + } + + // Cursor Horizontal line + Rectangle { + id: cursor_horizontal_line + anchors.left: parent.left + width: parent.width + height: 1 + + visible: mouse_area.containsMouse + + color: Style.colorBlue + + Rectangle { + color: parent.color + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + width: Math.max(cursor_y_text.width, 30) + height: cursor_y_text.height + DefaultText { + id: cursor_y_text + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: series.axisYRight.labelsFont.pixelSize + } + } + } + + // Cursor Vertical line + Rectangle { + id: cursor_vertical_line + + anchors.top: parent.top + width: 1 + height: parent.height + volume_chart.height + 6 + + visible: cursor_horizontal_line.visible + color: cursor_horizontal_line.color + + Rectangle { + color: parent.color + anchors.top: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + + width: cursor_x_text.width + height: cursor_x_text.height + + DefaultText { + id: cursor_x_text + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: series.axisYRight.labelsFont.pixelSize + } + } + } + + MouseArea { + id: mouse_area + anchors.fill: parent + + onWheel: updater.delta_wheel_y += wheel.angleDelta.y + + // Drag scroll + hoverEnabled: true + } + + // Time selection + DefaultComboBox { + id: combo_time + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: 25 + anchors.leftMargin: 35 + width: 75 + height: 30 + flat: true + font.pixelSize: Style.textSizeSmall3 + + currentIndex: 5 // 1h + model: General.chart_times + + property bool initialized: false + onCurrentTextChanged: { + if(initialized) cs_mapper.model.current_range = "" + getChartSeconds() + else initialized = true + } + } + + // Cursor values + DefaultText { + id: cursor_values + anchors.left: combo_time.right + anchors.top: combo_time.top + anchors.leftMargin: 10 + color: series.axisX.labelsColor + font.pixelSize: Style.textSizeSmall + } + + // MA texts + DefaultText { + anchors.left: cursor_values.left + anchors.bottom: combo_time.bottom + font.pixelSize: cursor_values.font.pixelSize + text_value: `MA ${series_ma1.num}    MA ${series_ma2.num}` + } + + + + // Canvas updater + Timer { + id: update_block_timer + running: false + repeat: false + interval: 1 + onTriggered: updater.can_update = true + } + Timer { + id: updater + property bool can_update: true + + readonly property double scroll_speed_x: 0.0001 + readonly property double scroll_speed_y: 0.05 + property double delta_wheel_y: 0 + property double click_started_inside_area + property double prev_mouse_pressed + property double prev_mouse_x + property double prev_mouse_y + + interval: 1 + running: mouse_area.containsMouse + repeat: true + onTriggered: updateChart() + + property bool locked_min_max_value: true + readonly property double visible_min_value: cs_mapper.model.visible_min_value + readonly property double visible_max_value: cs_mapper.model.visible_max_value + + onVisible_min_valueChanged: { + if(locked_min_max_value) { + cs_mapper.model.min_value = visible_min_value + } + } + + onVisible_max_valueChanged: { + if(locked_min_max_value) { + cs_mapper.model.max_value = visible_max_value + } + } + + function capDateStart(timestamp, current_distance) { + return Math.max(timestamp, first_value_timestamp - current_distance*0.9) + } + + function capDateEnd(timestamp, current_distance) { + return Math.min(timestamp, last_value_timestamp + current_distance*0.9) + } + + function capPriceMin(price) { + return Math.max(price, global_min_value) + } + function capPriceMax(price) { + return Math.min(price, global_max_value) + } + + function getMinTimeDifference() { + return 20 * getChartSeconds() * 1000 + } + + function getMinValueDifference() { + return series.last_value * 0.05 + } + + function scrollHorizontal(pixels) { + const model = cs_mapper.model + const min = model.series_from.getTime() + const max = model.series_to.getTime() + + const diff = max - min + const scale = pixels / chart.plotArea.width + const amount = diff * scale + + // Cap without zooming, more complex + let new_max = capDateEnd(max - amount, diff) + const new_min = capDateStart(new_max - diff, diff) + new_max = capDateEnd(new_min + diff, diff) + + if(new_max - new_min < getMinTimeDifference()) return + model.series_from = new Date(new_min) + model.series_to = new Date(new_max) + } + + function scrollVertical(pixels) { + if(locked_min_max_value) return + + const model = cs_mapper.model + const min = model.min_value + const max = model.max_value + const scale = pixels / chart.plotArea.height + const amount = (max - min) * scale + + const new_min = capPriceMin(model.min_value + amount) + const new_max = capPriceMax(model.max_value + amount) + if(new_max - new_min < getMinValueDifference()) return + model.min_value = new_min + model.max_value = new_max + } + + function zoomHorizontal(factor) { + const model = cs_mapper.model + const min = model.series_from.getTime() + const max = model.series_to.getTime() + + const diff = max - min + + const new_min = capDateStart(min * (1 - factor), diff) + const new_max = capDateEnd(max * (1 + 0.2*factor), diff) + if(new_max - new_min < getMinTimeDifference()) return + model.series_from = new Date(new_min) + model.series_to = new Date(new_max) + } + + function zoomVertical(factor) { + locked_min_max_value = false + + const model = cs_mapper.model + + const new_min = capPriceMin(model.min_value * (1 - factor)) + const new_max = capPriceMax(model.max_value * (1 + factor)) + if(new_max - new_min < getMinValueDifference()) return + model.min_value = new_min + model.max_value = new_max + } + + function updateChart(force) { + if(!can_update && !force) return + can_update = false + + // Update + const mouse_x = mouse_area.mouseX + const mouse_y = mouse_area.mouseY + const diff_x = mouse_x - prev_mouse_x + const diff_y = mouse_y - prev_mouse_y + prev_mouse_x = mouse_x + prev_mouse_y = mouse_y + + const area = chart.plotArea + const inside_plot_area = mouse_x < area.x + area.width + + const curr_mouse_pressed = mouse_area.containsPress + const clicked = !prev_mouse_pressed && curr_mouse_pressed + prev_mouse_pressed = curr_mouse_pressed + + if(clicked) { + click_started_inside_area = inside_plot_area + } + + // Update drag + if(curr_mouse_pressed) { + if(click_started_inside_area && diff_x !== 0) { + scrollHorizontal(diff_x) + } + + if(diff_y !== 0) { + if(click_started_inside_area) { + scrollVertical(diff_y) + } + else { + zoomVertical((diff_y/area.height) * scroll_speed_y) + } + } + } + + // Update zoom + const zoomed = delta_wheel_y !== 0 + if (zoomed) { + if(inside_plot_area) zoomHorizontal((-delta_wheel_y/360) * scroll_speed_x) + else zoomVertical((-delta_wheel_y/360) * scroll_speed_y) + + delta_wheel_y = 0 + } + + // Update cursor line + if(curr_mouse_pressed || zoomed || diff_x !== 0 || diff_y !== 0) { + // Map mouse position to value + const cp = chart.mapToValue(Qt.point(mouse_x, mouse_y), series) + + // Find closest real data + const realData = API.get().find_closest_ohlc_data(getChartSeconds(), cp.x / 1000) + const realDataFound = realData.timestamp + if(realDataFound) { + cursor_vertical_line.x = chart.mapToPosition(Qt.point(realData.timestamp*1000, 0), series).x + } + + // Texts + cursor_x_text.text_value = realDataFound ? General.timestampToDate(realData.timestamp).toString() : "" + cursor_y_text.text_value = General.formatDouble(cp.y, General.recommendedPrecision) + + const highlightColor = realDataFound && realData.close >= realData.open ? Style.colorGreen : Style.colorRed + cursor_values.text_value = realDataFound ? ( + `O:${realData.open}    ` + + `H:${realData.high}    ` + + `L:${realData.low}    ` + + `C:${realData.close}    ` + + `Vol:${realData.volume.toFixed(0)}K` + ) : `` + + // Positions + cursor_horizontal_line.y = mouse_y + } + + series.updateLastValueY() + + // Block this function for a while to allow engine to render + update_block_timer.start() + } + } + } + + DefaultBusyIndicator { + visible: !chart.visible + anchors.centerIn: parent + } +} + + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/atomic_qt_design/qml/Exchange/Trade/ConfirmTradeModal.qml b/atomic_qt_design/qml/Exchange/Trade/ConfirmTradeModal.qml index 89fb8ee695..5eaab320c6 100644 --- a/atomic_qt_design/qml/Exchange/Trade/ConfirmTradeModal.qml +++ b/atomic_qt_design/qml/Exchange/Trade/ConfirmTradeModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" import ".." @@ -10,7 +10,7 @@ import ".." DefaultModal { id: root - width: 800 + width: 1100 // Inside modal ColumnLayout { @@ -30,15 +30,13 @@ DefaultModal { height: 120 Layout.alignment: Qt.AlignHCenter - item: ({ - my_info: { - my_coin: getTicker(true), - other_coin: getTicker(false), - my_amount: form_base.field.text, - other_amount: form_rel.field.text - }, + details: ({ + base_coin: getTicker(true), + rel_coin: getTicker(false), + base_amount: form_base.field.text, + rel_amount: form_rel.field.text, - uuid: '', + order_id: '', date: '', }) in_modal: true @@ -54,12 +52,11 @@ DefaultModal { Layout.fillWidth: true } - Rectangle { + FloatingBackground { Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 10 color: Style.colorTheme5 - radius: 10 width: warning_texts.width + 20 height: warning_texts.height + 20 @@ -71,13 +68,13 @@ DefaultModal { DefaultText { Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (qsTr("This swap request can not be undone and is a final event!")) + text_value: API.get().empty_string + (qsTr("This swap request can not be undone and is a final event!")) } DefaultText { Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (qsTr("This transaction can take up to 10 mins - DO NOT close this application!")) + text_value: API.get().empty_string + (qsTr("This transaction can take up to 10 mins - DO NOT close this application!")) font.pixelSize: Style.textSizeSmall4 } } diff --git a/atomic_qt_design/qml/Exchange/Trade/OrderForm.qml b/atomic_qt_design/qml/Exchange/Trade/OrderForm.qml index c731842ab2..7ef564cfce 100644 --- a/atomic_qt_design/qml/Exchange/Trade/OrderForm.qml +++ b/atomic_qt_design/qml/Exchange/Trade/OrderForm.qml @@ -1,20 +1,25 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 +import QtGraphicalEffects 1.0 + import "../../Components" import "../../Constants" -// Right side -Rectangle { +FloatingBackground { id: root property alias field: input_volume.field property bool my_side: false property bool enabled: true + property alias column_layout: form_layout property bool recursive_update: false + function getFiatText(v, ticker) { + return General.formatFiat('', v === '' ? 0 : API.get().get_fiat_from_amount(ticker, v), API.get().current_fiat) + " " + General.cex_icon + } + function update(new_ticker) { updateTickerList(new_ticker) } @@ -29,6 +34,7 @@ Rectangle { recursive_update = new_ticker !== undefined ticker_list = my_side ? General.getTickersAndBalances(getFilteredCoins()) : General.getTickers(getFilteredCoins()) + update_timer.running = true } @@ -52,7 +58,7 @@ Rectangle { } function canShowFees() { - return my_side && !General.isZero(getVolume()) + return my_side && valid_trade_info && !General.isZero(getVolume()) } function getVolume() { @@ -64,10 +70,14 @@ Rectangle { } function getAnyAvailableCoin(filter_ticker) { - let coins = getFilteredCoins() + let coins = getFilteredCoins().map(c => c.ticker) + + // Filter out ticker if(filter_ticker !== undefined || filter_ticker !== '') - coins = coins.filter(c => c.ticker !== filter_ticker) - return coins.length > 0 ? coins[0].ticker : '' + coins = coins.filter(c => c !== filter_ticker) + + // Pick a random one if prioritized ones do not satisfy + return coins.length > 0 ? coins[0] : '' } function fieldsAreFilled() { @@ -103,26 +113,7 @@ Rectangle { } function getTicker() { - if(combo.currentIndex === -1) return '' - const coins = getFilteredCoins() - - const coin = coins[combo.currentIndex] - - // If invalid index - if(coin === undefined) { - // If there are other coins, select first - if(coins.length > 0) { - combo.currentIndex = 0 - return coins[combo.currentIndex].ticker - } - // If there isn't any, reset index - else { - combo.currentIndex = -1 - return '' - } - } - - return coin.ticker + return ticker_list.length > 0 ? ticker_list[combo.currentIndex].value : "" } function setTicker(ticker) { @@ -189,131 +180,313 @@ Rectangle { } } - color: Style.colorTheme7 - radius: Style.rectangleCornerRadius - - implicitWidth: form_layout.width implicitHeight: form_layout.height - DefaultText { - font.pixelSize: Style.textSize2 - text: API.get().empty_string + (my_side ? qsTr("Sell") : qsTr("Receive")) - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: form_layout.top - anchors.bottomMargin: combo.Layout.rightMargin * 0.5 - } - ColumnLayout { id: form_layout - width: 300 - RowLayout { - Image { - Layout.leftMargin: combo.Layout.rightMargin - source: General.coinIcon(getTicker()) - Layout.preferredWidth: 32 - Layout.preferredHeight: Layout.preferredWidth - } + width: parent.width - DefaultComboBox { - id: combo + ColumnLayout { + Layout.alignment: Qt.AlignTop - enabled: root.enabled + Layout.fillWidth: true + spacing: 15 - Layout.fillWidth: true - Layout.topMargin: 10 - Layout.rightMargin: 15 - - model: ticker_list - onCurrentTextChanged: { - if(!recursive_update) { - resetTradeInfo() - - setPair(my_side) - if(my_side) prev_base = getTicker() - else prev_rel = getTicker() - updateForms(my_side, combo.currentText) - } - } + // Top Line + RowLayout { + id: top_line + Layout.topMargin: parent.spacing + Layout.leftMargin: parent.spacing*2 + Layout.rightMargin: Layout.leftMargin - MouseArea { - visible: !my_side - anchors.fill: parent - onClicked: { - order_receive_modal.open() - } + // Title + DefaultText { + font.pixelSize: Style.textSizeMid2 + text_value: API.get().empty_string + (my_side ? qsTr("Sell") : qsTr("Receive")) + color: my_side ? Style.colorRed : Style.colorGreen + font.weight: Font.Bold } - OrderReceiveModal { - id: order_receive_modal + Arrow { + up: my_side + color: my_side ? Style.colorRed : Style.colorGreen + Layout.leftMargin: 20 + Layout.rightMargin: 20 } - OrderbookModal { - id: orderbook_modal + DefaultImage { + Layout.leftMargin: combo.Layout.rightMargin * 3 + source: General.coinIcon(getTicker()) + Layout.preferredWidth: 32 + Layout.preferredHeight: Layout.preferredWidth } - } - } - RowLayout { - DefaultButton { - Layout.leftMargin: combo.Layout.rightMargin - Layout.topMargin: Layout.rightMargin - Layout.bottomMargin: Layout.rightMargin - visible: my_side - text: API.get().empty_string + (qsTr("MAX")) - onClicked: setMax() - enabled: !shouldBlockInput() + DefaultComboBox { + id: combo + + enabled: root.enabled + + Layout.fillWidth: true + + model: ticker_list + + + textRole: "text" + + onCurrentTextChanged: { + if(!recursive_update) { + updateForms(my_side, combo.currentText) + setPair(my_side) + } + } + + MouseArea { + visible: !my_side + anchors.fill: parent + onClicked: { + order_receive_modal.open() + } + } + + OrderReceiveModal { + id: order_receive_modal + } + + OrderbookModal { + id: orderbook_modal + } + } } - AmountField { - id: input_volume - field.enabled: root.enabled && !shouldBlockInput() + HorizontalLine { Layout.fillWidth: true - Layout.rightMargin: combo.Layout.rightMargin - Layout.leftMargin: Layout.rightMargin - Layout.topMargin: Layout.rightMargin - Layout.bottomMargin: Layout.rightMargin - field.placeholderText: API.get().empty_string + (my_side ? qsTr("Amount to sell") : - field.enabled ? qsTr("Amount to receive") : qsTr("Please fill the send amount")) - field.onTextChanged: onBaseChanged() } - } - - RowLayout { - Layout.leftMargin: combo.Layout.rightMargin - Layout.bottomMargin: Layout.leftMargin ColumnLayout { - Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.leftMargin: top_line.Layout.leftMargin + Layout.rightMargin: top_line.Layout.rightMargin DefaultText { - id: tx_fee_text - text: API.get().empty_string + (canShowFees() ? qsTr('Transaction Fee') + ':' : '') - font.pixelSize: Style.textSizeSmall + text_value: API.get().empty_string + (qsTr("Amount") + ':') + font.pixelSize: Style.textSizeSmall1 } - DefaultText { - text: API.get().empty_string + (canShowFees() ? qsTr('Trading Fee') + ':' : '') - font.pixelSize: tx_fee_text.font.pixelSize + Item { + Layout.fillWidth: true + height: input_volume.height + + AmountField { + id: input_volume + width: parent.width + field.enabled: root.enabled && !shouldBlockInput() + field.placeholderText: API.get().empty_string + (my_side ? qsTr("Amount to sell") : + field.enabled ? qsTr("Amount to receive") : qsTr("Please fill the send amount")) + field.onTextChanged: { + const before_checks = field.text + onBaseChanged() + const after_checks = field.text + + // Update slider only if the value is not from slider, or value got corrected here + if(before_checks !== after_checks || !input_volume_slider.updating_text_field) { + input_volume_slider.updating_from_text_field = true + input_volume_slider.value = parseFloat(field.text) + input_volume_slider.updating_from_text_field = false + } + } + + field.font.pixelSize: Style.textSizeSmall1 + field.font.weight: Font.Bold + } + + DefaultText { + anchors.left: input_volume.left + anchors.top: input_volume.bottom + anchors.topMargin: 5 + + text_value: getFiatText(input_volume.field.text, getTicker()) + font.pixelSize: input_volume.field.font.pixelSize + + CexInfoTrigger {} + } + + DefaultText { + anchors.right: input_volume.right + anchors.rightMargin: 10 + anchors.verticalCenter: input_volume.verticalCenter + + text_value: getTicker() + font.pixelSize: input_volume.field.font.pixelSize + } } } - ColumnLayout { - visible: canShowFees() - Layout.alignment: Qt.AlignRight + + Slider { + id: input_volume_slider + function getRealValue() { + return input_volume_slider.position * (input_volume_slider.to - input_volume_slider.from) + } + + enabled: input_volume.field.enabled + property bool updating_from_text_field: false + property bool updating_text_field: false + readonly property int precision: General.getRecommendedPrecision(to) + visible: my_side + Layout.fillWidth: true + Layout.leftMargin: top_line.Layout.leftMargin + Layout.rightMargin: top_line.Layout.rightMargin + Layout.bottomMargin: top_line.Layout.rightMargin + from: 0 + stepSize: 1/Math.pow(10, precision) + to: parseFloat(getMaxVolume()) + live: false + + onValueChanged: { + if(updating_from_text_field) return + + if(pressed) { + updating_text_field = true + input_volume.field.text = General.formatDouble(value) + updating_text_field = false + } + } DefaultText { - text: API.get().empty_string + (canShowFees() && valid_trade_info ? (General.formatCrypto("", curr_trade_info.tx_fee, curr_trade_info.is_ticker_of_fees_eth ? "ETH" : getTicker(true))) + - // ETH Fees - (hasEthFees() ? " + " + General.formatCrypto("", curr_trade_info.erc_fees, 'ETH') : '') : qsTr("Calculating...")) - font.pixelSize: tx_fee_text.font.pixelSize + visible: parent.pressed + anchors.horizontalCenter: parent.handle.horizontalCenter + anchors.bottom: parent.handle.top + + text_value: General.formatDouble(input_volume_slider.getRealValue(), input_volume_slider.precision) + font.pixelSize: input_volume.field.font.pixelSize + } + + DefaultText { + anchors.left: parent.left + anchors.top: parent.bottom + + text_value: API.get().empty_string + (qsTr("Min")) + font.pixelSize: input_volume.field.font.pixelSize } + DefaultText { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.bottom + text_value: API.get().empty_string + (qsTr("Half")) + font.pixelSize: input_volume.field.font.pixelSize + } DefaultText { - text: API.get().empty_string + (canShowFees() && valid_trade_info ? General.formatCrypto("", curr_trade_info.trade_fee, getTicker(true)) : qsTr("Calculating...")) - font.pixelSize: tx_fee_text.font.pixelSize + anchors.right: parent.right + anchors.top: parent.bottom + + text_value: API.get().empty_string + (qsTr("Max")) + font.pixelSize: input_volume.field.font.pixelSize + } + } + + + // Fees + InnerBackground { + visible: my_side + + radius: 100 + id: bg + Layout.fillWidth: true + Layout.leftMargin: top_line.Layout.leftMargin + Layout.rightMargin: top_line.Layout.rightMargin + Layout.bottomMargin: top_line.Layout.rightMargin + + content: RowLayout { + width: bg.width + height: tx_fee_text.font.pixelSize * 4 + + ColumnLayout { + id: fees + visible: canShowFees() + + spacing: -2 + Layout.leftMargin: 10 + Layout.rightMargin: Layout.leftMargin + Layout.alignment: Qt.AlignLeft + + DefaultText { + id: tx_fee_text + text_value: API.get().empty_string + ((qsTr('Transaction Fee') + ': ' + General.formatCrypto("", curr_trade_info.tx_fee, curr_trade_info.is_ticker_of_fees_eth ? "ETH" : getTicker(true))) + + // ETH Fees + (hasEthFees() ? " + " + General.formatCrypto("", curr_trade_info.erc_fees, 'ETH') : '') + + + // Fiat part + (" ("+ + getFiatText(!hasEthFees() ? curr_trade_info.tx_fee : General.formatDouble((parseFloat(curr_trade_info.tx_fee) + parseFloat(curr_trade_info.erc_fees))), + curr_trade_info.is_ticker_of_fees_eth ? 'ETH' : getTicker(true)) + +")") + + + ) + font.pixelSize: Style.textSizeSmall1 + + CexInfoTrigger {} + } + + DefaultText { + text_value: API.get().empty_string + (qsTr('Trading Fee') + ': ' + General.formatCrypto("", curr_trade_info.trade_fee, getTicker(true)) + + + // Fiat part + (" ("+ + getFiatText(curr_trade_info.trade_fee, getTicker(true)) + +")") + ) + font.pixelSize: tx_fee_text.font.pixelSize + + CexInfoTrigger {} + } + } + + + DefaultText { + visible: !fees.visible + + text_value: API.get().empty_string + (qsTr('Fees will be calculated')) + Layout.alignment: Qt.AlignCenter + font.pixelSize: tx_fee_text.font.pixelSize + } } } } + + + // Trade button + DefaultButton { + visible: !my_side + + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.rightMargin: top_line.Layout.rightMargin + Layout.bottomMargin: top_line.Layout.rightMargin + width: 170 + + text: API.get().empty_string + (qsTr("Trade")) + enabled: valid_trade_info && form_base.isValid() && form_rel.isValid() + onClicked: confirm_trade_modal.open() + } + } + + + opacity_mask_enabled: true + mask: OpacityMask { + source: rect + invert: true + maskSource: Item { + width: rect.width; + height: rect.height; + Rectangle { + anchors.verticalCenter: parent.verticalCenter + anchors.left: my_side ? parent.right : undefined + anchors.leftMargin: my_side ? -17.5 : 0 + anchors.right: my_side ? undefined : parent.left + anchors.rightMargin: my_side ? 0 : -17.5 + width: 110; height: width; radius: Infinity + } + } } } diff --git a/atomic_qt_design/qml/Exchange/Trade/OrderReceiveModal.qml b/atomic_qt_design/qml/Exchange/Trade/OrderReceiveModal.qml index a7455cbee9..0b803039de 100644 --- a/atomic_qt_design/qml/Exchange/Trade/OrderReceiveModal.qml +++ b/atomic_qt_design/qml/Exchange/Trade/OrderReceiveModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" @@ -40,27 +40,22 @@ DefaultModal { } // List - ListView { + DefaultListView { id: list - ScrollBar.vertical: ScrollBar {} - implicitWidth: contentItem.childrenRect.width implicitHeight: 600 model: General.filterCoins(getFilteredCoins().sort((a, b) => getOrderCount(b.ticker) - getOrderCount(a.ticker)), input_coin_filter.text) - clip: true delegate: Rectangle { - property bool hovered: false - - color: hovered ? Style.colorTheme4 : "transparent" + color: mouse_area.containsMouse ? Style.colorTheme4 : "transparent" width: modal_layout.width height: 50 MouseArea { + id: mouse_area anchors.fill: parent hoverEnabled: true - onHoveredChanged: hovered = containsMouse onClicked: { setTicker(model.modelData.ticker) root_modal.close() @@ -74,7 +69,7 @@ DefaultModal { } // Icon - Image { + DefaultImage { id: icon anchors.left: parent.left anchors.leftMargin: 20 @@ -90,7 +85,7 @@ DefaultModal { anchors.left: icon.right anchors.leftMargin: Style.iconTextMargin - text: API.get().empty_string + (model.modelData.name + " (" + model.modelData.ticker + ")" + " - " + + text_value: API.get().empty_string + (model.modelData.name + " (" + model.modelData.ticker + ")" + " - " + (getOrderCount(model.modelData.ticker) === 0 ? qsTr("Click to create an order") : qsTr("Click to see %n order(s)", "", getOrderCount(model.modelData.ticker)))) anchors.verticalCenter: parent.verticalCenter diff --git a/atomic_qt_design/qml/Exchange/Trade/OrderbookModal.qml b/atomic_qt_design/qml/Exchange/Trade/OrderbookModal.qml index 980c4d4a34..4071521d90 100644 --- a/atomic_qt_design/qml/Exchange/Trade/OrderbookModal.qml +++ b/atomic_qt_design/qml/Exchange/Trade/OrderbookModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" @@ -36,9 +36,7 @@ DefaultModal { } // List header - Rectangle { - color: "transparent" - + Item { Layout.alignment: Qt.AlignTop Layout.fillWidth: true @@ -51,7 +49,7 @@ DefaultModal { anchors.right: parent.right anchors.rightMargin: parent.width * 0.77 - text: API.get().empty_string + (qsTr("Price")) + text_value: API.get().empty_string + (qsTr("Price")) color: Style.colorWhite1 anchors.verticalCenter: parent.verticalCenter } @@ -62,7 +60,7 @@ DefaultModal { anchors.right: parent.right anchors.rightMargin: parent.width * 0.44 - text: API.get().empty_string + (qsTr("Volume")) + text_value: API.get().empty_string + (qsTr("Volume")) color: Style.colorWhite1 anchors.verticalCenter: parent.verticalCenter } @@ -73,7 +71,7 @@ DefaultModal { anchors.right: parent.right anchors.rightMargin: parent.width * 0.11 - text: API.get().empty_string + (qsTr("Receive")) + text_value: API.get().empty_string + (qsTr("Receive")) color: Style.colorWhite1 anchors.verticalCenter: parent.verticalCenter } @@ -87,29 +85,24 @@ DefaultModal { } // List - ListView { + DefaultListView { id: list Layout.alignment: Qt.AlignTop Layout.fillWidth: true Layout.fillHeight: true - ScrollBar.vertical: ScrollBar {} model: getCurrentOrderbook().sort((a, b) => parseFloat(b.price) - parseFloat(a.price)) - clip: true - delegate: Rectangle { - property bool hovered: false - - color: hovered ? Style.colorTheme4 : "transparent" + color: mouse_area.containsMouse ? Style.colorTheme4 : "transparent" width: modal_layout.width height: 50 MouseArea { + id: mouse_area anchors.fill: parent hoverEnabled: true - onHoveredChanged: hovered = containsMouse onClicked: chooseOrder(model.modelData) } @@ -118,7 +111,7 @@ DefaultModal { anchors.right: parent.right anchors.rightMargin: price_header.anchors.rightMargin - text: API.get().empty_string + (General.formatDouble(model.modelData.price)) + text_value: API.get().empty_string + (General.formatDouble(model.modelData.price)) color: Style.colorWhite4 anchors.verticalCenter: parent.verticalCenter } @@ -128,7 +121,7 @@ DefaultModal { anchors.right: parent.right anchors.rightMargin: volume_header.anchors.rightMargin - text: API.get().empty_string + (model.modelData.volume) + text_value: API.get().empty_string + (model.modelData.volume) color: Style.colorWhite4 anchors.verticalCenter: parent.verticalCenter } @@ -138,7 +131,7 @@ DefaultModal { anchors.right: parent.right anchors.rightMargin: receive_header.anchors.rightMargin - text: API.get().empty_string + (getReceiveAmount(model.modelData.price, model.modelData.volume) + " " + getTicker()) + text_value: API.get().empty_string + (getReceiveAmount(model.modelData.price, model.modelData.volume) + " " + getTicker()) color: Style.colorWhite4 anchors.verticalCenter: parent.verticalCenter } diff --git a/atomic_qt_design/qml/Exchange/Trade/PriceLine.qml b/atomic_qt_design/qml/Exchange/Trade/PriceLine.qml index 571a79c88a..cbf64cd09a 100644 --- a/atomic_qt_design/qml/Exchange/Trade/PriceLine.qml +++ b/atomic_qt_design/qml/Exchange/Trade/PriceLine.qml @@ -1,14 +1,137 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" // Price -DefaultText { - Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (!hasValidPrice() ? '' : - (!orderIsSelected() ? qsTr("Price") : qsTr("Selected Price")) + ": " + - General.formatCrypto("", !orderIsSelected() ? getCalculatedPrice() : preffered_order.price, getTicker(false))) +RowLayout { + readonly property double price: !orderIsSelected() ? getCalculatedPrice() : preffered_order.price + readonly property bool invalid_cex_price: parseFloat(cex_price) === 0 + readonly property double price_diff: invalid_cex_price ? 0 : 100 * (1 - parseFloat(price) / parseFloat(cex_price)) + + readonly property int fontSize: Style.textSizeSmall2 + readonly property int fontSizeBigger: Style.textSizeSmall4 + readonly property int line_scale: getComparisonScale(price_diff) + + readonly property bool price_entered: hasValidPrice() + + function getComparisonScale(value) { + return Math.min(Math.pow(10, General.getDigitCount(value)), 1000000000) + } + + function limitDigits(value) { + return parseFloat(value.toFixed(2)) + } + + spacing: 100 + + DefaultText { + visible: !price_entered && invalid_cex_price + Layout.alignment: Qt.AlignHCenter + text_value: API.get().empty_string + (qsTr("Fill the amounts to see the price information")) + font.pixelSize: fontSize + } + + + ColumnLayout { + visible: price_entered + + DefaultText { + Layout.alignment: Qt.AlignHCenter + text_value: API.get().empty_string + (qsTr("Exchange rate") + (orderIsSelected() ? (" (" + qsTr("Selected") + ")") : "")) + font.pixelSize: fontSize + } + + // Price reversed + DefaultText { + Layout.alignment: Qt.AlignHCenter + text_value: API.get().empty_string + ("1 " + getTicker(false) + " = " + General.formatCrypto("", General.formatDouble(1 / parseFloat(price)), getTicker(true))) + font.pixelSize: fontSizeBigger + font.bold: true + } + + // Price + DefaultText { + Layout.alignment: Qt.AlignHCenter + text_value: API.get().empty_string + ("1 " + getTicker(true) + " = " + General.formatCrypto("", price, getTicker(false))) + font.pixelSize: fontSize + } + } + + + // Price Comparison + ColumnLayout { + visible: price_entered && !invalid_cex_price + + DefaultText { + id: price_diff_text + Layout.topMargin: 10 + Layout.bottomMargin: Layout.topMargin + Layout.alignment: Qt.AlignHCenter + color: price_diff <= 0 ? Style.colorGreen : Style.colorRed + text_value: API.get().empty_string + ((price_diff > 0 ? qsTr("Expensive") : qsTr("Expedient")) + ":    " + qsTr("%1 compared to CEX", "PRICE_DIFF%").arg("" + General.formatPercent(limitDigits(price_diff)) + "")) + font.pixelSize: fontSize + } + + RowLayout { + DefaultText { + text_value: API.get().empty_string + (General.formatPercent(line_scale)) + font.pixelSize: fontSize + } + + GradientRectangle { + width: 200 + height: 6 + + start_color: Style.colorGreen + end_color: Style.colorRed + + Rectangle { + width: 4 + height: parent.height * 2 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: 0.5 * parent.width * Math.min(Math.max(price_diff / line_scale, -1), 1) + } + } + + DefaultText { + text_value: API.get().empty_string + (General.formatPercent(-line_scale)) + font.pixelSize: fontSize + } + } + } + + + + + + // CEXchange + ColumnLayout { + visible: !invalid_cex_price + DefaultText { + Layout.alignment: Qt.AlignHCenter + text_value: API.get().empty_string + (General.cex_icon + " " + qsTr("CEXchange rate")) + font.pixelSize: fontSize + + CexInfoTrigger {} + } + + // Price reversed + DefaultText { + Layout.alignment: Qt.AlignHCenter + text_value: API.get().empty_string + ("1 " + getTicker(false) + " = " + General.formatCrypto("", General.formatDouble(1 / parseFloat(cex_price)), getTicker(true))) + font.pixelSize: fontSizeBigger + font.bold: true + } + + // Price + DefaultText { + Layout.alignment: Qt.AlignHCenter + text_value: API.get().empty_string + ("1 " + getTicker(true) + " = " + General.formatCrypto("", cex_price, getTicker(false))) + font.pixelSize: fontSize + } + } } diff --git a/atomic_qt_design/qml/Exchange/Trade/Trade.qml b/atomic_qt_design/qml/Exchange/Trade/Trade.qml index 269f9c3554..20c875dc1b 100644 --- a/atomic_qt_design/qml/Exchange/Trade/Trade.qml +++ b/atomic_qt_design/qml/Exchange/Trade/Trade.qml @@ -1,16 +1,15 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../../Components" import "../../Constants" +import "../../Wallet" Item { id: exchange_trade property string action_result - property string prev_base - property string prev_rel // Override property var onOrderSuccess: () => {} @@ -23,8 +22,6 @@ Item { function fullReset() { reset(true) - prev_base = '' - prev_rel = '' orderbook_timer.running = false } @@ -47,9 +44,15 @@ Item { // Price + property string cex_price + function updateCexPrice(base, rel) { + cex_price = API.get().get_cex_rates(base, rel) + } + readonly property var empty_order: ({ "price": "0","price_denom":"0","price_numer":"0","volume":"0"}) property var preffered_order: General.clone(empty_order) + function orderIsSelected() { return preffered_order.price !== empty_order.price } @@ -205,6 +208,7 @@ Item { updateOrderbook() reset(true) updateForms() + setPair(true) } function updateForms(my_side, new_ticker) { @@ -228,8 +232,24 @@ Item { return cb === undefined ? [] : cb } + function moveToBeginning(coins, ticker) { + const idx = coins.map(c => c.ticker).indexOf(ticker) + if(idx === -1) return + + const coin = coins[idx] + return [coin].concat(coins.filter(c => c.ticker !== ticker)) + } + function getCoins(my_side) { - const coins = API.get().enabled_coins + let coins = API.get().enabled_coins + + if(coins.length === 0) return coins + + // Prioritize KMD / BTC pair by moving them to the start + coins = moveToBeginning(coins, "BTC") + coins = moveToBeginning(coins, "KMD") + + // Return full list if(my_side === undefined) return coins // Filter for Sell @@ -255,26 +275,6 @@ Item { else form_rel.setTicker(ticker) } - function swapPair() { - let base = getTicker(true) - let rel = getTicker(false) - - // Fill previous ones if they are blank - if(prev_base === '') prev_base = form_base.getAnyAvailableCoin(rel) - if(prev_rel === '') prev_rel = form_rel.getAnyAvailableCoin(base) - - // Get different value if they are same - if(base === rel) { - if(base !== prev_base) base = prev_base - else if(rel !== prev_rel) rel = prev_rel - } - - // Swap - const curr_base = base - setTicker(true, rel) - setTicker(false, curr_base) - } - function validBaseRel() { const base = getTicker(true) const rel = getTicker(false) @@ -282,18 +282,21 @@ Item { } function setPair(is_base) { - if(getTicker(true) === getTicker(false)) swapPair() - else { - if(validBaseRel()) { - const new_base = getTicker(true) - const rel = getTicker(false) - console.log("Setting current orderbook with params: ", new_base, rel) - API.get().set_current_orderbook(new_base, rel) - reset(true, is_base) - updateOrderbook() - - exchange.onTradeTickerChanged(new_base) - } + if(getTicker(true) === getTicker(false)) { + // Base got selected, same as rel + // Change rel ticker + form_rel.setAnyTicker() + } + + if(validBaseRel()) { + const new_base = getTicker(true) + const rel = getTicker(false) + console.log("Setting current orderbook with params: ", new_base, rel) + API.get().set_current_orderbook(new_base, rel) + reset(true, is_base) + updateOrderbook() + updateCexPrice(new_base, rel) + exchange.onTradeTickerChanged(new_base) } } @@ -352,7 +355,7 @@ Item { anchors.centerIn: parent visible: form_base.ticker_list.length === 0 - Image { + DefaultImage { Layout.alignment: Qt.AlignHCenter source: General.image_path + "setup-wallet-restore-2.svg" Layout.bottomMargin: 30 @@ -360,13 +363,13 @@ Item { DefaultText { Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (qsTr("No balance available")) + text_value: API.get().empty_string + (qsTr("No balance available")) font.pixelSize: Style.textSize2 } DefaultText { Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (qsTr("Please enable a coin with balance or deposit funds")) + text_value: API.get().empty_string + (qsTr("Please enable a coin with balance or deposit funds")) } } @@ -374,12 +377,34 @@ Item { ColumnLayout { id: form + spacing: layout_margin + visible: form_base.ticker_list.length > 0 - anchors.centerIn: parent + anchors.fill: parent + + + InnerBackground { + id: graph_bg + + Layout.alignment: Qt.AlignTop + + visible: chart.pair_supported + + Layout.fillWidth: true + Layout.fillHeight: true + implicitHeight: wallet.height*0.6 + + CandleStickChart { + id: chart + width: graph_bg.width + height: graph_bg.height + } + } RowLayout { - spacing: 15 + Layout.alignment: Qt.AlignVCenter + spacing: 0 // Sell OrderForm { @@ -388,63 +413,59 @@ Item { my_side: true } - Image { - source: General.image_path + "exchange-exchange.svg" - Layout.alignment: Qt.AlignVCenter + FloatingBackground { + id: trade_icon_bg + z: 1 + radius: 100 + width: 75 + height: width + auto_set_size: false + + content: DefaultImage { + source: General.image_path + "trade_icon.svg" + Layout.alignment: Qt.AlignVCenter + fillMode: Image.PreserveAspectFit + width: trade_icon_bg.width*0.4 + height: width + } } // Receive OrderForm { id: form_rel + Layout.fillWidth: true + Layout.preferredHeight: form_base.height + column_layout.height: form_base.height field.enabled: enabled && !orderIsSelected() } } - // Trade button - PrimaryButton { - id: action_button - Layout.fillWidth: true - - text: API.get().empty_string + (qsTr("Trade")) - enabled: valid_trade_info && form_base.isValid() && form_rel.isValid() - onClicked: confirm_trade_modal.open() - } - - ConfirmTradeModal { - id: confirm_trade_modal - } - // Price PriceLine { - Layout.alignment: Qt.AlignHCenter - } - - // Result - DefaultText { - Layout.alignment: Qt.AlignHCenter - - color: action_result === "success" ? Style.colorGreen : Style.colorRed - - text: API.get().empty_string + (action_result === "" ? "" : action_result === "success" ? "" : qsTr("Failed to place the order.")) + Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter } // Show errors DefaultText { - Layout.alignment: Qt.AlignHCenter + Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter + color: Style.colorRed - text: API.get().empty_string + (notEnoughBalanceForFees() ? + text_value: API.get().empty_string + (notEnoughBalanceForFees() ? (qsTr("Not enough balance for the fees. Need at least %1 more", "AMT TICKER").arg(General.formatCrypto("", parseFloat(curr_trade_info.amount_needed), form_base.getTicker()))) : (form_base.hasEthFees() && !form_base.hasEnoughEthForFees()) ? (qsTr("Not enough ETH for the transaction fee")) : (form_base.fieldsAreFilled() && !form_base.higherThanMinTradeAmount()) ? (qsTr("Sell amount is lower than minimum trade amount") + " : " + General.getMinTradeAmount()) : (form_rel.fieldsAreFilled() && !form_rel.higherThanMinTradeAmount()) ? (qsTr("Receive amount is lower than minimum trade amount") + " : " + General.getMinTradeAmount()) : "" ) - color: Style.colorRed visible: form_base.fieldsAreFilled() && (notEnoughBalanceForFees() || (form_base.hasEthFees() && !form_base.hasEnoughEthForFees()) || !form_base.higherThanMinTradeAmount() || (form_rel.fieldsAreFilled() && !form_rel.higherThanMinTradeAmount())) } + + ConfirmTradeModal { + id: confirm_trade_modal + } } } diff --git a/atomic_qt_design/qml/NoConnection.qml b/atomic_qt_design/qml/NoConnection.qml index 6d66c072b7..701a05de95 100644 --- a/atomic_qt_design/qml/NoConnection.qml +++ b/atomic_qt_design/qml/NoConnection.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "Screens" import "Constants" import "Components" @@ -40,17 +40,17 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter DefaultText { - text: API.get().empty_string + (qsTr("No connection")) + text_value: API.get().empty_string + (qsTr("No connection")) Layout.alignment: Qt.AlignHCenter font.pixelSize: Style.textSize3 } - BusyIndicator { + DefaultBusyIndicator { Layout.alignment: Qt.AlignHCenter } DefaultText { - text: API.get().empty_string + (qsTr("Please make sure you are connected to the internet")) + text_value: API.get().empty_string + (qsTr("Please make sure you are connected to the internet")) Layout.alignment: Qt.AlignHCenter } } diff --git a/atomic_qt_design/qml/Portfolio/Portfolio.qml b/atomic_qt_design/qml/Portfolio/Portfolio.qml index 2d2d2305aa..cf0a668922 100644 --- a/atomic_qt_design/qml/Portfolio/Portfolio.qml +++ b/atomic_qt_design/qml/Portfolio/Portfolio.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import QtCharts 2.3 @@ -15,51 +15,17 @@ ColumnLayout { Layout.fillHeight: true readonly property int sort_by_name: 0 - readonly property int sort_by_ticker: 1 - readonly property int sort_by_value: 2 - readonly property int sort_by_balance: 3 - readonly property int sort_by_price: 4 - readonly property int sort_by_change: 5 - readonly property int sort_by_trend: 6 + readonly property int sort_by_value: 1 + readonly property int sort_by_change: 3 + readonly property int sort_by_trend: 4 + readonly property int sort_by_price: 5 property int current_sort: sort_by_value - property bool highest_first: true - - function reset() { - updatePortfolio() - } - - function onOpened() { - updatePortfolio() - } - - function inCurrentPage() { - return dashboard.inCurrentPage() && - dashboard.current_page === General.idx_dashboard_portfolio - } - - property var portfolio_coins: ([]) - - function updatePortfolio() { - portfolio_coins = API.get().get_portfolio_informations() + property bool ascending: false - update_timer.running = true - } - - Timer { - id: update_timer - running: false - repeat: true - interval: 5000 - onTriggered: { - if(inCurrentPage()) updatePortfolio() - } - } + function reset() { } - function getColor(data) { - return data.rates === null || data.rates[API.get().fiat].percent_change_24h === 0 ? Style.colorWhite4 : - data.rates[API.get().fiat].percent_change_24h > 0 ? Style.colorGreen : Style.colorRed - } + function onOpened() { } function updateChart(chart, historical) { chart.removeAllSeries() @@ -69,7 +35,7 @@ ColumnLayout { // Fill chart let series = chart.createSeries(ChartView.SeriesTypeSpline, "Price", chart.axes[0], chart.axes[1]); - series.style = Qt.DashDotLine + series.style = Qt.SolidLine series.color = Style.colorTheme1 let min = 999999999 @@ -91,12 +57,12 @@ ColumnLayout { } // Top part - Rectangle { - color: "transparent" + Item { Layout.fillWidth: true height: 200 ColumnLayout { + id: top_layout anchors.centerIn: parent // Total Title @@ -104,7 +70,7 @@ ColumnLayout { Layout.topMargin: 50 Layout.bottomMargin: 0 Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (qsTr("TOTAL")) + text_value: API.get().empty_string + (qsTr("TOTAL")) font.pixelSize: Style.textSize color: Style.colorWhite5 } @@ -113,19 +79,29 @@ ColumnLayout { DefaultText { Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 30 - text: API.get().empty_string + (General.formatFiat("", API.get().balance_fiat_all, API.get().fiat)) + text_value: API.get().empty_string + (General.formatFiat("", API.get().balance_fiat_all, API.get().current_currency)) font.pixelSize: Style.textSize4 + privacy: true } } + MouseArea { + anchors.fill: top_layout + + onClicked: { + const current_fiat = API.get().current_currency + const available_fiats = API.get().get_available_currencies() + const current_index = available_fiats.indexOf(current_fiat) + const next_index = (current_index + 1) % available_fiats.length + const next_fiat = available_fiats[next_index] + API.get().current_currency = next_fiat + } + } // Add button PlusButton { id: add_coin_button - - width: 50 - - mouse_area.onClicked: enable_coin_modal.prepareAndOpen() + onClicked: enable_coin_modal.prepareAndOpen() anchors.right: parent.right anchors.rightMargin: parent.height * 0.5 - width * 0.5 @@ -148,15 +124,17 @@ ColumnLayout { placeholderText: API.get().empty_string + (qsTr("Search")) selectByMouse: true + onTextChanged: { + API.get().portfolio_mdl.portfolio_proxy_mdl.setFilterFixedString(text) + } + width: 120 } } // List header - Rectangle { - color: "transparent" - + Item { Layout.alignment: Qt.AlignTop Layout.fillWidth: true @@ -187,7 +165,7 @@ ColumnLayout { id: balance_header icon_at_left: true anchors.left: parent.left - anchors.leftMargin: parent.width * 0.3 + anchors.leftMargin: parent.width * 0.265 anchors.verticalCenter: parent.verticalCenter text: API.get().empty_string + (qsTr("Balance")) @@ -199,7 +177,7 @@ ColumnLayout { id: change_24h_header icon_at_left: false anchors.right: parent.right - anchors.rightMargin: parent.width * 0.27 + anchors.rightMargin: parent.width * 0.37 anchors.verticalCenter: parent.verticalCenter text: API.get().empty_string + (qsTr("Change 24h")) @@ -211,7 +189,7 @@ ColumnLayout { id: trend_7d_header icon_at_left: false anchors.right: parent.right - anchors.rightMargin: parent.width * 0.15 + anchors.rightMargin: parent.width * 0.24 anchors.verticalCenter: parent.verticalCenter text: API.get().empty_string + (qsTr("Trend 7d")) @@ -240,10 +218,9 @@ ColumnLayout { } // Transactions or loading - Rectangle { + Item { id: loading - color: "transparent" - visible: portfolio_coins.length === 0 + visible: API.get().portfolio_mdl.length === 0 Layout.alignment: Qt.AlignCenter Layout.fillWidth: true Layout.fillHeight: true @@ -252,77 +229,42 @@ ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter DefaultText { - text: API.get().empty_string + (qsTr("Loading")) + text_value: API.get().empty_string + (qsTr("Loading")) Layout.alignment: Qt.AlignHCenter font.pixelSize: Style.textSize2 } - BusyIndicator { + DefaultBusyIndicator { Layout.alignment: Qt.AlignHCenter } } } // List - ListView { + DefaultListView { id: list - visible: portfolio_coins.length > 0 + visible: API.get().portfolio_mdl.length > 0 Layout.alignment: Qt.AlignTop Layout.fillWidth: true Layout.fillHeight: true - ScrollBar.vertical: ScrollBar {} - - model: General.filterCoins(portfolio_coins, input_coin_filter.text) - .sort((a, b) => { - const order = highest_first ? 1 : -1 - let val_a - let val_b - let result - switch(current_sort) { - case sort_by_name: return (b.name.toUpperCase() > a.name.toUpperCase() ? -1 : 1) * order - case sort_by_ticker: return (b.ticker > a.ticker ? -1 : 1) * order - case sort_by_value: - val_a = parseFloat(a.balance_fiat) - val_b = parseFloat(b.balance_fiat) - result = val_b - val_a - - if(result === 0) { - let val_a = parseFloat(a.balance) - let val_b = parseFloat(b.balance) - result = val_b - val_a - } - - return result * order - case sort_by_price: return (parseFloat(b.price) - parseFloat(a.price)) * order - case sort_by_balance: return (parseFloat(b.balance) - parseFloat(a.balance)) * order - case sort_by_trend: return (parseFloat(b.price) - parseFloat(a.price)) * order - case sort_by_change: - val_a = a.rates === null ? -9999999 : a.rates[API.get().fiat].percent_change_24h - val_b = b.rates === null ? -9999999 : b.rates[API.get().fiat].percent_change_24h - return (val_b - val_a) * order - } - }) - - clip: true + model: portfolio_coins delegate: Rectangle { - property bool hovered: false - - color: hovered ? Style.colorTheme5 : index % 2 == 0 ? Style.colorTheme6 : Style.colorTheme7 + color: mouse_area.containsMouse ? Style.colorTheme5 : index % 2 == 0 ? Style.colorTheme6 : Style.colorTheme7 width: portfolio.width height: 50 // Click area MouseArea { + id: mouse_area anchors.fill: parent hoverEnabled: true - onHoveredChanged: hovered = containsMouse acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { if (mouse.button === Qt.RightButton) context_menu.popup() else { - API.get().current_coin_info.ticker = model.modelData.ticker + API.get().current_coin_info.ticker = ticker dashboard.current_page = General.idx_dashboard_wallet } } @@ -335,19 +277,19 @@ ColumnLayout { Menu { id: context_menu Action { - text: API.get().empty_string + (qsTr("Disable %1", "TICKER").arg(model.modelData.ticker)) - onTriggered: API.get().disable_coins([model.modelData.ticker]) - enabled: General.canDisable(model.modelData.ticker) + text: API.get().empty_string + (qsTr("Disable %1", "TICKER").arg(ticker)) + onTriggered: API.get().disable_coins([ticker]) + enabled: General.canDisable(ticker) } } // Icon - Image { + DefaultImage { id: icon anchors.left: parent.left anchors.leftMargin: coin_header.anchors.leftMargin - source: General.image_path + "coins/" + model.modelData.ticker.toLowerCase() + ".png" + source: General.coinIcon(ticker) fillMode: Image.PreserveAspectFit width: Style.textSize2 anchors.verticalCenter: parent.verticalCenter @@ -357,8 +299,7 @@ ColumnLayout { DefaultText { anchors.left: icon.right anchors.leftMargin: 10 - - text: API.get().empty_string + (model.modelData.name) + text_value: API.get().empty_string + (name) anchors.verticalCenter: parent.verticalCenter } @@ -368,31 +309,10 @@ ColumnLayout { anchors.left: parent.left anchors.leftMargin: balance_header.anchors.leftMargin - text: API.get().empty_string + (model.modelData.balance) + text_value: API.get().empty_string + (General.formatCrypto("", balance, ticker, main_currency_balance, API.get().current_currency)) color: Style.colorWhite4 anchors.verticalCenter: parent.verticalCenter - } - - // Ticker - DefaultText { - id: balance_ticker - anchors.left: balance_value.right - anchors.leftMargin: 5 - anchors.baseline: balance_value.baseline - - text: API.get().empty_string + (model.modelData.ticker) - color: Style.colorWhite6 - font.pixelSize: Style.textSize * 0.9 - } - - // Value - DefaultText { - anchors.left: balance_ticker.right - anchors.leftMargin: 10 - - text: API.get().empty_string + ("(" + General.formatFiat('', model.modelData.balance_fiat, API.get().fiat) + ")") - color: Style.colorWhite5 - anchors.verticalCenter: parent.verticalCenter + privacy: true } // Change 24h @@ -400,10 +320,11 @@ ColumnLayout { anchors.right: parent.right anchors.rightMargin: change_24h_header.anchors.rightMargin - text: API.get().empty_string + (model.modelData.rates === null ? '-' : - ((model.modelData.rates[API.get().fiat].percent_change_24h > 0 ? '+' : '') + - (model.modelData.rates[API.get().fiat].percent_change_24h + '%'))) - color: getColor(model.modelData) + text_value: { + const v = parseFloat(change_24h) + return API.get().empty_string + (v === 0 ? '-' : General.formatPercent(v)) + } + color: Style.getValueColor(change_24h) anchors.verticalCenter: parent.verticalCenter } @@ -412,13 +333,14 @@ ColumnLayout { anchors.right: parent.right anchors.rightMargin: price_header.anchors.rightMargin - text: API.get().empty_string + (General.formatFiat('', model.modelData.price, API.get().fiat)) - color: Style.colorWhite6 + text_value: API.get().empty_string + (General.formatFiat('', main_currency_price_for_one_unit, API.get().current_currency)) + color: Style.colorThemeDarkLight anchors.verticalCenter: parent.verticalCenter } // 7d Trend ChartView { + property var historical: trend_7d id: chart width: 200 height: 100 @@ -428,23 +350,10 @@ ColumnLayout { anchors.verticalCenter: parent.verticalCenter legend.visible: false - Component.onCompleted: updateChart(chart, model.modelData.historical) + onHistoricalChanged: updateChart(chart, historical) backgroundColor: "transparent" } } } } - - - - - - - - -/*##^## -Designer { - D{i:0;autoSize:true;height:600;width:1200} -} -##^##*/ diff --git a/atomic_qt_design/qml/Screens/Dashboard.qml b/atomic_qt_design/qml/Screens/Dashboard.qml index d8fd420d1b..1151ca8bf9 100644 --- a/atomic_qt_design/qml/Screens/Dashboard.qml +++ b/atomic_qt_design/qml/Screens/Dashboard.qml @@ -1,7 +1,8 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + +import QtGraphicalEffects 1.0 import "../Components" import "../Constants" @@ -16,11 +17,18 @@ Item { Layout.fillWidth: true + function getMainPage() { + return API.design_editor ? General.idx_dashboard_wallet : General.idx_dashboard_portfolio + } + property int prev_page: -1 - property int current_page: API.design_editor ? General.idx_dashboard_exchange : General.idx_dashboard_portfolio + property int current_page: getMainPage() function reset() { - current_page = General.idx_dashboard_portfolio + // Fill all coins list + General.all_coins = API.get().get_all_coins() + + current_page = getMainPage() prev_page = -1 // Reset all sections @@ -36,8 +44,11 @@ Item { return app.current_page === idx_dashboard } + property var portfolio_coins: API.get().portfolio_mdl.portfolio_proxy_mdl + onCurrent_pageChanged: { if(prev_page !== current_page) { + // Handle DEX enter/exit if(current_page === General.idx_dashboard_exchange) { API.get().on_gui_enter_dex() exchange.onOpened() @@ -46,9 +57,13 @@ Item { API.get().on_gui_leave_dex() } + // Opening of other pages if(current_page === General.idx_dashboard_portfolio) { portfolio.onOpened() } + else if(current_page === General.idx_dashboard_wallet) { + wallet.onOpened() + } else if(current_page === General.idx_dashboard_settings) { settings.onOpened() } @@ -64,11 +79,17 @@ Item { onTriggered: General.enableEthIfNeeded() } - // Left side + // Sidebar, left side + Sidebar { + id: sidebar + } + + // Right side Rectangle { - color: Style.colorTheme6 + color: Style.colorTheme8 width: parent.width - sidebar.width height: parent.height + x: sidebar.width // Modals EnableCoinModal { @@ -97,13 +118,13 @@ Item { DefaultText { id: news - text: API.get().empty_string + (qsTr("News")) + text_value: API.get().empty_string + (qsTr("News")) function reset() { } } DefaultText { id: dapps - text: API.get().empty_string + (qsTr("DApps")) + text_value: API.get().empty_string + (qsTr("Dapps")) function reset() { } } @@ -114,26 +135,40 @@ Item { } } - // Sidebar, right side - Rectangle { - id: sidebar - color: Style.colorTheme8 - width: 150 - height: parent.height - x: parent.width - width + DropShadow { + anchors.fill: sidebar + source: sidebar + cached: false + horizontalOffset: 0 + verticalOffset: 0 + radius: 32 + samples: 32 + spread: 0 + color: Style.colorSidebarDropShadow + smooth: true + } - Image { - source: General.image_path + "komodo-icon.png" - anchors.horizontalCenter: parent.horizontalCenter - y: parent.width * 0.25 - transformOrigin: Item.Center - width: 64 - fillMode: Image.PreserveAspectFit - } + // CEX Rates info + DefaultModal { + id: cex_rates_modal + width: 500 - Sidebar { + // Inside modal + ColumnLayout { width: parent.width - anchors.verticalCenter: parent.verticalCenter + + ModalHeader { + title: API.get().empty_string + (General.cex_icon + " " + qsTr("CEX Data")) + } + + DefaultText { + text_value: API.get().empty_string + (qsTr('Markets data (prices, charts, etc.) marked with the ⓘ icon originates from third party sources. (coinpaprika.com)')) + wrapMode: Text.WordWrap + Layout.preferredWidth: cex_rates_modal.width + + onLinkActivated: Qt.openUrlExternally(link) + linkColor: color + } } } } diff --git a/atomic_qt_design/qml/Screens/FirstLaunch.qml b/atomic_qt_design/qml/Screens/FirstLaunch.qml index 0a377afb17..cd0effef16 100644 --- a/atomic_qt_design/qml/Screens/FirstLaunch.qml +++ b/atomic_qt_design/qml/Screens/FirstLaunch.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" import "../Settings" @@ -19,12 +19,23 @@ SetupPage { property var wallets: ([]) - image_scale: 0.7 - image_path: General.image_path + "komodo-icon.png" - title: API.get().empty_string + (qsTr("Welcome!")) + image_scale: 0.72 + image_path: General.image_path + "atomicdex-logo-large.svg" + image_margin: 30 content: ColumnLayout { + width: 400 + spacing: Style.rowSpacing + DefaultText { + text_value: API.get().empty_string + (qsTr("Welcome")) + } + + HorizontalLine { + Layout.fillWidth: true + } + RowLayout { Layout.fillWidth: true + spacing: Style.buttonSpacing DefaultButton { Layout.fillWidth: true @@ -32,7 +43,7 @@ SetupPage { onClicked: onClickedRecoverSeed() } - PrimaryButton { + DefaultButton { Layout.fillWidth: true text: API.get().empty_string + (qsTr("New User")) onClicked: onClickedNewUser() @@ -41,58 +52,66 @@ SetupPage { // Wallets ColumnLayout { + spacing: Style.rowSpacing + visible: wallets.length > 0 + // Name DefaultText { - Layout.topMargin: 10 - text: API.get().empty_string + (qsTr("Wallets")) + text_value: API.get().empty_string + (qsTr("Wallets")) + font.pixelSize: Style.textSizeSmall2 } - HorizontalLine { + InnerBackground { + id: bg Layout.fillWidth: true - } - - ListView { - ScrollBar.vertical: ScrollBar {} - implicitWidth: contentItem.childrenRect.width - implicitHeight: contentItem.childrenRect.height - clip: true - - model: wallets - - delegate: Rectangle { - property bool hovered: false - - color: hovered ? Style.colorTheme7 : "transparent" - width: 300 - height: 30 - - // Click area - MouseArea { - anchors.fill: parent - hoverEnabled: true - onHoveredChanged: hovered = containsMouse - onClicked: { - API.get().wallet_default_name = model.modelData - onClickedWallet() + readonly property int row_height: 40 + Layout.minimumHeight: row_height + Layout.preferredHeight: row_height * Math.min(wallets.length, 3) + + content: DefaultListView { + id: list + implicitHeight: bg.Layout.preferredHeight + + model: wallets + + delegate: GradientRectangle { + start_color: mouse_area.containsMouse ? Style.colorWalletsHighlightGradient1 : "transparent" + end_color: mouse_area.containsMouse ? Style.colorWalletsHighlightGradient2 : "transparent" + + width: bg.width + height: bg.row_height + + // Click area + MouseArea { + id: mouse_area + anchors.fill: parent + hoverEnabled: true + onClicked: { + API.get().wallet_default_name = model.modelData + onClickedWallet() + } } - } - // Name - DefaultText { - anchors.left: parent.left - anchors.leftMargin: 5 + // Name + DefaultText { + anchors.left: parent.left + anchors.leftMargin: 40 - text: API.get().empty_string + (Style.listItemPrefix + model.modelData) - anchors.verticalCenter: parent.verticalCenter - } + text_value: API.get().empty_string + (model.modelData) + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: Style.textSizeSmall2 + } - // Line - HorizontalLine { - visible: index !== wallets.length - 1 - width: parent.width - color: Style.colorWhite9 - anchors.bottom: parent.bottom + HorizontalLine { + visible: index !== wallets.length -1 + width: parent.width - 4 + + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: -height/2 + light: true + } } } } @@ -101,12 +120,11 @@ SetupPage { HorizontalLine { + light: true Layout.fillWidth: true - Layout.bottomMargin: 10 } Languages { - Layout.alignment: Qt.AlignHCenter } } } diff --git a/atomic_qt_design/qml/Screens/InitialLoading.qml b/atomic_qt_design/qml/Screens/InitialLoading.qml index dd338eabf5..0eec4517aa 100644 --- a/atomic_qt_design/qml/Screens/InitialLoading.qml +++ b/atomic_qt_design/qml/Screens/InitialLoading.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" import "../Wallet" @@ -25,19 +25,26 @@ SetupPage { image_scale: 0.7 image_path: General.image_path + "komodo-icon.png" - title: API.get().empty_string + (qsTr("Loading, please wait")) - content: RowLayout { - BusyIndicator { - Layout.alignment: Qt.AlignHCenter - Layout.leftMargin: -15 - Layout.rightMargin: Layout.leftMargin - scale: 0.5 - } + content: ColumnLayout { DefaultText { - text: API.get().empty_string + ((API.get().initial_loading_status === "initializing_mm2" ? qsTr("Initializing MM2") : - API.get().initial_loading_status === "enabling_coins" ? qsTr("Enabling coins") : - API.get().initial_loading_status === "complete" ? qsTr("Complete") : "") + "...") + text_value: API.get().empty_string + (qsTr("Loading, please wait")) + Layout.bottomMargin: 10 + } + + RowLayout { + DefaultBusyIndicator { + Layout.alignment: Qt.AlignHCenter + Layout.leftMargin: -15 + Layout.rightMargin: Layout.leftMargin*0.75 + scale: 0.5 + } + + DefaultText { + text_value: API.get().empty_string + ((API.get().initial_loading_status === "initializing_mm2" ? qsTr("Initializing MM2") : + API.get().initial_loading_status === "enabling_coins" ? qsTr("Enabling coins") : + API.get().initial_loading_status === "complete" ? qsTr("Complete") : "") + "...") + } } } } diff --git a/atomic_qt_design/qml/Screens/Login.qml b/atomic_qt_design/qml/Screens/Login.qml index a01335399e..eeb147a60f 100644 --- a/atomic_qt_design/qml/Screens/Login.qml +++ b/atomic_qt_design/qml/Screens/Login.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -33,8 +33,10 @@ SetupPage { image_scale: 0.7 image_path: General.image_path + "setup-logs.svg" - title: API.get().empty_string + (qsTr("Login") + ": " + API.get().wallet_default_name) + content: ColumnLayout { + spacing: Style.rowSpacing + function reset() { login.reset() input_password.reset() @@ -47,7 +49,16 @@ SetupPage { reset() } - width: 275 + width: 400 + + DefaultText { + text_value: API.get().empty_string + (qsTr("Login") + ": " + API.get().wallet_default_name) + } + + HorizontalLine { + Layout.fillWidth: true + } + PasswordForm { id: input_password confirm: false @@ -55,6 +66,8 @@ SetupPage { } RowLayout { + spacing: Style.buttonSpacing + DefaultButton { text: API.get().empty_string + (qsTr("Back")) Layout.fillWidth: true @@ -75,7 +88,7 @@ SetupPage { } DefaultText { - text: API.get().empty_string + (text_error) + text_value: API.get().empty_string + (text_error) color: Style.colorRed visible: text !== '' } diff --git a/atomic_qt_design/qml/Screens/NewUser.qml b/atomic_qt_design/qml/Screens/NewUser.qml index 1dfab2008c..6c687e351b 100644 --- a/atomic_qt_design/qml/Screens/NewUser.qml +++ b/atomic_qt_design/qml/Screens/NewUser.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -88,11 +88,20 @@ SetupPage { } image_scale: 0.7 - image_path: General.image_path + (form_is_filled ? "settings-seed.svg" : "setup-welcome-wallet.svg") - title: API.get().empty_string + (qsTr("New User")) + // Removed the image for now, no space + //image_path: General.image_path + (form_is_filled ? "settings-seed.svg" : "setup-welcome-wallet.svg") content: ColumnLayout { width: 600 + spacing: Style.rowSpacing + + DefaultText { + text_value: API.get().empty_string + (qsTr("New User")) + } + + HorizontalLine { + Layout.fillWidth: true + } function reset() { new_user.reset() @@ -130,18 +139,27 @@ SetupPage { // First page, fill the form ColumnLayout { visible: !form_is_filled + spacing: Style.rowSpacing WalletNameField { id: input_wallet_name field.onAccepted: completeForm() } - Rectangle { + TextAreaWithTitle { + id: input_generated_seed + title: API.get().empty_string + (qsTr("Generated Seed")) + field.text: current_mnemonic + field.readOnly: true + copyable: true + onReturn: completeForm + } + + FloatingBackground { Layout.topMargin: 10 Layout.bottomMargin: Layout.topMargin Layout.fillWidth: true color: Style.colorRed3 - radius: 10 height: warning_texts.height + 20 Column { @@ -156,29 +174,20 @@ SetupPage { width: parent.width - 40 horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter - text: API.get().empty_string + (qsTr("Important: Back up your seed phrase before proceeding!")) + text_value: API.get().empty_string + (qsTr("Important: Back up your seed phrase before proceeding!")) } DefaultText { width: parent.width - 40 horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter - text: API.get().empty_string + (qsTr("We recommend storing it offline.")) + text_value: API.get().empty_string + (qsTr("We recommend storing it offline.")) font.pixelSize: Style.textSizeSmall4 color: Style.colorWhite4 } } } - TextAreaWithTitle { - id: input_generated_seed - title: API.get().empty_string + (qsTr("Generated Seed")) - field.text: current_mnemonic - field.readOnly: true - copyable: true - onReturn: completeForm - } - TextAreaWithTitle { id: input_confirm_seed title: API.get().empty_string + (qsTr("Confirm Seed")) @@ -194,6 +203,8 @@ SetupPage { } RowLayout { + spacing: Style.buttonSpacing + DefaultButton { Layout.fillWidth: true text: API.get().empty_string + (qsTr("Back")) @@ -217,7 +228,7 @@ SetupPage { } DefaultText { - text: API.get().empty_string + (text_error) + text_value: API.get().empty_string + (text_error) color: Style.colorRed visible: text !== '' } @@ -228,12 +239,10 @@ SetupPage { ColumnLayout { visible: form_is_filled - Rectangle { + FloatingBackground { Layout.topMargin: 10 Layout.bottomMargin: Layout.topMargin Layout.fillWidth: true - color: Style.colorTheme7 - radius: 10 height: 160 Column { @@ -247,13 +256,13 @@ SetupPage { DefaultText { width: parent.width - 40 anchors.horizontalCenter: parent.horizontalCenter - text: API.get().empty_string + (qsTr("Let's double check your seed phrase")) + text_value: API.get().empty_string + (qsTr("Let's double check your seed phrase")) } DefaultText { width: parent.width - 40 anchors.horizontalCenter: parent.horizontalCenter - text: API.get().empty_string + (qsTr("Your seed phrase is important - that's why we like to make sure it's correct. We'll ask you three different questions about your seed phrase to make sure you'll be able to easily restore your wallet whenever you want.")) + text_value: API.get().empty_string + (qsTr("Your seed phrase is important - that's why we like to make sure it's correct. We'll ask you three different questions about your seed phrase to make sure you'll be able to easily restore your wallet whenever you want.")) font.pixelSize: Style.textSizeSmall4 color: Style.colorWhite4 } @@ -286,7 +295,7 @@ SetupPage { } DefaultText { - text: API.get().empty_string + (guess_text_error) + text_value: API.get().empty_string + (guess_text_error) color: Style.colorRed visible: text !== '' } diff --git a/atomic_qt_design/qml/Screens/RecoverSeed.qml b/atomic_qt_design/qml/Screens/RecoverSeed.qml index 4dbfb756d1..87892ae85c 100644 --- a/atomic_qt_design/qml/Screens/RecoverSeed.qml +++ b/atomic_qt_design/qml/Screens/RecoverSeed.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -32,10 +32,21 @@ SetupPage { property string text_error image_scale: 0.7 - image_path: General.image_path + "setup-wallet-restore-2.svg" - title: API.get().empty_string + (qsTr("Recovery")) + + // Removed the image for now, no space + // image_path: General.image_path + "setup-wallet-restore-2.svg" + content: ColumnLayout { width: 400 + spacing: Style.rowSpacing + + DefaultText { + text_value: API.get().empty_string + (qsTr("Recovery")) + } + + HorizontalLine { + Layout.fillWidth: true + } function reset() { recover_seed.reset() @@ -103,7 +114,7 @@ SetupPage { onReturn: trySubmit } - CheckBox { + DefaultCheckBox { id: allow_custom_seed text: API.get().empty_string + (qsTr("Allow custom seed")) } @@ -116,6 +127,8 @@ SetupPage { } RowLayout { + spacing: Style.buttonSpacing + DefaultButton { Layout.fillWidth: true text: API.get().empty_string + (qsTr("Back")) @@ -139,7 +152,7 @@ SetupPage { } DefaultText { - text: API.get().empty_string + (text_error) + text_value: API.get().empty_string + (text_error) color: Style.colorRed visible: text !== '' } diff --git a/atomic_qt_design/qml/Settings/DeleteWalletModal.qml b/atomic_qt_design/qml/Settings/DeleteWalletModal.qml index 9691374483..c9aebd4e28 100644 --- a/atomic_qt_design/qml/Settings/DeleteWalletModal.qml +++ b/atomic_qt_design/qml/Settings/DeleteWalletModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -28,12 +28,11 @@ DefaultModal { title: API.get().empty_string + (qsTr("Delete Wallet")) } - Rectangle { + FloatingBackground { Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 10 color: Style.colorRed2 - radius: 10 width: parent.width - 5 height: warning_texts.height + 20 @@ -45,14 +44,14 @@ DefaultModal { DefaultText { Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (qsTr("Are you sure you want to delete %1 wallet?", "WALLET_NAME").arg(API.get().wallet_default_name)) + text_value: API.get().empty_string + (qsTr("Are you sure you want to delete %1 wallet?", "WALLET_NAME").arg(API.get().wallet_default_name)) font.pixelSize: Style.textSize2 } DefaultText { Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (qsTr("If so, make sure you record your seed phrase in order to restore your wallet in future.")) + text_value: API.get().empty_string + (qsTr("If so, make sure you record your seed phrase in order to restore your wallet in future.")) } } } @@ -65,7 +64,7 @@ DefaultModal { } DefaultText { - text: API.get().empty_string + (qsTr("Wrong Password")) + text_value: API.get().empty_string + (qsTr("Wrong Password")) color: Style.colorRed visible: wrong_password } diff --git a/atomic_qt_design/qml/Settings/Languages.qml b/atomic_qt_design/qml/Settings/Languages.qml index de05c88756..250150a647 100644 --- a/atomic_qt_design/qml/Settings/Languages.qml +++ b/atomic_qt_design/qml/Settings/Languages.qml @@ -1,49 +1,53 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import "../Components" import "../Constants" ColumnLayout { RowLayout { - Layout.alignment: Qt.AlignHCenter + Layout.alignment: Qt.AlignVCenter + spacing: 20 DefaultText { Layout.alignment: Qt.AlignVCenter - text: API.get().empty_string + (qsTr("Language")) - } - Image { - Layout.alignment: Qt.AlignBottom - source: General.image_path + "lang/" + API.get().lang + ".png" - fillMode: Image.PreserveAspectFit - scale: 0.5 + text_value: API.get().empty_string + (qsTr("Language") + ":") + font.pixelSize: Style.textSizeSmall2 } - } - Grid { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 10 - clip: true - - columns: 8 - spacing: 10 - - layoutDirection: Qt.LeftToRight - - Repeater { - model: API.get().get_available_langs() - delegate: Image { - source: General.image_path + "lang/" + model.modelData + ".png" - fillMode: Image.PreserveAspectFit - width: Style.textSize2 - - // Click area - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: { - API.get().lang = model.modelData + Grid { + Layout.alignment: Qt.AlignVCenter + + clip: true + + columns: 8 + spacing: 10 + + layoutDirection: Qt.LeftToRight + + Repeater { + model: API.get().get_available_langs() + delegate: Rectangle { + width: image.sourceSize.width - 4 // Current icons have too much space around them + height: image.sourceSize.height - 2 + color: API.get().lang === model.modelData ? Style.colorTheme11 : "transparent" + + DefaultImage { + id: image + anchors.centerIn: parent + source: General.image_path + "lang/" + model.modelData + ".png" + fillMode: Image.PreserveAspectFit + width: Style.textSize2 + + // Click area + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + API.get().lang = model.modelData + } + } } } } diff --git a/atomic_qt_design/qml/Settings/RecoverSeedModal.qml b/atomic_qt_design/qml/Settings/RecoverSeedModal.qml index 06ec475ab8..9ccf69f02e 100644 --- a/atomic_qt_design/qml/Settings/RecoverSeedModal.qml +++ b/atomic_qt_design/qml/Settings/RecoverSeedModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -52,7 +52,7 @@ DefaultModal { Layout.bottomMargin: 10 Layout.alignment: Qt.AlignHCenter - text: API.get().empty_string + (qsTr("Please enter your password to view the seed.")) + text_value: API.get().empty_string + (qsTr("Please enter your password to view the seed.")) } PasswordForm { @@ -63,7 +63,7 @@ DefaultModal { } DefaultText { - text: API.get().empty_string + (qsTr("Wrong Password")) + text_value: API.get().empty_string + (qsTr("Wrong Password")) color: Style.colorRed visible: wrong_password } diff --git a/atomic_qt_design/qml/Settings/Settings.qml b/atomic_qt_design/qml/Settings/Settings.qml index eb294e5470..0fd5cb8469 100644 --- a/atomic_qt_design/qml/Settings/Settings.qml +++ b/atomic_qt_design/qml/Settings/Settings.qml @@ -1,12 +1,13 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import "../Components" import "../Constants" Item { + id: root function disconnect() { API.get().disconnect() onDisconnect() @@ -21,111 +22,103 @@ Item { } property string mm2_version: '' - property var fiats: (["USD", "EUR"]) + property var fiats: API.get().get_available_fiats() - ColumnLayout { + InnerBackground { + id: layout_background anchors.centerIn: parent - DefaultText { - Layout.alignment: Qt.AlignHCenter - font.pixelSize: Style.textSize2 - text: API.get().empty_string + (qsTr("Settings")) - } + Layout.alignment: Qt.AlignHCenter - Rectangle { - color: Style.colorTheme7 - radius: Style.rectangleCornerRadius + width: 400 + height: 750 - Layout.alignment: Qt.AlignHCenter + content: ColumnLayout { + width: layout_background.width - 60 + height: layout_background.height - width: 400 - height: layout.childrenRect.height + layout.anchors.topMargin * 2 + ComboBoxWithTitle { + id: combo_fiat + title: API.get().empty_string + (qsTr("Fiat")) + Layout.fillWidth: true - ColumnLayout { - anchors.left: parent.left - anchors.leftMargin: 15 - anchors.right: parent.right - anchors.rightMargin: 15 - anchors.top: parent.top - anchors.topMargin: anchors.leftMargin - id: layout + field.model: fiats - ComboBoxWithTitle { - id: combo_fiat - title: API.get().empty_string + (qsTr("Fiat")) - Layout.fillWidth: true - - field.model: fiats - field.onCurrentIndexChanged: { - API.get().fiat = fiats[field.currentIndex] - } - Component.onCompleted: { - field.currentIndex = fiats.indexOf(API.get().fiat) + property bool initialized: false + field.onCurrentIndexChanged: { + if(initialized) { + const new_fiat = fiats[field.currentIndex] + API.get().current_fiat = new_fiat + API.get().current_currency = new_fiat } } - - Languages { - Layout.alignment: Qt.AlignHCenter + Component.onCompleted: { + field.currentIndex = field.model.indexOf(API.get().current_fiat) + initialized = true } + } - HorizontalLine { - Layout.fillWidth: true - Layout.topMargin: 10 - } + Languages { + Layout.alignment: Qt.AlignHCenter + } - DefaultButton { - Layout.fillWidth: true - text: API.get().empty_string + (qsTr("Open Logs Folder")) - onClicked: { - API.get().export_swaps_json() - const prefix = Qt.platform.os == "windows" ? "file:///" : "file://" - Qt.openUrlExternally(prefix + API.get().get_log_folder()) - } - } + HorizontalLine { + Layout.fillWidth: true + Layout.topMargin: 10 + } - DefaultButton { - Layout.fillWidth: true - text: API.get().empty_string + (qsTr("View Seed")) - onClicked: recover_seed_modal.open() + DefaultButton { + Layout.fillWidth: true + text: API.get().empty_string + (qsTr("Open Logs Folder")) + onClicked: { + API.get().export_swaps_json() + const prefix = Qt.platform.os == "windows" ? "file:///" : "file://" + Qt.openUrlExternally(prefix + API.get().get_log_folder()) } + } - RecoverSeedModal { - id: recover_seed_modal - } + DefaultButton { + Layout.fillWidth: true + text: API.get().empty_string + (qsTr("View Seed")) + onClicked: recover_seed_modal.open() + } - HorizontalLine { - Layout.fillWidth: true - } + RecoverSeedModal { + id: recover_seed_modal + } - DefaultButton { - Layout.fillWidth: true - text: API.get().empty_string + (qsTr("Disclaimer and ToS")) - onClicked: eula.open() - } + HorizontalLine { + Layout.fillWidth: true + } - EulaModal { - id: eula - close_only: true - } + DefaultButton { + Layout.fillWidth: true + text: API.get().empty_string + (qsTr("Disclaimer and ToS")) + onClicked: eula.open() + } - HorizontalLine { - Layout.fillWidth: true - } + EulaModal { + id: eula + close_only: true + } - DangerButton { - text: API.get().empty_string + (qsTr("Delete Wallet")) - Layout.fillWidth: true - onClicked: delete_wallet_modal.open() - } + HorizontalLine { + Layout.fillWidth: true + } - DeleteWalletModal { - id: delete_wallet_modal - } + DangerButton { + text: API.get().empty_string + (qsTr("Delete Wallet")) + Layout.fillWidth: true + onClicked: delete_wallet_modal.open() + } - DefaultButton { - Layout.fillWidth: true - text: API.get().empty_string + (qsTr("Log out")) - onClicked: disconnect() - } + DeleteWalletModal { + id: delete_wallet_modal + } + + DefaultButton { + Layout.fillWidth: true + text: API.get().empty_string + (qsTr("Log out")) + onClicked: disconnect() } } } @@ -135,7 +128,7 @@ Item { anchors.bottom: parent.bottom anchors.bottomMargin: 10 anchors.rightMargin: anchors.bottomMargin - text: API.get().empty_string + (qsTr("mm2 version") + ": " + mm2_version) + text_value: API.get().empty_string + (qsTr("mm2 version") + ": " + mm2_version) font.pixelSize: Style.textSizeSmall } } diff --git a/atomic_qt_design/qml/Sidebar/Sidebar.qml b/atomic_qt_design/qml/Sidebar/Sidebar.qml index ba4ff8e022..6175874e98 100644 --- a/atomic_qt_design/qml/Sidebar/Sidebar.qml +++ b/atomic_qt_design/qml/Sidebar/Sidebar.qml @@ -1,57 +1,160 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 -import "../Constants" - -ColumnLayout { - id: window_layout - transformOrigin: Item.Center - spacing: 0 +import QtGraphicalEffects 1.0 - SidebarLine { - dashboard_index: General.idx_dashboard_portfolio - text: API.get().empty_string + (qsTr("Portfolio")) - image: General.image_path + "menu-assets-portfolio.png" - Layout.fillWidth: true +import "../Constants" +import "../Components" + +Item { + id: sidebar + x: -top_rect.radius + width: 200 - x + height: parent.height + + // Cursor + Rectangle { + id: cursor + width: 170 - cursor_round_edge.radius + anchors.right: parent.right + height: Style.sidebarLineHeight + top_rect.radius*2 + transformOrigin: Item.Left + anchors.verticalCenter: cursor_round_edge.verticalCenter + + gradient: Gradient { + orientation: Qt.Horizontal + + GradientStop { + position: 0.0 + color: Style.colorSidebarHighlightGradient1 + } + GradientStop { + position: cursor_round_edge.radius / cursor.width + color: Style.colorSidebarHighlightGradient1 + } + GradientStop { + position: 0.375 + color: Style.colorSidebarHighlightGradient2 + } + GradientStop { + position: 0.7292 + color: Style.colorSidebarHighlightGradient3 + } + GradientStop { + position: 1.0 + color: Style.colorSidebarHighlightGradient4 + } + } } - SidebarLine { - dashboard_index: General.idx_dashboard_wallet - text: API.get().empty_string + (qsTr("Wallet")) - image: General.image_path + "menu-assets-white.svg" - Layout.fillWidth: true + // Top Rect + SidebarPanel { + id: top_rect + anchors.left: parent.left + width: parent.width + anchors.top: parent.top + anchors.bottom: cursor_round_edge.top + + radius: Style.rectangleCornerRadius } - SidebarLine { - dashboard_index: General.idx_dashboard_exchange - text: API.get().empty_string + (qsTr("DEX")) - image: General.image_path + "menu-exchange-white.svg" - Layout.fillWidth: true + // Bottom Rect + SidebarPanel { + id: bottom_rect + anchors.left: parent.left + width: parent.width + anchors.top: cursor_round_edge.bottom + anchors.bottom: parent.bottom + + radius: Style.rectangleCornerRadius } - SidebarLine { - dashboard_index: General.idx_dashboard_news - text: API.get().empty_string + (qsTr("News")) - image: General.image_path + "menu-news-white.svg" - Layout.fillWidth: true + + // Left Rect + SidebarPanel { + id: left_rect + anchors.left: top_rect.left + anchors.top: top_rect.bottom + anchors.bottom: bottom_rect.top + anchors.right: cursor.left + anchors.topMargin: -top_rect.border.width + anchors.bottomMargin: anchors.topMargin + + border.width: 0 + radius: 0 + + end_pos: top_rect.width*0.95 / width } - SidebarLine { - dashboard_index: General.idx_dashboard_dapps - id: dapps_line - text: API.get().empty_string + (qsTr("DApps")) - image: General.image_path + "menu-dapp-white.svg" - Layout.fillWidth: true + + // Cursor left edge + Rectangle { + id: cursor_round_edge + color: Style.colorSidebarHighlightGradient1 + width: radius*2 + anchors.rightMargin: -width/2 + height: Style.sidebarLineHeight + anchors.right: cursor.left + radius: Style.rectangleCornerRadius + + y: { + switch(dashboard.current_page) { + case General.idx_dashboard_portfolio: + case General.idx_dashboard_wallet: + case General.idx_dashboard_exchange: + case General.idx_dashboard_news: + case General.idx_dashboard_dapps: + return sidebar_center.y + dashboard.current_page * Style.sidebarLineHeight + case General.idx_dashboard_settings: + return sidebar_bottom.y + } + } } - SidebarLine { - dashboard_index: General.idx_dashboard_settings - text: API.get().empty_string + (qsTr("Settings")) - Layout.topMargin: dapps_line.height * 0.5 - image: General.image_path + "menu-settings-white.svg" - Layout.fillWidth: true + // Content + Item { + anchors.right: parent.right + width: parent.width + parent.x + height: parent.height + + DefaultImage { + source: General.image_path + Style.sidebar_atomicdex_logo + anchors.horizontalCenter: parent.horizontalCenter + y: parent.width * 0.25 + transformOrigin: Item.Center + height: 85 + fillMode: Image.PreserveAspectFit + } + + Separator { + anchors.bottom: version_text.top + anchors.bottomMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + } + + DefaultText { + id: version_text + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: parent.width * 0.85 + text_value: API.get().empty_string + ("V. AtomicDEX PRO " + API.get().get_version()) + font.pixelSize: Style.textSizeVerySmall8 + color: Style.colorThemeDarkLight + } + + SidebarCenter { + id: sidebar_center + width: parent.width + anchors.verticalCenter: parent.verticalCenter + } + + SidebarBottom { + id: sidebar_bottom + width: parent.width + anchors.bottom: parent.bottom + anchors.bottomMargin: parent.width * 0.25 + } } } @@ -62,7 +165,6 @@ ColumnLayout { - /*##^## Designer { D{i:0;autoSize:true;height:264;width:150} diff --git a/atomic_qt_design/qml/Sidebar/SidebarBottom.qml b/atomic_qt_design/qml/Sidebar/SidebarBottom.qml new file mode 100644 index 0000000000..c5aba01cd4 --- /dev/null +++ b/atomic_qt_design/qml/Sidebar/SidebarBottom.qml @@ -0,0 +1,52 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import "../Constants" + +ColumnLayout { + id: window_layout + + transformOrigin: Item.Center + spacing: 0 + + SidebarLine { + dashboard_index: General.idx_dashboard_settings + text_value: API.get().empty_string + (qsTr("Settings")) + image: General.image_path + "menu-settings-white.svg" + Layout.fillWidth: true + separator: false + } + + SidebarLine { + dashboard_index: General.idx_dashboard_privacy_mode + text_value: API.get().empty_string + (qsTr("Privacy")) + image: "" + Layout.fillWidth: true + separator: false + checked: General.privacy_mode + } + +// SidebarLine { +// dashboard_index: General.idx_dashboard_light_ui +// text_value: API.get().empty_string + (qsTr("Light UI")) +// image: "" +// Layout.fillWidth: true +// separator: false +// checked: !Style.dark_theme +// } +} + + + + + + + + + +/*##^## +Designer { + D{i:0;autoSize:true;height:264;width:150} +} +##^##*/ diff --git a/atomic_qt_design/qml/Sidebar/SidebarCenter.qml b/atomic_qt_design/qml/Sidebar/SidebarCenter.qml new file mode 100644 index 0000000000..7d24312975 --- /dev/null +++ b/atomic_qt_design/qml/Sidebar/SidebarCenter.qml @@ -0,0 +1,62 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import "../Constants" + +ColumnLayout { + id: window_layout + + transformOrigin: Item.Center + spacing: 0 + + SidebarLine { + dashboard_index: General.idx_dashboard_portfolio + text_value: API.get().empty_string + (qsTr("Dashboard")) + image: General.image_path + "menu-assets-portfolio.svg" + Layout.fillWidth: true + separator: false + } + + SidebarLine { + dashboard_index: General.idx_dashboard_wallet + text_value: API.get().empty_string + (qsTr("Wallet")) + image: General.image_path + "menu-assets-white.svg" + Layout.fillWidth: true + } + + SidebarLine { + dashboard_index: General.idx_dashboard_exchange + text_value: API.get().empty_string + (qsTr("DEX")) + image: General.image_path + "menu-exchange-white.svg" + Layout.fillWidth: true + } + + SidebarLine { + dashboard_index: General.idx_dashboard_news + text_value: API.get().empty_string + (qsTr("News")) + image: General.image_path + "menu-news-white.svg" + Layout.fillWidth: true + } + + SidebarLine { + dashboard_index: General.idx_dashboard_dapps + text_value: API.get().empty_string + (qsTr("Dapps")) + image: General.image_path + "menu-dapp-white.svg" + Layout.fillWidth: true + } +} + + + + + + + + + +/*##^## +Designer { + D{i:0;autoSize:true;height:264;width:150} +} +##^##*/ diff --git a/atomic_qt_design/qml/Sidebar/SidebarLine.qml b/atomic_qt_design/qml/Sidebar/SidebarLine.qml index f6b3dd102c..7d8f626c1c 100644 --- a/atomic_qt_design/qml/Sidebar/SidebarLine.qml +++ b/atomic_qt_design/qml/Sidebar/SidebarLine.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import "../Components" import "../Constants" @@ -9,45 +9,104 @@ import "../Constants" Item { property int dashboard_index property alias image: img.source - property alias text: txt.text + property alias text_value: txt.text + property alias separator: separator.visible + property alias checked: switch_input.checked + readonly property bool selected: dashboard.current_page === dashboard_index + + function toggleDarkUI() { + Style.dark_theme = !Style.dark_theme + } + + function togglePrivacyMode() { + General.privacy_mode = !General.privacy_mode + } + + height: Style.sidebarLineHeight - height: 48 + Switch { + id: switch_input + visible: dashboard_index === General.idx_dashboard_light_ui || + dashboard_index === General.idx_dashboard_privacy_mode + anchors.left: parent.left + anchors.leftMargin: 26 + anchors.verticalCenter: img.verticalCenter + scale: 0.8 + } - Image { + DefaultImage { id: img - width: Style.textSize * 2 + height: txt.font.pixelSize * 1.4 fillMode: Image.PreserveAspectFit anchors.left: parent.left - anchors.leftMargin: 20 + anchors.leftMargin: 50 anchors.verticalCenter: parent.verticalCenter visible: false } + DropShadow { + visible: selected + anchors.fill: img + source: img + cached: false + horizontalOffset: 0 + verticalOffset: 3 + radius: 3 + samples: 4 + spread: 0 + color: "#40000000" + smooth: true + } ColorOverlay { + id: img_color + visible: img.source != "" anchors.fill: img source: img - color: txt.color + color: txt.font.bold ? Style.colorSidebarIconHighlighted : txt.color } - property bool hovered: false - DefaultText { id: txt - anchors.left: parent.left - anchors.leftMargin: img.anchors.leftMargin + Style.textSize * 2.5 + anchors.right: parent.right + anchors.rightMargin: img.anchors.leftMargin anchors.verticalCenter: parent.verticalCenter - font.bold: dashboard.current_page === dashboard_index - color: font.bold ? Style.colorTheme0 : hovered ? Style.colorWhite1 : Style.colorWhite4 + font.pixelSize: Style.textSizeSmall1 + font.weight: selected ? Font.Bold : Font.Medium + color: selected ? Style.colorWhite1 : mouse_area.containsMouse ? Style.colorThemePassiveLight : Style.colorThemePassive + } + DropShadow { + visible: selected + anchors.fill: txt + source: txt + cached: false + horizontalOffset: 0 + verticalOffset: 3 + radius: 3 + samples: 4 + spread: 0 + color: "#40000000" + smooth: true } MouseArea { + id: mouse_area hoverEnabled: true - onHoveredChanged: hovered = containsMouse width: parent.width height: parent.height onClicked: function() { - dashboard.current_page = dashboard_index + if(dashboard_index === General.idx_dashboard_light_ui) { + toggleDarkUI() + } + else if(dashboard_index === General.idx_dashboard_privacy_mode) { + togglePrivacyMode() + } + else dashboard.current_page = dashboard_index } } + + Separator { + id: separator + anchors.horizontalCenter: parent.horizontalCenter + } } diff --git a/atomic_qt_design/qml/Wallet/AddressBook.qml b/atomic_qt_design/qml/Wallet/AddressBook.qml new file mode 100644 index 0000000000..ea79b97bfe --- /dev/null +++ b/atomic_qt_design/qml/Wallet/AddressBook.qml @@ -0,0 +1,509 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 +import "../Components" +import "../Constants" + + +ColumnLayout { + id: address_book + + property bool global_edit_in_progress: false + Layout.fillWidth: true + + property bool initialized: false + property bool inCurrentPage: wallet.inCurrentPage() && main_layout.currentIndex === 1 + + onInCurrentPageChanged: { + if(inCurrentPage) { + initialized = true + } + // Clean-up if user leaves this page + else { + if(initialized) { + console.log("Cleaning up the empty items at address book...") + global_edit_in_progress = false + } + // Open main wallet page + if(main_layout.currentIndex === 1) + closeAddressBook() + } + } + + readonly property var essential_coins: General.all_coins.filter(c => { + if(c.type === "ERC-20" && c.ticker !== "ETH") return false + if(c.type === "Smart Chain" && c.ticker !== "KMD") return false + + return true + }) + + spacing: 20 + + DefaultText { + id: back_button + property bool disabled: global_edit_in_progress + Layout.leftMargin: layout_margin + text_value: API.get().empty_string + ("< " + qsTr("Back")) + font.bold: true + color: disabled ? Style.colorTextDisabled : Style.colorText + + MouseArea { + anchors.fill: parent + onClicked: { if(!back_button.disabled) closeAddressBook() } + } + } + + RowLayout { + Layout.leftMargin: layout_margin + Layout.fillWidth: true + + DefaultText { + text_value: API.get().empty_string + (qsTr("Address Book")) + font.bold: true + font.pixelSize: Style.textSize3 + Layout.fillWidth: true + } + + DefaultButton { + Layout.rightMargin: layout_margin + Layout.alignment: Qt.AlignRight + text: API.get().empty_string + (qsTr("New Contact")) + enabled: !global_edit_in_progress + onClicked: { + API.get().addressbook_mdl.add_contact_entry() + } + } + } + + HorizontalLine { + Layout.fillWidth: true + } + + // Contacts list + DefaultListView { + id: list + Layout.fillWidth: true + Layout.fillHeight: true + model: API.get().addressbook_mdl.addressbook_proxy_mdl + + delegate: Item { + id: contact + readonly property int line_height: 200 + readonly property bool is_last_item: index === model.length - 1 + property bool editing: false + + readonly property var selected_coins: modelData.readonly_addresses.map(c => c.type) + + width: list.width + height: contact_bg.height + layout_margin + + + function kill() { + if(address_book.initialized) + API.get().addressbook_mdl.remove_at(index) + } + + Connections { + target: address_book + + function onInCurrentPageChanged() { + if(!address_book.inCurrentPage) { + const addresses_list = modelData.readonly_addresses + + // No killing if any of the addresses is filled + for(const a of addresses_list) + if(a.address !== "") { + if(contact.editing) + contact.editing = false + + return + } + + // Kill if all addresses are empty + contact.kill() + } + } + } + + + // Contact card + FloatingBackground { + id: contact_bg + + width: parent.width - 2*layout_margin + height: column_layout.height + layout_margin + anchors.centerIn: parent + + ColumnLayout { + id: column_layout + width: parent.width + anchors.centerIn: parent + + RowLayout { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 50 + Layout.alignment: Qt.AlignVCenter + + // Contact name + RowLayout { + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.leftMargin: layout_margin + + DefaultText { + Layout.leftMargin: name_input.Layout.leftMargin + + text: modelData.name + color: Style.colorText + visible: !editing + + Component.onCompleted: { + // Start editing if it's a new/empty one + if(text.length === 0) { + editing = global_edit_in_progress = true + } + } + } + DefaultTextField { + id: name_input + + color: Style.colorText + placeholderText: API.get().empty_string + (qsTr("Enter the contact name")) + width: 150 + onTextChanged: { + const max_length = 50 + if(text.length > max_length) + text = text.substring(0, max_length) + } + + visible: editing + } + + DefaultText { + id: edit_contact + Layout.leftMargin: layout_margin * 0.25 + + visible: !editing && enabled + enabled: !global_edit_in_progress + text: "✎" + font.bold: true + color: Style.colorGreen + + MouseArea { + anchors.fill: parent + onClicked: { + if(edit_contact.enabled) { + name_input.text = modelData.name + editing = global_edit_in_progress = true + } + } + } + } + } + + // Buttons + RowLayout { + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: layout_margin + + PrimaryButton { + Layout.leftMargin: layout_margin + + visible: editing + enabled: name_input.length > 0 + font.pixelSize: Style.textSizeSmall3 + text: "💾" + minWidth: height + onClicked: { + modelData.name = name_input.text + editing = global_edit_in_progress = false + } + } + + DefaultButton { + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: layout_margin + + visible: !editing + enabled: !global_edit_in_progress && essential_coins.length > contact.selected_coins.length + font.pixelSize: Style.textSizeSmall3 + text: "New Address" + onClicked: { + modelData.add_address_content() + } + } + + DangerButton { + Layout.alignment: Qt.AlignVCenter + visible: editing + Layout.leftMargin: layout_margin + + font.pixelSize: Style.textSizeSmall3 + text: "🗑" + minWidth: height + onClicked: { + global_edit_in_progress = false + kill() + } + } + } + } + + HorizontalLine { + Layout.fillWidth: true + } + + // Address list + Column { + Layout.fillWidth: true + + Repeater { + id: address_list + + model: modelData + delegate: Rectangle { + id: address_line + + property bool initialized: false + + function kill() { + if(address_book.initialized) modelData.remove_at(index) + } + + Connections { + target: address_book + + function onInCurrentPageChanged() { + if(address_book.inCurrentPage && !address_line.initialized && address_line.selectable_coins.length === 0) { + address_line.updateSelectableCoins() + address_line.initialized = true + } + + if(!address_book.inCurrentPage) { + if(address === "") address_line.kill() + else if(address_line.editing_address) { + address_line.editing_address = false + } + } + } + } + + property bool editing_address: false + + + property var selectable_coins: ([]) + + Connections { + target: contact + + function onSelected_coinsChanged() { + address_line.updateSelectableCoins() + } + } + + function updateSelectableCoins() { + const original_text = type + + selectable_coins = essential_coins.filter(c => c.ticker === type || contact.selected_coins.indexOf(c.ticker) === -1).map(c => c.ticker) + + if(original_text !== "") + combo_base.currentIndex = address_line.selectable_coins.indexOf(original_text) + } + + + width: contact_bg.width + height: 50 + + color: mouse_area.containsMouse ? Style.colorTheme6 : "transparent" + + MouseArea { + id: mouse_area + anchors.fill: parent + hoverEnabled: true + } + + // Edit + DefaultText { + id: edit_icon + anchors.left: parent.left + anchors.leftMargin: layout_margin + anchors.verticalCenter: parent.verticalCenter + + visible: !editing_address && enabled + enabled: !global_edit_in_progress + text: "✎" + font.bold: true + color: enabled ? Style.colorGreen : Style.colorTextDisabled + + MouseArea { + anchors.fill: parent + onClicked: { + if(edit_icon.enabled) { + address_input.text = address + editing_address = global_edit_in_progress = true + } + } + } + } + + // Icon + DefaultImage { + id: icon + + anchors.left: edit_icon.right + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + + source: General.coinIcon(type) + fillMode: Image.PreserveAspectFit + width: Style.textSize2 + } + + // Name + DefaultText { + anchors.left: combo_base.anchors.left + anchors.leftMargin: combo_base.anchors.leftMargin + anchors.verticalCenter: parent.verticalCenter + visible: !combo_base.visible + + text_value: API.get().empty_string + (type) + } + + DefaultComboBox { + id: combo_base + + anchors.left: icon.right + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + visible: editing_address + + model: selectable_coins + + onCurrentTextChanged: { if(currentText !== type) type = currentText } + } + + VerticalLine { + anchors.top: parent.top + anchors.bottom: parent.bottom + } + + // Address name + DefaultText { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: layout_margin * 5 + text: address + visible: !address_input.visible + font.pixelSize: Style.textSizeSmall3 + + Component.onCompleted: { + // Start editing if it's a new/empty one + if(text.length === 0) { + editing_address = global_edit_in_progress = true + } + } + } + AddressField { + id: address_input + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: layout_margin * 7 + font.pixelSize: Style.textSizeSmall3 + placeholderText: API.get().empty_string + (qsTr("Enter the address")) + width: 400 + visible: editing_address + } + + RowLayout { + anchors.right: parent.right + anchors.rightMargin: layout_margin + anchors.verticalCenter: parent.verticalCenter + + PrimaryButton { + Layout.leftMargin: layout_margin + + visible: editing_address + font.pixelSize: Style.textSizeSmall3 + text: "💾" + enabled: address_input.length > 0 + minWidth: height + onClicked: { + address = address_input.text + editing_address = global_edit_in_progress = false + } + } + + DefaultButton { + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: layout_margin + + font.pixelSize: Style.textSizeSmall3 + text: API.get().empty_string + (qsTr("Explorer")) + enabled: address !== "" && type !== "" + visible: !editing_address + onClicked: General.viewAddressAtExplorer(type, address) + } + + DefaultButton { + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: layout_margin + + font.pixelSize: Style.textSizeSmall3 + text: API.get().empty_string + (qsTr("Send")) + minWidth: height + enabled: address !== "" && type !== "" && API.get().enabled_coins.map(c => c.ticker).indexOf(type) !== -1 + visible: !editing_address + onClicked: { + API.get().current_coin_info.ticker = type + closeAddressBook() + send_modal.address_field.text = address + send_modal.open() + } + } + + DangerButton { + Layout.alignment: Qt.AlignVCenter + visible: editing_address + Layout.leftMargin: layout_margin + + font.pixelSize: Style.textSizeSmall3 + text: "🗑" + minWidth: height + onClicked: { + global_edit_in_progress = false + address_line.kill() + } + } + } + + HorizontalLine { + visible: index !== modelData.length -1 + width: parent.width - 4 + + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: -height/2 + light: true + } + } + } + } + } + } + } + } +} + + + + + + + + +/*##^## +Designer { + D{i:0;autoSize:true;height:600;width:1200} +} +##^##*/ diff --git a/atomic_qt_design/qml/Wallet/AddressList.qml b/atomic_qt_design/qml/Wallet/AddressList.qml index 357a656484..0724eb7783 100644 --- a/atomic_qt_design/qml/Wallet/AddressList.qml +++ b/atomic_qt_design/qml/Wallet/AddressList.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -25,8 +25,9 @@ ColumnLayout { // Row delegate: DefaultText { - text: API.get().empty_string + (model.modelData) + text_value: API.get().empty_string + (model.modelData) color: Style.modalValueColor + privacy: true } } } diff --git a/atomic_qt_design/qml/Wallet/ClaimRewardsModal.qml b/atomic_qt_design/qml/Wallet/ClaimRewardsModal.qml index 14a74c7913..1f0c704ed8 100644 --- a/atomic_qt_design/qml/Wallet/ClaimRewardsModal.qml +++ b/atomic_qt_design/qml/Wallet/ClaimRewardsModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -52,7 +52,7 @@ DefaultModal { // Inside modal // width: stack_layout.children[stack_layout.currentIndex].width + horizontalPadding * 2 - width: 600 + width: 650 height: stack_layout.children[stack_layout.currentIndex].height + verticalPadding * 2 StackLayout { width: parent.width @@ -67,7 +67,7 @@ DefaultModal { DefaultText { visible: text_error.text === "" - text: API.get().empty_string + (qsTr("You will receive %1", "AMT TICKER").arg(General.formatCrypto("", prepare_claim_rewards_result.balance_change, API.get().current_coin_info.ticker))) + text_value: API.get().empty_string + (qsTr("You will receive %1", "AMT TICKER").arg(General.formatCrypto("", prepare_claim_rewards_result.balance_change, API.get().current_coin_info.ticker))) } DefaultText { diff --git a/atomic_qt_design/qml/Wallet/CoinList.qml b/atomic_qt_design/qml/Wallet/CoinList.qml index 40938558cf..b9a9b277bc 100644 --- a/atomic_qt_design/qml/Wallet/CoinList.qml +++ b/atomic_qt_design/qml/Wallet/CoinList.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -16,7 +16,7 @@ Column { checkState: parentBox.checkState } - CheckBox { + DefaultCheckBox { id: parentBox visible: utxo_list.model.length > 0 checkState: childGroup.checkState @@ -25,13 +25,13 @@ Column { Repeater { id: utxo_list - delegate: CheckBox { + delegate: DefaultCheckBox { text: API.get().empty_string + " " + (model.modelData.name + " (" + model.modelData.ticker + ")") leftPadding: indicator.width ButtonGroup.group: childGroup // Icon - Image { + DefaultImage { id: icon anchors.left: parent.left anchors.leftMargin: parent.leftPadding + 28 diff --git a/atomic_qt_design/qml/Wallet/EnableCoinModal.qml b/atomic_qt_design/qml/Wallet/EnableCoinModal.qml index 0165b283a5..7b816f4ac5 100644 --- a/atomic_qt_design/qml/Wallet/EnableCoinModal.qml +++ b/atomic_qt_design/qml/Wallet/EnableCoinModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -69,14 +69,12 @@ DefaultModal { selectByMouse: true } - Flickable { + DefaultFlickable { visible: API.get().enableable_coins.length > 0 width: 350 height: 400 contentWidth: col.width contentHeight: col.height - clip: true - ScrollBar.vertical: ScrollBar { } Column { id: col @@ -106,7 +104,7 @@ DefaultModal { DefaultText { visible: API.get().enableable_coins.length === 0 - text: API.get().empty_string + (qsTr("All coins are already enabled!")) + text_value: API.get().empty_string + (qsTr("All coins are already enabled!")) } // Buttons diff --git a/atomic_qt_design/qml/Wallet/Main.qml b/atomic_qt_design/qml/Wallet/Main.qml new file mode 100644 index 0000000000..534eda5b6b --- /dev/null +++ b/atomic_qt_design/qml/Wallet/Main.qml @@ -0,0 +1,459 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 +import "../Components" +import "../Constants" + +// Right side, main +Item { + property alias send_modal: send_modal + readonly property int layout_margin: 30 + + function openAddressBook() { + main_layout.currentIndex = 1 + } + + function closeAddressBook() { + main_layout.currentIndex = 0 + } + + function reset() { + send_modal.reset(true) + receive_modal.reset() + claim_rewards_modal.reset() + } + + function loadingPercentage(remaining) { + return General.formatPercent((100 * (1 - parseFloat(remaining)/parseFloat(API.get().current_coin_info.tx_current_block))).toFixed(3), false) + } + + Layout.fillHeight: true + Layout.fillWidth: true + + StackLayout { + id: main_layout + + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: layout_margin + anchors.bottom: parent.bottom + + ColumnLayout { + id: wallet_layout + + spacing: 20 + + // Balance box + FloatingBackground { + id: balance_box + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: layout_margin + Layout.rightMargin: layout_margin + + content: RowLayout { + width: balance_box.width + + RowLayout { + Layout.alignment: Qt.AlignLeft + Layout.topMargin: 12 + Layout.bottomMargin: Layout.topMargin + Layout.leftMargin: 15 + spacing: 15 + // Icon + DefaultImage { + source: General.coinIcon(API.get().current_coin_info.ticker) + Layout.preferredHeight: 60 + Layout.preferredWidth: Layout.preferredHeight + } + + // Name and crypto amount + ColumnLayout { + id: balance_layout + spacing: -2 + + DefaultText { + id: name + text_value: API.get().empty_string + (API.get().current_coin_info.name) + Layout.alignment: Qt.AlignLeft + font.pixelSize: Style.textSizeMid + } + + DefaultText { + id: name_value + text_value: API.get().empty_string + (General.formatCrypto("", API.get().current_coin_info.balance, API.get().current_coin_info.ticker)) + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + privacy: true + } + } + } + + // Wallet Balance + ColumnLayout { + Layout.alignment: Qt.AlignLeft + spacing: balance_layout.spacing + DefaultText { + text_value: API.get().empty_string + (qsTr("Wallet Balance")) + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + color: price.color + } + + DefaultText { + text_value: API.get().empty_string + (General.formatFiat("", API.get().current_coin_info.fiat_amount, API.get().current_currency)) + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + privacy: true + } + } + + VerticalLine { + Layout.alignment: Qt.AlignLeft + Layout.rightMargin: 30 + height: balance_layout.height * 0.8 + color: Style.colorThemeDarkLight + } + + // Price + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: balance_layout.spacing + DefaultText { + id: price + text_value: API.get().empty_string + (qsTr("Price")) + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + color: Style.colorText2 + } + + DefaultText { + text_value: API.get().empty_string + (General.formatFiat('', API.get().current_coin_info.main_currency_balance, API.get().current_currency)) + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + } + } + + // Change 24h + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: balance_layout.spacing + DefaultText { + text_value: API.get().empty_string + (qsTr("Change 24h")) + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + color: price.color + } + + DefaultText { + text_value: { + const v = parseFloat(API.get().current_coin_info.change_24h) + return API.get().empty_string + (v === 0 ? '-' : General.formatPercent(v)) + } + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + color: Style.getValueColor(API.get().current_coin_info.change_24h) + } + } + + // Portfolio % + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: balance_layout.spacing + DefaultText { + text_value: API.get().empty_string + (qsTr("Portfolio %")) + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + color: price.color + } + + DefaultText { + text_value: { + const fiat_amount = parseFloat(API.get().current_coin_info.fiat_amount) + const portfolio_balance = parseFloat(API.get().balance_fiat_all) + if(fiat_amount <= 0 || portfolio_balance <= 0) return "-" + + return API.get().empty_string + (General.formatPercent((100 * fiat_amount/portfolio_balance).toFixed(2), false)) + } + Layout.alignment: Qt.AlignLeft + font.pixelSize: name.font.pixelSize + privacy: true + } + } + } + } + + InnerBackground { + id: price_graph_bg + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: layout_margin + Layout.rightMargin: layout_margin + Layout.bottomMargin: -parent.spacing*0.5 + implicitHeight: wallet.height*0.6 + + visible: chart.has_data + + PriceGraph { + id: chart + width: price_graph_bg.width + height: price_graph_bg.height + + RowLayout { + spacing: 60 + y: 10 + anchors.horizontalCenter: parent.horizontalCenter + + RowLayout { + Layout.alignment: Qt.AlignLeft + + FloatingBackground { + id: left_circle + + verticalShadow: true + width: 28; height: 28 + radius: 100 + + content: DefaultImage { + source: General.image_path + "shadowed_circle_green.svg" + + width: 12; height: width + } + } + + DefaultText { + id: left_text + text_value: API.get().empty_string + (qsTr("%1 / %2 Price", "TICKER").arg(API.get().current_coin_info.ticker).arg(API.get().current_fiat) + " " + General.cex_icon) + font.pixelSize: Style.textSizeSmall3 + + CexInfoTrigger {} + } + } + + RowLayout { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + + FloatingBackground { + verticalShadow: left_circle.verticalShadow + width: left_circle.width; height: left_circle.height + radius: 100 + + content: DefaultImage { + source: General.image_path + "shadowed_circle_blue.svg" + + width: 12; height: width + } + } + + DefaultText { + text_value: API.get().empty_string + (qsTr("Volume 24h") + " (" + API.get().current_fiat + ")") + font: left_text.font + } + } + } + } + } + + // Address Book, Send, Receive buttons + RowLayout { + Layout.preferredWidth: main_layout.width + Layout.bottomMargin: -parent.spacing*0.5 + Layout.leftMargin: layout_margin + Layout.rightMargin: layout_margin + + spacing: 15 + + RowLayout { + Layout.alignment: Qt.AlignRight + spacing: 15 + + DefaultButton { + text: API.get().empty_string + (qsTr("Address Book")) + onClicked: openAddressBook() + } + + DefaultButton { + enabled: parseFloat(API.get().current_coin_info.balance) > 0 + text: API.get().empty_string + (qsTr("Send")) + onClicked: send_modal.open() + text_offset: -arrow_send.anchors.rightMargin + text_left_align: true + + Arrow { + id: arrow_send + up: true + color: Style.colorGreen + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 8 + } + } + + SendModal { + id: send_modal + } + + DefaultButton { + text: API.get().empty_string + (qsTr("Receive")) + onClicked: receive_modal.open() + text_offset: -arrow_send.anchors.rightMargin + text_left_align: true + + Arrow { + up: false + color: Style.colorBlue + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: arrow_send.anchors.rightMargin + } + } + + ReceiveModal { + id: receive_modal + } + + DefaultButton { + text: API.get().empty_string + (qsTr("Swap")) + onClicked: onClickedSwap() + text_offset: -arrow_send.anchors.rightMargin + text_left_align: true + + Arrow { + up: true + color: Style.colorGreen + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: arrow_send.anchors.rightMargin*2.4 + } + + Arrow { + up: false + color: Style.colorBlue + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: arrow_send.anchors.rightMargin + } + } + + PrimaryButton { + id: button_claim_rewards + text: API.get().empty_string + (qsTr("Claim Rewards")) + + visible: API.get().current_coin_info.is_claimable === true + enabled: claim_rewards_modal.canClaim() + onClicked: { + claim_rewards_modal.prepareClaimRewards() + claim_rewards_modal.open() + } + } + + ClaimRewardsModal { + id: claim_rewards_modal + + postClaim: () => { button_claim_rewards.enabled = claim_rewards_modal.canClaim() } + } + } + } + + // Separator line + HorizontalLine { + Layout.fillWidth: true + Layout.leftMargin: layout_margin + Layout.rightMargin: layout_margin + Layout.alignment: Qt.AlignHCenter + } + + // Transactions or loading + Item { + id: loading_tx + visible: API.get().current_coin_info.tx_state === "InProgress" + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + implicitHeight: 100 + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + DefaultText { + text_value: API.get().empty_string + (qsTr("Loading")) + Layout.alignment: Qt.AlignHCenter + font.pixelSize: Style.textSize2 + } + + DefaultBusyIndicator { + Layout.alignment: Qt.AlignHCenter + } + + DefaultText { + text_value: API.get().empty_string + ( + API.get().current_coin_info.type === "ERC-20" ? + (qsTr("Scanning blocks for TX History...") + " " + loadingPercentage(API.get().current_coin_info.blocks_left)) : + (qsTr("Syncing TX History...") + " " + loadingPercentage(API.get().current_coin_info.transactions_left)) + ) + Layout.alignment: Qt.AlignHCenter + } + } + } + + // Separator line + HorizontalLine { + visible: loading_tx.visible && API.get().current_coin_info.transactions.length > 0 + width: 720 + Layout.alignment: Qt.AlignHCenter + } + + InnerBackground { + id: transactions_bg + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: layout_margin + Layout.rightMargin: layout_margin + Layout.bottomMargin: layout_margin + + implicitHeight: wallet.height*0.54 + + content: Item { + width: transactions_bg.width + height: transactions_bg.height + + DefaultText { + anchors.centerIn: parent + visible: API.get().current_coin_info.tx_state !== "InProgress" && API.get().current_coin_info.transactions.length === 0 + text_value: API.get().empty_string + (qsTr("No transactions")) + font.pixelSize: Style.textSize + color: Style.colorWhite4 + } + + Transactions { + width: parent.width + height: parent.height + } + } + } + + implicitHeight: Math.min(contentItem.childrenRect.height, wallet.height*0.5) + } + + AddressBook { + + } + } +} + + + + + + + + + +/*##^## +Designer { + D{i:0;autoSize:true;height:600;width:1200} +} +##^##*/ diff --git a/atomic_qt_design/qml/Wallet/PriceGraph.qml b/atomic_qt_design/qml/Wallet/PriceGraph.qml new file mode 100644 index 0000000000..551cf3f953 --- /dev/null +++ b/atomic_qt_design/qml/Wallet/PriceGraph.qml @@ -0,0 +1,183 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtCharts 2.3 +import "../Components" +import "../Constants" + +// List +ChartView { + readonly property bool has_data: series.count > 0 + + AreaSeries { + id: series_area2 + color: Style.colorTheme10 + + onHovered: updateValueText(state, point.y, axisYRight.labelsColor, 0) + + borderWidth: series_area.borderWidth + opacity: series_area.opacity + + axisX: series2.axisX + axisYRight: series2.axisYRight + upperSeries: series2 + } + + // Other, back + LineSeries { + id: series2 + color: Style.colorTheme10 + + style: series.style + width: series.width + + onHovered: updateValueText(state, point.y, axisYRight.labelsColor, 0) + + axisX: DateTimeAxis { + visible: false + titleVisible: series.axisX.titleVisible + lineVisible: series.axisX.lineVisible + labelsFont: series.axisX.labelsFont + gridLineColor: series.axisX.gridLineColor + labelsColor: series.axisX.labelsColor + format: "MMM d" + } + axisYRight: ValueAxis { + visible: true + titleVisible: series.axisY.titleVisible + lineVisible: series.axisY.lineVisible + labelsFont: series.axisY.labelsFont + gridLineColor: series.axisY.gridLineColor + labelsColor: series2.color + labelFormat: "%.3f M" + } + } + + AreaSeries { + id: series_area + color: Style.colorTheme1 + onHovered: updateValueText(state, point.y, axisY.labelsColor, 2) + + borderWidth: 0 + opacity: 0.15 + + axisX: series.axisX + axisY: series.axisY + upperSeries: series + } + + // Price, front + LineSeries { + id: series + color: Style.colorTheme0 + + style: Qt.SolidLine + width: 1.5 + + onHovered: updateValueText(state, point.y, axisY.labelsColor, 2) + + axisX: DateTimeAxis { + titleVisible: false + lineVisible: false + labelsFont.family: Style.font_family + labelsFont.weight: Font.Bold + gridLineColor: Style.colorThemeDark2 + labelsColor: Style.colorThemeDark3 + format: "
MMM d" + } + axisY: ValueAxis { + titleVisible: series.axisX.titleVisible + lineVisible: series.axisX.lineVisible + labelsFont: series.axisX.labelsFont + gridLineColor: series.axisX.gridLineColor + labelsColor: series.color + } + } + + function updateValueText(state, value, color, precision) { + value_text.visible = state + value_text.text = General.formatDouble(value, precision) + value_text.color = color + } + + DefaultText { + id: value_text + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: 50 + anchors.leftMargin: anchors.topMargin * 2 + font.pixelSize: Style.textSizeSmall3 + } + + + function updateChart(historical) { + series.clear() + series2.clear() + + if(historical === undefined) return + + if(historical.length > 0) { + let min_price = Infinity + let max_price = -Infinity + let min_other = Infinity + let max_other = -Infinity + + for(let i = 0; i < historical.length; ++i) { + const price = historical[i].price + const other = historical[i].volume_24h / 1000000 + + series.append(General.timestampToDouble(historical[i].timestamp), price) + series2.append(General.timestampToDouble(historical[i].timestamp), other) + + min_price = Math.min(min_price, price) + max_price = Math.max(max_price, price) + min_other = Math.min(min_other, other) + max_other = Math.max(max_other, other) + } + + // Date + series.axisX.min = historical[0].timestamp + series.axisX.max = historical[historical.length-1].timestamp + series.axisX.tickCount = 7 + + series2.axisX.min = series.axisX.min + series2.axisX.max = series.axisX.max + series2.axisX.tickCount = series.axisX.tickCount + + const y_margin = 0.05 + + // Price + series.axisY.min = min_price * (1 - y_margin) + series.axisY.max = max_price * (1 + y_margin) + + // Other + series2.axisYRight.min = min_other * (1 - y_margin) + series2.axisYRight.max = max_other * (1 + y_margin) + } + } + + property var historical: API.get().current_coin_info.trend_7d + onHistoricalChanged: { + updateChart(historical) + } + + id: chart + width: parent.width + height: parent.height + antialiasing: true + + legend.visible: false + + backgroundColor: "transparent" +} + + + + + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/atomic_qt_design/qml/Wallet/ReceiveModal.qml b/atomic_qt_design/qml/Wallet/ReceiveModal.qml index c2fdc22d11..d3ebd12ad7 100644 --- a/atomic_qt_design/qml/Wallet/ReceiveModal.qml +++ b/atomic_qt_design/qml/Wallet/ReceiveModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -35,10 +35,10 @@ DefaultModal { Layout.alignment: Qt.AlignHCenter source: "image://QZXing/encode/" + API.get().current_coin_info.address + - "?correctionLevel=M" + - "&format=qrcode" - sourceSize.width: 180 - sourceSize.height: 180 + "?correctionLevel=H" + + "&format=qrcode&border=true" + sourceSize.width: 240 + sourceSize.height: 240 } // Buttons diff --git a/atomic_qt_design/qml/Wallet/SendModal.qml b/atomic_qt_design/qml/Wallet/SendModal.qml index 017d359d29..7a0adaa1b2 100644 --- a/atomic_qt_design/qml/Wallet/SendModal.qml +++ b/atomic_qt_design/qml/Wallet/SendModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -9,6 +9,9 @@ import "../Constants" DefaultModal { id: root + property alias address_field: input_address.field + + onClosed: if(stack_layout.currentIndex === 2) reset(true) // Local @@ -154,7 +157,7 @@ DefaultModal { // Inside modal // width: stack_layout.children[stack_layout.currentIndex].width + horizontalPadding * 2 - width: 600 + width: 650 height: stack_layout.children[stack_layout.currentIndex].height + verticalPadding * 2 StackLayout { width: parent.width @@ -169,10 +172,24 @@ DefaultModal { } // Send address - AddressField { - id: input_address - title: API.get().empty_string + (qsTr("Recipient's address")) - field.placeholderText: API.get().empty_string + (qsTr("Enter address of the recipient")) + RowLayout { + spacing: Style.buttonSpacing + + AddressFieldWithTitle { + id: input_address + Layout.alignment: Qt.AlignLeft + title: API.get().empty_string + (qsTr("Recipient's address")) + field.placeholderText: API.get().empty_string + (qsTr("Enter address of the recipient")) + } + + DefaultButton { + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + text: API.get().empty_string + (qsTr("Address Book")) + onClicked: { + openAddressBook() + root.close() + } + } } // ERC-20 Lowercase issue @@ -182,7 +199,7 @@ DefaultModal { DefaultText { Layout.alignment: Qt.AlignLeft color: Style.colorRed - text: API.get().empty_string + (qsTr("The address has to be mixed case.")) + text_value: API.get().empty_string + (qsTr("The address has to be mixed case.")) } DefaultButton { @@ -193,6 +210,8 @@ DefaultModal { } RowLayout { + spacing: Style.buttonSpacing + // Amount input AmountField { id: input_amount @@ -203,6 +222,7 @@ DefaultModal { Switch { id: input_max_amount + Layout.alignment: Qt.AlignRight | Qt.AlignBottom text: API.get().empty_string + (qsTr("MAX")) onCheckedChanged: input_amount.field.text = "" } @@ -222,7 +242,7 @@ DefaultModal { DefaultText { font.pixelSize: Style.textSize color: Style.colorRed - text: API.get().empty_string + (qsTr("Only use custom fees if you know what you are doing!")) + text_value: API.get().empty_string + (qsTr("Only use custom fees if you know what you are doing!")) } // Normal coins, Custom fees input @@ -263,7 +283,7 @@ DefaultModal { color: Style.colorRed - text: API.get().empty_string + (qsTr("Custom Fee can't be higher than the amount")) + text_value: API.get().empty_string + (qsTr("Custom Fee can't be higher than the amount")) } // Not enough funds error @@ -273,7 +293,7 @@ DefaultModal { color: Style.colorRed - text: API.get().empty_string + (qsTr("Not enough funds.") + "\n" + qsTr("You have %1", "AMT TICKER").arg(General.formatCrypto("", API.get().get_balance(API.get().current_coin_info.ticker), API.get().current_coin_info.ticker))) + text_value: API.get().empty_string + (qsTr("Not enough funds.") + "\n" + qsTr("You have %1", "AMT TICKER").arg(General.formatCrypto("", API.get().get_balance(API.get().current_coin_info.ticker), API.get().current_coin_info.ticker))) } DefaultText { @@ -322,7 +342,7 @@ DefaultModal { // Fees TextWithTitle { title: API.get().empty_string + (qsTr("Fees")) - text: API.get().empty_string + (General.formatCrypto("", prepare_send_result.fees, API.get().current_coin_info.ticker)) + text: API.get().empty_string + (General.formatCrypto("", prepare_send_result.fees, General.txFeeTicker(API.get().current_coin_info))) } // Date diff --git a/atomic_qt_design/qml/Wallet/SendResult.qml b/atomic_qt_design/qml/Wallet/SendResult.qml index 4988a2582b..e27e247aa7 100644 --- a/atomic_qt_design/qml/Wallet/SendResult.qml +++ b/atomic_qt_design/qml/Wallet/SendResult.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -33,7 +33,7 @@ ColumnLayout { // Fees TextWithTitle { title: API.get().empty_string + (qsTr("Fees")) - text: API.get().empty_string + (result.fees) + text: API.get().empty_string + (General.formatCrypto("", result.fees, General.txFeeTicker(API.get().current_coin_info))) } // Date diff --git a/atomic_qt_design/qml/Wallet/Sidebar.qml b/atomic_qt_design/qml/Wallet/Sidebar.qml new file mode 100644 index 0000000000..d255753070 --- /dev/null +++ b/atomic_qt_design/qml/Wallet/Sidebar.qml @@ -0,0 +1,234 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import QtGraphicalEffects 1.0 +import "../Components" +import "../Constants" + +// Coins bar at left side +Item { + id: root + + function reset() { + input_coin_filter_text = '' + resetted() + } + + signal resetted() + + property string input_coin_filter_text + + Layout.alignment: Qt.AlignLeft + width: 175 + Layout.fillHeight: true + + // Background + SidebarPanel { + id: background + anchors.right: parent.right + width: sidebar.width + parent.width + + height: parent.height + + // Panel contents + Item { + id: coins_bar + width: 175 + height: parent.height + anchors.right: parent.right + + VerticalLine { + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + anchors.topMargin: anchors.bottomMargin + color: Style.colorWhite12 + } + + InnerBackground { + id: search_row_bg + anchors.top: parent.top + anchors.topMargin: 30 + width: list_bg.width + anchors.horizontalCenter: list_bg.horizontalCenter + + radius: 100 + + content: RowLayout { + id: search_row + + width: search_row_bg.width + + // Search button + Item { + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: search_button.width + Layout.rightMargin: -Layout.leftMargin + width: search_button.width + height: search_button.height + DefaultImage { + id: search_button + + source: General.image_path + "exchange-search.svg" + + width: input_coin_filter.font.pixelSize; height: width + + visible: false + } + ColorOverlay { + id: search_button_overlay + + anchors.fill: search_button + source: search_button + color: Style.colorText + } + } + + // Search input + DefaultTextField { + id: input_coin_filter + + Connections { + target: root + + function onResetted() { + input_coin_filter.text = "" + } + } + + onTextChanged: input_coin_filter_text = text + font.pixelSize: Style.textSizeSmall3 + + background: null + + selectByMouse: true + Layout.fillWidth: true + } + } + } + + // Add button + PlusButton { + id: add_coin_button + onClicked: enable_coin_modal.prepareAndOpen() + + anchors.bottom: parent.bottom + anchors.bottomMargin: parent.width * 0.5 - height * 0.5 + anchors.horizontalCenter: parent.horizontalCenter + } + + // Coins list + InnerBackground { + id: list_bg + width: 145 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + content: DefaultListView { + id: list + implicitHeight: Math.min(contentItem.childrenRect.height, coins_bar.height - 250) + + model: General.filterCoins(API.get().enabled_coins, input_coin_filter_text) + + delegate: GradientRectangle { + width: list_bg.width - list_bg.border.width*2 - 2 + height: 44 + radius: Style.rectangleCornerRadius + + start_color: API.get().current_coin_info.ticker === model.modelData.ticker ? Style.colorCoinListHighlightGradient1 : mouse_area.containsMouse ? Style.colorCoinListHighlightGradient1 : "transparent" + end_color: API.get().current_coin_info.ticker === model.modelData.ticker ? Style.colorCoinListHighlightGradient2 : mouse_area.containsMouse ? Style.colorWhite8 : "transparent" + + // Click area + MouseArea { + id: mouse_area + anchors.fill: parent + hoverEnabled: true + + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.RightButton) context_menu.popup() + else API.get().current_coin_info.ticker = model.modelData.ticker + + main.send_modal.reset() + } + onPressAndHold: { + if (mouse.source === Qt.MouseEventNotSynthesized) context_menu.popup() + } + } + + // Right click menu + Menu { + id: context_menu + Action { + text: API.get().empty_string + (qsTr("Disable %1", "TICKER").arg(model.modelData.ticker)) + onTriggered: API.get().disable_coins([model.modelData.ticker]) + enabled: General.canDisable(model.modelData.ticker) + } + } + + readonly property double side_margin: 25 + + // Icon + DefaultImage { + id: icon + anchors.left: parent.left + anchors.leftMargin: side_margin - scrollbar_margin + + source: General.coinIcon(model.modelData.ticker) + fillMode: Image.PreserveAspectFit + width: Style.textSizeSmall4*2 + anchors.verticalCenter: parent.verticalCenter + } + + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: side_margin + scrollbar_margin + +// spacing: -3 +// // Name +// DefaultText { +// Layout.alignment: Qt.AlignRight +// text_value: API.get().empty_string + (model.modelData.name.replace(" (TESTCOIN)", "")) +// font.pixelSize: text.length > 15 ? Style.textSizeVerySmall8 : text.length > 12 ? Style.textSizeVerySmall9 : Style.textSizeSmall1 +// } + + // Ticker + DefaultText { + Layout.alignment: Qt.AlignRight + text_value: API.get().empty_string + (model.modelData.ticker) + font.pixelSize: text.length > 15 ? Style.textSizeVerySmall8 : text.length > 12 ? Style.textSizeVerySmall9 : Style.textSizeSmall1 +// font.pixelSize: Style.textSizeSmall1 +// color: Style.colorThemePassive + } + + ToolTip { + visible: mouse_area.containsMouse + background: FloatingBackground { auto_set_size: false } + contentItem: DefaultText { + text_value: API.get().empty_string + (model.modelData.name.replace(" (TESTCOIN)", "")) + font.pixelSize: Style.textSizeSmall4 + } + } + } + } + } + } + } + } + + DropShadow { + anchors.fill: background + source: background + cached: false + horizontalOffset: 0 + verticalOffset: 0 + radius: 32 + samples: 32 + spread: 0 + color: Style.colorWalletsSidebarDropShadow + smooth: true + } +} diff --git a/atomic_qt_design/qml/Wallet/TransactionDetailsModal.qml b/atomic_qt_design/qml/Wallet/TransactionDetailsModal.qml index 2e4b48c231..fc308b2251 100644 --- a/atomic_qt_design/qml/Wallet/TransactionDetailsModal.qml +++ b/atomic_qt_design/qml/Wallet/TransactionDetailsModal.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" @@ -14,9 +14,13 @@ DefaultModal { } property var details + contentWidth: layout.width // Inside modal ColumnLayout { + id: layout + width: 700 + ModalHeader { title: API.get().empty_string + (qsTr("Transaction Details")) } @@ -24,26 +28,29 @@ DefaultModal { // Amount TextWithTitle { title: API.get().empty_string + (qsTr("Amount")) - text: API.get().empty_string + (General.formatCrypto(details.received, details.amount, API.get().current_coin_info.ticker, details.amount_fiat, API.get().fiat)) + text: API.get().empty_string + (General.formatCrypto(details.received, details.amount, API.get().current_coin_info.ticker, details.amount_fiat, API.get().current_currency)) value_color: details.received ? Style.colorGreen : Style.colorRed + privacy: true } // Fees TextWithTitle { title: API.get().empty_string + (qsTr("Fees")) - text: API.get().empty_string + (General.formatCrypto("", details.fees, API.get().current_coin_info.ticker)) + text: API.get().empty_string + (General.formatCrypto("", details.fees, General.txFeeTicker(API.get().current_coin_info))) + privacy: true } // Date TextWithTitle { title: API.get().empty_string + (qsTr("Date")) - text:API.get().empty_string + (details.timestamp === 0 ? qsTr("Unconfirmed"): details.date) + text: API.get().empty_string + (details.timestamp === 0 ? qsTr("Unconfirmed"): details.date) } // Transaction Hash TextWithTitle { title: API.get().empty_string + (qsTr("Transaction Hash")) text: API.get().empty_string + (details.tx_hash) + privacy: true } // Confirmations diff --git a/atomic_qt_design/qml/Wallet/Transactions.qml b/atomic_qt_design/qml/Wallet/Transactions.qml index e624ba6fce..b43a38e6fd 100644 --- a/atomic_qt_design/qml/Wallet/Transactions.qml +++ b/atomic_qt_design/qml/Wallet/Transactions.qml @@ -1,42 +1,33 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import "../Components" import "../Constants" -// List -ListView { +DefaultListView { id: list - ScrollBar.vertical: ScrollBar {} - implicitWidth: contentItem.childrenRect.width - implicitHeight: contentItem.childrenRect.height + + readonly property int row_height: 45 model: { const confirmed = API.get().current_coin_info.transactions.filter(t => t.timestamp !== 0) const unconfirmed = API.get().current_coin_info.transactions.filter(t => t.timestamp === 0) return unconfirmed.concat(confirmed) } - clip: true - - function reset() { - - } // Row delegate: Rectangle { id: rectangle implicitWidth: list.width - height: 65 - - property bool hovered: false + height: row_height - color: hovered ? Style.colorTheme8 : "transparent" + color: mouse_area.containsMouse ? Style.colorTheme6 : "transparent" MouseArea { + id: mouse_area anchors.fill: parent hoverEnabled: true - onHoveredChanged: hovered = containsMouse onClicked: tx_details_modal.open() } @@ -45,47 +36,77 @@ ListView { details: model.modelData } - // Icon - Image { + Arrow { id: received_icon - source: General.image_path + "circle-" + (model.modelData.received ? "success" : "failed") + ".png" - fillMode: Image.PreserveAspectFit - width: Style.textSize2 + up: !model.modelData.received + color: model.modelData.received ? Style.colorGreen : Style.colorRed anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.horizontalCenter - anchors.rightMargin: 350 + anchors.left: parent.left + anchors.leftMargin: 15 } - // Amount - ColumnLayout { + // Description + DefaultText { + id: description + text_value: API.get().empty_string + (model.modelData.received ? qsTr("Incoming transaction") : qsTr("Outgoing transaction")) + font.pixelSize: Style.textSizeSmall1 + anchors.verticalCenter: parent.verticalCenter + anchors.left: received_icon.right + anchors.leftMargin: 25 + } + + // Crypto + DefaultText { + id: crypto_amount + text_value: API.get().empty_string + (General.formatCrypto(model.modelData.received, model.modelData.amount, API.get().current_coin_info.ticker)) + font.pixelSize: description.font.pixelSize anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.horizontalCenter - anchors.rightMargin: -100 - - // Crypto - DefaultText { - text: API.get().empty_string + (General.formatCrypto(model.modelData.received, model.modelData.amount, API.get().current_coin_info.ticker)) - Layout.alignment: Qt.AlignRight - font.pixelSize: Style.textSize2 - } - - // Fiat - DefaultText { - text: API.get().empty_string + (General.formatFiat(model.modelData.received, model.modelData.amount_fiat, API.get().fiat)) - Layout.topMargin: -10 - Layout.rightMargin: 4 - Layout.alignment: Qt.AlignRight - font.pixelSize: Style.textSize - color: Style.colorWhite4 - } + anchors.left: parent.left + anchors.leftMargin: parent.width * 0.25 + color: model.modelData.received ? Style.colorGreen : Style.colorRed + privacy: true + } + + // Fiat + DefaultText { + text_value: API.get().empty_string + (General.formatFiat(model.modelData.received, model.modelData.amount_fiat, API.get().current_currency)) + font.pixelSize: description.font.pixelSize + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: parent.width * 0.45 + color: crypto_amount.color + privacy: true + } + + // Fee + DefaultText { + text_value: API.get().empty_string + (General.formatCrypto(!(parseFloat(model.modelData.fees) > 0), Math.abs(parseFloat(model.modelData.fees)), + General.txFeeTicker(API.get().current_coin_info)) + " " + qsTr("transaction fee")) + font.pixelSize: description.font.pixelSize + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: parent.width * 0.575 + privacy: true } // Date DefaultText { - anchors.right: parent.horizontalCenter - anchors.rightMargin: -380 - text: API.get().empty_string + (model.modelData.timestamp === 0 ? qsTr("Unconfirmed"): model.modelData.date) + font.pixelSize: description.font.pixelSize + text_value: API.get().empty_string + (model.modelData.timestamp === 0 ? qsTr("Unconfirmed"): model.modelData.date) anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 20 + privacy: true + } + + HorizontalLine { + visible: index !== API.get().current_coin_info.transactions.length -1 + width: parent.width - 4 + + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: -height/2 + light: true } } } @@ -95,7 +116,6 @@ ListView { - /*##^## Designer { D{i:0;autoSize:true;height:480;width:640} diff --git a/atomic_qt_design/qml/Wallet/Wallet.qml b/atomic_qt_design/qml/Wallet/Wallet.qml index 5b262ba4dc..66013c1cfd 100644 --- a/atomic_qt_design/qml/Wallet/Wallet.qml +++ b/atomic_qt_design/qml/Wallet/Wallet.qml @@ -1,7 +1,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -import QtQuick.Controls.Material 2.12 + import QtGraphicalEffects 1.0 import "../Components" import "../Constants" @@ -9,6 +9,11 @@ import "../Constants" RowLayout { id: wallet + function inCurrentPage() { + return dashboard.inCurrentPage() && + dashboard.current_page === General.idx_dashboard_wallet + } + // Local function onClickedSwap() { dashboard.current_page = General.idx_dashboard_exchange @@ -17,336 +22,25 @@ RowLayout { } function reset() { - send_modal.reset(true) - receive_modal.reset() - claim_rewards_modal.reset() + main.reset() + sidebar.reset() enable_coin_modal.reset() - - transactions.reset() - input_coin_filter.reset() } - function loadingPercentage(remaining) { - return General.formatPercent((100 * (1 - parseFloat(remaining)/parseFloat(API.get().current_coin_info.tx_current_block))).toFixed(3), false) - } + function onOpened() { } readonly property double button_margin: 0.05 spacing: 0 Layout.fillWidth: true - // Left side, main - Item { - Layout.fillHeight: true - Layout.fillWidth: true - ColumnLayout { - id: wallet_layout - width: parent.width - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 50 - anchors.bottom: parent.bottom - - spacing: 30 - - // Balance texts - RowLayout { - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - ColumnLayout { - id: balance_layout - DefaultText { - id: balance_text - text: API.get().empty_string + (General.formatCrypto("", API.get().current_coin_info.balance, API.get().current_coin_info.ticker)) - Layout.alignment: Qt.AlignRight - font.pixelSize: Style.textSize5 - } - - DefaultText { - id: balance_fiat_text - text: API.get().empty_string + (General.formatFiat("", API.get().current_coin_info.fiat_amount, API.get().fiat)) - Layout.topMargin: -15 - Layout.rightMargin: 4 - Layout.alignment: Qt.AlignRight - font.pixelSize: Style.textSize2 - color: Style.colorWhite4 - } - } - Image { - source: General.coinIcon(API.get().current_coin_info.ticker) - Layout.leftMargin: 10 - Layout.preferredHeight: balance_text.font.pixelSize + balance_fiat_text.font.pixelSize - Layout.preferredWidth: Layout.preferredHeight - } - } - - // Send, Receive buttons at top - RowLayout { - Layout.topMargin: -10 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - spacing: 50 - - DefaultButton { - text: API.get().empty_string + (qsTr("Send")) - onClicked: send_modal.open() - enabled: parseFloat(API.get().current_coin_info.balance) > 0 - } - - SendModal { - id: send_modal - } - - DefaultButton { - text: API.get().empty_string + (qsTr("Receive")) - onClicked: receive_modal.open() - } - - ReceiveModal { - id: receive_modal - } - - DefaultButton { - text: API.get().empty_string + (qsTr("Swap")) - onClicked: onClickedSwap() - } - - PrimaryButton { - id: button_claim_rewards - text: API.get().empty_string + (qsTr("Claim Rewards")) - - visible: API.get().current_coin_info.is_claimable === true - enabled: claim_rewards_modal.canClaim() - onClicked: { - if(claim_rewards_modal.prepareClaimRewards()) - claim_rewards_modal.open() - } - } - - ClaimRewardsModal { - id: claim_rewards_modal - - postClaim: () => { button_claim_rewards.enabled = claim_rewards_modal.canClaim() } - } - } - - // Separator line - HorizontalLine { - width: 720 - Layout.alignment: Qt.AlignHCenter - } - - DefaultText { - visible: API.get().current_coin_info.tx_state !== "InProgress" && API.get().current_coin_info.transactions.length === 0 - text: API.get().empty_string + (qsTr("No transactions")) - font.pixelSize: Style.textSize - color: Style.colorWhite4 - Layout.alignment: Qt.AlignHCenter - } - - - // Transactions or loading - Rectangle { - id: loading_tx - color: "transparent" - visible: API.get().current_coin_info.tx_state === "InProgress" - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - implicitHeight: 100 - - ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - DefaultText { - text: API.get().empty_string + (qsTr("Loading")) - Layout.alignment: Qt.AlignHCenter - font.pixelSize: Style.textSize2 - } - - BusyIndicator { - Layout.alignment: Qt.AlignHCenter - } - - DefaultText { - text: API.get().empty_string + ( - API.get().current_coin_info.type === "ERC-20" ? - (qsTr("Scanning blocks for TX History...") + " " + loadingPercentage(API.get().current_coin_info.blocks_left)) : - (qsTr("Syncing TX History...") + " " + loadingPercentage(API.get().current_coin_info.transactions_left)) - ) - Layout.alignment: Qt.AlignHCenter - } - } - } - - // Separator line - HorizontalLine { - visible: loading_tx.visible && transactions.model.length > 0 - width: 720 - Layout.alignment: Qt.AlignHCenter - } - - Transactions { - id: transactions - Layout.fillWidth: true - Layout.fillHeight: true - implicitHeight: Math.min(contentItem.childrenRect.height, wallet.height*0.5) - } - - implicitHeight: Math.min(contentItem.childrenRect.height, wallet.height*0.5) - } + // Coins bar at left side + Sidebar { + id: sidebar } - // Coins bar at right side - Rectangle { - id: coins_bar - Layout.alignment: Qt.AlignRight - width: 150 - Layout.fillHeight: true - color: Style.colorTheme7 - - // Balance - DefaultText { - anchors.top: parent.top - anchors.topMargin: search_button.anchors.topMargin * 0.5 - font.pixelSize * 0.5 - anchors.horizontalCenter: parent.horizontalCenter - - text: API.get().empty_string + (General.formatFiat("", API.get().balance_fiat_all, API.get().fiat)) - } - - // Search button - Image { - id: search_button - - source: General.image_path + "exchange-search.svg" - - width: 32; height: width - - anchors.top: parent.top - anchors.topMargin: parent.width * 0.5 - height * 0.5 - anchors.horizontalCenter: parent.horizontalCenter - - visible: false - } - ColorOverlay { - id: search_button_overlay - property bool hovered: false - - anchors.fill: search_button - source: search_button - color: search_button_overlay.hovered || input_coin_filter.visible ? Style.colorWhite1 : Style.colorWhite4 - - MouseArea { - anchors.fill: parent - hoverEnabled: true - onHoveredChanged: search_button_overlay.hovered = containsMouse - onClicked: { - input_coin_filter.text = "" - input_coin_filter.visible = !input_coin_filter.visible - if(input_coin_filter.visible) - input_coin_filter.focus = true - } - } - } - - // Search input - DefaultTextField { - id: input_coin_filter - - function reset() { - visible = false - text = "" - } - - anchors.top: search_button.bottom - anchors.horizontalCenter: parent.horizontalCenter - - placeholderText: API.get().empty_string + (qsTr("Search")) - selectByMouse: true - - visible: false - - width: parent.width * 0.8 - } - - // Add button - PlusButton { - id: add_coin_button - - width: 32 - - mouse_area.onClicked: enable_coin_modal.prepareAndOpen() - - anchors.bottom: parent.bottom - anchors.bottomMargin: parent.width * 0.5 - height * 0.5 - anchors.horizontalCenter: parent.horizontalCenter - } - - // Coins list - ListView { - ScrollBar.vertical: ScrollBar {} - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - implicitWidth: contentItem.childrenRect.width - implicitHeight: Math.min(contentItem.childrenRect.height, parent.height - coins_bar.width * 2) - clip: true - - model: General.filterCoins(API.get().enabled_coins, input_coin_filter.text) - - delegate: Rectangle { - property bool hovered: false - - color: API.get().current_coin_info.ticker === model.modelData.ticker ? Style.colorTheme2 : hovered ? Style.colorTheme4 : "transparent" - width: coins_bar.width - height: 50 - - // Click area - MouseArea { - anchors.fill: parent - hoverEnabled: true - onHoveredChanged: hovered = containsMouse - - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: { - if (mouse.button === Qt.RightButton) context_menu.popup() - else API.get().current_coin_info.ticker = model.modelData.ticker - - send_modal.reset() - } - onPressAndHold: { - if (mouse.source === Qt.MouseEventNotSynthesized) context_menu.popup() - } - } - - // Right click menu - Menu { - id: context_menu - Action { - text: API.get().empty_string + (qsTr("Disable %1", "TICKER").arg(model.modelData.ticker)) - onTriggered: API.get().disable_coins([model.modelData.ticker]) - enabled: General.canDisable(model.modelData.ticker) - } - } - - // Icon - Image { - id: icon - anchors.left: parent.left - anchors.leftMargin: 20 - - source: General.image_path + "coins/" + model.modelData.ticker.toLowerCase() + ".png" - fillMode: Image.PreserveAspectFit - width: Style.textSize2 - anchors.verticalCenter: parent.verticalCenter - } - - // Name - DefaultText { - anchors.left: icon.right - anchors.leftMargin: 5 - - text: API.get().empty_string + (model.modelData.ticker) - anchors.verticalCenter: parent.verticalCenter - } - } - } + // Right side, main + Main { + id: main } } diff --git a/atomic_qt_design/qml/main.qml b/atomic_qt_design/qml/main.qml index 048ddab5e1..94d0e1be8d 100644 --- a/atomic_qt_design/qml/main.qml +++ b/atomic_qt_design/qml/main.qml @@ -12,6 +12,9 @@ Window { minimumHeight: General.minimumHeight title: API.get().empty_string + (qsTr("AtomicDEX Pro")) flags: Qt.Window | Qt.WindowFullscreenButtonHint + + Component.onCompleted: showMaximized() + onVisibilityChanged: API.get().change_state(visibility) App { anchors.fill: parent diff --git a/ci_tools_atomic_dex/README.md b/ci_tools_atomic_dex/README.md index e1faa2e402..872d65d4af 100644 --- a/ci_tools_atomic_dex/README.md +++ b/ci_tools_atomic_dex/README.md @@ -68,6 +68,31 @@ git checkout curl-7_70_0 make install ``` +Installling libbitcoin: + +``` +git clone --depth 1 --branch version5 --single-branch "https://github.com/libbitcoin/secp256k1" +cd secp256k1 +./autogen.sh +./configure --disable-shared --disable-tests --enable-module-recovery +make -j3 +sudo make install +cd ../ +``` + +Installing libbitcoin-system: + +``` +git clone --depth 1 --branch version3 --single-branch https://github.com/KomodoPlatform/libbitcoin-system.git +cd libbitcoin-system +./autogen.sh +./configure --with-boost --disable-shared +make -j3 +sudo make install +sudo update_dyld_shared_cache +``` + + ### Install Linux dependencies In your terminal (shell,...) execute: @@ -108,6 +133,15 @@ export QT_ROOT=~/Qt/5.15.0 ## Build AtomicDEX Pro +Please clone with submodules initialization : `git clone --recurse-submodules --remote-submodules https://github.com/KomodoPlatform/atomicDEX-Pro.git` + +Install vcpkg from within the `ci_tools_atomic_dex` folder: + +``` +nimble build +./ci_tools_atomic_dex --install_vcpkg +``` + ### Windows In your x64 Visual Studio command prompt, from within the `ci_tools_atomic_dex` folder, type: diff --git a/ci_tools_atomic_dex/src/dependencies.nim b/ci_tools_atomic_dex/src/dependencies.nim index a16bd3b89e..c85235c321 100644 --- a/ci_tools_atomic_dex/src/dependencies.nim +++ b/ci_tools_atomic_dex/src/dependencies.nim @@ -10,6 +10,7 @@ let g_packages = [ (name: "folly", head: false), (name: "boost-multiprecision", head: false), (name: "boost-random", head: false), + (name: "boost-lockfree", head: false), (name: "doctest", head: false), (name: "fmt", head: false), (name: "curl", head: false), diff --git a/ci_tools_atomic_dex/vcpkg-repo b/ci_tools_atomic_dex/vcpkg-repo index a92b724ca8..1e4c0144f9 160000 --- a/ci_tools_atomic_dex/vcpkg-repo +++ b/ci_tools_atomic_dex/vcpkg-repo @@ -1 +1 @@ -Subproject commit a92b724ca802ed27cfe2b7078e8484589fbae23a +Subproject commit 1e4c0144f9a64359b537dde07f202e85bf97ad8d diff --git a/main.cpp b/main.cpp index a71610b4eb..9764ec4fdb 100644 --- a/main.cpp +++ b/main.cpp @@ -18,6 +18,8 @@ #ifdef __APPLE__ # include "atomic.dex.osx.manager.hpp" +//# include +//# include #endif inline constexpr size_t g_qsize_spdlog = 10240; @@ -25,6 +27,15 @@ inline constexpr size_t g_spdlog_thread_count = 4; inline constexpr size_t g_spdlog_max_file_size = 7777777; inline constexpr size_t g_spdlog_max_file_rotation = 3; + +void +signal_handler(int signal) +{ + spdlog::trace("sigabort received, cleaning mm2"); + atomic_dex::kill_executable("mm2"); + std::exit(signal); +} + #if defined(WINDOWS_RELEASE_MAIN) INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT) @@ -34,41 +45,13 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) #endif { + std::signal(SIGABRT, signal_handler); //! Project #if defined(_WIN32) || defined(WIN32) - using namespace std::string_literals; - auto install_db_tz_path = std::make_unique(ag::core::assets_real_path() / "tools" / "timezone" / "tzdata"); - std::cout << install_db_tz_path->string() << std::endl; - date::set_install(install_db_tz_path->string()); - //fs::path file_db_gz_path = fs::path(std::string(std::getenv("APPDATA"))) / "atomic_qt" / ("tzdata"s + "2020a"s + ".tar.gz"s); - //std::cout << file_db_gz_path.string() << std::endl; - /*if (not fs::exists(file_db_gz_path)) - { - - std::cout << "2020a" << std::endl; - bool res_tz = date::remote_download("2020a"); - std::cout << "Pass here" << std::endl; - assert(res_tz == true); - if (not fs::exists(install_db_tz_path / "version")) - { - //! We need to untar - boost::system::error_code ec; - fs::create_directory(install_db_tz_path, ec); - std::string cmd_line = "tar -xf "; - cmd_line += file_db_gz_path.string(); - cmd_line += " -C "; - cmd_line += install_db_tz_path.string(); - std::cout << cmd_line << std::endl; - system(cmd_line.c_str()); - fs::path xml_windows_tdata_path = - fs::path(std::string(std::getenv("APPDATA"))) / "atomic_qt" / ("tzdata" + date::remote_version() + "windowsZones.xml"); - fs::copy(xml_windows_tdata_path, install_db_tz_path / "windowsZones.xml"); - } - //atomic_dex::spawn([&file_db_gz_path]() { - // - //}); - - }*/ + using namespace std::string_literals; + auto install_db_tz_path = std::make_unique(ag::core::assets_real_path() / "tools" / "timezone" / "tzdata"); + std::cout << install_db_tz_path->string() << std::endl; + date::set_install(install_db_tz_path->string()); #endif #if defined(_WIN32) || defined(WIN32) || defined(__linux__) @@ -98,15 +81,18 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) spdlog::set_pattern("[%H:%M:%S %z] [%L] [thr %t] %v"); spdlog::info("Logger successfully initialized"); + // spdlog::info("asan report: {}", (fs::temp_directory_path() / "asan.log").string().c_str()); + //__sanitizer_set_report_path((fs::temp_directory_path() / "asan.log").string().c_str()); + //! App declaration atomic_dex::application atomic_app; //! QT QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - int ac = 0; - QApplication app(ac, nullptr); + int ac = 0; + std::shared_ptr app = std::make_shared(ac, nullptr); - atomic_app.set_qt_app(&app); + atomic_app.set_qt_app(app); //! QT QML QQmlApplicationEngine engine; @@ -122,7 +108,7 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) const QUrl url(QStringLiteral("qrc:/atomic_qt_design/qml/main.qml")); QObject::connect( - &engine, &QQmlApplicationEngine::objectCreated, &app, + &engine, &QQmlApplicationEngine::objectCreated, app.get(), [url](QObject* obj, const QUrl& objUrl) { if ((obj == nullptr) && url == objUrl) { @@ -141,10 +127,15 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) #endif atomic_app.launch(); - auto res = app.exec(); + auto res = app->exec(); #if defined(_WIN32) || defined(WIN32) || defined(__linux__) - auto wallet_exit_res = wally_cleanup(0); + auto wallet_exit_res = wally_cleanup(0); assert(wallet_exit_res == WALLY_OK); #endif + + + //__lsan_do_leak_check(); + //__lsan_disable(); + return res; } diff --git a/qml.qrc b/qml.qrc index 1e9bbe2c1c..c803f4760a 100644 --- a/qml.qrc +++ b/qml.qrc @@ -7,7 +7,13 @@ atomic_qt_design/assets/languages/atomic_qt_tr.qm atomic_qt_design/assets/languages/atomic_qt_fr.qm atomic_qt_design/assets/fonts/Rubik-Regular.ttf + atomic_qt_design/assets/fonts/Montserrat-SemiBold.ttf + atomic_qt_design/assets/fonts/Montserrat-Medium.ttf + atomic_qt_design/assets/fonts/Montserrat-Light.ttf + atomic_qt_design/assets/fonts/Montserrat-Thin.ttf atomic_qt_design/assets/fonts/Montserrat-Regular.ttf + atomic_qt_design/assets/images/arrow_up.svg + atomic_qt_design/assets/images/arrow_down.svg atomic_qt_design/assets/images/arrow-up.svg atomic_qt_design/assets/images/arrow-down.svg atomic_qt_design/assets/images/setup-logs.svg @@ -16,11 +22,14 @@ atomic_qt_design/assets/images/dashboard-eye.svg atomic_qt_design/assets/images/dashboard-eye-hide.svg atomic_qt_design/assets/images/dashboard-copy.svg + atomic_qt_design/assets/images/atomicdex-logo.svg + atomic_qt_design/assets/images/atomicdex-logo-dark.svg + atomic_qt_design/assets/images/atomicdex-logo-large.svg atomic_qt_design/assets/images/komodo-icon.png atomic_qt_design/assets/images/exchange-exchange.svg atomic_qt_design/assets/images/exchange-search.svg atomic_qt_design/assets/images/settings-seed.svg - atomic_qt_design/assets/images/menu-assets-portfolio.png + atomic_qt_design/assets/images/menu-assets-portfolio.svg atomic_qt_design/assets/images/menu-settings-white.svg atomic_qt_design/assets/images/menu-dapp-white.svg atomic_qt_design/assets/images/menu-news-white.svg @@ -28,19 +37,27 @@ atomic_qt_design/assets/images/menu-assets-white.svg atomic_qt_design/assets/images/exchange-trade-complete.svg atomic_qt_design/assets/images/dashboard-info.svg + atomic_qt_design/assets/images/trade_icon.svg atomic_qt_design/assets/images/circle-success.png atomic_qt_design/assets/images/circle-failed.png + atomic_qt_design/assets/images/shadowed_circle_green.svg + atomic_qt_design/assets/images/shadowed_circle_blue.svg atomic_qt_design/assets/images/coins/awc.png + atomic_qt_design/assets/images/coins/axe.png atomic_qt_design/assets/images/coins/bat.png atomic_qt_design/assets/images/coins/btc.png atomic_qt_design/assets/images/coins/bch.png atomic_qt_design/assets/images/coins/bet.png atomic_qt_design/assets/images/coins/bots.png + atomic_qt_design/assets/images/coins/busd.png + atomic_qt_design/assets/images/coins/ccl.png atomic_qt_design/assets/images/coins/crypto.png + atomic_qt_design/assets/images/coins/dai.png atomic_qt_design/assets/images/coins/dash.png atomic_qt_design/assets/images/coins/dgb.png atomic_qt_design/assets/images/coins/doge.png atomic_qt_design/assets/images/coins/eca.png + atomic_qt_design/assets/images/coins/ftc.png atomic_qt_design/assets/images/coins/jumblr.png atomic_qt_design/assets/images/coins/ltc.png atomic_qt_design/assets/images/coins/mcl.png @@ -48,14 +65,18 @@ atomic_qt_design/assets/images/coins/kmd.png atomic_qt_design/assets/images/coins/chips.png atomic_qt_design/assets/images/coins/dex.png + atomic_qt_design/assets/images/coins/emc2.png atomic_qt_design/assets/images/coins/iln.png atomic_qt_design/assets/images/coins/labs.png atomic_qt_design/assets/images/coins/rick.png atomic_qt_design/assets/images/coins/mgw.png atomic_qt_design/assets/images/coins/morty.png atomic_qt_design/assets/images/coins/nav.png + atomic_qt_design/assets/images/coins/oot.png atomic_qt_design/assets/images/coins/pangea.png + atomic_qt_design/assets/images/coins/pax.png atomic_qt_design/assets/images/coins/revs.png + atomic_qt_design/assets/images/coins/rfox.png atomic_qt_design/assets/images/coins/hush.png atomic_qt_design/assets/images/coins/qtum.png atomic_qt_design/assets/images/coins/rvn.png @@ -66,6 +87,7 @@ atomic_qt_design/assets/images/coins/tusd.png atomic_qt_design/assets/images/coins/xzc.png atomic_qt_design/assets/images/coins/zec.png + atomic_qt_design/assets/images/coins/zer.png atomic_qt_design/assets/images/lang/en.png atomic_qt_design/assets/images/lang/fr.png atomic_qt_design/assets/images/lang/tr.png @@ -87,6 +109,7 @@ atomic_qt_design/qml/Exchange/Trade/OrderForm.qml atomic_qt_design/qml/Exchange/Trade/OrderReceiveModal.qml atomic_qt_design/qml/Exchange/Trade/OrderbookModal.qml + atomic_qt_design/qml/Exchange/Trade/CandleStickChart.qml atomic_qt_design/qml/Exchange/Orders/Orders.qml atomic_qt_design/qml/Exchange/Orders/OrderList.qml atomic_qt_design/qml/Exchange/History/History.qml @@ -97,14 +120,19 @@ atomic_qt_design/qml/Exchange/OrderModal.qml atomic_qt_design/qml/Exchange/OrderContent.qml atomic_qt_design/qml/Sidebar/Sidebar.qml + atomic_qt_design/qml/Sidebar/SidebarCenter.qml + atomic_qt_design/qml/Sidebar/SidebarBottom.qml atomic_qt_design/qml/Sidebar/SidebarLine.qml + atomic_qt_design/qml/Components/Arrow.qml atomic_qt_design/qml/Components/EulaModal.qml atomic_qt_design/qml/Components/Toast.qml + atomic_qt_design/qml/Components/Circle.qml atomic_qt_design/qml/Components/ToastManager.qml atomic_qt_design/qml/Components/ColumnHeader.qml atomic_qt_design/qml/Components/RightClickMenu.qml atomic_qt_design/qml/Components/PlusButton.qml atomic_qt_design/qml/Components/AddressField.qml + atomic_qt_design/qml/Components/AddressFieldWithTitle.qml atomic_qt_design/qml/Components/AmountField.qml atomic_qt_design/qml/Components/AmountIntField.qml atomic_qt_design/qml/Components/CopyFieldButton.qml @@ -114,30 +142,50 @@ atomic_qt_design/qml/Components/WalletNameField.qml atomic_qt_design/qml/Components/DangerButton.qml atomic_qt_design/qml/Components/PrimaryButton.qml + atomic_qt_design/qml/Components/InnerBackground.qml + atomic_qt_design/qml/Components/FloatingBackground.qml + atomic_qt_design/qml/Components/DefaultImage.qml + atomic_qt_design/qml/Components/DefaultInnerShadow.qml atomic_qt_design/qml/Components/DefaultButton.qml + atomic_qt_design/qml/Components/DefaultBusyIndicator.qml atomic_qt_design/qml/Components/DefaultText.qml + atomic_qt_design/qml/Components/DefaultRectangle.qml + atomic_qt_design/qml/Components/DefaultScrollBar.qml atomic_qt_design/qml/Components/DefaultTextField.qml atomic_qt_design/qml/Components/DefaultTextArea.qml + atomic_qt_design/qml/Components/DefaultListView.qml + atomic_qt_design/qml/Components/DefaultFlickable.qml atomic_qt_design/qml/Components/DefaultComboBox.qml + atomic_qt_design/qml/Components/DefaultCheckBox.qml + atomic_qt_design/qml/Components/GradientRectangle.qml atomic_qt_design/qml/Components/LogModal.qml + atomic_qt_design/qml/Components/UpdateModal.qml + atomic_qt_design/qml/Components/UpdateNotificationLine.qml atomic_qt_design/qml/Components/DefaultModal.qml - atomic_qt_design/qml/Components/PaneWithTitle.qml + atomic_qt_design/qml/Components/SidebarPanel.qml atomic_qt_design/qml/Components/SetupPage.qml + atomic_qt_design/qml/Components/Separator.qml atomic_qt_design/qml/Components/HorizontalLine.qml atomic_qt_design/qml/Components/VerticalLine.qml + atomic_qt_design/qml/Components/VerticalLineBasic.qml atomic_qt_design/qml/Components/ComboBoxWithTitle.qml atomic_qt_design/qml/Components/TextWithTitle.qml atomic_qt_design/qml/Components/TextFieldWithTitle.qml atomic_qt_design/qml/Components/TextAreaWithTitle.qml atomic_qt_design/qml/Components/ModalHeader.qml + atomic_qt_design/qml/Components/CexInfoTrigger.qml atomic_qt_design/qml/Portfolio/Portfolio.qml atomic_qt_design/qml/Wallet/Wallet.qml atomic_qt_design/qml/Wallet/ClaimRewardsModal.qml atomic_qt_design/qml/Wallet/ReceiveModal.qml atomic_qt_design/qml/Wallet/SendModal.qml atomic_qt_design/qml/Wallet/SendResult.qml + atomic_qt_design/qml/Wallet/PriceGraph.qml atomic_qt_design/qml/Wallet/EnableCoinModal.qml atomic_qt_design/qml/Wallet/CoinList.qml + atomic_qt_design/qml/Wallet/Sidebar.qml + atomic_qt_design/qml/Wallet/Main.qml + atomic_qt_design/qml/Wallet/AddressBook.qml atomic_qt_design/qml/Wallet/Transactions.qml atomic_qt_design/qml/Wallet/TransactionDetailsModal.qml atomic_qt_design/qml/Wallet/AddressList.qml diff --git a/qtquickcontrols2.conf b/qtquickcontrols2.conf index 91fc5e68ea..35fa774243 100644 --- a/qtquickcontrols2.conf +++ b/qtquickcontrols2.conf @@ -1,10 +1,8 @@ [Controls] -Style=Material +Style=Universal -[Material] +[Universal] Theme=Dark -Variant=Dense Accent=#41EAD4 -Primary=#283547 Foreground=#FFFFFF Background=#1E2938 diff --git a/src/atomic.dex.app.cpp b/src/atomic.dex.app.cpp index 481c9ad6a2..fc9bd61f40 100644 --- a/src/atomic.dex.app.cpp +++ b/src/atomic.dex.app.cpp @@ -41,15 +41,20 @@ //! Project Headers #include "atomic.dex.app.hpp" #include "atomic.dex.mm2.hpp" +#include "atomic.dex.provider.cex.prices.hpp" #include "atomic.dex.provider.coinpaprika.hpp" #include "atomic.dex.qt.bindings.hpp" +#include "atomic.dex.qt.utilities.hpp" #include "atomic.dex.security.hpp" +#include "atomic.dex.update.service.hpp" #include "atomic.dex.utilities.hpp" #include "atomic.dex.version.hpp" #include "atomic.threadpool.hpp" namespace { + constexpr std::size_t g_timeout_q_timer_ms = 8; + #if defined(_WIN32) || defined(WIN32) bool acquire_context(HCRYPTPROV* ctx) @@ -90,7 +95,7 @@ namespace namespace atomic_dex { void - atomic_dex::application::change_state(int visibility) + atomic_dex::application::change_state([[maybe_unused]] int visibility) { #ifdef __APPLE__ qDebug() << visibility; @@ -102,13 +107,13 @@ namespace atomic_dex #endif } - QObjectList + QVariantList atomic_dex::application::get_enabled_coins() const noexcept { return m_enabled_coins; } - QObjectList + QVariantList atomic_dex::application::get_enableable_coins() const noexcept { return m_enableable_coins; @@ -132,6 +137,7 @@ namespace atomic_dex { std::vector coins_std; + this->m_portfolio->disable_coins(coins); coins_std.reserve(coins.size()); for (auto&& coin: coins) { coins_std.push_back(coin.toStdString()); } get_mm2().disable_multiple_coins(coins_std); @@ -140,57 +146,6 @@ namespace atomic_dex return false; } - bool - atomic_dex::application::create(const QString& password, const QString& seed, const QString& wallet_name) - { - std::error_code ec; - auto key = atomic_dex::derive_password(password.toStdString(), ec); - if (ec) - { - spdlog::warn("{}", ec.message()); - if (ec == dextop_error::derive_password_failed) - { - return false; - } - } - else - { - using namespace std::string_literals; - const fs::path seed_path = get_atomic_dex_config_folder() / (wallet_name.toStdString() + ".seed"s); - const fs::path wallet_object_path = get_atomic_dex_export_folder() / (wallet_name.toStdString() + ".wallet.json"s); - const std::string wallet_cfg_file = std::string(atomic_dex::get_raw_version()) + "-coins"s + "."s + wallet_name.toStdString() + ".json"s; - const fs::path wallet_cfg_path = get_atomic_dex_config_folder() / wallet_cfg_file; - - - if (not fs::exists(wallet_cfg_path)) - { - const auto cfg_path = ag::core::assets_real_path() / "config"; - std::string filename = std::string(atomic_dex::get_raw_version()) + "-coins.json"; - fs::copy(cfg_path / filename, wallet_cfg_path); - } - - // Encrypt seed - atomic_dex::encrypt(seed_path, seed.toStdString().data(), key.data()); - // sodium_memzero(&seed, seed.size()); - sodium_memzero(key.data(), key.size()); - - std::ofstream ofs((get_atomic_dex_config_folder() / "default.wallet"s).string().c_str()); - ofs << wallet_name.toStdString(); - - set_wallet_default_name(wallet_name); - - std::ofstream wallet_object(wallet_object_path.string()); - nlohmann::json wallet_object_json; - - wallet_object_json["name"] = wallet_name.toStdString(); - wallet_object << wallet_object_json.dump(4); - wallet_object.close(); - - return true; - } - return false; - } - bool atomic_dex::application::first_run() { @@ -201,9 +156,9 @@ namespace atomic_dex application::launch() { this->system_manager_.start(); - auto timer = new QTimer(this); + auto* timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &application::tick); - timer->start(8); + timer->start(g_timeout_q_timer_ms); } QString @@ -227,12 +182,12 @@ namespace atomic_dex bip39_mnemonic_validate(output_words, output); return output; #else - std::array data; + std::array data{}; boost::random_device device; device.generate(data.begin(), data.end()); - char* output; - words* output_words; - bip39_get_wordlist(NULL, &output_words); + char* output = nullptr; + words* output_words = nullptr; + bip39_get_wordlist(nullptr, &output_words); bip39_mnemonic_from_bytes(output_words, data.data(), data.size(), &output); bip39_mnemonic_validate(output_words, output); return output; @@ -246,7 +201,8 @@ namespace atomic_dex if (this->m_need_a_full_refresh_of_mm2) { auto& mm2_s = system_manager_.create_system(); - system_manager_.create_system(mm2_s); + system_manager_.create_system(mm2_s, m_config); + system_manager_.create_system(mm2_s); connect_signals(); this->m_need_a_full_refresh_of_mm2 = false; @@ -257,74 +213,14 @@ namespace atomic_dex { if (m_coin_info->get_ticker().isEmpty() && not m_enabled_coins.empty()) { - auto coin = mm2.get_enabled_coins().front(); - m_coin_info->set_ticker(QString::fromStdString(coin.ticker)); + // auto coin = mm2.get_enabled_coins().front(); + //! KMD Is our default coin + m_coin_info->set_ticker("KMD"); emit coinInfoChanged(); } - if (m_refresh_enabled_coin_event) - { - auto refresh_coin = [this](auto&& coins_container, auto&& coins_list) { - coins_list.clear(); - coins_list = to_qt_binding(std::move(coins_container), this); - }; - - { - auto coins = mm2.get_enabled_coins(); - refresh_coin(coins, m_enabled_coins); - emit enabledCoinsChanged(); - } - { - auto coins = mm2.get_enableable_coins(); - refresh_coin(coins, m_enableable_coins); - emit enableableCoinsChanged(); - } - { - if (not m_coin_info->get_ticker().isEmpty()) - { - refresh_transactions(mm2); - } - } - m_refresh_enabled_coin_event = false; - } - - if (m_refresh_current_ticker_infos) - { - refresh_transactions(mm2); - refresh_fiat_balance(mm2, paprika); - refresh_address(mm2); - const auto& info = get_mm2().get_coin_info(m_coin_info->get_ticker().toStdString()); - m_coin_info->set_name(QString::fromStdString(info.name)); - m_coin_info->set_claimable(info.is_claimable); - m_coin_info->set_type(QString::fromStdString(info.type)); - m_coin_info->set_paprika_id(QString::fromStdString(info.coinpaprika_id)); - m_coin_info->set_minimal_balance_for_asking_rewards(QString::fromStdString(info.minimal_claim_amount)); - m_coin_info->set_explorer_url(QString::fromStdString(info.explorer_url[0])); - m_refresh_current_ticker_infos = false; - } - - if (m_refresh_orders_needed) - { - emit myOrdersUpdated(); - m_refresh_orders_needed = false; - } - - if (m_refresh_transaction_only) - { - spdlog::info("refreshing transactions"); - refresh_transactions(mm2); - m_refresh_transaction_only = false; - } - std::error_code ec; - auto fiat_balance_std = paprika.get_price_in_fiat_all(m_current_fiat.toStdString(), ec); - - if (!ec) - { - this->set_current_balance_fiat_all(QString::fromStdString(fiat_balance_std)); - } - - auto second_fiat_balance_std = paprika.get_price_in_fiat_all(m_second_current_fiat.toStdString(), ec); + auto fiat_balance_std = paprika.get_price_in_fiat_all(m_config.current_currency, ec); if (!ec) { @@ -337,6 +233,72 @@ namespace atomic_dex refresh_address(mm2); } } + + if (not this->m_actions_queue.empty() && not this->m_about_to_exit_app) + { + action last_action; + this->m_actions_queue.pop(last_action); + switch (last_action) + { + case action::refresh_enabled_coin: + if (mm2.is_mm2_running()) + { + this->process_refresh_enabled_coin_action(); + } + break; + case action::refresh_current_ticker: + if (mm2.is_mm2_running()) + { + this->process_refresh_current_ticker_infos(); + } + break; + case action::refresh_ohlc: + if (mm2.is_mm2_running()) + { + // emit OHLCDataUpdated(); + if (this->m_candlestick_need_a_reset) + { + this->m_candlestick_chart_ohlc->init_data(); + } + else + { + this->m_candlestick_chart_ohlc->update_data(); + } + } + break; + case action::refresh_transactions: + if (mm2.is_mm2_running()) + { + refresh_transactions(mm2); + } + break; + case action::refresh_portfolio_ticker_balance: + if (mm2.is_mm2_running()) + { + this->m_portfolio->update_balance_values(*this->m_ticker_balance_to_refresh); + } + break; + case action::post_process_orders_finished: + if (mm2.is_mm2_running()) + { + this->m_orders->refresh_or_insert_orders(); + } + break; + case action::post_process_swaps_finished: + if (mm2.is_mm2_running()) + { + this->m_orders->refresh_or_insert_swaps(); + } + break; + case action::refresh_update_status: + spdlog::trace("refreshing update status in GUI"); + const auto& update_service_sys = this->system_manager_.get_system(); + QJsonDocument doc = QJsonDocument::fromJson(QString::fromStdString(update_service_sys.get_update_status().dump()).toUtf8()); + this->m_update_status = doc.toVariant(); + emit updateStatusChanged(); + break; + } + } } void @@ -346,10 +308,12 @@ namespace atomic_dex QString target_balance = QString::fromStdString(mm2.my_balance(m_coin_info->get_ticker().toStdString(), ec)); m_coin_info->set_balance(target_balance); - if (m_current_fiat == "USD" || m_current_fiat == "EUR") + if (std::any_of(begin(m_config.possible_currencies), end(m_config.possible_currencies), [this](const std::string& cur_fiat) { + return cur_fiat == m_config.current_currency; + })) { ec = std::error_code(); - auto amount = QString::fromStdString(paprika.get_price_in_fiat(m_current_fiat.toStdString(), m_coin_info->get_ticker().toStdString(), ec)); + auto amount = QString::fromStdString(paprika.get_price_in_fiat(m_config.current_currency, m_coin_info->get_ticker().toStdString(), ec)); if (!ec) { m_coin_info->set_fiat_amount(amount); @@ -360,19 +324,20 @@ namespace atomic_dex void application::refresh_transactions(const mm2& mm2) { - spdlog::debug("{} l{}", __FUNCTION__, __LINE__); + const auto ticker = m_coin_info->get_ticker().toStdString(); + spdlog::debug("{} l{} for coin {}", __FUNCTION__, __LINE__, ticker); std::error_code ec; - auto txs = mm2.get_tx_history(m_coin_info->get_ticker().toStdString(), ec); + auto txs = mm2.get_tx_history(ticker, ec); if (!ec) { - m_coin_info->set_transactions(to_qt_binding(std::move(txs), this, get_paprika(), m_current_fiat, m_coin_info->get_ticker().toStdString())); + m_coin_info->set_transactions(to_qt_binding(std::move(txs), get_paprika(), m_config.current_currency, ticker)); } - auto tx_state = mm2.get_tx_state(m_coin_info->get_ticker().toStdString(), ec); + auto tx_state = mm2.get_tx_state(ticker, ec); if (!ec) { m_coin_info->set_tx_state(QString::fromStdString(tx_state.state)); - if (mm2.get_coin_info(m_coin_info->get_ticker().toStdString()).is_erc_20) + if (mm2.get_coin_info(ticker).is_erc_20) { m_coin_info->set_blocks_left(tx_state.blocks_left); } @@ -434,11 +399,20 @@ namespace atomic_dex emit on_second_fiat_balance_all_changed(); } - application::application(QObject* pParent) noexcept : QObject(pParent), m_coin_info(new current_coin_info(dispatcher_, this)) + application::application(QObject* pParent) noexcept : + QObject(pParent), + m_update_status(QJsonObject{ + {"update_needed", false}, {"changelog", ""}, {"current_version", ""}, {"download_url", ""}, {"new_version", ""}, {"rpc_code", 0}, {"status", ""}}), + m_coin_info(new current_coin_info(dispatcher_, this)), m_addressbook(new addressbook_model(this->m_wallet_manager, this)), + m_portfolio(new portfolio_model(this->system_manager_, this->m_config, this)), m_orders(new orders_model(this->system_manager_, this)), + m_candlestick_chart_ohlc(new candlestick_charts_model(this->system_manager_, this)) { + get_dispatcher().sink().connect<&application::on_refresh_update_status_event>(*this); //! MM2 system need to be created before the GUI and give the instance to the gui auto& mm2_system = system_manager_.create_system(); - system_manager_.create_system(mm2_system); + system_manager_.create_system(mm2_system, m_config); + system_manager_.create_system(mm2_system); + system_manager_.create_system(); connect_signals(); if (is_there_a_default_wallet()) @@ -451,10 +425,9 @@ namespace atomic_dex atomic_dex::application::cancel_order(const QString& order_id) { auto& mm2 = get_mm2(); - atomic_dex::spawn([&mm2, order_id, this]() { + atomic_dex::spawn([&mm2, order_id]() { ::mm2::api::rpc_cancel_order({order_id.toStdString()}); - mm2.fetch_infos_thread(); - this->get_dispatcher().trigger(); + mm2.process_orders(); }); } @@ -462,11 +435,10 @@ namespace atomic_dex atomic_dex::application::cancel_all_orders() { auto& mm2 = get_mm2(); - atomic_dex::spawn([&mm2, this]() { + atomic_dex::spawn([&mm2]() { ::mm2::api::cancel_all_orders_request req; ::mm2::api::rpc_cancel_all_orders(std::move(req)); mm2.process_orders(); - this->get_dispatcher().trigger(); }); } @@ -474,54 +446,86 @@ namespace atomic_dex application::cancel_all_orders_by_ticker(const QString& ticker) { auto& mm2 = get_mm2(); - atomic_dex::spawn([&mm2, &ticker, this]() { + atomic_dex::spawn([&mm2, &ticker]() { ::mm2::api::cancel_data cd; cd.ticker = ticker.toStdString(); ::mm2::api::cancel_all_orders_request req{{"Coin", cd}}; ::mm2::api::rpc_cancel_all_orders(std::move(req)); mm2.process_orders(); - this->get_dispatcher().trigger(); }); } void - atomic_dex::application::on_enabled_coins_event(const enabled_coins_event&) noexcept + atomic_dex::application::on_enabled_coins_event([[maybe_unused]] const enabled_coins_event& evt) noexcept { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); - m_refresh_enabled_coin_event = true; + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_enabled_coin); + } } - QString - application::get_current_fiat() const noexcept + void + application::on_enabled_default_coins_event([[maybe_unused]] const enabled_default_coins_event& evt) noexcept { - return this->m_current_fiat; + spdlog::debug("{} l{}", __FUNCTION__, __LINE__); + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_enabled_coin); + } + } + + void + application::on_coin_fully_initialized_event(const coin_fully_initialized& evt) noexcept + { + //! This event is called when a call is enabled and cex provider finished fetch datas + spdlog::debug("{} l{}", __FUNCTION__, __LINE__); + this->m_portfolio->initialize_portfolio(evt.ticker); } QString - application::get_second_current_fiat() const noexcept + application::get_current_currency() const noexcept { - return this->m_second_current_fiat; + return QString::fromStdString(this->m_config.current_currency); } void - application::set_current_fiat(QString current_fiat) noexcept + application::set_current_currency(const QString& current_currency) noexcept { - this->m_current_fiat = std::move(current_fiat); - emit on_fiat_changed(); + if (current_currency.toStdString() != m_config.current_currency) + { + spdlog::info("change currency {} to {}", m_config.current_currency, current_currency.toStdString()); + atomic_dex::change_currency(m_config, current_currency.toStdString()); + this->m_portfolio->update_currency_values(); + emit on_currency_changed(); + } + } + + QString + application::get_current_fiat() const noexcept + { + return QString::fromStdString(this->m_config.current_fiat); } void - application::set_second_current_fiat(QString current_fiat) noexcept + application::set_current_fiat(const QString& current_fiat) noexcept { - this->m_second_current_fiat = std::move(current_fiat); - emit on_second_fiat_changed(); + if (current_fiat.toStdString() != m_config.current_fiat) + { + spdlog::info("change fiat {} to {}", m_config.current_fiat, current_fiat.toStdString()); + atomic_dex::change_fiat(m_config, current_fiat.toStdString()); + emit on_fiat_changed(); + } } void - application::on_change_ticker_event(const change_ticker_event&) noexcept + application::on_change_ticker_event([[maybe_unused]] const change_ticker_event& evt) noexcept { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); - m_refresh_current_ticker_infos = true; + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_current_ticker); + } } void @@ -536,7 +540,7 @@ namespace atomic_dex application::prepare_send(const QString& address, const QString& amount, bool max) { atomic_dex::t_withdraw_request req{ - .to = address.toStdString(), .coin = m_coin_info->get_ticker().toStdString(), .max = max, .amount = amount.toStdString()}; + .coin = m_coin_info->get_ticker().toStdString(), .to = address.toStdString(), .amount = amount.toStdString(), .max = max}; if (req.max) { req.amount = "0"; @@ -552,8 +556,8 @@ namespace atomic_dex const QString& address, const QString& amount, bool is_erc_20, const QString& fees_amount, const QString& gas_price, const QString& gas, bool max) { atomic_dex::t_withdraw_request req{ - .to = address.toStdString(), .coin = m_coin_info->get_ticker().toStdString(), .max = max, .amount = amount.toStdString()}; - if (req.max == true) + .coin = m_coin_info->get_ticker().toStdString(), .to = address.toStdString(), .amount = amount.toStdString(), .max = max}; + if (req.max) { req.amount = "0"; } @@ -579,6 +583,7 @@ namespace atomic_dex { std::error_code ec; auto answer = get_mm2().claim_rewards(ticker.toStdString(), ec); + if (not answer.error.has_value()) { if (ec) @@ -586,8 +591,10 @@ namespace atomic_dex answer.error = ec.message(); } } - auto coin = get_mm2().get_coin_info(m_coin_info->get_ticker().toStdString()); - auto obj = to_qt_binding(std::move(answer), this, QString::fromStdString(coin.explorer_url[0])); + + const auto coin = get_mm2().get_coin_info(m_coin_info->get_ticker().toStdString()); + QObject* obj = to_qt_binding(std::move(answer), this, QString::fromStdString(coin.explorer_url[0])); + return obj; } @@ -596,8 +603,11 @@ namespace atomic_dex { atomic_dex::t_broadcast_request req{.tx_hex = tx_hex.toStdString(), .coin = m_coin_info->get_ticker().toStdString()}; std::error_code ec; - auto answer = mm2::broadcast(std::move(req), ec); - m_refresh_current_ticker_infos = true; + auto answer = get_mm2().broadcast(std::move(req), ec); + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_current_ticker); + } refresh_infos(); return QString::fromStdString(answer.tx_hash); } @@ -608,16 +618,22 @@ namespace atomic_dex atomic_dex::t_broadcast_request req{.tx_hex = tx_hex.toStdString(), .coin = m_coin_info->get_ticker().toStdString()}; std::error_code ec; auto answer = get_mm2().send_rewards(std::move(req), ec); - m_refresh_current_ticker_infos = true; + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_current_ticker); + } refresh_infos(); return QString::fromStdString(answer.tx_hash); } void - application::on_tx_fetch_finished_event(const tx_fetch_finished&) noexcept + application::on_tx_fetch_finished_event([[maybe_unused]] const tx_fetch_finished& evt) noexcept { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); - m_refresh_transaction_only = true; + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_transactions); + } } bool @@ -662,10 +678,7 @@ namespace atomic_dex { return QString::fromStdString(answer.error.value()); } - else - { - return ""; - } + return ""; } bool @@ -682,10 +695,13 @@ namespace atomic_dex } void - application::on_coin_disabled_event(const coin_disabled&) noexcept + application::on_coin_disabled_event([[maybe_unused]] const coin_disabled& evt) noexcept { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); - m_refresh_enabled_coin_event = true; + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_enabled_coin); + } } QString @@ -722,14 +738,14 @@ namespace atomic_dex } void - application::on_mm2_initialized_event(const mm2_initialized&) noexcept + application::on_mm2_initialized_event([[maybe_unused]] const mm2_initialized& evt) noexcept { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); this->set_status("enabling_coins"); } void - application::on_mm2_started_event(const mm2_started&) noexcept + application::on_mm2_started_event([[maybe_unused]] const mm2_started& evt) noexcept { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); this->set_status("complete"); @@ -738,6 +754,9 @@ namespace atomic_dex void application::set_current_orderbook(const QString& base, const QString& rel) { + auto& provider = this->system_manager_.get_system(); + auto [normal, quoted] = provider.is_pair_supported(base.toStdString(), rel.toStdString()); + this->m_candlestick_chart_ohlc->set_is_pair_supported(normal || quoted); this->dispatcher_.trigger(base.toStdString(), rel.toStdString()); } @@ -770,52 +789,41 @@ namespace atomic_dex return out; } - QVariantMap - application::get_my_orders() - { - auto& mm2 = get_mm2(); - QVariantMap output; - auto coins = mm2.get_enabled_coins(); - for (auto&& coin: coins) - { - std::error_code ec; - output.insert(QString::fromStdString(coin.ticker), QVariant::fromValue(to_qt_binding(mm2.get_orders(coin.ticker, ec), this))); - } - return output; - } - void - application::on_refresh_order_event(const refresh_order_needed&) noexcept + application::on_refresh_update_status_event([[maybe_unused]] const refresh_update_status& evt) noexcept { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); - this->m_refresh_orders_needed = true; + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_update_status); + } } void application::refresh_infos() { auto& mm2 = get_mm2(); - spawn([&mm2, &dispatcher = dispatcher_]() { - mm2.fetch_infos_thread(); - dispatcher.trigger(); - }); + spawn([&mm2]() { mm2.fetch_infos_thread(); }); } void application::refresh_orders_and_swaps() { auto& mm2 = get_mm2(); - spawn([&mm2, &dispatcher = dispatcher_]() { + spawn([&mm2]() { mm2.process_swaps(); mm2.process_orders(); - dispatcher.trigger(); }); } - QObject* + QVariant application::get_coin_info(const QString& ticker) { - return to_qt_binding(get_mm2().get_coin_info(ticker.toStdString()), this); + QVariant out; + nlohmann::json j = to_qt_binding(get_mm2().get_coin_info(ticker.toStdString())); + QJsonDocument q_json = QJsonDocument::fromJson(QString::fromStdString(j.dump()).toUtf8()); + out = q_json.toVariant(); + return out; } bool @@ -823,16 +831,50 @@ namespace atomic_dex { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); + //! Clear pending events + while (not this->m_actions_queue.empty()) + { + [[maybe_unused]] action act; + this->m_actions_queue.pop(act); + } + + //! Clear models + if (auto count = this->m_addressbook->rowCount(); count > 0) + { + this->m_addressbook->removeRows(0, count); + } + + if (auto count = this->m_portfolio->rowCount(QModelIndex()); count > 0) + { + this->m_portfolio->removeRows(0, count, QModelIndex()); + } + + if (auto count = this->m_orders->rowCount(QModelIndex()); count > 0) + { + this->m_orders->removeRows(0, count, QModelIndex()); + } + this->m_orders->clear_registry(); + this->m_candlestick_chart_ohlc->clear_data(); + + //! Mark systems system_manager_.mark_system(); system_manager_.mark_system(); + system_manager_.mark_system(); + //! Disconnect signals + get_dispatcher().sink().disconnect<&application::on_ticker_balance_updated_event>(*this); get_dispatcher().sink().disconnect<&application::on_change_ticker_event>(*this); get_dispatcher().sink().disconnect<&application::on_enabled_coins_event>(*this); + get_dispatcher().sink().disconnect<&application::on_enabled_default_coins_event>(*this); + get_dispatcher().sink().disconnect<&application::on_coin_fully_initialized_event>(*this); get_dispatcher().sink().disconnect<&application::on_tx_fetch_finished_event>(*this); get_dispatcher().sink().disconnect<&application::on_coin_disabled_event>(*this); get_dispatcher().sink().disconnect<&application::on_mm2_initialized_event>(*this); get_dispatcher().sink().disconnect<&application::on_mm2_started_event>(*this); - get_dispatcher().sink().disconnect<&application::on_refresh_order_event>(*this); + get_dispatcher().sink().disconnect<&application::on_refresh_ohlc_event>(*this); + get_dispatcher().sink().disconnect<&application::on_process_orders_finished_event>(*this); + get_dispatcher().sink().disconnect<&application::on_process_swaps_finished_event>(*this); + get_dispatcher().sink().disconnect<&application::on_start_fetching_new_ohlc_data_event>(*this); this->m_need_a_full_refresh_of_mm2 = true; @@ -843,47 +885,23 @@ namespace atomic_dex application::connect_signals() { spdlog::debug("{} l{}", __FUNCTION__, __LINE__); + get_dispatcher().sink().connect<&application::on_ticker_balance_updated_event>(*this); get_dispatcher().sink().connect<&application::on_change_ticker_event>(*this); get_dispatcher().sink().connect<&application::on_enabled_coins_event>(*this); + get_dispatcher().sink().connect<&application::on_enabled_default_coins_event>(*this); + get_dispatcher().sink().connect<&application::on_coin_fully_initialized_event>(*this); get_dispatcher().sink().connect<&application::on_tx_fetch_finished_event>(*this); get_dispatcher().sink().connect<&application::on_coin_disabled_event>(*this); get_dispatcher().sink().connect<&application::on_mm2_initialized_event>(*this); get_dispatcher().sink().connect<&application::on_mm2_started_event>(*this); - get_dispatcher().sink().connect<&application::on_refresh_order_event>(*this); - } - - QString - application::get_wallet_default_name() const noexcept - { - return m_current_default_wallet; - } - - void - application::set_wallet_default_name(QString wallet_name) noexcept - { - using namespace std::string_literals; - if (wallet_name == "") - { - fs::remove(get_atomic_dex_config_folder() / "default.wallet"); - return; - } - if (not fs::exists(get_atomic_dex_config_folder() / "default.wallet"s)) - { - std::ofstream ofs((get_atomic_dex_config_folder() / "default.wallet"s).string()); - ofs << wallet_name.toStdString(); - } - else - { - std::ofstream ofs((get_atomic_dex_config_folder() / "default.wallet"s).string(), std::ios_base::out | std::ios_base::trunc); - ofs << wallet_name.toStdString(); - } - - this->m_current_default_wallet = std::move(wallet_name); - emit on_wallet_default_name_changed(); + get_dispatcher().sink().connect<&application::on_refresh_ohlc_event>(*this); + get_dispatcher().sink().connect<&application::on_process_orders_finished_event>(*this); + get_dispatcher().sink().connect<&application::on_process_swaps_finished_event>(*this); + get_dispatcher().sink().connect<&application::on_start_fetching_new_ohlc_data_event>(*this); } QString - atomic_dex::application::get_regex_password_policy() const noexcept + atomic_dex::application::get_regex_password_policy() noexcept { return QString(::atomic_dex::get_regex_password_policy()); } @@ -894,10 +912,8 @@ namespace atomic_dex spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); QVariantMap out; - - spdlog::info("ticker: {}, receive ticker: {}, amount: {}", ticker.toStdString(), receive_ticker.toStdString(), amount.toStdString()); - auto trade_fee_f = get_mm2().get_trade_fee(ticker.toStdString(), amount.toStdString(), false); - auto answer = get_mm2().get_trade_fixed_fee(ticker.toStdString()); + t_float_50 trade_fee_f = get_mm2().get_trade_fee(ticker.toStdString(), amount.toStdString(), false); + auto answer = get_mm2().get_trade_fixed_fee(ticker.toStdString()); if (!answer.amount.empty()) { @@ -909,21 +925,20 @@ namespace atomic_dex get_mm2().apply_erc_fees(receive_ticker.toStdString(), erc_fees); } - auto tx_fee_value = QString::fromStdString(get_formated_float(tx_fee_f)); - auto final_balance_f = t_float_50(amount.toStdString()) - (trade_fee_f + tx_fee_f); - std::string final_balance = amount.toStdString(); - // spdlog::info("final_balance_f: {}", get_formated_float(final_balance_f)); + auto tx_fee_value = QString::fromStdString(get_formated_float(tx_fee_f)); + + const std::string amount_std = t_float_50(amount.toStdString()) < minimal_trade_amount() ? minimal_trade_amount_str() : amount.toStdString(); + t_float_50 final_balance_f = t_float_50(amount_std) - (trade_fee_f + tx_fee_f); + std::string final_balance = amount.toStdString(); if (final_balance_f.convert_to() > 0.0) { final_balance = get_formated_float(final_balance_f); out.insert("not_enough_balance_to_pay_the_fees", false); - // spdlog::info("final_balance is: {}", final_balance); } else { - spdlog::info("final_balance_f < 0"); out.insert("not_enough_balance_to_pay_the_fees", true); - auto amount_needed = t_float_50(0.00777) - final_balance_f; + t_float_50 amount_needed = minimal_trade_amount() - final_balance_f; out.insert("amount_needed", QString::fromStdString(get_formated_float(amount_needed))); } auto final_balance_qt = QString::fromStdString(final_balance); @@ -938,32 +953,6 @@ namespace atomic_dex out.insert("is_ticker_of_fees_eth", get_mm2().get_coin_info(ticker.toStdString()).is_erc_20); out.insert("input_final_value", final_balance_qt); } - qDebug() << out; - return out; - } - - QVariantList - application::get_portfolio_informations() - { - QVariantList out; - nlohmann::json j = nlohmann::json::array(); - - auto coins = get_mm2().get_enabled_coins(); - for (auto&& coin: coins) - { - std::error_code ec; - nlohmann::json cur_obj{ - {"ticker", coin.ticker}, - {"name", coin.name}, - {"price", get_paprika().get_rate_conversion(m_current_fiat.toStdString(), coin.ticker, ec, true)}, - {"balance", get_mm2().my_balance(coin.ticker, ec)}, - {"balance_fiat", get_paprika().get_price_in_fiat(m_current_fiat.toStdString(), coin.ticker, ec)}, - {"rates", get_paprika().get_ticker_infos(coin.ticker).answer}, - {"historical", get_paprika().get_ticker_historical(coin.ticker).answer}}; - j.push_back(cur_obj); - } - QJsonDocument q_json = QJsonDocument::fromJson(QString::fromStdString(j.dump()).toUtf8()); - out = q_json.array().toVariantList(); return out; } @@ -981,16 +970,16 @@ namespace atomic_dex { change_lang(m_config, current_lang.toStdString()); } - auto get_locale = [](const QString current_lang) { + auto get_locale = [](const QString& current_lang) { if (current_lang == "tr") { return QLocale::Language::Turkish; } - else if (current_lang == "en") + if (current_lang == "en") { return QLocale::Language::English; } - else if (current_lang == "fr") + if (current_lang == "fr") { return QLocale::Language::French; } @@ -1000,7 +989,7 @@ namespace atomic_dex qDebug() << "locale before: " << QLocale().name(); QLocale::setDefault(get_locale(current_lang)); qDebug() << "locale after: " << QLocale().name(); - auto res = this->m_translator.load("atomic_qt_" + current_lang, QLatin1String(":/atomic_qt_design/assets/languages")); + [[maybe_unused]] auto res = this->m_translator.load("atomic_qt_" + current_lang, QLatin1String(":/atomic_qt_design/assets/languages")); assert(res); this->m_app->installTranslator(&m_translator); emit on_lang_changed(); @@ -1008,9 +997,10 @@ namespace atomic_dex } void - application::set_qt_app(QApplication* app) noexcept + application::set_qt_app(std::shared_ptr app) noexcept { this->m_app = app; + connect(m_app.get(), SIGNAL(aboutToQuit()), this, SLOT(exit_handler())); set_current_lang(QString::fromStdString(m_config.current_lang)); } @@ -1023,10 +1013,29 @@ namespace atomic_dex return out; } - QString + QStringList + application::get_available_fiats() const + { + QStringList out; + out.reserve(m_config.available_fiat.size()); + for (auto&& cur_fiat: m_config.available_fiat) { out.push_back(QString::fromStdString(cur_fiat)); } + return out; + } + + QStringList + application::get_available_currencies() const + { + QStringList out; + out.reserve(m_config.possible_currencies.size()); + for (auto&& cur_currency: m_config.possible_currencies) { out.push_back(QString::fromStdString(cur_currency)); } + return out; + } + + const QString& application::get_empty_string() { - return ""; + static const QString empty_string = ""; + return empty_string; } QString @@ -1055,7 +1064,7 @@ namespace atomic_dex bool - application::mnemonic_validate(QString entropy) + application::mnemonic_validate(const QString& entropy) { #ifdef __APPLE__ std::vector mnemonic; @@ -1078,33 +1087,6 @@ namespace atomic_dex #endif } - QVariantMap - application::get_recent_swaps() - { - QVariantMap out; - auto swaps = get_mm2().get_swaps(); - - for (auto& swap: swaps.swaps) - { - nlohmann::json j2 = { - {"maker_coin", swap.maker_coin}, - {"taker_coin", swap.taker_coin}, - {"total_time_in_seconds", swap.total_time_in_seconds}, - {"is_recoverable", swap.funds_recoverable}, - {"maker_amount", swap.maker_amount}, - {"taker_amount", swap.taker_amount}, - {"error_events", swap.error_events}, - {"success_events", swap.success_events}, - {"type", swap.type}, - {"events", swap.events}, - {"my_info", swap.my_info}}; - - auto out_swap = QJsonDocument::fromJson(QString::fromStdString(j2.dump()).toUtf8()); - out.insert(QString::fromStdString(swap.uuid), out_swap.toVariant()); - } - return out; - } - bool application::export_swaps_json() noexcept { @@ -1148,7 +1130,7 @@ namespace atomic_dex } QString - application::recover_fund(QString uuid) const + application::recover_fund(const QString& uuid) { QString result; @@ -1176,44 +1158,63 @@ namespace atomic_dex //! Constructor / Destructor namespace atomic_dex { - application::~application() noexcept { export_swaps_json(); } + application::~application() noexcept + { + if (this->m_addressbook->rowCount() > 0) + { + this->m_addressbook->removeRows(0, this->m_addressbook->rowCount()); + } + export_swaps_json(); + } } // namespace atomic_dex //! Misc QML Utilities namespace atomic_dex { + QVariant + application::get_update_status() const noexcept + { + return m_update_status; + } + + QVariantList + application::get_all_coins() const noexcept + { + return to_qt_binding(get_mm2().get_all_coins()); + } + QString - application::get_paprika_id_from_ticker(QString ticker) const + application::get_paprika_id_from_ticker(const QString& ticker) const { return QString::fromStdString(get_mm2().get_coin_info(ticker.toStdString()).coinpaprika_id); } QString - application::get_version() const noexcept + application::get_version() noexcept { return QString::fromStdString(atomic_dex::get_version()); } QString - application::get_log_folder() const + application::get_log_folder() { return QString::fromStdString(get_atomic_dex_logs_folder().string()); } QString - application::get_mm2_version() const + application::get_mm2_version() { return QString::fromStdString(::mm2::api::rpc_version()); } QString - application::get_export_folder() const + application::get_export_folder() { - return QString::fromStdString(get_atomic_dex_export_folder().string().c_str()); + return QString::fromStdString(get_atomic_dex_export_folder().string()); } QString - application::to_eth_checksum_qt(QString eth_lowercase_address) const + application::to_eth_checksum_qt(const QString& eth_lowercase_address) { auto str = eth_lowercase_address.toStdString(); to_eth_checksum(str); @@ -1221,13 +1222,174 @@ namespace atomic_dex } } // namespace atomic_dex +//! Trading functions +namespace atomic_dex +{ + QString + application::get_cex_rates(const QString& base, const QString& rel) + { + std::error_code ec; + return QString::fromStdString(get_paprika().get_cex_rates(base.toStdString(), rel.toStdString(), ec)); + } + + QString + application::get_fiat_from_amount(const QString& ticker, const QString& amount) + { + std::error_code ec; + return QString::fromStdString(get_paprika().get_price_as_currency_from_amount(m_config.current_fiat, ticker.toStdString(), amount.toStdString(), ec)); + } +} // namespace atomic_dex + +//! OHLC Relative functions +namespace atomic_dex +{ + candlestick_charts_model* + application::get_candlestick_charts() const noexcept + { + return m_candlestick_chart_ohlc; + } + + QVariantList + application::get_ohlc_data(const QString& range) + { + QVariantList out; + auto& provider = this->system_manager_.get_system(); + auto json = provider.get_ohlc_data(range.toStdString()); + + QJsonDocument q_json = QJsonDocument::fromJson(QString::fromStdString(json.dump()).toUtf8()); + out = q_json.array().toVariantList(); + return out; + } + + QVariantMap + application::find_closest_ohlc_data(int range, int timestamp) + { + QVariantMap out; + auto& provider = this->system_manager_.get_system(); + auto json = provider.get_ohlc_data(std::to_string(range)); + + auto it = std::lower_bound(rbegin(json), rend(json), timestamp, [](const nlohmann::json& current_json, int timestamp) { + int res = current_json.at("timestamp").get(); + return timestamp < res; + }); + + if (it != json.rend()) + { + QJsonDocument q_json = QJsonDocument::fromJson(QString::fromStdString(it->dump()).toUtf8()); + out = q_json.object().toVariantMap(); + } + return out; + } + + bool + application::is_supported_ohlc_data_ticker_pair(const QString& base, const QString& rel) + { + auto& provider = this->system_manager_.get_system(); + auto [normal, quoted] = provider.is_pair_supported(base.toStdString(), rel.toStdString()); + return normal || quoted; + } + + void + application::on_refresh_ohlc_event([[maybe_unused]] const refresh_ohlc_needed& evt) noexcept + { + spdlog::debug("{} l{}", __FUNCTION__, __LINE__); + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_ohlc); + this->m_candlestick_need_a_reset = evt.is_a_reset; + } + } + + void + application::on_ticker_balance_updated_event(const ticker_balance_updated& evt) noexcept + { + spdlog::trace("{} l{}", __FUNCTION__, __LINE__); + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::refresh_portfolio_ticker_balance); + } + *this->m_ticker_balance_to_refresh = evt.ticker; + } +} // namespace atomic_dex + +//! Addressbook +namespace atomic_dex +{ + addressbook_model* + application::get_addressbook() const noexcept + { + return m_addressbook; + } +} // namespace atomic_dex + +//! Orders +namespace atomic_dex +{ + void + application::on_process_swaps_finished_event([[maybe_unused]] const process_swaps_finished& evt) noexcept + { + spdlog::trace("{} l{}", __FUNCTION__, __LINE__); + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::post_process_swaps_finished); + } + } + + void + application::on_process_orders_finished_event([[maybe_unused]] const process_orders_finished& evt) noexcept + { + spdlog::trace("{} l{}", __FUNCTION__, __LINE__); + if (not m_about_to_exit_app) + { + this->m_actions_queue.push(action::post_process_orders_finished); + } + } + + orders_model* + application::get_orders() const noexcept + { + return m_orders; + } +} // namespace atomic_dex + +//! Portfolio +namespace atomic_dex +{ + portfolio_model* + application::get_portfolio() const noexcept + { + return m_portfolio; + } +} // namespace atomic_dex + //! Wallet manager QML API namespace atomic_dex { + QString + application::get_wallet_default_name() const noexcept + { + return m_wallet_manager.get_wallet_default_name(); + } + + void + application::set_wallet_default_name(QString wallet_name) noexcept + { + m_wallet_manager.set_wallet_default_name(std::move(wallet_name)); + emit on_wallet_default_name_changed(); + } + + bool + atomic_dex::application::create(const QString& password, const QString& seed, const QString& wallet_name) + { + return m_wallet_manager.create(password, seed, wallet_name); + } + bool application::login(const QString& password, const QString& wallet_name) { - return m_wallet_manager.login(password, wallet_name, get_mm2(), this->m_current_default_wallet, [this]() { this->set_status("initializing_mm2"); }); + bool res = m_wallet_manager.login(password, wallet_name, get_mm2(), [this]() { this->set_status("initializing_mm2"); }); + this->m_addressbook->initializeFromCfg(); + return res; } bool @@ -1260,3 +1422,72 @@ namespace atomic_dex return atomic_dex::qt_wallet_manager::is_there_a_default_wallet(); } } // namespace atomic_dex + +//! Actions implementation +namespace atomic_dex +{ + void + application::process_refresh_enabled_coin_action() + { + auto& mm2 = get_mm2(); + auto refresh_coin = [](t_coins coins_container, auto&& coins_list) { + coins_list.clear(); + coins_list = to_qt_binding(std::move(coins_container)); + }; + + { + auto coins = mm2.get_enabled_coins(); + refresh_coin(coins, m_enabled_coins); + emit enabledCoinsChanged(); + } + { + auto coins = mm2.get_enableable_coins(); + refresh_coin(coins, m_enableable_coins); + emit enableableCoinsChanged(); + } + { + if (not m_coin_info->get_ticker().isEmpty()) + { + refresh_transactions(mm2); + } + } + } + + void + application::process_refresh_current_ticker_infos() + { + auto& mm2 = get_mm2(); + auto& paprika = get_paprika(); + + refresh_transactions(mm2); + refresh_fiat_balance(mm2, paprika); + refresh_address(mm2); + { + const auto ticker = m_coin_info->get_ticker().toStdString(); + const auto& info = get_mm2().get_coin_info(ticker); + m_coin_info->set_name(QString::fromStdString(info.name)); + m_coin_info->set_claimable(info.is_claimable); + m_coin_info->set_type(QString::fromStdString(info.type)); + m_coin_info->set_paprika_id(QString::fromStdString(info.coinpaprika_id)); + m_coin_info->set_minimal_balance_for_asking_rewards(QString::fromStdString(info.minimal_claim_amount)); + m_coin_info->set_explorer_url(QString::fromStdString(info.explorer_url[0])); + std::error_code ec; + m_coin_info->set_price(QString::fromStdString(paprika.get_rate_conversion(m_config.current_currency, ticker, ec, true))); + m_coin_info->set_change24h(retrieve_change_24h(paprika, info, m_config)); + m_coin_info->set_trend_7d(nlohmann_json_array_to_qt_json_array(paprika.get_ticker_historical(ticker).answer)); + } + } + + void + application::exit_handler() + { + spdlog::trace("will quit app, prevent all threading event"); + this->m_about_to_exit_app = true; + } + + void + application::on_start_fetching_new_ohlc_data_event(const start_fetching_new_ohlc_data& evt) + { + this->m_candlestick_chart_ohlc->set_is_currently_fetching(evt.is_a_reset); + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.app.hpp b/src/atomic.dex.app.hpp index ee1b57ee60..b722c4bc52 100644 --- a/src/atomic.dex.app.hpp +++ b/src/atomic.dex.app.hpp @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include @@ -31,12 +32,18 @@ #include "atomic.dex.cfg.hpp" #include "atomic.dex.mm2.hpp" #include "atomic.dex.provider.coinpaprika.hpp" +#include "atomic.dex.qt.addressbook.model.hpp" #include "atomic.dex.qt.bindings.hpp" +#include "atomic.dex.qt.candlestick.charts.model.hpp" #include "atomic.dex.qt.current.coin.infos.hpp" +#include "atomic.dex.qt.orders.model.hpp" +#include "atomic.dex.qt.portfolio.model.hpp" #include "atomic.dex.qt.wallet.manager.hpp" namespace ag = antara::gaming; +inline constexpr std::size_t g_max_actions_size{128}; + namespace atomic_dex { struct application : public QObject, public ag::world::app @@ -45,11 +52,16 @@ namespace atomic_dex //! Properties Q_PROPERTY(QString empty_string READ get_empty_string NOTIFY lang_changed) - Q_PROPERTY(QList enabled_coins READ get_enabled_coins NOTIFY enabledCoinsChanged) - Q_PROPERTY(QList enableable_coins READ get_enableable_coins NOTIFY enableableCoinsChanged) + Q_PROPERTY(QList enabled_coins READ get_enabled_coins NOTIFY enabledCoinsChanged) + Q_PROPERTY(QList enableable_coins READ get_enableable_coins NOTIFY enableableCoinsChanged) Q_PROPERTY(QObject* current_coin_info READ get_current_coin_info NOTIFY coinInfoChanged) - Q_PROPERTY(QString fiat READ get_current_fiat WRITE set_current_fiat NOTIFY on_fiat_changed) - Q_PROPERTY(QString second_fiat READ get_second_current_fiat WRITE set_second_current_fiat NOTIFY on_second_fiat_changed) + Q_PROPERTY(addressbook_model* addressbook_mdl READ get_addressbook NOTIFY addressbookChanged) + Q_PROPERTY(orders_model* orders_mdl READ get_orders NOTIFY ordersChanged) + Q_PROPERTY(candlestick_charts_model* candlestick_charts_mdl READ get_candlestick_charts NOTIFY candlestickChartsChanged) + Q_PROPERTY(QVariant update_status READ get_update_status NOTIFY updateStatusChanged) + Q_PROPERTY(portfolio_model* portfolio_mdl READ get_portfolio NOTIFY portfolioChanged) + Q_PROPERTY(QString current_currency READ get_current_currency WRITE set_current_currency NOTIFY on_currency_changed) + Q_PROPERTY(QString current_fiat READ get_current_fiat WRITE set_current_fiat NOTIFY on_fiat_changed) Q_PROPERTY(QString lang READ get_current_lang WRITE set_current_lang NOTIFY on_lang_changed) Q_PROPERTY(QString wallet_default_name READ get_wallet_default_name WRITE set_wallet_default_name NOTIFY on_wallet_default_name_changed) Q_PROPERTY(QString balance_fiat_all READ get_balance_fiat_all WRITE set_current_balance_fiat_all NOTIFY on_fiat_balance_all_changed) @@ -65,47 +77,73 @@ namespace atomic_dex void connect_signals(); void tick(); + public: + enum class action + { + refresh_enabled_coin = 0, + refresh_current_ticker = 1, + refresh_ohlc = 2, + refresh_transactions = 3, + refresh_portfolio_ticker_balance = 4, + refresh_update_status = 5, + post_process_orders_finished = 6, + post_process_swaps_finished = 7 + }; + public: //! Constructor explicit application(QObject* pParent = nullptr) noexcept; ~application() noexcept; //! entt::dispatcher events + void on_ticker_balance_updated_event(const ticker_balance_updated&) noexcept; void on_enabled_coins_event(const enabled_coins_event&) noexcept; + void on_enabled_default_coins_event(const enabled_default_coins_event&) noexcept; + void on_coin_fully_initialized_event(const coin_fully_initialized&) noexcept; void on_change_ticker_event(const change_ticker_event&) noexcept; void on_tx_fetch_finished_event(const tx_fetch_finished&) noexcept; void on_coin_disabled_event(const coin_disabled&) noexcept; void on_mm2_initialized_event(const mm2_initialized&) noexcept; void on_mm2_started_event(const mm2_started&) noexcept; - void on_refresh_order_event(const refresh_order_needed&) noexcept; + void on_refresh_ohlc_event(const refresh_ohlc_needed&) noexcept; + void on_refresh_update_status_event(const refresh_update_status&) noexcept; + void on_process_orders_finished_event(const process_orders_finished&) noexcept; + void on_process_swaps_finished_event(const process_swaps_finished&) noexcept; + void on_start_fetching_new_ohlc_data_event(const start_fetching_new_ohlc_data&); //! Properties Getter - QString get_empty_string(); - mm2& get_mm2() noexcept; - const mm2& get_mm2() const noexcept; - coinpaprika_provider& get_paprika() noexcept; - entt::dispatcher& get_dispatcher() noexcept; - QObject* get_current_coin_info() const noexcept; - QObjectList get_enabled_coins() const noexcept; - QObjectList get_enableable_coins() const noexcept; - QString get_current_fiat() const noexcept; - QString get_second_current_fiat() const noexcept; - QString get_current_lang() const noexcept; - QString get_balance_fiat_all() const noexcept; - QString get_second_balance_fiat_all() const noexcept; - QString get_wallet_default_name() const noexcept; - QString get_status() const noexcept; - Q_INVOKABLE QString get_version() const noexcept; + static const QString& get_empty_string(); + mm2& get_mm2() noexcept; + const mm2& get_mm2() const noexcept; + coinpaprika_provider& get_paprika() noexcept; + entt::dispatcher& get_dispatcher() noexcept; + QObject* get_current_coin_info() const noexcept; + addressbook_model* get_addressbook() const noexcept; + portfolio_model* get_portfolio() const noexcept; + orders_model* get_orders() const noexcept; + candlestick_charts_model* get_candlestick_charts() const noexcept; + ; + QVariantList get_enabled_coins() const noexcept; + QVariantList get_enableable_coins() const noexcept; + QString get_current_currency() const noexcept; + QString get_current_fiat() const noexcept; + QString get_current_lang() const noexcept; + QString get_balance_fiat_all() const noexcept; + QString get_second_balance_fiat_all() const noexcept; + QString get_wallet_default_name() const noexcept; + QString get_status() const noexcept; + QVariant get_update_status() const noexcept; + Q_INVOKABLE static QString get_version() noexcept; //! Properties Setter - void set_current_fiat(QString current_fiat) noexcept; - void set_second_current_fiat(QString current_fiat) noexcept; + void set_current_currency(const QString& current_currency) noexcept; + void set_current_fiat(const QString& current_fiat) noexcept; void set_current_lang(const QString& current_lang) noexcept; void set_wallet_default_name(QString wallet_default_name) noexcept; void set_current_balance_fiat_all(QString current_fiat_all_balance) noexcept; void set_second_current_balance_fiat_all(QString current_fiat_all_balance) noexcept; void set_status(QString status) noexcept; - void set_qt_app(QApplication* app) noexcept; + void set_qt_app(std::shared_ptr app) noexcept; //! Launch the internal loop for the SDK. void launch(); @@ -114,6 +152,7 @@ namespace atomic_dex //! Wallet Manager QML API Bindings, this internally call the `atomic_dex::qt_wallet_manager` Q_INVOKABLE bool login(const QString& password, const QString& wallet_name); + Q_INVOKABLE bool create(const QString& password, const QString& seed, const QString& wallet_name); Q_INVOKABLE static QStringList get_wallets(); Q_INVOKABLE static bool is_there_a_default_wallet(); Q_INVOKABLE static QString get_default_wallet_name(); @@ -121,23 +160,24 @@ namespace atomic_dex Q_INVOKABLE static bool confirm_password(const QString& wallet_name, const QString& password); //! Miscs - Q_INVOKABLE QString get_paprika_id_from_ticker(QString ticker) const; - Q_INVOKABLE QString to_eth_checksum_qt(QString eth_lowercase_address) const; - Q_INVOKABLE QString get_mm2_version() const; - Q_INVOKABLE QString get_log_folder() const; - Q_INVOKABLE QString get_export_folder() const; - Q_INVOKABLE QStringList get_available_langs() const; - Q_INVOKABLE static void change_state(int visibility); + Q_INVOKABLE QString get_paprika_id_from_ticker(const QString& ticker) const; + Q_INVOKABLE static QString to_eth_checksum_qt(const QString& eth_lowercase_address); + Q_INVOKABLE static QString get_mm2_version(); + Q_INVOKABLE static QString get_log_folder(); + Q_INVOKABLE static QString get_export_folder(); + Q_INVOKABLE QStringList get_available_langs() const; + Q_INVOKABLE QStringList get_available_fiats() const; + Q_INVOKABLE QStringList get_available_currencies() const; + Q_INVOKABLE static void change_state(int visibility); //! Portfolio QML API Bindings - Q_INVOKABLE QString recover_fund(QString uuid) const; + Q_INVOKABLE static QString recover_fund(const QString& uuid); Q_INVOKABLE QObject* prepare_send(const QString& address, const QString& amount, bool max = false); Q_INVOKABLE QObject* prepare_send_fees( const QString& address, const QString& amount, bool is_erc_20, const QString& fees_amount, const QString& gas_price, const QString& gas, bool max = false); - Q_INVOKABLE QString send(const QString& tx_hex); - Q_INVOKABLE QString send_rewards(const QString& tx_hex); - Q_INVOKABLE QVariantList get_portfolio_informations(); + Q_INVOKABLE QString send(const QString& tx_hex); + Q_INVOKABLE QString send_rewards(const QString& tx_hex); //! Trading QML API Bindings Q_INVOKABLE void on_gui_enter_dex(); @@ -147,39 +187,47 @@ namespace atomic_dex Q_INVOKABLE void cancel_all_orders_by_ticker(const QString& ticker); //! Others - Q_INVOKABLE bool mnemonic_validate(QString entropy); - Q_INVOKABLE QString retrieve_seed(const QString& wallet_name, const QString& password); - Q_INVOKABLE void refresh_infos(); - Q_INVOKABLE void refresh_orders_and_swaps(); - Q_INVOKABLE QString get_mnemonic(); - Q_INVOKABLE bool first_run(); - Q_INVOKABLE bool disconnect(); - Q_INVOKABLE bool create(const QString& password, const QString& seed, const QString& wallet_name); - Q_INVOKABLE bool enable_coins(const QStringList& coins); - Q_INVOKABLE QString get_balance(const QString& coin); - Q_INVOKABLE QString get_price_amount(const QString& base_amount, const QString& rel_amount); - Q_INVOKABLE bool place_buy_order(const QString& base, const QString& rel, const QString& price, const QString& volume); - Q_INVOKABLE QString place_sell_order(const QString& base, const QString& rel, const QString& price, const QString& volume, bool is_created_order, const QString& price_denom, const QString& price_numer); + Q_INVOKABLE static bool mnemonic_validate(const QString& entropy); + Q_INVOKABLE static QString retrieve_seed(const QString& wallet_name, const QString& password); + Q_INVOKABLE void refresh_infos(); + Q_INVOKABLE void refresh_orders_and_swaps(); + Q_INVOKABLE static QString get_mnemonic(); + Q_INVOKABLE static bool first_run(); + Q_INVOKABLE bool disconnect(); + Q_INVOKABLE bool enable_coins(const QStringList& coins); + Q_INVOKABLE QString get_balance(const QString& coin); + Q_INVOKABLE static QString get_price_amount(const QString& base_amount, const QString& rel_amount); + Q_INVOKABLE bool place_buy_order(const QString& base, const QString& rel, const QString& price, const QString& volume); + Q_INVOKABLE QString place_sell_order( + const QString& base, const QString& rel, const QString& price, const QString& volume, bool is_created_order, const QString& price_denom, + const QString& price_numer); Q_INVOKABLE void set_current_orderbook(const QString& base, const QString& rel); Q_INVOKABLE QVariantMap get_orderbook(const QString& ticker); Q_INVOKABLE bool do_i_have_enough_funds(const QString& ticker, const QString& amount) const; Q_INVOKABLE bool disable_coins(const QStringList& coins); Q_INVOKABLE bool is_claiming_ready(const QString& ticker); Q_INVOKABLE QObject* claim_rewards(const QString& ticker); - Q_INVOKABLE QObject* get_coin_info(const QString& ticker); - Q_INVOKABLE QVariantMap get_my_orders(); - Q_INVOKABLE QVariantMap get_recent_swaps(); - Q_INVOKABLE bool export_swaps(const QString& csv_filename) noexcept; - Q_INVOKABLE bool export_swaps_json() noexcept; - Q_INVOKABLE QString get_regex_password_policy() const noexcept; - Q_INVOKABLE QVariantMap get_trade_infos(const QString& ticker, const QString& receive_ticker, const QString& amount); + Q_INVOKABLE QString get_cex_rates(const QString& base, const QString& rel); + Q_INVOKABLE QString get_fiat_from_amount(const QString& ticker, const QString& amount); + + Q_INVOKABLE QVariantList get_ohlc_data(const QString& range); + Q_INVOKABLE QVariantMap find_closest_ohlc_data(int range, int timestamp); + Q_INVOKABLE bool is_supported_ohlc_data_ticker_pair(const QString& base, const QString& rel); + Q_INVOKABLE QVariant get_coin_info(const QString& ticker); + Q_INVOKABLE bool export_swaps(const QString& csv_filename) noexcept; + Q_INVOKABLE bool export_swaps_json() noexcept; + Q_INVOKABLE static QString get_regex_password_policy() noexcept; + Q_INVOKABLE QVariantMap get_trade_infos(const QString& ticker, const QString& receive_ticker, const QString& amount); + Q_INVOKABLE QVariantList get_all_coins() const noexcept; + signals: //! Signals to the QML Worlds void enabledCoinsChanged(); void enableableCoinsChanged(); void coinInfoChanged(); + void on_currency_changed(); void on_fiat_changed(); void on_second_fiat_changed(); void on_lang_changed(); @@ -189,33 +237,59 @@ namespace atomic_dex void on_status_changed(); void on_wallet_default_name_changed(); void myOrdersUpdated(); + void addressbookChanged(); + void OHLCDataUpdated(); + void portfolioChanged(); + void updateStatusChanged(); + void ordersChanged(); + void candlestickChartsChanged(); + public slots: + void exit_handler(); + ; + + private: + void process_refresh_enabled_coin_action(); + void process_refresh_current_ticker_infos(); private: //! CFG atomic_dex::cfg m_config{load_cfg()}; //! QT Application - QApplication* m_app; + std::shared_ptr m_app; //! Wallet Manager atomic_dex::qt_wallet_manager m_wallet_manager; //! Private members - std::atomic_bool m_refresh_enabled_coin_event{false}; - std::atomic_bool m_refresh_current_ticker_infos{false}; - std::atomic_bool m_refresh_orders_needed{false}; - std::atomic_bool m_refresh_transaction_only{false}; + boost::lockfree::queue m_actions_queue{g_max_actions_size}; + boost::synchronized_value m_ticker_balance_to_refresh; + bool m_need_a_full_refresh_of_mm2{false}; - QObjectList m_enabled_coins; - QObjectList m_enableable_coins; + QVariantList m_enabled_coins; + QVariantList m_enableable_coins; + QVariant m_update_status; QTranslator m_translator; - QString m_current_fiat{"USD"}; - QString m_second_current_fiat{"BTC"}; QString m_current_lang{QString::fromStdString(m_config.current_lang)}; QString m_current_status{"None"}; QString m_current_balance_all{"0.00"}; QString m_second_current_balance_all{"0.00"}; - QString m_current_default_wallet{""}; current_coin_info* m_coin_info; + + + //! Addressbook based on the current wallet + addressbook_model* m_addressbook; + + //! Portfolio based on the current wallet + portfolio_model* m_portfolio; + + //! Orders model based on the current wallet + orders_model* m_orders; + + //! Candlestick charts + candlestick_charts_model* m_candlestick_chart_ohlc; + std::atomic_bool m_candlestick_need_a_reset{false}; + + std::atomic_bool m_about_to_exit_app{false}; }; } // namespace atomic_dex diff --git a/src/atomic.dex.cfg.cpp b/src/atomic.dex.cfg.cpp index 3cebe5dac8..60f889a0fd 100644 --- a/src/atomic.dex.cfg.cpp +++ b/src/atomic.dex.cfg.cpp @@ -16,17 +16,10 @@ #include "atomic.dex.cfg.hpp" -namespace atomic_dex +namespace { void - from_json(const nlohmann::json& j, atomic_dex::cfg& config) - { - j.at("lang").get_to(config.current_lang); - j.at("available_lang").get_to(config.available_lang); - } - - void - change_lang(std::string new_lang) + upgrade_cfg(atomic_dex::cfg& config) { fs::path cfg_path = ag::core::assets_real_path() / "config"; std::ifstream ifs((cfg_path / "cfg.json").c_str()); @@ -34,7 +27,10 @@ namespace atomic_dex assert(ifs.is_open()); ifs >> config_json_data; - config_json_data["lang"] = std::move(new_lang); + config_json_data["lang"] = config.current_lang; + config_json_data["current_currency"] = config.current_currency; + config_json_data["current_fiat"] = config.current_fiat; + config_json_data["possible_currencies"] = config.possible_currencies; ifs.close(); @@ -43,12 +39,26 @@ namespace atomic_dex assert(ofs.is_open()); ofs << config_json_data; } +} // namespace + +namespace atomic_dex +{ + void + from_json(const nlohmann::json& j, atomic_dex::cfg& config) + { + j.at("lang").get_to(config.current_lang); + j.at("available_lang").get_to(config.available_lang); + j.at("current_currency").get_to(config.current_currency); + j.at("current_fiat").get_to(config.current_fiat); + j.at("available_fiat").get_to(config.available_fiat); + j.at("possible_currencies").get_to(config.possible_currencies); + } void change_lang(cfg& config, const std::string& new_lang) { config.current_lang = new_lang; - change_lang(new_lang); + upgrade_cfg(config); } cfg @@ -65,4 +75,33 @@ namespace atomic_dex from_json(config_json_data, out); return out; } + + bool + is_this_currency_a_fiat(cfg& config, const std::string& currency) noexcept + { + return ranges::any_of(config.available_fiat, [currency](const std::string& current_fiat) { return current_fiat == currency; }); + } + + void + change_currency(cfg& config, const std::string& new_currency) + { + config.current_currency = new_currency; + + //! If it's fiat, i set the first element of the possible currencies to the new currency (the new fiat here) and i also set the current fiat + if (is_this_currency_a_fiat(config, new_currency)) + { + spdlog::info("{} is fiat, setting it as current fiat and possible currencies", new_currency); + config.current_fiat = new_currency; + config.possible_currencies[0] = new_currency; + } + upgrade_cfg(config); + } + + void + change_fiat(cfg& config, const std::string& new_fiat) + { + config.current_fiat = new_fiat; + config.possible_currencies[0] = new_fiat; + upgrade_cfg(config); + } } // namespace atomic_dex diff --git a/src/atomic.dex.cfg.hpp b/src/atomic.dex.cfg.hpp index 5aa57311c4..cd802f6f71 100644 --- a/src/atomic.dex.cfg.hpp +++ b/src/atomic.dex.cfg.hpp @@ -22,12 +22,18 @@ namespace atomic_dex { struct cfg { - std::string current_lang{"en"}; - std::vector available_lang; + std::string current_lang{"en"}; + std::string current_currency; + std::string current_fiat; + std::vector available_lang; + std::vector available_fiat; + std::array possible_currencies; }; void from_json(const nlohmann::json& j, cfg& config); - void change_lang(std::string new_lang); void change_lang(cfg& config, const std::string& new_lang); - cfg load_cfg(); -} \ No newline at end of file + void change_currency(cfg& config, const std::string& new_currency); + void change_fiat(cfg& config, const std::string& new_fiat); + [[nodiscard]] bool is_this_currency_a_fiat(cfg& config, const std::string& currency) noexcept; + cfg load_cfg(); +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.events.hpp b/src/atomic.dex.events.hpp index 6f043ab4d8..0c97854b69 100644 --- a/src/atomic.dex.events.hpp +++ b/src/atomic.dex.events.hpp @@ -21,20 +21,44 @@ namespace atomic_dex { - using mm2_started = entt::tag<"mm2_started"_hs>; - using gui_enter_trading = entt::tag<"gui_enter_trading"_hs>; - using gui_leave_trading = entt::tag<"gui_leave_trading"_hs>; - using mm2_initialized = entt::tag<"mm2_running_and_enabling"_hs>; - using enabled_coins_event = entt::tag<"gui_enabled_coins"_hs>; - using change_ticker_event = entt::tag<"gui_change_ticker"_hs>; - using tx_fetch_finished = entt::tag<"gui_tx_fetch_finished"_hs>; - using refresh_order_needed = entt::tag<"gui_refresh_order_needed"_hs>; + using mm2_started = entt::tag<"mm2_started"_hs>; + using gui_enter_trading = entt::tag<"gui_enter_trading"_hs>; + using gui_leave_trading = entt::tag<"gui_leave_trading"_hs>; + using mm2_initialized = entt::tag<"mm2_running_and_enabling"_hs>; + using enabled_coins_event = entt::tag<"gui_enabled_coins"_hs>; + using enabled_default_coins_event = entt::tag<"gui_enabled_default_coins"_hs>; + using change_ticker_event = entt::tag<"gui_change_ticker"_hs>; + using tx_fetch_finished = entt::tag<"gui_tx_fetch_finished"_hs>; + using refresh_update_status = entt::tag<"gui_refresh_update_status"_hs>; + using process_orders_finished = entt::tag<"gui_process_orders_finished"_hs>; + using process_swaps_finished = entt::tag<"gui_process_swaps_finished"_hs>; + + struct refresh_ohlc_needed + { + bool is_a_reset; + }; + + struct start_fetching_new_ohlc_data + { + bool is_a_reset; + }; + + struct ticker_balance_updated + { + std::string ticker; + }; struct coin_enabled { std::string ticker; }; + //! Event when paprika fetch all the data of this specific coin + struct coin_fully_initialized + { + std::string ticker; + }; + struct coin_disabled { std::string ticker; diff --git a/src/atomic.dex.kill.hpp b/src/atomic.dex.kill.hpp index 15613202e2..02dfe031d5 100644 --- a/src/atomic.dex.kill.hpp +++ b/src/atomic.dex.kill.hpp @@ -16,6 +16,7 @@ #pragma once -namespace atomic_dex { +namespace atomic_dex +{ void kill_executable(const char* exec_name); } \ No newline at end of file diff --git a/src/atomic.dex.ma.series.data.hpp b/src/atomic.dex.ma.series.data.hpp new file mode 100644 index 0000000000..f056436b9d --- /dev/null +++ b/src/atomic.dex.ma.series.data.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace atomic_dex +{ + struct ma_series_data + { + std::size_t m_timestamp; + double m_average; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.mm2.api.cpp b/src/atomic.dex.mm2.api.cpp index f7b9a25cba..7c096b130d 100644 --- a/src/atomic.dex.mm2.api.cpp +++ b/src/atomic.dex.mm2.api.cpp @@ -20,13 +20,60 @@ //! Project Headers #include "atomic.dex.mm2.api.hpp" +//! Utilities namespace { - namespace bm = boost::multiprecision; + template + void + extract_rpc_json_answer(const nlohmann::json& j, RpcReturnType& answer) + { + if (j.contains("error") && j.at("error").is_string()) + { + answer.error = j.at("error").get(); + } + else if (j.contains("result")) + { + answer.result = j.at("result").get(); + } + } } // namespace +//! Implementation RPC [max_taker_vol] namespace mm2::api { + //! Serialization + void + to_json(nlohmann::json& j, const max_taker_vol_request& cfg) + { + j["coin"] = cfg.coin; + } + + //! Deserialization + void + from_json(const nlohmann::json& j, max_taker_vol_answer_success& cfg) + { + j.at("denom").get_to(cfg.denom); + j.at("numer").get_to(cfg.numer); + } + + void + from_json(const nlohmann::json& j, max_taker_vol_answer& answer) + { + extract_rpc_json_answer(j, answer); + } + + //! Rpc Call + max_taker_vol_answer + rpc_max_taker_vol(max_taker_vol_request&& request) + { + return process_rpc(std::forward(request), "max_taker_vol"); + } +} // namespace mm2::api + +//! Implementation RPC [enable] +namespace mm2::api +{ + //! Serialization void to_json(nlohmann::json& j, const enable_request& cfg) { @@ -37,6 +84,7 @@ namespace mm2::api j["tx_history"] = cfg.with_tx_history; } + //! Deserialization void from_json(const nlohmann::json& j, enable_answer& cfg) { @@ -44,15 +92,12 @@ namespace mm2::api j.at("balance").get_to(cfg.balance); j.at("result").get_to(cfg.result); } +} // namespace mm2::api - void - from_json(const nlohmann::json& j, electrum_answer& cfg) - { - j.at("address").get_to(cfg.address); - j.at("balance").get_to(cfg.balance); - j.at("result").get_to(cfg.result); - } - +//! Implementation RPC [electrum] +namespace mm2::api +{ + //! Serialization void to_json(nlohmann::json& j, const electrum_request& cfg) { @@ -61,12 +106,27 @@ namespace mm2::api j["tx_history"] = cfg.with_tx_history; } + //! Deserialization + void + from_json(const nlohmann::json& j, electrum_answer& cfg) + { + j.at("address").get_to(cfg.address); + j.at("balance").get_to(cfg.balance); + j.at("result").get_to(cfg.result); + } +} // namespace mm2::api + +//! Implementation RPC [disable_coin] +namespace mm2::api +{ + //! Serialization void to_json(nlohmann::json& j, const disable_coin_request& req) { j["coin"] = req.coin; } + //! Deserialization void from_json(const nlohmann::json& j, disable_coin_answer_success& resp) { @@ -76,16 +136,12 @@ namespace mm2::api void from_json(const nlohmann::json& j, disable_coin_answer& resp) { - if (j.count("error") == 1) - { - resp.error = j.get(); - } - else if (j.count("result") == 1) - { - resp.result = j.at("result").get(); - } + extract_rpc_json_answer(j, resp); } +} // namespace mm2::api +namespace mm2::api +{ void to_json(nlohmann::json& j, const balance_request& cfg) { @@ -166,9 +222,9 @@ namespace mm2::api using namespace date; using namespace std::chrono; date::sys_seconds tp{seconds{cfg.timestamp}}; - auto tp_zoned = date::make_zoned(current_zone(), tp); - std::string s = date::format("%e %b %Y, %I:%M", tp_zoned); - cfg.timestamp_as_date = std::move(s); + auto tp_zoned = date::make_zoned(current_zone(), tp); + std::string s = date::format("%e %b %Y, %I:%M", tp_zoned); + cfg.timestamp_as_date = std::move(s); } void @@ -353,8 +409,11 @@ namespace mm2::api j.at("age").get_to(contents.age); j.at("zcredits").get_to(contents.zcredits); - boost::trim_right_if(contents.price, boost::is_any_of("0")); - contents.price = contents.price; + if (contents.price.find('.') != std::string::npos) + { + boost::trim_right_if(contents.price, boost::is_any_of("0")); + contents.price = contents.price; + } contents.maxvolume = adjust_precision(contents.maxvolume); } @@ -375,8 +434,8 @@ namespace mm2::api j.at("timestamp").get_to(answer.timestamp); sys_time tp{std::chrono::milliseconds{answer.timestamp}}; - auto tp_zoned = date::make_zoned(current_zone(), tp); - answer.human_timestamp = date::format("%Y-%m-%d %I:%M:%S", tp_zoned); + auto tp_zoned = date::make_zoned(current_zone(), tp); + answer.human_timestamp = date::format("%Y-%m-%d %I:%M:%S", tp_zoned); } void @@ -610,14 +669,17 @@ namespace mm2::api j.at("type").get_to(contents.type); j.at("recoverable").get_to(contents.funds_recoverable); - contents.taker_amount = adjust_precision(contents.taker_amount); - contents.maker_amount = adjust_precision(contents.maker_amount); - contents.events = nlohmann::json::array(); + contents.taker_amount = adjust_precision(contents.taker_amount); + contents.maker_amount = adjust_precision(contents.maker_amount); + contents.events = nlohmann::json::array(); if (j.contains("my_info")) { contents.my_info = j.at("my_info"); - contents.my_info["other_amount"] = adjust_precision(contents.my_info["other_amount"].get()); - contents.my_info["my_amount"] = adjust_precision(contents.my_info["my_amount"].get()); + if (not contents.my_info.is_null()) + { + contents.my_info["other_amount"] = adjust_precision(contents.my_info["other_amount"].get()); + contents.my_info["my_amount"] = adjust_precision(contents.my_info["my_amount"].get()); + } } using t_event_timestamp_registry = std::unordered_map; t_event_timestamp_registry event_timestamp_registry; @@ -629,7 +691,7 @@ namespace mm2::api const nlohmann::json& j_evt = content.at("event"); auto timestamp = content.at("timestamp").get(); auto tp = sys_milliseconds{std::chrono::milliseconds{timestamp}}; - auto tp_zoned = date::make_zoned(current_zone(), tp); + auto tp_zoned = date::make_zoned(current_zone(), tp); std::string human_date = date::format("%F %T", tp_zoned); auto evt_type = j_evt.at("type").get(); @@ -639,7 +701,6 @@ namespace mm2::api { std::int64_t ts = event_timestamp_registry.at(previous_event); jf_evt["started_at"] = ts; - std::int64_t ts2 = jf_evt.at("timestamp").get(); std::stringstream ss; sys_time t1{std::chrono::milliseconds{ts}}; @@ -777,10 +838,6 @@ namespace mm2::api rpc_process_answer(const RestClient::Response& resp, const std::string& rpc_command) noexcept { spdlog::info("resp code for rpc_command {} is {}", rpc_command, resp.code); - /*if (rpc_command == "orderbook") - { - spdlog::debug("resp body: {}", resp.body); - }*/ RpcReturnType answer; try @@ -818,7 +875,7 @@ namespace mm2::api } catch (const std::exception& error) { - spdlog::error("exception caught {}", error.what()); + spdlog::error("{} l{} f[{}], exception caught {} for rpc {}", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string(), error.what(), rpc_command); answer.rpc_result_code = -1; answer.raw_result = error.what(); } @@ -995,10 +1052,6 @@ namespace mm2::api req_json_data.push_back(json_data); } - // auto json_copy = req_json_data; - // json_copy["userpass"] = "*******"; - // spdlog::debug("request: {}", json_copy.dump()); - auto resp = RestClient::post(g_endpoint, "application/json", req_json_data.dump()); spdlog::info("{} resp code: {}", __FUNCTION__, resp.code); @@ -1030,10 +1083,6 @@ namespace mm2::api req_json_data.push_back(json_data); } - // auto json_copy = req_json_data; - // json_copy["userpass"] = "*******"; - // spdlog::debug("request: {}", json_copy.dump()); - auto resp = RestClient::post(g_endpoint, "application/json", req_json_data.dump()); spdlog::info("{} resp code: {}", __FUNCTION__, resp.code); diff --git a/src/atomic.dex.mm2.api.hpp b/src/atomic.dex.mm2.api.hpp index 00e7f36d09..4fd618ca50 100644 --- a/src/atomic.dex.mm2.api.hpp +++ b/src/atomic.dex.mm2.api.hpp @@ -27,6 +27,35 @@ namespace mm2::api inline constexpr const char* g_endpoint = "http://127.0.0.1:7783"; std::string rpc_version(); + + //! max taker vol + struct max_taker_vol_request + { + std::string coin; + }; + + void to_json(nlohmann::json& j, const max_taker_vol_request& cfg); + + struct max_taker_vol_answer_success + { + std::string denom; + std::string numer; + }; + + void from_json(const nlohmann::json& j, max_taker_vol_answer_success& cfg); + + struct max_taker_vol_answer + { + std::optional result; + std::optional error; + int rpc_result_code; + std::string raw_result; + }; + + void from_json(const nlohmann::json& j, max_taker_vol_answer& answer); + + max_taker_vol_answer rpc_max_taker_vol(max_taker_vol_request&& request); + //! Only for erc 20 struct enable_request { @@ -323,8 +352,8 @@ namespace mm2::api struct send_raw_transaction_request { - std::string coin; std::string tx_hex; + std::string coin; }; void to_json(nlohmann::json& j, const send_raw_transaction_request& cfg); @@ -540,10 +569,10 @@ namespace mm2::api std::string rel; bool cancellable; std::size_t timestamp; - std::string human_timestamp; std::string order_type; std::string base_amount; std::string rel_amount; + std::string human_timestamp; }; struct my_orders_answer diff --git a/src/atomic.dex.mm2.cpp b/src/atomic.dex.mm2.cpp index fc561d4094..161f614c25 100644 --- a/src/atomic.dex.mm2.cpp +++ b/src/atomic.dex.mm2.cpp @@ -76,7 +76,7 @@ namespace //! Delete old cfg boost::system::error_code ec; fs::remove(precedent_version_cfg_path, ec); - if (!ec) + if (ec) { spdlog::error("error: {}", ec.message()); } @@ -138,7 +138,7 @@ namespace atomic_dex dispatcher_.sink().connect<&mm2::on_gui_leave_trading>(*this); dispatcher_.sink().connect<&mm2::on_refresh_orderbook>(*this); - m_swaps_registry.insert("result", t_my_recent_swaps_answer{.total = 0}); + m_swaps_registry.insert("result", t_my_recent_swaps_answer{.limit = 0, .total = 0}); } void @@ -204,6 +204,23 @@ namespace atomic_dex return m_mm2_running; } + t_coins + mm2::get_all_coins() const noexcept + { + t_coins destination; + + destination.reserve(m_coins_informations.size()); + for (auto&& [key, value]: m_coins_informations) + { + //! + destination.push_back(value); + } + + std::sort(begin(destination), end(destination), [](auto&& lhs, auto&& rhs) { return lhs.ticker < rhs.ticker; }); + + return destination; + } + t_coins mm2::get_enabled_coins() const noexcept { @@ -306,7 +323,7 @@ namespace atomic_dex if (not coin_info.is_erc_20) { t_electrum_request request{.coin_name = coin_info.ticker, .servers = coin_info.electrum_urls.value(), .with_tx_history = true}; - auto answer = rpc_electrum(std::move(request)); + const auto answer = rpc_electrum(std::move(request)); if (answer.result not_eq "success") { return false; @@ -315,7 +332,7 @@ namespace atomic_dex else { t_enable_request request{.coin_name = coin_info.ticker, .urls = coin_info.eth_urls.value()}; - auto answer = rpc_enable(std::move(request)); + const auto answer = rpc_enable(std::move(request)); if (answer.result not_eq "success") { return false; @@ -326,15 +343,8 @@ namespace atomic_dex coin_info.currently_enabled = true; m_coins_informations.assign(coin_info.ticker, coin_info); - spawn([this, copy_ticker = ticker]() { - // loguru::set_thread_name("balance thread"); - process_balance(copy_ticker); - }); - - spawn([this, copy_ticker = ticker]() { - // loguru::set_thread_name("tx thread"); - process_tx(copy_ticker); - }); + spawn([this, copy_ticker = ticker]() { process_balance(copy_ticker); }); + spawn([this, copy_ticker = ticker]() { process_tx(copy_ticker); }); dispatcher_.trigger(ticker); if (emit_event) @@ -359,26 +369,15 @@ namespace atomic_dex tickers.reserve(coins.size()); for (auto&& current_coin: coins) { tickers.push_back(current_coin.ticker); } - futures.emplace_back(spawn([this, tickers]() { - // loguru::set_thread_name("enable thread"); - batch_enable_coins(tickers); - })); + futures.emplace_back(spawn([this, tickers]() { batch_enable_coins(tickers); })); for (auto&& fut: futures) { fut.get(); } - this->dispatcher_.trigger(); + this->dispatcher_.trigger(); - spawn([this]() { - // loguru::set_thread_name("swaps thread"); - process_swaps(); - }); + spawn([this]() { process_orders(); }); - spawn([this]() { - // loguru::set_thread_name("orders thread"); - process_orders(); - }); - - // spawn([this]() { process_fees(); }); + spawn([this]() { process_swaps(); }); return result.load() == 1; } @@ -391,7 +390,6 @@ namespace atomic_dex for (const auto& ticker: tickers) { spawn([this, ticker]() { - // loguru::set_thread_name("disable multiple coins"); std::error_code ec; disable_coin(ticker, ec); if (ec) @@ -439,16 +437,11 @@ namespace atomic_dex coin_info.currently_enabled = true; m_coins_informations.assign(coin_info.ticker, coin_info); - spawn([this, copy_ticker = ticker]() { - // loguru::set_thread_name("balance thread"); - process_balance(copy_ticker); - }); + auto fut = spawn([this, copy_ticker = ticker]() { process_balance(copy_ticker); }); - spawn([this, copy_ticker = ticker]() { - // loguru::set_thread_name("tx thread"); - process_tx(copy_ticker); - }); + spawn([this, copy_ticker = ticker]() { process_tx(copy_ticker); }); + fut.get(); dispatcher_.trigger(ticker); if (emit_event) { @@ -479,10 +472,7 @@ namespace atomic_dex void mm2::enable_multiple_coins(const std::vector& tickers) noexcept { - spawn([this, tickers]() { - // loguru::set_thread_name("enable multiple coins"); - batch_enable_coins(tickers, true); - }); + spawn([this, tickers]() { batch_enable_coins(tickers, true); }); update_coin_status(this->m_current_wallet_name, tickers, true); } @@ -559,7 +549,6 @@ namespace atomic_dex void mm2::fetch_infos_thread() { - // loguru::set_thread_name("info thread"); spdlog::info("{}: Fetching Infos l{}", __FUNCTION__, __LINE__); t_coins coins = get_enabled_coins(); @@ -567,26 +556,14 @@ namespace atomic_dex futures.reserve(coins.size() * 2 + 2); - futures.emplace_back(spawn([this]() { - // loguru::set_thread_name("swaps thread"); - process_swaps(); - })); + futures.emplace_back(spawn([this]() { process_orders(); })); - futures.emplace_back(spawn([this]() { - // loguru::set_thread_name("orders thread"); - process_orders(); - })); + futures.emplace_back(spawn([this]() { process_swaps(); })); for (auto&& current_coin: coins) { - futures.emplace_back(spawn([this, ticker = current_coin.ticker]() { - // loguru::set_thread_name("balance thread"); - process_balance(ticker); - })); - futures.emplace_back(spawn([this, ticker = current_coin.ticker]() { - // loguru::set_thread_name("tx thread"); - process_tx(ticker); - })); + futures.emplace_back(spawn([this, ticker = current_coin.ticker]() { process_balance(ticker); })); + futures.emplace_back(spawn([this, ticker = current_coin.ticker]() { process_tx(ticker); })); } for (auto&& fut: futures) { fut.get(); } @@ -624,7 +601,7 @@ namespace atomic_dex spdlog::debug("command line: {}, from directory: {}", args[0], options.working_directory); const auto ec = m_mm2_instance.start(args, options); - + std::free((void*)options.working_directory); if (ec) { spdlog::error("{}", ec.message()); @@ -632,22 +609,28 @@ namespace atomic_dex m_mm2_init_thread = std::thread([this, mm2_cfg_path]() { using namespace std::chrono_literals; - // loguru::set_thread_name("mm2 init thread"); + auto check_mm2_alive = []() { return ::mm2::api::rpc_version() != "error occured during rpc_version"; }; + static std::size_t nb_try = 0; - const auto wait_ec = m_mm2_instance.wait(2s).second; - fs::remove(mm2_cfg_path); - if (wait_ec.value() == static_cast(std::errc::timed_out) || wait_ec.value() == 258) - { - spdlog::info("mm2 is initialized"); - dispatcher_.trigger(); - enable_default_coins(); - m_mm2_running = true; - dispatcher_.trigger(); - } - else + while (not check_mm2_alive()) { - spdlog::error("{}", wait_ec.message()); + nb_try += 1; + if (nb_try == 30) + { + spdlog::error("MM2 not started correctly"); + //! TODO: emit mm2_failed_initialization + fs::remove(mm2_cfg_path); + return; + } + std::this_thread::sleep_for(1s); } + + fs::remove(mm2_cfg_path); + spdlog::info("mm2 is initialized"); + dispatcher_.trigger(); + enable_default_coins(); + m_mm2_running = true; + dispatcher_.trigger(); }); } @@ -720,11 +703,19 @@ namespace atomic_dex t_broadcast_answer mm2::broadcast(t_broadcast_request&& request, t_mm2_ec& ec) noexcept { - auto result = rpc_send_raw_transaction(std::move(request)); + std::string coin = request.coin; + auto result = rpc_send_raw_transaction(std::move(request)); if (result.rpc_result_code == -1) { ec = dextop_error::rpc_send_raw_transaction_error; } + else + { + if (this->get_coin_info(coin).is_erc_20) + { + result.tx_hash = "0x" + result.tx_hash; + } + } return result; } @@ -736,17 +727,20 @@ namespace atomic_dex if (answer.raw_result.find("error") == std::string::npos) { m_balance_informations.insert_or_assign(ticker, answer); + this->dispatcher_.trigger(ticker); } } void mm2::process_swaps() { - t_my_recent_swaps_request request{.limit = 50}; + std::size_t total = this->m_swaps_registry.at("result").total; + t_my_recent_swaps_request request{.limit = total > 0 ? total : 50}; auto answer = rpc_my_recent_swaps(std::move(request)); if (answer.result.has_value()) { m_swaps_registry.insert_or_assign("result", answer.result.value()); + this->dispatcher_.trigger(); } /*if (not m_swaps_registry.empty()) @@ -765,6 +759,7 @@ namespace atomic_dex mm2::process_orders() { m_orders_registry.insert_or_assign("result", ::mm2::api::rpc_my_orders()); + this->dispatcher_.trigger(); } void @@ -787,13 +782,19 @@ namespace atomic_dex auto rpc_fees = [this]() { t_get_trade_fee_request req{.coin = this->m_current_orderbook_ticker_base}; auto answer = ::mm2::api::rpc_get_trade_fee(std::move(req)); - this->m_trade_fees_registry.insert_or_assign(this->m_current_orderbook_ticker_base, answer); + if (answer.rpc_result_code == 200) + { + this->m_trade_fees_registry.insert_or_assign(this->m_current_orderbook_ticker_base, answer); + } if (not m_current_orderbook_ticker_rel.empty()) { t_get_trade_fee_request req_rel{.coin = this->m_current_orderbook_ticker_rel}; auto answer_rel = ::mm2::api::rpc_get_trade_fee(std::move(req_rel)); - this->m_trade_fees_registry.insert_or_assign(this->m_current_orderbook_ticker_rel, answer_rel); + if (answer_rel.rpc_result_code == 200) + { + this->m_trade_fees_registry.insert_or_assign(this->m_current_orderbook_ticker_rel, answer_rel); + } } }; @@ -818,8 +819,8 @@ namespace atomic_dex t_tx_state state{ .state = answer.result.value().sync_status.state, .current_block = answer.result.value().current_block, - .transactions_left = 0, - .blocks_left = 0}; + .blocks_left = 0, + .transactions_left = 0}; if (answer.result.value().sync_status.additional_info.has_value()) { @@ -886,11 +887,14 @@ namespace atomic_dex } } - spawn([this]() { - // loguru::set_thread_name("r_book thread"); - process_fees(); - fetch_current_orderbook_thread(); - }); + if (this->m_mm2_running) + { + spawn([this]() { + // loguru::set_thread_name("r_book thread"); + process_fees(); + fetch_current_orderbook_thread(); + }); + } } void @@ -935,8 +939,8 @@ namespace atomic_dex bool mm2::do_i_have_enough_funds(const std::string& ticker, const t_float_50& amount) const { - auto funds = get_balance(ticker); - return funds > amount; + t_float_50 funds = get_balance(ticker); + return funds >= amount; } std::string @@ -951,6 +955,17 @@ namespace atomic_dex return m_balance_informations.at(ticker).address; } + ::mm2::api::my_orders_answer + mm2::get_raw_orders(t_mm2_ec& ec) const noexcept + { + if (m_orders_registry.find("result") == m_orders_registry.cend()) + { + ec = dextop_error::order_not_available_yet; + return {}; + } + return m_orders_registry.at("result"); + } + ::mm2::api::my_orders_answer mm2::get_orders(const std::string& ticker, t_mm2_ec& ec) const noexcept { @@ -1063,7 +1078,7 @@ namespace atomic_dex ec = not info.is_claimable ? dextop_error::ticker_is_not_claimable : dextop_error::claim_not_enough_funds; return {}; } - t_withdraw_request req{.max = true, .coin = ticker, .amount = "0", .to = m_balance_informations.at(ticker).address}; + t_withdraw_request req{.coin = ticker, .to = m_balance_informations.at(ticker).address, .amount = "0", .max = true}; auto answer = ::mm2::api::rpc_withdraw(std::move(req)); return answer; } @@ -1128,4 +1143,9 @@ namespace atomic_dex return m_trade_fees_registry.find(ticker) != m_trade_fees_registry.cend() ? m_trade_fees_registry.at(ticker) : t_get_trade_fee_answer{}; } + bool + mm2::is_orderbook_thread_active() const noexcept + { + return this->m_orderbook_thread_active.load(); + } } // namespace atomic_dex diff --git a/src/atomic.dex.mm2.hpp b/src/atomic.dex.mm2.hpp index 062e9ebd40..a3ddc49505 100644 --- a/src/atomic.dex.mm2.hpp +++ b/src/atomic.dex.mm2.hpp @@ -50,9 +50,9 @@ namespace atomic_dex struct tx_state { std::string state; - std::size_t transactions_left; - std::size_t blocks_left; std::size_t current_block; + std::size_t blocks_left; + std::size_t transactions_left; }; using t_allocator = folly::AlignedSysAllocator()>>; @@ -202,7 +202,7 @@ namespace atomic_dex [[nodiscard]] static t_withdraw_answer withdraw(t_withdraw_request&& request, t_mm2_ec& ec) noexcept; //! Broadcast a raw transaction on the blockchain - [[nodiscard]] static t_broadcast_answer broadcast(t_broadcast_request&& request, t_mm2_ec& ec) noexcept; + [[nodiscard]] t_broadcast_answer broadcast(t_broadcast_request&& request, t_mm2_ec& ec) noexcept; //! Last 50 transactions maximum [[nodiscard]] t_transactions get_tx_history(const std::string& ticker, t_mm2_ec& ec) const; @@ -228,6 +228,9 @@ namespace atomic_dex //! Get coins that can be activated [[nodiscard]] t_coins get_enableable_coins() const noexcept; + //! Get all coins + [[nodiscard]] t_coins get_all_coins() const noexcept;; + //! Get Specific info about one coin [[nodiscard]] coin_config get_coin_info(const std::string& ticker) const; @@ -244,6 +247,7 @@ namespace atomic_dex //! Get orders [[nodiscard]] ::mm2::api::my_orders_answer get_orders(const std::string& ticker, t_mm2_ec& ec) const noexcept; + [[nodiscard]] ::mm2::api::my_orders_answer get_raw_orders(t_mm2_ec& ec) const noexcept; [[nodiscard]] std::vector<::mm2::api::my_orders_answer> get_orders(t_mm2_ec& ec) const noexcept; //! Get Swaps @@ -255,6 +259,8 @@ namespace atomic_dex //! Return true if we the balance of the `ticker` > amount, false otherwise. [[nodiscard]] bool do_i_have_enough_funds(const std::string& ticker, const t_float_50& amount) const; + + [[nodiscard]] bool is_orderbook_thread_active() const noexcept; }; } // namespace atomic_dex diff --git a/src/atomic.dex.pch.hpp b/src/atomic.dex.pch.hpp index 0d29622c84..ce159a40e5 100644 --- a/src/atomic.dex.pch.hpp +++ b/src/atomic.dex.pch.hpp @@ -117,8 +117,11 @@ namespace folly #include #include #include +#include #include #include +#include +#include //#include #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" diff --git a/src/atomic.dex.provider.cex.prices.api.cpp b/src/atomic.dex.provider.cex.prices.api.cpp new file mode 100644 index 0000000000..89f0fd1720 --- /dev/null +++ b/src/atomic.dex.provider.cex.prices.api.cpp @@ -0,0 +1,100 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.provider.cex.prices.api.hpp" + +namespace atomic_dex +{ + inline constexpr const char* g_cex_endpoint = "https://komodo.live:3333/"; +} + +//! Json Serialization / Deserialization functions +namespace atomic_dex +{ + void + from_json(const nlohmann::json& j, ohlc_answer_success& answer) + { + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + + answer.raw_result = j; + + for (const auto& [key, value]: j.items()) + { + ohlc_answer_success::t_ohlc_contents contents; + for (const auto& c_value: value.items()) + { + auto cur_value = c_value.value(); + + contents.emplace_back(ohlc_contents{ + .close_time_timestamp = cur_value.at("timestamp").get(), + .human_readeable_closing_time = "todo", + .open = std::to_string(cur_value.at("open").get()), + .high = std::to_string(cur_value.at("high").get()), + .low = std::to_string(cur_value.at("low").get()), + .close = std::to_string(cur_value.at("close").get()), + .volume = std::to_string(cur_value.at("volume").get()), + .quote_volume = std::to_string(cur_value.at("quote_volume").get())}); + } + answer.result.insert({key, contents}); + } + } + + void + from_json(const nlohmann::json& j, ohlc_answer& answer) + { + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + + if (j.contains("60")) + { + answer.result = ohlc_answer_success{}; + from_json(j, answer.result.value()); + } + } + + ohlc_answer + rpc_ohlc_get_data(ohlc_request&& request) + { + using namespace std::string_literals; + ohlc_answer answer; + + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + auto&& [base_id, quote_id] = request; + const auto url = g_cex_endpoint + "/api/v1/ohlc/"s + base_id + "-"s + quote_id; + const auto resp = RestClient::get(url); + + spdlog::info("url: {}", url); + spdlog::info("{} l{} resp code: {}", __FUNCTION__, __LINE__, resp.code); + + if (resp.code != 200) + { + answer.error = "error occured, code : "s + std::to_string(resp.code); + } + else + { + try + { + const auto json_answer = nlohmann::json::parse(resp.body); + from_json(json_answer, answer); + } + catch (const std::exception& error) + { + spdlog::warn("{}", error.what()); + answer.error = error.what(); + } + } + return answer; + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.provider.cex.prices.api.hpp b/src/atomic.dex.provider.cex.prices.api.hpp new file mode 100644 index 0000000000..545b9d7241 --- /dev/null +++ b/src/atomic.dex.provider.cex.prices.api.hpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.pch.hpp" + +namespace atomic_dex +{ + struct ohlc_request + { + std::string base_asset; + std::string quote_asset; + }; + + struct ohlc_contents + { + std::size_t close_time_timestamp; + std::string human_readeable_closing_time; + std::string open; + std::string high; + std::string low; + std::string close; + std::string volume; + std::string quote_volume; + }; + + struct ohlc_answer_success + { + using t_format = std::string; + using t_ohlc_contents = std::vector; + std::unordered_map result; + nlohmann::json raw_result; + }; + + struct ohlc_answer + { + std::optional result; + std::optional error; + }; + + void from_json(const nlohmann::json& j, ohlc_answer_success& answer); + void from_json(const nlohmann::json& j, ohlc_answer& answer); + + ohlc_answer rpc_ohlc_get_data(ohlc_request&& request); + +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.provider.cex.prices.api.tests.cpp b/src/atomic.dex.provider.cex.prices.api.tests.cpp new file mode 100644 index 0000000000..2f7b118180 --- /dev/null +++ b/src/atomic.dex.provider.cex.prices.api.tests.cpp @@ -0,0 +1,57 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.provider.cex.prices.api.hpp" +#include + +TEST_CASE("ohlc answer success") +{ + auto j = R"( + { + "60":[{"timestamp":1593341640,"open":0.0000677,"high":0.0000677,"low":0.0000677,"close":0.0000677,"volume":419.16,"quote_volume":0.028377132}], + "120":[{"timestamp":1593341640,"open":0.0000677,"high":0.0000677,"low":0.0000677,"close":0.0000677,"volume":419.16,"quote_volume":0.028377132}] + } + )"_json; + + + atomic_dex::ohlc_answer_success answer; + CHECK_NOTHROW(atomic_dex::from_json(j, answer)); +} + +TEST_CASE("ohlc answer") +{ + auto j = R"( + { + "60":[{"timestamp":1593341640,"open":0.0000677,"high":0.0000677,"low":0.0000677,"close":0.0000677,"volume":419.16,"quote_volume":0.028377132}], + "120":[{"timestamp":1593341640,"open":0.0000677,"high":0.0000677,"low":0.0000677,"close":0.0000677,"volume":419.16,"quote_volume":0.028377132}] + } + )"_json; + + + atomic_dex::ohlc_answer answer; + CHECK_NOTHROW(atomic_dex::from_json(j, answer)); +} + +TEST_CASE("rpc ohlc") +{ + // + atomic_dex::ohlc_request req{.base_asset = "kmd", .quote_asset = "btc"}; + auto answer = atomic_dex::rpc_ohlc_get_data(std::move(req)); + CHECK_FALSE(answer.error.has_value()); + CHECK(answer.result.has_value()); + CHECK_GT(answer.result.value().result.size(), 0); + CHECK(answer.result.value().raw_result.contains("60")); +} \ No newline at end of file diff --git a/src/atomic.dex.provider.cex.prices.cpp b/src/atomic.dex.provider.cex.prices.cpp new file mode 100644 index 0000000000..d7dc642034 --- /dev/null +++ b/src/atomic.dex.provider.cex.prices.cpp @@ -0,0 +1,244 @@ +/****************************************************************************** + * Copyright © 2013-2019 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. * + * * + ******************************************************************************/ + +//! Project headers +#include "atomic.dex.provider.cex.prices.hpp" +#include "atomic.dex.provider.cex.prices.api.hpp" +#include "atomic.threadpool.hpp" + +namespace atomic_dex +{ + cex_prices_provider::cex_prices_provider(entt::registry& registry, mm2& mm2_instance) : system(registry), m_mm2_instance(mm2_instance) + { + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + disable(); + dispatcher_.sink().connect<&cex_prices_provider::on_mm2_started>(*this); + dispatcher_.sink().connect<&cex_prices_provider::on_current_orderbook_ticker_pair_changed>(*this); + } + + void + cex_prices_provider::update() noexcept + { + } + + cex_prices_provider::~cex_prices_provider() noexcept + { + //! + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + + consume_pending_tasks(); + + m_provider_thread_timer.interrupt(); + + if (m_provider_ohlc_fetcher_thread.joinable()) + { + m_provider_ohlc_fetcher_thread.join(); + } + + dispatcher_.sink().disconnect<&cex_prices_provider::on_mm2_started>(*this); + dispatcher_.sink().disconnect<&cex_prices_provider::on_current_orderbook_ticker_pair_changed>(*this); + } + + void + cex_prices_provider::on_current_orderbook_ticker_pair_changed(const orderbook_refresh& evt) noexcept + { + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + + if (auto [normal, quoted] = is_pair_supported(evt.base, evt.rel); !normal && !quoted) + { + m_current_ohlc_data->clear(); + m_current_orderbook_ticker_pair.first = ""; + m_current_orderbook_ticker_pair.second = ""; + this->dispatcher_.trigger(); + return; + } + + m_current_ohlc_data = nlohmann::json::array(); + m_current_orderbook_ticker_pair = {boost::algorithm::to_lower_copy(evt.base), boost::algorithm::to_lower_copy(evt.rel)}; + auto [base, rel] = m_current_orderbook_ticker_pair; + spdlog::debug("new orderbook pair for cex provider [{} / {}]", base, rel); + m_pending_tasks.push(spawn([base = base, rel = rel, this]() { process_ohlc(base, rel, true); })); + } + + void + cex_prices_provider::on_mm2_started([[maybe_unused]] const mm2_started& evt) noexcept + { + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + + m_provider_ohlc_fetcher_thread = std::thread([this]() { + // + spdlog::info("cex prices provider thread started"); + + using namespace std::chrono_literals; + do + { + spdlog::info("fetching ohlc value"); + auto [base, rel] = m_current_orderbook_ticker_pair; + if (not base.empty() && not rel.empty() && m_mm2_instance.is_orderbook_thread_active()) + { + process_ohlc(base, rel); + } + else + { + spdlog::info("Nothing todo, sleeping"); + } + } while (not m_provider_thread_timer.wait_for(1min)); + }); + } + + bool + cex_prices_provider::process_ohlc(const std::string& base, const std::string& rel, bool is_a_reset) noexcept + { + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + if (auto [normal, quoted] = is_pair_supported(base, rel); normal || quoted) + { + spdlog::info("{} / {} is supported, processing", base, rel); + this->dispatcher_.trigger(is_a_reset); + atomic_dex::ohlc_request req{base, rel}; + if (quoted) + { + //! Quoted + req.base_asset = rel; + req.quote_asset = base; + } + auto answer = atomic_dex::rpc_ohlc_get_data(std::move(req)); + if (answer.result.has_value()) + { + m_current_ohlc_data = answer.result.value().raw_result; + this->updating_quote_and_average(quoted); + this->dispatcher_.trigger(is_a_reset); + return true; + } + spdlog::error("http error: {}", answer.error.value_or("dummy")); + return false; + } + + spdlog::warn("{} / {} not supported yet from the provider, skipping", base, rel); + return false; + } + + std::pair + cex_prices_provider::is_pair_supported(const std::string& base, const std::string& rel) const noexcept + { + std::pair result; + const std::string tickers = boost::algorithm::to_lower_copy(base) + "-" + boost::algorithm::to_lower_copy(rel); + result.first = std::any_of(begin(m_supported_pair), end(m_supported_pair), [tickers](auto&& cur_str) { return cur_str == tickers; }); + const std::string quoted_tickers = boost::algorithm::to_lower_copy(rel) + "-" + boost::algorithm::to_lower_copy(base); + result.second = std::any_of(begin(m_supported_pair), end(m_supported_pair), [quoted_tickers](auto&& cur_str) { return cur_str == quoted_tickers; }); + return result; + } + + bool + cex_prices_provider::is_ohlc_data_available() const noexcept + { + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + bool res = false; + res = !m_current_ohlc_data->empty(); + return res; + } + + nlohmann::json + cex_prices_provider::get_ohlc_data(const std::string& range) noexcept + { + nlohmann::json res = nlohmann::json::array(); + if (m_current_ohlc_data->contains(range)) + { + res = m_current_ohlc_data->at(range); + } + return res; + } + + void + cex_prices_provider::consume_pending_tasks() + { + while (not m_pending_tasks.empty()) + { + auto& fut_tasks = m_pending_tasks.front(); + if (fut_tasks.valid()) + { + fut_tasks.wait(); + } + m_pending_tasks.pop(); + } + } + + void + cex_prices_provider::reverse_ohlc_data(nlohmann::json& cur_range) noexcept + { + cur_range["open"] = 1 / cur_range.at("open").get(); + cur_range["high"] = 1 / cur_range.at("high").get(); + cur_range["low"] = 1 / cur_range.at("low").get(); + cur_range["close"] = 1 / cur_range.at("close").get(); + auto volume = cur_range.at("volume").get(); + cur_range["volume"] = cur_range["quote_volume"]; + cur_range["quote_volume"] = volume; + } + + nlohmann::json + cex_prices_provider::get_all_ohlc_data() noexcept + { + return *m_current_ohlc_data; + } + + void + cex_prices_provider::updating_quote_and_average(bool is_quoted) + { + nlohmann::json ohlc_data = *this->m_current_ohlc_data; + auto add_moving_average_functor = [](nlohmann::json& current_item, std::size_t idx, const std::vector& sums, std::size_t num) { + int real_num = num; + int first_idx = static_cast(idx) - real_num; + if (first_idx < 0) + { + first_idx = 0; + num = idx; + } + + if (num == 0) + { + current_item["ma_" + std::to_string(real_num)] = current_item.at("open").get(); + } + else + { + current_item["ma_" + std::to_string(real_num)] = static_cast(sums.at(idx) - sums.at(first_idx)) / num; + } + }; + + for (auto&& [key, value]: ohlc_data.items()) + { + std::size_t idx = 0; + std::vector sums; + for (auto&& cur_range: value) + { + if (is_quoted) + { + this->reverse_ohlc_data(cur_range); + } + if (idx == 0) + { + sums.emplace_back(cur_range.at("open").get()); + } + else + { + sums.emplace_back(cur_range.at("open").get() + sums[idx - 1]); + } + add_moving_average_functor(cur_range, idx, sums, 20); + add_moving_average_functor(cur_range, idx, sums, 50); + ++idx; + } + } + this->m_current_ohlc_data = ohlc_data; + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.provider.cex.prices.hpp b/src/atomic.dex.provider.cex.prices.hpp new file mode 100644 index 0000000000..e1ab7d814c --- /dev/null +++ b/src/atomic.dex.provider.cex.prices.hpp @@ -0,0 +1,100 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.pch.hpp" + +//! Project header +#include "atomic.dex.ma.series.data.hpp" +#include "atomic.dex.mm2.hpp" + +inline constexpr const std::size_t nb_pair_supported = 40_sz; + +namespace atomic_dex +{ + enum class moving_average + { + twenty, + fifty + }; + + namespace ag = antara::gaming; + + class cex_prices_provider final : public ag::ecs::pre_update_system + { + using t_supported_pairs = std::array; + using t_current_orderbook_ticker_pair = std::pair; + using t_synchronized_json = boost::synchronized_value; + + //! Private fields + mm2& m_mm2_instance; + + //! OHLC Related + t_current_orderbook_ticker_pair m_current_orderbook_ticker_pair{"", ""}; + t_supported_pairs m_supported_pair{"eth-btc", "eth-usdc", "btc-usdc", "btc-busd", "btc-tusd", "bat-btc", "bat-eth", "bat-usdc", + "bat-tusd", "bat-busd", "bch-btc", "bch-eth", "bch-usdc", "bch-tusd", "bch-busd", "dash-btc", + "dash-eth", "dgb-btc", "doge-btc", "kmd-btc", "kmd-eth", "ltc-btc", "ltc-eth", "ltc-usdc", + "ltc-tusd", "ltc-busd", "nav-btc", "nav-eth", "pax-btc", "pax-eth", "qtum-btc", "qtum-eth", + "rvn-btc", "xzc-btc", "xzc-eth", "zec-btc", "zec-eth", "zec-usdc", "zec-tusd", "zec-busd"}; + + //! OHLC Data + t_synchronized_json m_current_ohlc_data; + + //! Threads + std::queue> m_pending_tasks; + std::thread m_provider_ohlc_fetcher_thread; + timed_waiter m_provider_thread_timer; + + //! Private API + void reverse_ohlc_data(nlohmann::json& cur_range) noexcept; + + public: + //! Constructor + cex_prices_provider(entt::registry& registry, mm2& mm2_instance); + + //! Destructor + ~cex_prices_provider() noexcept final; + + //! Queue + void consume_pending_tasks(); + + // Override + void update() noexcept final; + + //! Process OHLC http rest request + bool process_ohlc(const std::string& base, const std::string& rel, bool is_a_reset = false) noexcept; + + //! Return true if json ohlc data is not empty, otherwise return false + bool is_ohlc_data_available() const noexcept; + + //! First boolean if it's supported as regular, second one if it's supported as quoted + std::pair is_pair_supported(const std::string& base, const std::string& rel) const noexcept; + + //! Event that occur when the mm2 process is launched correctly. + void on_mm2_started(const mm2_started& evt) noexcept; + + nlohmann::json get_ohlc_data(const std::string& range) noexcept; + + nlohmann::json get_all_ohlc_data() noexcept; + + //! Event that occur when the ticker pair is changed in the front end + void on_current_orderbook_ticker_pair_changed(const orderbook_refresh& evt) noexcept; + void updating_quote_and_average(bool quoted); + }; +} // namespace atomic_dex + +REFL_AUTO(type(atomic_dex::cex_prices_provider)) \ No newline at end of file diff --git a/src/atomic.dex.provider.cex.prices.tests.cpp b/src/atomic.dex.provider.cex.prices.tests.cpp new file mode 100644 index 0000000000..8cbbeae072 --- /dev/null +++ b/src/atomic.dex.provider.cex.prices.tests.cpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.events.hpp" +#include "atomic.dex.provider.cex.prices.hpp" +#include + +TEST_CASE("atomic dex cex prices provider constructor") +{ + entt::registry registry; + registry.set(); + atomic_dex::mm2 mm2(registry); + atomic_dex::cex_prices_provider provider(registry, mm2); +} + +SCENARIO("atomic dex cex price service functionnality") +{ + spdlog::set_level(spdlog::level::trace); + spdlog::set_pattern("[%H:%M:%S %z] [%L] [thr %t] %v"); + GIVEN("A basic environment") + { + entt::registry registry; + registry.set(); + antara::gaming::ecs::system_manager system_manager_{registry}; + auto& mm2_s = system_manager_.create_system(); + auto& cex_system = system_manager_.create_system(mm2_s); + + THEN("I start mm2") + { + registry.ctx().trigger(); + + AND_WHEN("i set the current orderbook pair to a valid supported pair (kmd-btc)") + { + registry.ctx().trigger("kmd", "btc"); + using namespace std::chrono_literals; + cex_system.consume_pending_tasks(); + + AND_THEN("i check if data are available, and if the port is not supported") + { + CHECK(cex_system.is_ohlc_data_available()); + CHECK(cex_system.is_pair_supported("kmd", "btc").first); + CHECK_FALSE(cex_system.get_ohlc_data("60").empty()); + } + } + } + } +} \ No newline at end of file diff --git a/src/atomic.dex.provider.coinpaprika.cpp b/src/atomic.dex.provider.coinpaprika.cpp index e51d769a47..2314eb2057 100644 --- a/src/atomic.dex.provider.coinpaprika.cpp +++ b/src/atomic.dex.provider.coinpaprika.cpp @@ -41,7 +41,7 @@ namespace void process_ticker_infos(const atomic_dex::coin_config& current_coin, atomic_dex::coinpaprika_provider::t_ticker_infos_registry& reg) { - const ticker_infos_request request{.ticker_currency_id = current_coin.coinpaprika_id, .ticker_quotes = {"USD", "EUR"}}; + const ticker_infos_request request{.ticker_currency_id = current_coin.coinpaprika_id, .ticker_quotes = {"USD", "EUR", "BTC"}}; auto answer = tickers_info(request); retry(answer, request, [&answer](const ticker_infos_request& request) { answer = tickers_info(request); }); @@ -55,7 +55,7 @@ namespace { return; } - const ticker_historical_request request{.ticker_currency_id = current_coin.coinpaprika_id}; + const ticker_historical_request request{.ticker_currency_id = current_coin.coinpaprika_id, .interval = "2h"}; auto answer = ticker_historical(request); retry(answer, request, [&answer](const ticker_historical_request& request) { answer = ticker_historical(request); }); if (answer.raw_result.find("error") == std::string::npos) @@ -92,13 +92,47 @@ namespace rate_providers.insert_or_assign(current_coin.ticker, "1.00"); } } + + std::string + compute_result(const std::string& amount, const std::string& price, const std::string& currency, atomic_dex::cfg& cfg) + { + const t_float_50 amount_f(amount); + const t_float_50 current_price_f(price); + const t_float_50 final_price = amount_f * current_price_f; + std::size_t default_precision = atomic_dex::is_this_currency_a_fiat(cfg, currency) ? 2 : 8; + std::string result; + + if (auto final_price_str = final_price.str(default_precision, std::ios_base::fixed); final_price_str == "0.00" && final_price > 0.00000000) + { + const auto retry = [&result, &final_price, &default_precision]() { result = final_price.str(default_precision, std::ios_base::fixed); }; + + result = final_price.str(default_precision); + if (result.find("e") != std::string::npos) + { + //! We have scientific notations lets get ride of that + do { + default_precision += 1; + retry(); + } while (t_float_50(result) <= 0); + } + } + else + { + result = final_price.str(default_precision, std::ios_base::fixed); + } + + boost::trim_right_if(result, boost::is_any_of("0")); + boost::trim_right_if(result, boost::is_any_of(".")); + return result; + } } // namespace namespace atomic_dex { namespace bm = boost::multiprecision; - coinpaprika_provider::coinpaprika_provider(entt::registry& registry, mm2& mm2_instance) : system(registry), m_mm2_instance(mm2_instance) + coinpaprika_provider::coinpaprika_provider(entt::registry& registry, mm2& mm2_instance, atomic_dex::cfg& cfg) : + system(registry), m_mm2_instance(mm2_instance), m_cfg(cfg) { spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); disable(); @@ -131,36 +165,37 @@ namespace atomic_dex spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); m_provider_rates_thread = std::thread([this]() { - // loguru::set_thread_name("paprika thread"); spdlog::info("paprika thread started"); using namespace std::chrono_literals; - do - { + do { spdlog::info("refreshing rate conversion from coinpaprika"); t_coins coins = m_mm2_instance.get_enabled_coins(); + std::vector> out_fut; + + out_fut.reserve(coins.size() * 6); for (auto&& current_coin: coins) { if (current_coin.coinpaprika_id == "test-coin") { continue; } - spawn([this, cur_coin = current_coin]() { process_ticker_infos(cur_coin, this->m_ticker_infos_registry); }); - spawn([this, cur_coin = current_coin]() { process_ticker_historical(cur_coin, this->m_ticker_historical_registry); }); - process_provider(current_coin, m_usd_rate_providers, "usd-us-dollars"); + out_fut.push_back(spawn([this, cur_coin = current_coin]() { process_ticker_infos(cur_coin, this->m_ticker_infos_registry); })); + out_fut.push_back(spawn([this, cur_coin = current_coin]() { process_ticker_historical(cur_coin, this->m_ticker_historical_registry); })); + out_fut.push_back(spawn([this, cur_coin = current_coin]() { process_provider(cur_coin, m_usd_rate_providers, "usd-us-dollars"); })); if (current_coin.ticker != "BTC") { - process_provider(current_coin, m_btc_rate_providers, "btc-bitcoin"); + out_fut.push_back(spawn([this, cur_coin = current_coin]() { process_provider(cur_coin, m_btc_rate_providers, "btc-bitcoin"); })); } if (current_coin.ticker != "KMD") { - process_provider(current_coin, m_kmd_rate_providers, "kmd-komodo"); + out_fut.push_back(spawn([this, cur_coin = current_coin]() { process_provider(cur_coin, m_kmd_rate_providers, "kmd-komodo"); })); } - process_provider(current_coin, m_eur_rate_providers, "eur-euro"); + out_fut.push_back(spawn([this, cur_coin = current_coin]() { process_provider(cur_coin, m_eur_rate_providers, "eur-euro"); })); } - + for (auto&& cur_fut: out_fut) { cur_fut.get(); } } while (not m_provider_thread_timer.wait_for(120s)); }); } @@ -196,15 +231,16 @@ namespace atomic_dex return "0.00"; } - const bm::cpp_dec_float_50 price_f(price); - const bm::cpp_dec_float_50 amount_f(amount); - const auto final_price = price_f * amount_f; - std::stringstream ss; - if (not skip_precision) { - ss.precision(2); + return compute_result(amount, price, fiat, this->m_cfg); } + + const t_float_50 price_f(price); + const t_float_50 amount_f(amount); + const t_float_50 final_price = price_f * amount_f; + std::stringstream ss; + ss << std::fixed << final_price; return ss.str() == "0" ? "0.00" : ss.str(); @@ -213,12 +249,12 @@ namespace atomic_dex std::string coinpaprika_provider::get_price_in_fiat_all(const std::string& fiat, std::error_code& ec) const noexcept { - t_coins coins = m_mm2_instance.get_enabled_coins(); + t_coins coins = m_mm2_instance.get_enabled_coins(); try { - bm::cpp_dec_float_50 final_price_f = 0; - std::string current_price = "0.00"; - std::stringstream ss; + t_float_50 final_price_f = 0; + std::string current_price = "0.00"; + std::stringstream ss; for (auto&& current_coin: coins) { @@ -238,14 +274,18 @@ namespace atomic_dex if (not current_price.empty()) { - const auto current_price_f = bm::cpp_dec_float_50(current_price); + const auto current_price_f = t_float_50(current_price); final_price_f += current_price_f; } } - ss.precision(2); + std::size_t default_precision = (fiat == "USD" || fiat == "EUR") ? 2 : 8; + ss.precision(default_precision); ss << std::fixed << final_price_f; - return ss.str(); + std::string result = ss.str(); + boost::trim_right_if(result, boost::is_any_of("0")); + boost::trim_right_if(result, boost::is_any_of(".")); + return result; } catch (const std::exception& error) { @@ -255,26 +295,39 @@ namespace atomic_dex } std::string - coinpaprika_provider::get_price_in_fiat_from_tx(const std::string& fiat, const std::string& ticker, const tx_infos& tx, std::error_code& ec) const noexcept + coinpaprika_provider::get_price_as_currency_from_tx( + const std::string& currency, const std::string& ticker, const tx_infos& tx, std::error_code& ec) const noexcept { if (m_mm2_instance.get_coin_info(ticker).coinpaprika_id == "test-coin") { return "0.00"; } const auto amount = tx.am_i_sender ? tx.my_balance_change.substr(1) : tx.my_balance_change; - const auto current_price = get_rate_conversion(fiat, ticker, ec); + const auto current_price = get_rate_conversion(currency, ticker, ec); if (ec) { return "0.00"; } - const bm::cpp_dec_float_50 amount_f(amount); - const bm::cpp_dec_float_50 current_price_f(current_price); - const auto final_price = amount_f * current_price_f; - std::stringstream ss; - ss.precision(2); - ss << std::fixed << final_price; - std::string final_price_str = ss.str(); - return final_price_str; + return compute_result(amount, current_price, currency, this->m_cfg); + } + + std::string + coinpaprika_provider::get_price_as_currency_from_amount( + const std::string& currency, const std::string& ticker, const std::string& amount, std::error_code& ec) const noexcept + { + if (m_mm2_instance.get_coin_info(ticker).coinpaprika_id == "test-coin") + { + return "0.00"; + } + + const auto current_price = get_rate_conversion(currency, ticker, ec); + + if (ec) + { + return "0.00"; + } + + return compute_result(amount, current_price, currency, this->m_cfg); } std::string @@ -330,9 +383,22 @@ namespace atomic_dex if (adjusted) { - std::stringstream ss; - ss << std::fixed << std::setprecision(2) << t_float_50(current_price); - current_price = ss.str(); + std::size_t default_precision = (fiat == "USD" || fiat == "EUR") ? 2 : 8; + + t_float_50 current_price_f(current_price); + if (fiat == "USD" || fiat == "EUR") + { + if (current_price_f < 1.0) + { + default_precision = 5; + } + } + //! Trick: If there conversion in a fixed representation is 0.00 then use a default precision to 2 without fixed ios flags + if (auto fixed_str = current_price_f.str(default_precision, std::ios_base::fixed); fixed_str == "0.00" && current_price_f > 0.00000000) + { + return current_price_f.str(default_precision); + } + return current_price_f.str(default_precision, std::ios::fixed); } return current_price; } @@ -346,18 +412,25 @@ namespace atomic_dex if (config.coinpaprika_id != "test-coin") { - process_provider(config, m_usd_rate_providers, "usd-us-dollars"); - process_provider(config, m_eur_rate_providers, "eur-euro"); - if (evt.ticker != "BTC") - { - process_provider(config, m_btc_rate_providers, "btc-bitcoin"); - } - if (evt.ticker != "KMD") - { - process_provider(config, m_kmd_rate_providers, "kmd-komodo"); - } - process_ticker_infos(config, m_ticker_infos_registry); - process_ticker_historical(config, m_ticker_historical_registry); + spawn([config, evt, this]() { + process_provider(config, m_usd_rate_providers, "usd-us-dollars"); + process_provider(config, m_eur_rate_providers, "eur-euro"); + if (evt.ticker != "BTC") + { + process_provider(config, m_btc_rate_providers, "btc-bitcoin"); + } + if (evt.ticker != "KMD") + { + process_provider(config, m_kmd_rate_providers, "kmd-komodo"); + } + process_ticker_infos(config, m_ticker_infos_registry); + process_ticker_historical(config, m_ticker_historical_registry); + this->dispatcher_.trigger(evt.ticker); + }); + } + else + { + this->dispatcher_.trigger(evt.ticker); } } @@ -391,4 +464,31 @@ namespace atomic_dex return m_ticker_historical_registry.find(ticker) != m_ticker_historical_registry.cend() ? m_ticker_historical_registry.at(ticker) : t_ticker_historical_answer{.answer = nlohmann::json::array()}; } + + std::string + coinpaprika_provider::get_cex_rates(const std::string& base, const std::string& rel, std::error_code& ec) const noexcept + { + std::string base_rate_str = get_rate_conversion("USD", base, ec, false); + if (ec) + { + return "0.00"; + } + std::string rel_rate_str = get_rate_conversion("USD", rel, ec, false); + if (ec) + { + return "0.00"; + } + if (base_rate_str == "0.00" || rel_rate_str == "0.00") + { + //! One of the rate is not available + return "0.00"; + } + t_float_50 base_rate_f(base_rate_str); + t_float_50 rel_rate_f(rel_rate_str); + t_float_50 result = base_rate_f / rel_rate_f; + std::string result_str = result.str(8, std::ios_base::fixed); + boost::trim_right_if(result_str, boost::is_any_of("0")); + boost::trim_right_if(result_str, boost::is_any_of(".")); + return result_str; + } } // namespace atomic_dex diff --git a/src/atomic.dex.provider.coinpaprika.hpp b/src/atomic.dex.provider.coinpaprika.hpp index 8841da511e..2737be25b1 100644 --- a/src/atomic.dex.provider.coinpaprika.hpp +++ b/src/atomic.dex.provider.coinpaprika.hpp @@ -22,6 +22,7 @@ //! Project Headers #include "atomic.dex.events.hpp" #include "atomic.dex.mm2.hpp" +#include "atomic.dex.cfg.hpp" #include "atomic.dex.provider.coinpaprika.api.hpp" namespace atomic_dex @@ -41,6 +42,7 @@ namespace atomic_dex //! Private fields mm2& m_mm2_instance; + atomic_dex::cfg& m_cfg; t_providers_registry m_usd_rate_providers{}; t_providers_registry m_eur_rate_providers{}; t_providers_registry m_btc_rate_providers{}; @@ -53,7 +55,7 @@ namespace atomic_dex public: //! Constructor - coinpaprika_provider(entt::registry& registry, mm2& mm2_instance); + coinpaprika_provider(entt::registry& registry, mm2& mm2_instance, atomic_dex::cfg& config); //! Destructor ~coinpaprika_provider() noexcept final; @@ -67,8 +69,14 @@ namespace atomic_dex //! Get the whole balance in the given fiat. std::string get_price_in_fiat_all(const std::string& fiat, std::error_code& ec) const noexcept; - //! Get the price in fiat from a transaction. - std::string get_price_in_fiat_from_tx(const std::string& fiat, const std::string& ticker, const tx_infos& tx, std::error_code& ec) const noexcept; + //! Get the price in currency from a transaction. + std::string get_price_as_currency_from_tx(const std::string& currency, const std::string& ticker, const tx_infos& tx, std::error_code& ec) const noexcept; + + //! Get the price in currency from a fees. + std::string get_price_as_currency_from_amount(const std::string& currency, const std::string& ticker, const std::string& amount, std::error_code& ec) const noexcept; + + //! Get the cex rates base / rel eg: VRSC / KMD = price of usd VRSC / KMD price USD + std::string get_cex_rates(const std::string& base, const std::string& rel, std::error_code& ec) const noexcept; //! Get the ticker informations. t_ticker_info_answer get_ticker_infos(const std::string& ticker) const noexcept; diff --git a/src/atomic.dex.qt.addressbook.contact.contents.hpp b/src/atomic.dex.qt.addressbook.contact.contents.hpp new file mode 100644 index 0000000000..343fae3cfb --- /dev/null +++ b/src/atomic.dex.qt.addressbook.contact.contents.hpp @@ -0,0 +1,28 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +namespace atomic_dex +{ + struct qt_contact_address_contents + { + QString type; + QString address; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.addressbook.model.cpp b/src/atomic.dex.qt.addressbook.model.cpp new file mode 100644 index 0000000000..04bbcba49c --- /dev/null +++ b/src/atomic.dex.qt.addressbook.model.cpp @@ -0,0 +1,183 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! PCH +#include "atomic.dex.pch.hpp" + +//! Project headers +#include "atomic.dex.qt.addressbook.model.hpp" + +//! Addressbook model +namespace atomic_dex +{ + addressbook_model::addressbook_model(atomic_dex::qt_wallet_manager& wallet_manager_, QObject* parent) noexcept : + QAbstractListModel(parent), m_wallet_manager(wallet_manager_), m_addressbook_proxy(new addressbook_proxy_model(this)) + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("addressbook model created"); + this->m_addressbook_proxy->setSourceModel(this); + this->m_addressbook_proxy->setSortRole(SubModelRole); + this->m_addressbook_proxy->setDynamicSortFilter(true); + this->m_addressbook_proxy->sort(0); + } + + addressbook_model::~addressbook_model() noexcept + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("addressbook model destroyed"); + } + + int + atomic_dex::addressbook_model::rowCount([[maybe_unused]] const QModelIndex& parent) const + { + return m_addressbook.count(); + } + + QVariant + atomic_dex::addressbook_model::data(const QModelIndex& index, int role) const + { + if (!hasIndex(index.row(), index.column(), index.parent())) + { + return {}; + } + + switch (static_cast(role)) + { + case SubModelRole: + return QVariant::fromValue(m_addressbook.at(index.row())); + default: + return {}; + } + } + + bool + atomic_dex::addressbook_model::insertRows(int position, int rows, [[maybe_unused]] const QModelIndex& parent) + { + spdlog::trace("(addressbook_model::insertRows) inserting {} elements at position {}", rows, position); + beginInsertRows(QModelIndex(), position, position + rows - 1); + + for (int row = 0; row < rows; ++row) { this->m_addressbook.insert(position, new contact_model(this->m_wallet_manager, this)); } + + endInsertRows(); + return true; + } + + bool + atomic_dex::addressbook_model::removeRows(int position, int rows, [[maybe_unused]] const QModelIndex& parent) + { + spdlog::trace("(addressbook_model::removeRows) removing {} elements at position {}", rows, position); + beginRemoveRows(QModelIndex(), position, position + rows - 1); + + for (int row = 0; row < rows; ++row) + { + contact_model* element = this->m_addressbook.at(position); + if ((element->rowCount(QModelIndex()) == 0 && not element->get_name().isEmpty()) || + (this->m_should_delete_contacts && not element->get_name().isEmpty())) + { + this->m_wallet_manager.delete_contact(element->get_name()); + this->m_wallet_manager.update_wallet_cfg(); + } + delete element; + this->m_addressbook.removeAt(position); + } + + endRemoveRows(); + return true; + } + + void + atomic_dex::addressbook_model::initializeFromCfg() + { + this->m_addressbook.clear(); + auto functor = [this](const atomic_dex::contact& cur_contact) { + int position = 0; + int rows = 1; + spdlog::trace("(addressbook_model::initializeFromCfg) inserting {} elements at position {}", rows, position); + + auto* contact_ptr = new contact_model(this->m_wallet_manager, nullptr); + contact_ptr->set_name(QString::fromStdString(cur_contact.name)); + for (auto&& contact_contents: cur_contact.contents) + { + contact_ptr->m_addresses.push_back(qt_contact_address_contents{ + .type = QString::fromStdString(contact_contents.type), .address = QString::fromStdString(contact_contents.address)}); + } + beginInsertRows(QModelIndex(), this->m_addressbook.count(), this->m_addressbook.count()); + + for (int row = 0; row < rows; ++row) + { + //! Insert contact + this->m_addressbook.push_back(contact_ptr); + } + + endInsertRows(); + }; + const wallet_cfg& cfg = this->m_wallet_manager.get_wallet_cfg(); + for (auto&& cur: cfg.address_book) { functor(cur); } + } + + void + atomic_dex::addressbook_model::add_contact_entry() + { + insertRow(0); + } + + void + atomic_dex::addressbook_model::remove_at(int position) + { + this->m_should_delete_contacts = true; + removeRow(position); + this->m_should_delete_contacts = false; + } + + QHash + atomic_dex::addressbook_model::roleNames() const + { + return { + {SubModelRole, "contacts"}, + }; + } + + addressbook_proxy_model* + addressbook_model::get_addressbook_proxy_mdl() const noexcept + { + return m_addressbook_proxy; + } + + void + addressbook_model::cleanup() + { + int nb_rows = this->rowCount(QModelIndex()) - 1; + for (int cur_contact_idx = 0; cur_contact_idx < nb_rows; ++cur_contact_idx) + { + QVariant value = this->data(index(cur_contact_idx, 0), SubModelRole); + QObject* obj = qvariant_cast(value); + contact_model* cur_contact = qobject_cast(obj); + for (int cur_idx = 0; cur_idx < cur_contact->rowCount(QModelIndex()); ++cur_idx) + { + if (cur_contact->data(index(cur_idx), contact_model::ContactRoles::AddressRole).toString().isEmpty()) + { + cur_contact->remove_at(cur_idx); + } + } + if (cur_contact->get_addresses().isEmpty()) + { + this->remove_at(cur_contact_idx); + } + } + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.addressbook.model.hpp b/src/atomic.dex.qt.addressbook.model.hpp new file mode 100644 index 0000000000..c60213da9e --- /dev/null +++ b/src/atomic.dex.qt.addressbook.model.hpp @@ -0,0 +1,67 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 //! QObject +#include + +//! Project include +#include "atomic.dex.qt.addressbook.contact.contents.hpp" +#include "atomic.dex.qt.addressbook.proxy.filter.model.hpp" +#include "atomic.dex.qt.contact.model.hpp" +#include "atomic.dex.qt.wallet.manager.hpp" + +namespace atomic_dex +{ + class addressbook_model final : public QAbstractListModel + { + Q_OBJECT + Q_PROPERTY(addressbook_proxy_model* addressbook_proxy_mdl READ get_addressbook_proxy_mdl NOTIFY addressbookProxyChanged); + Q_ENUMS(AddressBookRoles) + + public: + enum AddressBookRoles + { + SubModelRole = Qt::UserRole + 1, + }; + + public: + explicit addressbook_model(atomic_dex::qt_wallet_manager& wallet_manager_, QObject* parent = nullptr) noexcept; + ~addressbook_model() noexcept final; + [[nodiscard]] QVariant data(const QModelIndex& index, int role) const final; + [[nodiscard]] int rowCount(const QModelIndex& parent = QModelIndex()) const final; + bool insertRows(int position, int rows, const QModelIndex& parent) final; + bool removeRows(int position, int rows, const QModelIndex& parent = QModelIndex()) final; + void initializeFromCfg(); + Q_INVOKABLE void add_contact_entry(); + Q_INVOKABLE void remove_at(int position); + Q_INVOKABLE void cleanup(); + [[nodiscard]] QHash roleNames() const final; + + //! Properties + [[nodiscard]] addressbook_proxy_model* get_addressbook_proxy_mdl() const noexcept; + signals: + void addressbookProxyChanged(); + + private: + atomic_dex::qt_wallet_manager& m_wallet_manager; + atomic_dex::addressbook_proxy_model* m_addressbook_proxy; + QVector m_addressbook; + bool m_should_delete_contacts{false}; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.addressbook.proxy.filter.model.cpp b/src/atomic.dex.qt.addressbook.proxy.filter.model.cpp new file mode 100644 index 0000000000..876c353b31 --- /dev/null +++ b/src/atomic.dex.qt.addressbook.proxy.filter.model.cpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * Copyright © 2013-2019 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. * + * * + ******************************************************************************/ + +//! PCH +#include "atomic.dex.pch.hpp" + +//! Project Headers +#include "atomic.dex.qt.addressbook.model.hpp" +#include "atomic.dex.qt.addressbook.proxy.filter.model.hpp" + +namespace atomic_dex +{ + //! Constructor + addressbook_proxy_model::addressbook_proxy_model(QObject* parent) : QSortFilterProxyModel(parent) + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("addressbook proxy model created"); + } + + //! Destructor + addressbook_proxy_model::~addressbook_proxy_model() + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("addressbook proxy model destroyed"); + } + + //! Protected members override + bool + addressbook_proxy_model::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const + { + int role = this->sortRole(); + QVariant left_data = sourceModel()->data(source_left, role); + QVariant right_data = sourceModel()->data(source_right, role); + + switch (static_cast(role)) + { + case addressbook_model::SubModelRole: + QObject* left_obj = qvariant_cast(left_data); + contact_model* left_contact = qobject_cast(left_obj); + QObject* right_obj = qvariant_cast(right_data); + contact_model* right_contact = qobject_cast(right_obj); + spdlog::trace("comparing {} to {}", left_contact->get_name().toLower().toStdString(), right_contact->get_name().toLower().toStdString()); + return left_contact->get_name().toLower() < right_contact->get_name().toLower(); + } + return false; + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.addressbook.proxy.filter.model.hpp b/src/atomic.dex.qt.addressbook.proxy.filter.model.hpp new file mode 100644 index 0000000000..83d4dd851a --- /dev/null +++ b/src/atomic.dex.qt.addressbook.proxy.filter.model.hpp @@ -0,0 +1,37 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +namespace atomic_dex +{ + class addressbook_proxy_model final : public QSortFilterProxyModel + { + Q_OBJECT + public: + //! Constructor + addressbook_proxy_model(QObject* parent); + + //! Destructor + ~addressbook_proxy_model() final; + + protected: + //! Override member functions + [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const final; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.bindings.cpp b/src/atomic.dex.qt.bindings.cpp index 7e5f7bc8b9..f208a597ae 100644 --- a/src/atomic.dex.qt.bindings.cpp +++ b/src/atomic.dex.qt.bindings.cpp @@ -18,11 +18,7 @@ namespace atomic_dex { - qt_coin_config::qt_coin_config(QObject* parent) : QObject(parent) {} qt_send_answer::qt_send_answer(QObject* parent) : QObject(parent) {} - qt_transactions::qt_transactions(QObject* parent) : QObject(parent) {} qt_orderbook::qt_orderbook(QObject* parent) : QObject(parent) {} qt_ordercontent::qt_ordercontent(QObject* parent) : QObject(parent) {} - qt_my_order_contents::qt_my_order_contents(QObject* parent) : QObject(parent) {} - qt_my_orders::qt_my_orders(QObject* parent) : QObject(parent) {} } // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.bindings.hpp b/src/atomic.dex.qt.bindings.hpp index 73d094c4e5..50a3c70cfb 100644 --- a/src/atomic.dex.qt.bindings.hpp +++ b/src/atomic.dex.qt.bindings.hpp @@ -17,6 +17,8 @@ #pragma once #include +#include +#include #include //! PCH Headers @@ -28,120 +30,6 @@ namespace atomic_dex { - struct qt_my_order_contents : QObject - { - Q_OBJECT - public: - explicit qt_my_order_contents(QObject* parent = nullptr); - QString m_order_id; - QString m_date; - QString m_base; - QString m_rel; - bool m_cancellable; - QString m_base_amount; - QString m_rel_amount; - QString m_price; - int m_timestamp; - bool m_am_i_maker; - - Q_PROPERTY(QString price READ get_price CONSTANT MEMBER m_price) - Q_PROPERTY(QString date READ get_date CONSTANT MEMBER m_date) - Q_PROPERTY(int timestamp READ get_timestamp CONSTANT MEMBER m_timestamp) - Q_PROPERTY(QString base READ get_base CONSTANT MEMBER m_base) - Q_PROPERTY(QString rel READ get_rel CONSTANT MEMBER m_rel) - Q_PROPERTY(bool cancellable READ is_cancellable CONSTANT MEMBER m_cancellable) - Q_PROPERTY(bool am_i_maker READ is_maker CONSTANT MEMBER m_am_i_maker) - Q_PROPERTY(QString base_amount READ get_base_amount CONSTANT MEMBER m_base_amount) - Q_PROPERTY(QString rel_amount READ get_rel_amount CONSTANT MEMBER m_rel_amount) - Q_PROPERTY(QString uuid READ get_uuid CONSTANT MEMBER m_order_id) - - [[nodiscard]] int get_timestamp() const noexcept - { - return m_timestamp; - } - - [[nodiscard]] QString - get_uuid() const noexcept - { - return m_order_id; - } - - [[nodiscard]] bool - is_cancellable() const noexcept - { - return m_cancellable; - } - - [[nodiscard]] bool - is_maker() const noexcept - { - return m_am_i_maker; - } - - [[nodiscard]] QString - get_base_amount() const noexcept - { - return m_base_amount; - } - - [[nodiscard]] QString - get_rel_amount() const noexcept - { - return m_rel_amount; - } - - [[nodiscard]] QString - get_base() const noexcept - { - return m_base; - } - - [[nodiscard]] QString - get_rel() const noexcept - { - return m_rel; - } - - [[nodiscard]] QString - get_price() const noexcept - { - return m_price; - } - - [[nodiscard]] QString - get_date() const noexcept - { - return m_date; - } - }; - - using qt_my_order_contents_ptr = qt_my_order_contents*; - - struct qt_my_orders : QObject - { - Q_OBJECT - public: - explicit qt_my_orders(QObject* parent = nullptr); - QObjectList m_taker_orders; - QObjectList m_maker_orders; - - Q_PROPERTY(QList taker_orders READ get_taker_orders CONSTANT MEMBER m_taker_orders) - Q_PROPERTY(QList maker_orders READ get_maker_orders CONSTANT MEMBER m_maker_orders) - - [[nodiscard]] QObjectList get_taker_orders() const noexcept - { - return m_taker_orders; - } - - [[nodiscard]] QObjectList - get_maker_orders() const noexcept - { - return m_maker_orders; - } - }; - - using qt_my_orders_ptr = qt_my_orders*; - struct qt_ordercontent : QObject { Q_OBJECT @@ -275,221 +163,66 @@ namespace atomic_dex } }; - struct qt_transactions : QObject - { - Q_OBJECT - public: - explicit qt_transactions(QObject* parent = nullptr); - bool m_received; - QString m_amount; - QString m_amount_fiat; - QString m_date; - int m_timestamp; - QString m_tx_hash; - QString m_fees; - QStringList m_to; - QStringList m_from; - unsigned int m_blockheight; - unsigned int m_confirmations; - - Q_PROPERTY(bool received READ get_received CONSTANT MEMBER m_received) - Q_PROPERTY(unsigned int blockheight READ get_blockheight CONSTANT MEMBER m_blockheight) - Q_PROPERTY(unsigned int confirmations READ get_confirmations CONSTANT MEMBER m_confirmations) - Q_PROPERTY(int timestamp READ get_timestamp CONSTANT MEMBER m_timestamp) - Q_PROPERTY(QString amount READ get_amount CONSTANT MEMBER m_amount) - Q_PROPERTY(QString amount_fiat READ get_amount_fiat CONSTANT MEMBER m_amount_fiat) - Q_PROPERTY(QString date READ get_date CONSTANT MEMBER m_date) - Q_PROPERTY(QString tx_hash READ get_tx_hash CONSTANT MEMBER m_tx_hash) - Q_PROPERTY(QString fees READ get_fees CONSTANT MEMBER m_fees) - Q_PROPERTY(QStringList to READ get_to CONSTANT MEMBER m_to) - Q_PROPERTY(QStringList from READ get_from CONSTANT MEMBER m_from) - - [[nodiscard]] int get_timestamp() const noexcept - { - return m_timestamp; - } - - [[nodiscard]] unsigned int - get_confirmations() const noexcept - { - return m_confirmations; - } - - [[nodiscard]] unsigned int - get_blockheight() const noexcept - { - return m_blockheight; - } - - [[nodiscard]] QStringList - get_to() const noexcept - { - return m_to; - } - - [[nodiscard]] QStringList - get_from() const noexcept - { - return m_from; - } - - [[nodiscard]] QString - get_fees() const noexcept - { - return m_fees; - } - - [[nodiscard]] QString - get_tx_hash() const noexcept - { - return m_tx_hash; - } - - [[nodiscard]] bool - get_received() const noexcept - { - return m_received; - } - - [[nodiscard]] QString - get_amount() const noexcept - { - return m_amount; - } - - [[nodiscard]] QString - get_amount_fiat() const noexcept - { - return m_amount_fiat; - } - - [[nodiscard]] QString - get_date() const noexcept - { - return m_date; - } - }; - - struct qt_coin_config : QObject - { - Q_OBJECT - public: - explicit qt_coin_config(QObject* parent = nullptr); - QString m_ticker; - QString m_explorer_url; - QString m_name; - QString m_type; - bool m_active; - bool m_claimable; - QString m_minimal_balance_for_asking_rewards; - - Q_PROPERTY(bool active READ get_active CONSTANT MEMBER m_active) - Q_PROPERTY(bool is_claimable READ is_claimable_coin CONSTANT MEMBER m_claimable) - Q_PROPERTY(QString minimal_balance_for_asking_rewards READ get_minimal_balance_for_asking_rewards CONSTANT MEMBER m_minimal_balance_for_asking_rewards) - Q_PROPERTY(QString ticker READ get_ticker CONSTANT MEMBER m_ticker) - Q_PROPERTY(QString name READ get_name CONSTANT MEMBER m_name) - Q_PROPERTY(QString type READ get_type CONSTANT MEMBER m_type) - Q_PROPERTY(QString explorer_url READ get_explorer_url CONSTANT MEMBER m_explorer_url) - - [[nodiscard]] QString get_type() const noexcept - { - return m_type; - } - - [[nodiscard]] QString - get_explorer_url() const noexcept - { - return m_explorer_url; - } - - [[nodiscard]] bool - is_claimable_coin() const noexcept - { - return m_claimable; - } - - [[nodiscard]] bool - get_active() const noexcept - { - return m_active; - } - - [[nodiscard]] QString - get_minimal_balance_for_asking_rewards() const noexcept - { - return m_minimal_balance_for_asking_rewards; - } - - [[nodiscard]] QString - get_ticker() const noexcept - { - return m_ticker; - } - - [[nodiscard]] QString - get_name() const noexcept - { - return m_name; - } - }; - - inline QObject* - to_qt_binding(tx_infos&& tx, QObject* parent, QString fiat_amount) + inline nlohmann::json + to_qt_binding(tx_infos&& tx, std::string fiat_amount) { - auto* obj = new qt_transactions(parent); - obj->m_amount = QString::fromStdString(tx.my_balance_change); - obj->m_received = !tx.am_i_sender; + nlohmann::json obj{ + {"amount", tx.my_balance_change}, + {"received", !tx.am_i_sender}, + {"date", tx.date}, + {"timestamp", tx.timestamp}, + {"amount_fiat", std::move(fiat_amount)}, + {"tx_hash", tx.tx_hash}, + {"fees", tx.fees}, + {"from", tx.from}, + {"to", tx.to}, + {"blockheight", tx.block_height}, + {"confirmations", tx.confirmations}}; if (tx.am_i_sender) { - obj->m_amount = obj->m_amount.remove(0, 1); + obj["amount"] = tx.my_balance_change.substr(1); } - obj->m_date = QString::fromStdString(tx.date); - obj->m_timestamp = tx.timestamp; - obj->m_amount_fiat = std::move(fiat_amount); - obj->m_tx_hash = QString::fromStdString(tx.tx_hash); - obj->m_fees = QString::fromStdString(tx.fees); - obj->m_from.reserve(tx.from.size()); - for (auto&& cur: tx.from) { obj->m_from.append(QString::fromStdString(cur)); } - obj->m_to.reserve(tx.to.size()); - for (auto&& cur: tx.to) { obj->m_to.append(QString::fromStdString(cur)); } - obj->m_blockheight = tx.block_height; - obj->m_confirmations = tx.confirmations; return obj; } - QObjectList inline to_qt_binding( - t_transactions&& transactions, QObject* parent, coinpaprika_provider& paprika, const QString& fiat, const std::string& ticker) + QVariantList inline to_qt_binding(t_transactions&& transactions, coinpaprika_provider& paprika, const std::string& fiat, const std::string& ticker) { - QObjectList out; + QVariantList out; out.reserve(transactions.size()); + nlohmann::json j = nlohmann::json::array(); for (auto&& tx: transactions) { std::error_code ec; - auto fiat_amount = QString::fromStdString(paprika.get_price_in_fiat_from_tx(fiat.toStdString(), ticker, tx, ec)); - out.append(to_qt_binding(std::move(tx), parent, fiat_amount)); + auto fiat_amount = paprika.get_price_as_currency_from_tx(fiat, ticker, tx, ec); + j.push_back(to_qt_binding(std::move(tx), fiat_amount)); } + QJsonDocument q_json = QJsonDocument::fromJson(QString::fromStdString(j.dump()).toUtf8()); + out = q_json.array().toVariantList(); return out; } - inline QObject* - to_qt_binding(t_coins::value_type&& coin, QObject* parent) + inline nlohmann::json + to_qt_binding(t_coins::value_type&& coin) { - auto* obj = new qt_coin_config(parent); - obj->m_ticker = QString::fromStdString(coin.ticker); - obj->m_name = QString::fromStdString(coin.name); - obj->m_active = coin.active; - obj->m_type = QString::fromStdString(coin.type); - obj->m_claimable = coin.is_claimable; - obj->m_explorer_url = QString::fromStdString(coin.explorer_url[0]); - obj->m_minimal_balance_for_asking_rewards = QString::fromStdString(coin.minimal_claim_amount); - return obj; + nlohmann::json j{ + {"active", coin.active}, + {"is_claimable", coin.is_claimable}, + {"minimal_balance_for_asking_rewards", coin.minimal_claim_amount}, + {"ticker", coin.ticker}, + {"name", coin.name}, + {"type", coin.type}, + {"explorer_url", coin.explorer_url}}; + return j; } - QObjectList inline to_qt_binding(t_coins&& coins, QObject* parent) + QVariantList inline to_qt_binding(t_coins&& coins) { - QObjectList out; + QVariantList out; out.reserve(coins.size()); - for (auto&& coin: coins) { out.append(to_qt_binding(std::move(coin), parent)); } + nlohmann::json j = nlohmann::json::array(); + for (auto&& coin: coins) { j.push_back(to_qt_binding(std::move(coin))); } + QJsonDocument q_json = QJsonDocument::fromJson(QString::fromStdString(j.dump()).toUtf8()); + out = q_json.array().toVariantList(); return out; } @@ -539,42 +272,4 @@ namespace atomic_dex } return obj; } - - inline QObject* - to_qt_binding(t_my_orders_answer&& answer, QObject* parent) - { - auto* obj = new qt_my_orders(parent); - - auto functor = [&parent, &obj](auto&& collection, bool is_taker) { - for (auto&& cur_order: collection) - { - auto* qt_cur_order = new qt_my_order_contents(parent); - qt_cur_order->m_rel = QString::fromStdString(cur_order.second.rel); - qt_cur_order->m_base = QString::fromStdString(cur_order.second.base); - qt_cur_order->m_date = QString::fromStdString(cur_order.second.human_timestamp); - qt_cur_order->m_cancellable = cur_order.second.cancellable; - qt_cur_order->m_base_amount = QString::fromStdString(cur_order.second.base_amount); - qt_cur_order->m_rel_amount = QString::fromStdString(cur_order.second.rel_amount); - qt_cur_order->m_order_id = QString::fromStdString(cur_order.second.order_id); - qt_cur_order->m_am_i_maker = cur_order.second.order_type == "maker"; - qt_cur_order->m_timestamp = cur_order.second.timestamp; - - if (is_taker) - { - obj->m_taker_orders.append(qt_cur_order); - } - else - { - obj->m_maker_orders.append(qt_cur_order); - } - } - }; - - functor(answer.taker_orders, true); - functor(answer.maker_orders, false); - return obj; - } -} // namespace atomic_dex - -Q_DECLARE_METATYPE(atomic_dex::qt_my_orders_ptr); -Q_DECLARE_METATYPE(atomic_dex::qt_my_order_contents_ptr); \ No newline at end of file +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.candlestick.charts.model.cpp b/src/atomic.dex.qt.candlestick.charts.model.cpp new file mode 100644 index 0000000000..7d61b111fc --- /dev/null +++ b/src/atomic.dex.qt.candlestick.charts.model.cpp @@ -0,0 +1,486 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! Project Headers +#include "atomic.dex.provider.cex.prices.hpp" +#include "atomic.dex.qt.candlestick.charts.model.hpp" +#include "atomic.threadpool.hpp" + +namespace atomic_dex +{ + candlestick_charts_model::candlestick_charts_model(ag::ecs::system_manager& system_manager, QObject* parent) : + QAbstractTableModel(parent), m_system_manager(system_manager) + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("candlestick charts model created"); + } + + candlestick_charts_model::~candlestick_charts_model() noexcept + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("candlestick charts model destroyed"); + } + + int + candlestick_charts_model::rowCount([[maybe_unused]] const QModelIndex& parent) const + { + if (m_model_data.empty()) + { + return 0; + } + return m_model_data.size(); + } + + int + candlestick_charts_model::columnCount([[maybe_unused]] const QModelIndex& parent) const + { + return 12; + } + + QVariant + candlestick_charts_model::data([[maybe_unused]] const QModelIndex& index, [[maybe_unused]] int role) const + { + Q_UNUSED(role) + + if (!index.isValid()) + { + return QVariant(); + } + + if (index.row() >= rowCount() || index.row() < 0) + { + return QVariant(); + } + + switch (index.column()) + { + case 0: + return m_model_data.at(index.row()).at("timestamp").get() * 1000ull; + case 1: + return m_model_data.at(index.row()).at("open").get(); + case 2: + return m_model_data.at(index.row()).at("high").get(); + case 3: + return m_model_data.at(index.row()).at("low").get(); + case 4: + return m_model_data.at(index.row()).at("close").get(); + case 5: + return m_model_data.at(index.row()).at("volume").get(); + + // Volume Candlestick chart + case 6: // Open + return m_model_data.at(index.row()).at("close").get() >= m_model_data.at(index.row()).at("open").get() + ? 0 + : m_model_data.at(index.row()).at("volume").get(); + case 7: // High + return m_model_data.at(index.row()).at("volume").get(); + case 8: // Low + return 0; + case 9: // Close + return m_model_data.at(index.row()).at("close").get() >= m_model_data.at(index.row()).at("open").get() + ? m_model_data.at(index.row()).at("volume").get() + : 0; + + //! MA 20 + case 10: + return m_model_data.at(index.row()).contains("ma_20") ? m_model_data.at(index.row()).at("ma_20").get() + : m_model_data.at(index.row()).at("open").get(); + //! MA 50 + case 11: + return m_model_data.at(index.row()).contains("ma_50") ? m_model_data.at(index.row()).at("ma_50").get() + : m_model_data.at(index.row()).at("open").get(); + default: + return QVariant(); + } + } + + bool + candlestick_charts_model::common_reset_data() + { + auto& provider = this->m_system_manager.get_system(); + if (not provider.is_ohlc_data_available()) + { + this->clear_data(); + return false; + } + + this->beginResetModel(); + this->m_model_data = provider.get_ohlc_data(m_current_range); + this->endResetModel(); + this->set_is_currently_fetching(false); + + return true; + } + + void + candlestick_charts_model::init_data() + { + if (not common_reset_data()) + { + return; + } + emit chartFullyModelReset(); + + assert(not m_model_data.empty()); + double max_value = std::numeric_limits::min(); + double min_value = std::numeric_limits::max(); + + for (auto&& cur: m_model_data) + { + if (auto min_to_compare = cur.at("low").get(); min_value > min_to_compare) + { + min_value = min_to_compare; + } + if (auto max_to_compare = cur.at("high").get(); max_value < max_to_compare) + { + max_value = max_to_compare; + } + } + spdlog::trace("new range value IS: min: {} / max: {}", min_value, max_value); + this->set_global_min_value(min_value); + this->set_global_max_value(max_value); + + auto date_start = m_model_data[int(this->m_model_data.size() * 0.9)].at("timestamp").get(); + auto date_end = m_model_data.back().at("timestamp").get(); + auto date_diff = date_end - date_start; + auto date_init_margin = date_diff * 0.1; + date_start += date_init_margin; + date_end += date_init_margin; + QDateTime from, to; + from.setSecsSinceEpoch(date_start); + to.setSecsSinceEpoch(date_end); + this->set_series_from(from); + this->set_series_to(to); + this->set_min_value(m_visible_min_value); + this->set_max_value(m_visible_max_value); + + emit seriesSizeChanged(get_series_size()); + } + + void + candlestick_charts_model::update_data() + { + /*auto& provider = this->m_system_manager.get_system(); + nlohmann::json ohlc_data = provider.get_ohlc_data(m_current_range); + if (ohlc_data.back().at("timestamp").get() != m_model_data.back().at("timestamp").get()) + { + this->beginInsertRows(QModelIndex(), this->m_model_data.size(), this->m_model_data.size()); + m_model_data.push_back(ohlc_data.back()); + this->endInsertRows(); + emit seriesSizeChanged(get_series_size()); + }*/ + + if (not common_reset_data()) + { + return; + } + + emit seriesSizeChanged(get_series_size()); + } + + int + candlestick_charts_model::get_series_size() const noexcept + { + return rowCount(); + } + + void + candlestick_charts_model::clear_data() + { + //! If it's already empty dont reset the model + if (this->m_model_data.empty()) + { + spdlog::trace("already empty, skipping"); + return; + } + + spdlog::trace("clearing the chart candlestick model"); + beginResetModel(); + this->m_model_data.clear(); + this->set_min_value(0); + this->set_max_value(0); + endResetModel(); + emit seriesFromChanged(get_series_from()); + emit seriesToChanged(get_series_to()); + emit seriesSizeChanged(get_series_size()); + } + + QString + candlestick_charts_model::get_current_range() const noexcept + { + return QString::fromStdString(m_current_range); + } + + void + candlestick_charts_model::set_current_range(const QString& range) noexcept + { + this->m_current_range = range.toStdString(); + init_data(); + emit rangeChanged(); + } + + QDateTime + atomic_dex::candlestick_charts_model::get_series_to() const noexcept + { + if (this->m_model_data.empty()) + { + return QDateTime(); + } + return m_series_to; + } + + QDateTime + atomic_dex::candlestick_charts_model::get_series_from() const noexcept + { + if (this->m_model_data.empty()) + { + return QDateTime(); + } + return m_series_from; + } + + double + candlestick_charts_model::get_min_value() const noexcept + { + return m_min_value; + } + + double + candlestick_charts_model::get_max_value() const noexcept + { + return m_max_value; + } + + double + candlestick_charts_model::get_global_min_value() const noexcept + { + return m_global_min_value; + } + + double + candlestick_charts_model::get_global_max_value() const noexcept + { + return m_global_max_value; + } + + void + candlestick_charts_model::set_global_max_value(double value) + { + if (qFuzzyCompare(m_global_max_value, value)) + { + return; + } + + m_global_max_value = value; + emit globalMaxValueChanged(m_global_max_value); + } + + void + candlestick_charts_model::set_global_min_value(double value) + { + if (qFuzzyCompare(m_global_min_value, value)) + { + return; + } + + m_global_min_value = value; + emit globalMinValueChanged(m_global_min_value); + } + + void + candlestick_charts_model::set_max_value(double value) + { + if (qFuzzyCompare(m_max_value, value)) + { + return; + } + + m_max_value = value; + emit maxValueChanged(m_max_value); + } + + void + candlestick_charts_model::set_min_value(double value) + { + if (qFuzzyCompare(m_min_value, value)) + { + return; + } + + m_min_value = value; + emit minValueChanged(m_min_value); + } + + void + candlestick_charts_model::set_series_from(QDateTime value) + { + m_series_from = std::move(value); + emit seriesFromChanged(m_series_from); + } + + void + candlestick_charts_model::set_series_to(QDateTime value) + { + m_series_to = std::move(value); + emit seriesToChanged(m_series_to); + this->update_visible_range(); + } + + void + candlestick_charts_model::update_visible_range() + { + auto from_timestamp = get_series_from().toSecsSinceEpoch(); + auto first_timestamp = m_model_data[0].at("timestamp").get(); + if (from_timestamp < first_timestamp) + { + from_timestamp = first_timestamp; + } + + auto to_timestamp = get_series_to().toSecsSinceEpoch(); + auto last_timestamp = m_model_data[m_model_data.size() - 1].at("timestamp").get(); + if (to_timestamp > last_timestamp) + { + to_timestamp = last_timestamp; + } + + auto from_it = std::lower_bound(begin(m_model_data), end(m_model_data), from_timestamp, [](const nlohmann::json& current_json, int timestamp) { + int res = current_json.at("timestamp").get(); + return res < timestamp; + }); + + auto to_it = std::lower_bound(begin(m_model_data), end(m_model_data), to_timestamp, [](const nlohmann::json& current_json, int timestamp) { + int res = current_json.at("timestamp").get(); + return res < timestamp; + }); + + if (from_it != m_model_data.end() && to_it != m_model_data.end()) + { + auto min_value_j = std::min_element(from_it, to_it, [](nlohmann::json& left, nlohmann::json& right) { + auto left_value = left.at("low").get(); + auto right_value = right.at("low").get(); + return left_value < right_value; + }); + + auto max_value_j = std::max_element(from_it, to_it, [](nlohmann::json& left, nlohmann::json& right) { + auto left_value = left.at("high").get(); + auto right_value = right.at("high").get(); + return left_value < right_value; + }); + + auto max_volume_j = std::max_element(from_it, to_it, [](nlohmann::json& left, nlohmann::json& right) { + auto left_value = left.at("volume").get(); + auto right_value = right.at("volume").get(); + return left_value < right_value; + }); + + auto min_value = min_value_j->at("low").get(); + auto max_value = max_value_j->at("high").get(); + auto max_volume = max_volume_j->at("volume").get(); + this->set_visible_min_value(min_value); + this->set_visible_max_value(max_value); + this->set_visible_max_volume(max_volume); + } + } + + double + candlestick_charts_model::get_visible_max_volume() const noexcept + { + return m_visible_max_volume; + } + + double + candlestick_charts_model::get_visible_max_value() const noexcept + { + return m_visible_max_value; + } + + double + candlestick_charts_model::get_visible_min_value() const noexcept + { + return m_visible_min_value; + } + + void + candlestick_charts_model::set_visible_max_volume(double value) + { + if (qFuzzyCompare(m_visible_max_volume, value)) + { + return; + } + + m_visible_max_volume = value; + emit visibleMaxVolumeChanged(m_visible_max_volume); + } + + void + candlestick_charts_model::set_visible_max_value(double value) + { + if (qFuzzyCompare(m_visible_max_value, value)) + { + return; + } + + m_visible_max_value = value; + emit visibleMaxValueChanged(m_visible_max_value); + } + + void + candlestick_charts_model::set_visible_min_value(double value) + { + if (qFuzzyCompare(m_visible_min_value, value)) + { + return; + } + + m_visible_min_value = value; + emit visibleMinValueChanged(m_visible_min_value); + } + + bool + candlestick_charts_model::is_pair_supported() const noexcept + { + return m_current_pair_supported; + } + + void + candlestick_charts_model::set_is_pair_supported(bool is_support) + { + if (is_support != m_current_pair_supported) + { + m_current_pair_supported = is_support; + emit pairSupportedChanged(m_current_pair_supported); + } + } + + bool + candlestick_charts_model::is_currently_fetching() const noexcept + { + return m_currently_fetching; + } + + void + candlestick_charts_model::set_is_currently_fetching(bool is_fetching) + { + if (is_fetching != m_currently_fetching) + { + this->m_currently_fetching = is_fetching; + emit fetchingStatusChanged(m_currently_fetching); + } + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.candlestick.charts.model.hpp b/src/atomic.dex.qt.candlestick.charts.model.hpp new file mode 100644 index 0000000000..ae229ada69 --- /dev/null +++ b/src/atomic.dex.qt.candlestick.charts.model.hpp @@ -0,0 +1,125 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! PCH +#include "atomic.dex.pch.hpp" + +namespace atomic_dex +{ + class candlestick_charts_model final : public QAbstractTableModel + { + Q_OBJECT + Q_PROPERTY(int series_size READ get_series_size NOTIFY seriesSizeChanged) + Q_PROPERTY(QString current_range READ get_current_range WRITE set_current_range NOTIFY rangeChanged) + Q_PROPERTY(QDateTime series_from READ get_series_from WRITE set_series_from NOTIFY seriesFromChanged) + Q_PROPERTY(QDateTime series_to READ get_series_to WRITE set_series_to NOTIFY seriesToChanged) + Q_PROPERTY(double min_value READ get_min_value WRITE set_min_value NOTIFY minValueChanged) + Q_PROPERTY(double max_value READ get_max_value WRITE set_max_value NOTIFY maxValueChanged) + Q_PROPERTY(double global_max_value READ get_global_max_value NOTIFY globalMaxValueChanged) + Q_PROPERTY(double global_min_value READ get_global_min_value NOTIFY globalMinValueChanged) + Q_PROPERTY(double visible_min_value READ get_visible_min_value WRITE set_visible_min_value NOTIFY visibleMinValueChanged) + Q_PROPERTY(double visible_max_value READ get_visible_max_value WRITE set_visible_max_value NOTIFY visibleMaxValueChanged) + Q_PROPERTY(double visible_max_volume READ get_visible_max_volume WRITE set_visible_max_volume NOTIFY visibleMaxVolumeChanged) + Q_PROPERTY(bool is_current_pair_supported READ is_pair_supported WRITE set_is_pair_supported NOTIFY pairSupportedChanged) + Q_PROPERTY(bool is_fetching READ is_currently_fetching WRITE set_is_currently_fetching NOTIFY fetchingStatusChanged) + + public: + candlestick_charts_model(ag::ecs::system_manager& system_manager, QObject* parent = nullptr); + ~candlestick_charts_model() noexcept final; + + [[nodiscard]] int rowCount(const QModelIndex& parent = QModelIndex()) const final; + [[nodiscard]] int columnCount(const QModelIndex& parent) const final; + [[nodiscard]] QVariant data(const QModelIndex& index, int role) const final; + + //! Public API + void init_data(); + void update_data(); + void clear_data(); + + //! Property + [[nodiscard]] bool is_pair_supported() const noexcept; + void set_is_pair_supported(bool is_support); + [[nodiscard]] bool is_currently_fetching() const noexcept;; + void set_is_currently_fetching(bool is_fetching);; + [[nodiscard]] int get_series_size() const noexcept; + [[nodiscard]] QDateTime get_series_from() const noexcept; + [[nodiscard]] QDateTime get_series_to() const noexcept; + [[nodiscard]] double get_min_value() const noexcept; + [[nodiscard]] double get_max_value() const noexcept; + [[nodiscard]] double get_visible_min_value() const noexcept; + [[nodiscard]] double get_visible_max_value() const noexcept; + [[nodiscard]] double get_visible_max_volume() const noexcept; + [[nodiscard]] double get_global_min_value() const noexcept; + [[nodiscard]] double get_global_max_value() const noexcept; + [[nodiscard]] QString get_current_range() const noexcept; + void set_current_range(const QString& range) noexcept; + void set_min_value(double value); + void set_max_value(double value); + void set_visible_min_value(double value); + void set_visible_max_value(double value); + void set_visible_max_volume(double value); + void set_series_from(QDateTime value); + void set_series_to(QDateTime value); + + signals: + void seriesSizeChanged(int value); + void seriesFromChanged(QDateTime date); + void seriesToChanged(QDateTime date); + void minValueChanged(double value); + void maxValueChanged(double value); + void visibleMinValueChanged(double value); + void visibleMaxValueChanged(double value); + void visibleMaxVolumeChanged(double value); + void globalMinValueChanged(double value); + void globalMaxValueChanged(double value); + void pairSupportedChanged(bool supported); + void fetchingStatusChanged(bool fetching_status); + void rangeChanged(); + void maTwentySeriesChanged(); + void maFiftySeriesChanged(); + void chartFullyModelReset(); + + private: + void set_global_min_value(double value); + void set_global_max_value(double value); + void update_visible_range(); + + bool common_reset_data(); + + ag::ecs::system_manager& m_system_manager; + + nlohmann::json m_model_data; + + std::string m_current_range{"3600"}; //! 1h + + bool m_current_pair_supported{false}; + bool m_currently_fetching{false}; + double m_visible_min_value{0}; + double m_visible_max_value{0}; + double m_visible_max_volume{0}; + double m_max_value{0}; + double m_min_value{0}; + double m_global_max_value{0}; + double m_global_min_value{0}; + QDateTime m_series_from; + QDateTime m_series_to; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.contact.model.cpp b/src/atomic.dex.qt.contact.model.cpp new file mode 100644 index 0000000000..d7abc60244 --- /dev/null +++ b/src/atomic.dex.qt.contact.model.cpp @@ -0,0 +1,190 @@ +/****************************************************************************** + * Copyright © 2013-2019 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. * + * * + ******************************************************************************/ + +//! QT +#include + +//! PCH +#include "atomic.dex.pch.hpp" + +//! Project +#include "atomic.dex.qt.contact.model.hpp" + +namespace atomic_dex +{ + contact_model::contact_model(atomic_dex::qt_wallet_manager& wallet_manager_, QObject* parent) noexcept : + QAbstractListModel(parent), m_wallet_manager(wallet_manager_) + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("contact model created"); + } + + contact_model::~contact_model() noexcept + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("contact model destroyed"); + } + + QString + atomic_dex::contact_model::get_name() const noexcept + { + return m_name; + } + + void + atomic_dex::contact_model::set_name(const QString& name) noexcept + { + if (name != m_name) + { + spdlog::trace("name {} changed to {}", m_name.toStdString(), name.toStdString()); + this->m_wallet_manager.update_or_insert_contact_name(m_name, name); + this->m_wallet_manager.update_wallet_cfg(); + m_name = name; + emit nameChanged(); + } + } + + QVariant + contact_model::data(const QModelIndex& index, int role = Qt::DisplayRole) const + { + if (!hasIndex(index.row(), index.column(), index.parent())) + { + return {}; + } + + const qt_contact_address_contents& item = m_addresses.at(index.row()); + switch (role) + { + case TypeRole: + return item.type; + case AddressRole: + return item.address; + default: + return {}; + } + } + + bool + atomic_dex::contact_model::setData(const QModelIndex& index, const QVariant& value, int role) + { + if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid()) + { + return false; + } + + qt_contact_address_contents& item = m_addresses[index.row()]; + switch (role) + { + case TypeRole: + if (value.toString() != item.type) + { + spdlog::trace("changing contact {} ticker {} to {}", this->m_name.toStdString(), item.type.toStdString(), value.toString().toStdString()); + this->m_wallet_manager.update_contact_ticker(this->m_name, item.type, value.toString()); + this->m_wallet_manager.update_wallet_cfg(); + item.type = value.toString(); + } + break; + case AddressRole: + if (value.toString() != item.address) + { + item.address = value.toString(); + spdlog::trace("changing contact {} ticker {} to address {}", this->m_name.toStdString(), item.type.toStdString(), item.address.toStdString()); + this->m_wallet_manager.update_contact_address(this->m_name, item.type, item.address); + this->m_wallet_manager.update_wallet_cfg(); + emit addressesChanged(); + } + break; + default: + return false; + } + + emit dataChanged(index, index, {role}); + return true; + } + + QVariantList + atomic_dex::contact_model::get_addresses() const noexcept + { + QVariantList out; + out.reserve(this->m_addresses.count()); + for (auto&& cur: this->m_addresses) + { + nlohmann::json j{{"type", cur.type.toStdString()}, {"address", cur.address.toStdString()}}; + QJsonDocument q_json = QJsonDocument::fromJson(QString::fromStdString(j.dump()).toUtf8()); + out.push_back(q_json.toVariant()); + } + return out; + } + + bool + atomic_dex::contact_model::insertRows(int position, int rows, [[maybe_unused]] const QModelIndex& parent) + { + spdlog::trace("(contact_model::insertRows) inserting {} elements at position {}", rows, position); + beginInsertRows(QModelIndex(), position, position + rows - 1); + + for (int row = 0; row < rows; ++row) { this->m_addresses.insert(position, qt_contact_address_contents{}); } + + endInsertRows(); + emit addressesChanged(); + return true; + } + + bool + atomic_dex::contact_model::removeRows(int position, int rows, [[maybe_unused]] const QModelIndex& parent) + { + spdlog::trace("(contact_model::removeRows) removing {} elements at position {}", rows, position); + beginRemoveRows(QModelIndex(), position, position + rows - 1); + + for (int row = 0; row < rows; ++row) + { + auto contact_contents = this->m_addresses.at(position); + this->m_wallet_manager.remove_address_entry(this->m_name, contact_contents.type); + this->m_wallet_manager.update_wallet_cfg(); + this->m_addresses.removeAt(position); + } + + endRemoveRows(); + emit addressesChanged(); + return true; + } + + void + atomic_dex::contact_model::add_address_content() + { + insertRow(0); + } + + void + atomic_dex::contact_model::remove_at(int position) + { + removeRow(position); + } + + int + contact_model::rowCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const + { + return m_addresses.size(); + } + + QHash + atomic_dex::contact_model::roleNames() const + { + return { + {TypeRole, "type"}, + {AddressRole, "address"}, + }; + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.contact.model.hpp b/src/atomic.dex.qt.contact.model.hpp new file mode 100644 index 0000000000..a8c10000a1 --- /dev/null +++ b/src/atomic.dex.qt.contact.model.hpp @@ -0,0 +1,71 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! QT +#include +#include +#include + +//! Project +#include "atomic.dex.qt.wallet.manager.hpp" + +namespace atomic_dex +{ + class contact_model final : public QAbstractListModel + { + Q_OBJECT + Q_PROPERTY(QString name READ get_name WRITE set_name NOTIFY nameChanged) + Q_PROPERTY(QList readonly_addresses READ get_addresses NOTIFY addressesChanged) + Q_ENUMS(ContactRoles) + public: + enum ContactRoles + { + TypeRole = Qt::UserRole + 1, + AddressRole + }; + + QString get_name() const noexcept; + + void set_name(const QString& name) noexcept; + + public: + explicit contact_model(atomic_dex::qt_wallet_manager& wallet_manager_, QObject* parent = nullptr) noexcept; + ~contact_model() noexcept final; + QVariant data(const QModelIndex& index, int role) const final; + int rowCount(const QModelIndex& parent) const final; + QHash roleNames() const final; + bool setData(const QModelIndex& index, const QVariant& value, int role) final; + bool insertRows(int position, int rows, const QModelIndex& parent) final; + bool removeRows(int position, int rows, const QModelIndex& parent) final; + QVariantList get_addresses() const noexcept; + Q_INVOKABLE void add_address_content(); + Q_INVOKABLE void remove_at(int position); + + signals: + void nameChanged(); + void addressesChanged(); + + public: + //! Contact stuff + QString m_name; + QVector m_addresses; + + private: + atomic_dex::qt_wallet_manager& m_wallet_manager; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.current.coin.infos.cpp b/src/atomic.dex.qt.current.coin.infos.cpp index b05b287cfa..5c858433f5 100644 --- a/src/atomic.dex.qt.current.coin.infos.cpp +++ b/src/atomic.dex.qt.current.coin.infos.cpp @@ -96,30 +96,36 @@ namespace atomic_dex this->selected_coin_fiat_amount = std::move(fiat_amount); emit fiat_amount_changed(); } - QObjectList + + QVariantList current_coin_info::get_transactions() const noexcept { return this->selected_coin_transactions; } void - current_coin_info::set_transactions(QObjectList transactions) noexcept + current_coin_info::set_transactions(QVariantList transactions) noexcept { this->selected_coin_transactions.clear(); this->selected_coin_transactions = std::move(transactions); emit transactionsChanged(); } + QString current_coin_info::get_address() const noexcept { + spdlog::trace("[{}]", selected_coin_address.toStdString()); return selected_coin_address; } void current_coin_info::set_address(QString address) noexcept { - this->selected_coin_address = std::move(address); - emit address_changed(); + if (address != this->selected_coin_address) + { + this->selected_coin_address = std::move(address); + emit address_changed(); + } } void @@ -210,7 +216,7 @@ namespace atomic_dex QString atomic_dex::current_coin_info::get_paprika_id() const noexcept { - return this->selected_coin_paprika_id; + return this->selected_coin_paprika_id; } void @@ -219,4 +225,43 @@ namespace atomic_dex this->selected_coin_paprika_id = std::move(paprika_id); emit coinpaprika_id_changed(); } + + QString + current_coin_info::get_price() const noexcept + { + return this->selected_coin_price; + } + + void + current_coin_info::set_price(QString price) noexcept + { + this->selected_coin_price = std::move(price); + emit price_changed(); + } + + QString + current_coin_info::get_change24h() const noexcept + { + return this->selected_coin_change24h; + } + + void + current_coin_info::set_change24h(QString change24h) noexcept + { + this->selected_coin_change24h = std::move(change24h); + emit change24h_changed(); + } + + QVariant + current_coin_info::get_trend_7d() const noexcept + { + return this->selected_coin_trend_7d; + } + + void + current_coin_info::set_trend_7d(QVariant trend_7d) noexcept + { + this->selected_coin_trend_7d = std::move(trend_7d); + emit trend_7d_changed(); + } } // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.current.coin.infos.hpp b/src/atomic.dex.qt.current.coin.infos.hpp index 1060595b79..d3f58f005c 100644 --- a/src/atomic.dex.qt.current.coin.infos.hpp +++ b/src/atomic.dex.qt.current.coin.infos.hpp @@ -17,9 +17,9 @@ #pragma once //! QT -#include //! QObject -#include //! QObjectList -#include //! QString +#include //! QObject +#include //! QString +#include //! QVariantList //! PCH #include "atomic.dex.pch.hpp" @@ -40,8 +40,11 @@ namespace atomic_dex Q_PROPERTY(QString address READ get_address WRITE set_address NOTIFY address_changed) Q_PROPERTY(QString fiat_amount READ get_fiat_amount WRITE set_fiat_amount NOTIFY fiat_amount_changed); Q_PROPERTY(QString explorer_url READ get_explorer_url WRITE set_explorer_url NOTIFY explorer_url_changed); - Q_PROPERTY(QList transactions READ get_transactions WRITE set_transactions NOTIFY transactionsChanged) + Q_PROPERTY(QList transactions READ get_transactions WRITE set_transactions NOTIFY transactionsChanged) Q_PROPERTY(QString tx_state READ get_tx_state WRITE set_tx_state NOTIFY tx_state_changed); + Q_PROPERTY(QString main_currency_balance READ get_price WRITE set_price NOTIFY price_changed); + Q_PROPERTY(QString change_24h READ get_change24h WRITE set_change24h NOTIFY change24h_changed); + Q_PROPERTY(QVariant trend_7d READ get_trend_7d WRITE set_trend_7d NOTIFY trend_7d_changed); Q_PROPERTY(unsigned int transactions_left READ get_txs_left WRITE set_txs_left NOTIFY txs_left_changed); Q_PROPERTY(unsigned int blocks_left READ get_blocks_left WRITE set_blocks_left NOTIFY blocks_left_changed); Q_PROPERTY(unsigned int tx_current_block READ get_tx_current_block WRITE set_tx_current_block NOTIFY tx_current_block_changed); @@ -60,8 +63,8 @@ namespace atomic_dex void set_txs_left(unsigned int txs) noexcept; [[nodiscard]] unsigned int get_blocks_left() const noexcept; void set_blocks_left(unsigned int blocks) noexcept; - [[nodiscard]] QObjectList get_transactions() const noexcept; - void set_transactions(QObjectList transactions) noexcept; + [[nodiscard]] QVariantList get_transactions() const noexcept; + void set_transactions(QVariantList transactions) noexcept; [[nodiscard]] QString get_ticker() const noexcept; void set_ticker(QString ticker) noexcept; [[nodiscard]] QString get_name() const noexcept; @@ -77,7 +80,13 @@ namespace atomic_dex [[nodiscard]] QString get_fiat_amount() const noexcept; void set_fiat_amount(QString fiat_amount) noexcept; [[nodiscard]] QString get_type() const noexcept; - void set_type(QString type) noexcept;; + void set_type(QString type) noexcept; + [[nodiscard]] QString get_price() const noexcept; + void set_price(QString price) noexcept; + [[nodiscard]] QString get_change24h() const noexcept; + void set_change24h(QString change24h) noexcept; + [[nodiscard]] QVariant get_trend_7d() const noexcept; + void set_trend_7d(QVariant trend_7d) noexcept; signals: void ticker_changed(); @@ -95,6 +104,9 @@ namespace atomic_dex void txs_left_changed(); void blocks_left_changed(); void coinpaprika_id_changed(); + void price_changed(); + void change24h_changed(); + void trend_7d_changed(); public: QString selected_coin_name; @@ -106,10 +118,13 @@ namespace atomic_dex QString selected_coin_fiat_amount{"0"}; QString selected_coin_url; QString selected_coin_state; + QString selected_coin_price; + QString selected_coin_change24h; + QVariant selected_coin_trend_7d; unsigned int selected_coin_block; unsigned int selected_coin_txs_left; unsigned int selected_coin_blocks_left; - QObjectList selected_coin_transactions; + QVariantList selected_coin_transactions; bool selected_coin_is_claimable; QString selected_coin_minimal_balance_for_asking_rewards{"0"}; entt::dispatcher& m_dispatcher; diff --git a/src/atomic.dex.qt.orders.data.hpp b/src/atomic.dex.qt.orders.data.hpp new file mode 100644 index 0000000000..29302dc897 --- /dev/null +++ b/src/atomic.dex.qt.orders.data.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +namespace atomic_dex +{ + struct order_data + { + //! eg: true / false + bool is_maker; + + //! eg: RICK + QString base_coin; + + //! eg: MORTY + QString rel_coin; + + //! eg: RICK/MORTY + QString ticker_pair; + + //! eg: 1 + QString base_amount; + + //! eg: 1 + QString rel_amount; + + //! eg: Taker order; + QString order_type; + + //! eg: 2020-07-2020 17:23:36.625 + QString human_date; + + //! eg: 1595406178 + unsigned long long unix_timestamp; + + //! eg: b741646a-5738-4012-b5b0-dcd1375affd1 + QString order_id; + + //! eg: Successful / On Going / Matched / Matching + QString order_status; + + QString maker_payment_id; + + QString taker_payment_id; + + //! eg: true / false + bool is_swap; + + //! eg: true / false + bool is_cancellable; + + //! eg: true / false + bool is_recoverable; + + //! Order error state + QString order_error_state; + + //! Order error message + QString order_error_message; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.orders.model.cpp b/src/atomic.dex.qt.orders.model.cpp new file mode 100644 index 0000000000..78207add25 --- /dev/null +++ b/src/atomic.dex.qt.orders.model.cpp @@ -0,0 +1,512 @@ +/****************************************************************************** + * Copyright © 2013-2019 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. * + * * + ******************************************************************************/ + +//! Project +#include "atomic.dex.qt.orders.model.hpp" +#include "atomic.dex.mm2.hpp" + +//! Utils +namespace +{ + template + void + update_value(int role, const TValue& value, const QModelIndex& idx, TModel& model) + { + if (value != model.data(idx, role)) + { + model.setData(idx, value, role); + } + } + + std::pair + extract_error(const ::mm2::api::swap_contents& contents) + { + for (auto&& cur_event: contents.events) + { + if (std::any_of(begin(contents.error_events), end(contents.error_events), [&cur_event](auto&& error_str) { + return cur_event.at("state").get() == error_str; + })) + { + //! It's an error + if (cur_event.contains("data") && cur_event.at("data").contains("error")) + { + return { + QString::fromStdString(cur_event.at("state").get()), + QString::fromStdString(cur_event.at("data").at("error").get())}; + } + } + } + return {}; + } +} // namespace + +namespace atomic_dex +{ + orders_model::orders_model(ag::ecs::system_manager& system_manager, QObject* parent) noexcept : + QAbstractListModel(parent), m_system_manager(system_manager), m_model_proxy(new orders_proxy_model(this)) + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("orders model created"); + + this->m_model_proxy->setSourceModel(this); + this->m_model_proxy->setDynamicSortFilter(true); + this->m_model_proxy->setSortRole(UnixTimestampRole); + this->m_model_proxy->setFilterRole(TickerPairRole); + this->m_model_proxy->sort(0, Qt::DescendingOrder); + } + + orders_model::~orders_model() noexcept + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("orders model destroyed"); + } + + int + orders_model::rowCount([[maybe_unused]] const QModelIndex& parent) const + { + return this->m_model_data.count(); + } + + + bool + orders_model::setData(const QModelIndex& index, const QVariant& value, int role) + { + if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid()) + { + return false; + } + + order_data& item = m_model_data[index.row()]; + switch (static_cast(role)) + { + case BaseCoinRole: + item.base_coin = value.toString(); + break; + case RelCoinRole: + item.rel_coin = value.toString(); + break; + case TickerPairRole: + item.ticker_pair = value.toString(); + break; + case BaseCoinAmountRole: + item.base_amount = value.toString(); + break; + case RelCoinAmountRole: + item.rel_amount = value.toString(); + break; + case OrderTypeRole: + item.order_type = value.toString(); + break; + case HumanDateRole: + item.human_date = value.toString(); + break; + case UnixTimestampRole: + item.unix_timestamp = value.toULongLong(); + break; + case OrderIdRole: + item.order_id = value.toString(); + break; + case OrderStatusRole: + item.order_status = value.toString(); + break; + case MakerPaymentIdRole: + item.maker_payment_id = value.toString(); + break; + case TakerPaymentIdRole: + item.taker_payment_id = value.toString(); + break; + case CancellableRole: + item.is_cancellable = value.toBool(); + break; + case IsMakerRole: + item.is_maker = value.toBool(); + break; + case IsSwapRole: + item.is_swap = value.toBool(); + case IsRecoverableRole: + item.is_recoverable = value.toBool(); + case OrderErrorStateRole: + item.order_error_state = value.toString(); + case OrderErrorMessageRole: + item.order_error_message = value.toString(); + } + + emit dataChanged(index, index, {role}); + return true; + } + + QVariant + orders_model::data(const QModelIndex& index, int role) const + { + if (!hasIndex(index.row(), index.column(), index.parent())) + { + return {}; + } + + const order_data& item = m_model_data.at(index.row()); + switch (static_cast(role)) + { + case BaseCoinRole: + return item.base_coin; + case RelCoinRole: + return item.rel_coin; + case TickerPairRole: + return item.ticker_pair; + case BaseCoinAmountRole: + return item.base_amount; + case RelCoinAmountRole: + return item.rel_amount; + case OrderTypeRole: + return item.order_type; + case HumanDateRole: + return item.human_date; + case UnixTimestampRole: + return item.unix_timestamp; + case OrderIdRole: + return item.order_id; + case OrderStatusRole: + return item.order_status; + case MakerPaymentIdRole: + return item.maker_payment_id; + case TakerPaymentIdRole: + return item.taker_payment_id; + case CancellableRole: + return item.is_cancellable; + case IsMakerRole: + return item.is_maker; + case IsSwapRole: + return item.is_swap; + case IsRecoverableRole: + return item.is_recoverable; + case OrderErrorStateRole: + return item.order_error_state; + case OrderErrorMessageRole: + return item.order_error_message; + } + return {}; + } + + bool + orders_model::removeRows(int position, int rows, [[maybe_unused]] const QModelIndex& parent) + { + spdlog::trace("(orders_model::removeRows) removing {} elements at position {}", rows, position); + + beginRemoveRows(QModelIndex(), position, position + rows - 1); + for (int row = 0; row < rows; ++row) + { + this->m_model_data.removeAt(position); + emit lengthChanged(); + } + endRemoveRows(); + + return true; + } + + + QString + orders_model::determine_payment_id(const ::mm2::api::swap_contents& contents, bool am_i_maker, bool want_taker_id) noexcept + { + QString result = ""; + + if (contents.events.empty()) + { + return result; + } + + std::string search_name; + if (am_i_maker) + { + search_name = want_taker_id ? "TakerPaymentSpent" : "MakerPaymentSent"; + } + else + { + search_name = want_taker_id ? "TakerPaymentSent" : "MakerPaymentSpent"; + } + for (auto&& cur_event: contents.events) + { + if (cur_event.at("state").get() == search_name) + { + result = QString::fromStdString(cur_event.at("data").at("tx_hash").get()); + } + } + return result; + } + + QString + orders_model::determine_order_status_from_last_event(const ::mm2::api::swap_contents& contents) noexcept + { + if (contents.events.empty()) + { + return "matching"; + } + auto last_event = contents.events.back().at("state").get(); + if (last_event == "Started") + { + return "matched"; + } + + if (last_event == "TakerPaymentWaitRefundStarted" || last_event == "MakerPaymentWaitRefundStarted") + { + return "refunding"; + } + + QString status = "ongoing"; + + if (last_event == "Finished") + { + status = "successful"; + //! Find error or not + for (auto&& cur_event: contents.events) + { + if (cur_event.contains("data") && cur_event.at("data").contains("error") && + std::any_of(begin(contents.error_events), end(contents.error_events), [&cur_event](auto&& error_str) { + return cur_event.at("state").get() == error_str; + })) + { + status = "failed"; + } + } + } + + return status; + } + + void + orders_model::initialize_swap(const ::mm2::api::swap_contents& contents) noexcept + { + spdlog::trace("inserting in model order id {}", contents.uuid); + beginInsertRows(QModelIndex(), this->m_model_data.count(), this->m_model_data.count()); + bool is_maker = boost::algorithm::to_lower_copy(contents.type) == "maker"; + order_data data{ + .is_maker = is_maker, + .base_coin = is_maker ? QString::fromStdString(contents.maker_coin) : QString::fromStdString(contents.taker_coin), + .rel_coin = is_maker ? QString::fromStdString(contents.taker_coin) : QString::fromStdString(contents.maker_coin), + .base_amount = is_maker ? QString::fromStdString(contents.maker_amount) : QString::fromStdString(contents.taker_amount), + .rel_amount = is_maker ? QString::fromStdString(contents.taker_amount) : QString::fromStdString(contents.maker_amount), + .order_type = is_maker ? "maker" : "taker", + .human_date = not contents.events.empty() ? QString::fromStdString(contents.events.back().at("human_timestamp").get()) : "", + .unix_timestamp = not contents.events.empty() ? contents.events.back().at("timestamp").get() : 0, + .order_id = QString::fromStdString(contents.uuid), + .order_status = determine_order_status_from_last_event(contents), + .maker_payment_id = determine_payment_id(contents, is_maker, false), + .taker_payment_id = determine_payment_id(contents, is_maker, true), + .is_swap = true, + .is_cancellable = false, + .is_recoverable = contents.funds_recoverable}; + data.ticker_pair = data.base_coin + "/" + data.rel_coin; + if (data.order_status == "failed") + { + auto error = extract_error(contents); + data.order_error_state = error.first; + data.order_error_message = error.second; + } + if (this->m_swaps_id_registry.find(contents.uuid) == m_swaps_id_registry.end()) { + this->m_swaps_id_registry.emplace(contents.uuid); + } + this->m_model_data.push_back(std::move(data)); + endInsertRows(); + emit lengthChanged(); + } + + void + orders_model::update_swap(const ::mm2::api::swap_contents& contents) noexcept + { + if (const auto res = this->match(index(0, 0), OrderIdRole, QString::fromStdString(contents.uuid)); not res.isEmpty()) + { + const QModelIndex& idx = res.at(0); + bool is_maker = boost::algorithm::to_lower_copy(contents.type) == "maker"; + update_value(OrdersRoles::IsRecoverableRole, contents.funds_recoverable, idx, *this); + update_value(OrdersRoles::OrderStatusRole, determine_order_status_from_last_event(contents), idx, *this); + update_value( + OrdersRoles::UnixTimestampRole, not contents.events.empty() ? contents.events.back().at("timestamp").get() : 0, idx, *this); + update_value( + OrdersRoles::HumanDateRole, + not contents.events.empty() ? QString::fromStdString(contents.events.back().at("human_timestamp").get()) : "", idx, *this); + update_value(OrdersRoles::MakerPaymentIdRole, determine_payment_id(contents, is_maker, false), idx, *this); + update_value(OrdersRoles::TakerPaymentIdRole, determine_payment_id(contents, is_maker, true), idx, *this); + auto [state, msg] = extract_error(contents); + update_value(OrdersRoles::OrderErrorStateRole, state, idx, *this); + update_value(OrdersRoles::OrderErrorMessageRole, msg, idx, *this); + emit lengthChanged(); + } else { + bool is_maker = boost::algorithm::to_lower_copy(contents.type) == "maker"; + spdlog::error("swap with id {} and ticker: {}, not found in the model, cannot update, forcing an initialization instead", contents.uuid, is_maker ? contents.maker_coin : contents.taker_coin); + initialize_swap(contents); + } + } + + void + orders_model::initialize_order(const ::mm2::api::my_order_contents& contents) noexcept + { + spdlog::trace("inserting in model order id {}", contents.order_id); + beginInsertRows(QModelIndex(), this->m_model_data.count(), this->m_model_data.count()); + order_data data{ + .is_maker = contents.order_type == "maker", + .base_coin = QString::fromStdString(contents.base), + .rel_coin = QString::fromStdString(contents.rel), + .base_amount = QString::fromStdString(contents.base_amount), + .rel_amount = QString::fromStdString(contents.rel_amount), + .order_type = QString::fromStdString(contents.order_type), + .human_date = QString::fromStdString(contents.human_timestamp), + .unix_timestamp = static_cast(contents.timestamp), + .order_id = QString::fromStdString(contents.order_id), + .order_status = "matching", + .is_swap = false, + .is_cancellable = contents.cancellable, + .is_recoverable = false}; + data.ticker_pair = data.base_coin + "/" + data.rel_coin; + this->m_orders_id_registry.emplace(contents.order_id); + this->m_model_data.push_back(std::move(data)); + endInsertRows(); + emit lengthChanged(); + } + + + void + orders_model::update_existing_order(const ::mm2::api::my_order_contents& contents) noexcept + { + if (const auto res = this->match(index(0, 0), OrderIdRole, QString::fromStdString(contents.order_id)); not res.isEmpty()) + { + const QModelIndex& idx = res.at(0); + update_value(OrdersRoles::CancellableRole, contents.cancellable, idx, *this); + update_value(OrdersRoles::IsMakerRole, contents.order_type == "maker", idx, *this); + update_value(OrdersRoles::OrderTypeRole, QString::fromStdString(contents.order_type), idx, *this); + emit lengthChanged(); + } + } + + void + orders_model::refresh_or_insert_orders() noexcept + { + const auto& mm2_system = this->m_system_manager.get_system(); + std::error_code ec; + const auto orders = mm2_system.get_raw_orders(ec); + + if (!ec) + { + auto functor_process_orders = [this](auto&& orders) { + for (auto&& [key, value]: orders) + { + if (this->m_orders_id_registry.find(value.order_id) != this->m_orders_id_registry.end()) + { + //! Find update needed + this->update_existing_order(value); + } + else + { + //! Not found, insert and initialize. + this->initialize_order(value); + } + } + }; + + functor_process_orders(orders.maker_orders); + functor_process_orders(orders.taker_orders); + + //! Check for cleaning orders that are not present anymore + std::unordered_set to_remove; + for (auto&& id: this->m_orders_id_registry) + { + //! Check if the current id from the model registry is present in the orders collection + + //! Check in maker_orders + bool res = std::none_of(begin(orders.maker_orders), end(orders.maker_orders), [id](auto&& contents) { return contents.second.order_id == id; }); + + //! And compute with taker orders + res &= std::none_of(begin(orders.taker_orders), end(orders.taker_orders), [id](auto&& contents) { return contents.second.order_id == id; }); + if (res) + { + //! If it's the case retrieve the index of the row that match this id + auto res_list = this->match(index(0, 0), OrderIdRole, QString::fromStdString(id)); + if (not res_list.empty()) + { + //! And then delete it + this->removeRow(res_list.at(0).row()); + to_remove.emplace(id); + } + } + } + std::unordered_set out; + std::set_difference(begin(m_orders_id_registry), end(m_orders_id_registry), begin(to_remove), end(to_remove), std::inserter(out, out.begin())); + m_orders_id_registry = out; + } + } + + void + orders_model::refresh_or_insert_swaps() noexcept + { + const auto& mm2_system = this->m_system_manager.get_system(); + const auto result = mm2_system.get_swaps(); + for (auto&& current_swap: result.swaps) + { + if (this->m_swaps_id_registry.find(current_swap.uuid) != this->m_swaps_id_registry.end()) + { + this->update_swap(current_swap); + } + else + { + this->initialize_swap(current_swap); + } + } + } + + QHash + orders_model::roleNames() const + { + return { + {BaseCoinRole, "base_coin"}, + {RelCoinRole, "rel_coin"}, + {TickerPairRole, "ticker_pair"}, + {BaseCoinAmountRole, "base_amount"}, + {RelCoinAmountRole, "rel_amount"}, + {OrderTypeRole, "type"}, + {IsMakerRole, "is_maker"}, + {HumanDateRole, "date"}, + {UnixTimestampRole, "timestamp"}, + {OrderIdRole, "order_id"}, + {OrderStatusRole, "order_status"}, + {MakerPaymentIdRole, "maker_payment_id"}, + {TakerPaymentIdRole, "taker_payment_id"}, + {IsSwapRole, "is_swap"}, + {CancellableRole, "cancellable"}, + {IsRecoverableRole, "recoverable"}, + {OrderErrorStateRole, "order_error_state"}, + {OrderErrorMessageRole, "order_error_message"}}; + } + + int + orders_model::get_length() const noexcept + { + return this->m_model_proxy->rowCount(QModelIndex()); + } + + orders_proxy_model* + orders_model::get_orders_proxy_mdl() const noexcept + { + return m_model_proxy; + } + + void + orders_model::clear_registry() noexcept + { + spdlog::trace("clearing orders"); + this->beginResetModel(); + this->m_swaps_id_registry.clear(); + this->m_orders_id_registry.clear(); + this->m_model_data.clear(); + this->endResetModel(); + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.orders.model.hpp b/src/atomic.dex.qt.orders.model.hpp new file mode 100644 index 0000000000..23ae16eec2 --- /dev/null +++ b/src/atomic.dex.qt.orders.model.hpp @@ -0,0 +1,104 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! QT +#include +#include + +//! PCH +#include "atomic.dex.pch.hpp" + +//! Project +#include "atomic.dex.mm2.api.hpp" +#include "atomic.dex.qt.orders.data.hpp" +#include "atomic.dex.qt.orders.proxy.model.hpp" + +namespace atomic_dex +{ + class orders_model final : public QAbstractListModel + { + Q_OBJECT + Q_PROPERTY(orders_proxy_model* orders_proxy_mdl READ get_orders_proxy_mdl NOTIFY ordersProxyChanged); + Q_PROPERTY(int length READ get_length NOTIFY lengthChanged); + Q_ENUMS(OrdersRoles) + public: + enum OrdersRoles + { + BaseCoinRole = Qt::UserRole + 1, + RelCoinRole, + TickerPairRole, + BaseCoinAmountRole, + RelCoinAmountRole, + OrderTypeRole, + IsMakerRole, + HumanDateRole, + UnixTimestampRole, + OrderIdRole, + OrderStatusRole, + MakerPaymentIdRole, + TakerPaymentIdRole, + IsSwapRole, + CancellableRole, + IsRecoverableRole, + OrderErrorStateRole, + OrderErrorMessageRole + }; + + + orders_model(ag::ecs::system_manager& system_manager, QObject* parent = nullptr) noexcept; + ~orders_model() noexcept final; + int rowCount(const QModelIndex& parent) const final; + QVariant data(const QModelIndex& index, int role) const final; + bool removeRows(int row, int count, const QModelIndex& parent) final; + QHash roleNames() const final; + bool setData(const QModelIndex& index, const QVariant& value, int role) final; + + //! Public api + void refresh_or_insert_orders() noexcept; + void refresh_or_insert_swaps() noexcept; + void clear_registry() noexcept; + + //! Properties + [[nodiscard]] int get_length() const noexcept; + [[nodiscard]] orders_proxy_model* get_orders_proxy_mdl() const noexcept; + signals: + void lengthChanged(); + void ordersProxyChanged(); + + private: + ag::ecs::system_manager& m_system_manager; + + using t_orders_datas = QVector; + using t_orders_id_registry = std::unordered_set; + using t_swaps_id_registry = std::unordered_set; + + t_orders_id_registry m_orders_id_registry; + t_swaps_id_registry m_swaps_id_registry; + t_orders_datas m_model_data; + + orders_proxy_model* m_model_proxy; + + //! Private api + void initialize_order(const ::mm2::api::my_order_contents& contents) noexcept; + void update_existing_order(const ::mm2::api::my_order_contents& contents) noexcept; + void initialize_swap(const ::mm2::api::swap_contents& contents) noexcept; + void update_swap(const ::mm2::api::swap_contents& contents) noexcept; + QString determine_order_status_from_last_event(const ::mm2::api::swap_contents& contents) noexcept; + QString determine_payment_id(const ::mm2::api::swap_contents& contents, bool am_i_maker, bool want_taker_id) noexcept; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.orders.proxy.model.cpp b/src/atomic.dex.qt.orders.proxy.model.cpp new file mode 100644 index 0000000000..88286a63cb --- /dev/null +++ b/src/atomic.dex.qt.orders.proxy.model.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! PCH +#include "atomic.dex.pch.hpp" + +//! Project +#include "atomic.dex.qt.orders.model.hpp" +#include "atomic.dex.qt.orders.proxy.model.hpp" + +namespace atomic_dex +{ + orders_proxy_model::orders_proxy_model(QObject* parent) : QSortFilterProxyModel(parent) + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("orders proxy model created"); + } + + orders_proxy_model::~orders_proxy_model() + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("orders proxy model destroyed"); + } + + bool + orders_proxy_model::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const + { + int role = this->sortRole(); + QVariant left_data = sourceModel()->data(source_left, role); + QVariant right_data = sourceModel()->data(source_right, role); + switch (static_cast(role)) + { + case orders_model::BaseCoinRole: + break; + case orders_model::RelCoinRole: + break; + case orders_model::TickerPairRole: + break; + case orders_model::BaseCoinAmountRole: + break; + case orders_model::RelCoinAmountRole: + break; + case orders_model::OrderTypeRole: + break; + case orders_model::IsMakerRole: + break; + case orders_model::HumanDateRole: + break; + case orders_model::UnixTimestampRole: + return left_data.toULongLong() < right_data.toULongLong(); + case orders_model::OrderIdRole: + break; + case orders_model::OrderStatusRole: + break; + case orders_model::MakerPaymentIdRole: + break; + case orders_model::TakerPaymentIdRole: + break; + case orders_model::IsSwapRole: + break; + case orders_model::CancellableRole: + break; + case orders_model::IsRecoverableRole: + break; + case orders_model::OrdersRoles::OrderErrorStateRole: + break; + case orders_model::OrdersRoles::OrderErrorMessageRole: + break; + } + return true; + } + + bool + orders_proxy_model::am_i_in_history() const noexcept + { + return m_is_history; + } + + void + orders_proxy_model::set_is_history(bool is_history) noexcept + { + if (is_history != this->m_is_history) + { + this->m_is_history = is_history; + emit isHistoryChanged(); + this->invalidateFilter(); + emit qobject_cast(this->sourceModel())->lengthChanged(); + } + } + + bool + orders_proxy_model::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const + { + QModelIndex idx = this->sourceModel()->index(source_row, 0, source_parent); + assert(this->sourceModel()->hasIndex(idx.row(), 0)); + auto data = this->sourceModel()->data(idx, orders_model::OrdersRoles::OrderStatusRole).toString(); + assert(not data.isEmpty()); + if (this->m_is_history) + { + if (data == "matching" || data == "ongoing" || data == "matched" || data == "refunding") + { + return false; + } + } + else + { + if (data == "successful" || data == "failed") + { + return false; + } + } + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.orders.proxy.model.hpp b/src/atomic.dex.qt.orders.proxy.model.hpp new file mode 100644 index 0000000000..6c71a636fa --- /dev/null +++ b/src/atomic.dex.qt.orders.proxy.model.hpp @@ -0,0 +1,50 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +namespace atomic_dex +{ + class orders_proxy_model final : public QSortFilterProxyModel + { + Q_OBJECT + Q_PROPERTY(bool is_history READ am_i_in_history WRITE set_is_history NOTIFY isHistoryChanged); + + public: + //! Constructor + orders_proxy_model(QObject* parent); + + //! Destructor + ~orders_proxy_model() final; + + [[nodiscard]] bool am_i_in_history() const noexcept; + + void set_is_history(bool is_history) noexcept; + + signals: + void isHistoryChanged(); + + protected: + //! Override member functions + [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const final; + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + + private: + bool m_is_history{false}; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.portfolio.data.hpp b/src/atomic.dex.qt.portfolio.data.hpp new file mode 100644 index 0000000000..efb7873954 --- /dev/null +++ b/src/atomic.dex.qt.portfolio.data.hpp @@ -0,0 +1,47 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +namespace atomic_dex +{ + struct portfolio_data + { + //! eg: BTC,ETH,KMD (constant) + const QString ticker; + + //! eg: Bitcoin + const QString name; + + //! eg: 1 + QString balance; + + //! eg: 18800 $ + QString main_currency_balance; + + //! eg: +2.4% + QString change_24h; + + //! eg: 9400 $ + QString main_currency_price_for_one_unit; + + //! Paprika data rates + QJsonArray trend_7d; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.portfolio.model.cpp b/src/atomic.dex.qt.portfolio.model.cpp new file mode 100644 index 0000000000..a2a378b9dc --- /dev/null +++ b/src/atomic.dex.qt.portfolio.model.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + * Copyright © 2013-2019 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. * + * * + ******************************************************************************/ + +//! Project Headers +#include "atomic.dex.qt.portfolio.model.hpp" +#include "atomic.dex.qt.utilities.hpp" +#include "atomic.threadpool.hpp" + +//! Utils +namespace +{ + void + update_value(atomic_dex::portfolio_model::PortfolioRoles role, const QString& value, const QModelIndex& idx, atomic_dex::portfolio_model& model) + { + if (value != model.data(idx, role).toString()) + { + model.setData(idx, value, role); + } + } +} // namespace + +namespace atomic_dex +{ + portfolio_model::portfolio_model(ag::ecs::system_manager& system_manager, atomic_dex::cfg& config, QObject* parent) noexcept : + QAbstractListModel(parent), m_system_manager(system_manager), m_config(config), m_model_proxy(new portfolio_proxy_model(this)) + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("portfolio model created"); + + this->m_model_proxy->setSourceModel(this); + this->m_model_proxy->setDynamicSortFilter(true); + this->m_model_proxy->sort_by_currency_balance(false); + this->m_model_proxy->setFilterRole(NameRole); + this->m_model_proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + } + + portfolio_model::~portfolio_model() noexcept + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("portfolio model destroyed"); + } + + void + atomic_dex::portfolio_model::initialize_portfolio(std::string ticker) + { + const auto& mm2_system = this->m_system_manager.get_system(); + const auto& paprika = this->m_system_manager.get_system(); + auto coin = mm2_system.get_coin_info(ticker); + + beginInsertRows(QModelIndex(), this->m_model_data.count(), this->m_model_data.count()); + std::error_code ec; + const QString change_24h = retrieve_change_24h(paprika, coin, m_config); + portfolio_data data{ + .ticker = QString::fromStdString(coin.ticker), + .name = QString::fromStdString(coin.name), + .balance = QString::fromStdString(mm2_system.my_balance(coin.ticker, ec)), + .main_currency_balance = QString::fromStdString(paprika.get_price_in_fiat(m_config.current_currency, coin.ticker, ec)), + .change_24h = change_24h, + .main_currency_price_for_one_unit = QString::fromStdString(paprika.get_rate_conversion(m_config.current_currency, coin.ticker, ec, true)), + .trend_7d = nlohmann_json_array_to_qt_json_array(paprika.get_ticker_historical(coin.ticker).answer)}; + spdlog::trace( + "inserting ticker {} with name {} balance {} main currency balance {}", coin.ticker, coin.name, data.balance.toStdString(), + data.main_currency_balance.toStdString()); + this->m_model_data.push_back(std::move(data)); + endInsertRows(); + emit lengthChanged(); + } + + void + portfolio_model::update_currency_values() + { + const auto& mm2_system = this->m_system_manager.get_system(); + const auto& paprika = this->m_system_manager.get_system(); + t_coins coins = mm2_system.get_enabled_coins(); + const std::string& currency = m_config.current_currency; + std::vector> pending_tasks; + for (auto&& coin: coins) + { + pending_tasks.push_back(spawn([coin, &paprika, &mm2_system, currency, this]() { + const std::string& ticker = coin.ticker; + if (const auto res = this->match(this->index(0, 0), TickerRole, QString::fromStdString(ticker)); not res.isEmpty()) + { + std::error_code ec; + const QModelIndex& idx = res.at(0); + const QString main_currency_balance_value = QString::fromStdString(paprika.get_price_in_fiat(currency, ticker, ec)); + update_value(MainCurrencyBalanceRole, main_currency_balance_value, idx, *this); + const QString currency_price_for_one_unit = QString::fromStdString(paprika.get_rate_conversion(currency, ticker, ec, true)); + update_value(MainCurrencyPriceForOneUnit, currency_price_for_one_unit, idx, *this); + QString change24_h = retrieve_change_24h(paprika, coin, m_config); + update_value(Change24H, change24_h, idx, *this); + const QString balance = QString::fromStdString(mm2_system.my_balance(coin.ticker, ec)); + update_value(BalanceRole, balance, idx, *this); + } + })); + } + for (auto&& cur_task: pending_tasks) { cur_task.wait(); } + } + + void + portfolio_model::update_balance_values(const std::string& ticker) noexcept + { + if (const auto res = this->match(this->index(0, 0), TickerRole, QString::fromStdString(ticker)); not res.isEmpty()) + { + const auto& mm2_system = this->m_system_manager.get_system(); + const auto& paprika = this->m_system_manager.get_system(); + std::error_code ec; + const std::string& currency = m_config.current_currency; + const QModelIndex& idx = res.at(0); + const QString balance = QString::fromStdString(mm2_system.my_balance(ticker, ec)); + update_value(BalanceRole, balance, idx, *this); + const QString main_currency_balance_value = QString::fromStdString(paprika.get_price_in_fiat(currency, ticker, ec)); + update_value(MainCurrencyBalanceRole, main_currency_balance_value, idx, *this); + const QString currency_price_for_one_unit = QString::fromStdString(paprika.get_rate_conversion(currency, ticker, ec, true)); + update_value(MainCurrencyPriceForOneUnit, currency_price_for_one_unit, idx, *this); + } + } + + QVariant + atomic_dex::portfolio_model::data(const QModelIndex& index, int role) const + { + if (!hasIndex(index.row(), index.column(), index.parent())) + { + return {}; + } + + const portfolio_data& item = m_model_data.at(index.row()); + switch (static_cast(role)) + { + case TickerRole: + return item.ticker; + case BalanceRole: + return item.balance; + case MainCurrencyBalanceRole: + return item.main_currency_balance; + case Change24H: + return item.change_24h; + case MainCurrencyPriceForOneUnit: + return item.main_currency_price_for_one_unit; + case NameRole: + return item.name; + case Trend7D: + return item.trend_7d; + } + return {}; + } + + bool + atomic_dex::portfolio_model::setData(const QModelIndex& index, const QVariant& value, int role) + { + if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid()) + { + return false; + } + + portfolio_data& item = m_model_data[index.row()]; + switch (static_cast(role)) + { + case BalanceRole: + item.balance = value.toString(); + break; + case MainCurrencyBalanceRole: + item.main_currency_balance = value.toString(); + break; + case Change24H: + item.change_24h = value.toString(); + break; + case MainCurrencyPriceForOneUnit: + item.main_currency_price_for_one_unit = value.toString(); + break; + case Trend7D: + item.trend_7d = value.toJsonArray(); + break; + default: + return false; + } + + emit dataChanged(index, index, {role}); + return true; + } + + bool + portfolio_model::removeRows(int position, int rows, [[maybe_unused]] const QModelIndex& parent) + { + spdlog::trace("(portfolio_model::removeRows) removing {} elements at position {}", rows, position); + + beginRemoveRows(QModelIndex(), position, position + rows - 1); + for (int row = 0; row < rows; ++row) + { + this->m_model_data.removeAt(position); + emit lengthChanged(); + } + endRemoveRows(); + + return true; + } + + void + portfolio_model::disable_coins(const QStringList& coins) + { + for (auto&& coin: coins) + { + auto res = this->match(this->index(0, 0), TickerRole, coin); + assert(not res.empty()); + this->removeRow(res.at(0).row()); + } + } + + int + atomic_dex::portfolio_model::rowCount([[maybe_unused]] const QModelIndex& parent) const + { + return this->m_model_data.count(); + } + + QHash + portfolio_model::roleNames() const + { + return {{TickerRole, "ticker"}, {NameRole, "name"}, + {BalanceRole, "balance"}, {MainCurrencyBalanceRole, "main_currency_balance"}, + {Change24H, "change_24h"}, {MainCurrencyPriceForOneUnit, "main_currency_price_for_one_unit"}, + {Trend7D, "trend_7d"}}; + } + + portfolio_proxy_model* + atomic_dex::portfolio_model::get_portfolio_proxy_mdl() const noexcept + { + return m_model_proxy; + } + + int + portfolio_model::get_length() const noexcept + { + return this->rowCount(QModelIndex()); + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.portfolio.model.hpp b/src/atomic.dex.qt.portfolio.model.hpp new file mode 100644 index 0000000000..d889fece03 --- /dev/null +++ b/src/atomic.dex.qt.portfolio.model.hpp @@ -0,0 +1,94 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! QT Headers +#include +#include +#include + +//! PCH Header +#include "atomic.dex.pch.hpp" + +//! Project headers +#include "atomic.dex.mm2.hpp" +#include "atomic.dex.provider.coinpaprika.hpp" +#include "atomic.dex.qt.portfolio.data.hpp" +#include "atomic.dex.qt.portfolio.proxy.filter.model.hpp" + +namespace atomic_dex +{ + class portfolio_model final : public QAbstractListModel + { + Q_OBJECT + Q_PROPERTY(portfolio_proxy_model* portfolio_proxy_mdl READ get_portfolio_proxy_mdl NOTIFY portfolioProxyChanged); + Q_PROPERTY(int length READ get_length NOTIFY lengthChanged); + Q_ENUMS(PortfolioRoles) + public: + enum PortfolioRoles + { + TickerRole = Qt::UserRole + 1, + NameRole, + BalanceRole, + MainCurrencyBalanceRole, + Change24H, + MainCurrencyPriceForOneUnit, + Trend7D + }; + + private: + //! Typedef + using t_portfolio_datas = QVector; + + public: + //! Constructor / Destructor + explicit portfolio_model(ag::ecs::system_manager& system_manager, atomic_dex::cfg& config, QObject* parent = nullptr) noexcept; + ~portfolio_model() noexcept final; + + //! Overrides + [[nodiscard]] QVariant data(const QModelIndex& index, int role) const final; + bool setData(const QModelIndex& index, const QVariant& value, int role) final; //< Will be used internally + [[nodiscard]] int rowCount(const QModelIndex& parent) const final; + [[nodiscard]] QHash roleNames() const final; + bool removeRows(int row, int count, const QModelIndex& parent) final; + + //! Public api + void initialize_portfolio(std::string ticker); + void update_currency_values(); + void update_balance_values(const std::string& ticker) noexcept; + void disable_coins(const QStringList& coins); + + //! Properties + [[nodiscard]] portfolio_proxy_model* get_portfolio_proxy_mdl() const noexcept; + [[nodiscard]] int get_length() const noexcept; + + signals: + void portfolioProxyChanged(); + void lengthChanged(); + + private: + //! From project + ag::ecs::system_manager& m_system_manager; + atomic_dex::cfg& m_config; + + //! Properties + portfolio_proxy_model* m_model_proxy; + //! Data holders + t_portfolio_datas m_model_data; + }; + +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.portfolio.proxy.filter.model.cpp b/src/atomic.dex.qt.portfolio.proxy.filter.model.cpp new file mode 100644 index 0000000000..048f948a2c --- /dev/null +++ b/src/atomic.dex.qt.portfolio.proxy.filter.model.cpp @@ -0,0 +1,95 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.qt.portfolio.proxy.filter.model.hpp" +#include "atomic.dex.qt.portfolio.model.hpp" + +namespace atomic_dex +{ + //! Constructor + portfolio_proxy_model::portfolio_proxy_model(QObject* parent) : QSortFilterProxyModel(parent) + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("portfolio proxy model created"); + } + + //! Destructor + portfolio_proxy_model::~portfolio_proxy_model() + { + spdlog::trace("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::trace("portfolio proxy model destroyed"); + } + + //! Public API + void + portfolio_proxy_model::sort_by_name(bool is_ascending) + { + this->setSortRole(atomic_dex::portfolio_model::NameRole); + this->sort(0, is_ascending ? Qt::AscendingOrder : Qt::DescendingOrder); + } + + void + portfolio_proxy_model::sort_by_currency_balance(bool is_ascending) + { + this->setSortRole(atomic_dex::portfolio_model::MainCurrencyBalanceRole); + this->sort(0, is_ascending ? Qt::AscendingOrder : Qt::DescendingOrder); + } + + void + portfolio_proxy_model::sort_by_change_last24h(bool is_ascending) + { + this->setSortRole(atomic_dex::portfolio_model::Change24H); + this->sort(0, is_ascending ? Qt::AscendingOrder : Qt::DescendingOrder); + } + + void + portfolio_proxy_model::sort_by_currency_unit(bool is_ascending) + { + this->setSortRole(atomic_dex::portfolio_model::MainCurrencyPriceForOneUnit); + this->sort(0, is_ascending ? Qt::AscendingOrder : Qt::DescendingOrder); + } + + //! Override member functions + bool + portfolio_proxy_model::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const + { + int role = this->sortRole(); + QVariant left_data = sourceModel()->data(source_left, role); + QVariant right_data = sourceModel()->data(source_right, role); + switch (static_cast(role)) + { + case atomic_dex::portfolio_model::TickerRole: + return left_data.toString() > right_data.toString(); + case atomic_dex::portfolio_model::BalanceRole: + return t_float_50(left_data.toString().toStdString()) < t_float_50(right_data.toString().toStdString()); + case atomic_dex::portfolio_model::MainCurrencyBalanceRole: + if (left_data.toFloat() == right_data.toFloat()) + { + left_data = sourceModel()->data(source_left, atomic_dex::portfolio_model::BalanceRole); + right_data = sourceModel()->data(source_right, atomic_dex::portfolio_model::BalanceRole); + } + return left_data.toFloat() < right_data.toFloat(); + case atomic_dex::portfolio_model::Change24H: + return left_data.toFloat() < right_data.toFloat(); + case atomic_dex::portfolio_model::MainCurrencyPriceForOneUnit: + return t_float_50(left_data.toString().toStdString()) < t_float_50(right_data.toString().toStdString()); + case atomic_dex::portfolio_model::NameRole: + return left_data.toString() < right_data.toString(); + case portfolio_model::Trend7D: + return false; + } + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.portfolio.proxy.filter.model.hpp b/src/atomic.dex.qt.portfolio.proxy.filter.model.hpp new file mode 100644 index 0000000000..689f079fa9 --- /dev/null +++ b/src/atomic.dex.qt.portfolio.proxy.filter.model.hpp @@ -0,0 +1,44 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +namespace atomic_dex +{ + class portfolio_proxy_model final : public QSortFilterProxyModel + { + Q_OBJECT + public: + //! Constructor + portfolio_proxy_model(QObject* parent); + + //! Destructor + ~portfolio_proxy_model() final; + + public: + //! API + Q_INVOKABLE void sort_by_name(bool is_ascending); + Q_INVOKABLE void sort_by_currency_balance(bool is_ascending); + Q_INVOKABLE void sort_by_change_last24h(bool is_ascending); + Q_INVOKABLE void sort_by_currency_unit(bool is_ascending); + + protected: + //! Override member functions + [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const final; + }; +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.utilities.cpp b/src/atomic.dex.qt.utilities.cpp new file mode 100644 index 0000000000..4173593061 --- /dev/null +++ b/src/atomic.dex.qt.utilities.cpp @@ -0,0 +1,54 @@ +/****************************************************************************** + * Copyright © 2013-2019 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. * + * * + ******************************************************************************/ + +//! QT Headers +#include + +//! Project headers +#include "atomic.dex.qt.utilities.hpp" + +namespace atomic_dex +{ + bool + am_i_able_to_reach_this_endpoint(const QString& endpoint) + { + return RestClient::get(endpoint.toStdString()).code == 200; + } + + QJsonArray + nlohmann_json_array_to_qt_json_array(const nlohmann::json& j) + { + QJsonArray out; + QJsonDocument q_json = QJsonDocument::fromJson(QString::fromStdString(j.dump()).toUtf8()); + out = q_json.array(); + return out; + } + + QString + retrieve_change_24h(const atomic_dex::coinpaprika_provider& paprika, const atomic_dex::coin_config& coin, const atomic_dex::cfg& config) + { + auto ticker_infos = paprika.get_ticker_infos(coin.ticker).answer; + QString change_24h = "0"; + if (not ticker_infos.empty() && ticker_infos.contains(config.current_currency)) + { + auto change_24h_str = + std::to_string(paprika.get_ticker_infos(coin.ticker).answer.at(config.current_currency).at("percent_change_24h").get()); + std::replace(begin(change_24h_str), end(change_24h_str), ',', '.'); + change_24h = QString::fromStdString(change_24h_str); + } + return change_24h; + } +} // namespace atomic_dex diff --git a/src/atomic.dex.qt.utilities.hpp b/src/atomic.dex.qt.utilities.hpp new file mode 100644 index 0000000000..47c1f8496b --- /dev/null +++ b/src/atomic.dex.qt.utilities.hpp @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! Project headers +#include "atomic.dex.coins.config.hpp" +#include "atomic.dex.pch.hpp" +#include "atomic.dex.provider.coinpaprika.hpp" +#include "atomic.dex.wallet.config.hpp" + +namespace atomic_dex +{ + bool am_i_able_to_reach_this_endpoint(const QString& endpoint); + QJsonArray nlohmann_json_array_to_qt_json_array(const nlohmann::json& j); + QString retrieve_change_24h(const atomic_dex::coinpaprika_provider& paprika, const atomic_dex::coin_config& coin, const atomic_dex::cfg& config); +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.utilities.tests.cpp b/src/atomic.dex.qt.utilities.tests.cpp new file mode 100644 index 0000000000..1a33f14b24 --- /dev/null +++ b/src/atomic.dex.qt.utilities.tests.cpp @@ -0,0 +1,24 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.qt.utilities.hpp" +#include + +TEST_CASE("simple ping") +{ + CHECK(atomic_dex::am_i_able_to_reach_this_endpoint("www.google.com")); + //WARN(atomic_dex::am_i_able_to_reach_this_endpoint("8.8.8.8")); +} \ No newline at end of file diff --git a/src/atomic.dex.qt.wallet.manager.cpp b/src/atomic.dex.qt.wallet.manager.cpp new file mode 100644 index 0000000000..17c346ee23 --- /dev/null +++ b/src/atomic.dex.qt.wallet.manager.cpp @@ -0,0 +1,316 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.qt.wallet.manager.hpp" + +namespace atomic_dex +{ + QString + qt_wallet_manager::get_wallet_default_name() const noexcept + { + return m_current_default_wallet; + } + + void + qt_wallet_manager::set_wallet_default_name(QString wallet_name) noexcept + { + using namespace std::string_literals; + if (wallet_name == "") + { + fs::remove(get_atomic_dex_config_folder() / "default.wallet"); + return; + } + if (not fs::exists(get_atomic_dex_config_folder() / "default.wallet"s)) + { + std::ofstream ofs((get_atomic_dex_config_folder() / "default.wallet"s).string()); + ofs << wallet_name.toStdString(); + } + else + { + std::ofstream ofs((get_atomic_dex_config_folder() / "default.wallet"s).string(), std::ios_base::out | std::ios_base::trunc); + ofs << wallet_name.toStdString(); + } + + this->m_current_default_wallet = std::move(wallet_name); + } + + bool + qt_wallet_manager::create(const QString& password, const QString& seed, const QString& wallet_name) + { + std::error_code ec; + auto key = atomic_dex::derive_password(password.toStdString(), ec); + if (ec) + { + spdlog::warn("{}", ec.message()); + if (ec == dextop_error::derive_password_failed) + { + return false; + } + } + else + { + using namespace std::string_literals; + const fs::path seed_path = get_atomic_dex_config_folder() / (wallet_name.toStdString() + ".seed"s); + const fs::path wallet_object_path = get_atomic_dex_export_folder() / (wallet_name.toStdString() + ".wallet.json"s); + const std::string wallet_cfg_file = std::string(atomic_dex::get_raw_version()) + "-coins"s + "."s + wallet_name.toStdString() + ".json"s; + const fs::path wallet_cfg_path = get_atomic_dex_config_folder() / wallet_cfg_file; + + + if (not fs::exists(wallet_cfg_path)) + { + const auto cfg_path = ag::core::assets_real_path() / "config"; + std::string filename = std::string(atomic_dex::get_raw_version()) + "-coins.json"; + fs::copy(cfg_path / filename, wallet_cfg_path); + } + + // Encrypt seed + atomic_dex::encrypt(seed_path, seed.toStdString().data(), key.data()); + // sodium_memzero(&seed, seed.size()); + sodium_memzero(key.data(), key.size()); + + std::ofstream ofs((get_atomic_dex_config_folder() / "default.wallet"s).string().c_str()); + ofs << wallet_name.toStdString(); + + set_wallet_default_name(wallet_name); + + std::ofstream wallet_object(wallet_object_path.string()); + nlohmann::json wallet_object_json; + + wallet_object_json["name"] = wallet_name.toStdString(); + wallet_object << wallet_object_json.dump(4); + wallet_object.close(); + + return true; + } + return false; + } + + QStringList + qt_wallet_manager::get_wallets() noexcept + { + QStringList out; + + for (auto&& p: fs::directory_iterator((get_atomic_dex_config_folder()))) + { + if (p.path().extension().string() == ".seed") + { + out.push_back(QString::fromStdString(p.path().stem().string())); + } + } + + return out; + } + + bool + qt_wallet_manager::is_there_a_default_wallet() noexcept + { + return fs::exists(get_atomic_dex_config_folder() / "default.wallet"); + } + + QString + qt_wallet_manager::get_default_wallet_name() noexcept + { + if (is_there_a_default_wallet()) + { + std::ifstream ifs((get_atomic_dex_config_folder() / "default.wallet").c_str()); + assert(ifs); + std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + return QString::fromStdString(str); + } + return "nonexistent"; + } + + bool + qt_wallet_manager::delete_wallet(const QString& wallet_name) noexcept + { + using namespace std::string_literals; + return fs::remove(get_atomic_dex_config_folder() / (wallet_name.toStdString() + ".seed"s)); + } + + bool + qt_wallet_manager::confirm_password(const QString& wallet_name, const QString& password) + { + std::error_code ec; + auto key = atomic_dex::derive_password(password.toStdString(), ec); + if (ec) + { + spdlog::debug("{}", ec.message()); + if (ec == dextop_error::derive_password_failed) + { + return false; + } + } + using namespace std::string_literals; + const fs::path seed_path = get_atomic_dex_config_folder() / (wallet_name.toStdString() + ".seed"s); + auto seed = atomic_dex::decrypt(seed_path, key.data(), ec); + if (ec == dextop_error::corrupted_file_or_wrong_password) + { + spdlog::warn("{}", ec.message()); + return false; + } + return true; + } + + bool + qt_wallet_manager::load_wallet_cfg(const std::string& wallet_name) + { + using namespace std::string_literals; + const fs::path wallet_object_path = get_atomic_dex_export_folder() / (wallet_name + ".wallet.json"s); + std::ifstream ifs(wallet_object_path.string()); + + if (not ifs.is_open()) + { + return false; + } + nlohmann::json j; + ifs >> j; + m_wallet_cfg = j; + return true; + } + + bool + qt_wallet_manager::update_wallet_cfg() noexcept + { + using namespace std::string_literals; + const fs::path wallet_object_path = get_atomic_dex_export_folder() / (m_wallet_cfg.name + ".wallet.json"s); + std::ofstream ofs(wallet_object_path.string(), std::ios::trunc); + if (not ofs.is_open()) + { + return false; + } + + nlohmann::json j; + atomic_dex::to_json(j, m_wallet_cfg); + ofs << j.dump(4); + return true; + } + + void + qt_wallet_manager::update_or_insert_contact_name(const QString& old_contact_name, const QString& contact_name) + { + std::string old_contact_name_str = old_contact_name.toStdString(); + std::string contact_name_str = contact_name.toStdString(); + + if (old_contact_name_str.empty() && !contact_name_str.empty()) + { + auto it = std::find_if(begin(m_wallet_cfg.address_book), end(m_wallet_cfg.address_book), [contact_name_str](auto&& cur_contact) { + return cur_contact.name == contact_name_str; + }); + if (it != m_wallet_cfg.address_book.end()) + { + //! Find this contact already exist do nothing + spdlog::trace("contact {} already exist, skipping", contact_name_str); + return; + } + m_wallet_cfg.address_book.push_back(contact{.name = std::move(contact_name_str)}); + } + else if (not old_contact_name_str.empty() && not contact_name_str.empty()) + { + auto it = std::find_if(begin(m_wallet_cfg.address_book), end(m_wallet_cfg.address_book), [old_contact_name_str](auto&& cur_contact) { + return cur_contact.name == old_contact_name_str; + }); + if (it != m_wallet_cfg.address_book.end()) + { + spdlog::trace("old contact {} found, changing the contact name to: {}", old_contact_name_str, contact_name_str); + it->name = contact_name_str; + } + } + } + + void + qt_wallet_manager::delete_contact(const QString& contact) + { + std::string contact_name_str = contact.toStdString(); + this->m_wallet_cfg.address_book.erase( + std::remove_if(begin(m_wallet_cfg.address_book), end(m_wallet_cfg.address_book), [contact_name_str](auto&& cur_contact) { + return contact_name_str == cur_contact.name; + })); + } + + void + qt_wallet_manager::update_contact_ticker(const QString& contact_name, const QString& old_ticker, const QString& new_ticker) + { + std::string contact_name_str = contact_name.toStdString(); + if (old_ticker.isEmpty() && not new_ticker.isEmpty()) + { + //! Add new ticker entry + auto it = std::find_if(begin(m_wallet_cfg.address_book), end(m_wallet_cfg.address_book), [contact_name_str](auto&& cur_contact) { + return cur_contact.name == contact_name_str; + }); + + if (it != m_wallet_cfg.address_book.end()) + { + spdlog::trace("add for contact {}, ticker {} entry", contact_name_str, new_ticker.toStdString()); + it->contents.emplace_back(contact_contents{.type = new_ticker.toStdString()}); + } + } + else + { + //! Update ticker entry + auto it = std::find_if(begin(m_wallet_cfg.address_book), end(m_wallet_cfg.address_book), [contact_name_str](auto&& cur_contact) { + return cur_contact.name == contact_name_str; + }); + + auto contents_it = std::find_if(begin(it->contents), end(it->contents), [&old_ticker](auto&& cur_contact_contents) { + return cur_contact_contents.type == old_ticker.toStdString(); + }); + + if (contents_it != it->contents.end()) + { + contents_it->type = new_ticker.toStdString(); + } + } + } + + void + qt_wallet_manager::update_contact_address(const QString& contact_name, const QString& ticker, const QString& address) + { + spdlog::trace("update contact {} with ticker {} with the new address {}", contact_name.toStdString(), ticker.toStdString(), address.toStdString()); + std::string contact_name_str = contact_name.toStdString(); + auto it = std::find_if(begin(m_wallet_cfg.address_book), end(m_wallet_cfg.address_book), [contact_name_str](auto&& cur_contact) { + return cur_contact.name == contact_name_str; + }); + auto contents_it = std::find_if( + begin(it->contents), end(it->contents), [&ticker](auto&& cur_contact_contents) { return cur_contact_contents.type == ticker.toStdString(); }); + if (contents_it != it->contents.end()) + { + contents_it->address = address.toStdString(); + } + } + + void + qt_wallet_manager::remove_address_entry(const QString& contact_name, const QString& ticker) + { + std::string contact_name_str = contact_name.toStdString(); + auto it = std::find_if(begin(m_wallet_cfg.address_book), end(m_wallet_cfg.address_book), [contact_name_str](auto&& cur_contact) { + return cur_contact.name == contact_name_str; + }); + it->contents.erase(std::remove_if( + begin(it->contents), end(it->contents), [&ticker](auto&& cur_contact_contents) { return cur_contact_contents.type == ticker.toStdString(); })); + } + + const wallet_cfg& + qt_wallet_manager::get_wallet_cfg() const noexcept + { + return m_wallet_cfg; + } + + const wallet_cfg& + qt_wallet_manager::get_wallet_cfg() noexcept + { + return m_wallet_cfg; + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.qt.wallet.manager.hpp b/src/atomic.dex.qt.wallet.manager.hpp index 01afd94d72..bb725c512a 100644 --- a/src/atomic.dex.qt.wallet.manager.hpp +++ b/src/atomic.dex.qt.wallet.manager.hpp @@ -19,35 +19,59 @@ //! QT Headers #include #include +#include +#include //! Project Headers +#include "atomic.dex.mm2.hpp" +#include "atomic.dex.qt.addressbook.contact.contents.hpp" #include "atomic.dex.security.hpp" #include "atomic.dex.version.hpp" +#include "atomic.dex.wallet.config.hpp" namespace atomic_dex { class qt_wallet_manager { public: + QString get_wallet_default_name() const noexcept; + + void set_wallet_default_name(QString wallet_name) noexcept; + template - bool inline login( - const QString& password, const QString& wallet_name, mm2& mm2_system, const QString& default_wallet_name, Functor&& login_if_success_functor); + bool login(const QString& password, const QString& wallet_name, mm2& mm2_system, Functor&& login_if_success_functor); + + bool create(const QString& password, const QString& seed, const QString& wallet_name); + + bool load_wallet_cfg(const std::string& wallet_name); + + static QStringList get_wallets() noexcept; + + static bool is_there_a_default_wallet() noexcept; - static inline QStringList get_wallets() noexcept; + static QString get_default_wallet_name() noexcept; - static inline bool is_there_a_default_wallet() noexcept; + static bool delete_wallet(const QString& wallet_name) noexcept; - static inline QString get_default_wallet_name() noexcept; + static bool confirm_password(const QString& wallet_name, const QString& password); - static inline bool delete_wallet(const QString& wallet_name) noexcept; + bool update_wallet_cfg() noexcept; - static inline bool confirm_password(const QString& wallet_name, const QString& password); + void update_contact_ticker(const QString& contact_name, const QString& old_ticker, const QString& new_ticker); + void update_contact_address(const QString& contact_name, const QString& ticker, const QString& address); + void update_or_insert_contact_name(const QString& old_contact_name, const QString& contact_name); + void remove_address_entry(const QString& contact_name, const QString& ticker); + void delete_contact(const QString& contact_name); + const wallet_cfg& get_wallet_cfg() const noexcept; + const wallet_cfg& get_wallet_cfg() noexcept; + private: + wallet_cfg m_wallet_cfg; + QString m_current_default_wallet{""}; }; template bool - qt_wallet_manager::login( - const QString& password, const QString& wallet_name, mm2& mm2_system, const QString& default_wallet, Functor&& login_if_success_functor) + qt_wallet_manager::login(const QString& password, const QString& wallet_name, mm2& mm2_system, Functor&& login_if_success_functor) { std::error_code ec; auto key = atomic_dex::derive_password(password.toStdString(), ec); @@ -83,75 +107,10 @@ namespace atomic_dex } login_if_success_functor(); - mm2_system.spawn_mm2_instance(default_wallet.toStdString(), seed); + load_wallet_cfg(get_default_wallet_name().toStdString()); + mm2_system.spawn_mm2_instance(get_default_wallet_name().toStdString(), seed); return true; } return false; } - - QStringList - qt_wallet_manager::get_wallets() noexcept - { - QStringList out; - - for (auto&& p: fs::directory_iterator((get_atomic_dex_config_folder()))) - { - if (p.path().extension().string() == ".seed") - { - out.push_back(QString::fromStdString(p.path().stem().string())); - } - } - - return out; - } - - bool - qt_wallet_manager::is_there_a_default_wallet() noexcept - { - return fs::exists(get_atomic_dex_config_folder() / "default.wallet"); - } - - QString - qt_wallet_manager::get_default_wallet_name() noexcept - { - if (is_there_a_default_wallet()) - { - std::ifstream ifs((get_atomic_dex_config_folder() / "default.wallet").c_str()); - assert(ifs); - std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - return QString::fromStdString(str); - } - return "nonexistent"; - } - - bool - qt_wallet_manager::delete_wallet(const QString& wallet_name) noexcept - { - using namespace std::string_literals; - return fs::remove(get_atomic_dex_config_folder() / (wallet_name.toStdString() + ".seed"s)); - } - - bool - qt_wallet_manager::confirm_password(const QString& wallet_name, const QString& password) - { - std::error_code ec; - auto key = atomic_dex::derive_password(password.toStdString(), ec); - if (ec) - { - spdlog::debug("{}", ec.message()); - if (ec == dextop_error::derive_password_failed) - { - return false; - } - } - using namespace std::string_literals; - const fs::path seed_path = get_atomic_dex_config_folder() / (wallet_name.toStdString() + ".seed"s); - auto seed = atomic_dex::decrypt(seed_path, key.data(), ec); - if (ec == dextop_error::corrupted_file_or_wrong_password) - { - spdlog::warn("{}", ec.message()); - return false; - } - return true; - } } // namespace atomic_dex diff --git a/src/atomic.dex.update.service.cpp b/src/atomic.dex.update.service.cpp new file mode 100644 index 0000000000..9454868810 --- /dev/null +++ b/src/atomic.dex.update.service.cpp @@ -0,0 +1,103 @@ +/****************************************************************************** + * Copyright © 2013-2019 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. * + * * + ******************************************************************************/ + +//! Project headers +#include "atomic.dex.pch.hpp" +#include "atomic.dex.update.service.hpp" +#include "atomic.dex.events.hpp" +#include "atomic.dex.version.hpp" +#include "atomic.threadpool.hpp" + +namespace +{ + constexpr const char* g_komodolive_endpoint = "https://komodo.live/adexproversion"; + + nlohmann::json + get_update_status_rpc(const char* version) + { + using namespace std::string_literals; + nlohmann::json resp; + + nlohmann::json req{{"currentVersion", version}}; + auto answer = RestClient::post(g_komodolive_endpoint, "application/json", req.dump()); + if (answer.code != 200) + { + resp["status"] = "cannot reach the endpoint: "s + g_komodolive_endpoint; + } + else + { + resp = nlohmann::json::parse(answer.body); + } + resp["rpc_code"] = answer.code; + resp["current_version"] = version; + if (answer.code == 200) { + bool update_needed = false; + std::string current_version_str = version; + std::string endpoint_version = resp.at("new_version").get(); + boost::algorithm::replace_all(current_version_str, ".", ""); + boost::algorithm::replace_all(endpoint_version, ".", ""); + boost::algorithm::trim_left_if(current_version_str, boost::is_any_of("0")); + boost::algorithm::trim_left_if(endpoint_version, boost::is_any_of("0")); + update_needed = std::stoi(current_version_str) < std::stoi(endpoint_version); + resp["update_needed"] = update_needed; + } + return resp; + } +} // namespace +namespace atomic_dex +{ + //! Constructor + update_system_service::update_system_service(entt::registry& registry) : system(registry) + { + m_update_clock = std::chrono::high_resolution_clock::now(); + this->m_update_status = nlohmann::json::object(); + this->fetch_update_status(); + } + + //! Public override + void + update_system_service::update() noexcept + { + using namespace std::chrono_literals; + + const auto now = std::chrono::high_resolution_clock::now(); + const auto s = std::chrono::duration_cast(now - m_update_clock); + if (s >= 1h) + { + this->fetch_update_status(); + m_update_clock = std::chrono::high_resolution_clock::now(); + } + } + + //! Private api + void + update_system_service::fetch_update_status() noexcept + { + spdlog::debug("{} l{} f[{}]", __FUNCTION__, __LINE__, fs::path(__FILE__).filename().string()); + spdlog::info("fetching update status"); + spawn([this]() { + this->m_update_status = get_update_status_rpc(atomic_dex::get_raw_version()); + //spdlog::trace("-> {}", this->m_update_status->dump(4)); + this->dispatcher_.trigger(); + }); + } + + const nlohmann::json + update_system_service::get_update_status() const noexcept + { + return *m_update_status; + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.update.service.hpp b/src/atomic.dex.update.service.hpp new file mode 100644 index 0000000000..b3a26125c1 --- /dev/null +++ b/src/atomic.dex.update.service.hpp @@ -0,0 +1,50 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 + +//! PCH +#include "atomic.dex.pch.hpp" + +namespace atomic_dex +{ + class update_system_service final : public ag::ecs::pre_update_system + { + //! Private typedefs + using t_update_time_point = std::chrono::high_resolution_clock::time_point; + using t_json_synchronized = boost::synchronized_value; + + //! Private members + t_json_synchronized m_update_status; + t_update_time_point m_update_clock; + + //! Private API + void fetch_update_status() noexcept; + + public: + //! Constructor + explicit update_system_service(entt::registry& registry); + ~update_system_service() noexcept final = default; + + //! Public override + void update() noexcept final; + + //! Public API + const nlohmann::json get_update_status() const noexcept; + }; +} // namespace atomic_dex + +REFL_AUTO(type(atomic_dex::update_system_service)) \ No newline at end of file diff --git a/src/atomic.dex.utilities.hpp b/src/atomic.dex.utilities.hpp index 7bc0bb6635..ff6e7cf4e7 100644 --- a/src/atomic.dex.utilities.hpp +++ b/src/atomic.dex.utilities.hpp @@ -1,10 +1,12 @@ #pragma once +//! QT Headers +#include +#include + //! PCH Headers #include "atomic.dex.pch.hpp" -#include -#include inline fs::path get_atomic_dex_data_folder() @@ -33,10 +35,10 @@ get_atomic_dex_current_log_file() { using namespace std::chrono; using namespace date; - static auto timestamp = duration_cast(system_clock::now().time_since_epoch()).count(); + static auto timestamp = duration_cast(system_clock::now().time_since_epoch()).count(); static date::sys_seconds tp{seconds{timestamp}}; - static std::string s = date::format("%Y-%m-%d-%H-%M-%S", tp); - static const fs::path log_path = get_atomic_dex_logs_folder() / (s + ".log"); + static std::string s = date::format("%Y-%m-%d-%H-%M-%S", tp); + static const fs::path log_path = get_atomic_dex_logs_folder() / (s + ".log"); return log_path; } @@ -50,6 +52,18 @@ get_atomic_dex_config_folder() return get_atomic_dex_data_folder() / "config"; } +inline const std::string +minimal_trade_amount_str() +{ + return "0.00777"; +} + +inline const t_float_50 +minimal_trade_amount() +{ + return t_float_50(minimal_trade_amount_str()); +} + inline fs::path get_atomic_dex_export_folder() { diff --git a/src/atomic.dex.version.hpp b/src/atomic.dex.version.hpp index 658e353ea0..1a0106f600 100644 --- a/src/atomic.dex.version.hpp +++ b/src/atomic.dex.version.hpp @@ -21,18 +21,18 @@ namespace atomic_dex constexpr const char* get_version() { - return "0.1.5-alpha"; + return "0.2.0-alpha"; } constexpr const char* get_raw_version() { - return "0.1.5"; + return "0.2.0"; } constexpr const char* get_precedent_raw_version() { - return "0.1.4"; + return "0.1.5"; } } // namespace atomic_dex diff --git a/src/atomic.dex.wallet.config.cpp b/src/atomic.dex.wallet.config.cpp new file mode 100644 index 0000000000..ea9dcc62e3 --- /dev/null +++ b/src/atomic.dex.wallet.config.cpp @@ -0,0 +1,63 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.wallet.config.hpp" + +namespace atomic_dex +{ + void + from_json(const nlohmann::json& j, wallet_cfg& cfg) + { + j.at("name").get_to(cfg.name); + if (j.contains("addressbook")) + { + for (const auto& cur: j.at("addressbook")) + { + contact current_contact; + cur.at("name").get_to(current_contact.name); + for (const auto& cur_addr: cur.at("addresses")) + { + contact_contents contents; + cur_addr.at("type").get_to(contents.type); + cur_addr.at("address").get_to(contents.address); + current_contact.contents.emplace_back(std::move(contents)); + } + cfg.address_book.emplace_back(std::move(current_contact)); + } + } + } + + void + to_json(nlohmann::json& j, const contact_contents& cfg) + { + j["type"] = cfg.type; + j["address"] = cfg.address; + } + + void + to_json(nlohmann::json& j, const contact& cfg) + { + j["name"] = cfg.name; + j["addresses"] = cfg.contents; + } + + void + to_json(nlohmann::json& j, const wallet_cfg& cfg) + { + j["name"] = cfg.name; + j["addressbook"] = cfg.address_book; + } +} // namespace atomic_dex \ No newline at end of file diff --git a/src/atomic.dex.wallet.config.hpp b/src/atomic.dex.wallet.config.hpp new file mode 100644 index 0000000000..c77a780a0f --- /dev/null +++ b/src/atomic.dex.wallet.config.hpp @@ -0,0 +1,47 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.pch.hpp" + +namespace atomic_dex +{ + struct contact_contents + { + std::string type; + std::string address; + }; + + void to_json(nlohmann::json& j, const contact_contents& cfg); + + struct contact + { + std::string name; + std::vector contents; + }; + + void to_json(nlohmann::json& j, const contact& cfg); + + struct wallet_cfg + { + std::string name; + std::vector address_book; + }; + + void from_json(const nlohmann::json& j, wallet_cfg& cfg); + void to_json(nlohmann::json& j, const wallet_cfg& cfg); +} // namespace atomic_dex diff --git a/src/atomic.dex.wallet.config.tests.cpp b/src/atomic.dex.wallet.config.tests.cpp new file mode 100644 index 0000000000..967b9931f6 --- /dev/null +++ b/src/atomic.dex.wallet.config.tests.cpp @@ -0,0 +1,155 @@ +/****************************************************************************** + * Copyright © 2013-2019 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 "atomic.dex.wallet.config.hpp" +#include + +TEST_CASE("validate json serialization to cpp data structure (wallet_config)") +{ + auto j = R"( + { + "name":"roman", + "addressbook":[ + { + "name":"ca333", + "addresses":[ + { + "type":"ERC-20", + "address":"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" + }, + { + "type":"SmartChains", + "address":"RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" + }, + { + "type":"BTC", + "address":"3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" + } + ] + }, + { + "name":"alice", + "addresses":[ + { + "type":"ERC-20", + "address":"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" + }, + { + "type":"SmartChains", + "address":"RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" + }, + { + "type":"BTC", + "address":"3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" + } + ] + }, + { + "name":"bob", + "addresses":[ + { + "type":"ERC-20", + "address":"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" + }, + { + "type":"SmartChains", + "address":"RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" + }, + { + "type":"BTC", + "address":"3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" + } + ] + } + ] +} + )"_json; + atomic_dex::wallet_cfg cfg; + atomic_dex::from_json(j, cfg); + // CHECK_EQ(cfg.addressbook_registry.count("ca333"), 1); + // CHECK_EQ(cfg.addressbook_registry.size(), 3); + // CHECK_EQ(cfg.categories_addressbook_registry.size(), 3); + CHECK_EQ(cfg.name, "roman"); +} + +TEST_CASE("validate json deserialization from cpp data structure to json") +{ + auto j = R"( + { + "name":"roman", + "addressbook":[ + { + "name":"ca333", + "addresses":[ + { + "type":"ERC-20", + "address":"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" + }, + { + "type":"SmartChains", + "address":"RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" + }, + { + "type":"BTC", + "address":"3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" + } + ] + }, + { + "name":"alice", + "addresses":[ + { + "type":"ERC-20", + "address":"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" + }, + { + "type":"SmartChains", + "address":"RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" + }, + { + "type":"BTC", + "address":"3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" + } + ] + }, + { + "name":"bob", + "addresses":[ + { + "type":"ERC-20", + "address":"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" + }, + { + "type":"SmartChains", + "address":"RB49Rm4jBe5mN9anErvkzf3kcQCzHqyz3e" + }, + { + "type":"BTC", + "address":"3FZbgi29cpjq2GjdwV8eyHuJJnkLtktZc5" + } + ] + } + ] +} + )"_json; + atomic_dex::wallet_cfg cfg; + atomic_dex::from_json(j, cfg); + + nlohmann::json out_json; + atomic_dex::to_json(out_json, cfg); + + CHECK_EQ(out_json, j); +} \ No newline at end of file