feat: new design system (#3280)

* refactor: focus controller refactoring

* feat: update to new design system

* feat: update to new desing system

* chore: bump version
This commit is contained in:
vkamn authored and GitHub committed 2026-10-14 11:13:36 +08:00
1 parent 3e8077f2ca
commit 11355e1de0
241 files changed
+29198 -32524

No files matched your search

+2 -2
View File
@@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
if(NOT AMNEZIAVPN_VERSION) if(NOT AMNEZIAVPN_VERSION)
set(AMNEZIAVPN_VERSION 5.0.3.1 CACHE STRING "Client app version") set(AMNEZIAVPN_VERSION 5.1.0.0 CACHE STRING "Client app version")
endif() endif()
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE) set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
@@ -34,7 +34,7 @@ set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
# bump by 2 on every release, because we're releasing two versions on the play store # bump by 2 on every release, because we're releasing two versions on the play store
set(APP_ANDROID_VERSION_CODE 2171) set(APP_ANDROID_VERSION_CODE 2173)
if(DEFINED APP_ANDROID_VERSION_CODE_OFFSET) if(DEFINED APP_ANDROID_VERSION_CODE_OFFSET)
math(EXPR APP_ANDROID_VERSION_CODE "${APP_ANDROID_VERSION_CODE} + ${APP_ANDROID_VERSION_CODE_OFFSET}") math(EXPR APP_ANDROID_VERSION_CODE "${APP_ANDROID_VERSION_CODE} + ${APP_ANDROID_VERSION_CODE_OFFSET}")
endif() endif()
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="black">#FF0E0E11</color> <color name="black">#FF101012</color>
<style name="NoActionBar"> <style name="NoActionBar">
<item name="android:windowBackground">@color/black</item> <item name="android:windowBackground">@color/black</item>
<item name="android:colorBackground">@color/black</item> <item name="android:colorBackground">@color/black</item>
-2
View File
@@ -66,7 +66,6 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h ${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h
${CLIENT_ROOT_DIR}/../common/logger/logger.h ${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.h ${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.h
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h ${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/countryCatalog.h ${CLIENT_ROOT_DIR}/core/utils/api/countryCatalog.h
${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.h ${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.h
@@ -155,7 +154,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp ${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp ${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.cpp ${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.cpp
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp ${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/countryCatalog.cpp ${CLIENT_ROOT_DIR}/core/utils/api/countryCatalog.cpp
${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.cpp ${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.cpp
@@ -187,7 +187,6 @@ void CoreController::initControllers()
if (m_engine) { if (m_engine) {
m_focusController = new FocusController(m_engine, this); m_focusController = new FocusController(m_engine, this);
setQmlContextProperty("FocusController", m_focusController);
} }
m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController, m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController,
+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 21C20.5523 21 21 21.4477 21 22V22.0098C21 22.5621 20.5523 23.0098 20 23.0098C19.4477 23.0098 19 22.5621 19 22.0098V22C19 21.4477 19.4477 21 20 21ZM20 3C20.7957 3 21.5585 3.3163 22.1211 3.87891C22.6837 4.44152 23 5.20435 23 6V10.5C23 11.0523 22.5523 11.5 22 11.5C21.4477 11.5 21 11.0523 21 10.5V8.81934L13.5664 13.5439L13.5605 13.5479C13.0928 13.8408 12.5519 13.9961 12 13.9961C11.4481 13.9961 10.9072 13.8408 10.4395 13.5479L10.4336 13.5439L3 8.81934V18C3 18.5477 3.45228 19 4 19H16.5C17.0523 19 17.5 19.4477 17.5 20C17.5 20.5523 17.0523 21 16.5 21H4C2.34772 21 1 19.6523 1 18V6C1 5.20435 1.3163 4.44152 1.87891 3.87891C2.44152 3.3163 3.20435 3 4 3H20ZM20 13C20.5523 13 21 13.4477 21 14V18C21 18.5523 20.5523 19 20 19C19.4477 19 19 18.5523 19 18V14C19 13.4477 19.4477 13 20 13ZM4 5C3.73478 5 3.4805 5.10543 3.29297 5.29297C3.10543 5.4805 3 5.73478 3 6V6.4502L11.5 11.8525C11.6496 11.9463 11.8235 11.9961 12 11.9961C12.1765 11.9961 12.3494 11.9463 12.499 11.8525L21 6.4502V6C21 5.73478 20.8946 5.48051 20.707 5.29297C20.5195 5.10543 20.2652 5 20 5H4Z" fill="#F87171"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg width="298" height="298" viewBox="46 46 298 298" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.5 50H64C56.268 50 50 56.268 50 64V129.5" stroke="white" stroke-width="4"/>
<path d="M260.5 50H326C333.732 50 340 56.268 340 64V129.5" stroke="white" stroke-width="4"/>
<path d="M340 260.5V326C340 333.732 333.732 340 326 340H260.5" stroke="white" stroke-width="4"/>
<path d="M129.5 340H64C56.268 340 50 333.732 50 326V260.5" stroke="white" stroke-width="4"/>
</svg>

After

Width:  |  Height:  |  Size: 481 B

-5
View File
@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.0001 12C14.0001 10.8954 13.1047 10 12.0001 10C10.8955 10 10.0001 10.8954 10.0001 12C10.0001 13.1046 10.8955 14 12.0001 14C13.1047 14 14.0001 13.1046 14.0001 12ZM16.0001 12C16.0001 14.2091 14.2092 16 12.0001 16C9.79094 16 8.00008 14.2091 8.00008 12C8.00008 9.79086 9.79094 8 12.0001 8C14.2092 8 16.0001 9.79086 16.0001 12Z" fill="#D7D8DB"/>
<path d="M20.5001 6C21.8808 6 23.0001 4.88071 23.0001 3.5C23.0001 2.11929 21.8808 1 20.5001 1C19.1194 1 18.0001 2.11929 18.0001 3.5C18.0001 4.88071 19.1194 6 20.5001 6Z" fill="#FBB36A"/>
<path d="M12.2198 1C12.9657 1 13.6811 1.28098 14.2315 1.7793C14.0814 2.32745 14.0001 2.90421 14.0001 3.5C14.0001 4.75704 14.3578 5.93021 14.9757 6.9248L14.7198 6.77539C14.2644 6.51244 13.8864 6.13489 13.6231 5.67969C13.3599 5.22423 13.2203 4.70672 13.2198 4.18066V4C13.2198 3.73478 13.1144 3.48051 12.9268 3.29297C12.7393 3.10557 12.4849 3 12.2198 3H11.7803C11.5153 3.00005 11.2608 3.10556 11.0733 3.29297C10.8858 3.4805 10.7803 3.73482 10.7803 4V4.18066C10.7798 4.70675 10.6403 5.22422 10.377 5.67969C10.1138 6.1349 9.73575 6.51343 9.28035 6.77637L9.27937 6.77539L8.85261 7.02441L8.84968 7.02637C8.39376 7.28946 7.87607 7.42773 7.34968 7.42773C6.82327 7.42764 6.30558 7.28958 5.84968 7.02637L5.72956 6.96191L5.70027 6.94629C5.47084 6.81401 5.1983 6.77827 4.94245 6.84668C4.71846 6.90662 4.52295 7.04188 4.38874 7.22852L4.33503 7.31152L4.11531 7.69141C3.98385 7.92039 3.94855 8.19212 4.01667 8.44727C4.07655 8.6712 4.21199 8.86674 4.39851 9.00098L4.48152 9.05469L4.53523 9.08789L4.68464 9.1875C5.11379 9.44747 5.47116 9.8098 5.7237 10.2441C5.98685 10.6968 6.12679 11.2108 6.12995 11.7344V12.2461L6.1241 12.4434C6.09561 12.9034 5.96204 13.3518 5.73152 13.7529C5.47084 14.2065 5.09507 14.5827 4.64363 14.8467L4.6446 14.8477L4.49421 14.9375L4.48152 14.9453C4.25235 15.0781 4.08509 15.2969 4.01667 15.5527C3.94855 15.8079 3.98385 16.0796 4.11531 16.3086L4.33406 16.6885C4.4668 16.9177 4.68662 17.0849 4.94245 17.1533C5.1983 17.2217 5.47084 17.186 5.70027 17.0537L5.72956 17.0381L5.84968 16.9736L6.02351 16.8809C6.43513 16.678 6.88902 16.5724 7.34968 16.5723C7.81025 16.5723 8.26427 16.6781 8.67585 16.8809L8.84968 16.9736L8.85261 16.9756L9.27937 17.2236C9.73477 17.4866 10.1138 17.8651 10.377 18.3203C10.6403 18.7758 10.7798 19.2932 10.7803 19.8193V20C10.7803 20.2652 10.8858 20.5195 11.0733 20.707C11.2608 20.8944 11.5153 21 11.7803 21H12.2198C12.4849 21 12.7393 20.8944 12.9268 20.707C13.1144 20.5195 13.2198 20.2652 13.2198 20V19.8193L13.2266 19.6221C13.2572 19.1646 13.3928 18.7188 13.6231 18.3203C13.8534 17.9221 14.1718 17.5828 14.5528 17.3281L14.7198 17.2236L15.1475 16.9756L15.1505 16.9736L15.3243 16.8809C15.7359 16.6781 16.1899 16.5723 16.6505 16.5723C17.1769 16.5723 17.6946 16.7104 18.1505 16.9736L18.2999 17.0537C18.5293 17.186 18.8018 17.2217 19.0577 17.1533C19.3136 17.0849 19.5323 16.9177 19.6651 16.6885L19.879 16.3086L19.8839 16.3008L19.9288 16.2119C20.0233 16.0026 20.0433 15.7658 19.9835 15.542C19.915 15.2863 19.7477 15.0683 19.5186 14.9355L19.3995 14.8721C19.3898 14.8669 19.3797 14.862 19.3702 14.8564C18.9124 14.5921 18.532 14.2112 18.2686 13.7529C18.0053 13.2946 17.8681 12.7746 17.8702 12.2461V11.75C17.8688 11.2228 18.0059 10.7042 18.2686 10.2471C18.3758 10.0606 18.5033 9.88765 18.6466 9.72949C19.2341 9.90406 19.8558 10 20.5001 10C20.8249 10 21.144 9.97473 21.4561 9.92871C21.2126 10.2693 20.9013 10.5607 20.5333 10.7764L20.5343 10.7773L20.3702 10.876C20.2176 10.9641 20.0908 11.0914 20.003 11.2441C19.9153 11.3968 19.8695 11.57 19.8702 11.7461V12.2539C19.8695 12.43 19.9153 12.6032 20.003 12.7559C20.0894 12.9062 20.214 13.0312 20.3634 13.1191L20.4903 13.1875L20.5216 13.2051L20.7706 13.3662C21.3298 13.7689 21.7365 14.3539 21.9161 15.0254C22.1205 15.7898 22.0131 16.6035 21.6202 17.29L21.6212 17.291L21.4014 17.6816L21.3956 17.6914C20.9972 18.379 20.342 18.8807 19.5743 19.0859C18.817 19.2883 18.0116 19.1847 17.3292 18.8008V18.8027L17.1798 18.7227C17.17 18.7175 17.16 18.7116 17.1505 18.7061C16.9986 18.6184 16.8258 18.5723 16.6505 18.5723C16.4751 18.5723 16.3024 18.6184 16.1505 18.7061L16.1495 18.7051L15.7227 18.9541L15.6114 19.0283C15.5077 19.1078 15.4204 19.2075 15.3546 19.3213C15.2668 19.4731 15.22 19.646 15.2198 19.8213V20C15.2198 20.7955 14.9043 21.5585 14.3419 22.1211C13.7793 22.6836 13.0154 23 12.2198 23H11.7803C10.9848 23 10.2208 22.6837 9.65828 22.1211C9.09594 21.5585 8.78035 20.7954 8.78035 20V19.8213L8.77156 19.6904C8.75435 19.5609 8.71141 19.4352 8.64558 19.3213C8.57971 19.2075 8.49243 19.1078 8.38874 19.0283L8.27742 18.9541L7.84968 18.7051C7.69776 18.6175 7.52506 18.5723 7.34968 18.5723C7.21824 18.5723 7.08852 18.5985 6.96785 18.6484L6.84968 18.7061C6.84011 18.7116 6.83013 18.7175 6.82038 18.7227L6.67097 18.8027L6.66999 18.8008C5.98771 19.1844 5.18286 19.2883 4.42585 19.0859C3.75418 18.9063 3.16836 18.4998 2.7657 17.9404L2.60456 17.6914L2.38484 17.3105L2.38386 17.3096C1.98692 16.6214 1.87897 15.8037 2.08406 15.0361C2.28803 14.2733 2.78502 13.6217 3.46589 13.2227L3.62995 13.124L3.73933 13.0508C3.84344 12.9708 3.93128 12.8704 3.99714 12.7559C4.08484 12.6032 4.13065 12.43 4.12995 12.2539V11.7461L4.12117 11.6162C4.10349 11.4873 4.06003 11.3622 3.99421 11.249C3.92841 11.136 3.84152 11.037 3.73835 10.958L3.62995 10.8857L3.57527 10.8516L3.42585 10.752C2.76614 10.351 2.2839 9.71122 2.08406 8.96387C1.87897 8.19633 1.98692 7.37864 2.38386 6.69043L2.38484 6.68848L2.60456 6.30859L2.7657 6.05957C3.16836 5.50016 3.75418 5.09371 4.42585 4.91406C5.1827 4.71172 5.9878 4.81485 6.66999 5.19824L6.67097 5.19727L6.82038 5.27734L6.84968 5.29395L6.967Line truncated
</svg>

Before

Width:  |  Height:  |  Size: 5.9 KiB

+2 -5
View File
@@ -1,6 +1,3 @@
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" width="18" height="18" rx="5" fill="white"/> <path d="M19.0275 5C19.0858 5.00093 19.1439 5.0068 19.2014 5.01758C19.2155 5.02023 19.2294 5.02411 19.2434 5.02734C19.2617 5.03158 19.28 5.03576 19.298 5.04102C19.3522 5.05672 19.4046 5.07722 19.4553 5.10156C19.4608 5.10422 19.4664 5.10661 19.4719 5.10938L19.4875 5.11719C19.4978 5.12261 19.5076 5.12899 19.5178 5.13477C19.5626 5.16041 19.6064 5.18867 19.6477 5.22168C19.6746 5.24318 19.701 5.26568 19.7258 5.29004C19.7418 5.30575 19.7559 5.32322 19.7707 5.33984C19.7771 5.3471 19.784 5.35393 19.7902 5.36133C19.9384 5.53804 20.0207 5.76027 20.0207 5.99121L20.0197 12.9941C20.0196 13.5415 19.5679 13.9853 19.0109 13.9854C18.454 13.9852 18.0022 13.5415 18.0021 12.9941V8.41309L12.2385 14.1777V18.4404C12.2382 18.9876 11.7875 19.4315 11.2307 19.4316C10.6737 19.4316 10.2221 18.9877 10.2219 18.4404V13.7715L10.2268 13.6748C10.2492 13.4505 10.3488 13.2393 10.5109 13.0771L16.6037 6.98242H12.0109C11.454 6.98233 11.0023 6.53854 11.0021 5.99121C11.0023 5.44389 11.454 5.00009 12.0109 5H19.0275ZM4.30097 6.0625C4.69824 5.67878 5.33821 5.6847 5.72871 6.0752L9.61836 9.96387C10.0087 10.3543 10.0026 10.9825 9.60566 11.3662C9.20853 11.7496 8.57034 11.7445 8.17988 11.3545L4.28925 7.46387C3.89917 7.07347 3.90418 6.44616 4.30097 6.0625Z" fill="#E4E4E7"/>
<path d="M8.49219 13.5L8.49219 9.44141L14.0191 4.99484" stroke="#0E0E11" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.47363 5.49805L6.98828 8.0127" stroke="#0E0E11" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.4727 9.5L14.4727 4.5033L9.50195 4.5033" stroke="#0E0E11" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 1.3 KiB

+2 -1
View File
@@ -42,6 +42,7 @@
<file>controls/infinity.svg</file> <file>controls/infinity.svg</file>
<file>controls/info.svg</file> <file>controls/info.svg</file>
<file>controls/mail.svg</file> <file>controls/mail.svg</file>
<file>controls/mail-warning.svg</file>
<file>controls/map-pin.svg</file> <file>controls/map-pin.svg</file>
<file>controls/more-vertical.svg</file> <file>controls/more-vertical.svg</file>
<file>controls/news.svg</file> <file>controls/news.svg</file>
@@ -49,6 +50,7 @@
<file>controls/unread-dot.svg</file> <file>controls/unread-dot.svg</file>
<file>controls/plus.svg</file> <file>controls/plus.svg</file>
<file>controls/qr-code.svg</file> <file>controls/qr-code.svg</file>
<file>controls/qr-scan-frame.svg</file>
<file>controls/radio-button-inner-circle-pressed.png</file> <file>controls/radio-button-inner-circle-pressed.png</file>
<file>controls/radio-button-inner-circle.png</file> <file>controls/radio-button-inner-circle.png</file>
<file>controls/radio-button-pressed.svg</file> <file>controls/radio-button-pressed.svg</file>
@@ -62,7 +64,6 @@
<file>controls/server.svg</file> <file>controls/server.svg</file>
<file>controls/settings-2.svg</file> <file>controls/settings-2.svg</file>
<file>controls/settings.svg</file> <file>controls/settings.svg</file>
<file>controls/settings-news.svg</file>
<file>controls/share-2.svg</file> <file>controls/share-2.svg</file>
<file>controls/star.svg</file> <file>controls/star.svg</file>
<file>controls/star-filled.svg</file> <file>controls/star-filled.svg</file>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+120 -181
View File
@@ -1,215 +1,154 @@
#include "focusController.h" #include "focusController.h"
#include "ui/utils/qmlUtils.h"
#include <QGuiApplication>
#include <QKeyEvent>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQuickItem>
#include <QQuickWindow> #include <QQuickWindow>
#include <QStyleHints>
#include "logger.h" #include <algorithm>
namespace { namespace
Logger logger("FocusController"); {
constexpr qreal ensureVisibleMargin = 40;
QQuickItem *focusChainRoot(QQuickItem *item)
{
for (QQuickItem *it = item; it; it = it->parentItem()) {
if (it->inherits("QQuickPopupItem")) {
return it;
}
}
return item->window()->contentItem();
}
} }
FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent) FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent) : QObject { parent }
: QObject { parent },
m_engine { engine },
m_focusChain {},
m_focusedItem { nullptr },
m_rootObjects {},
m_defaultFocusItem { nullptr },
m_lvfc { nullptr }
{ {
QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, this, [this](QObject *object, const QUrl &url) { QGuiApplication::styleHints()->setTabFocusBehavior(Qt::TabFocusAllControls);
QQuickItem *newDefaultFocusItem = object->findChild<QQuickItem *>("defaultFocusItem");
if (newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) { connect(engine, &QQmlApplicationEngine::objectCreated, this, [this](QObject *object) {
m_defaultFocusItem = newDefaultFocusItem; if (auto *window = qobject_cast<QQuickWindow *>(object)) {
window->installEventFilter(this);
} }
}); });
QObject::connect(this, &FocusController::focusedItemChanged, this,
[this]() { m_focusedItem->forceActiveFocus(Qt::TabFocusReason); });
} }
void FocusController::nextKeyTabItem() bool FocusController::eventFilter(QObject *watched, QEvent *event)
{ {
nextItem(Direction::Forward); if (event->type() != QEvent::KeyPress) {
} return false;
void FocusController::previousKeyTabItem()
{
nextItem(Direction::Backward);
}
void FocusController::nextKeyUpItem()
{
nextItem(Direction::Backward);
}
void FocusController::nextKeyDownItem()
{
nextItem(Direction::Forward);
}
void FocusController::nextKeyLeftItem()
{
nextItem(Direction::Backward);
}
void FocusController::nextKeyRightItem()
{
nextItem(Direction::Forward);
}
void FocusController::setFocusItem(QQuickItem *item)
{
if (m_focusedItem != item) {
m_focusedItem = item;
}
emit focusedItemChanged();
}
void FocusController::setFocusOnDefaultItem()
{
setFocusItem(m_defaultFocusItem);
}
void FocusController::pushRootObject(QObject *object)
{
m_rootObjects.push(object);
dropListView();
// setFocusOnDefaultItem();
}
void FocusController::dropRootObject(QObject *object)
{
if (m_rootObjects.empty()) {
return;
} }
if (m_rootObjects.top() == object) { auto *window = qobject_cast<QQuickWindow *>(watched);
m_rootObjects.pop(); auto *keyEvent = static_cast<QKeyEvent *>(event);
dropListView(); if (!window || (keyEvent->modifiers() & ~(Qt::ShiftModifier | Qt::KeypadModifier))) {
setFocusOnDefaultItem(); return false;
} else {
logger.warning() << "TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
}
}
void FocusController::resetRootObject()
{
m_rootObjects.clear();
}
void FocusController::reload(Direction direction)
{
m_focusChain.clear();
QObject *rootObject = (m_rootObjects.empty() ? m_engine->rootObjects().value(0) : m_rootObjects.top());
if (!rootObject) {
logger.error() << "No ROOT OBJECT found!";
resetRootObject();
dropListView();
return;
} }
m_focusChain.append(FocusControl::getSubChain(rootObject)); QQuickItem *current = window->activeFocusItem() ? window->activeFocusItem() : window->contentItem();
std::sort(m_focusChain.begin(), m_focusChain.end(), bool forward = false;
direction == Direction::Forward ? FocusControl::isLess : FocusControl::isMore); switch (keyEvent->key()) {
case Qt::Key_Tab: forward = true; break;
if (m_focusChain.empty()) { case Qt::Key_Backtab: forward = false; break;
logger.warning() << "Focus chain is empty!"; case Qt::Key_Down:
resetRootObject(); case Qt::Key_Right: forward = true; [[fallthrough]];
dropListView(); case Qt::Key_Up:
return; case Qt::Key_Left:
} if (wantsArrowKey(current, keyEvent->key())) {
} return false;
void FocusController::nextItem(Direction direction)
{
reload(direction);
if (m_lvfc && FocusControl::isListView(m_focusedItem)) {
direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem();
return;
}
if (m_focusChain.empty()) {
logger.warning() << "There are no items to navigate";
setFocusOnDefaultItem();
return;
}
auto focusedItemIndex = m_focusChain.indexOf(m_focusedItem);
if (focusedItemIndex == -1) {
focusedItemIndex = 0;
} else if (focusedItemIndex == (m_focusChain.size() - 1)) {
focusedItemIndex = 0;
} else {
focusedItemIndex++;
}
const auto focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(focusedItemIndex));
if (focusedItem == nullptr) {
logger.warning() << "Failed to get item to focus on. Setting focus on default";
setFocusOnDefaultItem();
return;
}
if (FocusControl::isListView(focusedItem)) {
m_lvfc = new ListViewFocusController(focusedItem, this);
m_focusedItem = focusedItem;
if (direction == Direction::Forward) {
m_lvfc->nextDelegate();
focusNextListViewItem();
} else {
m_lvfc->previousDelegate();
focusPreviousListViewItem();
} }
break;
default: return false;
}
sortListViewDelegates(focusChainRoot(current));
QQuickItem *next = current->nextItemInFocusChain(forward);
if (next && next != current) {
next->forceActiveFocus(forward ? Qt::TabFocusReason : Qt::BacktabFocusReason);
ensureVisible(next);
}
return true;
}
bool FocusController::wantsArrowKey(const QQuickItem *item, int key)
{
if (item->inherits("QQuickTextEdit")) {
return !item->property("readOnly").toBool();
}
if (item->inherits("QQuickTextInput")) {
return key == Qt::Key_Left || key == Qt::Key_Right;
}
return false;
}
void FocusController::sortListViewDelegates(QQuickItem *root)
{
if (!root->isVisible()) {
return; return;
} }
setFocusItem(focusedItem); if (root->inherits("QQuickListView")) {
} if (auto *contentItem = root->property("contentItem").value<QQuickItem *>()) {
QList<QQuickItem *> delegates = contentItem->childItems();
void FocusController::focusNextListViewItem() const auto byPosition = [](const QQuickItem *a, const QQuickItem *b) {
{ return a->y() != b->y() ? a->y() < b->y() : a->x() < b->x();
m_lvfc->reloadFocusChain(); };
if (m_lvfc->isLastFocusItemInListView() || m_lvfc->isReturnNeeded()) { if (!std::is_sorted(delegates.cbegin(), delegates.cend(), byPosition)) {
dropListView(); std::stable_sort(delegates.begin(), delegates.end(), byPosition);
nextItem(Direction::Forward); for (qsizetype i = 1; i < delegates.size(); ++i) {
return; delegates.at(i)->stackAfter(delegates.at(i - 1));
} else if (m_lvfc->isLastFocusItemInDelegate()) { }
m_lvfc->resetFocusChain(); }
m_lvfc->nextDelegate(); }
} }
m_lvfc->focusNextItem(); const QList<QQuickItem *> children = root->childItems();
for (QQuickItem *child : children) {
sortListViewDelegates(child);
}
} }
void FocusController::focusPreviousListViewItem() void FocusController::ensureVisible(QQuickItem *item)
{ {
m_lvfc->reloadFocusChain(); for (QQuickItem *flickable = item->parentItem(); flickable; flickable = flickable->parentItem()) {
if (m_lvfc->isFirstFocusItemInListView() || m_lvfc->isReturnNeeded()) { if (!flickable->inherits("QQuickFlickable")) {
dropListView(); continue;
nextItem(Direction::Backward); }
return;
} else if (m_lvfc->isFirstFocusItemInDelegate()) { auto *contentItem = flickable->property("contentItem").value<QQuickItem *>();
m_lvfc->resetFocusChain(); if (!contentItem) {
m_lvfc->previousDelegate(); continue;
}
const QRectF rect = item->mapRectToItem(contentItem, item->boundingRect());
scrollToRange(flickable, rect.top(), rect.bottom(), flickable->height(), "contentY", "originY",
"contentHeight", "topMargin", "bottomMargin");
scrollToRange(flickable, rect.left(), rect.right(), flickable->width(), "contentX", "originX",
"contentWidth", "leftMargin", "rightMargin");
} }
m_lvfc->focusPreviousItem();
} }
void FocusController::dropListView() void FocusController::scrollToRange(QQuickItem *flickable, qreal start, qreal end, qreal viewSize, const char *position,
const char *origin, const char *contentSize, const char *startMargin,
const char *endMargin)
{ {
if (m_lvfc) { const qreal current = flickable->property(position).toReal();
delete m_lvfc;
m_lvfc = nullptr; qreal target = current;
if (start < current) {
target = start - ensureVisibleMargin;
} else if (end > current + viewSize) {
target = std::min(end - viewSize + ensureVisibleMargin, start - ensureVisibleMargin);
}
if (target != current) {
const qreal min = flickable->property(origin).toReal() - flickable->property(startMargin).toReal();
const qreal max = flickable->property(origin).toReal() + flickable->property(contentSize).toReal()
+ flickable->property(endMargin).toReal() - viewSize;
flickable->setProperty(position, std::clamp(target, min, std::max(min, max)));
} }
} }
+10 -41
View File
@@ -1,57 +1,26 @@
#ifndef FOCUSCONTROLLER_H #ifndef FOCUSCONTROLLER_H
#define FOCUSCONTROLLER_H #define FOCUSCONTROLLER_H
#include "ui/controllers/qml/listViewFocusController.h"
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
/*! class QQuickItem;
* \brief The FocusController class makes focus control more straightforward
* \details Focus is handled only for visible and enabled items which have
* `isFocused` property from top left to bottom right.
* \note There are items handled differently (e.g. ListView)
*/
class FocusController : public QObject class FocusController : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit FocusController(QQmlApplicationEngine *engine, QObject *parent = nullptr); explicit FocusController(QQmlApplicationEngine *engine, QObject *parent = nullptr);
~FocusController() override = default;
Q_INVOKABLE void nextKeyTabItem(); protected:
Q_INVOKABLE void previousKeyTabItem(); bool eventFilter(QObject *watched, QEvent *event) override;
Q_INVOKABLE void nextKeyUpItem();
Q_INVOKABLE void nextKeyDownItem();
Q_INVOKABLE void nextKeyLeftItem();
Q_INVOKABLE void nextKeyRightItem();
Q_INVOKABLE void setFocusItem(QQuickItem *item);
Q_INVOKABLE void setFocusOnDefaultItem();
Q_INVOKABLE void pushRootObject(QObject *object);
Q_INVOKABLE void dropRootObject(QObject *object);
Q_INVOKABLE void resetRootObject();
private: private:
enum class Direction { static bool wantsArrowKey(const QQuickItem *item, int key);
Forward, static void sortListViewDelegates(QQuickItem *root);
Backward, static void ensureVisible(QQuickItem *item);
}; static void scrollToRange(QQuickItem *flickable, qreal start, qreal end, qreal viewSize, const char *position,
const char *origin, const char *contentSize, const char *startMargin,
void reload(Direction direction); const char *endMargin);
void nextItem(Direction direction);
void focusNextListViewItem();
void focusPreviousListViewItem();
void dropListView();
QQmlApplicationEngine *m_engine; // Pointer to engine to get root object
QList<QObject *> m_focusChain; // List of current objects to be focused
QQuickItem *m_focusedItem; // Pointer to the active focus item
QStack<QObject *> m_rootObjects; // Pointer to stack of roots for focus chain
QQuickItem *m_defaultFocusItem;
ListViewFocusController *m_lvfc; // ListView focus manager
signals:
void focusedItemChanged();
}; };
#endif // FOCUSCONTROLLER_H #endif // FOCUSCONTROLLER_H
@@ -1,323 +0,0 @@
#include "listViewFocusController.h"
#include "ui/utils/qmlUtils.h"
#include <QQuickWindow>
ListViewFocusController::ListViewFocusController(QQuickItem *listView, QObject *parent)
: QObject { parent },
m_listView { listView },
m_focusChain {},
m_currentSection { Section::Default },
m_header { nullptr },
m_footer { nullptr },
m_focusedItem { nullptr },
m_focusedItemIndex { -1 },
m_delegateIndex { 0 },
m_isReturnNeeded { false },
m_currentSectionString { "Default", "Header", "Delegate", "Footer" }
{
QVariant headerItemProperty = m_listView->property("headerItem");
m_header = headerItemProperty.canConvert<QQuickItem *>() ? headerItemProperty.value<QQuickItem *>() : nullptr;
QVariant footerItemProperty = m_listView->property("footerItem");
m_footer = footerItemProperty.canConvert<QQuickItem *>() ? footerItemProperty.value<QQuickItem *>() : nullptr;
}
ListViewFocusController::~ListViewFocusController()
{
}
void ListViewFocusController::viewAtCurrentIndex() const
{
switch (m_currentSection) {
case Section::Default: [[fallthrough]];
case Section::Header: {
QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning");
break;
}
case Section::Delegate: {
QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", Q_ARG(int, m_delegateIndex),
Q_ARG(int, 4));
break;
}
case Section::Footer: {
QMetaObject::invokeMethod(m_listView, "positionViewAtEnd");
break;
}
}
QMetaObject::invokeMethod(m_listView, "forceLayout");
}
int ListViewFocusController::size() const
{
return m_listView->property("count").toInt();
}
int ListViewFocusController::currentIndex() const
{
return m_delegateIndex;
}
void ListViewFocusController::setDelegateIndex(int index)
{
m_delegateIndex = index;
m_listView->setProperty("currentIndex", index);
}
void ListViewFocusController::nextDelegate()
{
switch (m_currentSection) {
case Section::Default: {
if (hasHeader()) {
m_currentSection = Section::Header;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
}
case Section::Header: {
if (size() > 0) {
m_currentSection = Section::Delegate;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
}
case Section::Delegate:
if (m_delegateIndex < (size() - 1)) {
setDelegateIndex(m_delegateIndex + 1);
viewAtCurrentIndex();
break;
} else if (hasFooter()) {
m_currentSection = Section::Footer;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
case Section::Footer: {
m_isReturnNeeded = true;
m_currentSection = Section::Default;
viewAtCurrentIndex();
break;
}
default: {
qCritical() << "Current section is invalid!";
break;
}
}
}
void ListViewFocusController::previousDelegate()
{
switch (m_currentSection) {
case Section::Default: {
if (hasFooter()) {
m_currentSection = Section::Footer;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
}
case Section::Footer: {
if (size() > 0) {
m_currentSection = Section::Delegate;
setDelegateIndex(size() - 1);
break;
}
[[fallthrough]];
}
case Section::Delegate: {
if (m_delegateIndex > 0) {
setDelegateIndex(m_delegateIndex - 1);
viewAtCurrentIndex();
break;
} else if (hasHeader()) {
m_currentSection = Section::Header;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
}
case Section::Header: {
m_isReturnNeeded = true;
m_currentSection = Section::Default;
viewAtCurrentIndex();
break;
}
default: {
qCritical() << "Current section is invalid!";
break;
}
}
}
void ListViewFocusController::decrementIndex()
{
m_delegateIndex--;
}
QQuickItem *ListViewFocusController::itemAtIndex(const int index) const
{
QQuickItem *item { nullptr };
QMetaObject::invokeMethod(m_listView, "itemAtIndex", Q_RETURN_ARG(QQuickItem *, item), Q_ARG(int, index));
return item;
}
QQuickItem *ListViewFocusController::currentDelegate() const
{
QQuickItem *result { nullptr };
switch (m_currentSection) {
case Section::Default: {
qWarning() << "No elements...";
break;
}
case Section::Header: {
result = m_header;
break;
}
case Section::Delegate: {
result = itemAtIndex(m_delegateIndex);
break;
}
case Section::Footer: {
result = m_footer;
break;
}
}
return result;
}
QQuickItem *ListViewFocusController::focusedItem() const
{
return m_focusedItem;
}
void ListViewFocusController::focusNextItem()
{
if (m_isReturnNeeded) {
return;
}
reloadFocusChain();
if (m_focusChain.empty()) {
qWarning() << "No elements found in the delegate. Going to next delegate...";
nextDelegate();
focusNextItem();
return;
}
m_focusedItemIndex++;
if (m_focusedItemIndex < 0 || m_focusedItemIndex >= m_focusChain.size()) {
m_focusedItemIndex = 0;
}
m_focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(m_focusedItemIndex));
m_focusedItem->forceActiveFocus(Qt::TabFocusReason);
}
void ListViewFocusController::focusPreviousItem()
{
if (m_isReturnNeeded) {
return;
}
if (m_focusChain.empty()) {
qInfo() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements...";
reloadFocusChain();
}
if (m_focusChain.empty()) {
qWarning() << "No elements found in the delegate. Going to next delegate...";
previousDelegate();
focusPreviousItem();
return;
}
if (m_focusedItemIndex < 0 || m_focusedItemIndex > m_focusChain.size()) {
m_focusedItemIndex = m_focusChain.size();
}
m_focusedItemIndex--;
if (m_focusedItemIndex < 0) {
m_focusedItemIndex = m_focusChain.size() - 1;
}
m_focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(m_focusedItemIndex));
m_focusedItem->forceActiveFocus(Qt::TabFocusReason);
}
void ListViewFocusController::resetFocusChain()
{
m_focusChain.clear();
m_focusedItem = nullptr;
m_focusedItemIndex = -1;
}
void ListViewFocusController::reloadFocusChain()
{
m_focusChain = FocusControl::getItemsChain(currentDelegate());
m_focusedItemIndex = m_focusChain.indexOf(m_focusedItem);
}
bool ListViewFocusController::isFirstFocusItemInDelegate() const
{
return m_focusedItem && !m_focusChain.isEmpty() && (m_focusedItem == m_focusChain.first());
}
bool ListViewFocusController::isLastFocusItemInDelegate() const
{
return m_focusedItem && !m_focusChain.isEmpty() && (m_focusedItem == m_focusChain.last());
}
bool ListViewFocusController::hasHeader() const
{
return m_header && !FocusControl::getItemsChain(m_header).isEmpty();
}
bool ListViewFocusController::hasFooter() const
{
return m_footer && !FocusControl::getItemsChain(m_footer).isEmpty();
}
bool ListViewFocusController::isFirstFocusItemInListView() const
{
switch (m_currentSection) {
case Section::Footer: {
return isFirstFocusItemInDelegate() && !hasHeader() && (size() == 0);
}
case Section::Delegate: {
return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader();
}
case Section::Header: {
return isFirstFocusItemInDelegate();
}
case Section::Default: {
return true;
}
default: qWarning() << "Wrong section"; return true;
}
}
bool ListViewFocusController::isLastFocusItemInListView() const
{
switch (m_currentSection) {
case Section::Default: {
return !hasHeader() && (size() == 0) && !hasFooter();
}
case Section::Header: {
return isLastFocusItemInDelegate() && (size() == 0) && !hasFooter();
}
case Section::Delegate: {
return isLastFocusItemInDelegate() && (m_delegateIndex == size() - 1) && !hasFooter();
}
case Section::Footer: {
return isLastFocusItemInDelegate();
}
default: qWarning() << "Wrong section"; return true;
}
}
bool ListViewFocusController::isReturnNeeded() const
{
return m_isReturnNeeded;
}
@@ -1,70 +0,0 @@
#ifndef LISTVIEWFOCUSCONTROLLER_H
#define LISTVIEWFOCUSCONTROLLER_H
#include <QList>
#include <QObject>
#include <QQuickItem>
#include <QSharedPointer>
#include <QStack>
/*!
* \brief The ListViewFocusController class manages the focus of elements in ListView
* \details This class object moving focus to ListView's controls since ListView stores
* it's data implicitly and it could be got one by one.
*
* This class was made to store as less as possible data getting it from QML
* when it's needed.
*/
class ListViewFocusController : public QObject
{
Q_OBJECT
public:
explicit ListViewFocusController(QQuickItem *listView, QObject *parent = nullptr);
~ListViewFocusController();
void nextDelegate();
void previousDelegate();
void decrementIndex();
void focusNextItem();
void focusPreviousItem();
void resetFocusChain();
void reloadFocusChain();
bool isFirstFocusItemInListView() const;
bool isFirstFocusItemInDelegate() const;
bool isLastFocusItemInListView() const;
bool isLastFocusItemInDelegate() const;
bool isReturnNeeded() const;
private:
enum class Section {
Default,
Header,
Delegate,
Footer,
};
int size() const;
int currentIndex() const;
void setDelegateIndex(int index);
void viewAtCurrentIndex() const;
QQuickItem *itemAtIndex(const int index) const;
QQuickItem *currentDelegate() const;
QQuickItem *focusedItem() const;
bool hasHeader() const;
bool hasFooter() const;
QQuickItem *m_listView;
QList<QObject *> m_focusChain;
Section m_currentSection;
QQuickItem *m_header;
QQuickItem *m_footer;
QQuickItem *m_focusedItem; // Pointer to focused item on Delegate
qsizetype m_focusedItemIndex;
qsizetype m_delegateIndex;
bool m_isReturnNeeded;
QList<QString> m_currentSectionString;
};
#endif // LISTVIEWFOCUSCONTROLLER_H
+3 -34
View File
@@ -89,12 +89,7 @@ void PageController::keyPressEvent(Qt::Key key)
switch (key) { switch (key) {
case Qt::Key_Back: case Qt::Key_Back:
case Qt::Key_Escape: { case Qt::Key_Escape: {
if (m_drawerDepth) { emit escapePressed();
emit closeTopDrawer();
decrementDrawerDepth();
} else {
emit escapePressed();
}
break; break;
} }
default: return; default: return;
@@ -104,9 +99,9 @@ void PageController::keyPressEvent(Qt::Key key)
unsigned int PageController::getInitialPageNavigationBarColor() unsigned int PageController::getInitialPageNavigationBarColor()
{ {
if (m_serversController->getServersCount()) { if (m_serversController->getServersCount()) {
return 0xFF1C1D21; return 0xFF1F1F23;
} else { } else {
return 0xFF0E0E11; return 0xFF101012;
} }
} }
@@ -150,32 +145,6 @@ void PageController::closeApplication()
qApp->quit(); qApp->quit();
} }
void PageController::setDrawerDepth(const int depth)
{
if (depth >= 0) {
m_drawerDepth = depth;
}
}
int PageController::getDrawerDepth() const
{
return m_drawerDepth;
}
int PageController::incrementDrawerDepth()
{
return ++m_drawerDepth;
}
int PageController::decrementDrawerDepth()
{
if (m_drawerDepth == 0) {
return m_drawerDepth;
} else {
return --m_drawerDepth;
}
}
bool PageController::isEdgeToEdgeEnabled() bool PageController::isEdgeToEdgeEnabled()
{ {
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
@@ -41,11 +41,6 @@ public slots:
void closeApplication(); void closeApplication();
void setDrawerDepth(const int depth);
int getDrawerDepth() const;
int incrementDrawerDepth();
int decrementDrawerDepth();
bool isEdgeToEdgeEnabled(); bool isEdgeToEdgeEnabled();
int getStatusBarHeight(); int getStatusBarHeight();
int getNavigationBarHeight(); int getNavigationBarHeight();
@@ -88,7 +83,6 @@ signals:
void unsupportedConnectDrawerRequested(); void unsupportedConnectDrawerRequested();
void escapePressed(); void escapePressed();
void closeTopDrawer();
void imeHeightChanged(int height); void imeHeightChanged(int height);
void safeAreaTopMarginChanged(); void safeAreaTopMarginChanged();
@@ -100,8 +94,6 @@ private:
bool m_isTriggeredByConnectButton; bool m_isTriggeredByConnectButton;
int m_drawerDepth = 0;
mutable int m_cachedStatusBarHeight = -1; mutable int m_cachedStatusBarHeight = -1;
mutable int m_cachedNavigationBarHeight = -1; mutable int m_cachedNavigationBarHeight = -1;
mutable bool m_cachedEdgeToEdgeEnabled = false; mutable bool m_cachedEdgeToEdgeEnabled = false;
-150
View File
@@ -1,150 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Shapes
import Style 1.0
import "../Config"
import "../Controls2"
import "../Controls2/TextTypes"
Rectangle {
id: root
property real contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin
property bool isFocusable: true
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: AmneziaStyle.color.translucentSlateGray }
GradientStop { position: 1.0; color: AmneziaStyle.color.translucentOnyxBlack }
}
border.width: 1
border.color: AmneziaStyle.color.onyxBlack
radius: 13
visible: ServersUiController.isAdVisible
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Keys.onEnterPressed: {
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
}
Keys.onReturnPressed: {
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
}
RowLayout {
id: content
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 12
anchors.topMargin: 12
anchors.bottomMargin: 12
spacing: 20
ColumnLayout {
Layout.fillWidth: true
spacing: 4
CaptionTextType {
Layout.fillWidth: true
text: ServersUiController.adHeader
color: AmneziaStyle.color.paleGray
font.pixelSize: 14
font.weight: 700
textFormat: Text.RichText
}
CaptionTextType {
Layout.fillWidth: true
text: ServersUiController.adDescription
color: AmneziaStyle.color.mutedGray
wrapMode: Text.WordWrap
lineHeight: 18
lineHeightMode: Text.FixedHeight
font.pixelSize: 14
visible: text !== ""
}
}
Item {
implicitWidth: 40
implicitHeight: 40
Layout.alignment: Qt.AlignVCenter
Rectangle {
id: chevronBackground
anchors.fill: parent
radius: 12
color: AmneziaStyle.color.transparent
border.width: root.activeFocus ? 1 : 0
border.color: AmneziaStyle.color.paleGray
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.width {
PropertyAnimation { duration: 200 }
}
}
Image {
anchors.centerIn: parent
source: "qrc:/images/controls/chevron-right.svg"
sourceSize: Qt.size(24, 24)
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: {
chevronBackground.color = AmneziaStyle.color.slateGray
}
onExited: {
chevronBackground.color = AmneziaStyle.color.transparent
}
onPressedChanged: {
chevronBackground.color = pressed ? AmneziaStyle.color.charcoalGray : containsMouse ? AmneziaStyle.color.slateGray : AmneziaStyle.color.transparent
}
onClicked: function() {
root.forceActiveFocus()
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
}
}
}
+22 -32
View File
@@ -1,15 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Style 1.0 import Style 1.0
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes"
Item { Item {
id: root id: root
property bool enabled: true
property string placeholderText: "" property string placeholderText: ""
property alias textField: searchField.textField property alias textField: searchField.textField
@@ -17,57 +15,49 @@ Item {
signal moreClicked() signal moreClicked()
implicitWidth: 360 implicitWidth: 360
implicitHeight: 96 implicitHeight: content.implicitHeight + 32 + PageController.safeAreaBottomMargin
Rectangle { Rectangle {
id: background
anchors.fill: parent anchors.fill: parent
color: "#0E0F12"
color: AmneziaStyle.color.bgBase
opacity: 0.85 opacity: 0.85
z: -1
} }
RowLayout { RowLayout {
id: addSiteButton id: content
enabled: root.enabled anchors.fill: parent
spacing: 2 anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
anchors { spacing: 8
fill: parent
topMargin: 16
leftMargin: 16
rightMargin: 16
bottomMargin: 24
}
TextFieldWithHeaderType { TextFieldType {
id: searchField id: searchField
Layout.fillWidth: true Layout.fillWidth: true
rightButtonClickedOnEnter: true
textField.placeholderText: root.placeholderText rightButtonClickedOnEnter: true
placeholderText: root.placeholderText
buttonImageSource: "qrc:/images/controls/plus.svg" buttonImageSource: "qrc:/images/controls/plus.svg"
buttonJoined: true
clickedFunc: function() { clickedFunc: function() {
root.addClicked(textField.text) root.addClicked(searchField.textField.text)
textField.text = "" searchField.textField.text = ""
} }
} }
ImageButtonType { IconButtonType {
id: addSiteButtonImage implicitWidth: 64
implicitWidth: 56 implicitHeight: 64
implicitHeight: 56
image: "qrc:/images/controls/more-vertical.svg" icon.source: "qrc:/images/controls/more-vertical.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: root.moreClicked() onClicked: root.moreClicked()
Keys.onReturnPressed: addSiteButtonImage.clicked()
Keys.onEnterPressed: addSiteButtonImage.clicked()
} }
} }
} }
+1 -2
View File
@@ -5,7 +5,7 @@ import QtQuick.Layouts
import "../Controls2" import "../Controls2"
TextFieldWithHeaderType { TextFieldType {
id: root id: root
property var scroller: null property var scroller: null
@@ -14,7 +14,6 @@ TextFieldWithHeaderType {
signal edited(string text) signal edited(string text)
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
+29 -35
View File
@@ -1,64 +1,58 @@
import QtQuick import QtQuick
import QtQuick.Controls.impl
import QtQuick.Layouts import QtQuick.Layouts
import Style 1.0 import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
RowLayout { RowLayout {
id: root id: root
property string iconSource: "" property string iconSource
property string titleText: "" property string title
property string bodyText: "" property string subtitle
property bool link: false property bool isLink: false
readonly property string bodyLineText: root.link && root.bodyText.length > 0 ? "@" + root.bodyText : root.bodyText spacing: 16
readonly property bool bodyClickable: root.link && root.bodyText.length > 0 IconImage {
spacing: 12
Image {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.preferredWidth: 22 Layout.preferredWidth: 24
Layout.preferredHeight: 22 Layout.preferredHeight: 24
source: root.iconSource source: root.iconSource
fillMode: Image.PreserveAspectFit sourceSize: Qt.size(24, 24)
color: AmneziaStyle.color.iconDefault
} }
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 4 spacing: 4
LabelTextType { H3TextType {
Layout.fillWidth: true Layout.fillWidth: true
text: root.titleText
color: AmneziaStyle.color.paleGray text: root.title
font.pixelSize: 16
font.weight: Font.DemiBold
wrapMode: Text.Wrap
} }
Item { SmallTextType {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: bodyLabel.implicitHeight
LabelTextType { visible: !root.isLink && text !== ""
id: bodyLabel text: root.subtitle
width: parent.width }
text: root.bodyLineText
color: root.link ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.mutedGray
font.pixelSize: 14
wrapMode: Text.Wrap
}
MouseArea { LinkButtonType {
anchors.fill: bodyLabel Layout.alignment: Qt.AlignLeft
visible: root.bodyClickable Layout.maximumWidth: parent.width
cursorShape: Qt.PointingHandCursor
onClicked: Qt.openUrlExternally("https://t.me/" + root.bodyText) visible: root.isLink && root.subtitle !== ""
} text: "@" + root.subtitle
onClicked: Qt.openUrlExternally("https://t.me/" + root.subtitle)
} }
} }
} }
+15 -18
View File
@@ -1,39 +1,36 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import "."
import Style 1.0 import Style 1.0
Rectangle { Pane {
id: root id: root
property var benefitsModel: null property var benefitsModel: null
visible: benefitsModel && benefitsModel.rowCount() > 0 visible: root.benefitsModel && root.benefitsModel.rowCount() > 0
radius: 16 padding: 16
color: AmneziaStyle.color.benefitsPanelBackground
implicitHeight: inner.implicitHeight + 24
ColumnLayout { background: Rectangle {
id: inner radius: 16
color: AmneziaStyle.color.bgSurface
}
anchors.left: parent.left contentItem: ColumnLayout {
anchors.right: parent.right spacing: 16
anchors.top: parent.top
anchors.margins: 12
spacing: 20
Repeater { Repeater {
model: benefitsModel model: root.benefitsModel
delegate: BenefitRow { delegate: BenefitRow {
Layout.fillWidth: true Layout.fillWidth: true
iconSource: model.icon iconSource: model.icon
titleText: model.title title: model.title
bodyText: model.body subtitle: model.body
link: !!model.link isLink: !!model.link
} }
} }
} }
@@ -0,0 +1,94 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
SheetType {
id: root
property string headerText
property string contentText
readonly property alias textArea: contentArea
expandedHeight: windowHeight * 0.9
Item {
anchors.fill: parent
IconButtonType {
id: backButton
x: 16
y: 8
size: IconButtonType.Small
icon.source: "qrc:/images/controls/arrow-left.svg"
onClicked: root.close()
}
FlickableType {
id: flickable
anchors.top: backButton.bottom
anchors.topMargin: 4
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
ColumnLayout {
id: content
width: flickable.width
spacing: 0
SectionHeaderType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: root.headerText
}
TextArea {
id: contentArea
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16 + PageController.safeAreaBottomMargin
padding: 0
leftPadding: 0
rightPadding: 0
leftInset: 0
rightInset: 0
readOnly: true
activeFocusOnTab: false
color: AmneziaStyle.color.textSecondary
selectionColor: AmneziaStyle.color.bgStateLayerAccent
selectedTextColor: AmneziaStyle.color.textSecondary
font.pixelSize: 16
font.weight: 400
font.family: "PT Root UI VF"
font.letterSpacing: -0.4
text: root.contentText
wrapMode: Text.Wrap
background: Item {}
}
}
}
}
}
+87 -125
View File
@@ -1,50 +1,25 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Effects
import QtQuick.Shapes import QtQuick.Shapes
import Qt5Compat.GraphicalEffects
import ConnectionState 1.0
import PageEnum 1.0
import Style 1.0 import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
Button { Button {
id: root id: root
property string defaultButtonColor: AmneziaStyle.color.paleGray property bool connected: ConnectionController.isConnected
property string progressButtonColor: AmneziaStyle.color.paleGray property bool inProgress: ConnectionController.isConnectionInProgress
property string connectedButtonColor: AmneziaStyle.color.goldenApricot
property bool buttonActiveFocus: activeFocus && (Qt.platform.os !== "android" || SettingsController.isOnTv())
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitWidth: 190 implicitWidth: 190
implicitHeight: 190 implicitHeight: 190
padding: 0
hoverEnabled: true
text: ConnectionController.connectionStateText text: ConnectionController.connectionStateText
Connections { Connections {
@@ -55,136 +30,123 @@ Button {
} }
} }
// enabled: !ConnectionController.isConnectionInProgress QtObject {
id: d
readonly property real ringRadius: (Math.min(root.width, root.height) - 2) / 2
readonly property color ringColor: root.connected && !root.inProgress ? AmneziaStyle.color.accentLight
: AmneziaStyle.color.borderEmphasis
readonly property int glowMargin: 24
}
background: Item { background: Item {
implicitWidth: parent.width implicitWidth: 190
implicitHeight: parent.height implicitHeight: 190
transformOrigin: Item.Center
Shape { Shape {
id: backgroundCircle anchors.fill: parent
width: parent.implicitWidth
height: parent.implicitHeight visible: root.inProgress
anchors.bottom: parent.bottom opacity: 0.1
anchors.right: parent.right preferredRendererType: Shape.CurveRenderer
layer.enabled: true
layer.samples: 4
layer.smooth: true
layer.effect: DropShadow {
anchors.fill: backgroundCircle
horizontalOffset: 0
verticalOffset: 0
radius: 10
samples: 25
color: root.buttonActiveFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
source: backgroundCircle
}
ShapePath { ShapePath {
fillColor: AmneziaStyle.color.transparent fillColor: AmneziaStyle.color.transparent
strokeColor: AmneziaStyle.color.paleGray strokeColor: AmneziaStyle.color.borderEmphasis
strokeWidth: root.buttonActiveFocus ? 1 : 0 strokeWidth: 1
capStyle: ShapePath.RoundCap
PathAngleArc { PathAngleArc {
centerX: backgroundCircle.width / 2 centerX: root.width / 2
centerY: backgroundCircle.height / 2 centerY: root.height / 2
radiusX: 94 radiusX: d.ringRadius
radiusY: 94 radiusY: d.ringRadius
startAngle: 0 startAngle: 0
sweepAngle: 360 sweepAngle: 360
} }
} }
ShapePath {
fillColor: AmneziaStyle.color.transparent
strokeColor: {
if (ConnectionController.isConnectionInProgress) {
return AmneziaStyle.color.darkCharcoal
} else if (ConnectionController.isConnected) {
return connectedButtonColor
} else {
return defaultButtonColor
}
}
strokeWidth: root.buttonActiveFocus ? 2 : 3
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: 93 - (root.buttonActiveFocus ? 2 : 0)
radiusY: 93 - (root.buttonActiveFocus ? 2 : 0)
startAngle: 0
sweepAngle: 360
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
} }
Shape { Item {
id: shape anchors.fill: parent
width: parent.implicitWidth
height: parent.implicitHeight
anchors.bottom: parent.bottom
anchors.right: parent.right
layer.enabled: true
layer.samples: 4
visible: ConnectionController.isConnectionInProgress Item {
id: ringSource
ShapePath { anchors.fill: parent
fillColor: AmneziaStyle.color.transparent anchors.margins: -d.glowMargin
strokeColor: AmneziaStyle.color.paleGray
strokeWidth: 3
capStyle: ShapePath.RoundCap
PathAngleArc { Shape {
centerX: shape.width / 2 anchors.fill: parent
centerY: shape.height / 2
radiusX: 93 preferredRendererType: Shape.CurveRenderer
radiusY: 93
startAngle: 245 ShapePath {
sweepAngle: -180 fillColor: AmneziaStyle.color.transparent
strokeColor: d.ringColor
strokeWidth: 2
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: ringSource.width / 2
centerY: ringSource.height / 2
radiusX: d.ringRadius
radiusY: d.ringRadius
startAngle: 100
sweepAngle: root.inProgress ? 160 : 360
}
}
} }
} }
RotationAnimator { MultiEffect {
target: shape anchors.fill: ringSource
running: ConnectionController.isConnectionInProgress
source: ringSource
shadowEnabled: true
shadowColor: d.ringColor
shadowBlur: 0.6
shadowHorizontalOffset: 0
shadowVerticalOffset: 0
blurMax: 16
}
RotationAnimator on rotation {
running: root.inProgress
from: 0 from: 0
to: 360 to: 360
loops: Animation.Infinite loops: Animation.Infinite
duration: 1000 duration: 1000
} }
} }
FocusRingType {
radius: root.width / 2
visible: root.visualFocus
}
} }
contentItem: Text { contentItem: ButtonTextType {
height: 24 text: root.text
font.family: "PT Root UI VF" color: root.connected && !root.inProgress ? AmneziaStyle.color.textAccent : AmneziaStyle.color.textPrimary
font.weight: 700 lineHeight: 30 + LanguageUiController.getLineHeightAppend()
font.pixelSize: 20 font.pixelSize: 20
color: ConnectionController.isConnected ? connectedButtonColor : defaultButtonColor leftPadding: 16
text: root.text rightPadding: 16
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
onClicked: { onClicked: {
ConnectionController.connectButtonClicked() ConnectionController.connectButtonClicked()
} }
Keys.onEnterPressed: this.clicked() Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: this.clicked() Keys.onReturnPressed: root.clicked()
} }
@@ -8,13 +8,12 @@ import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
import "../Config" import "../Config"
DrawerType2 { SheetType {
id: root id: root
width: parent.width expandedHeight: content.implicitHeight + 16 + PageController.safeAreaBottomMargin
height: parent.height
expandedStateContent: ColumnLayout { ColumnLayout {
id: content id: content
anchors.top: parent.top anchors.top: parent.top
@@ -22,13 +21,9 @@ DrawerType2 {
anchors.right: parent.right anchors.right: parent.right
spacing: 0 spacing: 0
Component.onCompleted: { SectionHeaderType {
root.expandedHeight = content.implicitHeight + 32
}
Header2Type {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.bottomMargin: 16 Layout.bottomMargin: 16
@@ -36,35 +31,34 @@ DrawerType2 {
headerText: qsTr("Add new connection") headerText: qsTr("Add new connection")
} }
LabelWithButtonType { ListItemType {
id: ip id: ip
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Configure your server") text: qsTr("Configure your server")
rightImageSource: "qrc:/images/controls/chevron-right.svg" trailingIconSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { onClicked: {
PageController.goToPage(PageEnum.PageSetupWizardCredentials) PageController.goToPage(PageEnum.PageSetupWizardCredentials)
root.closeTriggered() root.close()
} }
} }
DividerType {} ListItemType {
LabelWithButtonType {
id: qrCode id: qrCode
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Open config file, key or QR code") text: qsTr("Open config file, key or QR code")
rightImageSource: "qrc:/images/controls/chevron-right.svg" trailingIconSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { onClicked: {
PageController.goToPage(PageEnum.PageSetupWizardConfigSource) PageController.goToPage(PageEnum.PageSetupWizardConfigSource)
root.closeTriggered() root.close()
} }
} }
DividerType {}
} }
} }
@@ -0,0 +1,74 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
Item {
id: root
property string label
property string value
property string copiedText
property bool showSeparator: true
implicitWidth: content.implicitWidth + 28
implicitHeight: Math.max(72, content.implicitHeight + 24)
RowLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 16
anchors.rightMargin: 12
spacing: 0
ColumnLayout {
Layout.fillWidth: true
spacing: 4
CaptionTextType {
Layout.fillWidth: true
text: root.label
}
BodyTextType {
Layout.fillWidth: true
text: root.value
color: AmneziaStyle.color.textPrimary
wrapMode: Text.WrapAnywhere
}
}
IconButtonType {
icon.source: "qrc:/images/controls/copy.svg"
Accessible.name: root.label
onClicked: {
GC.copyToClipBoard(root.value)
if (root.copiedText !== "") {
PageController.showNotificationMessage(root.copiedText)
}
}
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 1
visible: root.showSeparator
color: AmneziaStyle.color.borderSoft
}
}
@@ -1,110 +1,32 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0 import Style 1.0
import "../Controls2/TextTypes" import "../Controls2"
Item { EmptyStateType {
id: root id: root
property bool isSearchResult: true property bool isSearchResult: true
property string categoryName: "" property string categoryName: ""
signal showAllRequested() signal showAllRequested()
ColumnLayout { iconColor: AmneziaStyle.color.accentLight
anchors.centerIn: parent
width: Math.min(parent.width - 32, 320)
spacing: 16 title: {
if (root.categoryName !== "") {
Item { return qsTr("Nothing found in \"%1\"").arg(root.categoryName)
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Image {
id: searchIcon
anchors.fill: parent
source: "qrc:/images/controls/search.svg"
visible: false
}
ColorOverlay {
anchors.fill: searchIcon
source: searchIcon
color: AmneziaStyle.color.goldenApricot
}
} }
return root.isSearchResult ? qsTr("Nothing found. Check the spelling or change your query")
: qsTr("No locations available yet")
}
ParagraphTextType { ButtonType {
Layout.fillWidth: true visible: root.isSearchResult || root.categoryName !== ""
variant: ButtonType.Text
text: qsTr("Show all")
color: AmneziaStyle.color.textPrimary onClicked: root.showAllRequested()
horizontalAlignment: Text.AlignHCenter
text: {
if (root.categoryName !== "") {
return qsTr("Nothing found in \"%1\"").arg(root.categoryName)
}
return root.isSearchResult ? qsTr("Nothing found. Check the spelling or change your query")
: qsTr("No locations available yet")
}
}
Item {
id: showAll
property bool isFocusable: root.isSearchResult || root.categoryName !== ""
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: showAllText.implicitWidth + 16
Layout.preferredHeight: showAllText.implicitHeight + 16
visible: root.isSearchResult || root.categoryName !== ""
Keys.onEnterPressed: root.showAllRequested()
Keys.onReturnPressed: root.showAllRequested()
Keys.onSpacePressed: root.showAllRequested()
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Rectangle {
anchors.fill: parent
color: AmneziaStyle.color.transparent
radius: 8
border.width: showAll.activeFocus ? 1 : 0
border.color: AmneziaStyle.color.paleGray
}
ParagraphTextType {
id: showAllText
anchors.centerIn: parent
color: AmneziaStyle.color.goldenApricot
horizontalAlignment: Text.AlignHCenter
text: qsTr("Show all")
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.showAllRequested()
}
}
} }
} }
@@ -0,0 +1,26 @@
import QtQuick
import Style 1.0
import "../Controls2"
ListItemType {
id: root
property bool favorite: false
property string countryFlagCode
signal favoriteToggled()
leadingSlotWidth: 24
icon.source: root.countryFlagCode !== "" ? "qrc:/countriesFlags/images/flagKit/" + root.countryFlagCode + ".svg" : ""
icon.width: 21
icon.height: 15
icon.color: AmneziaStyle.color.transparent
FavoriteButton {
favorite: root.favorite
onClicked: root.favoriteToggled()
}
}
@@ -1,107 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
Rectangle {
id: root
property alias textField: input
property alias text: input.text
readonly property alias displayText: input.displayText
signal cleared()
implicitHeight: 64
color: AmneziaStyle.color.surfaceBase
radius: 16
border.width: 1
border.color: input.activeFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.borderSoft
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
function clear() {
input.text = ""
root.cleared()
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 8
spacing: 8
TextField {
id: input
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Layout.fillWidth: true
color: AmneziaStyle.color.textPrimary
placeholderText: qsTr("Country or region")
placeholderTextColor: AmneziaStyle.color.mutedGray
selectionColor: AmneziaStyle.color.richBrown
selectedTextColor: AmneziaStyle.color.textPrimary
font.pixelSize: 16
font.weight: 400
font.family: "PT Root UI VF"
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
maximumLength: 64
topPadding: 0
bottomPadding: 0
leftPadding: 0
rightPadding: 0
background: Rectangle {
color: AmneziaStyle.color.transparent
}
Keys.onEscapePressed: function(event) {
if (input.text === "") {
event.accepted = false
return
}
root.clear()
}
ContextMenu.menu: ContextMenuType {
textObj: input
}
}
ImageButtonType {
visible: input.text !== ""
implicitWidth: 40
implicitHeight: 40
hoverEnabled: true
image: "qrc:/images/controls/close.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: root.clear()
Keys.onEnterPressed: root.clear()
Keys.onReturnPressed: root.clear()
}
}
}
+9 -13
View File
@@ -1,8 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Style 1.0
import "../Controls2" import "../Controls2"
RowLayout { RowLayout {
@@ -17,37 +15,35 @@ RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.topMargin: 12 Layout.topMargin: 16
spacing: 8 spacing: 8
CountrySearchField { SearchFieldType {
id: searchField id: searchField
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Country or region")
maximumLength: 64
onTextChanged: { onTextChanged: {
const current = searchField.text const current = searchField.text
const clean = root.listModel.sanitizeSearchFieldText(current) const clean = root.listModel.sanitizeSearchFieldText(current)
if (clean !== current) { if (clean !== current) {
const cursor = searchField.textField.cursorPosition - (current.length - clean.length) const cursor = searchField.cursorPosition - (current.length - clean.length)
searchField.text = clean searchField.text = clean
searchField.textField.cursorPosition = Math.max(0, Math.min(cursor, clean.length)) searchField.cursorPosition = Math.max(0, Math.min(cursor, clean.length))
} }
} }
onDisplayTextChanged: root.listModel.searchText = root.listModel.sanitizeSearchFieldText(searchField.displayText) onDisplayTextChanged: root.listModel.searchText = root.listModel.sanitizeSearchFieldText(searchField.displayText)
} }
ImageButtonType { IconButtonType {
objectName: "sortButton" objectName: "sortButton"
implicitWidth: 64 icon.source: "qrc:/images/controls/sort-desc.svg"
implicitHeight: 64
hoverEnabled: true
image: "qrc:/images/controls/sort-desc.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: root.sortRequested() onClicked: root.sortRequested()
} }
@@ -1,10 +1,11 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import QtQuick.Controls.impl
import Style 1.0 import Style 1.0
import "../Config" import "../Config"
import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
Item { Item {
@@ -12,7 +13,7 @@ Item {
property var listModel: null property var listModel: null
property bool isFocusable: root.isRealSection && !root.isPinnedOverlay activeFocusOnTab: root.isRealSection && !root.isPinnedOverlay
property string sectionKey: "" property string sectionKey: ""
property bool isPinnedOverlay: false property bool isPinnedOverlay: false
@@ -47,8 +48,16 @@ Item {
readonly property string subsubregionId: root.isRealSection ? root.listModel.sectionSubsubregionId(root.sectionKey) : "" readonly property string subsubregionId: root.isRealSection ? root.listModel.sectionSubsubregionId(root.sectionKey) : ""
readonly property int level: root.subsubregionId !== "" ? 3 : (root.subregionId !== "" ? 2 : 1) readonly property int level: root.subsubregionId !== "" ? 3 : (root.subregionId !== "" ? 2 : 1)
readonly property color titleColor: root.level === 1 ? AmneziaStyle.color.textPrimary readonly property color chevronColor: root.level === 1 ? AmneziaStyle.color.iconDefault
: AmneziaStyle.color.textTertiary : AmneziaStyle.color.iconSecondary
readonly property string displayTitle: {
if (!root.isRealSection) {
return ""
}
root.listModel.layoutRevision
return "%1 · %2".arg(root.title).arg(root.listModel.sectionCount(root.sectionKey))
}
readonly property bool collapsed: { readonly property bool collapsed: {
if (!root.isRealSection) { if (!root.isRealSection) {
@@ -70,7 +79,7 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: root.isPinnedOverlay ? AmneziaStyle.color.midnightBlack color: root.isPinnedOverlay ? AmneziaStyle.color.bgBase
: AmneziaStyle.color.transparent : AmneziaStyle.color.transparent
} }
@@ -82,30 +91,23 @@ Item {
anchors.bottomMargin: root.contentBottomMargin anchors.bottomMargin: root.contentBottomMargin
spacing: 8 spacing: 8
ParagraphTextType { BodyTextType {
Layout.fillWidth: true Layout.fillWidth: true
color: root.titleColor visible: root.level === 1
lineHeight: (root.level === 1 ? 24 : 18) + LanguageUiController.getLineHeightAppend() color: AmneziaStyle.color.textPrimary
font.pixelSize: root.level === 1 ? 16 : 14 font.weight: 600
font.weight: root.level === 1 ? 700 : 400 text: root.displayTitle
font.letterSpacing: root.level === 1 ? -0.4 : 0
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: {
if (!root.isRealSection) {
return ""
}
root.listModel.layoutRevision
if (!root.collapsed) {
return root.title
}
return "%1 (%2)".arg(root.title).arg(root.listModel.sectionCount(root.sectionKey))
}
} }
Image { SmallTextType {
Layout.fillWidth: true
visible: root.level !== 1
text: root.displayTitle
}
IconImage {
Layout.preferredWidth: 24 Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.leftMargin: 10 Layout.leftMargin: 10
@@ -113,13 +115,8 @@ Item {
visible: root.canToggle visible: root.canToggle
source: root.collapsed ? "qrc:/images/controls/chevron-down.svg" source: root.collapsed ? "qrc:/images/controls/chevron-down.svg"
: "qrc:/images/controls/chevron-up.svg" : "qrc:/images/controls/chevron-up.svg"
sourceSize: Qt.size(24, 24)
layer { color: root.chevronColor
enabled: true
effect: ColorOverlay {
color: root.titleColor
}
}
} }
} }
@@ -144,22 +141,9 @@ Item {
Keys.onReturnPressed: root.toggle() Keys.onReturnPressed: root.toggle()
Keys.onSpacePressed: root.toggle() Keys.onSpacePressed: root.toggle()
Keys.onTabPressed: { FocusRingType {
FocusController.nextKeyTabItem() anchors.margins: 0
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Rectangle {
anchors.fill: parent
anchors.margins: 2
color: AmneziaStyle.color.transparent
radius: 8 radius: 8
visible: root.activeFocus
border.width: root.activeFocus ? 1 : 0
border.color: AmneziaStyle.color.paleGray
} }
} }
@@ -1,10 +1,7 @@
import QtQuick import QtQuick
import Style 1.0
import "../Config" import "../Config"
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes"
ListViewType { ListViewType {
id: root id: root
@@ -14,10 +11,10 @@ ListViewType {
readonly property var chips: root.listModel !== null ? root.listModel.useCases : [] readonly property var chips: root.listModel !== null ? root.listModel.useCases : []
visible: root.chips.length > 1 visible: root.chips.length > 1
isFocusable: root.visible
implicitHeight: root.visible ? 36 : 0 implicitHeight: root.visible ? 34 : 0
clip: false
orientation: ListView.Horizontal orientation: ListView.Horizontal
spacing: 8 spacing: 8
leftMargin: 16 leftMargin: 16
@@ -27,74 +24,18 @@ ListViewType {
model: root.chips model: root.chips
delegate: Item { delegate: ChipType {
id: delegateRoot
required property var modelData required property var modelData
implicitWidth: chip.implicitWidth readonly property string useCaseId: modelData.useCaseId
implicitHeight: 36
Rectangle { checked: root.listModel !== null && root.listModel.activeUseCaseId === useCaseId
id: chip text: "%1 · %2".arg(CountryRegionNames.useCaseName(root.listModel, useCaseId)).arg(modelData.count)
readonly property string useCaseId: delegateRoot.modelData.useCaseId onClicked: {
readonly property bool active: root.listModel !== null if (root.listModel !== null) {
&& root.listModel.activeUseCaseId === chip.useCaseId root.listModel.activeUseCaseId = useCaseId
property bool isFocusable: true
anchors.fill: parent
implicitWidth: label.implicitWidth + 32
radius: height / 2
color: chipHover.hovered ? AmneziaStyle.color.surfaceHovered
: AmneziaStyle.color.surfaceBase
border.width: chip.activeFocus ? 2 : (chip.active ? 1 : 0)
border.color: chip.active ? AmneziaStyle.color.goldenApricot
: AmneziaStyle.color.paleGray
function select() {
if (root.listModel !== null) {
root.listModel.activeUseCaseId = chip.useCaseId
}
} }
ParagraphTextType {
id: label
anchors.centerIn: parent
color: chip.active ? AmneziaStyle.color.textPrimary : AmneziaStyle.color.paleGray
wrapMode: Text.NoWrap
lineHeightMode: Text.ProportionalHeight
lineHeight: 1.0
font.letterSpacing: -0.4
text: "%1 · %2".arg(CountryRegionNames.useCaseName(root.listModel, chip.useCaseId))
.arg(delegateRoot.modelData.count)
}
HoverHandler {
id: chipHover
cursorShape: Qt.PointingHandCursor
}
TapHandler {
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: chip.select()
}
Keys.onEnterPressed: chip.select()
Keys.onReturnPressed: chip.select()
Keys.onSpacePressed: chip.select()
Keys.onTabPressed: FocusController.nextKeyTabItem()
Keys.onBacktabPressed: FocusController.previousKeyTabItem()
Keys.onLeftPressed: FocusController.nextKeyLeftItem()
Keys.onRightPressed: FocusController.nextKeyRightItem()
Keys.onUpPressed: FocusController.nextKeyUpItem()
Keys.onDownPressed: FocusController.nextKeyDownItem()
} }
} }
} }
@@ -0,0 +1,13 @@
import QtQuick
import "../Controls2"
IconButtonType {
id: root
property bool favorite: false
icon.source: root.favorite ? "qrc:/images/controls/star-filled.svg" : "qrc:/images/controls/star.svg"
Accessible.name: root.favorite ? qsTr("Remove from favorites") : qsTr("Add to favorites")
}
@@ -1,98 +1,55 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0 import PageEnum 1.0
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes"
ListViewType { ListViewType {
id: menuContent id: root
property var rootWidth signal activated()
property var selectedText
width: rootWidth leftMargin: 16
anchors.top: parent.top rightMargin: 16
anchors.bottom: parent.bottom bottomMargin: 16
ButtonGroup { ButtonGroup {
id: containersRadioButtonGroup id: containersRadioButtonGroup
} }
delegate: Item { delegate: RadioItemType {
implicitWidth: rootWidth width: root.width - root.leftMargin - root.rightMargin
implicitHeight: content.implicitHeight
ColumnLayout { text: name
id: content subtitle: description
showSeparator: index < root.count - 1
anchors.fill: parent ButtonGroup.group: containersRadioButtonGroup
anchors.rightMargin: 16
anchors.leftMargin: 16
VerticalRadioButton { icon.source: isInstalled ? "" : "qrc:/images/controls/download.svg"
id: containerRadioButton
Layout.fillWidth: true checkable: isInstalled && !ConnectionController.isConnected
checked: root.model.mapToSource(index) === ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId)
text: name onClicked: {
descriptionText: description if (ConnectionController.isConnected && isInstalled) {
PageController.showNotificationMessage(qsTr("Unable change protocol while there is an active connection"))
ButtonGroup.group: containersRadioButtonGroup return
imageSource: "qrc:/images/controls/download.svg"
showImage: !isInstalled
checkable: isInstalled && !ConnectionController.isConnected
checked: proxyDefaultServerContainersModel.mapToSource(index) === ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId)
onClicked: {
if (ConnectionController.isConnected && isInstalled) {
PageController.showNotificationMessage(qsTr("Unable change protocol while there is an active connection"))
return
}
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
if (!isInstalled) {
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
ServersUiController.processedContainerIndex = containerIndex
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
containersDropDown.closeTriggered()
return
}
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
}
MouseArea {
anchors.fill: containerRadioButton
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
if (checkable) {
checked = true
}
containerRadioButton.clicked()
}
Keys.onReturnPressed: {
if (checkable) {
checked = true
}
containerRadioButton.clicked()
}
} }
DividerType { var containerIndex = root.model.mapToSource(index)
Layout.fillWidth: true
if (!isInstalled) {
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
ServersUiController.processedContainerIndex = containerIndex
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
root.activated()
return
} }
root.activated()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
} }
} }
} }
+120
View File
@@ -0,0 +1,120 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
AbstractButton {
id: root
property string leftImageSource
property color leftImageColor: AmneziaStyle.color.iconDefault
property int leftImageWidth: 24
property int leftImageHeight: 24
property string rightImageSource
property bool largeText: false
property int maximumLineCount: 1
implicitWidth: implicitContentWidth + leftPadding + rightPadding
implicitHeight: Math.max(40, implicitContentHeight + topPadding + bottomPadding)
horizontalPadding: 16
verticalPadding: 8
hoverEnabled: enabled
focusPolicy: enabled ? Qt.TabFocus : Qt.NoFocus
Accessible.name: root.text
background: Rectangle {
radius: 16
color: root.enabled && (root.hovered || root.down) ? AmneziaStyle.color.bgStateLayer : AmneziaStyle.color.transparent
border.width: root.enabled && root.down ? 1 : 0
border.color: AmneziaStyle.color.borderEmphasis
Behavior on color {
ColorAnimation { duration: 150 }
}
FocusRingType {
radius: 16
visible: root.visualFocus
}
}
contentItem: Item {
implicitWidth: row.implicitWidth
implicitHeight: row.implicitHeight
RowLayout {
id: row
anchors.centerIn: parent
width: Math.min(implicitWidth, parent.width)
spacing: 8
Image {
Layout.preferredWidth: root.leftImageWidth
Layout.preferredHeight: root.leftImageHeight
visible: root.leftImageSource !== "" && root.leftImageColor.a === 0
source: visible ? root.leftImageSource : ""
sourceSize: Qt.size(root.leftImageWidth, root.leftImageHeight)
fillMode: Image.PreserveAspectFit
}
IconImage {
Layout.preferredWidth: root.leftImageWidth
Layout.preferredHeight: root.leftImageHeight
visible: root.leftImageSource !== "" && root.leftImageColor.a > 0
source: visible ? root.leftImageSource : ""
sourceSize: Qt.size(root.leftImageWidth, root.leftImageHeight)
color: root.leftImageColor
}
BodyTextType {
Layout.fillWidth: true
visible: root.largeText
text: root.text
color: AmneziaStyle.color.textTertiary
maximumLineCount: root.maximumLineCount
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
SmallTextType {
Layout.fillWidth: true
visible: !root.largeText
text: root.text
maximumLineCount: root.maximumLineCount
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
IconImage {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: root.rightImageSource !== ""
source: root.rightImageSource
sourceSize: Qt.size(16, 16)
color: AmneziaStyle.color.iconDefault
}
}
}
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
}
@@ -0,0 +1,125 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
ColumnLayout {
id: root
property bool expanded: false
property bool pressed: false
property bool outdatedWarningVisible: false
property bool protocolSelectionVisible: false
property bool protocolSelectable: false
property string protocolText
signal expandRequested()
signal serverInfoRequested()
signal protocolSelectionRequested()
objectName: "collapsedColumnLayout"
spacing: 0
RowLayout {
objectName: "rowLayout"
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
spacing: 10
H1TextType {
objectName: "collapsedButtonHeader"
Layout.maximumWidth: root.width - 32 - (chevron.visible ? chevron.width + 10 : 0)
maximumLineCount: 1
wrapMode: Text.NoWrap
elide: Qt.ElideRight
text: ServersUiController.defaultServerName
horizontalAlignment: Qt.AlignHCenter
}
IconButtonType {
id: chevron
objectName: "collapsedButtonChevron"
visible: !root.expanded
hoverEnabled: false
down: pressed || root.pressed
size: IconButtonType.Small
icon.source: "qrc:/images/controls/chevron-down.svg"
onClicked: root.expandRequested()
}
}
HomeInfoButton {
objectName: "serverDescriptionButton"
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 6
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: (root.outdatedWarningVisible || root.protocolSelectionVisible) ? 8 : 16
enabled: ServersUiController.defaultServerImagePathCollapsed !== ""
largeText: true
maximumLineCount: 2
text: ServersUiController.defaultServerDescriptionCollapsed
leftImageSource: ServersUiController.defaultServerImagePathCollapsed
leftImageColor: AmneziaStyle.color.transparent
leftImageWidth: 20
leftImageHeight: 15
rightImageSource: enabled ? (root.expanded ? "qrc:/images/controls/chevron-down.svg" : "qrc:/images/controls/chevron-right.svg") : ""
onClicked: root.serverInfoRequested()
}
NotificationType {
objectName: "outdatedContainerWarning"
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
visible: root.outdatedWarningVisible
variant: NotificationType.Warning
text: qsTr("AmneziaWG 2.0 is outdated and no longer supported. Continued use requires a fresh installation of the AmneziaWG 3.1 container.")
}
HomeInfoButton {
objectName: "protocolButton"
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth
Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
visible: root.protocolSelectionVisible
enabled: root.protocolSelectable
largeText: true
text: root.protocolText
leftImageSource: "qrc:/images/controls/arrow-left-right.svg"
leftImageColor: AmneziaStyle.color.iconSecondary
rightImageSource: enabled ? "qrc:/images/controls/chevron-down.svg" : ""
onClicked: root.protocolSelectionRequested()
}
}
@@ -8,15 +8,14 @@ import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
import "../Config" import "../Config"
DrawerType2 { SheetType {
id: root id: root
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android" property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
anchors.fill: parent expandedHeight: Math.min(content.implicitHeight + 24 + PageController.safeAreaBottomMargin, windowHeight * 0.9)
expandedHeight: parent.height * 0.9
expandedStateContent: ColumnLayout { ColumnLayout {
id: content id: content
anchors.top: parent.top anchors.top: parent.top
@@ -24,74 +23,67 @@ DrawerType2 {
anchors.right: parent.right anchors.right: parent.right
spacing: 0 spacing: 0
Header2Type { SectionHeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.bottomMargin: 16 Layout.bottomMargin: 32
headerText: qsTr("Split tunneling") headerText: qsTr("Split tunneling")
descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others") descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others")
} }
LabelWithButtonType { ListItemType {
id: splitTunnelingSwitch id: splitTunnelingSwitch
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16
visible: ServersUiController.isDefaultServerDefaultContainerHasSplitTunneling visible: ServersUiController.isDefaultServerDefaultContainerHasSplitTunneling
text: qsTr("Split tunneling on the server") text: qsTr("Split tunneling on the server")
descriptionText: qsTr("Enabled \nCan't be disabled for current server") subtitle: qsTr("Enabled \nCan't be disabled for current server")
rightImageSource: "qrc:/images/controls/chevron-right.svg" trailingIconSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { onClicked: {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling) PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered() root.close()
} }
} }
DividerType { ListItemType {
visible: ServersUiController.isDefaultServerDefaultContainerHasSplitTunneling
}
LabelWithButtonType {
id: siteBasedSplitTunnelingSwitch id: siteBasedSplitTunnelingSwitch
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Site-based split tunneling") text: qsTr("Site-based split tunneling")
descriptionText: enabled && IpSplitTunnelingController.isSplitTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") subtitle: enabled && IpSplitTunnelingController.isSplitTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg" trailingIconSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { onClicked: {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling) PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered() root.close()
} }
} }
DividerType { ListItemType {
}
LabelWithButtonType {
id: appSplitTunnelingSwitch id: appSplitTunnelingSwitch
visible: isAppSplitTinnelingEnabled visible: isAppSplitTinnelingEnabled
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("App-based split tunneling") text: qsTr("App-based split tunneling")
descriptionText: AppSplitTunnelingController.isSplitTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") subtitle: AppSplitTunnelingController.isSplitTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg" trailingIconSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { onClicked: {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
root.closeTriggered() root.close()
} }
} }
DividerType {
visible: isAppSplitTinnelingEnabled
}
} }
} }
@@ -10,11 +10,10 @@ import SortFilterProxyModel 0.2
import InstalledAppsModel 1.0 import InstalledAppsModel 1.0
import Style 1.0 import Style 1.0
DrawerType2 { SheetType {
id: root id: root
anchors.fill: parent expandedHeight: windowHeight * 0.9
expandedHeight: parent.height * 0.9
onAboutToShow: { onAboutToShow: {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
@@ -26,121 +25,128 @@ DrawerType2 {
id: installedAppsModel id: installedAppsModel
} }
expandedStateContent: Item { Item {
id: container id: container
anchors.fill: parent
implicitHeight: expandedHeight SectionHeaderType {
id: header
ColumnLayout {
id: backButton
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 12
anchors.leftMargin: 16
anchors.rightMargin: 16
headerText: qsTr("Choose application")
}
ListViewType {
id: listView
anchors.top: header.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: searchField.top anchors.bottom: searchField.top
anchors.topMargin: 16 anchors.topMargin: 16
anchors.bottomMargin: 8
BackButtonType { leftMargin: 16
backButtonImage: "qrc:/images/controls/arrow-left.svg" rightMargin: 16
backButtonFunction: function() { topMargin: 16
root.closeTriggered()
model: SortFilterProxyModel {
id: proxyInstalledAppsModel
sourceModel: installedAppsModel
filters: RegExpFilter {
roleName: "appName"
pattern: ".*" + searchField.text + ".*"
caseSensitivity: Qt.CaseInsensitive
} }
} }
Header2Type { delegate: Item {
id: header width: listView.width - listView.leftMargin - listView.rightMargin
Layout.fillWidth: true implicitHeight: Math.max(72, row.implicitHeight + 24)
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Choose application") RowLayout {
} id: row
ListViewType { anchors.fill: parent
id: listView anchors.leftMargin: 16
anchors.rightMargin: 12
Layout.fillWidth: true spacing: 12
Layout.fillHeight: true
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
model: SortFilterProxyModel { CheckBoxType {
id: proxyInstalledAppsModel Layout.fillWidth: true
sourceModel: installedAppsModel
filters: RegExpFilter {
roleName: "appName"
pattern: ".*" + searchField.textField.text + ".*"
caseSensitivity: Qt.CaseInsensitive
}
}
ButtonGroup { spacing: 20
id: buttonGroup
}
delegate: ColumnLayout { text: appName
width: listView.width checked: isAppSelected
onCheckedChanged: {
RowLayout { installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked)
CheckBoxType {
Layout.fillWidth: true
text: appName
checked: isAppSelected
onCheckedChanged: {
installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked)
}
} }
}
Item {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Image { Image {
anchors.centerIn: parent
source: "image://installedAppImage/" + appIcon source: "image://installedAppImage/" + appIcon
sourceSize.width: 24 sourceSize.width: 24
sourceSize.height: 24 sourceSize.height: 24
Layout.rightMargin: 48
} }
} }
}
DividerType {} Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 1
color: AmneziaStyle.color.borderSoft
} }
} }
} }
TextFieldWithHeaderType { SearchFieldType {
id: searchField id: searchField
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: addButton.top anchors.bottom: addButton.top
anchors.bottomMargin: 16 anchors.bottomMargin: 12
anchors.rightMargin: 16 anchors.rightMargin: 16
anchors.leftMargin: 16 anchors.leftMargin: 16
backgroundColor: AmneziaStyle.color.slateGray placeholderText: qsTr("application name")
textField.placeholderText: qsTr("application name")
} }
BasicButtonType { ButtonType {
id: addButton id: addButton
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 16 anchors.bottomMargin: Math.max(16, PageController.safeAreaBottomMargin)
anchors.rightMargin: 16 anchors.rightMargin: 16
anchors.leftMargin: 16 anchors.leftMargin: 16
text: qsTr("Add selected") text: qsTr("Add selected")
clickedFunc: function() { onClicked: {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
AppSplitTunnelingController.addApps(installedAppsModel.getSelectedAppsInfo()) AppSplitTunnelingController.addApps(installedAppsModel.getSelectedAppsInfo())
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
root.closeTriggered() root.close()
} }
} }
} }
+121
View File
@@ -0,0 +1,121 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
ItemDelegate {
id: root
property string subtitle
property bool critical: false
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: implicitContentHeight + topPadding + bottomPadding
padding: 16
spacing: 12
hoverEnabled: true
focusPolicy: Qt.TabFocus
opacity: enabled ? 1.0 : 0.4
icon.width: 26
icon.height: 26
Accessible.name: root.text
Accessible.description: root.subtitle
Keys.onEnterPressed: root.click()
Keys.onReturnPressed: root.click()
background: Rectangle {
implicitWidth: 358
radius: 16
color: {
if (!root.enabled) {
return AmneziaStyle.color.bgSurface
}
if (root.down || root.hovered) {
return AmneziaStyle.color.bgStateLayer
}
return root.critical ? AmneziaStyle.color.promoOverlay : AmneziaStyle.color.bgBase
}
border.width: 1
border.color: {
if (root.enabled && root.down) {
return AmneziaStyle.color.borderMedium
}
return root.critical ? AmneziaStyle.color.promoRecommendation : AmneziaStyle.color.borderSoft
}
Behavior on color {
ColorAnimation { duration: 150 }
}
FocusRingType {
radius: 16
visible: root.visualFocus
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
enabled: root.enabled
}
}
contentItem: RowLayout {
spacing: root.spacing
Image {
Layout.preferredWidth: root.icon.width
Layout.preferredHeight: root.icon.height
visible: root.icon.source.toString() !== ""
source: root.icon.source
sourceSize: Qt.size(root.icon.width, root.icon.height)
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
H3TextType {
Layout.fillWidth: true
text: root.text
textFormat: Text.RichText
}
SmallTextType {
Layout.fillWidth: true
visible: text !== ""
text: root.subtitle
}
}
Item {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
Layout.leftMargin: 4 - root.spacing
IconImage {
anchors.centerIn: parent
width: 20
height: 20
source: "qrc:/images/controls/chevron-right.svg"
sourceSize: Qt.size(20, 20)
color: AmneziaStyle.color.iconDefault
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
import QtQuick
import QtQuick.Layouts
Rectangle {
id: root
property alias source: image.source
implicitWidth: 358
implicitHeight: root.width
Layout.preferredHeight: root.width
color: "white"
radius: 16
Image {
id: image
anchors.fill: parent
anchors.margins: 12
smooth: false
fillMode: Image.PreserveAspectFit
sourceSize.width: width
sourceSize.height: height
}
}
+14 -30
View File
@@ -11,7 +11,7 @@ import "../Controls2/TextTypes"
import "../Config" import "../Config"
DrawerType2 { SheetType {
id: root id: root
property string headerText property string headerText
@@ -22,71 +22,55 @@ DrawerType2 {
property var yesButtonFunction property var yesButtonFunction
property var noButtonFunction property var noButtonFunction
expandedStateContent: ColumnLayout { expandedHeight: content.implicitHeight + 8 + PageController.safeAreaBottomMargin
ColumnLayout {
id: content id: content
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
spacing: 8 spacing: 12
onImplicitHeightChanged: { SectionHeaderType {
root.expandedHeight = content.implicitHeight + 32 + PageController.safeAreaBottomMargin
}
Header2TextType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.bottomMargin: 14
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
text: root.headerText headerText: root.headerText
descriptionText: root.descriptionText
} }
ParagraphTextType { ButtonType {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.rightMargin: 16
Layout.leftMargin: 16
text: root.descriptionText
}
BasicButtonType {
id: yesButton id: yesButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
text: root.yesButtonText text: root.yesButtonText
clickedFunc: function() { onClicked: {
if (root.yesButtonFunction && typeof root.yesButtonFunction === "function") { if (root.yesButtonFunction && typeof root.yesButtonFunction === "function") {
root.yesButtonFunction() root.yesButtonFunction()
} }
} }
} }
BasicButtonType { ButtonType {
id: noButton id: noButton
variant: ButtonType.Secondary
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
visible: root.noButtonText !== "" visible: root.noButtonText !== ""
text: root.noButtonText text: root.noButtonText
clickedFunc: function() { onClicked: {
if (root.noButtonFunction && typeof root.noButtonFunction === "function") { if (root.noButtonFunction && typeof root.noButtonFunction === "function") {
root.noButtonFunction() root.noButtonFunction()
} }
@@ -9,21 +9,27 @@ import "../Controls2/TextTypes"
import "../Config" import "../Config"
DrawerType2 { SheetType {
property string serverNameText property string serverNameText
id: root id: root
objectName: "serverNameEditDrawer" objectName: "serverNameEditDrawer"
expandedStateContent: ColumnLayout { expandedHeight: content.implicitHeight + 16 + 8 + PageController.safeAreaBottomMargin + PageController.imeHeight
ColumnLayout {
id: content
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 32 anchors.topMargin: 16
anchors.leftMargin: 16 anchors.leftMargin: 16
anchors.rightMargin: 16 anchors.rightMargin: 16
TextFieldWithHeaderType { spacing: 26
TextFieldType {
id: serverName id: serverName
Layout.fillWidth: true Layout.fillWidth: true
@@ -33,14 +39,14 @@ DrawerType2 {
checkEmptyText: true checkEmptyText: true
} }
BasicButtonType { ButtonType {
id: saveButton id: saveButton
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Save") text: qsTr("Save")
clickedFunc: function() { onClicked: {
if (serverName.textField.text === "") { if (serverName.textField.text === "") {
return return
} }
@@ -48,7 +54,7 @@ DrawerType2 {
if (serverName.textField.text !== root.serverNameText) { if (serverName.textField.text !== root.serverNameText) {
ServersUiController.editServerName(ServersUiController.processedServerId, serverName.textField.text); ServersUiController.editServerName(ServersUiController.processedServerId, serverName.textField.text);
} }
root.closeTriggered() root.close()
} }
} }
} }
+44 -108
View File
@@ -8,42 +8,52 @@ import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
import "../Config" import "../Config"
DrawerType2 { SheetType {
id: root id: root
expandedStateContent: Item { expandedHeight: windowHeight * 0.9
Item {
id: container id: container
implicitHeight: root.height * 0.9 anchors.fill: parent
Component.onCompleted: {
root.expandedHeight = container.implicitHeight
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: headerLayout
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 16
BackButtonType { spacing: 0
id: backButton
Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 2
Layout.preferredHeight: 48
backButtonImage: "qrc:/images/controls/arrow-left.svg" IconButtonType {
backButtonFunction: function() { root.closeTriggered() } id: backButton
x: 16
y: 4
size: IconButtonType.Small
icon.source: "qrc:/images/controls/arrow-left.svg"
Accessible.name: qsTr("Back")
onClicked: root.close()
}
} }
Header2Type { SectionHeaderType {
id: header id: header
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 32
headerText: qsTr("Choose language") headerText: qsTr("Choose language")
} }
@@ -52,7 +62,7 @@ DrawerType2 {
ListViewType { ListViewType {
id: listView id: listView
anchors.top: backButtonLayout.bottom anchors.top: headerLayout.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@@ -61,111 +71,37 @@ DrawerType2 {
model: LanguageModel model: LanguageModel
footer: Item {
width: listView.width
height: 16 + PageController.safeAreaBottomMargin
}
ButtonGroup { ButtonGroup {
id: buttonGroup id: buttonGroup
} }
delegate: Item { delegate: Item {
implicitWidth: root.width width: listView.width
implicitHeight: delegateContent.implicitHeight implicitHeight: languageItem.implicitHeight
ColumnLayout { RadioItemType {
id: delegateContent id: languageItem
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
RadioButton { text: languageName
id: radioButton
implicitWidth: parent.width ButtonGroup.group: buttonGroup
implicitHeight: radioButtonContent.implicitHeight checked: listView.selectedIndex === index
hoverEnabled: true onClicked: {
listView.selectedIndex = index
property bool isFocusable: true LanguageUiController.changeLanguage(languageIndex)
root.close()
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle {
width: parent.width - 1
height: parent.height
color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack
border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent
border.width: radioButton.focus ? 1 : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
}
RowLayout {
id: radioButtonContent
anchors.fill: parent
anchors.rightMargin: 16
anchors.leftMargin: 16
spacing: 0
z: 1
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 20
Layout.bottomMargin: 20
text: languageName
}
Image {
source: "qrc:/images/controls/check.svg"
visible: radioButton.checked
width: 24
height: 24
Layout.rightMargin: 8
}
}
ButtonGroup.group: buttonGroup
checked: listView.selectedIndex === index
onClicked: {
listView.selectedIndex = index
LanguageUiController.changeLanguage(languageIndex)
root.closeTriggered()
}
} }
} }
Keys.onEnterPressed: radioButton.clicked()
Keys.onReturnPressed: radioButton.clicked()
} }
} }
} }
@@ -0,0 +1,40 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
RadioItemType {
id: root
property bool showWarning: false
signal settingsRequested()
Item {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
visible: root.showWarning
IconImage {
anchors.centerIn: parent
width: 24
height: 24
source: "qrc:/images/controls/alert-circle.svg"
sourceSize: Qt.size(24, 24)
color: AmneziaStyle.color.statusWarning
}
}
IconButtonType {
icon.source: "qrc:/images/controls/settings.svg"
Accessible.name: qsTr("Server settings")
onClicked: root.settingsRequested()
}
}
+61 -123
View File
@@ -1,29 +1,21 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0 import PageEnum 1.0
import ContainerProps 1.0
import ContainersModelFilters 1.0
import Style 1.0 import Style 1.0
import "./"
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
ListViewType { ListViewType {
id: root id: root
property int selectedIndex: ServersUiController.getServerIndexById(ServersUiController.defaultServerId) property int selectedIndex: ServersUiController.getServerIndexById(ServersUiController.defaultServerId)
anchors.top: serversMenuHeader.bottom signal serverSettingsOpened()
anchors.right: parent.right
anchors.left: parent.left leftMargin: 16
anchors.bottom: parent.bottom rightMargin: 16
anchors.topMargin: 16 bottomMargin: 16
model: ServersModel model: ServersModel
@@ -34,121 +26,67 @@ ListViewType {
} }
} }
delegate: Item { ButtonGroup {
id: menuContentDelegate id: serversRadioButtonGroup
objectName: "menuContentDelegate" objectName: "serversRadioButtonGroup"
}
property variant delegateData: model function openServerSettings(serverId) {
property VerticalRadioButton serverRadioButtonProperty: serverRadioButton ServersUiController.setProcessedServerId(serverId)
implicitWidth: root.width if (ServersUiController.isServerFromApi(ServersUiController.processedServerId)) {
implicitHeight: serverRadioButtonContent.implicitHeight if (ServersUiController.isServerCountrySelectionAvailable(ServersUiController.processedServerId)) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
ColumnLayout { } else {
id: serverRadioButtonContent PageController.showBusyIndicator(true)
objectName: "serverRadioButtonContent" let result = SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, false)
PageController.showBusyIndicator(false)
anchors.fill: parent if (!result) {
anchors.rightMargin: 16 return
anchors.leftMargin: 16
spacing: 0
RowLayout {
objectName: "serverRadioButtonRowLayout"
Layout.fillWidth: true
VerticalRadioButton {
id: serverRadioButton
objectName: "serverRadioButton"
Layout.fillWidth: true
text: name
descriptionText: isServerFromGatewayApi && (isSubscriptionExpired || isSubscriptionExpiringSoon)
? (isSubscriptionExpired ? qsTr("Subscription expired. Please renew") : qsTr("Subscription expiring soon"))
: serverDescription
descriptionColor: isServerFromGatewayApi && (isSubscriptionExpired || isSubscriptionExpiringSoon)
? (isSubscriptionExpired ? AmneziaStyle.color.vibrantRed : AmneziaStyle.color.goldenApricot)
: AmneziaStyle.color.mutedGray
checked: index === root.selectedIndex
checkable: !ConnectionController.isConnected
ButtonGroup.group: serversRadioButtonGroup
onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
return
}
root.selectedIndex = index
ServersUiController.setDefaultServerAtIndex(index)
}
Keys.onEnterPressed: serverRadioButton.clicked()
Keys.onReturnPressed: serverRadioButton.clicked()
} }
ImageButtonType { PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
id: outdatedContainerWarningIcon
objectName: "outdatedContainerWarningIcon"
visible: ServersUiController.serverHasOutdatedAwgContainer(serverId)
hoverEnabled: false
image: "qrc:/images/controls/alert-circle.svg"
imageColor: AmneziaStyle.color.goldenApricot
implicitWidth: 40
implicitHeight: 56
}
ImageButtonType {
id: serverInfoButton
objectName: "serverInfoButton"
image: "qrc:/images/controls/settings.svg"
imageColor: AmneziaStyle.color.paleGray
implicitWidth: 56
implicitHeight: 56
z: 1
onClicked: function() {
ServersUiController.setProcessedServerId(serverId)
if (ServersUiController.isServerFromApi(ServersUiController.processedServerId)) {
if (ServersUiController.isServerCountrySelectionAvailable(ServersUiController.processedServerId)) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, false)
PageController.showBusyIndicator(false)
if (!result) {
return
}
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
}
} else {
PageController.goToPage(PageEnum.PageSettingsServerInfo)
}
drawer.closeTriggered()
}
}
}
DividerType {
Layout.fillWidth: true
Layout.leftMargin: 0
Layout.rightMargin: 0
} }
} else {
PageController.goToPage(PageEnum.PageSettingsServerInfo)
} }
root.serverSettingsOpened()
}
delegate: ServerListItem {
objectName: "serverRadioButton"
readonly property bool subscriptionAlert: isServerFromGatewayApi && (isSubscriptionExpired || isSubscriptionExpiringSoon)
width: root.width - root.leftMargin - root.rightMargin
text: name
subtitle: subscriptionAlert
? (isSubscriptionExpired ? qsTr("Subscription expired. Please renew") : qsTr("Subscription expiring soon"))
: serverDescription
subtitleColor: subscriptionAlert
? (isSubscriptionExpired ? AmneziaStyle.color.statusDestructive : AmneziaStyle.color.statusWarning)
: AmneziaStyle.color.textTertiary
showWarning: ServersUiController.serverHasOutdatedAwgContainer(serverId)
showSeparator: index < root.count - 1
checked: index === root.selectedIndex
checkable: !ConnectionController.isConnected
ButtonGroup.group: serversRadioButtonGroup
onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
return
}
root.selectedIndex = index
ServersUiController.setDefaultServerAtIndex(index)
}
onSettingsRequested: root.openServerSettings(serverId)
} }
} }
@@ -1,41 +1,59 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls.impl
import QtQuick.Layouts import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0 import PageEnum 1.0
import Style 1.0 import Style 1.0
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes"
ListViewType { ListViewType {
id: root id: root
anchors.fill: parent anchors.fill: parent
delegate: ColumnLayout { delegate: Item {
width: root.width id: delegateRoot
property bool isOutdatedAwgContainer: Boolean(isInstalled && ServersUiController.isContainerOutdatedAwg(root.model.mapToSource(index))) readonly property bool isOutdatedAwgContainer: Boolean(isInstalled && ServersUiController.isContainerOutdatedAwg(root.model.mapToSource(index)))
LabelWithButtonType { implicitWidth: root.width
Layout.fillWidth: true implicitHeight: containerItem.implicitHeight
ListItemType {
id: containerItem
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
text: name text: name
descriptionText: description subtitle: description
rightWarningImageSource: isOutdatedAwgContainer ? "qrc:/images/controls/alert-circle.svg" : "" trailingIconSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg"
rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" trailingContentVisible: delegateRoot.isOutdatedAwgContainer
clickedFunction: function() { Item {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
IconImage {
anchors.centerIn: parent
width: 24
height: 24
source: "qrc:/images/controls/alert-circle.svg"
sourceSize: Qt.size(24, 24)
color: AmneziaStyle.color.statusWarning
}
}
onClicked: {
if (isInstalled) { if (isInstalled) {
var containerIndex = root.model.mapToSource(index) var containerIndex = root.model.mapToSource(index)
ServersUiController.processedContainerIndex = containerIndex ServersUiController.processedContainerIndex = containerIndex
if (isVpnContainer) { if (isVpnContainer) {
// var isThirdPartyConfig = root.model.data(index, ContainersModel.IsThirdPartyConfigRole)
if (isThirdPartyConfig) { if (isThirdPartyConfig) {
InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex) InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex)
PageController.goToPage(PageEnum.PageProtocolRaw) PageController.goToPage(PageEnum.PageProtocolRaw)
@@ -68,14 +86,11 @@ ListViewType {
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
} }
} }
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
} }
}
DividerType {} footer: Item {
width: root.width
height: 16 + PageController.safeAreaBottomMargin
} }
} }
@@ -0,0 +1,181 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
SheetType {
id: root
property string clientName
property string creationDate
property string latestHandshake
property string dataReceived
property string dataSent
property string allowedIps
signal renameRequested(string newName)
signal revokeRequested()
expandedHeight: content.implicitHeight + 16 + PageController.safeAreaBottomMargin
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 0
H2TextType {
Layout.fillWidth: true
Layout.bottomMargin: 12
text: root.clientName
maximumLineCount: 2
wrapMode: Text.Wrap
elide: Qt.ElideRight
}
BodyTextType {
Layout.fillWidth: true
visible: root.creationDate !== ""
maximumLineCount: 2
wrapMode: Text.Wrap
elide: Qt.ElideRight
text: qsTranslate("PageShare", "Creation date: %1").arg(root.creationDate)
}
BodyTextType {
Layout.fillWidth: true
visible: root.latestHandshake !== ""
maximumLineCount: 2
wrapMode: Text.Wrap
elide: Qt.ElideRight
text: qsTranslate("PageShare", "Latest handshake: %1").arg(root.latestHandshake)
}
BodyTextType {
Layout.fillWidth: true
visible: root.dataReceived !== ""
maximumLineCount: 2
wrapMode: Text.Wrap
elide: Qt.ElideRight
text: qsTranslate("PageShare", "Data received: %1").arg(root.dataReceived)
}
BodyTextType {
Layout.fillWidth: true
visible: root.dataSent !== ""
maximumLineCount: 2
wrapMode: Text.Wrap
elide: Qt.ElideRight
text: qsTranslate("PageShare", "Data sent: %1").arg(root.dataSent)
}
BodyTextType {
Layout.fillWidth: true
visible: root.allowedIps !== ""
wrapMode: Text.Wrap
text: qsTranslate("PageShare", "Allowed IPs: %1").arg(root.allowedIps)
}
ButtonType {
id: renameButton
Layout.fillWidth: true
Layout.topMargin: 24
variant: ButtonType.Secondary
text: qsTranslate("PageShare", "Rename")
onClicked: {
clientNameEditor.textField.text = root.clientName
clientNameEditDrawer.open()
}
}
ButtonType {
id: revokeButton
Layout.fillWidth: true
Layout.topMargin: 12
variant: ButtonType.Secondary
text: qsTranslate("PageShare", "Revoke")
onClicked: root.revokeRequested()
}
}
SheetType {
id: clientNameEditDrawer
expandedHeight: editContent.implicitHeight + 32 + PageController.safeAreaBottomMargin
ColumnLayout {
id: editContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 16
TextFieldType {
id: clientNameEditor
Layout.fillWidth: true
headerText: qsTranslate("PageShare", "Client name")
textField.text: root.clientName
textField.maximumLength: 20
checkEmptyText: true
}
ButtonType {
id: saveButton
Layout.fillWidth: true
text: qsTranslate("PageShare", "Save")
onClicked: {
if (clientNameEditor.textField.text === "") {
return
}
if (clientNameEditor.textField.text !== root.clientName) {
root.renameRequested(clientNameEditor.textField.text)
clientNameEditDrawer.close()
}
}
}
}
}
}
@@ -5,15 +5,14 @@ import QtQuick.Layouts
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
DrawerType2 { SheetType {
id: root id: root
property var listModel: null property var listModel: null
width: parent.width expandedHeight: content.implicitHeight + 32 + PageController.safeAreaBottomMargin
height: parent.height
expandedStateContent: ColumnLayout { ColumnLayout {
id: content id: content
anchors.top: parent.top anchors.top: parent.top
@@ -21,25 +20,21 @@ DrawerType2 {
anchors.right: parent.right anchors.right: parent.right
spacing: 0 spacing: 0
Component.onCompleted: {
root.expandedHeight = content.implicitHeight + 32 + PageController.safeAreaBottomMargin
}
ButtonGroup { ButtonGroup {
id: sortModeGroup id: sortModeGroup
} }
Header2TextType { SectionHeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.bottomMargin: 16 Layout.bottomMargin: 16
text: qsTr("Sort countries") headerText: qsTr("Sort countries")
} }
VerticalRadioButton { RadioItemType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
@@ -51,32 +46,25 @@ DrawerType2 {
onClicked: { onClicked: {
root.listModel.sortMode = 0 root.listModel.sortMode = 0
root.closeTriggered() root.close()
} }
} }
DividerType { RadioItemType {
Layout.fillWidth: true
}
VerticalRadioButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("Alphabetically") text: qsTr("Alphabetically")
showSeparator: false
ButtonGroup.group: sortModeGroup ButtonGroup.group: sortModeGroup
checked: root.listModel !== null && root.listModel.sortMode === 1 checked: root.listModel !== null && root.listModel.sortMode === 1
onClicked: { onClicked: {
root.listModel.sortMode = 1 root.listModel.sortMode = 1
root.closeTriggered() root.close()
} }
} }
DividerType {
Layout.fillWidth: true
}
} }
} }
@@ -9,16 +9,18 @@ import Style 1.0
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
DrawerType2 { SheetType {
id: root id: root
property bool isRenewalAvailable: false property bool isRenewalAvailable: false
onOpened: { expandedHeight: content.implicitHeight + 8 + PageController.safeAreaBottomMargin
onAboutToShow: {
isRenewalAvailable = ServersUiController.isServerRenewalAvailable(ServersUiController.defaultServerId) && !ApiAccountInfoModel.data("isInAppPurchase") isRenewalAvailable = ServersUiController.isServerRenewalAvailable(ServersUiController.defaultServerId) && !ApiAccountInfoModel.data("isInAppPurchase")
} }
expandedStateContent: ColumnLayout { ColumnLayout {
id: content id: content
anchors.top: parent.top anchors.top: parent.top
@@ -27,79 +29,47 @@ DrawerType2 {
spacing: 0 spacing: 0
onImplicitHeightChanged: { SectionHeaderType {
root.expandedHeight = content.implicitHeight + 32 + PageController.safeAreaBottomMargin
}
Item {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
implicitHeight: titleText.implicitHeight
Header2TextType {
id: titleText
anchors.left: parent.left
anchors.right: parent.right
text: ServersUiController.serverName(ServersUiController.defaultServerId) + qsTr(" subscription has expired")
horizontalAlignment: Text.AlignLeft
}
}
ParagraphTextType {
visible: root.isRenewalAvailable
Layout.fillWidth: true
Layout.topMargin: 8
Layout.rightMargin: 16
Layout.leftMargin: 16
text: qsTr("Renew to continue using VPN")
horizontalAlignment: Text.AlignLeft
}
BasicButtonType {
visible: root.isRenewalAvailable
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: ServersUiController.serverName(ServersUiController.defaultServerId) + qsTr(" subscription has expired")
descriptionText: root.isRenewalAvailable ? qsTr("Renew to continue using VPN") : ""
}
ButtonType {
visible: root.isRenewalAvailable
Layout.fillWidth: true
Layout.topMargin: 26
Layout.rightMargin: 16
Layout.leftMargin: 16
text: qsTr("Renew") text: qsTr("Renew")
defaultColor: AmneziaStyle.color.paleGray onClicked: {
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
SubscriptionUiController.getRenewalLink(ServersUiController.defaultServerId) SubscriptionUiController.getRenewalLink(ServersUiController.defaultServerId)
} }
} }
BasicButtonType { ButtonType {
Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true
Layout.topMargin: 8 Layout.topMargin: root.isRenewalAvailable ? 12 : 26
Layout.bottomMargin: 8 Layout.rightMargin: 16
Layout.leftMargin: 16
implicitHeight: 25 variant: ButtonType.Text
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Support") text: qsTr("Support")
clickedFunc: function() { onClicked: {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.defaultServerId, false) let result = SubscriptionUiController.getAccountInfo(ServersUiController.defaultServerId, false)
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
if (result) { if (result) {
root.closeTriggered() root.close()
PageController.goToPage(PageEnum.PageSettingsApiSupport) PageController.goToPage(PageEnum.PageSettingsApiSupport)
} }
} }
@@ -1,95 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
import "../Controls2/TextTypes"
Rectangle {
id: root
property bool selected: false
property string billingPeriod: ""
property string priceLabel: ""
property string subtitle: ""
property bool showRecommendedBadge: false
property string recommendedText: "Recommended"
signal selectRequested
implicitHeight: cardLayout.implicitHeight + 28
radius: 16
color: AmneziaStyle.color.transparent
border.width: selected ? 2 : 1
border.color: selected ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.charcoalGray
ColumnLayout {
id: cardLayout
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 14
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 8
RowLayout {
Layout.fillWidth: true
LabelTextType {
Layout.fillWidth: true
text: root.billingPeriod
color: root.selected ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.paleGray
font.pixelSize: 17
font.weight: Font.DemiBold
wrapMode: Text.Wrap
}
LabelTextType {
text: root.priceLabel
color: root.selected ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.paleGray
font.pixelSize: 17
font.weight: Font.DemiBold
}
}
RowLayout {
Layout.fillWidth: true
visible: root.subtitle.length > 0 || root.showRecommendedBadge
LabelTextType {
Layout.fillWidth: true
text: root.subtitle
color: AmneziaStyle.color.mutedGray
font.pixelSize: 13
wrapMode: Text.Wrap
}
Rectangle {
visible: root.showRecommendedBadge
Layout.alignment: Qt.AlignVCenter
radius: 10
color: AmneziaStyle.color.softViolet
implicitHeight: recLabel.implicitHeight + 8
implicitWidth: recLabel.implicitWidth + 16
LabelTextType {
id: recLabel
anchors.centerIn: parent
text: root.recommendedText
color: AmneziaStyle.color.midnightBlack
font.pixelSize: 11
font.weight: Font.Medium
}
}
}
}
MouseArea {
anchors.fill: parent
onClicked: root.selectRequested()
}
}
@@ -5,7 +5,7 @@ import Style 1.0
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
ParagraphTextType { CaptionTextType {
id: root id: root
property string termsUrl: "" property string termsUrl: ""
@@ -15,15 +15,12 @@ ParagraphTextType {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
textFormat: Text.RichText textFormat: Text.RichText
color: AmneziaStyle.color.mutedGray color: AmneziaStyle.color.textAccent
font.pixelSize: 12
lineHeight: 1.35
lineHeightMode: Text.ProportionalHeight
text: qsTr("By continuing, you agree to the <a href=\"%1\" style=\"color: %3;\">Terms of Use</a> and <a href=\"%2\" style=\"color: %3;\">Privacy Policy</a>") text: qsTr("By continuing, you agree to the <a href=\"%1\" style=\"color: %3;\">Terms of Use</a> and <a href=\"%2\" style=\"color: %3;\">Privacy Policy</a>")
.arg(root.termsUrl) .arg(root.termsUrl)
.arg(root.privacyUrl) .arg(root.privacyUrl)
.arg(AmneziaStyle.color.goldenApricotString) .arg(AmneziaStyle.color.textAccent)
onLinkActivated: function(link) { onLinkActivated: function(link) {
Qt.openUrlExternally(link) Qt.openUrlExternally(link)
@@ -1,61 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes"
Rectangle { SegmentedControlType {
id: root model: ["UDP", "TCP"]
property real rootWidth: root.width
property int currentIndex
implicitWidth: transportProtoButtonGroup.implicitWidth
implicitHeight: transportProtoButtonGroup.implicitHeight
color: AmneziaStyle.color.onyxBlack
radius: 16
onFocusChanged: {
if (focus) {
udpButton.forceActiveFocus()
}
}
RowLayout {
id: transportProtoButtonGroup
spacing: 0
HorizontalRadioButton {
id: udpButton
checked: root.currentIndex === 0
hoverEnabled: root.enabled
implicitWidth: (rootWidth - 32) / 2
text: "UDP"
onClicked: {
root.currentIndex = 0
}
}
HorizontalRadioButton {
id: tcpButton
checked: root.currentIndex === 1
hoverEnabled: root.enabled
implicitWidth: (rootWidth - 32) / 2
text: "TCP"
onClicked: {
root.currentIndex = 1
}
}
}
} }
@@ -8,19 +8,14 @@ import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
import "../Config" import "../Config"
DrawerType2 { SheetType {
id: root id: root
expandedStateContent: Item { expandedHeight: content.implicitHeight + 16 + 24 + PageController.safeAreaBottomMargin
Item {
id: contentRoot id: contentRoot
anchors.fill: parent
implicitHeight: content.implicitHeight + 40 + PageController.safeAreaBottomMargin
Binding {
target: root
property: "expandedHeight"
value: contentRoot.implicitHeight
}
ColumnLayout { ColumnLayout {
id: content id: content
@@ -28,65 +23,61 @@ DrawerType2 {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 24 anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 0 spacing: 0
Header2TextType { SectionHeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
text: qsTr("Support") headerText: qsTr("Support")
descriptionText: qsTr("If the update won't install, message us")
} }
AppTextType { ListItemType {
Layout.fillWidth: true
Layout.topMargin: 8
color: AmneziaStyle.color.textTertiary
text: qsTr("If the update won't install, message us")
}
LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16 Layout.topMargin: 16
text: qsTr("Telegram") text: qsTr("Telegram")
descriptionText: qsTr("We'll reply in chat") subtitle: qsTr("We'll reply in chat")
leftImageSource: "qrc:/images/controls/telegram.svg" icon.source: "qrc:/images/controls/telegram.svg"
rightImageSource: "qrc:/images/controls/chevron-right.svg" trailingIconSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { onClicked: {
Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en"))
} }
} }
DividerType {} ListItemType {
LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("support@amnezia.org") text: qsTr("support@amnezia.org")
descriptionText: qsTr("Support email") subtitle: qsTr("Support email")
leftImageSource: "qrc:/images/controls/mail.svg" icon.source: "qrc:/images/controls/mail.svg"
rightImageSource: "qrc:/images/controls/chevron-right.svg" trailingIconSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { onClicked: {
Qt.openUrlExternally(qsTr("mailto:support@amnezia.org")) Qt.openUrlExternally(qsTr("mailto:support@amnezia.org"))
} }
} }
DividerType {} ListItemType {
LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("amnezia.org") text: qsTr("amnezia.org")
descriptionText: qsTr("Download the update manually") subtitle: qsTr("Download the update manually")
leftImageSource: "qrc:/images/controls/amnezia.svg" icon.source: "qrc:/images/controls/amnezia.svg"
rightImageSource: "qrc:/images/controls/chevron-right.svg" trailingIconSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { onClicked: {
Qt.openUrlExternally(LanguageUiController.getCurrentSiteUrl("")) Qt.openUrlExternally(LanguageUiController.getCurrentSiteUrl(""))
} }
} }
-196
View File
@@ -1,196 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
Item {
id: root
default property alias trailingContent: trailingSlot.data
property string text
property string descriptionText
property string leftImageSource
property string rightImageSource
property color textColor: AmneziaStyle.color.textPrimary
property color leftImageColor: AmneziaStyle.color.surfaceInverse
property bool showDivider: true
property var clickedFunction
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Keys.onEnterPressed: root.click()
Keys.onReturnPressed: root.click()
function click() {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
implicitHeight: Math.max(72, content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin)
Rectangle {
id: background
anchors.fill: parent
anchors.topMargin: 4
anchors.bottomMargin: 4
radius: 16
color: (mouseArea.pressed || mouseArea.containsMouse) ? AmneziaStyle.color.translucentWhite : AmneziaStyle.color.transparent
border.color: (mouseArea.pressed || root.activeFocus) ? AmneziaStyle.color.textTertiary : AmneziaStyle.color.transparent
border.width: (mouseArea.pressed || root.activeFocus) ? 1 : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
enabled: root.enabled
hoverEnabled: root.enabled
cursorShape: Qt.PointingHandCursor
onClicked: root.click()
}
RowLayout {
id: content
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.topMargin: 12
anchors.bottomMargin: 12
spacing: 12
Item {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
visible: root.leftImageSource !== ""
Image {
id: leftImage
anchors.centerIn: parent
width: 24
height: 24
source: root.leftImageSource
sourceSize: Qt.size(24, 24)
visible: false
}
ColorOverlay {
anchors.fill: leftImage
source: leftImage
color: root.leftImageColor
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.leftMargin: 4
spacing: 4
AppH3TextType {
Layout.fillWidth: true
text: root.text
color: root.textColor
}
AppSmallTextType {
Layout.fillWidth: true
visible: text !== ""
text: root.descriptionText
}
}
Item {
id: trailingSlot
Layout.preferredWidth: childrenRect.width
Layout.preferredHeight: childrenRect.height
visible: children.length > 0
}
Item {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
visible: root.rightImageSource !== ""
Image {
id: rightImage
anchors.centerIn: parent
width: 24
height: 24
source: root.rightImageSource
sourceSize: Qt.size(24, 24)
visible: false
}
ColorOverlay {
anchors.fill: rightImage
source: rightImage
color: AmneziaStyle.color.surfaceInverse
}
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 1
visible: root.showDivider
color: AmneziaStyle.color.borderSoft
}
}
+18 -74
View File
@@ -9,10 +9,13 @@ import "TextTypes"
Popup { Popup {
id: root id: root
enum Role { Default, Destructive }
property string headerText property string headerText
property string descriptionText property string descriptionText
property string cancelButtonText: qsTr("Cancel") property string cancelButtonText: qsTr("Cancel")
property string confirmButtonText property string confirmButtonText
property int role: AlertDialogType.Default
signal confirmed() signal confirmed()
@@ -20,7 +23,7 @@ Popup {
x: Math.round((parent.width - width) / 2) x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2) y: Math.round((parent.height - height) / 2)
width: parent.width - 32 width: Math.min(parent.width - 32, 400)
padding: 0 padding: 0
topPadding: 20 topPadding: 20
@@ -30,7 +33,7 @@ Popup {
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
color: AmneziaStyle.color.overlayScrim color: AmneziaStyle.color.bgOverlay
} }
enter: Transition { enter: Transition {
@@ -42,21 +45,16 @@ Popup {
} }
onOpened: { onOpened: {
FocusController.pushRootObject(root)
cancelButton.forceActiveFocus() cancelButton.forceActiveFocus()
} }
onClosed: {
FocusController.dropRootObject(root)
}
background: Rectangle { background: Rectangle {
radius: 16 radius: 16
color: AmneziaStyle.color.surfaceElevated color: AmneziaStyle.color.bgSurfaceAlt
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
spacing: 0 spacing: 2
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
@@ -66,13 +64,13 @@ Popup {
spacing: 8 spacing: 8
AppH3TextType { H3TextType {
Layout.fillWidth: true Layout.fillWidth: true
text: root.headerText text: root.headerText
} }
AppSmallTextType { SmallTextType {
Layout.fillWidth: true Layout.fillWidth: true
visible: text !== "" visible: text !== ""
@@ -83,33 +81,30 @@ Popup {
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: 8
spacing: 8 spacing: 0
AlertDialogButton { DialogButtonType {
id: cancelButton id: cancelButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: 1
text: root.cancelButtonText text: root.cancelButtonText
textColor: AmneziaStyle.color.textTertiary
onClicked: { onClicked: root.close()
root.close()
}
} }
AlertDialogButton { DialogButtonType {
id: confirmButton id: confirmButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: 1
text: root.confirmButtonText text: root.confirmButtonText
textColor: AmneziaStyle.color.accentDanger emphasized: true
font.weight: 600 textColor: root.role === AlertDialogType.Destructive ? AmneziaStyle.color.statusDestructive
: AmneziaStyle.color.textAccent
onClicked: { onClicked: {
root.close() root.close()
@@ -118,55 +113,4 @@ Popup {
} }
} }
} }
component AlertDialogButton: AbstractButton {
id: button
property color textColor
property bool isFocusable: true
implicitHeight: 48
hoverEnabled: true
font.family: "PT Root UI VF"
font.pixelSize: 16
font.weight: 400
font.letterSpacing: -0.4
Keys.onTabPressed: FocusController.nextKeyTabItem()
Keys.onBacktabPressed: FocusController.previousKeyTabItem()
Keys.onLeftPressed: FocusController.nextKeyLeftItem()
Keys.onRightPressed: FocusController.nextKeyRightItem()
Keys.onEnterPressed: button.clicked()
Keys.onReturnPressed: button.clicked()
background: Rectangle {
radius: 16
color: (button.pressed || button.hovered) ? AmneziaStyle.color.translucentWhite : AmneziaStyle.color.transparent
border.color: (button.pressed || button.visualFocus) ? AmneziaStyle.color.textTertiary : AmneziaStyle.color.transparent
border.width: (button.pressed || button.visualFocus) ? 1 : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
contentItem: Text {
text: button.text
font: button.font
color: button.textColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
MouseArea {
anchors.fill: parent
enabled: false
cursorShape: Qt.PointingHandCursor
}
}
} }
+12 -33
View File
@@ -1,8 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
FocusScope { FocusScope {
id: root id: root
@@ -10,42 +6,25 @@ FocusScope {
property string backButtonImage: "qrc:/images/controls/arrow-left.svg" property string backButtonImage: "qrc:/images/controls/arrow-left.svg"
property var backButtonFunction property var backButtonFunction
implicitWidth: content.implicitWidth implicitWidth: backButton.implicitWidth + backButton.x
implicitHeight: content.implicitHeight implicitHeight: backButton.implicitHeight
visible: backButtonImage !== "" visible: backButtonImage !== ""
RowLayout { IconButtonType {
id: content id: backButton
anchors.fill: parent x: 16
anchors.leftMargin: 8 anchors.verticalCenter: parent.verticalCenter
ImageButtonType { icon.source: root.backButtonImage
id: backButton
image: backButtonImage
imageColor: AmneziaStyle.color.paleGray
implicitWidth: 40 onClicked: {
implicitHeight: 40 if (root.backButtonFunction && typeof root.backButtonFunction === "function") {
root.backButtonFunction()
onClicked: { } else {
if (backButtonFunction && typeof backButtonFunction === "function") { PageController.closePage()
backButtonFunction()
} else {
PageController.closePage()
}
} }
} }
Rectangle {
id: background
Layout.fillWidth: true
color: AmneziaStyle.color.transparent
}
} }
Keys.onEnterPressed: backButton.clicked()
Keys.onReturnPressed: backButton.clicked()
} }
+3 -2
View File
@@ -9,6 +9,7 @@ Item {
property Item sourceItem property Item sourceItem
property real blurPadding: 40 property real blurPadding: 40
property real blurRadius: 50 property real blurRadius: 50
property real radius: height / 2
property color tintColor: Qt.alpha(AmneziaStyle.color.textStaticWhite, 0.10) property color tintColor: Qt.alpha(AmneziaStyle.color.textStaticWhite, 0.10)
property color borderColor: Qt.alpha(AmneziaStyle.color.textStaticWhite, 0.75) property color borderColor: Qt.alpha(AmneziaStyle.color.textStaticWhite, 0.75)
@@ -54,13 +55,13 @@ Item {
id: backdropMask id: backdropMask
anchors.fill: parent anchors.fill: parent
radius: height / 2 radius: root.radius
visible: false visible: false
} }
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: height / 2 radius: root.radius
color: root.tintColor color: root.tintColor
border.color: root.borderColor border.color: root.borderColor
@@ -1,75 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Item {
id: root
property string headerText
property int headerTextFormat: Text.AutoText
property int headerHorizontalAlignment: Text.AlignLeft
property int headerTextMaximumLineCount: 2
property int headerTextElide: Qt.ElideRight
property string descriptionText
property string descriptionLinkText
property string descriptionLinkUrl
property alias headerRow: headerRow
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
RowLayout {
id: headerRow
Layout.fillWidth: true
Header1TextType {
id: header
Layout.fillWidth: true
text: root.headerText
textFormat: root.headerTextFormat
horizontalAlignment: root.headerHorizontalAlignment
maximumLineCount: root.headerTextMaximumLineCount
elide: root.headerTextElide
}
}
ParagraphTextType {
id: description
Layout.topMargin: 16
Layout.fillWidth: true
text: root.descriptionText
color: AmneziaStyle.color.mutedGray
visible: root.descriptionText !== ""
}
ParagraphTextType {
id: descriptionLink
Layout.topMargin: 16
Layout.fillWidth: true
text: root.descriptionLinkText !== "" && root.descriptionLinkUrl !== ""
? ("<a href=\"" + root.descriptionLinkUrl + "\" style=\"color: " + AmneziaStyle.color.goldenApricotString + ";\">" + root.descriptionLinkText + "</a>")
: ""
textFormat: Text.RichText
visible: root.descriptionLinkText !== ""
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
}
-210
View File
@@ -1,210 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
Button {
id: root
property string hoveredColor: AmneziaStyle.color.lightGray
property string defaultColor: AmneziaStyle.color.paleGray
property string disabledColor: AmneziaStyle.color.charcoalGray
property string pressedColor: AmneziaStyle.color.mutedGray
property string textColor: AmneziaStyle.color.midnightBlack
property string borderColor: AmneziaStyle.color.paleGray
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderWidth: 0
property int borderFocusedWidth: 1
property string leftImageSource
property string rightImageSource
property string leftImageColor: textColor
property bool changeLeftImageSize: true
property bool squareLeftSide: false
property var clickedFunc
property alias buttonTextLabel: buttonText
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 56
hoverEnabled: true
background: Rectangle {
id: focusBorder
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
anchors.fill: parent
radius: 16
Rectangle {
id: background
anchors.fill: focusBorder
anchors.margins: root.activeFocus ? 2 : 0
radius: root.activeFocus ? 14 : 16
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
border.color: borderColor
border.width: borderWidth
Behavior on color {
PropertyAnimation { duration: 200 }
}
Rectangle {
visible: root.squareLeftSide
z: 1
width: parent.radius
height: parent.radius
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
color: {
if (root.enabled) {
if (root.pressed) {
return root.pressedColor
}
return root.hovered ? root.hoveredColor : root.defaultColor
} else {
return root.disabledColor
}
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
}
}
MouseArea {
anchors.fill: focusBorder
enabled: false
cursorShape: Qt.PointingHandCursor
}
contentItem: Item {
anchors.fill: focusBorder
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.centerIn: parent
Image {
id: leftImage
source: root.leftImageSource
visible: root.leftImageSource === "" ? false : true
layer {
enabled: leftImageColor !== "" ? true : false
effect: ColorOverlay {
color: leftImageColor
}
}
Component.onCompleted: {
if (root.changeLeftImageSize) {
leftImage.Layout.preferredHeight = 20
leftImage.Layout.preferredWidth = 20
}
}
}
ButtonTextType {
id: buttonText
color: root.textColor
text: root.text
visible: root.text === "" ? false : true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
Image {
Layout.preferredHeight: 20
Layout.preferredWidth: 20
source: root.rightImageSource
visible: root.rightImageSource === "" ? false : true
layer {
enabled: true
effect: ColorOverlay {
color: textColor
}
}
}
}
}
Keys.onEnterPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
Keys.onReturnPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
onClicked: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
}
+169
View File
@@ -0,0 +1,169 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
AbstractButton {
id: root
property string subtitle
property string footnote
property string badgeText
property string errorText
property bool muted: false
readonly property real badgeOffset: root.badgeText !== "" ? 16 : 0
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: implicitContentHeight + topPadding + bottomPadding
topInset: root.badgeOffset
bottomInset: errorLabel.visible ? errorLabel.implicitHeight + 4 : 0
leftPadding: 16
rightPadding: 16
topPadding: root.badgeOffset + 12
bottomPadding: bottomInset + 12
spacing: 12
hoverEnabled: true
focusPolicy: Qt.NoFocus
opacity: enabled ? 1.0 : 0.4
icon.width: 24
icon.height: 24
icon.color: root.muted ? AmneziaStyle.color.iconSecondary : AmneziaStyle.color.iconDefault
Accessible.role: Accessible.Button
Accessible.name: root.text
Accessible.description: root.subtitle
background: Item {
implicitWidth: 358
Rectangle {
anchors.fill: parent
radius: 16
color: root.muted ? AmneziaStyle.color.bgSurfaceAlt : AmneziaStyle.color.bgSurface
opacity: root.muted ? 0.75 : 1.0
}
Rectangle {
anchors.fill: parent
radius: 16
color: !root.muted && root.enabled && (root.hovered || root.down) ? AmneziaStyle.color.bgStateLayer
: AmneziaStyle.color.transparent
Behavior on color {
ColorAnimation { duration: 150 }
}
}
RecommendationBadgeType {
x: 16
y: -root.badgeOffset
visible: root.badgeText !== ""
text: root.badgeText
}
SmallTextType {
id: errorLabel
anchors.top: parent.bottom
anchors.topMargin: 4
anchors.left: parent.left
anchors.right: parent.right
visible: root.errorText !== ""
text: root.errorText
color: AmneziaStyle.color.statusDestructive
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
enabled: root.enabled
}
}
contentItem: RowLayout {
spacing: root.spacing
IconImage {
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: root.icon.width
Layout.preferredHeight: root.icon.height
visible: root.icon.source.toString() !== ""
source: root.icon.source
sourceSize: Qt.size(root.icon.width, root.icon.height)
color: root.icon.color
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 4
H3TextType {
Layout.fillWidth: true
visible: text !== ""
text: root.text
color: root.muted ? AmneziaStyle.color.textTertiary : AmneziaStyle.color.textPrimary
}
SmallTextType {
Layout.fillWidth: true
visible: text !== "" && !root.muted
text: root.subtitle
textFormat: Text.RichText
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
}
CaptionTextType {
Layout.fillWidth: true
visible: text !== "" && root.muted
text: root.subtitle
textFormat: Text.RichText
color: AmneziaStyle.color.textQuaternary
}
SmallTextType {
Layout.fillWidth: true
Layout.topMargin: 8
visible: text !== ""
text: root.footnote
color: root.muted ? AmneziaStyle.color.textQuaternary : AmneziaStyle.color.textSecondary
}
}
IconButtonType {
Layout.alignment: Qt.AlignTop
Layout.leftMargin: -root.spacing
icon.source: "qrc:/images/controls/chevron-right.svg"
icon.color: AmneziaStyle.color.iconDefault
opacity: root.muted ? 0.5 : 1.0
enabled: root.enabled
Accessible.name: root.text
onClicked: root.clicked()
}
}
}
+30
View File
@@ -0,0 +1,30 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
Rectangle {
id: root
property bool filled: false
property alias spacing: column.spacing
default property alias content: column.data
implicitHeight: column.implicitHeight + 8 + 16 + PageController.safeAreaBottomMargin
color: root.filled ? AmneziaStyle.color.bgSurfaceAlt : AmneziaStyle.color.bgBase
ColumnLayout {
id: column
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 8
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 12
}
}
@@ -1,70 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Shapes
import Style 1.0
Popup {
id: root
anchors.centerIn: parent
modal: true
closePolicy: Popup.NoAutoClose
visible: false
Overlay.modal: Rectangle {
color: AmneziaStyle.color.translucentMidnightBlack
}
background: Rectangle {
color: AmneziaStyle.color.transparent
}
BusyIndicator {
id: busyIndicator
visible: true
running: true
contentItem: Item {
implicitWidth: 46
implicitHeight: 46
transformOrigin: Item.Center
Shape {
id: shape
width: parent.implicitWidth
height: parent.implicitHeight
anchors.bottom: parent.bottom
anchors.right: parent.right
layer.enabled: true
layer.samples: 4
ShapePath {
fillColor: AmneziaStyle.color.transparent
strokeColor: AmneziaStyle.color.charcoalGray
strokeWidth: 3
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: shape.width / 2
centerY: shape.height / 2
radiusX: 18
radiusY: 18
startAngle: 225
sweepAngle: -90
}
}
RotationAnimator {
target: shape
running: busyIndicator.visible && busyIndicator.running
from: 0
to: 360
loops: Animation.Infinite
duration: 1250
}
}
}
}
}
+194
View File
@@ -0,0 +1,194 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Button {
id: root
enum Variant { Primary, Secondary, Destructive, Text, Tonal, Surface }
enum Size { Large, Medium, Small }
property int variant: ButtonType.Primary
property int size: ButtonType.Large
property bool destructive: false
property string leftImageSource
property string rightImageSource
implicitHeight: d.height
horizontalPadding: 12
verticalPadding: 8
hoverEnabled: true
opacity: root.enabled ? 1 : d.disabledOpacity
QtObject {
id: d
readonly property bool small: root.size === ButtonType.Small
readonly property bool outlined: root.variant === ButtonType.Secondary || root.variant === ButtonType.Destructive
readonly property bool active: root.down || root.hovered
readonly property int height: root.size === ButtonType.Large ? 48 : (root.size === ButtonType.Medium ? 40 : 36)
readonly property int radius: root.size === ButtonType.Large ? 16 : (root.size === ButtonType.Medium ? 14 : 12)
readonly property int iconSize: small ? 20 : 24
readonly property int gap: root.size === ButtonType.Large ? 4 : 2
readonly property real disabledOpacity: {
switch (root.variant) {
case ButtonType.Primary: return 0.25
case ButtonType.Destructive: return 0.36
case ButtonType.Text: return 0.5
case ButtonType.Surface: return 0.5
default: return 1
}
}
readonly property color backgroundColor: {
switch (root.variant) {
case ButtonType.Primary:
return root.down ? AmneziaStyle.color.buttonPrimaryPress
: (root.hovered ? AmneziaStyle.color.buttonPrimaryHover : AmneziaStyle.color.buttonPrimaryBg)
case ButtonType.Secondary:
case ButtonType.Destructive:
case ButtonType.Surface:
return root.down ? AmneziaStyle.color.bgPanel
: (root.hovered ? AmneziaStyle.color.bgSurfaceAlt : AmneziaStyle.color.bgSurface)
case ButtonType.Tonal:
return AmneziaStyle.color.bgSurfaceAlt
default:
return AmneziaStyle.color.transparent
}
}
readonly property bool stateLayerVisible: active && (root.variant === ButtonType.Text || root.variant === ButtonType.Tonal)
readonly property color borderColor: {
if (outlined) {
return active ? AmneziaStyle.color.borderBold : AmneziaStyle.color.borderSoft
}
if (root.variant === ButtonType.Text && root.down) {
return AmneziaStyle.color.borderEmphasis
}
return AmneziaStyle.color.transparent
}
readonly property color labelColor: {
switch (root.variant) {
case ButtonType.Primary: return AmneziaStyle.color.textInverted
case ButtonType.Destructive: return AmneziaStyle.color.statusDestructive
case ButtonType.Text: return root.destructive ? AmneziaStyle.color.statusDestructive : AmneziaStyle.color.textAccent
case ButtonType.Secondary:
case ButtonType.Tonal:
return root.enabled ? AmneziaStyle.color.textSecondary : AmneziaStyle.color.textQuaternary
default: return AmneziaStyle.color.textSecondary
}
}
readonly property color iconColor: {
switch (root.variant) {
case ButtonType.Primary: return AmneziaStyle.color.iconInverted
case ButtonType.Destructive: return AmneziaStyle.color.statusDestructive
case ButtonType.Text: return root.destructive ? AmneziaStyle.color.statusDestructive : AmneziaStyle.color.textAccent
case ButtonType.Secondary:
case ButtonType.Tonal:
return root.enabled ? AmneziaStyle.color.iconDefault : AmneziaStyle.color.iconTertiary
default: return AmneziaStyle.color.iconDefault
}
}
}
background: Rectangle {
implicitHeight: d.height
radius: d.radius
color: d.backgroundColor
border.width: d.borderColor.a > 0 ? 1 : 0
border.color: d.borderColor
Behavior on color {
ColorAnimation { duration: 150 }
}
Rectangle {
anchors.fill: parent
radius: parent.radius
color: AmneziaStyle.color.bgStateLayer
visible: d.stateLayerVisible
}
FocusRingType {
radius: d.radius
visible: root.visualFocus
}
}
contentItem: Item {
implicitWidth: row.implicitWidth
implicitHeight: row.implicitHeight
RowLayout {
id: row
anchors.centerIn: parent
width: Math.min(implicitWidth, parent.width)
spacing: d.gap
IconImage {
Layout.preferredWidth: d.iconSize
Layout.preferredHeight: d.iconSize
visible: root.leftImageSource !== ""
source: root.leftImageSource
sourceSize: Qt.size(d.iconSize, d.iconSize)
color: d.iconColor
}
ButtonTextType {
Layout.fillWidth: true
visible: text !== ""
text: root.text
leftPadding: 2
rightPadding: 2
color: d.labelColor
lineHeight: (d.small ? 18 : 24) + LanguageUiController.getLineHeightAppend()
font.pixelSize: d.small ? 14 : 16
font.weight: root.variant === ButtonType.Text ? 400 : 600
wrapMode: Text.NoWrap
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
IconImage {
Layout.preferredWidth: d.iconSize
Layout.preferredHeight: d.iconSize
visible: root.rightImageSource !== ""
source: root.rightImageSource
sourceSize: Qt.size(d.iconSize, d.iconSize)
color: d.iconColor
}
}
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
}
+85 -159
View File
@@ -1,8 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window
import Qt5Compat.GraphicalEffects
import Style 1.0 import Style 1.0
@@ -34,20 +32,15 @@ Popup {
return 0 return 0
} }
leftMargin: 25 parent: Overlay.overlay
rightMargin: 25
bottomMargin: 70 + SettingsController.safeAreaBottomMargin
width: parent.width - leftMargin - rightMargin width: parent ? parent.width : 0
height: Math.min(contentLayout.implicitHeight + 21 + 8 + PageController.safeAreaBottomMargin, parent ? parent.height - 20 : 0)
x: Math.round((parent.width - width) / 2) x: 0
y: { y: parent ? parent.height - height - root.keyboardHeight : 0
const centered = Math.round((parent.height - height) / 2)
if (root.keyboardHeight === 0) { padding: 0
return centered
}
return Math.max(20, Math.min(centered, parent.height - root.keyboardHeight - height - 16))
}
Behavior on y { Behavior on y {
NumberAnimation { NumberAnimation {
@@ -57,14 +50,15 @@ Popup {
} }
modal: true modal: true
closePolicy: Popup.NoAutoClose closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
color: AmneziaStyle.color.translucentMidnightBlack color: AmneziaStyle.color.bgOverlay
} }
focus: true
onOpened: { onOpened: {
timer.start()
solutionField.textField.text = "" solutionField.textField.text = ""
solutionField.textField.forceActiveFocus() solutionField.textField.forceActiveFocus()
} }
@@ -81,155 +75,116 @@ Popup {
} }
} }
onClosed: { enter: Transition {
FocusController.dropRootObject(root) NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 150 }
} }
background: Rectangle { exit: Transition {
anchors.fill: parent NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 150 }
color: AmneziaStyle.color.slateGray
radius: 22
} }
Timer { background: Item {
id: timer Rectangle {
interval: 200 anchors.fill: parent
onTriggered: { anchors.bottomMargin: -border.width
FocusController.pushRootObject(root)
FocusController.setFocusItem(solutionField.textField) topLeftRadius: 16
topRightRadius: 16
color: AmneziaStyle.color.bgSurfaceAlt
border.color: AmneziaStyle.color.borderFaint
border.width: 1
}
SheetGrabberType {
anchors.top: parent.top
anchors.topMargin: 16
anchors.horizontalCenter: parent.horizontalCenter
} }
repeat: false
running: true
} }
contentItem: Item { contentItem: Item {
implicitWidth: contentLayout.implicitWidth
implicitHeight: contentLayout.implicitHeight
anchors.fill: parent
ColumnLayout { ColumnLayout {
id: contentLayout id: contentLayout
anchors.fill: parent anchors.top: parent.top
anchors.leftMargin: 20 anchors.left: parent.left
anchors.rightMargin: 20 anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 21 + 16
anchors.bottomMargin: 20 anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 16 spacing: 0
Text { H2TextType {
id: titleText id: titleText
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
text: root.hint text: root.hint
wrapMode: Text.WordWrap
color: AmneziaStyle.color.paleGray
font.pixelSize: 18
font.weight: Font.Bold
font.family: "PT Root UI VF"
lineHeight: 24 + LanguageUiController.getLineHeightAppend()
lineHeightMode: Text.FixedHeight
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignTop
} }
Item { Rectangle {
Layout.fillWidth: true id: imagePanel
Layout.preferredHeight: 200
Rectangle { Layout.fillWidth: true
id: imagePanel Layout.preferredHeight: 128
Layout.topMargin: 32
color: AmneziaStyle.color.bgSurfaceInverse
radius: 16
clip: true
Image {
id: captchaImage
anchors.fill: parent anchors.fill: parent
color: AmneziaStyle.color.pearlGray fillMode: Image.PreserveAspectFit
radius: 16 cache: false
Image { Component.onCompleted: {
id: captchaImage if (captchaImageBase64 !== "") {
source = "data:image/png;base64," + captchaImageBase64
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
cache: false
Component.onCompleted: {
if (captchaImageBase64 !== "") {
source = "data:image/png;base64," + captchaImageBase64
}
}
Connections {
target: root
function onCaptchaImageBase64Changed() {
captchaImage.source = "data:image/png;base64," + root.captchaImageBase64
}
} }
} }
BusyIndicator { Connections {
anchors.centerIn: parent target: root
running: captchaImage.status === Image.Loading function onCaptchaImageBase64Changed() {
} captchaImage.source = "data:image/png;base64," + root.captchaImageBase64
Rectangle {
id: refreshHit
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 10
width: 44
height: 44
radius: width / 2
color: AmneziaStyle.color.charcoalGray
Image {
id: refreshIcon
anchors.centerIn: parent
width: 26
height: 26
fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true
antialiasing: true
source: "qrc:/images/controls/refresh-cw.svg"
// Rasterize SVG at high resolution, then scale down — avoids blocky edges on HiDPI.
readonly property real _dpr: (Window.window && Window.window.screen)
? Window.window.screen.devicePixelRatio : 2.0
readonly property int _raster: Math.ceil(64 * Math.min(Math.max(_dpr, 1.0), 4.0))
sourceSize: Qt.size(_raster, _raster)
layer.enabled: true
layer.smooth: true
layer.textureSize: Qt.size(_raster, _raster)
layer.effect: ColorOverlay {
color: AmneziaStyle.color.goldenApricot
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.refreshCaptchaRequested()
} }
} }
} }
SpinnerType {
anchors.centerIn: parent
color: AmneziaStyle.color.textInverted
visible: captchaImage.status === Image.Loading
}
IconButtonType {
id: refreshButton
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: 16
anchors.bottomMargin: 12
filled: true
icon.source: "qrc:/images/controls/refresh-cw.svg"
onClicked: root.refreshCaptchaRequested()
}
} }
TextFieldWithHeaderType { TextFieldType {
id: solutionField id: solutionField
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.topMargin: 26
headerText: qsTr("Digits from the image") headerText: qsTr("Digits from the image")
headerTextColor: AmneziaStyle.color.mutedGray
textField.placeholderText: qsTr("_ _ _ _ _ _") textField.placeholderText: qsTr("_ _ _ _ _ _")
textField.placeholderTextColor: AmneziaStyle.color.mutedGray
textField.inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText textField.inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText
textField.maximumLength: 6 textField.maximumLength: 6
textField.validator: RegularExpressionValidator { regularExpression: /^[0-9]{0,6}$/ } textField.validator: RegularExpressionValidator { regularExpression: /^[0-9]{0,6}$/ }
@@ -240,47 +195,18 @@ Popup {
} }
} }
Item { ButtonType {
Layout.fillWidth: true
Layout.preferredHeight: 8
}
BasicButtonType {
id: continueButton id: continueButton
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 52 Layout.topMargin: 12
text: qsTr("Continue") text: qsTr("Continue")
defaultColor: AmneziaStyle.color.paleGray
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() { onClicked: {
submitIfNonEmpty() submitIfNonEmpty()
} }
} }
BasicButtonType {
id: closeButton
Layout.fillWidth: true
implicitHeight: 52
text: qsTr("Close")
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
borderColor: AmneziaStyle.color.mutedGray
borderFocusedColor: AmneziaStyle.color.paleGray
clickedFunc: function() {
root.close()
}
}
} }
} }
-166
View File
@@ -1,166 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
RadioButton {
id: root
property string headerText
property string bodyText
property string footerText
property string hoveredColor: AmneziaStyle.color.barelyTranslucentWhite
property string defaultColor: AmneziaStyle.color.transparent
property string disabledColor: AmneziaStyle.color.transparent
property string pressedColor: AmneziaStyle.color.barelyTranslucentWhite
property string selectedColor: AmneziaStyle.color.transparent
property string textColor: AmneziaStyle.color.midnightBlack
property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot
property string selectedBorderColor: AmneziaStyle.color.goldenApricot
property string defaultBodredColor: AmneziaStyle.color.transparent
property string focusBorderColor: AmneziaStyle.color.paleGray
property int borderWidth: 0
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
hoverEnabled: true
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle {
anchors.fill: parent
radius: 16
color: {
if (root.enabled) {
if (root.hovered) {
return hoveredColor
} else if (root.checked) {
return selectedColor
}
return defaultColor
} else {
return disabledColor
}
}
border.color: {
if (root.enabled) {
if (root.pressed) {
return pressedBorderColor
} else if (root.checked) {
return selectedBorderColor
} else if (root.activeFocus) {
return focusBorderColor
}
}
return defaultBodredColor
}
border.width: {
if (root.enabled) {
if(root.checked || root.activeFocus) {
return 1
}
return root.pressed ? 1 : 0
} else {
return 0
}
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
}
ColumnLayout {
id: content
anchors.fill: parent
spacing: 16
Text {
text: root.headerText
wrapMode: Text.WordWrap
color: AmneziaStyle.color.paleGray
font.pixelSize: 25
font.weight: 700
font.family: "PT Root UI VF"
height: 30
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
}
Text {
text: root.bodyText
wrapMode: Text.WordWrap
color: AmneziaStyle.color.paleGray
font.pixelSize: 16
font.weight: 400
font.family: "PT Root UI VF"
height: 24
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: root.footerText !== "" ? 0 : 16
}
Text {
text: root.footerText
wrapMode: Text.WordWrap
visible: root.footerText !== ""
color: AmneziaStyle.color.mutedGray
font.pixelSize: 13
font.weight: 400
font.family: "PT Root UI VF"
height: 16
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
}
@@ -1,205 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Button {
id: root
property string headerText
property string bodyText
property string footerText
property color headerTextColor: AmneziaStyle.color.paleGray
property color bodyTextColor: AmneziaStyle.color.mutedGray
property bool showRecommendedBadge: false
property string recommendedText: ""
property string hoveredColor: AmneziaStyle.color.slateGray
property string defaultColor: AmneziaStyle.color.onyxBlack
property string textColor: AmneziaStyle.color.midnightBlack
property string rightImageSource
property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageSource
property alias focusItem: rightImage
hoverEnabled: true
clip: false
readonly property real cardTextOpacity: !enabled ? 1.0 : pressed ? 0.7 : hovered ? 0.8 : 1.0
background: Rectangle {
id: backgroundRect
anchors.fill: parent
radius: 16
color: root.hovered && root.enabled ? root.hoveredColor : root.defaultColor
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
contentItem: Item {
id: contentRoot
z: 1
anchors.left: parent.left
anchors.right: parent.right
readonly property bool badgeVisible: root.showRecommendedBadge && root.recommendedText !== ""
implicitHeight: layoutCol.implicitHeight
ColumnLayout {
id: layoutCol
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
Item {
id: badgeTopSpacer
Layout.fillWidth: true
Layout.preferredHeight: contentRoot.badgeVisible ? (recBadge.height / 2 + 8) : 0
Rectangle {
id: recBadge
visible: contentRoot.badgeVisible
z: 2
anchors.left: parent.left
anchors.leftMargin: 20
anchors.verticalCenter: parent.top
radius: 10
color: AmneziaStyle.color.softViolet
implicitHeight: recLabel.implicitHeight + 8
implicitWidth: recLabel.implicitWidth + 16
width: implicitWidth
height: implicitHeight
BadgeTextType {
id: recLabel
anchors.centerIn: parent
text: root.recommendedText
}
}
}
RowLayout {
id: content
Layout.fillWidth: true
Image {
id: leftImage
source: leftImageSource
visible: leftImageSource !== ""
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: 24
Layout.bottomMargin: 24
Layout.leftMargin: 24
}
ColumnLayout {
ListItemTitleType {
text: root.headerText
visible: text !== ""
color: root.headerTextColor
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: contentRoot.badgeVisible ? 0 : 16
Layout.bottomMargin: root.bodyText !== "" ? 0 : 16
opacity: root.cardTextOpacity
}
CaptionTextType {
text: root.bodyText
visible: text !== ""
color: root.bodyTextColor
textFormat: Text.RichText
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: root.footerText !== "" ? 0 : 8
opacity: root.cardTextOpacity
}
ButtonTextType {
text: root.footerText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: 16
opacity: root.cardTextOpacity
}
}
ImageButtonType {
id: rightImage
implicitWidth: 40
implicitHeight: 40
hoverEnabled: false
image: rightImageSource
imageColor: rightImageColor
visible: rightImageSource ? true : false
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.topMargin: 16
Layout.bottomMargin: 16
Layout.rightMargin: 16
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: root.pressed ? rightImage.pressedColor : root.hovered && root.enabled ? rightImage.hoveredColor : rightImage.defaultColor
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
root.clicked()
}
}
}
}
}
}
@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import QtQuick.Controls.impl
import Style 1.0 import Style 1.0
@@ -21,20 +21,17 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 8 spacing: 8
Image { IconImage {
Layout.preferredWidth: 18 Layout.preferredWidth: 18
Layout.preferredHeight: 18 Layout.preferredHeight: 18
source: root.iconSource source: root.iconSource
sourceSize: Qt.size(18, 18) sourceSize: Qt.size(18, 18)
color: AmneziaStyle.color.statusSuccess
layer.enabled: true
layer.effect: ColorOverlay {
color: AmneziaStyle.color.accentSuccess
}
} }
AppH3EmphasizedTextType { H3TextType {
font.weight: 600
Layout.fillWidth: true Layout.fillWidth: true
text: root.title text: root.title
@@ -52,7 +49,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
AppTextType { BodyTextType {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.preferredWidth: 24 Layout.preferredWidth: 24
@@ -62,7 +59,7 @@ ColumnLayout {
text: "•" text: "•"
} }
AppTextType { BodyTextType {
Layout.fillWidth: true Layout.fillWidth: true
color: AmneziaStyle.color.textTertiary color: AmneziaStyle.color.textTertiary
+84 -142
View File
@@ -1,7 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0 import Style 1.0
@@ -11,179 +12,120 @@ CheckBox {
id: root id: root
property string descriptionText property string descriptionText
property string descriptionTextColor: AmneziaStyle.color.mutedGray
property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray
property string textColor: AmneziaStyle.color.paleGray implicitWidth: implicitContentWidth + leftPadding + rightPadding
property string textDisabledColor: AmneziaStyle.color.mutedGray implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + topPadding + bottomPadding
property string hoveredColor: AmneziaStyle.color.barelyTranslucentWhite topPadding: 8
property string defaultColor: AmneziaStyle.color.transparent bottomPadding: 8
property string pressedColor: AmneziaStyle.color.barelyTranslucentWhite leftPadding: implicitIndicatorWidth + spacing
rightPadding: 0
spacing: 12
property string defaultBorderColor: AmneziaStyle.color.paleGray hoverEnabled: true
property string checkedBorderColor: AmneziaStyle.color.goldenApricot focusPolicy: Qt.TabFocus
property string checkedBorderDisabledColor: AmneziaStyle.color.deepBrown
property string borderFocusedColor: AmneziaStyle.color.paleGray opacity: root.enabled ? 1 : 0.5
property string checkedImageColor: AmneziaStyle.color.goldenApricot indicator: Item {
property string pressedImageColor: AmneziaStyle.color.burntOrange x: 0
property string defaultImageColor: AmneziaStyle.color.transparent y: (root.height - height) / 2
property string checkedDisabledImageColor: AmneziaStyle.color.mutedBrown
property string imageSource: "qrc:/images/controls/check.svg" implicitWidth: 40
implicitHeight: 40
property bool isFocusable: true Rectangle {
anchors.fill: parent
Keys.onTabPressed: { radius: 12
FocusController.nextKeyTabItem() visible: root.enabled && (root.hovered || root.down)
} color: root.checked ? AmneziaStyle.color.bgStateLayerAccent : AmneziaStyle.color.bgStateLayer
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
hoverEnabled: enabled ? true : false
focusPolicy: Qt.NoFocus
background: Rectangle {
color: AmneziaStyle.color.transparent
border.color: root.focus ? borderFocusedColor : AmneziaStyle.color.transparent
border.width: 1
radius: 16
}
indicator: Rectangle {
id: background
anchors.verticalCenter: parent.verticalCenter
implicitWidth: 56
implicitHeight: 56
radius: 16
color: {
if (root.hovered) {
return hoveredColor
}
return defaultColor
}
Behavior on color {
PropertyAnimation { duration: 200 }
} }
Rectangle { Rectangle {
id: imageBorder id: box
anchors.centerIn: parent anchors.centerIn: parent
width: 24 width: 24
height: 24 height: 24
color: AmneziaStyle.color.transparent radius: 6
border.color: root.checked ?
(root.enabled ? color: root.checked ? AmneziaStyle.color.bgBase : AmneziaStyle.color.transparent
checkedBorderColor :
checkedBorderDisabledColor) :
defaultBorderColor
border.width: 1 border.width: 1
radius: 4 border.color: root.checked ? AmneziaStyle.color.accentDark : AmneziaStyle.color.borderBold
Image { Behavior on border.color {
anchors.centerIn: parent ColorAnimation { duration: 150 }
source: root.pressed ? imageSource : root.checked ? imageSource : ""
layer {
enabled: true
effect: ColorOverlay {
color: {
if (root.pressed) {
return root.pressedImageColor
} else if (root.checked) {
if (root.enabled) {
return root.checkedImageColor
} else {
return root.checkedDisabledImageColor
}
} else {
return root.defaultImageColor
}
}
}
}
} }
} }
MultiEffect {
anchors.fill: check
source: check
visible: root.checked
shadowEnabled: true
shadowColor: AmneziaStyle.color.accentLight
shadowBlur: 0.6
shadowHorizontalOffset: 0
shadowVerticalOffset: 0
}
IconImage {
id: check
anchors.centerIn: box
width: 16
height: 16
visible: root.checked
source: "qrc:/images/controls/check.svg"
sourceSize: Qt.size(16, 16)
color: AmneziaStyle.color.accentLight
}
FocusRingType {
target: box
radius: 6
visible: root.visualFocus
}
} }
contentItem: Item { contentItem: ColumnLayout {
anchors.left: parent.left spacing: 4
anchors.right: parent.right
anchors.leftMargin: 8 + background.width
implicitHeight: content.implicitHeight H3TextType {
Layout.fillWidth: true
ColumnLayout { visible: text !== ""
id: content text: root.text
}
anchors.left: parent.left SmallTextType {
anchors.right: parent.right Layout.fillWidth: true
anchors.verticalCenter: parent.verticalCenter
spacing: 4 visible: text !== ""
text: root.descriptionText
ListItemTitleType {
Layout.fillWidth: true
text: root.text
color: root.enabled ? root.textColor : root.textDisabledColor
}
CaptionTextType {
id: description
Layout.fillWidth: true
text: root.descriptionText
color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor
visible: root.descriptionText !== ""
}
} }
} }
MouseArea { HoverHandler {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: false
} }
Keys.onEnterPressed: event => handleSwitch(event) Keys.onEnterPressed: function(event) {
Keys.onReturnPressed: event => handleSwitch(event)
Keys.onSpacePressed: event => handleSwitch(event)
function handleSwitch(event) {
if (!event.isAutoRepeat) { if (!event.isAutoRepeat) {
root.checked = !root.checked root.toggle()
root.checkedChanged() root.toggled()
}
}
Keys.onReturnPressed: function(event) {
if (!event.isAutoRepeat) {
root.toggle()
root.toggled()
} }
event.accepted = true
} }
} }
+67
View File
@@ -0,0 +1,67 @@
import QtQuick
import QtQuick.Controls
import Style 1.0
import "TextTypes"
Button {
id: root
property bool applied: false
implicitWidth: implicitContentWidth + leftPadding + rightPadding
implicitHeight: 34
horizontalPadding: 14
verticalPadding: 8
hoverEnabled: true
opacity: root.enabled ? 1 : 0.5
background: Rectangle {
radius: 16
color: root.applied ? AmneziaStyle.color.accentLight : AmneziaStyle.color.bgSurfaceAlt
border.width: root.applied || root.checked || (root.hovered && !root.down) ? 1 : 0
border.color: root.applied || root.checked ? AmneziaStyle.color.accentLight : AmneziaStyle.color.borderBold
Rectangle {
anchors.fill: parent
radius: parent.radius
visible: root.down && !root.applied
color: AmneziaStyle.color.bgStateLayer
}
FocusRingType {
radius: 16
visible: root.visualFocus
}
}
contentItem: SmallTextType {
text: root.text
color: {
if (root.applied) {
return AmneziaStyle.color.textInverted
}
return root.checked ? AmneziaStyle.color.textPrimary : AmneziaStyle.color.textSecondary
}
font.weight: root.applied ? 600 : 400
font.letterSpacing: root.applied ? -0.4 : 0
wrapMode: Text.NoWrap
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
}
@@ -35,6 +35,26 @@ Item {
readonly property real listOcclusion: root.pinnedBottom - root.listView.y readonly property real listOcclusion: root.pinnedBottom - root.listView.y
readonly property bool pinnedStuck: root.listScrolled >= root.collapsibleHeight readonly property bool pinnedStuck: root.listScrolled >= root.collapsibleHeight
QtObject {
id: d
property real lastScrolled: 0
}
Connections {
target: root.listView
function onContentYChanged() {
d.lastScrolled = root.listScrolled
}
}
function keepAtTop() {
if (d.lastScrolled <= 0 && root.listScrolled !== 0) {
root.listView.contentY = root.listView.originY
}
}
function clampContentY(value) { function clampContentY(value) {
const maxY = root.listView.originY + Math.max(0, root.listView.contentHeight - root.listView.height) const maxY = root.listView.originY + Math.max(0, root.listView.contentHeight - root.listView.height)
return Math.max(root.listView.originY, Math.min(value, maxY)) return Math.max(root.listView.originY, Math.min(value, maxY))
@@ -44,18 +64,20 @@ Item {
id: headerComponent id: headerComponent
Item { Item {
readonly property real collapsibleHeight: collapsibleColumn.implicitHeight + 4 readonly property real collapsibleHeight: collapsibleColumn.implicitHeight + 12
width: root.listView.width width: root.listView.width
height: collapsibleHeight + pinnedBlock.height height: collapsibleHeight + pinnedBlock.height
onHeightChanged: Qt.callLater(root.keepAtTop)
ColumnLayout { ColumnLayout {
id: collapsibleColumn id: collapsibleColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 4 anchors.topMargin: 12
spacing: 0 spacing: 0
@@ -88,9 +110,9 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
color: AmneziaStyle.color.midnightBlack color: AmneziaStyle.color.bgBase
implicitHeight: navigationRow.y + navigationRow.height + 4 implicitHeight: navigationRow.y + navigationRow.height
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@@ -103,16 +125,20 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 20 + PageController.safeAreaTopMargin anchors.topMargin: PageController.safeAreaTopMargin
height: backButton.implicitHeight height: 48
BackButtonType { IconButtonType {
id: backButton id: backButton
objectName: "backButton" objectName: "backButton"
anchors.left: parent.left x: 16
anchors.right: navigationButtonsRow.left y: 4
anchors.verticalCenter: parent.verticalCenter
size: IconButtonType.Small
icon.source: "qrc:/images/controls/arrow-left.svg"
onClicked: PageController.closePage()
} }
Row { Row {
@@ -120,7 +146,9 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 16 anchors.rightMargin: 16
anchors.verticalCenter: parent.verticalCenter y: 4
spacing: 8
} }
} }
} }
@@ -133,7 +161,7 @@ Item {
y: topBar.height + Math.max(0, root.collapsibleHeight - root.listScrolled) y: topBar.height + Math.max(0, root.collapsibleHeight - root.listScrolled)
height: pinnedColumn.implicitHeight height: pinnedColumn.implicitHeight
color: AmneziaStyle.color.midnightBlack color: AmneziaStyle.color.bgBase
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@@ -151,30 +179,49 @@ Item {
} }
} }
Header1TextType { H1TextType {
id: largeTitleMetrics id: largeTitleMetrics
visible: false visible: false
text: movingTitle.text text: movingTitle.text
} }
Header1TextType { H1TextType {
id: movingTitle id: movingTitle
readonly property real compactScale: 18 / 32 readonly property real compactScale: compactTitle.font.pixelSize / movingTitle.font.pixelSize
readonly property real titleScale: 1 - (1 - movingTitle.compactScale) * root.collapseProgress readonly property real titleScale: 1 - (1 - movingTitle.compactScale) * root.collapseProgress
readonly property real expandedX: 16 readonly property real expandedX: 16
readonly property real compactX: (root.width - movingTitle.width * movingTitle.compactScale) / 2 readonly property real compactX: (root.width - compactTitle.width) / 2
readonly property real expandedY: topBar.height + 4 - Math.min(0, root.listScrolled) readonly property real expandedY: topBar.height + 12 - Math.min(0, root.listScrolled)
readonly property real compactY: navigationRow.y readonly property real compactY: navigationRow.y + (navigationRow.height - compactTitle.height) / 2
+ (navigationRow.height - movingTitle.implicitHeight * movingTitle.compactScale) / 2 readonly property real currentX: movingTitle.expandedX + (movingTitle.compactX - movingTitle.expandedX) * root.collapseProgress
readonly property real currentY: Math.max(movingTitle.compactY, movingTitle.expandedY - Math.max(0, root.listScrolled))
x: movingTitle.expandedX + (movingTitle.compactX - movingTitle.expandedX) * root.collapseProgress x: movingTitle.currentX
y: Math.max(movingTitle.compactY, movingTitle.expandedY - Math.max(0, root.listScrolled)) y: movingTitle.currentY
width: Math.min(movingTitle.implicitWidth, root.width - 32) width: Math.min(movingTitle.implicitWidth, root.width - 32)
transformOrigin: Item.TopLeft transformOrigin: Item.TopLeft
scale: movingTitle.titleScale scale: movingTitle.titleScale
opacity: 1 - root.collapseProgress
wrapMode: Text.NoWrap
maximumLineCount: 1
elide: Text.ElideRight
text: root.title
}
H3TextType {
id: compactTitle
x: movingTitle.currentX
y: movingTitle.currentY
width: Math.min(compactTitle.implicitWidth, root.width - 32)
transformOrigin: Item.TopLeft
scale: movingTitle.titleScale / movingTitle.compactScale
opacity: root.collapseProgress
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
maximumLineCount: 1 maximumLineCount: 1
+2 -2
View File
@@ -53,12 +53,12 @@ Menu {
MenuItem { MenuItem {
text: qsTr("C&ut") text: qsTr("C&ut")
enabled: textObj.selectedText enabled: textObj.selectedText.length > 0
onTriggered: textObj.cut() onTriggered: textObj.cut()
} }
MenuItem { MenuItem {
text: qsTr("&Copy") text: qsTr("&Copy")
enabled: textObj.selectedText enabled: textObj.selectedText.length > 0
onTriggered: textObj.copy() onTriggered: textObj.copy()
} }
MenuItem { MenuItem {
@@ -0,0 +1,53 @@
import QtQuick
import QtQuick.Controls
import Style 1.0
import "TextTypes"
AbstractButton {
id: root
property color textColor: AmneziaStyle.color.textTertiary
property bool emphasized: false
implicitWidth: label.implicitWidth + 24
implicitHeight: 48
hoverEnabled: true
background: Rectangle {
radius: 16
color: root.hovered || root.down ? AmneziaStyle.color.bgStateLayer : AmneziaStyle.color.transparent
border.width: root.down ? 1 : 0
border.color: AmneziaStyle.color.borderMedium
Behavior on color {
ColorAnimation { duration: 150 }
}
FocusRingType {
radius: 16
visible: root.visualFocus
}
}
contentItem: BodyTextType {
id: label
text: root.text
color: root.textColor
font.weight: root.emphasized ? 600 : 400
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
}
-14
View File
@@ -1,14 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
Rectangle {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
height: 1
color: AmneziaStyle.color.slateGray
}
+22
View File
@@ -0,0 +1,22 @@
import QtQuick
import Style 1.0
Item {
id: root
property color color: AmneziaStyle.color.promoRecommendation
implicitWidth: 16
implicitHeight: 16
Rectangle {
anchors.centerIn: parent
width: 6
height: 6
radius: width / 2
color: root.color
}
}
-386
View File
@@ -1,386 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Item {
id: root
readonly property string drawerExpandedStateName: "expanded"
readonly property string drawerCollapsedStateName: "collapsed"
readonly property bool isOpened: isExpandedStateActive() || (isCollapsedStateActive() && (dragArea.drag.active === true))
readonly property bool isClosed: isCollapsedStateActive() && (dragArea.drag.active === false)
property Component collapsedStateContent
property Component expandedStateContent
property string defaultColor: AmneziaStyle.color.onyxBlack
property string borderColor: AmneziaStyle.color.slateGray
property real expandedHeight
property real collapsedHeight: 0
property int depthIndex: 0
signal cursorEntered
signal cursorExited
signal pressed(bool pressed, bool entered)
signal aboutToHide
signal aboutToShow
signal closeTriggered
signal openTriggered
signal closed
signal opened
function isExpandedStateActive() {
return isStateActive(drawerExpandedStateName)
}
function isCollapsedStateActive() {
return isStateActive(drawerCollapsedStateName)
}
function isStateActive(stateName) {
return drawerContent.state === stateName
}
function isDrawerType2(obj) {
return obj && typeof obj.drawerExpandedStateName !== "undefined" &&
typeof obj.drawerCollapsedStateName !== "undefined"
}
function isDescendantOfDrawer(obj) {
var current = obj
while (current && current !== root.parent) {
if (isDrawerType2(current)) {
return true
}
current = current.parent
}
return false
}
function findComponent(obj, typeCtor) {
if (!obj)
return null
if (isDrawerType2(obj) || isDescendantOfDrawer(obj))
return null
if (obj instanceof typeCtor)
return obj
if (obj.children && obj.children.length > 0) {
for (var i = 0; i < obj.children.length; i++) {
var matchingChildren = findComponent(obj.children[i], typeCtor)
if (matchingChildren) return matchingChildren
}
}
if (obj.contentItem) {
var matchingContentItem = findComponent(obj.contentItem, typeCtor)
if (matchingContentItem) return matchingContentItem
}
return null
}
function setParentInteractive(value) {
var flickableType = findComponent(root.parent, Flickable)
var listViewType = findComponent(root.parent, ListView)
if (flickableType) flickableType.interactive = value
if (listViewType) listViewType.interactive = value
}
Connections {
target: Qt.application
function onStateChanged() {
if (Qt.application.state !== Qt.ApplicationActive) {
if (dragArea.drag.active) {
dragArea.drag.target = null
dragArea.drag.target = drawerContent
}
if (isOpened && !isCollapsedStateActive()) {
root.closeTriggered()
}
}
}
}
Connections {
target: PageController
function onCloseTopDrawer() {
if (depthIndex === PageController.getDrawerDepth()) {
if (isCollapsedStateActive()) {
return
}
aboutToHide()
drawerContent.state = root.drawerCollapsedStateName
depthIndex = 0
closed()
}
}
}
Connections {
target: root
function onCloseTriggered() {
if (isCollapsedStateActive()) {
return
}
aboutToHide()
setParentInteractive(true)
closed()
}
function onClosed() {
drawerContent.state = root.drawerCollapsedStateName
if (root.isCollapsedStateActive()) {
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
}
}
depthIndex = 0
PageController.decrementDrawerDepth()
FocusController.dropRootObject(root)
}
function onOpenTriggered() {
if (root.isExpandedStateActive()) {
return
}
root.aboutToShow()
setParentInteractive(false)
root.opened()
}
function onOpened() {
drawerContent.state = root.drawerExpandedStateName
if (isExpandedStateActive()) {
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(0xFF1C1D21)
}
}
depthIndex = PageController.incrementDrawerDepth()
FocusController.pushRootObject(root)
}
}
Rectangle {
id: background
anchors.fill: parent
color: root.isCollapsedStateActive() ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
MouseArea {
id: emptyArea
anchors.fill: parent
onClicked: {
root.closeTriggered()
}
}
MouseArea {
id: dragArea
objectName: "dragArea"
anchors.fill: drawerContentBackground
hoverEnabled: true
enabled: drawerContent.implicitHeight > 0
drag.target: drawerContent
drag.axis: Drag.YAxis
drag.maximumY: root.height - root.collapsedHeight
drag.minimumY: root.height - root.expandedHeight
/** If drag area is released at any point other than min or max y, transition to the other state */
onReleased: {
if (isCollapsedStateActive() && drawerContent.y < dragArea.drag.maximumY) {
root.openTriggered()
return
}
if (isExpandedStateActive() && drawerContent.y > dragArea.drag.minimumY) {
root.closeTriggered()
return
}
}
onEntered: {
root.cursorEntered()
}
onExited: {
root.cursorExited()
}
onPressedChanged: {
root.pressed(pressed, entered)
}
onClicked: {
if (isCollapsedStateActive()) {
root.openTriggered()
}
}
}
Rectangle {
id: drawerContentBackground
objectName: "drawerContentBackground"
anchors { left: drawerContent.left; right: drawerContent.right; top: drawerContent.top }
height: root.height
radius: 16
color: root.defaultColor
border.color: root.borderColor
border.width: 1
Rectangle {
width: parent.radius
height: parent.radius
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
color: parent.color
}
}
Item {
id: drawerContent
objectName: "drawerContent"
Drag.active: dragArea.drag.active
anchors.right: root.right
anchors.left: root.left
state: root.drawerCollapsedStateName
states: [
State {
name: root.drawerCollapsedStateName
PropertyChanges {
target: drawerContent
implicitHeight: collapsedHeight
y: root.height - root.collapsedHeight
}
PropertyChanges {
target: background
color: AmneziaStyle.color.transparent
}
PropertyChanges {
target: dragArea
cursorShape: Qt.PointingHandCursor
}
PropertyChanges {
target: emptyArea
enabled: false
visible: false
}
PropertyChanges {
target: collapsedLoader
// visible: true
}
PropertyChanges {
target: expandedLoader
visible: false
}
},
State {
name: root.drawerExpandedStateName
PropertyChanges {
target: drawerContent
implicitHeight: expandedHeight
y: dragArea.drag.minimumY
}
PropertyChanges {
target: background
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
}
PropertyChanges {
target: dragArea
cursorShape: Qt.ArrowCursor
}
PropertyChanges {
target: emptyArea
enabled: true
visible: true
}
PropertyChanges {
target: collapsedLoader
// visible: false
}
PropertyChanges {
target: expandedLoader
visible: true
}
}
]
transitions: [
Transition {
from: root.drawerCollapsedStateName
to: root.drawerExpandedStateName
PropertyAnimation {
target: drawerContent
properties: "y"
duration: 200
}
},
Transition {
from: root.drawerExpandedStateName
to: root.drawerCollapsedStateName
PropertyAnimation {
target: drawerContent
properties: "y"
duration: 200
}
}
]
Loader {
id: collapsedLoader
sourceComponent: root.collapsedStateContent
anchors.right: parent.right
anchors.left: parent.left
}
Loader {
id: expandedLoader
sourceComponent: root.expandedStateContent
anchors.right: parent.right
anchors.left: parent.left
}
}
}
+87 -163
View File
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Layouts import QtQuick.Layouts
import Style 1.0 import Style 1.0
@@ -7,227 +8,150 @@ import Style 1.0
import "TextTypes" import "TextTypes"
import "../Config" import "../Config"
Item { AbstractButton {
id: root id: root
property string text enum Variant { Default, Primary }
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray property int variant: DropDownType.Default
property int textMaximumLineCount: 2
property int textElide: Qt.ElideRight
property string descriptionText property string descriptionText
property string descriptionTextColor: AmneziaStyle.color.mutedGray property string placeholderText
property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray
property string headerText property string headerText
property string headerBackButtonImage property string headerBackButtonImage
property var rootButtonClickedFunction
property string rootButtonImage: "qrc:/images/controls/chevron-down.svg"
property string rootButtonImageColor: AmneziaStyle.color.paleGray
property string rootButtonBackgroundColor: AmneziaStyle.color.onyxBlack
property string rootButtonBackgroundHoveredColor: AmneziaStyle.color.onyxBlack
property string rootButtonBackgroundPressedColor: AmneziaStyle.color.onyxBlack
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
property string rootButtonHoveredBorderColor: AmneziaStyle.color.charcoalGray
property string rootButtonDefaultBorderColor: AmneziaStyle.color.slateGray
property string rootButtonPressedBorderColor: AmneziaStyle.color.paleGray
property int rootButtonTextLeftMargins: 16
property int rootButtonTextTopMargin: 16
property int rootButtonTextBottomMargin: 16
property real drawerHeight: 0.9 property real drawerHeight: 0.9
property bool fitContent: false property bool fitContent: false
property Item drawerParent
property Component listView property Component listView
signal openTriggered readonly property bool isPrimary: root.variant === DropDownType.Primary
signal closeTriggered
readonly property bool isFocusable: true function open() {
menu.open()
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
} }
Keys.onBacktabPressed: { function close() {
FocusController.previousKeyTabItem() menu.close()
} }
Keys.onUpPressed: { implicitWidth: implicitContentWidth + leftPadding + rightPadding
FocusController.nextKeyUpItem() implicitHeight: implicitContentHeight + topPadding + bottomPadding
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: { leftPadding: 16
FocusController.nextKeyRightItem() rightPadding: root.isPrimary ? 4 : 16
} topPadding: root.isPrimary ? 0 : 12
bottomPadding: root.isPrimary ? 0 : 12
Connections { hoverEnabled: true
target: Qt.application
function onStateChanged() { opacity: root.enabled ? 1 : 0.4
if (Qt.application.state !== Qt.ApplicationActive) {
if (!menu.isClosed) {
menu.closeTriggered()
}
}
}
}
implicitWidth: rootButtonContent.implicitWidth onClicked: menu.open()
implicitHeight: rootButtonContent.implicitHeight
onOpenTriggered: { background: Rectangle {
menu.openTriggered()
}
onCloseTriggered: {
menu.closeTriggered()
}
Keys.onEnterPressed: {
if (menu.isClosed) {
menu.openTriggered()
}
}
Keys.onReturnPressed: {
if (menu.isClosed) {
menu.openTriggered()
}
}
Rectangle {
id: focusBorder
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
anchors.fill: rootButtonContent
radius: 16 radius: 16
color: {
if (!root.isPrimary) {
return AmneziaStyle.color.bgSurface
}
if (root.down) {
return AmneziaStyle.color.buttonPrimaryPress
}
return root.hovered ? AmneziaStyle.color.buttonPrimaryHover : AmneziaStyle.color.buttonPrimaryBg
}
Behavior on color {
ColorAnimation { duration: 150 }
}
Rectangle { Rectangle {
id: rootButtonBackground anchors.fill: parent
anchors.fill: focusBorder radius: parent.radius
anchors.margins: root.activeFocus ? 2 : 0 visible: !root.isPrimary && root.enabled && (root.hovered || root.down)
radius: root.activeFocus ? 14 : 16 color: AmneziaStyle.color.bgStateLayer
}
color: { FocusRingType {
if (root.enabled) { radius: 16
if (root.pressed) { visible: root.visualFocus
return root.rootButtonBackgroundPressedColor
}
return root.hovered ? root.rootButtonBackgroundHoveredColor : root.rootButtonBackgroundColor
} else {
return AmneziaStyle.color.transparent
}
}
border.color: rootButtonDefaultBorderColor
border.width: 1
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
} }
} }
RowLayout { contentItem: RowLayout {
id: rootButtonContent spacing: 8
anchors.fill: parent
spacing: 0
ColumnLayout { ColumnLayout {
Layout.leftMargin: rootButtonTextLeftMargins Layout.fillWidth: true
Layout.topMargin: rootButtonTextTopMargin Layout.alignment: Qt.AlignVCenter
Layout.bottomMargin: rootButtonTextBottomMargin
LabelTextType { spacing: 4
CaptionTextType {
Layout.fillWidth: true Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft visible: text !== ""
verticalAlignment: Text.AlignVCenter
visible: root.descriptionText !== ""
color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor
text: root.descriptionText text: root.descriptionText
} }
ButtonTextType { H3TextType {
id: buttonText
Layout.fillWidth: true Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft visible: !root.isPrimary
verticalAlignment: Text.AlignVCenter text: root.text !== "" ? root.text : root.placeholderText
color: root.text !== "" ? AmneziaStyle.color.textPrimary : AmneziaStyle.color.textQuaternary
maximumLineCount: 2
elide: Text.ElideRight
}
color: root.enabled ? root.textColor : root.textDisabledColor ButtonTextType {
Layout.fillWidth: true
visible: root.isPrimary
text: root.text text: root.text
maximumLineCount: root.textMaximumLineCount color: AmneziaStyle.color.textInverted
elide: root.textElide maximumLineCount: 1
elide: Text.ElideRight
} }
} }
ImageButtonType { Item {
Layout.rightMargin: 16 Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.alignment: Qt.AlignVCenter
implicitWidth: 40 IconImage {
implicitHeight: 40 anchors.centerIn: parent
hoverEnabled: false width: 24
image: rootButtonImage height: 24
imageColor: rootButtonImageColor
source: "qrc:/images/controls/chevron-down.svg"
sourceSize: Qt.size(24, 24)
color: root.isPrimary ? AmneziaStyle.color.iconInverted : AmneziaStyle.color.iconDefault
}
} }
} }
MouseArea { HoverHandler {
anchors.fill: rootButtonContent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
hoverEnabled: root.enabled ? true : false
onClicked: {
if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") {
rootButtonClickedFunction()
} else {
menu.openTriggered()
}
}
} }
DrawerType2 { Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
SheetType {
id: menu id: menu
parent: drawerParent
anchors.fill: parent
property real measuredContentHeight: 0 property real measuredContentHeight: 0
expandedHeight: (root.fitContent && measuredContentHeight > 0) expandedHeight: (root.fitContent && measuredContentHeight > 0)
? Math.min(measuredContentHeight, drawerParent.height * root.drawerHeight) ? Math.min(measuredContentHeight, windowHeight * root.drawerHeight)
: drawerParent.height * root.drawerHeight : windowHeight * root.drawerHeight
expandedStateContent: Item { Item {
id: container id: container
implicitHeight: menu.expandedHeight anchors.fill: parent
property real fitHeight: backButton.implicitHeight + titleLabel.implicitHeight property real fitHeight: backButton.implicitHeight + titleLabel.implicitHeight
+ (listViewLoader.item ? listViewLoader.item.contentHeight : 0) + 48 + (listViewLoader.item ? listViewLoader.item.contentHeight : 0) + 48
@@ -243,10 +167,10 @@ Item {
BackButtonType { BackButtonType {
id: backButton id: backButton
backButtonImage: root.headerBackButtonImage backButtonImage: root.headerBackButtonImage
backButtonFunction: function() { menu.closeTriggered() } backButtonFunction: function() { menu.close() }
} }
Header2Type { SectionHeaderType {
id: titleLabel id: titleLabel
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
@@ -0,0 +1,82 @@
import QtQuick
import QtQuick.Controls.impl
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Item {
id: root
property string iconSource: "qrc:/images/controls/search.svg"
property color iconColor: AmneziaStyle.color.iconSecondary
property string title
property string description
default property alias actions: actionsRow.data
implicitWidth: content.implicitWidth + 32
implicitHeight: content.implicitHeight + 64
ColumnLayout {
id: content
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -32
width: Math.min(parent.width - 32, 358)
spacing: 20
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 2
IconImage {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 50
Layout.preferredHeight: 50
visible: root.iconSource !== ""
source: root.iconSource
sourceSize: Qt.size(50, 50)
color: root.iconColor
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: Math.min(280, content.width)
spacing: 6
H3TextType {
Layout.fillWidth: true
visible: text !== ""
text: root.title
horizontalAlignment: Text.AlignHCenter
}
SmallTextType {
Layout.fillWidth: true
visible: text !== ""
text: root.description
color: AmneziaStyle.color.textSecondary
horizontalAlignment: Text.AlignHCenter
}
}
}
RowLayout {
id: actionsRow
Layout.alignment: Qt.AlignHCenter
spacing: 16
visible: children.length > 0
}
}
}
+4 -19
View File
@@ -1,18 +1,8 @@
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
import "../Config"
Flickable { Flickable {
id: fl id: root
function ensureVisible(item) {
if (item.y < fl.contentY) {
fl.contentY = item.y - 40 // 40 is a top margin
} else if (item.y + item.height > fl.contentY + fl.height) {
fl.contentY = item.y + item.height - fl.height + 40 // 40 is a bottom margin
}
fl.returnToBounds()
}
clip: true clip: true
width: parent.width width: parent.width
@@ -22,10 +12,5 @@ Flickable {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 1 anchors.rightMargin: 1
Keys.onUpPressed: scrollBar.decrease() ScrollBar.vertical: ScrollBarType {}
Keys.onDownPressed: scrollBar.increase()
ScrollBar.vertical: ScrollBarType {
id: scrollBar
}
} }
+37
View File
@@ -0,0 +1,37 @@
import QtQuick
import QtQuick.Effects
import Style 1.0
Item {
id: root
property Item target: parent
property real radius: 16
property bool accent: false
anchors.fill: target
anchors.margins: -3
MultiEffect {
anchors.fill: ring
source: ring
visible: root.visible
shadowEnabled: true
shadowColor: ring.border.color
shadowBlur: 0.5
shadowHorizontalOffset: 0
shadowVerticalOffset: 0
}
Rectangle {
id: ring
anchors.fill: parent
radius: root.radius + 3
color: AmneziaStyle.color.transparent
border.width: 1
border.color: root.accent ? AmneziaStyle.color.accentLight : AmneziaStyle.color.borderEmphasis
}
}
-79
View File
@@ -1,79 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Item {
id: root
property string actionButtonImage
property var actionButtonFunction
property alias actionButton: headerActionButton
property string headerText
property string descriptionText
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
ColumnLayout {
id: content
anchors.fill: parent
RowLayout {
Header2TextType {
id: header
Layout.fillWidth: true
text: root.headerText
}
ImageButtonType {
id: headerActionButton
implicitWidth: 40
implicitHeight: 40
image: root.actionButtonImage
imageColor: AmneziaStyle.color.paleGray
visible: image ? true : false
onClicked: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
}
}
ParagraphTextType {
id: description
Layout.topMargin: 16
Layout.fillWidth: true
text: root.descriptionText
color: AmneziaStyle.color.mutedGray
visible: root.descriptionText !== ""
}
}
Keys.onEnterPressed: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
Keys.onReturnPressed: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
}
+153
View File
@@ -0,0 +1,153 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Item {
id: root
property string headerText
property int headerTextFormat: Text.AutoText
property int headerHorizontalAlignment: Text.AlignLeft
property int headerTextMaximumLineCount: 2
property int headerTextElide: Qt.ElideRight
property string descriptionText
property string descriptionLinkText
property string descriptionLinkUrl
property string actionButtonImage
property bool showSwitcher: false
readonly property alias actionButton: actionButton
readonly property alias switcher: switcher
signal actionClicked()
signal switcherToggled(bool checked)
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 8
RowLayout {
Layout.fillWidth: true
spacing: 8
H1TextType {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: root.headerText
textFormat: root.headerTextFormat
horizontalAlignment: root.headerHorizontalAlignment
maximumLineCount: root.headerTextMaximumLineCount
elide: root.headerTextElide
}
IconButtonType {
id: actionButton
Layout.alignment: Qt.AlignVCenter
visible: root.actionButtonImage !== ""
icon.source: root.actionButtonImage
onClicked: root.actionClicked()
}
SwitchType {
id: switcher
Layout.alignment: Qt.AlignVCenter
visible: root.showSwitcher
onToggled: root.switcherToggled(switcher.checked)
}
}
BodyTextType {
Layout.fillWidth: true
visible: root.descriptionText !== ""
text: root.descriptionText
color: AmneziaStyle.color.textSecondary
}
AbstractButton {
id: descriptionLink
Layout.fillWidth: true
visible: root.descriptionLinkText !== "" && root.descriptionLinkUrl !== ""
implicitHeight: linkRow.implicitHeight
hoverEnabled: true
text: root.descriptionLinkText
Accessible.role: Accessible.Link
background: null
contentItem: Item {
implicitHeight: linkRow.implicitHeight
RowLayout {
id: linkRow
width: Math.min(implicitWidth, parent.width)
spacing: 8
BodyTextType {
Layout.fillWidth: true
text: descriptionLink.text
color: AmneziaStyle.color.textAccent
font.underline: descriptionLink.hovered
}
IconImage {
Layout.preferredWidth: 14
Layout.preferredHeight: 14
source: "qrc:/images/controls/external-link.svg"
sourceSize: Qt.size(14, 14)
color: AmneziaStyle.color.textAccent
}
}
FocusRingType {
target: linkRow
radius: 4
visible: descriptionLink.visualFocus
}
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
onClicked: Qt.openUrlExternally(root.descriptionLinkUrl)
Keys.onEnterPressed: descriptionLink.clicked()
Keys.onReturnPressed: descriptionLink.clicked()
}
}
}
@@ -1,44 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
BaseHeaderType {
id: root
property string actionButtonImage
property var actionButtonFunction
property alias actionButton: headerActionButton
Component.onCompleted: {
headerRow.children.push(headerActionButton)
}
ImageButtonType {
id: headerActionButton
implicitWidth: 40
implicitHeight: 40
Layout.alignment: Qt.AlignRight
image: root.actionButtonImage
imageColor: AmneziaStyle.color.paleGray
visible: image ? true : false
onClicked: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
}
Keys.onEnterPressed: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
Keys.onReturnPressed: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
}
@@ -1,28 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Style 1.0
BaseHeaderType {
id: root
property var switcherFunction
property bool showSwitcher: false
property alias switcher: headerSwitcher
Component.onCompleted: {
headerRow.children.push(headerSwitcher)
}
SwitcherType {
id: headerSwitcher
Layout.alignment: Qt.AlignRight
visible: root.showSwitcher
onToggled: {
if (switcherFunction && typeof switcherFunction === "function") {
switcherFunction(checked)
}
}
}
}
@@ -1,139 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
RadioButton {
id: root
property string hoveredColor: AmneziaStyle.color.barelyTranslucentWhite
property string defaultColor: AmneziaStyle.color.transparent
property string checkedColor: AmneziaStyle.color.transparent
property string disabledColor: AmneziaStyle.color.transparent
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
property string pressedBorderColor: AmneziaStyle.color.charcoalGray
property string checkedBorderColor: AmneziaStyle.color.goldenApricot
property string defaultBodredColor: AmneziaStyle.color.transparent
property string checkedDisabledBorderColor: AmneziaStyle.color.mutedBrown
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderWidth: 0
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle {
anchors.fill: parent
radius: 16
color: {
if (root.enabled) {
if (root.hovered) {
return root.hoveredColor
} else if (root.checked) {
return root.checkedColor
}
return root.defaultColor
} else {
return root.disabledColor
}
}
border.color: {
if (root.enabled) {
if (root.pressed) {
return root.pressedBorderColor
} else if (root.checked) {
return root.checkedBorderColor
} else if (root.activeFocus) {
return root.borderFocusedColor
}
return root.defaultBodredColor
} else {
if (root.checked) {
return root.checkedDisabledBorderColor
}
return root.defaultBodredColor
}
}
border.width: {
if(root.checked || root.activeFocus) {
return 1
}
return root.pressed ? 1 : 0
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
}
ColumnLayout {
id: content
anchors.fill: parent
spacing: 0
ButtonTextType {
text: root.text
color: root.enabled ? root.textColor : root.textDisabledColor
Layout.fillWidth: true
Layout.rightMargin: 24
Layout.leftMargin: 24
Layout.topMargin: 12
Layout.bottomMargin: 12
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
this.clicked()
}
Keys.onReturnPressed: {
this.clicked()
}
}
@@ -0,0 +1,97 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import Style 1.0
Button {
id: root
enum Size { Small, Regular }
property int size: IconButtonType.Regular
property bool filled: false
implicitWidth: d.box
implicitHeight: d.box
padding: 0
hoverEnabled: true
display: AbstractButton.IconOnly
icon.width: d.iconSize
icon.height: d.iconSize
icon.color: root.filled ? AmneziaStyle.color.iconInverted : AmneziaStyle.color.iconDefault
opacity: root.enabled ? 1 : 0.5
QtObject {
id: d
readonly property int box: root.size === IconButtonType.Small ? 36 : 48
readonly property int iconSize: root.size === IconButtonType.Small ? 20 : 24
}
background: Item {
implicitWidth: d.box
implicitHeight: d.box
Rectangle {
anchors.fill: parent
anchors.margins: root.filled && root.down ? 2 : 0
radius: root.filled && root.down ? 10 : 12
color: {
if (root.filled) {
return root.hovered || root.down ? AmneziaStyle.color.bgSurfaceInverseAlt : AmneziaStyle.color.bgSurfaceInverse
}
return root.hovered || root.down ? AmneziaStyle.color.bgStateLayer : AmneziaStyle.color.transparent
}
border.width: !root.filled && root.down ? 1 : 0
border.color: AmneziaStyle.color.borderMedium
Behavior on color {
ColorAnimation { duration: 150 }
}
}
Rectangle {
anchors.fill: parent
visible: root.filled && root.down
radius: 12
color: AmneziaStyle.color.transparent
border.width: 1
border.color: AmneziaStyle.color.borderEmphasis
}
FocusRingType {
radius: 12
visible: root.visualFocus
}
}
contentItem: Item {
implicitWidth: root.icon.width
implicitHeight: root.icon.height
IconImage {
anchors.centerIn: parent
width: root.icon.width
height: root.icon.height
source: root.icon.source
sourceSize: Qt.size(root.icon.width, root.icon.height)
color: root.icon.color
}
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
}
@@ -1,94 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
Button {
id: root
property string image
property string hoveredColor: AmneziaStyle.color.translucentWhite
property string defaultColor: AmneziaStyle.color.transparent
property string pressedColor: AmneziaStyle.color.sheerWhite
property string disableColor: AmneziaStyle.color.slateGray
property string imageColor: AmneziaStyle.color.mutedGray
property string disableImageColor: AmneziaStyle.color.slateGray
property alias backgroundColor: background.color
property alias backgroundRadius: background.radius
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
hoverEnabled: true
icon.source: image
icon.color: root.enabled ? imageColor : disableImageColor
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
Behavior on icon.color {
PropertyAnimation { duration: 200 }
}
background: Rectangle {
id: background
anchors.fill: parent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return hovered ? hoveredColor : defaultColor
}
return defaultColor
}
radius: 12
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
}
MouseArea {
anchors.fill: parent
enabled: false
cursorShape: Qt.PointingHandCursor
}
}
@@ -0,0 +1,88 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import Style 1.0
import "TextTypes"
Button {
id: root
property bool joined: false
property string imageSource
implicitWidth: root.imageSource !== "" ? 48 : Math.max(83, implicitContentWidth + leftPadding + rightPadding)
implicitHeight: 64
horizontalPadding: 12
verticalPadding: 8
hoverEnabled: true
opacity: root.enabled ? 1 : 0.25
background: Rectangle {
radius: 16
topLeftRadius: root.joined ? 0 : 16
bottomLeftRadius: root.joined ? 0 : 16
color: {
if (root.down) {
return AmneziaStyle.color.buttonPrimaryPress
}
return root.hovered ? AmneziaStyle.color.buttonPrimaryHover : AmneziaStyle.color.buttonPrimaryBg
}
Behavior on color {
ColorAnimation { duration: 150 }
}
FocusRingType {
radius: 16
visible: root.visualFocus
}
}
contentItem: Item {
implicitWidth: root.imageSource !== "" ? 24 : label.implicitWidth
implicitHeight: 24
IconImage {
anchors.centerIn: parent
width: 24
height: 24
visible: root.imageSource !== ""
source: root.imageSource
sourceSize: Qt.size(24, 24)
color: AmneziaStyle.color.iconInverted
}
ButtonTextType {
id: label
anchors.centerIn: parent
width: Math.min(implicitWidth, parent.width)
visible: root.imageSource === "" && text !== ""
text: root.text
color: AmneziaStyle.color.textInverted
lineHeight: 18 + LanguageUiController.getLineHeightAppend()
font.pixelSize: 14
wrapMode: Text.NoWrap
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
}
@@ -1,349 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
Item {
id: root
// property alias focusObjectName: eyeImage.objectName
property string text
property int textMaximumLineCount: 2
property int textElide: Qt.ElideRight
property string descriptionText
property var clickedFunction
property string buttonImageSource
property string rightImageSource
property string leftImageSource
property string rightWarningImageSource
property string rightWarningImageColor: AmneziaStyle.color.goldenApricot
property bool isLeftImageHoverEnabled: true
property bool isSmallLeftImage: false
property alias rightButton: rightImage
property alias eyeButton: eyeImage
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
property string descriptionColor: AmneziaStyle.color.mutedGray
property string descriptionDisabledColor: AmneziaStyle.color.charcoalGray
property real textOpacity: 1.0
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageColor: ""
property bool descriptionOnTop: false
property bool hideDescription: true
property bool isFocusable: !(eyeImage.visible || rightImage.visible) // TODO: this component already has focusable items
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin
implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin
MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: root.enabled
onEntered: {
if (rightImageSource) {
rightImageBackground.color = rightImage.hoveredColor
} else if (leftImageSource) {
leftImageBackground.color = rightImage.hoveredColor
}
root.textOpacity = 0.8
}
onExited: {
if (rightImageSource) {
rightImageBackground.color = rightImage.defaultColor
} else if (leftImageSource) {
leftImageBackground.color = rightImage.defaultColor
}
root.textOpacity = 1
}
onPressedChanged: {
if (rightImageSource) {
rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
} else if (leftImageSource) {
leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
}
root.textOpacity = 0.7
}
onClicked: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
RowLayout {
id: content
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
anchors.topMargin: 16
anchors.bottomMargin: 16
Rectangle {
id: leftImageBackground
visible: leftImageSource ? true : false
Layout.preferredHeight: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage) ? 40 : 56
Layout.preferredWidth: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage)? 40 : 56
Layout.rightMargin: isSmallLeftImage ? 8 : (rightImageSource || !isLeftImageHoverEnabled) ? 16 : 0
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
Image {
id: leftImage
anchors.centerIn: parent
source: leftImageSource
visible: leftImageColor === ""
}
ColorOverlay {
anchors.fill: leftImage
source: leftImage
color: leftImageColor
visible: leftImageColor !== ""
}
}
ColumnLayout {
property real textLineHeight: 21.6
property real descriptionTextLineHeight: 16
property int textPixelSize: 18
property int descriptionTextSize: 13
ListItemTitleType {
text: root.text
color: {
if (root.enabled) {
return root.descriptionOnTop ? root.descriptionColor : root.textColor
} else {
return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor
}
}
maximumLineCount: root.textMaximumLineCount
elide: root.textElide
opacity: root.textOpacity
Layout.fillWidth: true
lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight
font.pixelSize: root.descriptionOnTop ? parent.descriptionTextSize : parent.textPixelSize
font.letterSpacing: root.descriptionOnTop ? 0.02 : 0
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
}
CaptionTextType {
id: description
text: (eyeImage.visible && hideDescription) ? replaceWithAsterisks(root.descriptionText) : root.descriptionText
color: {
if (root.enabled) {
return root.descriptionOnTop ? root.textColor : root.descriptionColor
} else {
return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor
}
}
opacity: root.textOpacity
visible: root.descriptionText !== ""
Layout.fillWidth: true
lineHeight: root.descriptionOnTop ? parent.textLineHeight : parent.descriptionTextLineHeight
font.pixelSize: root.descriptionOnTop ? parent.textPixelSize : parent.descriptionTextSize
font.letterSpacing: root.descriptionOnTop ? 0 : 0.02
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
function replaceWithAsterisks(input) {
return '*'.repeat(input.length)
}
}
}
Rectangle {
id: rightWarningImageBackground
visible: rightWarningImageSource !== ""
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignRight
radius: 12
color: AmneziaStyle.color.transparent
Image {
id: rightWarningImage
anchors.centerIn: parent
source: rightWarningImageSource
visible: false
}
ColorOverlay {
anchors.fill: rightWarningImage
source: rightWarningImage
color: rightWarningImageColor
}
}
ImageButtonType {
id: eyeImage
visible: buttonImageSource !== ""
implicitWidth: 40
implicitHeight: 40
hoverEnabled: true
image: buttonImageSource
imageColor: rightImageColor
Layout.alignment: Qt.AlignRight
Rectangle {
id: eyeImageBackground
anchors.fill: parent
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
hideDescription = !hideDescription
}
Keys.onEnterPressed: {
clicked()
}
Keys.onReturnPressed: {
clicked()
}
}
ImageButtonType {
id: rightImage
implicitWidth: 40
implicitHeight: 40
hoverEnabled: false
image: rightImageSource
imageColor: rightImageColor
visible: rightImageSource ? true : false
Layout.alignment: Qt.AlignRight
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
}
Rectangle {
id: background
anchors.fill: root
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
Keys.onEnterPressed: {
if (!rightImageSource && clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
Keys.onReturnPressed: {
if (!rightImageSource && clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
@@ -1,42 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
RowLayout {
id: root
property string imageSource
property string leftText
property var rightText
property bool isRightTextUndefined: rightText === undefined
property int rightTextFormat: Text.PlainText
visible: !isRightTextUndefined
Image {
Layout.preferredHeight: 18
Layout.preferredWidth: 18
source: root.imageSource
}
ListItemTitleType {
Layout.fillWidth: true
Layout.rightMargin: 10
Layout.alignment: Qt.AlignRight
text: root.leftText
}
ParagraphTextType {
visible: root.rightText !== ""
Layout.alignment: Qt.AlignLeft
text: root.isRightTextUndefined ? "" : root.rightText
textFormat: root.rightTextFormat
}
}
+101
View File
@@ -0,0 +1,101 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
AbstractButton {
id: root
enum Size { Small, Medium }
property int size: LinkButtonType.Small
property bool colored: true
property string leftImageSource
property string rightImageSource
padding: 0
hoverEnabled: true
opacity: root.enabled ? 1 : 0.5
QtObject {
id: d
readonly property color contentColor: {
if (!root.colored) {
return AmneziaStyle.color.textTertiary
}
if (root.down) {
return AmneziaStyle.color.accentDeep
}
return root.hovered ? AmneziaStyle.color.accentDefault : AmneziaStyle.color.textAccent
}
}
background: Item {
FocusRingType {
radius: 4
visible: root.visualFocus
}
}
contentItem: Item {
implicitWidth: row.implicitWidth
implicitHeight: row.implicitHeight
RowLayout {
id: row
anchors.centerIn: parent
width: Math.min(implicitWidth, parent.width)
spacing: 8
IconImage {
Layout.preferredWidth: 14
Layout.preferredHeight: 14
visible: root.leftImageSource !== ""
source: root.leftImageSource
sourceSize: Qt.size(14, 14)
color: d.contentColor
}
SmallTextType {
Layout.fillWidth: true
text: root.text
color: d.contentColor
font.letterSpacing: -0.4
font.pixelSize: root.size === LinkButtonType.Medium ? 16 : 14
lineHeight: (root.size === LinkButtonType.Medium ? 24 : 18) + LanguageUiController.getLineHeightAppend()
wrapMode: Text.NoWrap
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
IconImage {
Layout.preferredWidth: 14
Layout.preferredHeight: 14
visible: root.rightImageSource !== ""
source: root.rightImageSource
sourceSize: Qt.size(14, 14)
color: d.contentColor
}
}
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
}
Loaded 100 of 241 files, more files were not shown because too many files have changed in this diff. Show more