GODT-1554 / 1555: Implement gRPC go service and Qt 5 frontend C++ app.
WIP: updates WIP: cache on disk and autostart. WIP: mail, keychain and more. WIP: updated grpc version in go mod file. WIP: user list. WIP: RPC service placeholder WIP: test C++ RPC client skeleton. Other: missing license script update. WIP: use Qt test framework. WIP: test for app and login calls. WIP: test for update & cache on disk calls. WIP: tests for mail settings calls. WIP: all client tests. WIP: linter fixes. WIP: fix missing license link. WIP: update dependency_license script for gRPC and protobuf. WIP: removed unused file. WIP: app & login event streaming tests. WIP: update event stream tests. WIP: completed event streaming tests. GODT-1554: qt C++ frontend skeleton. WIP: C++ backend declaration. wip: started drafting user model. WIP: users. not functional. WIP: invokable methods WIP: Exception class + backend 'injection' into QML. WIP: switch to VCPKG to ease multi-arch compilation, C++ RPC client skeleton. WIP: Renaming and reorganisation WIP:introduced new 'grpc' go frontend. WIP: Worker & Oveerseer for thread management. WIP: added log to C++ app. WIP: event stream architecture on Go side. WIP: event parsing and streamer stopping. WIP: Moved grpc to frontend subfolder + use vcpkg for gRPC and protobuf. WIP: windows building ok WIP: wired a few messages WIP: more wiring. WIP: Fixed imports after rebase on top of devel. WIP: wired some bool and string properties. WIP: more properties. WIP: wired cache on disk stuff WIP: connect event watcher. WIP: login WIP: fix showSplashScreen WIP: Wired login calls. WIP: user list. WIP: Refactored main(). WIP: User retrieval . WIP: no shared pointer in user model. WIP: fixed user count. WIP: cached goos. WIP: Wired autostart WIP: beta channel toggle wired. WIP: User removal WIP: wired theme WIP: implemented configure apple mail. WIP: split mode. WIP: fixed user updates. WIP: fixed Quit from tray icon WIP: wired CurrentEmailClient WIP: wired UseSSLForSMTP WIP: wired change ports . WIP: wired DoH. . WIP: wired keychain calls. WIP: wired autoupdate option. WIP: QML Backend clean-up. WIP: cleanup. WIP: moved user related files in subfolder. . WIP: User are managed using smart pointers. WIP: cleanup. WIP: more cleanup. WIP: mail events forwarding WIP: code inspection tweaks from CLion. WIP: moved QML, cleanup, and missing copyright notices. WIP: Backend is not QMLBackend. Other: fixed issues reported by Leander. [skip ci]
45
internal/frontend/qt6/AppController.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "AppController.h"
|
||||
#include "QMLBackend.h"
|
||||
#include "GRPC/GRPCClient.h"
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The AppController instance.
|
||||
//****************************************************************************************************************************************************
|
||||
AppController &app()
|
||||
{
|
||||
static AppController app;
|
||||
return app;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
AppController::AppController()
|
||||
: backend_(std::make_unique<QMLBackend>())
|
||||
, grpc_(std::make_unique<GRPCClient>())
|
||||
, log_(std::make_unique<Log>())
|
||||
{
|
||||
|
||||
}
|
||||
59
internal/frontend/qt6/AppController.h
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_APP_CONTROLLER_H
|
||||
#define BRIDGE_QT6_APP_CONTROLLER_H
|
||||
|
||||
|
||||
class QMLBackend;
|
||||
class GRPCClient;
|
||||
class Log;
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief App controller class.
|
||||
//****************************************************************************************************************************************************
|
||||
class AppController: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend AppController& app();
|
||||
|
||||
public: // member functions.
|
||||
AppController(AppController const&) = delete; ///< Disabled copy-constructor.
|
||||
AppController(AppController&&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~AppController() override = default; ///< Destructor.
|
||||
AppController& operator=(AppController const&) = delete; ///< Disabled assignment operator.
|
||||
AppController& operator=(AppController&&) = delete; ///< Disabled move assignment operator.
|
||||
QMLBackend& backend() { return *backend_; } ///< Return a reference to the backend.
|
||||
GRPCClient& grpc() { return *grpc_; } ///< Return a reference to the GRPC client.
|
||||
Log& log() { return *log_; } ///< Return a reference to the log.
|
||||
|
||||
private: // member functions
|
||||
AppController(); ///< Default constructor.
|
||||
|
||||
private: // data members
|
||||
std::unique_ptr<QMLBackend> backend_; ///< The backend.
|
||||
std::unique_ptr<GRPCClient> grpc_; ///< The RPC client.
|
||||
std::unique_ptr<Log> log_; ///< The log.
|
||||
};
|
||||
|
||||
|
||||
AppController& app(); ///< Return a reference to the app controller.
|
||||
|
||||
|
||||
#endif // BRIDGE_QT6_APP_CONTROLLER_H
|
||||
136
internal/frontend/qt6/CMakeLists.txt
Normal file
@ -0,0 +1,136 @@
|
||||
# Copyright (c) 2022 Proton AG
|
||||
#
|
||||
# This file is part of Proton Mail Bridge.
|
||||
#
|
||||
# Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
set(CMAKE_OSX_ARCHITECTURES x86_64) # needs to be set before the first project() directive.
|
||||
|
||||
#We rely on vcpkg for to get gRPC+Protobuf
|
||||
if (NOT DEFINED ENV{VCPKG_ROOT})
|
||||
message(FATAL_ERROR "vcpkg is required. Install vcpkg and define VCPKG_ROOT to point the the vcpkg installation folder. (e.g. ~/vcpkg/")
|
||||
endif()
|
||||
if (APPLE)
|
||||
set(VCPKG_TARGET_TRIPLET x64-osx)
|
||||
endif()
|
||||
if (WIN32)
|
||||
set(VCPKG_TARGET_TRIPLET x64-mingw-static)
|
||||
endif()
|
||||
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "toolchain")
|
||||
|
||||
|
||||
project(bridge_qt6 LANGUAGES CXX)
|
||||
if (APPLE) # On macOS, we have some Objective-C++ code in DockIcon to deal with ... the dock icon.
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED ENV{QT5DIR})
|
||||
message(FATAL_ERROR "QT5DIR needs to be defined and point to the root of your Qt5 folder (e.g. /Users/MyName/Qt/5.10.1/clang_64).")
|
||||
endif()
|
||||
|
||||
|
||||
set(CMAKE_PREFIX_PATH $ENV{QT5DIR} ${CMAKE_PREFIX_PATH})
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
find_package(Protobuf CONFIG REQUIRED)
|
||||
message(STATUS "Using protobuf ${Protobuf_VERSION}")
|
||||
|
||||
|
||||
find_package(grpc CONFIG REQUIRED)
|
||||
message(STATUS "Using gRPC ${gRPC_VERSION}")
|
||||
|
||||
|
||||
if (APPLE) # We need to link the Cocoa framework for the dock icon.
|
||||
find_library(COCOA_LIBRARY Cocoa REQUIRED)
|
||||
endif()
|
||||
|
||||
|
||||
find_package(Qt5 COMPONENTS
|
||||
Core
|
||||
Quick
|
||||
Qml
|
||||
QuickControls2
|
||||
REQUIRED)
|
||||
|
||||
find_program(PROTOC_EXE protoc)
|
||||
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)
|
||||
set(PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../grpc")
|
||||
set(PROTO_FILE "${PROTO_DIR}/bridge.proto")
|
||||
set(GRPC_OUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/GRPC")
|
||||
set(PROTO_CPP_FILE "${GRPC_OUT_DIR}/bridge.pb.cc")
|
||||
set(PROTO_H_FILE "${GRPC_OUT_DIR}/bridge.pb.h")
|
||||
set(GRPC_CPP_FILE "${GRPC_OUT_DIR}/bridge.grpc.pb.cc")
|
||||
set(GRPC_H_FILE "${GRPC_OUT_DIR}/bridge.grpc.pb.h")
|
||||
if (APPLE)
|
||||
set(DOCK_ICON_SRC_FILE DockIcon/DockIcon.mm)
|
||||
else()
|
||||
set(DOCK_ICON_SRC_FILE DockIcon/DockIcon.cpp)
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${PROTO_CPP_FILE}
|
||||
${PROTO_H_FILE}
|
||||
${GRPC_CPP_FILE}
|
||||
${GRPC_H_FILE}
|
||||
COMMAND
|
||||
${PROTOC_EXE}
|
||||
ARGS
|
||||
--proto_path=${PROTO_DIR}
|
||||
--plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}"
|
||||
--cpp_out=${GRPC_OUT_DIR}
|
||||
--grpc_out=${GRPC_OUT_DIR}
|
||||
${PROTO_FILE}
|
||||
DEPENDS
|
||||
${PROTO_FILE}
|
||||
COMMENT "Generating gPRC/Protobuf C++ code"
|
||||
)
|
||||
|
||||
add_executable(bridge_qt6
|
||||
${PROTO_CPP_FILE} ${PROTO_H_FILE} ${GRPC_CPP_FILE} ${GRPC_H_FILE}
|
||||
AppController.cpp AppController.h
|
||||
EventStreamWorker.cpp EventStreamWorker.h
|
||||
Exception.cpp Exception.h
|
||||
Log.cpp Log.h
|
||||
main.cpp
|
||||
Pch.h
|
||||
QMLBackend.cpp QMLBackend.h
|
||||
${DOCK_ICON_SRC_FILE} DockIcon/DockIcon.h
|
||||
GRPC/GRPCClient.cpp GRPC/GRPCClient.h
|
||||
GRPC/GRPCUtils.cpp GRPC/GRPCUtils.h
|
||||
User/User.cpp User/User.h User/UserList.cpp User/UserList.h
|
||||
Worker/Overseer.cpp Worker/Overseer.h
|
||||
Worker/Worker.h)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_precompile_headers(bridge_qt6 PRIVATE Pch.h)
|
||||
|
||||
target_link_libraries(bridge_qt6
|
||||
Qt5::Core
|
||||
Qt5::Quick
|
||||
Qt5::Qml
|
||||
Qt5::QuickControls2
|
||||
protobuf::libprotobuf
|
||||
gRPC::grpc++
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(bridge_qt6 ${COCOA_LIBRARY})
|
||||
endif()
|
||||
29
internal/frontend/qt6/DockIcon/DockIcon.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
|
||||
|
||||
void setDockIconVisibleState(bool visible) { Q_UNUSED(visible) }
|
||||
bool getDockIconVisibleState() { return true; }
|
||||
|
||||
|
||||
#endif
|
||||
27
internal/frontend/qt6/DockIcon/DockIcon.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_DOCK_ICON_H
|
||||
#define BRIDGE_QT6_DOCK_ICON_H
|
||||
|
||||
|
||||
void setDockIconVisibleState(bool visible); ///< Set the DOCK icon visibility state
|
||||
bool getDockIconVisibleState(); ///< Get the Dock icon visibility state
|
||||
|
||||
|
||||
#endif // #ifndef BRIDGE_QT6_DOCK_ICON_H
|
||||
49
internal/frontend/qt6/DockIcon/DockIcon.mm
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include "DockIcon.h"
|
||||
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
|
||||
|
||||
void setDockIconVisibleState(bool visible) {
|
||||
if (visible) {
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
return;
|
||||
} else {
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool getDockIconVisibleState() {
|
||||
switch ([NSApp activationPolicy]) {
|
||||
case NSApplicationActivationPolicyAccessory:
|
||||
case NSApplicationActivationPolicyProhibited:
|
||||
return false;
|
||||
case NSApplicationActivationPolicyRegular:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // #ifdef Q_OS_MACOS
|
||||
2656
internal/frontend/qt6/Doxyfile
Normal file
57
internal/frontend/qt6/EventStreamWorker.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "EventStreamWorker.h"
|
||||
#include "GRPC/GRPCClient.h"
|
||||
#include "Log.h"
|
||||
#include "Exception.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] parent The parent object.
|
||||
//****************************************************************************************************************************************************
|
||||
EventStreamReader::EventStreamReader(QObject *parent)
|
||||
: Worker(parent)
|
||||
{
|
||||
connect(this, &EventStreamReader::started, [&]() { app().log().debug("EventStreamReader started");});
|
||||
connect(this, &EventStreamReader::finished, [&]() { app().log().debug("EventStreamReader finished");});
|
||||
connect(this, &EventStreamReader::error, &app().log(), &Log::error);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void EventStreamReader::run()
|
||||
{
|
||||
try
|
||||
{
|
||||
emit started();
|
||||
|
||||
grpc::Status const status = app().grpc().startEventStream();
|
||||
if (!status.ok())
|
||||
throw Exception(QString::fromStdString(status.error_message()));
|
||||
|
||||
emit finished();
|
||||
}
|
||||
catch (Exception const &e)
|
||||
{
|
||||
emit error(e.qwhat());
|
||||
}
|
||||
}
|
||||
52
internal/frontend/qt6/EventStreamWorker.h
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_EVENT_STREAM_WORKER_H
|
||||
#define BRIDGE_QT6_EVENT_STREAM_WORKER_H
|
||||
|
||||
|
||||
#include "GRPC/bridge.grpc.pb.h"
|
||||
#include "Worker/Worker.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Stream reader class.
|
||||
//****************************************************************************************************************************************************
|
||||
class EventStreamReader: public Worker
|
||||
{
|
||||
Q_OBJECT
|
||||
public: // member functions
|
||||
explicit EventStreamReader(QObject *parent); ///< Default constructor.
|
||||
EventStreamReader(EventStreamReader const&) = delete; ///< Disabled copy-constructor.
|
||||
EventStreamReader(EventStreamReader&&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~EventStreamReader() override = default; ///< Destructor.
|
||||
EventStreamReader& operator=(EventStreamReader const&) = delete; ///< Disabled assignment operator.
|
||||
EventStreamReader& operator=(EventStreamReader&&) = delete; ///< Disabled move assignment operator.
|
||||
|
||||
public slots:
|
||||
void run() override; ///< Run the reader.
|
||||
|
||||
signals:
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "NotImplementedFunctions"
|
||||
void eventReceived(QString eventString); ///< signal for events.
|
||||
#pragma clang diagnostic pop
|
||||
};
|
||||
|
||||
|
||||
#endif //BRIDGE_QT6_EVENT_STREAM_WORKER_H
|
||||
68
internal/frontend/qt6/Exception.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "Exception.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] what A description of the exception
|
||||
//****************************************************************************************************************************************************
|
||||
Exception::Exception(QString what) noexcept
|
||||
: std::exception()
|
||||
, what_(std::move(what))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] ref The Exception to copy from
|
||||
//****************************************************************************************************************************************************
|
||||
Exception::Exception(Exception const& ref) noexcept
|
||||
: std::exception(ref)
|
||||
, what_(ref.what_)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] ref The Exception to copy from
|
||||
//****************************************************************************************************************************************************
|
||||
Exception::Exception(Exception&& ref) noexcept
|
||||
: std::exception(ref)
|
||||
, what_(ref.what_)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return a string describing the exception
|
||||
//****************************************************************************************************************************************************
|
||||
QString const& Exception::qwhat() const noexcept
|
||||
{
|
||||
return what_;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return A pointer to the description string of the exception.
|
||||
//****************************************************************************************************************************************************
|
||||
const char* Exception::what() const noexcept
|
||||
{
|
||||
return what_.toLocal8Bit().constData();
|
||||
}
|
||||
46
internal/frontend/qt6/Exception.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_EXCEPTION_H
|
||||
#define BRIDGE_QT6_EXCEPTION_H
|
||||
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Exception class.
|
||||
//****************************************************************************************************************************************************
|
||||
class Exception: public std::exception
|
||||
{
|
||||
public: // member functions
|
||||
explicit Exception(QString what = QString()) noexcept; ///< Constructor
|
||||
Exception(Exception const& ref) noexcept; ///< copy constructor
|
||||
Exception(Exception&& ref) noexcept; ///< copy constructor
|
||||
Exception& operator=(Exception const&) = delete; ///< Disabled assignment operator
|
||||
Exception& operator=(Exception&&) = delete; ///< Disabled assignment operator
|
||||
~Exception() noexcept override = default; ///< Destructor
|
||||
QString const& qwhat() const noexcept; ///< Return the description of the exception as a QString
|
||||
const char* what() const noexcept override; ///< Return the description of the exception as C style string
|
||||
|
||||
private: // data members
|
||||
QString const what_; ///< The description of the exception
|
||||
};
|
||||
|
||||
|
||||
#endif //BRIDGE_QT6_EXCEPTION_H
|
||||
1183
internal/frontend/qt6/GRPC/GRPCClient.cpp
Normal file
214
internal/frontend/qt6/GRPC/GRPCClient.h
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_RPC_CLIENT_H
|
||||
#define BRIDGE_QT6_RPC_CLIENT_H
|
||||
|
||||
|
||||
#include "GRPC/bridge.grpc.pb.h"
|
||||
#include "grpc++/grpc++.h"
|
||||
#include "User/User.h"
|
||||
|
||||
|
||||
typedef grpc::Status (grpc::Bridge::Stub::*SimpleMethod)(grpc::ClientContext*, const google::protobuf::Empty&, google::protobuf::Empty*);
|
||||
typedef grpc::Status (grpc::Bridge::Stub::*BoolSetter)(grpc::ClientContext*, const google::protobuf::BoolValue&, google::protobuf::Empty*);
|
||||
typedef grpc::Status (grpc::Bridge::Stub::*BoolGetter)(grpc::ClientContext*, const google::protobuf::Empty&, google::protobuf::BoolValue*);
|
||||
typedef grpc::Status (grpc::Bridge::Stub::*Int32Setter)(grpc::ClientContext*, const google::protobuf::Int32Value&, google::protobuf::Empty*);
|
||||
typedef grpc::Status (grpc::Bridge::Stub::*Int32Getter)(grpc::ClientContext*, const google::protobuf::Empty&, google::protobuf::Int32Value*);
|
||||
typedef grpc::Status (grpc::Bridge::Stub::*StringGetter)(grpc::ClientContext*, const google::protobuf::Empty&, google::protobuf::StringValue*);
|
||||
typedef grpc::Status (grpc::Bridge::Stub::*StringSetter)(grpc::ClientContext*, const google::protobuf::StringValue&, google::protobuf::Empty*);
|
||||
typedef grpc::Status (grpc::Bridge::Stub::*StringParamMethod)(grpc::ClientContext*, const google::protobuf::StringValue&, google::protobuf::Empty*);
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief gRPC client class. This class encapsulate the gRPC service, abstracting all data type conversions.
|
||||
//****************************************************************************************************************************************************
|
||||
class GRPCClient: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public: // member functions.
|
||||
GRPCClient() = default; ///< Default constructor.
|
||||
GRPCClient(GRPCClient const&) = delete; ///< Disabled copy-constructor.
|
||||
GRPCClient(GRPCClient&&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~GRPCClient() override = default; ///< Destructor.
|
||||
GRPCClient& operator=(GRPCClient const&) = delete; ///< Disabled assignment operator.
|
||||
GRPCClient& operator=(GRPCClient&&) = delete; ///< Disabled move assignment operator.
|
||||
bool connectToServer(QString &outError); ///< Establish connection to the gRPC server.
|
||||
|
||||
grpc::Status guiReady(); ///< performs the "GuiReady" gRPC call.
|
||||
grpc::Status isFirstGUIStart(bool &outIsFirst); ///< performs the "IsFirstGUIStart" gRPC call.
|
||||
grpc::Status isAutostartOn(bool &outIsOn); ///< Performs the "isAutostartOn" gRPC call.
|
||||
grpc::Status setIsAutostartOn(bool on); ///< Performs the "setIsAutostartOn" gRPC call.
|
||||
grpc::Status isBetaEnabled(bool &outEnabled); ///< Performs the "isBetaEnabled" gRPC call.
|
||||
grpc::Status setisBetaEnabled(bool enabled); ///< Performs the 'setIsBetaEnabled' gRPC call.
|
||||
grpc::Status colorSchemeName(QString &outName); ///< Performs the "colorSchemeName' gRPC call.
|
||||
grpc::Status setColorSchemeName(QString const &name); ///< Performs the "setColorSchemeName' gRPC call.
|
||||
grpc::Status currentEmailClient(QString &outName); ///< Performs the 'currentEmailClient' gRPC call.
|
||||
|
||||
grpc::Status quit(); ///< Perform the "Quit" gRPC call.
|
||||
grpc::Status restart(); ///< Performs the Restart gRPC call.
|
||||
grpc::Status isPortFree(qint32 port, bool &outFree); ///< Performs the 'IsPortFree' call.
|
||||
grpc::Status showOnStartup(bool &outValue); ///< Performs the 'ShowOnStartup' call.
|
||||
grpc::Status showSplashScreen(bool &outValue); ///< Performs the 'ShowSplashScreen' call.
|
||||
grpc::Status goos(QString &outGoos); ///< Performs the 'GoOs' call.
|
||||
grpc::Status logsPath(QUrl &outPath); ///< Performs the 'LogsPath' call.
|
||||
grpc::Status licensePath(QUrl &outPath); ///< Performs the 'LicensePath' call.
|
||||
grpc::Status dependencyLicensesLink(QUrl &outUrl); ///< Performs the 'DependencyLicensesLink' call.
|
||||
grpc::Status version(QString &outVersion); ///< Performs the 'Version' call.
|
||||
grpc::Status hostname(QString &outHostname); ///< Performs the 'Hostname' call.
|
||||
|
||||
signals: // app related signals
|
||||
void internetStatus(bool isOn);
|
||||
void toggleAutostartFinished();
|
||||
void resetFinished();
|
||||
void reportBugFinished();
|
||||
void reportBugSuccess();
|
||||
void reportBugError();
|
||||
void showMainWindow();
|
||||
|
||||
// cache related calls
|
||||
public:
|
||||
grpc::Status isCacheOnDiskEnabled(bool &outEnabled); ///< Performs the 'isCacheOnDiskEnabled' call.
|
||||
grpc::Status diskCachePath(QUrl &outPath); ///< Performs the 'diskCachePath' call.
|
||||
grpc::Status changeLocalCache(bool enabled, QUrl const &path); ///< Performs the 'ChangeLocalCache' call.
|
||||
signals:
|
||||
void isCacheOnDiskEnabledChanged(bool enabled);
|
||||
void diskCachePathChanged(QUrl const&outPath);
|
||||
void cacheUnavailable(); // _ func() `signal:"cacheUnavailable"`
|
||||
void cacheCantMove(); // _ func() `signal:"cacheCantMove"`
|
||||
void cacheLocationChangeSuccess(); // _ func() `signal:"cacheLocationChangeSuccess"`
|
||||
void diskFull(); // _ func() `signal:"diskFull"`
|
||||
void changeLocalCacheFinished(); // _ func() `signal:"changeLocalCacheFinished"`
|
||||
|
||||
|
||||
// mail settings related calls
|
||||
public:
|
||||
grpc::Status useSSLForSMTP(bool &outUseSSL); ///< Performs the 'useSSLForSMTP' gRPC call
|
||||
grpc::Status setUseSSLForSMTP(bool useSSL); ///< Performs the 'currentEmailClient' gRPC call.
|
||||
grpc::Status portIMAP(int &outPort); ///< Performs the 'portImap' gRPC call.
|
||||
grpc::Status portSMTP(int &outPort); ///< Performs the 'portImap' gRPC call.
|
||||
grpc::Status changePorts(int portIMAP, int portSMTP); ///< Performs the 'changePorts' gRPC call.
|
||||
grpc::Status isDoHEnabled(bool &outEnabled); ///< Performs the 'isDoHEnabled' gRPC call.
|
||||
grpc::Status setIsDoHEnabled(bool enabled); ///< Performs the 'setIsDoHEnabled' gRPC call.
|
||||
|
||||
signals:
|
||||
void portIssueIMAP();
|
||||
void portIssueSMTP();
|
||||
void toggleUseSSLFinished();
|
||||
void changePortFinished();
|
||||
|
||||
public: // login related calls
|
||||
grpc::Status login(QString const &username, QString const& password); ///< Performs the 'login' call.
|
||||
grpc::Status login2FA(QString const &username, QString const& code); ///< Performs the 'login2FA' call.
|
||||
grpc::Status login2Passwords(QString const &username, QString const& password); ///< Performs the 'login2Passwords' call.
|
||||
grpc::Status loginAbort(QString const &username); ///< Performs the 'loginAbort' call.
|
||||
|
||||
signals:
|
||||
void loginUsernamePasswordError(QString const &errMsg); // _ func(errorMsg string) `signal:"loginUsernamePasswordError"`
|
||||
void loginFreeUserError(); // _ func() `signal:"loginFreeUserError"`
|
||||
void loginConnectionError(QString const &errMsg); // _ func(errorMsg string) `signal:"loginConnectionError"`
|
||||
void login2FARequested(QString const &userName); // _ func(username string) `signal:"login2FARequested"`
|
||||
void login2FAError(QString const& errMsg); // _ func(errorMsg string) `signal:"login2FAError"`
|
||||
void login2FAErrorAbort(QString const& errMsg); // _ func(errorMsg string) `signal:"login2FAErrorAbort"`
|
||||
void login2PasswordRequested(); // _ func() `signal:"login2PasswordRequested"`
|
||||
void login2PasswordError(QString const& errMsg); // _ func(errorMsg string) `signal:"login2PasswordError"`
|
||||
void login2PasswordErrorAbort(QString const& errMsg); // _ func(errorMsg string) `signal:"login2PasswordErrorAbort"`
|
||||
void loginFinished(QString const &userID); // _ func(index int) `signal:"loginFinished"`
|
||||
void loginAlreadyLoggedIn(QString const &userID); // _ func(index int) `signal:"loginAlreadyLoggedIn"`
|
||||
|
||||
public: // Update related calls
|
||||
grpc::Status checkUpdate();
|
||||
grpc::Status installUpdate();
|
||||
grpc::Status setIsAutomaticUpdateOn(bool on);
|
||||
grpc::Status isAutomaticUpdateOn(bool &isOn);
|
||||
|
||||
signals:
|
||||
void updateManualError();
|
||||
void updateForceError();
|
||||
void updateSilentError();
|
||||
void updateManualReady(QString const &version);
|
||||
void updateManualRestartNeeded();
|
||||
void updateForce(QString const &version);
|
||||
void updateSilentRestartNeeded();
|
||||
void updateIsLatestVersion();
|
||||
void checkUpdatesFinished();
|
||||
|
||||
public: // user related calls
|
||||
grpc::Status getUserList(QList<SPUser>& outUsers);
|
||||
grpc::Status getUser(QString const &userID, SPUser& outUser);
|
||||
grpc::Status logoutUser(QString const &userID); ///< Performs the 'logoutUser' call.
|
||||
grpc::Status removeUser(QString const &userID); ///< Performs the 'removeUser' call.
|
||||
grpc::Status configureAppleMail(QString const& userID, QString const &address); ///< Performs the 'configureAppleMail' call.
|
||||
grpc::Status setUserSplitMode(QString const& userID, bool active); ///< Performs the 'SetUserSplitMode' call.
|
||||
|
||||
signals:
|
||||
void toggleSplitModeFinished(QString const& userID);
|
||||
void userDisconnected(QString const& username);
|
||||
void userChanged(QString const& userID);
|
||||
|
||||
|
||||
public: // keychain related calls
|
||||
grpc::Status availableKeychains(QStringList &outKeychains);
|
||||
grpc::Status currentKeychain(QString &outKeychain);
|
||||
grpc::Status setCurrentKeychain(QString const &keychain);
|
||||
|
||||
signals:
|
||||
void changeKeychainFinished();
|
||||
void hasNoKeychain();
|
||||
void rebuildKeychain();
|
||||
|
||||
signals: // mail releated events
|
||||
void noActiveKeyForRecipient(QString const &email); // _ func(email string) `signal:noActiveKeyForRecipient`
|
||||
void addressChanged(QString const &address); // _ func(address string) `signal:addressChanged`
|
||||
void addressChangedLogout(QString const &address); // _ func(address string) `signal:addressChangedLogout`
|
||||
void apiCertIssue();
|
||||
|
||||
public:
|
||||
grpc::Status startEventStream(); ///< Retrieve and signal the events in the event stream.
|
||||
grpc::Status stopEventStream(); ///< Stop the event stream.
|
||||
|
||||
|
||||
|
||||
private:
|
||||
grpc::Status simpleMethod(SimpleMethod method); ///< perform a gRPC call to a bool setter.
|
||||
grpc::Status setBool(BoolSetter setter, bool value); ///< perform a gRPC call to a bool setter.
|
||||
grpc::Status getBool(BoolGetter getter, bool& outValue); ///< perform a gRPC call to a bool getter.
|
||||
grpc::Status setInt32(Int32Setter setter, int value); ///< perform a gRPC call to an int setter.
|
||||
grpc::Status getInt32(Int32Getter getter, int& outValue); ///< perform a gRPC call to an int getter.
|
||||
grpc::Status setString(StringSetter getter, QString const& value); ///< Perform a gRPC call to a string setter.
|
||||
grpc::Status getString(StringGetter getter, QString& outValue); ///< Perform a gRPC call to a string getter.
|
||||
grpc::Status getURLForLocalFile(StringGetter getter, QUrl& outValue); ///< Perform a gRPC call to a string getter, with resulted converted to QUrl for a local file path.
|
||||
grpc::Status getURL(StringGetter getter, QUrl& outValue); ///< Perform a gRPC call to a string getter, with resulted converted to QUrl.
|
||||
grpc::Status methodWithStringParam(StringParamMethod method, QString const& str); ///< Perfom a gRPC call that takes a string as a parameter and returns an Empty.
|
||||
|
||||
void processAppEvent(grpc::AppEvent const &event); ///< Process an 'App' event.
|
||||
void processLoginEvent(grpc::LoginEvent const &event); ///< Process a 'Login' event.
|
||||
void processUpdateEvent(grpc::UpdateEvent const &event); ///< Process an 'Update' event.
|
||||
void processCacheEvent(grpc::CacheEvent const &event); ///< Process a 'Cache' event.
|
||||
void processMailSettingsEvent(grpc::MailSettingsEvent const &event); ///< Process a 'MailSettings' event.
|
||||
void processKeychainEvent(grpc::KeychainEvent const &event); ///< Process a 'Keychain' event.
|
||||
void processMailEvent(grpc::MailEvent const &event); ///< Process a 'Mail' event.
|
||||
void processUserEvent(grpc::UserEvent const &event); ///< Process a 'User' event.
|
||||
|
||||
private: // data members.
|
||||
std::shared_ptr<grpc::Channel> channel_ { nullptr }; ///< The gRPC channel.
|
||||
std::shared_ptr<grpc::Bridge::Stub> stub_ { nullptr }; ///< The gRPC stub (a.k.a. client).
|
||||
};
|
||||
|
||||
|
||||
#endif // BRIDGE_QT6_RPC_CLIENT_H
|
||||
63
internal/frontend/qt6/GRPC/GRPCUtils.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "GRPCUtils.h"
|
||||
#include "QMLBackend.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] status The status
|
||||
/// \param[in] callName The call name.
|
||||
//****************************************************************************************************************************************************
|
||||
void logGRPCCallStatus(grpc::Status const& status, QString const &callName)
|
||||
{
|
||||
if (status.ok())
|
||||
app().log().debug(QString("%1()").arg(callName));
|
||||
else
|
||||
app().log().error(QString("%1() FAILED").arg(callName));
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] grpcUser the gRPC user struct
|
||||
/// \return a user.
|
||||
//****************************************************************************************************************************************************
|
||||
SPUser parsegrpcUser(grpc::User const &grpcUser)
|
||||
{
|
||||
// As we want to use shared pointers here, we do not want to use the Qt ownership system, so we set parent to nil.
|
||||
// But: From https://doc.qt.io/qt-5/qtqml-cppintegration-data.html:
|
||||
// " When data is transferred from C++ to QML, the ownership of the data always remains with C++. The exception to this rule
|
||||
// is when a QObject is returned from an explicit C++ method call: in this case, the QML engine assumes ownership of the object. "
|
||||
// This is the case here, so we explicitely indicate that the object is owned by C++.
|
||||
SPUser user = std::make_shared<User>(nullptr);
|
||||
QQmlEngine::setObjectOwnership(user.get(), QQmlEngine::CppOwnership);
|
||||
user->setProperty("username", QString::fromStdString(grpcUser.username()));
|
||||
user->setProperty("avatarText", QString::fromStdString(grpcUser.avatartext()));
|
||||
user->setProperty("loggedIn", grpcUser.loggedin());
|
||||
user->setProperty("splitMode", grpcUser.splitmode());
|
||||
user->setProperty("setupGuideSeen", grpcUser.setupguideseen());
|
||||
user->setProperty("usedBytes", float(grpcUser.usedbytes()));
|
||||
user->setProperty("totalBytes", float(grpcUser.totalbytes()));
|
||||
user->setProperty("password", QString::fromStdString(grpcUser.password()));
|
||||
QStringList addresses;
|
||||
for (int j = 0; j < grpcUser.addresses_size(); ++j)
|
||||
addresses.append(QString::fromStdString(grpcUser.addresses(j)));
|
||||
user->setProperty("addresses", addresses);
|
||||
user->setProperty("id", QString::fromStdString(grpcUser.id()));
|
||||
return user;
|
||||
}
|
||||
30
internal/frontend/qt6/GRPC/GRPCUtils.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_GRPCUTILS_H
|
||||
#define BRIDGE_QT6_GRPCUTILS_H
|
||||
|
||||
#include "GRPC/bridge.grpc.pb.h"
|
||||
#include "grpc++/grpc++.h"
|
||||
#include "User/User.h"
|
||||
|
||||
|
||||
void logGRPCCallStatus(grpc::Status const& status, QString const &callName); ///< Log the status of a gRPC code.
|
||||
SPUser parsegrpcUser(grpc::User const& grpcUser); ///< Parse a gRPC user struct and return a User.
|
||||
|
||||
#endif // BRIDGE_QT6_GRPCUTILS_H
|
||||
2179
internal/frontend/qt6/GRPC/bridge.grpc.pb.cc
Normal file
8146
internal/frontend/qt6/GRPC/bridge.grpc.pb.h
Normal file
12646
internal/frontend/qt6/GRPC/bridge.pb.cc
Normal file
14902
internal/frontend/qt6/GRPC/bridge.pb.h
Normal file
159
internal/frontend/qt6/Log.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
Log::Log()
|
||||
: QObject()
|
||||
, stdout_(stdout)
|
||||
, stderr_(stderr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] level The log level.
|
||||
//****************************************************************************************************************************************************
|
||||
void Log::setLevel(Log::Level level)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
level_ = level;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The log level.
|
||||
//****************************************************************************************************************************************************
|
||||
Log::Level Log::level() const
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
return Log::Level::Debug;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] value Should the log entries be sent to STDOUT/STDERR.
|
||||
//****************************************************************************************************************************************************
|
||||
void Log::setEchoInConsole(bool value)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
echoInConsole_ = value;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return true iff the log entries be should sent to STDOUT/STDERR.
|
||||
//****************************************************************************************************************************************************
|
||||
bool Log::echoInConsole() const
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
return echoInConsole_;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] message The message
|
||||
//****************************************************************************************************************************************************
|
||||
void Log::debug(QString const &message)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (qint32(level_) > qint32(Level::Debug))
|
||||
return;
|
||||
|
||||
emit debugEntryAdded(message);
|
||||
QString const withPrefix = "[DEBUG] " + message;
|
||||
emit entryAdded(withPrefix);
|
||||
if (echoInConsole_)
|
||||
{
|
||||
stdout_ << withPrefix << "\n";
|
||||
stdout_.flush();
|
||||
}
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] message The message
|
||||
//****************************************************************************************************************************************************
|
||||
void Log::info(QString const &message)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (qint32(level_) > qint32(Level::Info))
|
||||
return;
|
||||
|
||||
emit infoEntryAdded(message);
|
||||
QString const withPrefix = "[INFO] " + message;
|
||||
emit entryAdded(withPrefix);
|
||||
if (echoInConsole_)
|
||||
{
|
||||
stdout_ << withPrefix << "\n";
|
||||
stdout_.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void Log::warn(QString const &message)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (qint32(level_) > qint32(Level::Warn))
|
||||
return;
|
||||
|
||||
emit infoEntryAdded(message);
|
||||
QString const withPrefix = "[WARNING] " + message;
|
||||
emit entryAdded(withPrefix);
|
||||
if (echoInConsole_)
|
||||
{
|
||||
stdout_ << withPrefix << "\n";
|
||||
stdout_.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// message
|
||||
//****************************************************************************************************************************************************
|
||||
void Log::error(QString const &message)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (qint32(level_) > qint32(Level::Error))
|
||||
return;
|
||||
|
||||
emit infoEntryAdded(message);
|
||||
QString const withPrefix = "[ERROR] " + message;
|
||||
emit entryAdded(withPrefix);
|
||||
if (echoInConsole_)
|
||||
{
|
||||
stderr_ << withPrefix << "\n";
|
||||
stderr_.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
74
internal/frontend/qt6/Log.h
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_LOG_H
|
||||
#define BRIDGE_QT6_LOG_H
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Basic log class. No logging to file. Four levels. Rebroadcast received log entries via Qt signals.
|
||||
//****************************************************************************************************************************************************
|
||||
class Log : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public: // data types.
|
||||
enum class Level
|
||||
{
|
||||
Debug = 0, ///< Debug
|
||||
Info = 1, ///< Info
|
||||
Warn = 2, ///< Warn
|
||||
Error = 3, ///< Error
|
||||
}; ///< Log level class
|
||||
|
||||
public: // member functions.
|
||||
Log(); ///< Default constructor.
|
||||
Log(Log const &) = delete; ///< Disabled copy-constructor.
|
||||
Log(Log &&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~Log() override = default; ///< Destructor.
|
||||
Log &operator=(Log const &) = delete; ///< Disabled assignment operator.
|
||||
Log &operator=(Log &&) = delete; ///< Disabled move assignment operator.
|
||||
|
||||
void setLevel(Level level); ///< Set the log level.
|
||||
Level level() const; ///< Get the log level.
|
||||
void setEchoInConsole(bool value); ///< Set if the log entries should be echoed in STDOUT/STDERR.
|
||||
bool echoInConsole() const; ///< Check if the log entries should be echoed in STDOUT/STDERR.
|
||||
|
||||
public slots:
|
||||
void debug(QString const &message); ///< Adds a debug entry in the log.
|
||||
void info(QString const &message); ///< Adds an info entry to the log.
|
||||
void warn(QString const &message); ///< Adds a warning entry to the log.
|
||||
void error(QString const &message); ///< Adds an error entry to the log.
|
||||
|
||||
signals:
|
||||
void debugEntryAdded(QString const &); ///< Signal for debug entries.
|
||||
void infoEntryAdded(QString const &message); ///< Signal for info entries.
|
||||
void warnEntryAdded(QString const &message); ///< Signal for warning entries.
|
||||
void errorEntryAdded(QString const &message); ///< Signal for error entries.
|
||||
void entryAdded(QString const &message); ///< Signal emitted when any type of entry is added.
|
||||
|
||||
private: // data members
|
||||
mutable QMutex mutex_; ///< The mutex.
|
||||
Level level_{Level::Debug}; ///< The log level
|
||||
bool echoInConsole_{false}; ///< Set if the log messages should be sent to STDOUT/STDERR.
|
||||
|
||||
QTextStream stdout_; ///< The stdout stream.
|
||||
QTextStream stderr_; ///< The stderr stream.
|
||||
};
|
||||
|
||||
|
||||
#endif //BRIDGE_QT6_LOG_H
|
||||
23
internal/frontend/qt6/Pch.h
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include <QtCore>
|
||||
#include <QtQuick>
|
||||
#include <QtQml>
|
||||
#include <QtQuickControls2>
|
||||
#include <AppController.h>
|
||||
316
internal/frontend/qt6/QMLBackend.cpp
Normal file
@ -0,0 +1,316 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "QMLBackend.h"
|
||||
#include "Exception.h"
|
||||
#include "GRPC/GRPCClient.h"
|
||||
#include "Worker/Overseer.h"
|
||||
#include "EventStreamWorker.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
QMLBackend::QMLBackend()
|
||||
: QObject()
|
||||
, users_(new UserList(this))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::init()
|
||||
{
|
||||
this->connectGrpcEvents();
|
||||
|
||||
QString error;
|
||||
if (app().grpc().connectToServer(error))
|
||||
app().log().info("Connected to backend via gRPC service.");
|
||||
else
|
||||
throw Exception(QString("Cannot connectToServer to go backend via gRPC: %1").arg(error));
|
||||
|
||||
eventStreamOverseer_ = std::make_unique<Overseer>(new EventStreamReader(nullptr), nullptr);
|
||||
eventStreamOverseer_->startWorker(true);
|
||||
|
||||
this->retrieveUserList();
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::connectGrpcEvents()
|
||||
{
|
||||
GRPCClient *client = &app().grpc();
|
||||
|
||||
// app events
|
||||
connect(client, &GRPCClient::internetStatus, this, [&](bool isOn) { if (isOn) emit internetOn(); else emit internetOff(); });
|
||||
connect(client, &GRPCClient::toggleAutostartFinished, this, &QMLBackend::toggleAutostartFinished);
|
||||
connect(client, &GRPCClient::resetFinished, this, &QMLBackend::resetFinished);
|
||||
connect(client, &GRPCClient::reportBugFinished, this, &QMLBackend::reportBugFinished);
|
||||
connect(client, &GRPCClient::reportBugSuccess, this, &QMLBackend::bugReportSendSuccess);
|
||||
connect(client, &GRPCClient::reportBugError, this, &QMLBackend::bugReportSendError);
|
||||
|
||||
// cache events
|
||||
connect(client, &GRPCClient::isCacheOnDiskEnabledChanged, this, &QMLBackend::isDiskCacheEnabledChanged);
|
||||
connect(client, &GRPCClient::diskCachePathChanged, this, &QMLBackend::diskCachePathChanged);
|
||||
connect(client, &GRPCClient::cacheUnavailable, this, &QMLBackend::cacheUnavailable); // _ func() `signal:"cacheUnavailable"`
|
||||
connect(client, &GRPCClient::cacheCantMove, this, &QMLBackend::cacheCantMove);
|
||||
connect(client, &GRPCClient::diskFull, this, &QMLBackend::diskFull);
|
||||
connect(client, &GRPCClient::cacheLocationChangeSuccess, this, &QMLBackend::cacheLocationChangeSuccess);
|
||||
connect(client, &GRPCClient::changeLocalCacheFinished, this, &QMLBackend::changeLocalCacheFinished);
|
||||
|
||||
// login events
|
||||
connect(client, &GRPCClient::loginUsernamePasswordError, this, &QMLBackend::loginUsernamePasswordError);
|
||||
connect(client, &GRPCClient::loginFreeUserError, this, &QMLBackend::loginFreeUserError);
|
||||
connect(client, &GRPCClient::loginConnectionError, this, &QMLBackend::loginConnectionError);
|
||||
connect(client, &GRPCClient::login2FARequested, this, &QMLBackend::login2FARequested);
|
||||
connect(client, &GRPCClient::login2FAError, this, &QMLBackend::login2FAError);
|
||||
connect(client, &GRPCClient::login2FAErrorAbort, this, &QMLBackend::login2FAErrorAbort);
|
||||
connect(client, &GRPCClient::login2PasswordRequested, this, &QMLBackend::login2PasswordRequested);
|
||||
connect(client, &GRPCClient::login2PasswordError, this, &QMLBackend::login2PasswordError);
|
||||
connect(client, &GRPCClient::login2PasswordErrorAbort, this, &QMLBackend::login2PasswordErrorAbort);
|
||||
connect(client, &GRPCClient::loginFinished, this, [&](QString const &userID) {
|
||||
qint32 const index = users_->rowOfUserID(userID); emit loginFinished(index); });
|
||||
connect(client, &GRPCClient::loginAlreadyLoggedIn, this, [&](QString const &userID) {
|
||||
qint32 const index = users_->rowOfUserID(userID); emit loginAlreadyLoggedIn(index); });
|
||||
|
||||
// mail settings events
|
||||
connect(client, &GRPCClient::portIssueIMAP, this, &QMLBackend::portIssueIMAP);
|
||||
connect(client, &GRPCClient::portIssueSMTP, this, &QMLBackend::portIssueSMTP);
|
||||
connect(client, &GRPCClient::toggleUseSSLFinished, this, &QMLBackend::toggleUseSSLFinished);
|
||||
connect(client, &GRPCClient::changePortFinished, this, &QMLBackend::changePortFinished);
|
||||
|
||||
// keychain events
|
||||
connect(client, &GRPCClient::changeKeychainFinished, this, &QMLBackend::changeKeychainFinished);
|
||||
connect(client, &GRPCClient::hasNoKeychain, this, &QMLBackend::notifyHasNoKeychain);
|
||||
connect(client, &GRPCClient::rebuildKeychain, this, &QMLBackend::notifyRebuildKeychain);
|
||||
|
||||
// mail events
|
||||
connect(client, &GRPCClient::noActiveKeyForRecipient, this, &QMLBackend::noActiveKeyForRecipient);
|
||||
connect(client, &GRPCClient::addressChanged, this, &QMLBackend::addressChanged);
|
||||
connect(client, &GRPCClient::addressChangedLogout, this, &QMLBackend::addressChangedLogout);
|
||||
connect(client, &GRPCClient::apiCertIssue, this, &QMLBackend::apiCertIssue);
|
||||
|
||||
// user events
|
||||
connect(client, &GRPCClient::userDisconnected, this, &QMLBackend::userDisconnected);
|
||||
users_->connectGRPCEvents();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::retrieveUserList()
|
||||
{
|
||||
QList<SPUser> users;
|
||||
logGRPCCallStatus(app().grpc().getUserList(users), "getUserList");
|
||||
users_->reset(users);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::clearUserList()
|
||||
{
|
||||
users_->reset();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
QPoint QMLBackend::getCursorPos()
|
||||
{
|
||||
return QCursor::pos();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
bool QMLBackend::isPortFree(int port)
|
||||
{
|
||||
bool isFree = false;
|
||||
logGRPCCallStatus(app().grpc().isPortFree(port, isFree), "isPortFree");
|
||||
return isFree;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::guiReady()
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().guiReady(), "guiReady");
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::quit()
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().quit(), "quit");
|
||||
qApp->exit(0);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::restart()
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().restart(), "restart");
|
||||
app().log().error("RESTART is not implemented"); /// \todo GODT-1671 implement restart.
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] active Should we activate autostart.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::toggleAutostart(bool active)
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().setIsAutostartOn(active), "setIsAutostartOn");
|
||||
emit isAutostartOnChanged(this->isAutostartOn());
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] active The new state for the beta enabled property.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::toggleBeta(bool active)
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().setisBetaEnabled(active), "setIsBetaEnabled");
|
||||
emit isBetaEnabledChanged(this->isBetaEnabled());
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] scheme the scheme name
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::changeColorScheme(QString const &scheme)
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().setColorSchemeName(scheme), "setIsBetaEnabled");
|
||||
emit colorSchemeNameChanged(this->colorSchemeName());
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] makeItActive Should SSL for SMTP be enabled.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::toggleUseSSLforSMTP(bool makeItActive)
|
||||
{
|
||||
grpc::Status status = app().grpc().setUseSSLForSMTP(makeItActive);
|
||||
logGRPCCallStatus(status, "setUseSSLForSMTP");
|
||||
if (status.ok())
|
||||
emit useSSLforSMTPChanged(makeItActive);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] imapPort The IMAP port.
|
||||
/// \param[in] smtpPort The SMTP port.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::changePorts(int imapPort, int smtpPort)
|
||||
{
|
||||
grpc::Status status = app().grpc().changePorts(imapPort, smtpPort);
|
||||
logGRPCCallStatus(status, "changePorts");
|
||||
if (status.ok())
|
||||
{
|
||||
emit portIMAPChanged(imapPort);
|
||||
emit portSMTPChanged(smtpPort);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] active Should DoH be active.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::toggleDoH(bool active)
|
||||
{
|
||||
grpc::Status status = app().grpc().setIsDoHEnabled(active);
|
||||
logGRPCCallStatus(status, "toggleDoH");
|
||||
if (status.ok())
|
||||
emit isDoHEnabledChanged(active);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] keychain The new keychain.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::changeKeychain(QString const &keychain)
|
||||
{
|
||||
grpc::Status status = app().grpc().setCurrentKeychain(keychain);
|
||||
logGRPCCallStatus(status, "setCurrentKeychain");
|
||||
if (status.ok())
|
||||
emit currentKeychainChanged(keychain);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] active Should automatic update be turned on.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::toggleAutomaticUpdate(bool active)
|
||||
{
|
||||
grpc::Status status = app().grpc().setIsAutomaticUpdateOn(active);
|
||||
logGRPCCallStatus(status, "toggleAutomaticUpdate");
|
||||
if (status.ok())
|
||||
emit isAutomaticUpdateOnChanged(active);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::checkUpdates()
|
||||
{
|
||||
app().log().error(QString("%1() is not implemented.").arg(__FUNCTION__));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::installUpdate()
|
||||
{
|
||||
app().log().error(QString("%1() is not implemented.").arg(__FUNCTION__));
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::triggerReset()
|
||||
{
|
||||
app().log().error(QString("%1() is not implemented.").arg(__FUNCTION__));
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::reportBug(QString const &description, QString const &address, QString const &emailClient,
|
||||
bool includeLogs)
|
||||
{
|
||||
Q_UNUSED(includeLogs)
|
||||
app().log().error(QString("%1() is not implemented.").arg(__FUNCTION__));
|
||||
}
|
||||
222
internal/frontend/qt6/QMLBackend.h
Normal file
@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_QMLBACKEND_H
|
||||
#define BRIDGE_QT6_QMLBACKEND_H
|
||||
|
||||
|
||||
#include <grpcpp/support/status.h>
|
||||
#include "DockIcon/DockIcon.h"
|
||||
#include "GRPC/GRPCClient.h"
|
||||
#include "GRPC/GRPCUtils.h"
|
||||
#include "Worker/Overseer.h"
|
||||
#include "User/UserList.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Bridge C++ backend class.
|
||||
//****************************************************************************************************************************************************
|
||||
class QMLBackend: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public: // member functions.
|
||||
QMLBackend(); ///< Default constructor.
|
||||
QMLBackend(QMLBackend const &) = delete; ///< Disabled copy-constructor.
|
||||
QMLBackend(QMLBackend &&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~QMLBackend() override = default; ///< Destructor.
|
||||
QMLBackend &operator=(QMLBackend const &) = delete; ///< Disabled assignment operator.
|
||||
QMLBackend &operator=(QMLBackend &&) = delete; ///< Disabled move assignment operator.
|
||||
void init(); ///< Initialize the backend.
|
||||
void clearUserList(); ///< Clear the user list.
|
||||
|
||||
// invokable methods can be called from QML. They generally return a value, which slots cannot do.
|
||||
Q_INVOKABLE static QPoint getCursorPos(); // _ func() *core.QPoint `slot:"getCursorPos"`
|
||||
Q_INVOKABLE static bool isPortFree(int port); // _ func(port int) bool `slot:"isPortFree"`
|
||||
|
||||
public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise)
|
||||
Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged) // _ bool `property:showOnStartup`
|
||||
Q_PROPERTY(bool showSplashScreen READ showSplashScreen NOTIFY showSplashScreenChanged) // _ bool `property:showSplashScreen`
|
||||
Q_PROPERTY(QString goos READ goos NOTIFY goosChanged) // _ string `property:"goos"`
|
||||
Q_PROPERTY(QUrl logsPath READ logsPath NOTIFY logsPathChanged) // _ core.QUrl `property:"logsPath"`
|
||||
Q_PROPERTY(QUrl licensePath READ licensePath NOTIFY licensePathChanged) // _ core.QUrl `property:"licensePath"`
|
||||
Q_PROPERTY(QUrl releaseNotesLink READ releaseNotesLink WRITE setReleaseNotesLink NOTIFY releaseNotesLinkChanged) // _ core.QUrl `property:"releaseNotesLink"`
|
||||
Q_PROPERTY(QUrl dependencyLicensesLink READ dependencyLicensesLink NOTIFY dependencyLicensesLinkChanged) // _ core.QUrl `property:"dependencyLicensesLink"`
|
||||
Q_PROPERTY(QUrl landingPageLink READ landingPageLink WRITE setLandingPageLink NOTIFY landingPageLinkChanged) // _ core.QUrl `property:"landingPageLink"`
|
||||
Q_PROPERTY(QString version READ version NOTIFY versionChanged) // _ string `property:"version"`
|
||||
Q_PROPERTY(QString hostname READ hostname NOTIFY hostnameChanged) // _ string `property:"hostname"`
|
||||
Q_PROPERTY(bool isAutostartOn READ isAutostartOn NOTIFY isAutostartOnChanged) // _ bool `property:"isAutostartOn"`
|
||||
Q_PROPERTY(bool isBetaEnabled READ isBetaEnabled NOTIFY isBetaEnabledChanged) // _ bool `property:"isBetaEnabled"`
|
||||
Q_PROPERTY(QString colorSchemeName READ colorSchemeName NOTIFY colorSchemeNameChanged) // _ string `property:"colorSchemeName"`
|
||||
Q_PROPERTY(bool isDiskCacheEnabled READ isDiskCacheEnabled NOTIFY isDiskCacheEnabledChanged) // _ bool `property:"isDiskCacheEnabled"`
|
||||
Q_PROPERTY(QUrl diskCachePath READ diskCachePath NOTIFY diskCachePathChanged) // _ core.QUrl `property:"diskCachePath"`
|
||||
Q_PROPERTY(bool useSSLforSMTP READ useSSLForSMTP NOTIFY useSSLforSMTPChanged) // _ bool `property:"useSSLforSMTP"`
|
||||
Q_PROPERTY(int portIMAP READ portIMAP NOTIFY portIMAPChanged) // _ int `property:"portIMAP"`
|
||||
Q_PROPERTY(int portSMTP READ portSMTP NOTIFY portSMTPChanged) // _ int `property:"portSMTP"`
|
||||
Q_PROPERTY(bool isDoHEnabled READ isDoHEnabled NOTIFY isDoHEnabledChanged) // _ bool `property:"isDoHEnabled"`
|
||||
Q_PROPERTY(bool isFirstGUIStart READ isFirstGUIStart) // _ bool `property:"isFirstGUIStart"`
|
||||
Q_PROPERTY(bool isAutomaticUpdateOn READ isAutomaticUpdateOn NOTIFY isAutomaticUpdateOnChanged) // _ bool `property:"isAutomaticUpdateOn"`
|
||||
Q_PROPERTY(QString currentEmailClient READ currentEmailClient NOTIFY currentEmailClientChanged) // _ string `property:"currentEmailClient"`
|
||||
Q_PROPERTY(QStringList availableKeychain READ availableKeychain NOTIFY availableKeychainChanged) // _ []string `property:"availableKeychain"`
|
||||
Q_PROPERTY(QString currentKeychain READ currentKeychain NOTIFY currentKeychainChanged) // _ string `property:"currentKeychain"`
|
||||
Q_PROPERTY(UserList* users MEMBER users_ NOTIFY usersChanged)
|
||||
Q_PROPERTY(bool dockIconVisible READ dockIconVisible WRITE setDockIconVisible NOTIFY dockIconVisibleChanged) // _ bool `property:dockIconVisible`
|
||||
|
||||
// Qt Property system setters & getters.
|
||||
bool showOnStartup() const { bool v = false; logGRPCCallStatus(app().grpc().showOnStartup(v), "showOnStartup"); return v; };
|
||||
bool showSplashScreen() { bool show = false; logGRPCCallStatus(app().grpc().showSplashScreen(show), "showSplashScreen"); return show; }
|
||||
QString goos() { QString goos; logGRPCCallStatus(app().grpc().goos(goos), "goos"); return goos; }
|
||||
QUrl logsPath() const { QUrl path; logGRPCCallStatus(app().grpc().logsPath(path), "logsPath"); return path;}
|
||||
QUrl licensePath() const { QUrl path; logGRPCCallStatus(app().grpc().licensePath(path), "licensePath"); return path; }
|
||||
QUrl releaseNotesLink() const { return releaseNotesLink_; }
|
||||
void setReleaseNotesLink(QUrl const& url) { if (url != releaseNotesLink_) { releaseNotesLink_ = url; emit releaseNotesLinkChanged(url); } }
|
||||
QUrl dependencyLicensesLink() const { QUrl link; logGRPCCallStatus(app().grpc().dependencyLicensesLink(link), "dependencyLicensesLink"); return link; }
|
||||
QUrl landingPageLink() const { return landingPageLink_; }
|
||||
void setLandingPageLink(QUrl const& url) { if (url != landingPageLink_) { landingPageLink_ = url; emit landingPageLinkChanged(url); } }
|
||||
QString version() const { QString version; logGRPCCallStatus(app().grpc().version(version), "version"); return version; }
|
||||
QString hostname() const { QString hostname; logGRPCCallStatus(app().grpc().hostname(hostname), "hostname"); return hostname; }
|
||||
bool isAutostartOn() const { bool v; logGRPCCallStatus(app().grpc().isAutostartOn(v), "isAutostartOn"); return v; };
|
||||
bool isBetaEnabled() const { bool v; logGRPCCallStatus(app().grpc().isBetaEnabled(v), "isBetaEnabled"); return v; }
|
||||
QString colorSchemeName() const { QString name; logGRPCCallStatus(app().grpc().colorSchemeName(name), "colorSchemeName"); return name; }
|
||||
bool isDiskCacheEnabled() const { bool enabled; logGRPCCallStatus(app().grpc().isCacheOnDiskEnabled(enabled), "isCacheOnDiskEnabled"); return enabled;}
|
||||
QUrl diskCachePath() const { QUrl path; logGRPCCallStatus(app().grpc().diskCachePath(path), "diskCachePath"); return path; }
|
||||
bool useSSLForSMTP() const{ bool useSSL; logGRPCCallStatus(app().grpc().useSSLForSMTP(useSSL), "useSSLForSMTP"); return useSSL; }
|
||||
int portIMAP() const { int port; logGRPCCallStatus(app().grpc().portIMAP(port), "portIMAP"); return port; }
|
||||
int portSMTP() const { int port; logGRPCCallStatus(app().grpc().portSMTP(port), "portSMTP"); return port; }
|
||||
bool isDoHEnabled() const { bool isEnabled; logGRPCCallStatus(app().grpc().isDoHEnabled(isEnabled), "isDoHEnabled"); return isEnabled;}
|
||||
bool isFirstGUIStart() const { bool v; logGRPCCallStatus(app().grpc().isFirstGUIStart(v), "isFirstGUIStart"); return v; };
|
||||
bool isAutomaticUpdateOn() const { bool isOn = false; logGRPCCallStatus(app().grpc().isAutomaticUpdateOn(isOn), "isAutomaticUpdateOn"); return isOn; }
|
||||
QString currentEmailClient() { QString client; logGRPCCallStatus(app().grpc().currentEmailClient(client), "currentEmailClient"); return client;}
|
||||
QStringList availableKeychain() const { QStringList keychains; logGRPCCallStatus(app().grpc().availableKeychains(keychains), "availableKeychain"); return keychains; }
|
||||
QString currentKeychain() const { QString keychain; logGRPCCallStatus(app().grpc().currentKeychain(keychain), "currentKeychain"); return keychain; }
|
||||
bool dockIconVisible() const { return getDockIconVisibleState(); };
|
||||
void setDockIconVisible(bool visible) { setDockIconVisibleState(visible); emit dockIconVisibleChanged(visible); }
|
||||
|
||||
signals: // Signal used by the Qt property system. Many of them are unused but required to avoir warning from the QML engine.
|
||||
void showSplashScreenChanged(bool value);
|
||||
void showOnStartupChanged(bool value);
|
||||
void goosChanged(QString const &value);
|
||||
void isDiskCacheEnabledChanged(bool value);
|
||||
void diskCachePathChanged(QUrl const &url);
|
||||
void useSSLforSMTPChanged(bool value);
|
||||
void isAutomaticUpdateOnChanged(bool value);
|
||||
void isBetaEnabledChanged(bool value);
|
||||
void colorSchemeNameChanged(QString const &scheme);
|
||||
void isDoHEnabledChanged(bool value);
|
||||
void logsPathChanged(QUrl const &path);
|
||||
void licensePathChanged(QUrl const &path);
|
||||
void releaseNotesLinkChanged(QUrl const &link);
|
||||
void dependencyLicensesLinkChanged(QUrl const &link);
|
||||
void landingPageLinkChanged(QUrl const &link);
|
||||
void versionChanged(QString const &version);
|
||||
void currentEmailClientChanged(QString const &email);
|
||||
void currentKeychainChanged(QString const &keychain);
|
||||
void availableKeychainChanged(QStringList const &keychains);
|
||||
void hostnameChanged(QString const &hostname);
|
||||
void isAutostartOnChanged(bool value);
|
||||
void portIMAPChanged(int port);
|
||||
void portSMTPChanged(int port);
|
||||
void usersChanged(UserList* users);
|
||||
void dockIconVisibleChanged(bool value);
|
||||
|
||||
public slots: // slot for signals received from QML -> To be forwarded to Bridge via RPC Client calls.
|
||||
void toggleAutostart(bool active); // _ func(makeItActive bool) `slot:"toggleAutostart"`
|
||||
void toggleBeta(bool active); // _ func(makeItActive bool) `slot:"toggleBeta"`
|
||||
void changeColorScheme(QString const &scheme); // _ func(string) `slot:"changeColorScheme"`
|
||||
void changeLocalCache(bool enable, QUrl const& path) { logGRPCCallStatus(app().grpc().changeLocalCache(enable, path), "changeLocalCache"); } // _ func(enableDiskCache bool, diskCachePath core.QUrl) `slot:"changeLocalCache"`
|
||||
void login(QString const& username, QString const& password) { logGRPCCallStatus(app().grpc().login(username, password), "login");} // _ func(username, password string) `slot:"login"`
|
||||
void login2FA(QString const& username, QString const& code) { logGRPCCallStatus(app().grpc().login2FA(username, code), "login2FA");} // _ func(username, code string) `slot:"login2FA"`
|
||||
void login2Password(QString const& username, QString const& password) { logGRPCCallStatus(app().grpc().login2Passwords(username, password),
|
||||
"login2Passwords");} // _ func(username, password string) `slot:"login2Password"`
|
||||
void loginAbort(QString const& username){ logGRPCCallStatus(app().grpc().loginAbort(username), "loginAbort");} // _ func(username string) `slot:"loginAbort"`
|
||||
void toggleUseSSLforSMTP(bool makeItActive); // _ func(makeItActive bool) `slot:"toggleUseSSLforSMTP"`
|
||||
void changePorts(int imapPort, int smtpPort); // _ func(imapPort, smtpPort int) `slot:"changePorts"`
|
||||
void toggleDoH(bool active); // _ func(makeItActive bool) `slot:"toggleDoH"`
|
||||
void toggleAutomaticUpdate(bool makeItActive); // _ func(makeItActive bool) `slot:"toggleAutomaticUpdate"`
|
||||
void updateCurrentMailClient() { emit currentEmailClientChanged(currentEmailClient()); } // _ func() `slot:"updateCurrentMailClient"`
|
||||
void changeKeychain(QString const &keychain); // _ func(keychain string) `slot:"changeKeychain"`
|
||||
void guiReady(); // _ func() `slot:"guiReady"`
|
||||
void quit(); // _ func() `slot:"quit"`
|
||||
void restart(); // _ func() `slot:"restart"`
|
||||
void checkUpdates(); // _ func() `slot:"checkUpdates"`
|
||||
void installUpdate(); // _ func() `slot:"installUpdate"`
|
||||
void triggerReset(); // _ func() `slot:"triggerReset"`
|
||||
void reportBug(QString const &description, QString const& address, QString const &emailClient, bool includeLogs); // _ func(description, address, emailClient string, includeLogs bool) `slot:"reportBug"`
|
||||
|
||||
signals: // Signals received from the Go backend, to be forwarded to QML
|
||||
void toggleAutostartFinished(); // _ func() `signal:"toggleAutostartFinished"`
|
||||
void cacheUnavailable(); // _ func() `signal:"cacheUnavailable"`
|
||||
void cacheCantMove(); // _ func() `signal:"cacheCantMove"`
|
||||
void cacheLocationChangeSuccess(); // _ func() `signal:"cacheLocationChangeSuccess"`
|
||||
void diskFull(); // _ func() `signal:"diskFull"`
|
||||
void changeLocalCacheFinished(); // _ func() `signal:"changeLocalCacheFinished"`
|
||||
void loginUsernamePasswordError(QString const &errorMsg); // _ func(errorMsg string) `signal:"loginUsernamePasswordError"`
|
||||
void loginFreeUserError(); // _ func() `signal:"loginFreeUserError"`
|
||||
void loginConnectionError(QString const &errorMsg); // _ func(errorMsg string) `signal:"loginConnectionError"`
|
||||
void login2FARequested(QString const &username); // _ func(username string) `signal:"login2FARequested"`
|
||||
void login2FAError(QString const& errorMsg); // _ func(errorMsg string) `signal:"login2FAError"`
|
||||
void login2FAErrorAbort(QString const& errorMsg); // _ func(errorMsg string) `signal:"login2FAErrorAbort"`
|
||||
void login2PasswordRequested(); // _ func() `signal:"login2PasswordRequested"`
|
||||
void login2PasswordError(QString const& errorMsg); // _ func(errorMsg string) `signal:"login2PasswordError"`
|
||||
void login2PasswordErrorAbort(QString const& errorMsg); // _ func(errorMsg string) `signal:"login2PasswordErrorAbort"`
|
||||
void loginFinished(int index); // _ func(index int) `signal:"loginFinished"`
|
||||
void loginAlreadyLoggedIn(int index); // _ func(index int) `signal:"loginAlreadyLoggedIn"`
|
||||
void updateManualReady(QString const& version); // _ func(version string) `signal:"updateManualReady"`
|
||||
void updateManualRestartNeeded(); // _ func() `signal:"updateManualRestartNeeded"`
|
||||
void updateManualError(); // _ func() `signal:"updateManualError"`
|
||||
void updateForce(QString const& version); // _ func(version string) `signal:"updateForce"`
|
||||
void updateForceError(); // _ func() `signal:"updateForceError"`
|
||||
void updateSilentRestartNeeded(); // _ func() `signal:"updateSilentRestartNeeded"`
|
||||
void updateSilentError(); // _ func() `signal:"updateSilentError"`
|
||||
void updateIsLatestVersion(); // _ func() `signal:"updateIsLatestVersion"`
|
||||
void checkUpdatesFinished(); // _ func() `signal:"checkUpdatesFinished"`
|
||||
void toggleUseSSLFinished(); // _ func() `signal:"toggleUseSSLFinished"`
|
||||
void changePortFinished(); // _ func() `signal:"changePortFinished"`
|
||||
void portIssueIMAP(); // _ func() `signal:"portIssueIMAP"`
|
||||
void portIssueSMTP(); // _ func() `signal:"portIssueSMTP"`
|
||||
void changeKeychainFinished(); // _ func() `signal:"changeKeychainFinished"`
|
||||
void notifyHasNoKeychain(); // _ func() `signal:"notifyHasNoKeychain"`
|
||||
void notifyRebuildKeychain(); // _ func() `signal:"notifyRebuildKeychain"`
|
||||
void noActiveKeyForRecipient(QString const& email); // _ func(email string) `signal:noActiveKeyForRecipient`
|
||||
void addressChanged(QString const& address); // _ func(address string) `signal:addressChanged`
|
||||
void addressChangedLogout(QString const& address); // _ func(address string) `signal:addressChangedLogout`
|
||||
void apiCertIssue(); // _ func() `signal:apiCertIssue`
|
||||
void userDisconnected(QString const& username); // _ func(username string) `signal:userDisconnected`
|
||||
void internetOff(); // _ func() `signal:"internetOff"`
|
||||
void internetOn(); // _ func() `signal:"internetOn"`
|
||||
void resetFinished(); // _ func() `signal:"resetFinished"`
|
||||
void reportBugFinished(); // _ func() `signal:"reportBugFinished"`
|
||||
void bugReportSendSuccess(); // _ func() `signal:"bugReportSendSuccess"`
|
||||
void bugReportSendError(); // _ func() `signal:"bugReportSendError"`
|
||||
void showMainWindow(); // _ func() `signal:showMainWindow`
|
||||
|
||||
private: // member functions
|
||||
void retrieveUserList(); ///< Retrieve the list of users via gRPC.
|
||||
void connectGrpcEvents(); ///< Connect gRPC that need to be forwarded to QML via backend signals
|
||||
|
||||
private: // data members
|
||||
UserList* users_ { nullptr }; ///< The user list. Owned by backend.
|
||||
std::unique_ptr<Overseer> eventStreamOverseer_; ///< The event stream overseer.
|
||||
QUrl releaseNotesLink_; /// Release notes is not stored in the backend, it's pushed by the update check so we keep a local copy of it. \todo GODT-1670 Check this is implemented.
|
||||
QUrl landingPageLink_; /// Landing page link is not stored in the backend, it's pushed by the update check so we keep a local copy of it. \todo GODT-1670 Check this is implemented.
|
||||
|
||||
friend class AppController;
|
||||
};
|
||||
|
||||
|
||||
#endif // BRIDGE_QT6_QMLBACKEND_H
|
||||
95
internal/frontend/qt6/User/User.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "User.h"
|
||||
#include "GRPC/GRPCUtils.h"
|
||||
#include "GRPC/GRPCClient.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] parent The parent object.
|
||||
//****************************************************************************************************************************************************
|
||||
User::User(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] user The user to copy from
|
||||
//****************************************************************************************************************************************************
|
||||
void User::update(User const &user)
|
||||
{
|
||||
this->setProperty("username", user.username_);
|
||||
this->setProperty("avatarText", user.avatarText_);
|
||||
this->setProperty("loggedIn", user.loggedIn_);
|
||||
this->setProperty("splitMode", user.splitMode_);
|
||||
this->setProperty("setupGuideSeen", user.setupGuideSeen_);
|
||||
this->setProperty("usedBytes", user.usedBytes_);
|
||||
this->setProperty("totalBytes", user.totalBytes_);
|
||||
this->setProperty("password", user.password_);
|
||||
this->setProperty("addresses", user.addresses_);
|
||||
this->setProperty("id", user.id_);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] makeItActive Should split mode be made active.
|
||||
//****************************************************************************************************************************************************
|
||||
void User::toggleSplitMode(bool makeItActive)
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().setUserSplitMode(id_, makeItActive), "toggleSplitMode");
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void User::logout()
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().logoutUser(id_), "logoutUser");
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void User::remove()
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().removeUser(id_), "removeUser");
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] address The email address to configure Apple Mail for.
|
||||
//****************************************************************************************************************************************************
|
||||
void User::configureAppleMail(QString const &address)
|
||||
{
|
||||
logGRPCCallStatus(app().grpc().configureAppleMail(id_, address), "configureAppleMail");
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
// The only purpose of this call is to forward to the QML application the toggleSplitModeFinished(userID) event
|
||||
// that was received by the UserList model.
|
||||
//****************************************************************************************************************************************************
|
||||
void User::emitToggleSplitModeFinished()
|
||||
{
|
||||
this->setProperty("splitMode", QVariant::fromValue(!this->property("splitMode").toBool()));
|
||||
emit toggleSplitModeFinished();
|
||||
}
|
||||
93
internal/frontend/qt6/User/User.h
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_USER_H
|
||||
#define BRIDGE_QT6_USER_H
|
||||
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief User class.
|
||||
//****************************************************************************************************************************************************
|
||||
class User : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public: // member functions.
|
||||
explicit User(QObject *parent = nullptr); ///< Default constructor.
|
||||
User(User const &) = delete; ///< Disabled copy-constructor.
|
||||
User(User &&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~User() override = default; ///< Destructor.
|
||||
User &operator=(User const &) = delete; ///< Disabled assignment operator.
|
||||
User &operator=(User &&) = delete; ///< Disabled move assignment operator.
|
||||
void update(User const &user); ///< Update the user
|
||||
public slots:
|
||||
// slots for QML generated calls
|
||||
void toggleSplitMode(bool makeItActive); // _ func(makeItActive bool) `slot:"toggleSplitMode"`
|
||||
void logout(); // _ func() `slot:"logout"`
|
||||
void remove(); // _ func() `slot:"remove"`
|
||||
void configureAppleMail(QString const &address); // _ func(address string) `slot:"configureAppleMail"`
|
||||
|
||||
// slots for external signals
|
||||
void emitToggleSplitModeFinished();
|
||||
|
||||
public:
|
||||
Q_PROPERTY(QString username MEMBER username_ NOTIFY usernameChanged) // _ string `property:"username"`
|
||||
Q_PROPERTY(QString avatarText MEMBER avatarText_ NOTIFY avatarTextChanged) // _ string `property:"avatarText"`
|
||||
Q_PROPERTY(bool loggedIn MEMBER loggedIn_ NOTIFY loggedInChanged) // _ bool `property:"loggedIn"`
|
||||
Q_PROPERTY(bool splitMode MEMBER splitMode_ NOTIFY splitModeChanged) // _ bool `property:"splitMode"`
|
||||
Q_PROPERTY(bool setupGuideSeen MEMBER setupGuideSeen_ NOTIFY setupGuideSeenChanged) // _ bool `property:"setupGuideSeen"`
|
||||
Q_PROPERTY(float usedBytes MEMBER usedBytes_ NOTIFY usedBytesChanged) // _ float32 `property:"usedBytes"`
|
||||
Q_PROPERTY(float totalBytes MEMBER totalBytes_ NOTIFY totalBytesChanged) // _ float32 `property:"totalBytes"`
|
||||
Q_PROPERTY(QString password MEMBER password_ NOTIFY passwordChanged) // _ string `property:"password"`
|
||||
Q_PROPERTY(QStringList addresses MEMBER addresses_ NOTIFY addressesChanged) // _ []string `property:"addresses"`
|
||||
Q_PROPERTY(QString id MEMBER id_ NOTIFY idChanged) // _ string ID
|
||||
|
||||
signals:
|
||||
// signals used for Qt properties
|
||||
void usernameChanged(QString const &username);
|
||||
void avatarTextChanged(QString const &avatarText);
|
||||
void loggedInChanged(bool loggedIn);
|
||||
void splitModeChanged(bool splitMode);
|
||||
void setupGuideSeenChanged(bool seen);
|
||||
void usedBytesChanged(float byteCount);
|
||||
void totalBytesChanged(float byteCount);
|
||||
void passwordChanged(QString const &);
|
||||
void addressesChanged(QStringList const &);
|
||||
void idChanged(QStringList const &id);
|
||||
void toggleSplitModeFinished();
|
||||
|
||||
private:
|
||||
QString id_; ///< The userID.
|
||||
QString username_; ///< The username
|
||||
QString avatarText_; ///< The avatar text (i.e. initials of the user)
|
||||
bool loggedIn_{false}; ///< Is the user logged in.
|
||||
bool splitMode_{false}; ///< Is split mode active.
|
||||
bool setupGuideSeen_{false}; ///< Has the setup guide been seen.
|
||||
float usedBytes_{0.0f}; ///< The storage used by the user.
|
||||
float totalBytes_{0.0f}; ///< The storage quota of the user.
|
||||
QString password_; ///< The IMAP password of the user.
|
||||
QStringList addresses_; ///< The email address list of the user.
|
||||
};
|
||||
|
||||
|
||||
typedef std::shared_ptr<User> SPUser;
|
||||
|
||||
|
||||
#endif // BRIDGE_QT6_USER_H
|
||||
220
internal/frontend/qt6/User/UserList.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "UserList.h"
|
||||
#include "GRPC/GRPCClient.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] parent The parent object of the user list.
|
||||
//****************************************************************************************************************************************************
|
||||
UserList::UserList(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
/// \todo use mutex to prevent concurrent access
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::connectGRPCEvents() const
|
||||
{
|
||||
GRPCClient* client = &app().grpc();
|
||||
connect(client, &GRPCClient::userChanged, this, &UserList::onUserChanged);
|
||||
connect(client, &GRPCClient::toggleSplitModeFinished, this, &UserList::onToggleSplitModeFinished);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
int UserList::rowCount(QModelIndex const &) const
|
||||
{
|
||||
return users_.size();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] index The index to retrieve data for.
|
||||
/// \param[in] role The role to retrieve data for.
|
||||
/// \return The data at the index for the given role.
|
||||
//****************************************************************************************************************************************************
|
||||
QVariant UserList::data(QModelIndex const &index, int role) const
|
||||
{
|
||||
/// This It does not seem to be used, but the method is required by the base class.
|
||||
/// From the original QtThe recipe QML backend User model, the User is always returned, regardless of the role.
|
||||
Q_UNUSED(role)
|
||||
int const row = index.row();
|
||||
if ((row < 0) || (row >= users_.size()))
|
||||
return QVariant();
|
||||
return QVariant::fromValue(users_[row].get());
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] userID The userID.
|
||||
/// \return the row of the user.
|
||||
/// \return -1 if the userID is not in the list
|
||||
//****************************************************************************************************************************************************
|
||||
int UserList::rowOfUserID(QString const &userID) const
|
||||
{
|
||||
for (qint32 row = 0; row < users_.count(); ++row)
|
||||
if (userID == users_[row]->property("id"))
|
||||
return row;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::reset()
|
||||
{
|
||||
this->beginResetModel();
|
||||
users_.clear();
|
||||
this->endResetModel();
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] users The new user list.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::reset(QList<SPUser> const &users)
|
||||
{
|
||||
this->beginResetModel();
|
||||
users_ = users;
|
||||
this->endResetModel();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] user The user.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::appendUser(SPUser const& user)
|
||||
{
|
||||
int const size = users_.size();
|
||||
this->beginInsertRows(QModelIndex(), size, size);
|
||||
users_.append(user);
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] row The row.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::removeUserAt(int row)
|
||||
{
|
||||
if ((row < 0) && (row >= users_.size()))
|
||||
return;
|
||||
this->beginRemoveRows(QModelIndex(), row, row);
|
||||
users_.removeAt(row);
|
||||
this->endRemoveRows();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] row The row.
|
||||
/// \param[in] user The user.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::updateUserAtRow(int row, User const &user)
|
||||
{
|
||||
if ((row < 0) || (row >= users_.count()))
|
||||
{
|
||||
app().log().error(QString("invalid user at row %2 (user count = %2)").arg(row).arg(users_.count()));
|
||||
return;
|
||||
}
|
||||
|
||||
users_[row]->update(user);
|
||||
|
||||
QModelIndex modelIndex = this->index(row);
|
||||
emit dataChanged(modelIndex, modelIndex);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] row The row.
|
||||
//****************************************************************************************************************************************************
|
||||
User *UserList::get(int row) const
|
||||
{
|
||||
if ((row < 0) || (row >= users_.count()))
|
||||
{
|
||||
app().log().error(QString("Requesting invalid user at row %1 (user count = %2)").arg(row).arg(users_.count()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
app().log().debug(QString("Retrieving user at row %1 (user count = %2)").arg(row).arg(users_.count()));
|
||||
return users_[row].get();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] userID The userID.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::onUserChanged(QString const &userID)
|
||||
{
|
||||
int const index = this->rowOfUserID(userID);
|
||||
SPUser user;
|
||||
grpc::Status status = app().grpc().getUser(userID, user);
|
||||
if ((!user) || (!status.ok()))
|
||||
{
|
||||
if (index >= 0) // user exists here but not in the go backend. we delete it.
|
||||
{
|
||||
app().log().debug(QString("Removing user from userlist: %1").arg(userID));
|
||||
this->removeUserAt(index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
app().log().debug(QString("Adding user in userlist: %1").arg(userID));
|
||||
this->appendUser(user);
|
||||
return;
|
||||
}
|
||||
|
||||
app().log().debug(QString("Updating user in userlist: %1").arg(userID));
|
||||
this->updateUserAtRow(index, *user);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// The only purpose of this function is to forward the toggleSplitModeFinished event received from gRPC to the
|
||||
/// appropriate user.
|
||||
///
|
||||
/// \param[in] userID the userID.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::onToggleSplitModeFinished(QString const &userID)
|
||||
{
|
||||
int const index = this->rowOfUserID(userID);
|
||||
if (index < 0)
|
||||
{
|
||||
app().log().error(QString("Received toggleSplitModeFinished event for unknown userID %1").arg(userID));
|
||||
return;
|
||||
}
|
||||
|
||||
users_[index]->emitToggleSplitModeFinished();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return THe number of items in the list.
|
||||
//****************************************************************************************************************************************************
|
||||
int UserList::count() const
|
||||
{
|
||||
return users_.size();
|
||||
}
|
||||
64
internal/frontend/qt6/User/UserList.h
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_USER_LIST_H
|
||||
#define BRIDGE_QT6_USER_LIST_H
|
||||
|
||||
#include "User.h"
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief User list class.
|
||||
//****************************************************************************************************************************************************
|
||||
class UserList: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public: // member functions.
|
||||
explicit UserList(QObject *parent = nullptr); ///< Default constructor.
|
||||
UserList(UserList const &other) = delete ; ///< Disabled copy-constructor.
|
||||
UserList& operator=(UserList const& other) = delete; ///< Disabled assignment operator.
|
||||
~UserList() override = default; ///< Destructor
|
||||
void connectGRPCEvents() const; ///< Connects gRPC event to the model.
|
||||
int rowCount(QModelIndex const &parent) const override; ///< Return the number of row in the model
|
||||
QVariant data(QModelIndex const &index, int role) const override; ///< Retrieve model data.
|
||||
void reset(); ///< Reset the user list.
|
||||
void reset(QList<SPUser> const &users); ///< Replace the user list.
|
||||
int rowOfUserID(QString const &userID) const;
|
||||
void removeUserAt(int row); ///< Remove the user at a given row
|
||||
void appendUser(SPUser const& user); ///< Add a new user.
|
||||
void updateUserAtRow(int row, User const& user); ///< Update the user at given row.
|
||||
|
||||
// the count property.
|
||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||
int count() const; ///< The count property getter.
|
||||
|
||||
signals:
|
||||
void countChanged(int count); ///< Signal for the count property.
|
||||
|
||||
public:
|
||||
Q_INVOKABLE User* get(int row) const;
|
||||
|
||||
public slots: ///< handler for signals coming from the gRPC service
|
||||
void onUserChanged(QString const &userID);
|
||||
void onToggleSplitModeFinished(QString const &userID);
|
||||
|
||||
private: // data members
|
||||
QList<SPUser> users_; ///< The user list.
|
||||
};
|
||||
|
||||
|
||||
#endif // BRIDGE_QT6_USER_LIST_H
|
||||
103
internal/frontend/qt6/Worker/Overseer.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "Overseer.h"
|
||||
#include "Exception.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] worker The worker.
|
||||
/// \param[in] parent The parent object of the worker.
|
||||
//****************************************************************************************************************************************************
|
||||
Overseer::Overseer(Worker *worker, QObject *parent)
|
||||
: QObject(parent)
|
||||
, thread_(new QThread(parent))
|
||||
, worker_(worker)
|
||||
{
|
||||
if (!worker_)
|
||||
throw Exception("Overseer cannot accept a nil worker.");
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
Overseer::~Overseer()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] autorelease Should the overseer automatically release the worker and thread when done.
|
||||
//****************************************************************************************************************************************************
|
||||
void Overseer::startWorker(bool autorelease) const
|
||||
{
|
||||
if (!worker_)
|
||||
throw Exception("Cannot start overseer with null worker.");
|
||||
if (!thread_)
|
||||
throw Exception("Cannot start overseer with null thread.");
|
||||
|
||||
worker_->moveToThread(thread_);
|
||||
connect(thread_, &QThread::started, worker_, &Worker::run);
|
||||
connect(worker_, &Worker::finished, thread_, &QThread::quit);
|
||||
connect(worker_, &Worker::error, thread_, &QThread::quit);
|
||||
if (autorelease)
|
||||
{
|
||||
connect(worker_, &Worker::error, this, &Overseer::release);
|
||||
connect(worker_, &Worker::finished, this, &Overseer::release);
|
||||
}
|
||||
thread_->start();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void Overseer::release()
|
||||
{
|
||||
if (worker_)
|
||||
{
|
||||
worker_->deleteLater();
|
||||
worker_ = nullptr;
|
||||
}
|
||||
|
||||
if (thread_)
|
||||
{
|
||||
if (!thread_->isFinished())
|
||||
{
|
||||
thread_->quit();
|
||||
thread_->wait();
|
||||
}
|
||||
thread_->deleteLater();
|
||||
thread_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return true iff the worker is finished, release
|
||||
//****************************************************************************************************************************************************
|
||||
bool Overseer::isFinished() const
|
||||
{
|
||||
if ((!worker_) || (!worker_->thread()))
|
||||
return true;
|
||||
|
||||
return worker_->thread()->isFinished();
|
||||
}
|
||||
55
internal/frontend/qt6/Worker/Overseer.h
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_OVERSEER_H
|
||||
#define BRIDGE_QT6_OVERSEER_H
|
||||
|
||||
|
||||
#include "Worker.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Overseer used to manager a worker instance and its associated thread.
|
||||
//****************************************************************************************************************************************************
|
||||
class Overseer: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public: // member functions.
|
||||
explicit Overseer(Worker* worker, QObject* parent); ///< Default constructor.
|
||||
Overseer(Overseer const&) = delete; ///< Disabled copy-constructor.
|
||||
Overseer(Overseer&&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~Overseer() override; ///< Destructor.
|
||||
Overseer& operator=(Overseer const&) = delete; ///< Disabled assignment operator.
|
||||
Overseer& operator=(Overseer&&) = delete; ///< Disabled move assignment operator.
|
||||
bool isFinished() const; ///< Check if the worker is finished.
|
||||
|
||||
public slots:
|
||||
void startWorker(bool autorelease) const; ///< Run the worker.
|
||||
void release(); ///< Delete the worker and its thread.
|
||||
|
||||
public: // data members.
|
||||
QThread *thread_ { nullptr }; ///< The thread.
|
||||
Worker *worker_ { nullptr }; ///< The worker.
|
||||
};
|
||||
|
||||
|
||||
typedef std::unique_ptr<Overseer> UPOverseer; ///< Type definition for unique pointer to Overseer.
|
||||
typedef std::shared_ptr<Overseer> SPOverseer; ///< Type definition for shared pointer to Overseer.
|
||||
|
||||
|
||||
#endif //BRIDGE_QT6_OVERSEER_H
|
||||
46
internal/frontend/qt6/Worker/Worker.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_QT6_WORKER_H
|
||||
#define BRIDGE_QT6_WORKER_H
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Pure virtual class for worker intended to perform a threaded operation.
|
||||
//****************************************************************************************************************************************************
|
||||
class Worker: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public: // member functions
|
||||
explicit Worker(QObject *parent) : QObject(parent) {} ///< Default constructor.
|
||||
Worker(Worker const&) = delete; ///< Disabled copy-constructor.
|
||||
Worker(Worker&&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~Worker() override = default; ///< Destructor.
|
||||
Worker& operator=(Worker const&) = delete; ///< Disabled assignment operator.
|
||||
Worker& operator=(Worker&&) = delete; ///< Disabled move assignment operator.
|
||||
|
||||
public slots:
|
||||
virtual void run() = 0; ///< run the worker.
|
||||
|
||||
signals:
|
||||
void started(); ///< Signal for the start of the worker
|
||||
void finished(); ///< Signal for the end of the worker
|
||||
void error(QString const& message); ///< Signal for errors. After an error, worker ends and finished is NOT emitted.
|
||||
};
|
||||
|
||||
|
||||
#endif //BRIDGE_QT6_WORKER_H
|
||||
22
internal/frontend/qt6/build.sh
Executable file
@ -0,0 +1,22 @@
|
||||
# Copyright (c) 2022 Proton AG
|
||||
#
|
||||
# This file is part of Proton Mail Bridge.
|
||||
#
|
||||
# Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#!/bin/bash
|
||||
BUILD_DIR="./cmake-build-debug"
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja -S . -B ${BUILD_DIR}
|
||||
ninja -C ${BUILD_DIR}
|
||||
135
internal/frontend/qt6/main.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "Exception.h"
|
||||
#include "QMLBackend.h"
|
||||
#include "Log.h"
|
||||
#include "EventStreamWorker.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// // initialize the Qt application.
|
||||
//****************************************************************************************************************************************************
|
||||
std::shared_ptr<QGuiApplication> initQtApplication(int argc, char *argv[])
|
||||
{
|
||||
// Note the two following attributes must be set before instantiating the QCoreApplication/QGuiApplication class.
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false);
|
||||
if (QSysInfo::productType() != "windows")
|
||||
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
|
||||
|
||||
QString const qsgInfo = QProcessEnvironment::systemEnvironment().value("QSG_INFO");
|
||||
if ((!qsgInfo.isEmpty()) && (qsgInfo != "0"))
|
||||
QLoggingCategory::setFilterRules("qt.scenegraph.general=true");
|
||||
|
||||
auto app = std::make_shared<QGuiApplication>(argc, argv);
|
||||
|
||||
/// \todo GODT-1670 Get version from go backend.
|
||||
QGuiApplication::setApplicationName("Proton Mail Bridge");
|
||||
QGuiApplication::setApplicationVersion("3.0");
|
||||
QGuiApplication::setOrganizationName("Proton AG");
|
||||
QGuiApplication::setOrganizationDomain("proton.ch");
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void initLog()
|
||||
{
|
||||
Log &log = app().log();
|
||||
log.setEchoInConsole(true);
|
||||
log.setLevel(Log::Level::Debug);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] engine The QML engine.
|
||||
//****************************************************************************************************************************************************
|
||||
QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine)
|
||||
{
|
||||
/// \todo GODT-1669 pack QML and resources in QRC resource file.
|
||||
QDir qmlDir("qml");
|
||||
|
||||
qmlRegisterType<QMLBackend>("CppBackend", 1, 0, "QMLBackend");
|
||||
qmlRegisterType<UserList>("CppBackend", 1, 0, "UserList");
|
||||
qmlRegisterType<User>("CppBackend", 1, 0, "User");
|
||||
|
||||
auto rootComponent = new QQmlComponent(&engine, &engine);
|
||||
|
||||
engine.addImportPath(qmlDir.absolutePath());
|
||||
engine.addPluginPath(qmlDir.absolutePath());
|
||||
|
||||
QQuickStyle::addStylePath(qmlDir.absolutePath());
|
||||
QQuickStyle::setStyle("Proton");
|
||||
|
||||
rootComponent->loadUrl(qmlDir.absoluteFilePath("Bridge.qml"));
|
||||
if (rootComponent->status() != QQmlComponent::Status::Ready)
|
||||
throw Exception("Could not load QML component");
|
||||
|
||||
return rootComponent;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] argc The number of command-line arguments.
|
||||
/// \param[in] argv The list of command-line arguments.
|
||||
/// \return The exit code for the application.
|
||||
//****************************************************************************************************************************************************
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
try
|
||||
{
|
||||
std::shared_ptr<QGuiApplication> guiApp = initQtApplication(argc, argv);
|
||||
initLog();
|
||||
|
||||
/// \todo GODT-1667 Locate & Launch go backend (and wait for it).
|
||||
|
||||
app().backend().init();
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
QQmlComponent *rootComponent = createRootQmlComponent(engine);
|
||||
|
||||
QObject *rootObject = rootComponent->beginCreate(engine.rootContext());
|
||||
if (!rootObject)
|
||||
throw Exception("Could not create root object.");
|
||||
rootObject->setProperty("backend", QVariant::fromValue(&app().backend()));
|
||||
rootComponent->completeCreate();
|
||||
|
||||
int result = QGuiApplication::exec();
|
||||
|
||||
app().log().info(QString("Exiting app with return code %1").arg(result));
|
||||
|
||||
app().grpc().stopEventStream();
|
||||
app().backend().clearUserList();
|
||||
|
||||
/// \todo GODT-1667 shutdown go backend.
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception const &e)
|
||||
{
|
||||
app().log().error(e.qwhat());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
190
internal/frontend/qt6/qml/AccountDelegate.qml
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var user
|
||||
|
||||
property var _spacing: 12 * ProtonStyle.px
|
||||
|
||||
property color usedSpaceColor : {
|
||||
if (!root.enabled) return root.colorScheme.text_weak
|
||||
if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak
|
||||
if (root.usedFraction < .50) return root.colorScheme.signal_success
|
||||
if (root.usedFraction < .75) return root.colorScheme.signal_warning
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
property real usedFraction: root.user ? reasonableFracion(root.user.usedBytes, root.user.totalBytes) : 0
|
||||
property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0)
|
||||
property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0)
|
||||
|
||||
function reasonableFracion(used, total){
|
||||
var usedSafe = root.reasonableBytes(used)
|
||||
var totalSafe = root.reasonableBytes(total)
|
||||
if (totalSafe == 0 || usedSafe == 0) return 0
|
||||
if (totalSafe <= usedSafe) return 1
|
||||
return usedSafe / totalSafe
|
||||
}
|
||||
|
||||
function reasonableBytes(bytes){
|
||||
var safeBytes = bytes+0
|
||||
if (safeBytes != bytes) return 0
|
||||
if (safeBytes < 0) return 0
|
||||
return Math.ceil(safeBytes)
|
||||
}
|
||||
|
||||
function spaceWithUnits(bytes){
|
||||
if (bytes*1 !== bytes || bytes == 0 ) return "0 kB"
|
||||
var units = ['B',"kB", "MB", "GB", "TB"];
|
||||
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));
|
||||
|
||||
return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i]
|
||||
}
|
||||
|
||||
// width expected to be set by parent object
|
||||
implicitHeight : children[0].implicitHeight
|
||||
|
||||
enum ViewType{
|
||||
SmallView, LargeView
|
||||
}
|
||||
property var type : AccountDelegate.SmallView
|
||||
|
||||
RowLayout {
|
||||
spacing: root._spacing
|
||||
|
||||
anchors {
|
||||
top: root.top
|
||||
left: root.left
|
||||
right: root.rigth
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: avatar
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
|
||||
radius: ProtonStyle.avatar_radius
|
||||
|
||||
color: root.colorScheme.background_avatar
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
anchors.fill: parent
|
||||
text: root.user ? root.user.avatarText.toUpperCase(): ""
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
case AccountDelegate.LargeView: return Label.Title
|
||||
}
|
||||
}
|
||||
font.weight: Font.Normal
|
||||
color: "#FFFFFF"
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: account
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: root.width - (
|
||||
root._spacing + avatar.width
|
||||
)
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: root.user ? user.username : ""
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
case AccountDelegate.LargeView: return Label.Title
|
||||
}
|
||||
}
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0 }
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.user && root.user.loggedIn ? root.usedSpace : qsTr("Signed out")
|
||||
color: root.usedSpaceColor
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
case AccountDelegate.LargeView: return Label.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.user && root.user.loggedIn ? " / " + root.totalSpace : ""
|
||||
color: root.colorScheme.text_weak
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
case AccountDelegate.LargeView: return Label.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: storage_bar
|
||||
visible: root.user ? root.type == AccountDelegate.LargeView : false
|
||||
width: 140 * ProtonStyle.px
|
||||
height: 4 * ProtonStyle.px
|
||||
radius: ProtonStyle.storage_bar_radius
|
||||
color: root.colorScheme.border_weak
|
||||
|
||||
Rectangle {
|
||||
id: storage_bar_filled
|
||||
radius: ProtonStyle.storage_bar_radius
|
||||
color: root.usedSpaceColor
|
||||
visible: root.user ? parent.visible && root.user.loggedIn : false
|
||||
anchors {
|
||||
top : parent.top
|
||||
bottom : parent.bottom
|
||||
left : parent.left
|
||||
}
|
||||
width: Math.min(1,Math.max(0.02,root.usedFraction)) * parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
251
internal/frontend/qt6/qml/AccountView.qml
Normal file
@ -0,0 +1,251 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
property var notifications
|
||||
property var user
|
||||
|
||||
signal showSignIn()
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
property int _leftMargin: 64
|
||||
property int _rightMargin: 64
|
||||
property int _topMargin: 32
|
||||
property int _detailsTopMargin: 25
|
||||
property int _bottomMargin: 12
|
||||
property int _spacing: 20
|
||||
property int _lineWidth: 1
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
clip: true
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
// can't use parent here because parent is not ScrollView (Flickable inside contentItem inside ScrollView)
|
||||
width: scrollView.availableWidth
|
||||
height: scrollView.availableHeight
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
// do not set implicitWidth because implicit width of ColumnLayout will be equal to maximum implicit width of
|
||||
// internal items. And if one of internal items would be a Text or Label - implicit width of those is always
|
||||
// equal to non-wrapped text (i.e. one line only). That will lead to enabling horizontal scroll when not needed
|
||||
implicitWidth: width
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
id: topRectangle
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: root._spacing
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root._leftMargin
|
||||
anchors.rightMargin: root._rightMargin
|
||||
anchors.topMargin: root._topMargin
|
||||
anchors.bottomMargin: root._bottomMargin
|
||||
|
||||
RowLayout { // account delegate with action buttons
|
||||
Layout.fillWidth: true
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
user: root.user
|
||||
type: AccountDelegate.LargeView
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign out")
|
||||
secondary: true
|
||||
visible: root.user ? root.user.loggedIn : false
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.user.logout()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign in")
|
||||
secondary: true
|
||||
visible: root.user ? !root.user.loggedIn : false
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSignIn()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
icon.source: "icons/ic-trash.svg"
|
||||
secondary: true
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.notifications.askDeleteAccount(root.user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: root._lineWidth
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Email clients")
|
||||
actionText: qsTr("Configure")
|
||||
description: qsTr("Using the mailbox details below (re)configure your client.")
|
||||
type: SettingsItem.Button
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
visible: root.user ? !root.user.splitMode || root.user.addresses.length==1 : false
|
||||
showSeparator: splitMode.visible
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSetupGuide(root.user, user.addresses[0])
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: splitMode
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Split addresses")
|
||||
description: qsTr("Setup multiple email addresses individually.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.user ? root.user.splitMode : false
|
||||
visible: root.user ? root.user.addresses.length > 1 : false
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
showSeparator: addressSelector.visible
|
||||
onClicked: {
|
||||
if (!splitMode.checked){
|
||||
root.notifications.askEnableSplitMode(user)
|
||||
} else {
|
||||
addressSelector.currentIndex = 0
|
||||
root.user.toggleSplitMode(!splitMode.checked)
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
visible: root.user ? root.user.splitMode : false
|
||||
|
||||
ComboBox {
|
||||
id: addressSelector
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
model: root.user ? root.user.addresses : null
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Configure")
|
||||
secondary: true
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSetupGuide(root.user, addressSelector.displayText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: root.colorScheme.background_weak
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
id: configuration
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root._leftMargin
|
||||
anchors.rightMargin: root._rightMargin
|
||||
anchors.topMargin: root._detailsTopMargin
|
||||
anchors.bottomMargin: root._spacing
|
||||
|
||||
spacing: root._spacing
|
||||
visible: root.user ? root.user.loggedIn : false
|
||||
|
||||
property string currentAddress: addressSelector.displayText
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Mailbox details")
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("IMAP")
|
||||
hostname: root.backend.hostname
|
||||
port: root.backend.portIMAP.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user ? root.user.password : ""
|
||||
security: "STARTTLS"
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("SMTP")
|
||||
hostname : root.backend.hostname
|
||||
port : root.backend.portSMTP.toString()
|
||||
username : configuration.currentAddress
|
||||
password : root.user ? root.user.password : ""
|
||||
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
234
internal/frontend/qt6/qml/Banner.qml
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property Notification notification
|
||||
property var mainWindow
|
||||
|
||||
topMargin: 37
|
||||
leftMargin: (mainWindow.width - root.implicitWidth)/2
|
||||
|
||||
implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin
|
||||
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||
|
||||
popupType: ApplicationWindow.PopupType.Banner
|
||||
|
||||
shouldShow: notification ? (notification.active && !notification.dismissed) : false
|
||||
|
||||
modal: false
|
||||
|
||||
Action {
|
||||
id: defaultDismissAction
|
||||
|
||||
text: qsTr("OK")
|
||||
onTriggered: {
|
||||
if (!root.notification) {
|
||||
return
|
||||
}
|
||||
|
||||
root.notification.dismissed = true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
clip: true
|
||||
implicitHeight: children[1].implicitHeight + children[1].anchors.topMargin + children[1].anchors.bottomMargin
|
||||
implicitWidth: children[1].implicitWidth + children[1].anchors.leftMargin + children[1].anchors.rightMargin
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: parent.width + 10
|
||||
radius: ProtonStyle.banner_radius
|
||||
color: {
|
||||
if (!root.notification) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.topMargin: 14
|
||||
anchors.bottomMargin: 14
|
||||
anchors.leftMargin: 16
|
||||
|
||||
spacing: 8
|
||||
|
||||
ColorImage {
|
||||
color: root.colorScheme.text_invert
|
||||
width: 24
|
||||
height: 24
|
||||
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
|
||||
source: {
|
||||
if (!root.notification) {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
return "./icons/ic-info-circle-filled.svg"
|
||||
case Notification.NotificationType.Success:
|
||||
return "./icons/ic-info-circle-filled.svg"
|
||||
case Notification.NotificationType.Warning:
|
||||
return "./icons/ic-exclamation-circle-filled.svg"
|
||||
case Notification.NotificationType.Danger:
|
||||
return "./icons/ic-exclamation-circle-filled.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: 16
|
||||
|
||||
color: root.colorScheme.text_invert
|
||||
text: root.notification ? root.notification.description : ""
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
width: 1
|
||||
color: {
|
||||
if (!root.notification) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info_active
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success_active
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning_active
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger_active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillHeight: true
|
||||
|
||||
id: actionButton
|
||||
|
||||
action: (root.notification && root.notification.action.length > 0) ? root.notification.action[0] : defaultDismissAction
|
||||
|
||||
background: Item {
|
||||
clip: true
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: parent.width + 10
|
||||
radius: ProtonStyle.banner_radius
|
||||
color: {
|
||||
if (!root.notification) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
var norm
|
||||
var hover
|
||||
var active
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
norm = root.colorScheme.signal_info
|
||||
hover = root.colorScheme.signal_info_hover
|
||||
active = root.colorScheme.signal_info_active
|
||||
break;
|
||||
case Notification.NotificationType.Success:
|
||||
norm = root.colorScheme.signal_success
|
||||
hover = root.colorScheme.signal_success_hover
|
||||
active = root.colorScheme.signal_success_active
|
||||
break;
|
||||
case Notification.NotificationType.Warning:
|
||||
norm = root.colorScheme.signal_warning
|
||||
hover = root.colorScheme.signal_warning_hover
|
||||
active = root.colorScheme.signal_warning_active
|
||||
break;
|
||||
case Notification.NotificationType.Danger:
|
||||
norm = root.colorScheme.signal_danger
|
||||
hover = root.colorScheme.signal_danger_hover
|
||||
active = root.colorScheme.signal_danger_active
|
||||
break;
|
||||
}
|
||||
|
||||
if (actionButton.down) {
|
||||
return active
|
||||
}
|
||||
|
||||
if (actionButton.enabled && (actionButton.highlighted || actionButton.hovered || actionButton.checked)) {
|
||||
return hover
|
||||
}
|
||||
|
||||
if (actionButton.loading) {
|
||||
return hover
|
||||
}
|
||||
|
||||
return norm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
294
internal/frontend/qt6/qml/Bridge.qml
Normal file
@ -0,0 +1,294 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Window 2.13
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
function isInInterval(num, lower_limit, upper_limit) {
|
||||
return lower_limit <= num && num <= upper_limit
|
||||
}
|
||||
function bound(num, lower_limit, upper_limit) {
|
||||
return Math.max(lower_limit, Math.min(upper_limit, num))
|
||||
}
|
||||
|
||||
property var backend
|
||||
property var title: "Proton Mail Bridge"
|
||||
|
||||
property Notifications _notifications: Notifications {
|
||||
id: notifications
|
||||
backend: root.backend
|
||||
frontendMain: mainWindow
|
||||
frontendStatus: statusWindow
|
||||
frontendTray: trayIcon
|
||||
}
|
||||
|
||||
property MainWindow _mainWindow: MainWindow {
|
||||
id: mainWindow
|
||||
visible: false
|
||||
|
||||
title: root.title
|
||||
backend: root.backend
|
||||
notifications: root._notifications
|
||||
|
||||
onVisibleChanged: {
|
||||
backend.dockIconVisible = visible
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onCacheUnavailable: {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
onColorSchemeNameChanged: root.setColorScheme()
|
||||
}
|
||||
}
|
||||
|
||||
property StatusWindow _statusWindow: StatusWindow {
|
||||
id: statusWindow
|
||||
visible: false
|
||||
|
||||
title: root.title
|
||||
backend: root.backend
|
||||
notifications: root._notifications
|
||||
|
||||
onShowMainWindow: {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onShowHelp: {
|
||||
mainWindow.showHelp()
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onShowSettings: {
|
||||
mainWindow.showSettings()
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onShowSignIn: {
|
||||
mainWindow.showSignIn(username)
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onQuit: {
|
||||
backend.quit()
|
||||
}
|
||||
|
||||
property rect screenRect
|
||||
property rect iconRect
|
||||
|
||||
// use binding from function with width and height as arguments so it will be recalculated every time width and height are changed
|
||||
property point position: getPosition(width, height)
|
||||
x: position.x
|
||||
y: position.y
|
||||
|
||||
function getPosition(_width, _height) {
|
||||
if (screenRect.width === 0 || screenRect.height === 0) {
|
||||
return Qt.point(0, 0)
|
||||
}
|
||||
|
||||
var _x = 0
|
||||
var _y = 0
|
||||
|
||||
// fit above
|
||||
_y = iconRect.top - height
|
||||
if (isInInterval(_y, screenRect.top, screenRect.bottom - height)) {
|
||||
// position preferebly in the horizontal center but bound to the screen rect
|
||||
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// fit below
|
||||
_y = iconRect.bottom
|
||||
if (isInInterval(_y, screenRect.top, screenRect.bottom - height)) {
|
||||
// position preferebly in the horizontal center but bound to the screen rect
|
||||
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// fit to the left
|
||||
_x = iconRect.left - width
|
||||
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
|
||||
// position preferebly in the vertical center but bound to the screen rect
|
||||
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// fit to the right
|
||||
_x = iconRect.right
|
||||
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
|
||||
// position preferebly in the vertical center but bound to the screen rect
|
||||
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// Fallback: position satatus window right above icon and let window manager decide.
|
||||
console.warn("Can't position status window: screenRect =", screenRect, "iconRect =", iconRect)
|
||||
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
|
||||
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
}
|
||||
|
||||
property SystemTrayIcon _trayIcon: SystemTrayIcon {
|
||||
id: trayIcon
|
||||
visible: true
|
||||
icon.source: getTrayIconPath()
|
||||
icon.mask: true // make sure that systems like macOS will use proper color
|
||||
tooltip: `${root.title} v${backend.version}`
|
||||
onActivated: {
|
||||
function calcStatusWindowPosition() {
|
||||
// On some platforms (X11 / Plasma) Qt does not provide icon position and geometry info.
|
||||
// In this case we rely on cursor position
|
||||
var iconRect = Qt.rect(geometry.x, geometry.y, geometry.width, geometry.height)
|
||||
if (geometry.width == 0 && geometry.height == 0) {
|
||||
var mousePos = backend.getCursorPos()
|
||||
iconRect.x = mousePos.x
|
||||
iconRect.y = mousePos.y
|
||||
iconRect.width = 0
|
||||
iconRect.height = 0
|
||||
}
|
||||
|
||||
// Find screen
|
||||
var screen
|
||||
for (var i in Qt.application.screens) {
|
||||
var _screen = Qt.application.screens[i]
|
||||
if (
|
||||
isInInterval(iconRect.x, _screen.virtualX, _screen.virtualX + _screen.width) &&
|
||||
isInInterval(iconRect.y, _screen.virtualY, _screen.virtualY + _screen.height)
|
||||
) {
|
||||
screen = _screen
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!screen) {
|
||||
// Fallback to primary screen
|
||||
screen = Qt.application.screens[0]
|
||||
}
|
||||
|
||||
// In case we used mouse to detect icon position - we want to make a fake icon rectangle from a point
|
||||
if (iconRect.width == 0 && iconRect.height == 0) {
|
||||
iconRect.x = bound(iconRect.x - 16, screen.virtualX, screen.virtualX + screen.width - 32)
|
||||
iconRect.y = bound(iconRect.y - 16, screen.virtualY, screen.virtualY + screen.height - 32)
|
||||
iconRect.width = 32
|
||||
iconRect.height = 32
|
||||
}
|
||||
|
||||
statusWindow.screenRect = Qt.rect(screen.virtualX, screen.virtualY, screen.width, screen.height)
|
||||
statusWindow.iconRect = iconRect
|
||||
}
|
||||
|
||||
function toggleWindow(win) {
|
||||
if (win.visible) {
|
||||
win.close()
|
||||
} else {
|
||||
win.showAndRise()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (reason) {
|
||||
case SystemTrayIcon.Unknown:
|
||||
break;
|
||||
case SystemTrayIcon.Context:
|
||||
case SystemTrayIcon.Trigger:
|
||||
case SystemTrayIcon.DoubleClick:
|
||||
case SystemTrayIcon.MiddleClick:
|
||||
calcStatusWindowPosition()
|
||||
toggleWindow(statusWindow)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
property NotificationFilter _systrayfilter: NotificationFilter {
|
||||
source: root._notifications ? root._notifications.all : undefined
|
||||
}
|
||||
|
||||
function getTrayIconPath() {
|
||||
var color = backend.goos == "darwin" ? "mono" : "color"
|
||||
|
||||
var level = "norm"
|
||||
if (_systrayfilter.topmost) {
|
||||
switch (_systrayfilter.topmost.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
level = "error"
|
||||
break;
|
||||
case Notification.NotificationType.Warning:
|
||||
level = "warn"
|
||||
break;
|
||||
case Notification.NotificationType.Info:
|
||||
level = "update"
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return `./icons/systray-${color}-${level}.png`
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!root.backend) {
|
||||
console.log("backend not loaded")
|
||||
}
|
||||
|
||||
root.setColorScheme()
|
||||
|
||||
|
||||
if (!root.backend.users) {
|
||||
console.log("users not loaded")
|
||||
}
|
||||
|
||||
var c = root.backend.users.count
|
||||
var u = root.backend.users.get(0)
|
||||
// DEBUG
|
||||
if (c != 0) {
|
||||
console.log("users non zero", c)
|
||||
console.log("first user", u )
|
||||
}
|
||||
|
||||
if (c === 0) {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
if (u) {
|
||||
if (c === 1 && u.loggedIn === false) {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
}
|
||||
|
||||
if (root.backend.showOnStartup) {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
root.backend.guiReady()
|
||||
}
|
||||
|
||||
function setColorScheme() {
|
||||
if (root.backend.colorSchemeName == "light") ProtonStyle.currentStyle = ProtonStyle.lightStyle
|
||||
if (root.backend.colorSchemeName == "dark") ProtonStyle.currentStyle = ProtonStyle.darkStyle
|
||||
}
|
||||
}
|
||||
316
internal/frontend/qt6/qml/BridgeTest/UserControl.qml
Normal file
@ -0,0 +1,316 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var user
|
||||
property var userIndex
|
||||
property var backend
|
||||
|
||||
spacing : 5
|
||||
|
||||
Layout.fillHeight: true
|
||||
//Layout.fillWidth: true
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: user !== undefined ? user.username : ""
|
||||
|
||||
onEditingFinished: {
|
||||
user.username = text
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Switch {
|
||||
id: userLoginSwitch
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "LoggedIn"
|
||||
enabled: user !== undefined && user.username.length > 0
|
||||
|
||||
checked: user ? user.loggedIn : false
|
||||
|
||||
onCheckedChanged: {
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
if (user === backend.loginUser) {
|
||||
var newUserObject = backend.userComponent.createObject(backend, {username: user.username, loggedIn: true, setupGuideSeen: user.setupGuideSeen})
|
||||
backend.users.append( { object: newUserObject } )
|
||||
|
||||
user.username = ""
|
||||
user.resetLoginRequests()
|
||||
return
|
||||
}
|
||||
|
||||
user.loggedIn = true
|
||||
user.resetLoginRequests()
|
||||
return
|
||||
} else {
|
||||
user.loggedIn = false
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Switch {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Setup guide seen"
|
||||
enabled: user !== undefined && user.username.length > 0
|
||||
|
||||
checked: user ? user.setupGuideSeen : false
|
||||
|
||||
onCheckedChanged: {
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
|
||||
user.setupGuideSeen = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: loginLabel
|
||||
text: "Login:"
|
||||
|
||||
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "name/pass error"
|
||||
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided
|
||||
|
||||
onClicked: {
|
||||
root.backend.loginUsernamePasswordError("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "free user error"
|
||||
enabled: user !== undefined //&& user.isLoginRequested
|
||||
onClicked: {
|
||||
root.backend.loginFreeUserError()
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "connection error"
|
||||
enabled: user !== undefined //&& user.isLoginRequested
|
||||
onClicked: {
|
||||
root.backend.loginConnectionError("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: faLabel
|
||||
text: "2FA:"
|
||||
|
||||
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "request"
|
||||
|
||||
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested
|
||||
onClicked: {
|
||||
root.backend.login2FARequested(user.username)
|
||||
user.isLogin2FARequested = true
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "error"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
onClicked: {
|
||||
root.backend.login2FAError("")
|
||||
user.isLogin2FAProvided = false
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Abort"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
onClicked: {
|
||||
root.backend.login2FAErrorAbort("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: passLabel
|
||||
text: "2 Password:"
|
||||
|
||||
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "request"
|
||||
|
||||
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2PasswordRequested && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
root.backend.login2PasswordRequested("")
|
||||
user.isLogin2PasswordRequested = true
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "error"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
root.backend.login2PasswordError("")
|
||||
|
||||
user.isLogin2PasswordProvided = false
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Abort"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
root.backend.login2PasswordErrorAbort("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Login Finished"
|
||||
|
||||
onClicked: {
|
||||
root.backend.loginFinished(0+loginFinishedIndex.text)
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: loginFinishedIndex
|
||||
colorScheme: root.colorScheme
|
||||
label: "Index:"
|
||||
text: root.userIndex
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Already logged in"
|
||||
|
||||
onClicked: {
|
||||
root.backend.loginAlreadyLoggedIn(0+loginAlreadyLoggedInIndex.text)
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: loginAlreadyLoggedInIndex
|
||||
colorScheme: root.colorScheme
|
||||
label: "Index:"
|
||||
text: root.userIndex
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
label: "used:"
|
||||
text: user && user.usedBytes ? user.usedBytes : 0
|
||||
onEditingFinished: {
|
||||
user.usedBytes = parseFloat(text)
|
||||
}
|
||||
implicitWidth: 200
|
||||
}
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
label: "total:"
|
||||
text: user && user.totalBytes ? user.totalBytes : 0
|
||||
onEditingFinished: {
|
||||
user.totalBytes = parseFloat(text)
|
||||
}
|
||||
implicitWidth: 200
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Split mode"}
|
||||
Toggle { colorScheme: root.colorScheme; checked: user ? user.splitMode : false; onClicked: {user.splitMode = !user.splitMode}}
|
||||
Button { colorScheme: root.colorScheme; text: "Toggle Finished"; onClicked: {user.toggleSplitModeFinished()}}
|
||||
}
|
||||
|
||||
TextArea { // TODO: this is causing binding loop on imlicitWidth
|
||||
colorScheme: root.colorScheme
|
||||
text: user && user.addresses ? user.addresses.join("\n") : "user@protonmail.com"
|
||||
Layout.fillWidth: true
|
||||
|
||||
onEditingFinished: {
|
||||
user.addresses = text.split("\n")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
102
internal/frontend/qt6/qml/BridgeTest/UserList.qml
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
|
||||
property alias currentIndex: usersListView.currentIndex
|
||||
ListView {
|
||||
id: usersListView
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 200
|
||||
|
||||
model: backend.usersTest
|
||||
highlightFollowsCurrentItem: true
|
||||
|
||||
delegate: Item {
|
||||
|
||||
implicitHeight: children[0].implicitHeight + anchors.topMargin + anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + anchors.leftMargin + anchors.rightMargin
|
||||
|
||||
width: usersListView.width
|
||||
|
||||
anchors.margins: 10
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: modelData.username
|
||||
anchors.margins: 10
|
||||
anchors.fill: parent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
usersListView.currentIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlight: Rectangle {
|
||||
color: root.colorScheme.interaction_default_active
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "+"
|
||||
|
||||
onClicked: {
|
||||
var newUserObject = backend.userComponent.createObject(backend)
|
||||
newUserObject.username = backend.loginUser.username.length > 0 ? backend.loginUser.username : "test@protonmail.com"
|
||||
newUserObject.loggedIn = true
|
||||
newUserObject.setupGuideSeen = true // backend.loginUser.setupGuideSeen
|
||||
|
||||
backend.loginUser.username = ""
|
||||
backend.loginUser.loggedIn = false
|
||||
backend.loginUser.setupGuideSeen = false
|
||||
|
||||
backend.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "-"
|
||||
|
||||
enabled: usersListView.currentIndex != 0
|
||||
|
||||
onClicked: {
|
||||
// var userObject = backend.users.get(usersListView.currentIndex - 1)
|
||||
backend.users.remove(usersListView.currentIndex - 1)
|
||||
// userObject.deleteLater()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
internal/frontend/qt6/qml/BridgeTest/UserModel.qml
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml.Models 2.12
|
||||
|
||||
ListModel {
|
||||
// overriding get method to ignore any role and return directly object itself
|
||||
function get(row) {
|
||||
if (row < 0 || row >= count) {
|
||||
return undefined
|
||||
}
|
||||
return data(index(row, 0), Qt.DisplayRole)
|
||||
}
|
||||
}
|
||||
982
internal/frontend/qt6/qml/Bridge_test.qml
Normal file
@ -0,0 +1,982 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import QtQml.Models 2.12
|
||||
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
import "./BridgeTest"
|
||||
import BridgePreview 1.0
|
||||
|
||||
import Notifications 1.0
|
||||
|
||||
Window {
|
||||
id: root
|
||||
|
||||
x: 10
|
||||
y: 10
|
||||
width: 800
|
||||
height: 800
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.darkStyle
|
||||
|
||||
flags : Qt.Window | Qt.Dialog
|
||||
visible : true
|
||||
title : "Bridge Test GUI"
|
||||
|
||||
// This is needed because on MacOS if first window shown is not transparent -
|
||||
// all other windows of application will not have transparent background (black
|
||||
// instead of transparency). In our case that mean that if BridgeTest will be
|
||||
// shown before StatusWindow - StatusWindow will not have transparent corners.
|
||||
color: "transparent"
|
||||
|
||||
function getCursorPos() {
|
||||
return BridgePreview.getCursorPos()
|
||||
}
|
||||
|
||||
function restart() {
|
||||
root.quit()
|
||||
console.log("Restarting....")
|
||||
root.openBridge()
|
||||
}
|
||||
|
||||
function openBridge() {
|
||||
bridge = bridgeComponent.createObject()
|
||||
var showSetupGuide = false
|
||||
if (showSetupGuide) {
|
||||
var newUserObject = root.userComponent.createObject(root)
|
||||
newUserObject.username = "LerooooyJenkins@protonmail.com"
|
||||
newUserObject.loggedIn = true
|
||||
newUserObject.setupGuideSeen = false
|
||||
root.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function quit() {
|
||||
if (bridge !== undefined && bridge !== null) {
|
||||
bridge.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function guiReady() {
|
||||
console.log("Gui Ready")
|
||||
}
|
||||
|
||||
function _log(msg, color) {
|
||||
logTextArea.text += "<p style='color: " + color + ";'>" + msg + "</p>"
|
||||
logTextArea.text += "\n"
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
console.log(msg)
|
||||
_log(msg, root.colorScheme.signal_info)
|
||||
}
|
||||
|
||||
function error(msg) {
|
||||
console.error(msg)
|
||||
_log(msg, root.colorScheme.signal_danger)
|
||||
}
|
||||
|
||||
// No user object should be put in this list until a successful login
|
||||
property var users: UserModel {
|
||||
id: _users
|
||||
|
||||
onRowsInserted: {
|
||||
for (var i = first; i <= last; i++) {
|
||||
_usersTest.insert(i + 1, { object: get(i) } )
|
||||
}
|
||||
}
|
||||
|
||||
onRowsRemoved: {
|
||||
_usersTest.remove(first + 1, first - last + 1)
|
||||
}
|
||||
|
||||
onRowsMoved: {
|
||||
_usersTest.move(start + 1, row + 1, end - start + 1)
|
||||
}
|
||||
|
||||
onDataChanged: {
|
||||
for (var i = topLeft.row; i <= bottomRight.row; i++) {
|
||||
_usersTest.set(i + 1, { object: get(i) } )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this list is used on test gui: it contains same users list as users above + fake user to represent login request of new user on pos 0
|
||||
property var usersTest: UserModel {
|
||||
id: _usersTest
|
||||
}
|
||||
|
||||
property var userComponent: Component {
|
||||
id: _userComponent
|
||||
|
||||
QtObject {
|
||||
property string username: ""
|
||||
property bool loggedIn: false
|
||||
property bool splitMode: false
|
||||
|
||||
property bool setupGuideSeen: true
|
||||
|
||||
property var usedBytes: 5350*1024*1024
|
||||
property var totalBytes: 20*1024*1024*1024
|
||||
property string avatarText: "jd"
|
||||
|
||||
property string password: "SMj975NnEYYsqu55GGmlpv"
|
||||
property var addresses: [
|
||||
"jaanedoe@protonmail.com",
|
||||
"jane@pm.me",
|
||||
"jdoe@pm.me"
|
||||
]
|
||||
|
||||
signal loginUsernamePasswordError()
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError()
|
||||
signal login2FARequested()
|
||||
signal login2FAError()
|
||||
signal login2FAErrorAbort()
|
||||
signal login2PasswordRequested()
|
||||
signal login2PasswordError()
|
||||
signal login2PasswordErrorAbort()
|
||||
|
||||
// Test purpose only:
|
||||
property bool isFakeUser: this === root.loginUser
|
||||
|
||||
function userSignal(msg) {
|
||||
if (isFakeUser) {
|
||||
return
|
||||
}
|
||||
|
||||
root.log("<- User (" + username + "): " + msg)
|
||||
}
|
||||
|
||||
function toggleSplitMode(makeActive) {
|
||||
userSignal("toggle split mode "+makeActive)
|
||||
}
|
||||
signal toggleSplitModeFinished()
|
||||
|
||||
function configureAppleMail(address){
|
||||
userSignal("confugure apple mail "+address)
|
||||
}
|
||||
|
||||
function logout(){
|
||||
userSignal("logout")
|
||||
loggedIn = false
|
||||
}
|
||||
function remove(){
|
||||
console.log("remove this", users.count)
|
||||
for (var i=0; i<users.count; i++) {
|
||||
if (users.get(i) === this) {
|
||||
users.remove(i,1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
userSignal("loginUsernamePasswordError")
|
||||
}
|
||||
onLoginFreeUserError: {
|
||||
userSignal("loginFreeUserError")
|
||||
}
|
||||
onLoginConnectionError: {
|
||||
userSignal("loginConnectionError")
|
||||
}
|
||||
onLogin2FARequested: {
|
||||
userSignal("login2FARequested")
|
||||
}
|
||||
onLogin2FAError: {
|
||||
userSignal("login2FAError")
|
||||
}
|
||||
onLogin2FAErrorAbort: {
|
||||
userSignal("login2FAErrorAbort")
|
||||
}
|
||||
onLogin2PasswordRequested: {
|
||||
userSignal("login2PasswordRequested")
|
||||
}
|
||||
onLogin2PasswordError: {
|
||||
userSignal("login2PasswordError")
|
||||
}
|
||||
onLogin2PasswordErrorAbort: {
|
||||
userSignal("login2PasswordErrorAbort")
|
||||
}
|
||||
|
||||
function resetLoginRequests() {
|
||||
isLoginRequested = false
|
||||
isLogin2FARequested = false
|
||||
isLogin2FAProvided = false
|
||||
isLogin2PasswordRequested = false
|
||||
isLogin2PasswordProvided = false
|
||||
}
|
||||
|
||||
property bool isLoginRequested: false
|
||||
|
||||
property bool isLogin2FARequested: false
|
||||
property bool isLogin2FAProvided: false
|
||||
|
||||
property bool isLogin2PasswordRequested: false
|
||||
property bool isLogin2PasswordProvided: false
|
||||
}
|
||||
}
|
||||
|
||||
// this it fake user used only for representing first login request
|
||||
property var loginUser
|
||||
Component.onCompleted: {
|
||||
var newLoginUser = _userComponent.createObject()
|
||||
root.loginUser = newLoginUser
|
||||
root.loginUser.setupGuideSeen = false
|
||||
_usersTest.append({object: newLoginUser})
|
||||
|
||||
newLoginUser.loginUsernamePasswordError.connect(root.loginUsernamePasswordError)
|
||||
newLoginUser.loginFreeUserError.connect(root.loginFreeUserError)
|
||||
newLoginUser.loginConnectionError.connect(root.loginConnectionError)
|
||||
newLoginUser.login2FARequested.connect(root.login2FARequested)
|
||||
newLoginUser.login2FAError.connect(root.login2FAError)
|
||||
newLoginUser.login2FAErrorAbort.connect(root.login2FAErrorAbort)
|
||||
newLoginUser.login2PasswordRequested.connect(root.login2PasswordRequested)
|
||||
newLoginUser.login2PasswordError.connect(root.login2PasswordError)
|
||||
newLoginUser.login2PasswordErrorAbort.connect(root.login2PasswordErrorAbort)
|
||||
|
||||
|
||||
// add one user on start
|
||||
var hasUserOnStart = false
|
||||
if (hasUserOnStart) {
|
||||
var newUserObject = root.userComponent.createObject(root)
|
||||
newUserObject.username = "LerooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooyJenkins@protonmail.com"
|
||||
newUserObject.loggedIn = true
|
||||
newUserObject.setupGuideSeen = true
|
||||
root.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TabBar {
|
||||
id: tabBar
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
TabButton {
|
||||
text: "Global settings"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "User control"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Notifications"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Log"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Settings signals"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
anchors.top: tabBar.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
implicitHeight: children[0].contentHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].contentWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
currentIndex: tabBar.currentIndex
|
||||
anchors.margins: 10
|
||||
|
||||
RowLayout {
|
||||
id: globalTab
|
||||
spacing : 5
|
||||
|
||||
ColumnLayout {
|
||||
spacing : 5
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Global settings"
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: styleRadioGroup
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: "Light UI"
|
||||
checked: ProtonStyle.currentStyle === ProtonStyle.lightStyle
|
||||
ButtonGroup.group: styleRadioGroup
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked && ProtonStyle.currentStyle !== ProtonStyle.lightStyle) {
|
||||
root.colorSchemeName = "light"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: "Dark UI"
|
||||
checked: ProtonStyle.currentStyle === ProtonStyle.darkStyle
|
||||
ButtonGroup.group: styleRadioGroup
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked && ProtonStyle.currentStyle !== ProtonStyle.darkStyle) {
|
||||
root.colorSchemeName = "dark"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showOnStartupCheckbox
|
||||
colorScheme: root.colorScheme
|
||||
text: "Show on startup"
|
||||
checked: root.showOnStartup
|
||||
onCheckedChanged: {
|
||||
root.showOnStartup = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showSplashScreen
|
||||
colorScheme: root.colorScheme
|
||||
text: "Show splash screen"
|
||||
checked: root.showSplashScreen
|
||||
onCheckedChanged: {
|
||||
root.showSplashScreen = checked
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
//Layout.fillWidth: true
|
||||
|
||||
text: "Open Bridge"
|
||||
enabled: bridge === undefined || bridge === null
|
||||
onClicked: root.openBridge()
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
//Layout.fillWidth: true
|
||||
|
||||
text: "Close Bridge"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing : 5
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notifications"
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: danger"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyOnlyPaidUsers()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: warning"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyUpdateManually()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: success"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyUserAdded()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: usersTab
|
||||
UserList {
|
||||
id: usersListView
|
||||
Layout.fillHeight: true
|
||||
colorScheme: root.colorScheme
|
||||
backend: root
|
||||
}
|
||||
|
||||
UserControl {
|
||||
colorScheme: root.colorScheme
|
||||
backend: root
|
||||
user: ((root.usersTest.count > usersListView.currentIndex) && usersListView.currentIndex != -1) ? root.usersTest.get(usersListView.currentIndex) : undefined
|
||||
userIndex: usersListView.currentIndex - 1 // -1 because 0 index is fake user
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: notificationsTab
|
||||
spacing: 5
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 5
|
||||
|
||||
Switch {
|
||||
text: "Internet connection"
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
onCheckedChanged: {
|
||||
checked ? root.internetOn() : root.internetOff()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual ready"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualReady("3.14.1592")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualRestartNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update force"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForce("3.14.1592")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update force error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForceError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update silent done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateSilentRestartNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update silent error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateSilentError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update is latest version"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateIsLatestVersion()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Bug report send OK"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendSuccess()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 5
|
||||
|
||||
Button {
|
||||
text: "Bug report send error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache anavailable"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheUnavailable()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache can't move"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheCantMove()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache location change success"
|
||||
onClicked: {
|
||||
root.cacheLocationChangeSuccess()
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Disk full"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.diskFull()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "No keychain"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.notifyHasNoKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Rebuild keychain"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.notifyRebuildKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Address changed"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.addressChanged("p@v.el")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Address changed + Logout"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.addressChangedLogout("p@v.el")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: logTextArea
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.preferredHeight: 200
|
||||
|
||||
textFormat: TextEdit.RichText
|
||||
//readOnly: true
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: settingsTab
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Label {colorScheme : root.colorScheme ; text : "GOOS : "}
|
||||
Button {colorScheme : root.colorScheme ; text : "Linux" ; onClicked : root.goos = "linux" ; enabled: root.goos != "linux"}
|
||||
Button {colorScheme : root.colorScheme ; text : "Windows" ; onClicked : root.goos = "windows" ; enabled: root.goos != "windows"}
|
||||
Button {colorScheme : root.colorScheme ; text : "macOS" ; onClicked : root.goos = "darwin" ; enabled: root.goos != "darwin"}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Automatic updates:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutomaticUpdateOn; onClicked: root.isAutomaticUpdateOn = !root.isAutomaticUpdateOn}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Autostart:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutostartOn; onClicked: root.isAutostartOn = !root.isAutostartOn}
|
||||
Button {colorScheme: root.colorScheme; text: "Toggle finished"; onClicked: root.toggleAutostartFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Beta:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isBetaEnabled; onClicked: root.isBetaEnabled = !root.isBetaEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "DoH:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDoHEnabled; onClicked: root.isDoHEnabled = !root.isDoHEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "All Mail disabled:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAllMailVisible; onClicked: root.isAllMailVisible = !root.isAllMailVisible}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Ports:"}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "IMAP"
|
||||
text: root.portIMAP
|
||||
onEditingFinished: root.portIMAP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "SMTP"
|
||||
text: root.portSMTP
|
||||
onEditingFinished: root.portSMTP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished"; onClicked: root.changePortFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "SMTP using SSL:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.useSSLforSMTP; onClicked: root.useSSLforSMTP = !root.useSSLforSMTP}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Local cache:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDiskCacheEnabled; onClicked: root.isDiskCacheEnabled = !root.isDiskCacheEnabled}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "Path"
|
||||
text: root.diskCachePath.toString().replace("file://", "")
|
||||
implicitWidth: 160
|
||||
onEditingFinished: {
|
||||
root.diskCachePath = Qt.resolvedUrl("file://"+text)
|
||||
}
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished"; onClicked: root.changeLocalCacheFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Reset:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.resetFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Check update:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.checkUpdatesFinished()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Bridge bridge
|
||||
|
||||
property string goos: "darwin"
|
||||
|
||||
property bool showOnStartup: true // this actually needs to be false, but since we use Bridge_test for testing purpose - lets default this to true just for convenience
|
||||
property bool dockIconVisible: false
|
||||
|
||||
// this signals are used only when trying to login with new user (i.e. not in users model)
|
||||
signal loginUsernamePasswordError(string errorMsg)
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError(string errorMsg)
|
||||
signal login2FARequested(string username)
|
||||
signal login2FAError(string errorMsg)
|
||||
signal login2FAErrorAbort(string errorMsg)
|
||||
signal login2PasswordRequested()
|
||||
signal login2PasswordError(string errorMsg)
|
||||
signal login2PasswordErrorAbort(string errorMsg)
|
||||
signal loginFinished(int index)
|
||||
signal loginAlreadyLoggedIn(int index)
|
||||
|
||||
signal internetOff()
|
||||
signal internetOn()
|
||||
|
||||
signal updateManualReady(var version)
|
||||
signal updateManualRestartNeeded()
|
||||
signal updateManualError()
|
||||
signal updateForce(var version)
|
||||
signal updateForceError()
|
||||
signal updateSilentRestartNeeded()
|
||||
signal updateSilentError()
|
||||
signal updateIsLatestVersion()
|
||||
function checkUpdates(){
|
||||
console.log("check updates")
|
||||
}
|
||||
signal checkUpdatesFinished()
|
||||
function installUpdate() {
|
||||
console.log("manuall install update triggered")
|
||||
}
|
||||
|
||||
|
||||
property bool isDiskCacheEnabled: true
|
||||
// Qt.resolvedUrl("file:///C:/Users/user/AppData/Roaming/protonmail/bridge/cache/c11/messages")
|
||||
property url diskCachePath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
signal cacheUnavailable()
|
||||
signal cacheCantMove()
|
||||
signal cacheLocationChangeSuccess()
|
||||
signal diskFull()
|
||||
function changeLocalCache(enableDiskCache, diskCachePath) {
|
||||
console.debug("-> disk cache", enableDiskCache, diskCachePath)
|
||||
}
|
||||
signal changeLocalCacheFinished()
|
||||
|
||||
|
||||
// Settings
|
||||
property bool isAutomaticUpdateOn : true
|
||||
function toggleAutomaticUpdate(makeItActive) {
|
||||
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
var callback = function () {
|
||||
root.isAutomaticUpdateOn = makeItActive;
|
||||
console.debug("-> CHANGED silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
}
|
||||
atimer.onTriggered.connect(callback)
|
||||
atimer.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: atimer
|
||||
interval: 2000
|
||||
running: false
|
||||
repeat: false
|
||||
}
|
||||
|
||||
property bool isAutostartOn : true // Example of settings with loading state
|
||||
function toggleAutostart(makeItActive) {
|
||||
console.debug("-> autostart", makeItActive, root.isAutostartOn)
|
||||
}
|
||||
signal toggleAutostartFinished()
|
||||
|
||||
property bool isBetaEnabled : false
|
||||
function toggleBeta(makeItActive){
|
||||
console.debug("-> beta", makeItActive, root.isBetaEnabled)
|
||||
root.isBetaEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool isDoHEnabled : true
|
||||
function toggleDoH(makeItActive){
|
||||
console.debug("-> DoH", makeItActive, root.isDoHEnabled)
|
||||
root.isDoHEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool isAllMailVisible : true
|
||||
function changeIsAllMailVisible(isVisible){
|
||||
console.debug("-> All Mail Visible", isVisible, root.isAllMailVisible)
|
||||
root.isAllMailVisible = isVisible
|
||||
}
|
||||
|
||||
|
||||
property bool useSSLforSMTP: false
|
||||
function toggleUseSSLforSMTP(makeItActive){
|
||||
console.debug("-> SMTP SSL", makeItActive, root.useSSLforSMTP)
|
||||
}
|
||||
signal toggleUseSSLFinished()
|
||||
|
||||
property string hostname: "127.0.0.1"
|
||||
property int portIMAP: 1143
|
||||
property int portSMTP: 1025
|
||||
function changePorts(imapPort, smtpPort){
|
||||
console.debug("-> ports", imapPort, smtpPort)
|
||||
}
|
||||
function isPortFree(port){
|
||||
if (port == portIMAP) return false
|
||||
if (port == portSMTP) return false
|
||||
if (port == 12345) return false
|
||||
return true
|
||||
}
|
||||
signal changePortFinished()
|
||||
signal portIssueIMAP()
|
||||
signal portIssueSMTP()
|
||||
|
||||
function triggerReset() {
|
||||
console.debug("-> trigger reset")
|
||||
}
|
||||
signal resetFinished()
|
||||
|
||||
property string version: "2.0.X-BridePreview"
|
||||
property url logsPath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
property url licensePath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
property url releaseNotesLink: Qt.resolvedUrl("https://protonmail.com/download/bridge/early_releases.html")
|
||||
property url dependencyLicensesLink: Qt.resolvedUrl("https://github.com/ProtonMail/proton-bridge/v2/blob/master/COPYING_NOTES.md#dependencies")
|
||||
property url landingPageLink: Qt.resolvedUrl("https://protonmail.com/bridge")
|
||||
|
||||
property string colorSchemeName: "light"
|
||||
function changeColorScheme(newScheme){
|
||||
root.colorSchemeName = newScheme
|
||||
}
|
||||
|
||||
|
||||
property string currentEmailClient: "" // "Apple Mail 14.0"
|
||||
function updateCurrentMailClient(){
|
||||
currentEmailClient = "Apple Mail 14.0"
|
||||
}
|
||||
|
||||
function reportBug(description,address,emailClient,includeLogs){
|
||||
console.log("report bug")
|
||||
console.log(" description",description)
|
||||
console.log(" address",address)
|
||||
console.log(" emailClient",emailClient)
|
||||
console.log(" includeLogs",includeLogs)
|
||||
}
|
||||
signal reportBugFinished()
|
||||
signal bugReportSendSuccess()
|
||||
signal bugReportSendError()
|
||||
|
||||
property var availableKeychain: ["gnome-keyring", "pass", "macos-keychain", "windows-credentials"]
|
||||
property string currentKeychain: availableKeychain[0]
|
||||
function changeKeychain(wantedKeychain){
|
||||
console.log("Changing keychain from", root.currentKeychain, "to", wantedKeychain)
|
||||
root.currentKeychain = wantedKeychain
|
||||
root.changeKeychainFinished()
|
||||
}
|
||||
signal changeKeychainFinished()
|
||||
signal notifyHasNoKeychain()
|
||||
signal notifyRebuildKeychain()
|
||||
|
||||
signal noActiveKeyForRecipient(string email)
|
||||
signal showMainWindow()
|
||||
|
||||
signal addressChanged(string address)
|
||||
signal addressChangedLogout(string address)
|
||||
signal userDisconnected(string username)
|
||||
signal apiCertIssue()
|
||||
|
||||
property bool showSplashScreen: false
|
||||
|
||||
|
||||
function login(username, password) {
|
||||
root.log("-> login(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.username = username
|
||||
loginUser.isLoginRequested = true
|
||||
}
|
||||
|
||||
function login2FA(username, code) {
|
||||
root.log("-> login2FA(" + username + ", " + code + ")")
|
||||
|
||||
loginUser.isLogin2FAProvided = true
|
||||
}
|
||||
|
||||
function login2Password(username, password) {
|
||||
root.log("-> login2FA(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.isLogin2PasswordProvided = true
|
||||
}
|
||||
|
||||
function loginAbort(username) {
|
||||
root.log("-> loginAbort(" + username + ")")
|
||||
|
||||
loginUser.resetLoginRequests()
|
||||
}
|
||||
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
console.debug("<- loginUsernamePasswordError")
|
||||
}
|
||||
onLoginFreeUserError: {
|
||||
console.debug("<- loginFreeUserError")
|
||||
}
|
||||
onLoginConnectionError: {
|
||||
console.debug("<- loginConnectionError")
|
||||
}
|
||||
onLogin2FARequested: {
|
||||
console.debug("<- login2FARequested", username)
|
||||
}
|
||||
onLogin2FAError: {
|
||||
console.debug("<- login2FAError")
|
||||
}
|
||||
onLogin2FAErrorAbort: {
|
||||
console.debug("<- login2FAErrorAbort")
|
||||
}
|
||||
onLogin2PasswordRequested: {
|
||||
console.debug("<- login2PasswordRequested")
|
||||
}
|
||||
onLogin2PasswordError: {
|
||||
console.debug("<- login2PasswordError")
|
||||
}
|
||||
onLogin2PasswordErrorAbort: {
|
||||
console.debug("<- login2PasswordErrorAbort")
|
||||
}
|
||||
onLoginFinished: {
|
||||
console.debug("<- loginFinished", index)
|
||||
}
|
||||
onLoginAlreadyLoggedIn: {
|
||||
console.debug("<- loginAlreadyLoggedIn", index)
|
||||
}
|
||||
|
||||
onInternetOff: {
|
||||
console.debug("<- internetOff")
|
||||
}
|
||||
onInternetOn: {
|
||||
console.debug("<- internetOn")
|
||||
}
|
||||
|
||||
Component {
|
||||
id: bridgeComponent
|
||||
|
||||
Bridge {
|
||||
backend: root
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onClosing: {
|
||||
Qt.quit()
|
||||
}
|
||||
}
|
||||
192
internal/frontend/qt6/qml/BugReportView.qml
Normal file
@ -0,0 +1,192 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
fillHeight: true
|
||||
|
||||
property var selectedAddress
|
||||
|
||||
Label {
|
||||
text: qsTr("Report a problem")
|
||||
colorScheme: root.colorScheme
|
||||
type: Label.Heading
|
||||
}
|
||||
|
||||
|
||||
TextArea {
|
||||
id: description
|
||||
property int _minLength: 150
|
||||
property int _maxLength: 800
|
||||
|
||||
label: qsTr("Description")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: heightForLinesVisible(4)
|
||||
hint: description.text.length + "/" + _maxLength
|
||||
placeholderText: qsTr("Tell us what went wrong or isn't working (min. %1 characters).").arg(_minLength)
|
||||
|
||||
validator: function(text) {
|
||||
if (description.text.length < description._minLength) {
|
||||
return qsTr("Enter a problem description (min. %1 characters).").arg(_minLength)
|
||||
}
|
||||
|
||||
if (description.text.length > description._maxLength) {
|
||||
return qsTr("Enter a problem description (max. %1 characters).").arg(_maxLength)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
// Rise max length error imidiatly while typing
|
||||
if (description.text.length > description._maxLength) {
|
||||
validate()
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.priority: KeyNavigation.BeforeItem
|
||||
KeyNavigation.tab: address
|
||||
|
||||
// set implicitHeight to explicit height because se don't
|
||||
// want TextArea implicitHeight (which is height of all text)
|
||||
// to be considered in SettingsView internal scroll view
|
||||
implicitHeight: height
|
||||
}
|
||||
|
||||
|
||||
TextField {
|
||||
id: address
|
||||
|
||||
label: qsTr("Your contact email")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. jane.doe@protonmail.com")
|
||||
|
||||
validator: function(str) {
|
||||
if (!isValidEmail(str)) {
|
||||
return qsTr("Enter valid email address")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: emailClient
|
||||
|
||||
label: qsTr("Your email client (including version)")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. Apple Mail 14.0")
|
||||
|
||||
validator: function(str) {
|
||||
if (str.length === 0) {
|
||||
return qsTr("Enter an email client name and version")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: includeLogs
|
||||
text: qsTr("Include my recent logs")
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
}
|
||||
Button {
|
||||
Layout.leftMargin: 12
|
||||
text: qsTr("View logs")
|
||||
secondary: true
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: Qt.openUrlExternally(root.backend.logsPath)
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
text: qsTr("Reports are not end-to-end encrypted, please do not send any sensitive information.")
|
||||
|
||||
readOnly: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: root.colorScheme.text_weak
|
||||
font.family: ProtonStyle.font_family
|
||||
font.weight: ProtonStyle.fontWeight_400
|
||||
font.pixelSize: ProtonStyle.caption_font_size
|
||||
font.letterSpacing: ProtonStyle.caption_letter_spacing
|
||||
// No way to set lineHeight: Style.caption_line_height
|
||||
selectionColor: root.colorScheme.interaction_norm
|
||||
selectedTextColor: root.colorScheme.text_invert
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
}
|
||||
|
||||
Button {
|
||||
id: sendButton
|
||||
text: qsTr("Send")
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
onClicked: {
|
||||
description.validate()
|
||||
address.validate()
|
||||
emailClient.validate()
|
||||
|
||||
if (description.error || address.error || emailClient.error) {
|
||||
return
|
||||
}
|
||||
|
||||
submit()
|
||||
}
|
||||
|
||||
Connections {target: root.backend; onReportBugFinished: sendButton.loading = false }
|
||||
}
|
||||
|
||||
function setDefaultValue() {
|
||||
description.text = ""
|
||||
address.text = root.selectedAddress
|
||||
emailClient.text = root.backend.currentEmailClient
|
||||
includeLogs.checked = true
|
||||
}
|
||||
|
||||
function isValidEmail(text){
|
||||
var reEmail = /^[^@]+@[^@]+\.[A-Za-z]+\s*$/
|
||||
return reEmail.test(text)
|
||||
}
|
||||
|
||||
function submit() {
|
||||
sendButton.loading = true
|
||||
root.backend.reportBug(
|
||||
description.text,
|
||||
address.text,
|
||||
emailClient.text,
|
||||
includeLogs.checked
|
||||
)
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
root.setDefaultValue()
|
||||
}
|
||||
}
|
||||
73
internal/frontend/qt6/qml/Configuration.qml
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property string title
|
||||
property string hostname
|
||||
property string port
|
||||
property string username
|
||||
property string password
|
||||
property string security
|
||||
|
||||
implicitWidth: 304
|
||||
implicitHeight: content.height + 2*root._margin
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
radius: ProtonStyle.card_radius
|
||||
|
||||
property int _margin: 24
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
width: root.width - 2*root._margin
|
||||
anchors{
|
||||
top: root.top
|
||||
left: root.left
|
||||
leftMargin : root._margin
|
||||
rightMargin : root._margin
|
||||
topMargin : root._margin
|
||||
bottomMargin : root._margin
|
||||
}
|
||||
|
||||
spacing: 12
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.title
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Item{}
|
||||
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Hostname") ; value: root.hostname }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Port") ; value: root.port }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Username") ; value: root.username }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Password") ; value: root.password }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Security") ; value: root.security }
|
||||
}
|
||||
}
|
||||
|
||||
89
internal/frontend/qt6/qml/ConfigurationItem.qml
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var colorScheme
|
||||
property string label
|
||||
property string value
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: root.width
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.label
|
||||
type: Label.Body
|
||||
}
|
||||
TextEdit {
|
||||
id: valueText
|
||||
text: root.value
|
||||
color: root.colorScheme.text_weak
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
selectionColor: root.colorScheme.text_weak
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
source: "icons/ic-copy.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
sourceSize.height: root.colorScheme.body_font_size
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked : {
|
||||
valueText.select(0, valueText.length)
|
||||
valueText.copy()
|
||||
valueText.deselect()
|
||||
}
|
||||
onPressed: parent.scale = 0.90
|
||||
onReleased: parent.scale = 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_norm
|
||||
}
|
||||
}
|
||||
}
|
||||
408
internal/frontend/qt6/qml/ContentWrapper.qml
Normal file
@ -0,0 +1,408 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
id: leftBar
|
||||
property ColorScheme colorScheme: root.colorScheme.prominent
|
||||
|
||||
Layout.minimumWidth: 264
|
||||
Layout.maximumWidth: 320
|
||||
Layout.preferredWidth: 320
|
||||
Layout.fillHeight: true
|
||||
|
||||
color: colorScheme.background_norm
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
id:topLeftBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 60
|
||||
Layout.maximumHeight: 60
|
||||
Layout.preferredHeight: 60
|
||||
spacing: 0
|
||||
|
||||
Status {
|
||||
Layout.leftMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 17
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
colorScheme: leftBar.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
notificationWhitelist: Notifications.Group.Connection | Notifications.Group.ForceUpdate
|
||||
}
|
||||
|
||||
// just a placeholder
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: leftBar.colorScheme
|
||||
Layout.minimumHeight: 36
|
||||
Layout.maximumHeight: 36
|
||||
Layout.preferredHeight: 36
|
||||
Layout.minimumWidth: 36
|
||||
Layout.maximumWidth: 36
|
||||
Layout.preferredWidth: 36
|
||||
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 9
|
||||
Layout.rightMargin: 4
|
||||
|
||||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-question-circle.svg"
|
||||
|
||||
onClicked: rightContent.showHelpView()
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: leftBar.colorScheme
|
||||
Layout.minimumHeight: 36
|
||||
Layout.maximumHeight: 36
|
||||
Layout.preferredHeight: 36
|
||||
Layout.minimumWidth: 36
|
||||
Layout.maximumWidth: 36
|
||||
Layout.preferredWidth: 36
|
||||
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 9
|
||||
Layout.rightMargin: 16
|
||||
|
||||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-cog-wheel.svg"
|
||||
|
||||
onClicked: rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
Item {implicitHeight:10}
|
||||
|
||||
// Separator line
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 1
|
||||
Layout.maximumHeight: 1
|
||||
color: leftBar.colorScheme.border_weak
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: accounts
|
||||
|
||||
property var _topBottomMargins: 24
|
||||
property var _leftRightMargins: 16
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: accounts._leftRightMargins
|
||||
Layout.rightMargin: accounts._leftRightMargins
|
||||
Layout.topMargin: accounts._topBottomMargins
|
||||
Layout.bottomMargin: accounts._topBottomMargins
|
||||
|
||||
spacing: 12
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
header: Rectangle {
|
||||
height: headerLabel.height+16
|
||||
// color: ProtonStyle.transparent
|
||||
Label{
|
||||
colorScheme: leftBar.colorScheme
|
||||
id: headerLabel
|
||||
text: qsTr("Accounts")
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
}
|
||||
|
||||
highlight: Rectangle {
|
||||
color: leftBar.colorScheme.interaction_default_active
|
||||
radius: ProtonStyle.account_row_radius
|
||||
}
|
||||
|
||||
model: root.backend.users
|
||||
delegate: Item {
|
||||
width: leftBar.width - 2*accounts._leftRightMargins
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
AccountDelegate {
|
||||
id: accountDelegate
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
|
||||
colorScheme: leftBar.colorScheme
|
||||
user: root.backend.users.get(index)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
var user = root.backend.users.get(index)
|
||||
accounts.currentIndex = index
|
||||
if (!user) return
|
||||
if (user.loggedIn) {
|
||||
rightContent.showAccount()
|
||||
} else {
|
||||
signIn.username = user.username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 1
|
||||
Layout.maximumHeight: 1
|
||||
color: leftBar.colorScheme.border_weak
|
||||
}
|
||||
|
||||
Item {
|
||||
id: bottomLeftBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 52
|
||||
Layout.maximumHeight: 52
|
||||
Layout.preferredHeight: 52
|
||||
|
||||
Button {
|
||||
colorScheme: leftBar.colorScheme
|
||||
width: 36
|
||||
height: 36
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
||||
anchors.leftMargin: 16
|
||||
anchors.topMargin: 7
|
||||
|
||||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-plus.svg"
|
||||
|
||||
onClicked: {
|
||||
signIn.username = ""
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // right content background
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
color: colorScheme.background_norm
|
||||
|
||||
StackLayout {
|
||||
id: rightContent
|
||||
anchors.fill: parent
|
||||
|
||||
AccountView { // 0
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
user: {
|
||||
if (accounts.currentIndex < 0) return undefined
|
||||
if (root.backend.users.count == 0) return undefined
|
||||
return root.backend.users.get(accounts.currentIndex)
|
||||
}
|
||||
onShowSignIn: {
|
||||
signIn.username = this.user.username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
onShowSetupGuide: {
|
||||
root.showSetupGuide(user,address)
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout { // 1 Sign In
|
||||
columns: 2
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
Layout.leftMargin: 18
|
||||
Layout.topMargin: 10
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
signIn.abort()
|
||||
rightContent.showAccount()
|
||||
}
|
||||
icon.source: "icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
horizontalPadding: 8
|
||||
}
|
||||
|
||||
SignIn {
|
||||
id: signIn
|
||||
Layout.topMargin: 68
|
||||
Layout.leftMargin: 80 - backButton.width - 18
|
||||
Layout.rightMargin: 80
|
||||
Layout.bottomMargin: 68
|
||||
Layout.preferredWidth: 320
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
}
|
||||
|
||||
GeneralSettings { // 2
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
onBack: {
|
||||
rightContent.showAccount()
|
||||
}
|
||||
}
|
||||
|
||||
KeychainSettings { // 3
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
onBack: {
|
||||
rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
PortSettings { // 4
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
onBack: {
|
||||
rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
SMTPSettings { // 5
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
onBack: {
|
||||
rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
LocalCacheSettings { // 6
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
onBack: {
|
||||
rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
HelpView { // 7
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
onBack: {
|
||||
rightContent.showAccount()
|
||||
}
|
||||
}
|
||||
|
||||
BugReportView { // 8
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
selectedAddress: {
|
||||
if (accounts.currentIndex < 0) return ""
|
||||
if (root.backend.users.count == 0) return ""
|
||||
var user = root.backend.users.get(accounts.currentIndex)
|
||||
if (!user) return ""
|
||||
return user.addresses[0]
|
||||
}
|
||||
|
||||
onBack: {
|
||||
rightContent.showHelpView()
|
||||
}
|
||||
}
|
||||
|
||||
function showAccount(index) {
|
||||
if (index !== undefined && index >= 0){
|
||||
accounts.currentIndex = index
|
||||
}
|
||||
rightContent.currentIndex = 0
|
||||
}
|
||||
|
||||
function showSignIn () { rightContent.currentIndex = 1 }
|
||||
function showGeneralSettings () { rightContent.currentIndex = 2 }
|
||||
function showKeychainSettings () { rightContent.currentIndex = 3 }
|
||||
function showPortSettings () { rightContent.currentIndex = 4 }
|
||||
function showSMTPSettings () { rightContent.currentIndex = 5 }
|
||||
function showLocalCacheSettings () { rightContent.currentIndex = 6 }
|
||||
function showHelpView () { rightContent.currentIndex = 7 }
|
||||
function showBugReport () { rightContent.currentIndex = 8 }
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onLoginFinished: rightContent.showAccount(index)
|
||||
onLoginAlreadyLoggedIn: rightContent.showAccount(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showLocalCacheSettings(){rightContent.showLocalCacheSettings() }
|
||||
function showSettings(){rightContent.showGeneralSettings() }
|
||||
function showHelp(){rightContent.showHelpView() }
|
||||
function showSignIn(username){
|
||||
signIn.username = username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
55
internal/frontend/qt6/qml/DebugWrapper.qml
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import "."
|
||||
import "./Proton"
|
||||
|
||||
Rectangle {
|
||||
property var target: parent
|
||||
|
||||
x: target.x
|
||||
y: target.y
|
||||
width: target.width
|
||||
height: target.height
|
||||
|
||||
color: "transparent"
|
||||
border.color: "red"
|
||||
border.width: 1
|
||||
//z: parent.z - 1
|
||||
z: 10000000
|
||||
|
||||
Label {
|
||||
text: parent.width + "x" + parent.height
|
||||
anchors.centerIn: parent
|
||||
color: "black"
|
||||
colorScheme: ProtonStyle.currentStyle
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: target.implicitWidth
|
||||
height: target.implicitHeight
|
||||
|
||||
color: "transparent"
|
||||
border.color: "green"
|
||||
border.width: 1
|
||||
//z: parent.z - 1
|
||||
z: 10000000
|
||||
}
|
||||
}
|
||||
225
internal/frontend/qt6/qml/GeneralSettings.qml
Normal file
@ -0,0 +1,225 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property bool _isAdvancedShown: false
|
||||
property var notifications
|
||||
|
||||
fillHeight: false
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Settings")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: autoUpdate
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Automatic updates")
|
||||
description: qsTr("Bridge will automatically update in the background.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isAutomaticUpdateOn
|
||||
onClicked: root.backend.toggleAutomaticUpdate(!autoUpdate.checked)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: autostart
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Open on startup")
|
||||
description: qsTr("Bridge will open upon startup.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isAutostartOn
|
||||
onClicked: {
|
||||
autostart.loading = true
|
||||
root.backend.toggleAutostart(!autostart.checked)
|
||||
}
|
||||
Connections{
|
||||
target: root.backend
|
||||
onToggleAutostartFinished: {
|
||||
autostart.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: beta
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Beta access")
|
||||
description: qsTr("Be among the first to try new features.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isBetaEnabled
|
||||
onClicked: {
|
||||
if (!beta.checked) {
|
||||
root.notifications.askEnableBeta()
|
||||
} else {
|
||||
root.backend.toggleBeta(false)
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ColorImage {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
source: root._isAdvancedShown ? "icons/ic-chevron-up.svg" : "icons/ic-chevron-down.svg"
|
||||
color: root.colorScheme.interaction_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
sourceSize.height: root.colorScheme.body_font_size
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root._isAdvancedShown = !root._isAdvancedShown
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: advSettLabel
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Advanced settings")
|
||||
color: root.colorScheme.interaction_norm
|
||||
type: Label.Body
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root._isAdvancedShown = !root._isAdvancedShown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: keychains
|
||||
visible: root._isAdvancedShown && root.backend.availableKeychain.length > 1
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Change keychain")
|
||||
description: qsTr("Change which keychain Bridge uses as default")
|
||||
actionText: qsTr("Change")
|
||||
type: SettingsItem.Button
|
||||
checked: root.backend.isDoHEnabled
|
||||
onClicked: root.parent.showKeychainSettings()
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: doh
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Alternative routing")
|
||||
description: qsTr("If Proton’s servers are blocked in your location, alternative network routing will be used to reach Proton.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isDoHEnabled
|
||||
onClicked: root.backend.toggleDoH(!doh.checked)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: darkMode
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Dark mode")
|
||||
description: qsTr("Choose dark color theme.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.colorSchemeName == "dark"
|
||||
onClicked: root.backend.changeColorScheme( darkMode.checked ? "light" : "dark")
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: allMail
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Show All Mail")
|
||||
description: qsTr("Choose to list the All Mail folder in your local client.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isAllMailVisible
|
||||
onClicked: root.notifications.askChangeAllMailVisibility(root.backend.isAllMailVisible)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: ports
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Default ports")
|
||||
actionText: qsTr("Change")
|
||||
description: qsTr("Choose which ports are used by default.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showPortSettings()
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: smtp
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection mode")
|
||||
actionText: qsTr("Change")
|
||||
description: qsTr("Change the protocol Bridge and your client use to connect.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showSMTPSettings()
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: cache
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Local cache")
|
||||
actionText: qsTr("Configure")
|
||||
description: qsTr("Configure Bridge's local cache.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showLocalCacheSettings()
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: reset
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Reset Bridge")
|
||||
actionText: qsTr("Reset")
|
||||
description: qsTr("Remove all accounts, clear cached data, and restore the original settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
root.notifications.askResetBridge()
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
116
internal/frontend/qt6/qml/HelpView.qml
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
fillHeight: true
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Help")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: setupPage
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Installation and setup")
|
||||
actionText: qsTr("Go to help topics")
|
||||
actionIcon: "./icons/ic-external-link.svg"
|
||||
description: qsTr("Get help setting up your client with our instructions and FAQs.")
|
||||
type: SettingsItem.PrimaryButton
|
||||
onClicked: {Qt.openUrlExternally("https://protonmail.com/support/categories/bridge/")}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: checkUpdates
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Updates")
|
||||
actionText: qsTr("Check now")
|
||||
description: qsTr("Check that you're using the latest version of Bridge. To stay up to date, enable auto-updates in settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
checkUpdates.loading = true
|
||||
root.backend.checkUpdates()
|
||||
}
|
||||
|
||||
Connections {target: root.backend; onCheckUpdatesFinished: checkUpdates.loading = false}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: logs
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Logs")
|
||||
actionText: qsTr("View logs")
|
||||
description: qsTr("Open and review logs to troubleshoot.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: Qt.openUrlExternally(root.backend.logsPath)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: reportBug
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Report a problem")
|
||||
actionText: qsTr("Report a problem")
|
||||
description: qsTr("Something not working as expected? Let us know.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
root.backend.updateCurrentMailClient()
|
||||
root.parent.showBugReport()
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// fill height so the footer label will be allways attached to the bottom
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
colorScheme: root.colorScheme
|
||||
type: Label.Caption
|
||||
color: root.colorScheme.text_weak
|
||||
textFormat: Text.StyledText
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
text: qsTr("Proton Mail Bridge v%1<br>© 2021 Proton AG<br>%2 %3<br>%4").
|
||||
arg(root.backend.version).
|
||||
arg(link(root.backend.licensePath, qsTr("License"))).
|
||||
arg(link(root.backend.dependencyLicensesLink, qsTr("Dependencies"))).
|
||||
arg(link(root.backend.releaseNotesLink, qsTr("Release notes")))
|
||||
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
116
internal/frontend/qt6/qml/KeychainSettings.qml
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
fillHeight: false
|
||||
|
||||
property bool _valuesChanged: keychainSelection.checkedButton && keychainSelection.checkedButton.text != root.backend.currentKeychain
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Default keychain")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Change which keychain Bridge uses as default")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
ButtonGroup{ id: keychainSelection }
|
||||
|
||||
Repeater {
|
||||
model: root.backend.availableKeychain
|
||||
|
||||
RadioButton {
|
||||
colorScheme: root.colorScheme
|
||||
ButtonGroup.group: keychainSelection
|
||||
text: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
enabled: root._valuesChanged
|
||||
onClicked: {
|
||||
root.backend.changeKeychain(keychainSelection.checkedButton.text)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onChangeKeychainFinished: {
|
||||
submitButton.loading = false
|
||||
root.back()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
for (var bi in keychainSelection.buttons){
|
||||
var button = keychainSelection.buttons[bi]
|
||||
if (button.text == root.backend.currentKeychain) {
|
||||
button.checked = true
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: root.setDefaultValues()
|
||||
}
|
||||
149
internal/frontend/qt6/qml/LocalCacheSettings.qml
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
import QtQuick.Dialogs 1.1
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
fillHeight: false
|
||||
|
||||
property var notifications
|
||||
property bool _diskCacheEnabled: true
|
||||
property url _diskCachePath: pathDialog.shortcuts.home
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Local cache")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Bridge stores your encrypted messages locally to optimize communication with your client.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: this.parent.Layout.maximumWidth
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Enable local cache")
|
||||
description: qsTr("Recommended for optimal performance.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root._diskCacheEnabled
|
||||
onClicked: root._diskCacheEnabled = !root._diskCacheEnabled
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Current cache location")
|
||||
actionText: qsTr("Change location")
|
||||
description: root.backend.goos === "windows" ?
|
||||
root._diskCachePath.toString().replace("file:///", "").replace(new RegExp("/", 'g'), "\\") + "\\" :
|
||||
root._diskCachePath.toString().replace("file://", "") + "/"
|
||||
descriptionWrap: Text.WrapAnywhere
|
||||
type: SettingsItem.Button
|
||||
enabled: root._diskCacheEnabled
|
||||
onClicked: {
|
||||
pathDialog.open()
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
FileDialog {
|
||||
id: pathDialog
|
||||
title: qsTr("Select cache location")
|
||||
folder: root._diskCachePath
|
||||
onAccepted: root._diskCachePath = pathDialog.fileUrl
|
||||
selectFolder: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
enabled: (
|
||||
root.backend.diskCachePath != root._diskCachePath ||
|
||||
root.backend.isDiskCacheEnabled != root._diskCacheEnabled
|
||||
)
|
||||
onClicked: {
|
||||
root.submit()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onChangeLocalCacheFinished: {
|
||||
submitButton.loading = false
|
||||
root.setDefaultValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function submit(){
|
||||
if (!root._diskCacheEnabled && root.backend.isDiskCacheEnabled) {
|
||||
root.notifications.askDisableLocalCache()
|
||||
return
|
||||
}
|
||||
|
||||
if (root._diskCacheEnabled && !root.backend.isDiskCacheEnabled) {
|
||||
root.notifications.askEnableLocalCache(root._diskCachePath)
|
||||
return
|
||||
}
|
||||
|
||||
// Not asking, only changing path
|
||||
submitButton.loading = true
|
||||
root.backend.changeLocalCache(root.backend.isDiskCacheEnabled, root._diskCachePath)
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
root._diskCacheEnabled = root.backend.isDiskCacheEnabled
|
||||
root._diskCachePath = root.backend.diskCachePath
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
root.setDefaultValues()
|
||||
}
|
||||
}
|
||||
212
internal/frontend/qt6/qml/MainWindow.qml
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
import CppBackend 1.0
|
||||
|
||||
import "tests"
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
|
||||
width: 960
|
||||
height: 576
|
||||
|
||||
visible: true
|
||||
|
||||
minimumHeight: contentLayout.implicitHeight
|
||||
minimumWidth: contentLayout.implicitWidth
|
||||
|
||||
colorScheme: ProtonStyle.currentStyle
|
||||
|
||||
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
// This is needed because on MacOS if first window shown is not transparent -
|
||||
// all other windows of application will not have transparent background (black
|
||||
// instead of transparency). In our case that mean that if MainWindow will be
|
||||
// shown before StatusWindow - StatusWindow will not have transparent corners.
|
||||
color: "transparent"
|
||||
|
||||
// show Setup Guide on every new user
|
||||
Connections {
|
||||
target: root.backend.users
|
||||
|
||||
onRowsInserted: {
|
||||
// considerring that users are added one-by-one
|
||||
var user = root.backend.users.get(first)
|
||||
|
||||
if (!user.loggedIn) {
|
||||
return
|
||||
}
|
||||
|
||||
if (user.setupGuideSeen) {
|
||||
return
|
||||
}
|
||||
|
||||
root.showSetup(user,user.addresses[0])
|
||||
}
|
||||
|
||||
onRowsAboutToBeRemoved: {
|
||||
for (var i = first; i <= last; i++ ) {
|
||||
var user = root.backend.users.get(i)
|
||||
|
||||
if (setupGuide.user === user) {
|
||||
setupGuide.user = null
|
||||
contentLayout._showSetup = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onShowMainWindow: {
|
||||
root.showAndRise()
|
||||
}
|
||||
|
||||
onLoginFinished: {
|
||||
console.debug("Login finished", index)
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: contentLayout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
property bool _showSetup: false
|
||||
currentIndex: {
|
||||
// show welcome when there are no users or only one non-logged-in user is present
|
||||
if (backend.users.count === 0) {
|
||||
return 1
|
||||
}
|
||||
|
||||
var u = backend.users.get(0)
|
||||
|
||||
if (!u) {
|
||||
console.trace()
|
||||
console.log("empty user")
|
||||
return 1
|
||||
}
|
||||
|
||||
if (backend.users.count === 1 && u.loggedIn === false) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (contentLayout._showSetup) {
|
||||
return 2
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
ContentWrapper { // 0
|
||||
id: contentWrapper
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
onShowSetupGuide: {
|
||||
root.showSetup(user,address)
|
||||
}
|
||||
}
|
||||
|
||||
WelcomeGuide { // 1
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SetupGuide { // 2
|
||||
id: setupGuide
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
onDismissed: {
|
||||
root.showSetup(null,"")
|
||||
}
|
||||
|
||||
onFinished: {
|
||||
// TODO: Do not close window. Trigger backend to check that
|
||||
// there is a successfully connected client. Then backend
|
||||
// should send another signal to close the setup guide.
|
||||
root.showSetup(null,"")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NotificationPopups {
|
||||
colorScheme: root.colorScheme
|
||||
notifications: root.notifications
|
||||
mainWindow: root
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
SplashScreen {
|
||||
id: splashScreen
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() }
|
||||
function showSettings() { contentWrapper.showSettings() }
|
||||
function showHelp() { contentWrapper.showHelp() }
|
||||
|
||||
function showSignIn(username) {
|
||||
if (contentLayout.currentIndex == 1) return
|
||||
contentWrapper.showSignIn(username)
|
||||
}
|
||||
|
||||
function showSetup(user, address) {
|
||||
setupGuide.user = user
|
||||
setupGuide.address = address
|
||||
setupGuide.reset()
|
||||
if (setupGuide.user) {
|
||||
contentLayout._showSetup = true
|
||||
} else {
|
||||
contentLayout._showSetup = false
|
||||
}
|
||||
}
|
||||
|
||||
function showAndRise() {
|
||||
root.show()
|
||||
root.raise()
|
||||
if (!root.active) {
|
||||
root.requestActivate()
|
||||
}
|
||||
}
|
||||
}
|
||||
119
internal/frontend/qt6/qml/NotificationDialog.qml
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
property var notification
|
||||
|
||||
shouldShow: notification && notification.active && !notification.dismissed
|
||||
modal: true
|
||||
|
||||
default property alias data: additionalChildrenContainer.children
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
sourceSize.width: 64
|
||||
sourceSize.height: 64
|
||||
|
||||
Layout.preferredHeight: 64
|
||||
Layout.preferredWidth: 64
|
||||
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
visible: source != ""
|
||||
|
||||
source: {
|
||||
if (!root.notification) {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
return "./icons/ic-info.svg"
|
||||
case Notification.NotificationType.Success:
|
||||
return "./icons/ic-success.svg"
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Danger:
|
||||
return "./icons/ic-alert.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 8
|
||||
colorScheme: root.colorScheme
|
||||
text: root.notification.title
|
||||
type: Label.LabelType.Title
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 240
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: root.notification.description
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
type: Label.LabelType.Body
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: additionalChildrenContainer
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
visible: children.length > 0
|
||||
|
||||
implicitHeight: additionalChildrenContainer.childrenRect.height
|
||||
implicitWidth: additionalChildrenContainer.childrenRect.width
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Repeater {
|
||||
model: root.notification.action
|
||||
delegate: Button {
|
||||
Layout.fillWidth: true
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
action: modelData
|
||||
|
||||
secondary: index > 0
|
||||
|
||||
loading: modelData.loading
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
132
internal/frontend/qt6/qml/NotificationPopups.qml
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var backend
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var notifications
|
||||
property var mainWindow
|
||||
|
||||
property int notificationWhitelist: NotificationFilter.FilterConsts.All
|
||||
property int notificationBlacklist: NotificationFilter.FilterConsts.None
|
||||
|
||||
NotificationFilter {
|
||||
id: bannerNotificationFilter
|
||||
|
||||
source: root.notifications.all
|
||||
blacklist: Notifications.Group.Dialogs
|
||||
}
|
||||
|
||||
Banner {
|
||||
colorScheme: root.colorScheme
|
||||
notification: bannerNotificationFilter.topmost
|
||||
mainWindow: root.mainWindow
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.updateManualReady
|
||||
|
||||
Switch {
|
||||
id:autoUpdate
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Update automatically in the future")
|
||||
checked: root.backend.isAutomaticUpdateOn
|
||||
onClicked: root.backend.toggleAutomaticUpdate(autoUpdate.checked)
|
||||
}
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.updateForce
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.updateForceError
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.enableBeta
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.cacheUnavailable
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.cacheCantMove
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.diskFull
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.enableSplitMode
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.disableLocalCache
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.enableLocalCache
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.resetBridge
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.changeAllMailVisibility
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.deleteAccount
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.noKeychain
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.rebuildKeychain
|
||||
}
|
||||
}
|
||||
54
internal/frontend/qt6/qml/Notifications/Notification.qml
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
default property var children
|
||||
|
||||
enum NotificationType {
|
||||
Info = 0,
|
||||
Success = 1,
|
||||
Warning = 2,
|
||||
Danger = 3
|
||||
}
|
||||
|
||||
// title is used in dialogs only
|
||||
property string title
|
||||
// description is used in banners and in dialogs as description
|
||||
property string description
|
||||
// brief is used in status view only
|
||||
property string brief
|
||||
|
||||
property string icon
|
||||
property list<Action> action
|
||||
property int type
|
||||
property int group
|
||||
|
||||
property bool dismissed: false
|
||||
property bool active: false
|
||||
readonly property var occurred: active ? new Date() : undefined
|
||||
|
||||
property var data
|
||||
|
||||
onActiveChanged: {
|
||||
dismissed = false
|
||||
}
|
||||
}
|
||||
114
internal/frontend/qt6/qml/Notifications/NotificationFilter.qml
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQml.Models 2.12
|
||||
|
||||
// contains notifications that satisfy black- and whitelist and are sorted in time-occurred order
|
||||
ListModel {
|
||||
id: root
|
||||
|
||||
enum FilterConsts {
|
||||
None = 0,
|
||||
All = 255
|
||||
}
|
||||
|
||||
property int whitelist: NotificationFilter.FilterConsts.All
|
||||
property int blacklist: NotificationFilter.FilterConsts.None
|
||||
|
||||
property Notification topmost
|
||||
property var source
|
||||
|
||||
property bool componentCompleted: false
|
||||
Component.onCompleted: {
|
||||
root.componentCompleted = true
|
||||
root.rebuildList()
|
||||
}
|
||||
|
||||
// overriding get method to ignore any role and return directly object itself
|
||||
function get(row) {
|
||||
if (row < 0 || row >= count) {
|
||||
return undefined
|
||||
}
|
||||
return data(index(row, 0), Qt.DisplayRole)
|
||||
}
|
||||
|
||||
function rebuildList() {
|
||||
// avoid evaluation of the list before Component.onCompleted
|
||||
if (!root.componentCompleted) {
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < root.count; i++) {
|
||||
root.get(i).onActiveChanged.disconnect( root.updateList )
|
||||
}
|
||||
|
||||
root.clear()
|
||||
|
||||
if (!root.source) {
|
||||
return
|
||||
}
|
||||
|
||||
for (i = 0; i < root.source.length; i++) {
|
||||
var obj = root.source[i]
|
||||
if (obj.group & root.blacklist) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!(obj.group & root.whitelist)) {
|
||||
continue
|
||||
}
|
||||
|
||||
root.append({obj})
|
||||
obj.onActiveChanged.connect( root.updateList )
|
||||
}
|
||||
}
|
||||
|
||||
function updateList() {
|
||||
var topmost = null
|
||||
|
||||
for (var i = 0; i < root.count; i++) {
|
||||
var obj = root.get(i)
|
||||
|
||||
if (!obj.active) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (topmost && (topmost.type > obj.type)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (topmost && (topmost.type === obj.type) && (topmost.occurred > obj.occurred)) {
|
||||
continue
|
||||
}
|
||||
|
||||
topmost = obj
|
||||
}
|
||||
|
||||
root.topmost = topmost
|
||||
}
|
||||
|
||||
onWhitelistChanged: {
|
||||
root.rebuildList()
|
||||
}
|
||||
onBlacklistChanged: {
|
||||
root.rebuildList()
|
||||
}
|
||||
onSourceChanged: {
|
||||
root.rebuildList()
|
||||
}
|
||||
}
|
||||
1021
internal/frontend/qt6/qml/Notifications/Notifications.qml
Normal file
6
internal/frontend/qt6/qml/Notifications/qmldir
Normal file
@ -0,0 +1,6 @@
|
||||
module Notifications
|
||||
depends QtQml 2.12
|
||||
|
||||
Notifications 1.0 Notifications.qml
|
||||
NotificationFilter 1.0 NotificationFilter.qml
|
||||
Notification 1.0 Notification.qml
|
||||
164
internal/frontend/qt6/qml/PortSettings.qml
Normal file
@ -0,0 +1,164 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
fillHeight: false
|
||||
|
||||
property bool _valuesChanged: (
|
||||
imapField.text*1 !== root.backend.portIMAP ||
|
||||
smtpField.text*1 !== root.backend.portSMTP
|
||||
)
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Default ports")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Changes require reconfiguration of your email client. Bridge will automatically restart.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 16
|
||||
|
||||
TextField {
|
||||
id: imapField
|
||||
colorScheme: root.colorScheme
|
||||
label: qsTr("IMAP port")
|
||||
Layout.preferredWidth: 160
|
||||
validator: root.validate
|
||||
}
|
||||
TextField {
|
||||
id: smtpField
|
||||
colorScheme: root.colorScheme
|
||||
label: qsTr("SMTP port")
|
||||
Layout.preferredWidth: 160
|
||||
validator: root.validate
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
enabled: root._valuesChanged
|
||||
onClicked: {
|
||||
// removing error here because we may have set it manually (port occupied)
|
||||
imapField.error = false
|
||||
smtpField.error = false
|
||||
|
||||
// checking errors seperatly because we want to display "same port" error only once
|
||||
imapField.validate()
|
||||
if (imapField.error) {
|
||||
return
|
||||
}
|
||||
smtpField.validate()
|
||||
if (smtpField.error) {
|
||||
return
|
||||
}
|
||||
|
||||
submitButton.loading = true
|
||||
|
||||
// check both ports before returning an error
|
||||
var err = false
|
||||
err |= !isPortFree(imapField)
|
||||
err |= !isPortFree(smtpField)
|
||||
if (err) {
|
||||
submitButton.loading = false
|
||||
return
|
||||
}
|
||||
|
||||
root.backend.changePorts(imapField.text, smtpField.text)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onChangePortFinished: submitButton.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function validate(port) {
|
||||
var num = port*1
|
||||
if (! (num > 1 && num < 65536) ) {
|
||||
return qsTr("Invalid port number")
|
||||
}
|
||||
|
||||
if (imapField.text == smtpField.text) {
|
||||
return qsTr("Port numbers must be different")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
function isPortFree(field) {
|
||||
var num = field.text*1
|
||||
if (num === root.backend.portIMAP) return true
|
||||
if (num === root.backend.portSMTP) return true
|
||||
if (!root.backend.isPortFree(num)) {
|
||||
field.error = true
|
||||
field.errorString = qsTr("Port occupied")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
imapField.text = backend.portIMAP
|
||||
smtpField.text = backend.portSMTP
|
||||
}
|
||||
|
||||
Component.onCompleted: root.setDefaultValues()
|
||||
}
|
||||
23
internal/frontend/qt6/qml/Proton/Action.qml
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
|
||||
T.Action {
|
||||
property bool loading
|
||||
}
|
||||
134
internal/frontend/qt6/qml/Proton/ApplicationWindow.qml
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
|
||||
T.ApplicationWindow {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
// popup priority based on types
|
||||
enum PopupType {
|
||||
Banner = 0,
|
||||
Dialog = 1
|
||||
}
|
||||
|
||||
// contains currently visible popup
|
||||
property var popupVisible: null
|
||||
|
||||
// list of all popups within ApplicationWindow
|
||||
property ListModel popups: ListModel {
|
||||
// overriding get method to ignore any role and return directly object itself
|
||||
function get(row) {
|
||||
if (row < 0 || row >= count) {
|
||||
return undefined
|
||||
}
|
||||
return data(index(row, 0), Qt.DisplayRole)
|
||||
}
|
||||
|
||||
onRowsInserted: {
|
||||
for (var i = first; i <= last; i++) {
|
||||
var obj = popups.get(i)
|
||||
obj.onShouldShowChanged.connect( root.processPopups )
|
||||
}
|
||||
|
||||
processPopups()
|
||||
}
|
||||
|
||||
onRowsAboutToBeRemoved: {
|
||||
for (var i = first; i <= last; i++ ) {
|
||||
var obj = popups.get(i)
|
||||
obj.onShouldShowChanged.disconnect( root.processPopups )
|
||||
|
||||
// if currently visible popup was removed
|
||||
if (root.popupVisible === obj) {
|
||||
root.popupVisible.visible = false
|
||||
root.popupVisible = null
|
||||
}
|
||||
}
|
||||
|
||||
processPopups()
|
||||
}
|
||||
}
|
||||
|
||||
function processPopups() {
|
||||
if ((root.popupVisible) && (!root.popupVisible.shouldShow)) {
|
||||
root.popupVisible.visible = false
|
||||
}
|
||||
|
||||
var topmost = null
|
||||
for (var i = 0; i < popups.count; i++) {
|
||||
var obj = popups.get(i)
|
||||
|
||||
if (obj.shouldShow === false) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (topmost && (topmost.popupType > obj.popupType)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) {
|
||||
continue
|
||||
}
|
||||
|
||||
topmost = obj
|
||||
}
|
||||
|
||||
if (root.popupVisible !== topmost) {
|
||||
if (root.popupVisible) {
|
||||
root.popupVisible.visible = false
|
||||
}
|
||||
root.popupVisible = topmost
|
||||
}
|
||||
|
||||
if (!root.popupVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
root.popupVisible.visible = true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.popupVisible
|
||||
|
||||
onVisibleChanged: {
|
||||
if (root.popupVisible.visible) {
|
||||
return
|
||||
}
|
||||
|
||||
root.popupVisible = null
|
||||
root.processPopups()
|
||||
}
|
||||
}
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
overlay.modal: Rectangle {
|
||||
color: root.colorScheme.backdrop_norm
|
||||
}
|
||||
|
||||
overlay.modeless: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
245
internal/frontend/qt6/qml/Proton/Button.qml
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import "." as Proton
|
||||
|
||||
T.Button {
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property alias secondary: control.flat
|
||||
readonly property bool primary: !secondary
|
||||
readonly property bool isIcon: control.text === ""
|
||||
|
||||
property bool loading: false
|
||||
|
||||
property bool borderless: false
|
||||
|
||||
property int labelType: Proton.Label.LabelType.Body
|
||||
|
||||
property alias textVerticalAlignment: label.verticalAlignment
|
||||
property alias textHorizontalAlignment: label.horizontalAlignment
|
||||
|
||||
// TODO: store previous enabled state and restore it?
|
||||
// For now assuming that only enabled buttons could have loading state
|
||||
onLoadingChanged: {
|
||||
if (loading) {
|
||||
enabled = false
|
||||
} else {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
id: control
|
||||
|
||||
implicitWidth: Math.max(
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding
|
||||
)
|
||||
implicitHeight: Math.max(
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding
|
||||
)
|
||||
|
||||
padding: 8
|
||||
horizontalPadding: 16
|
||||
spacing: 10
|
||||
|
||||
font: label.font
|
||||
|
||||
icon.width: 16
|
||||
icon.height: 16
|
||||
icon.color: {
|
||||
if (primary && !isIcon) {
|
||||
return "#FFFFFF"
|
||||
} else {
|
||||
return control.colorScheme.text_norm
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
id: _contentItem
|
||||
spacing: control.spacing
|
||||
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
|
||||
visible: !control.isIcon
|
||||
text: control.text
|
||||
color: {
|
||||
if (primary && !isIcon) {
|
||||
return "#FFFFFF"
|
||||
} else {
|
||||
return control.colorScheme.text_norm
|
||||
}
|
||||
}
|
||||
opacity: control.enabled || control.loading ? 1.0 : 0.5
|
||||
|
||||
type: labelType
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
id: iconImage
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
width: {
|
||||
// special case for loading since we want icon to be square for rotation animation
|
||||
if (control.loading) {
|
||||
return Math.min(control.icon.width, availableWidth, control.icon.height, availableHeight)
|
||||
}
|
||||
|
||||
return Math.min(control.icon.width, availableWidth)
|
||||
}
|
||||
height: {
|
||||
if (control.loading) {
|
||||
return width
|
||||
}
|
||||
|
||||
Math.min(control.icon.height, availableHeight)
|
||||
}
|
||||
|
||||
sourceSize.width: control.icon.width
|
||||
sourceSize.height: control.icon.height
|
||||
|
||||
color: control.icon.color
|
||||
source: control.loading ? "../icons/Loader_16.svg" : control.icon.source
|
||||
visible: control.loading || control.icon.source
|
||||
|
||||
RotationAnimation {
|
||||
target: iconImage
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
from: 0
|
||||
to: 360
|
||||
direction: RotationAnimation.Clockwise
|
||||
running: control.loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 36
|
||||
implicitHeight: 36
|
||||
radius: Style.button_radius
|
||||
visible: true
|
||||
color: {
|
||||
if (!isIcon) {
|
||||
if (primary) {
|
||||
// Primary colors
|
||||
|
||||
if (control.down) {
|
||||
return control.colorScheme.interaction_norm_active
|
||||
}
|
||||
|
||||
if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) {
|
||||
return control.colorScheme.interaction_norm_hover
|
||||
}
|
||||
|
||||
if (control.loading) {
|
||||
return control.colorScheme.interaction_norm_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.interaction_norm
|
||||
} else {
|
||||
// Secondary colors
|
||||
|
||||
if (control.down) {
|
||||
return control.colorScheme.interaction_default_active
|
||||
}
|
||||
|
||||
if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) {
|
||||
return control.colorScheme.interaction_default_hover
|
||||
}
|
||||
|
||||
if (control.loading) {
|
||||
return control.colorScheme.interaction_default_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.interaction_default
|
||||
}
|
||||
} else {
|
||||
if (primary) {
|
||||
// Primary icon colors
|
||||
|
||||
if (control.down) {
|
||||
return control.colorScheme.interaction_default_active
|
||||
}
|
||||
|
||||
if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) {
|
||||
return control.colorScheme.interaction_default_hover
|
||||
}
|
||||
|
||||
if (control.loading) {
|
||||
return control.colorScheme.interaction_default_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.interaction_default
|
||||
} else {
|
||||
// Secondary icon colors
|
||||
|
||||
if (control.down) {
|
||||
return control.colorScheme.interaction_default_active
|
||||
}
|
||||
|
||||
if (control.enabled && (control.highlighted || control.hovered || control.checked || control.activeFocus)) {
|
||||
return control.colorScheme.interaction_default_hover
|
||||
}
|
||||
|
||||
if (control.loading) {
|
||||
return control.colorScheme.interaction_default_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.interaction_default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
border.color: {
|
||||
return control.colorScheme.border_norm
|
||||
}
|
||||
border.width: secondary && !borderless ? 1 : 0
|
||||
|
||||
opacity: control.enabled || control.loading ? 1.0 : 0.5
|
||||
}
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!control.colorScheme) {
|
||||
console.trace()
|
||||
var next = root
|
||||
for (var i = 0; i<1000; i++) {
|
||||
console.log(i, next, "colorscheme", next.colorScheme)
|
||||
next = next.parent
|
||||
if (!next) break
|
||||
}
|
||||
console.error("ColorScheme not defined")
|
||||
}
|
||||
}
|
||||
}
|
||||
134
internal/frontend/qt6/qml/Proton/CheckBox.qml
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
|
||||
T.CheckBox {
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property bool error: false
|
||||
|
||||
id: control
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding,
|
||||
implicitIndicatorHeight + topPadding + bottomPadding)
|
||||
|
||||
padding: 0
|
||||
spacing: 8
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
radius: Style.checkbox_radius
|
||||
|
||||
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
|
||||
y: control.topPadding + (control.availableHeight - height) / 2
|
||||
|
||||
color: {
|
||||
if (!checked) {
|
||||
return control.colorScheme.background_norm
|
||||
}
|
||||
|
||||
if (!control.enabled) {
|
||||
return control.colorScheme.field_disabled
|
||||
}
|
||||
|
||||
if (control.error) {
|
||||
return control.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
if (control.hovered || control.activeFocus) {
|
||||
return control.colorScheme.interaction_norm_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.interaction_norm
|
||||
}
|
||||
|
||||
border.width: control.checked ? 0 : 1
|
||||
border.color: {
|
||||
if (!control.enabled) {
|
||||
return control.colorScheme.field_disabled
|
||||
}
|
||||
|
||||
if (control.error) {
|
||||
return control.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
if (control.hovered || control.activeFocus) {
|
||||
return control.colorScheme.interaction_norm_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.field_norm
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
sourceSize.width: parent.width - 4
|
||||
sourceSize.height: parent.height - 4
|
||||
color: "#FFFFFF"
|
||||
source: "../icons/ic-check.svg"
|
||||
visible: control.checkState === Qt.Checked
|
||||
}
|
||||
|
||||
// TODO: do we need PartiallyChecked state?
|
||||
|
||||
// Rectangle {
|
||||
// x: (parent.width - width) / 2
|
||||
// y: (parent.height - height) / 2
|
||||
// width: 16
|
||||
// height: 3
|
||||
// color: control.palette.text
|
||||
// visible: control.checkState === Qt.PartiallyChecked
|
||||
//}
|
||||
}
|
||||
|
||||
contentItem: CheckLabel {
|
||||
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
|
||||
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
|
||||
|
||||
text: control.text
|
||||
|
||||
color: {
|
||||
if (!enabled) {
|
||||
return control.colorScheme.text_disabled
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return control.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
return control.colorScheme.text_norm
|
||||
}
|
||||
|
||||
font.family: Style.font_family
|
||||
font.weight: Style.fontWeight_400
|
||||
font.pixelSize: Style.body_font_size
|
||||
lineHeight: Style.body_line_height
|
||||
lineHeightMode: Text.FixedHeight
|
||||
font.letterSpacing: Style.body_letter_spacing
|
||||
}
|
||||
}
|
||||
92
internal/frontend/qt6/qml/Proton/ColorScheme.qml
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.13
|
||||
|
||||
QtObject {
|
||||
// should be a pointer to ColorScheme object
|
||||
property var prominent
|
||||
|
||||
// Primary
|
||||
property color primay_norm
|
||||
|
||||
// Interaction-norm
|
||||
property color interaction_norm
|
||||
property color interaction_norm_hover
|
||||
property color interaction_norm_active
|
||||
|
||||
// Text
|
||||
property color text_norm
|
||||
property color text_weak
|
||||
property color text_hint
|
||||
property color text_disabled
|
||||
property color text_invert
|
||||
|
||||
// Field
|
||||
property color field_norm
|
||||
property color field_hover
|
||||
property color field_disabled
|
||||
|
||||
// Border
|
||||
property color border_norm
|
||||
property color border_weak
|
||||
|
||||
// Background
|
||||
property color background_norm
|
||||
property color background_weak
|
||||
property color background_strong
|
||||
property color background_avatar
|
||||
|
||||
// Interaction-weak
|
||||
property color interaction_weak
|
||||
property color interaction_weak_hover
|
||||
property color interaction_weak_active
|
||||
|
||||
// Interaction-default
|
||||
property color interaction_default
|
||||
property color interaction_default_hover
|
||||
property color interaction_default_active
|
||||
|
||||
// Scrollbar
|
||||
property color scrollbar_norm
|
||||
property color scrollbar_hover
|
||||
|
||||
// Signal
|
||||
property color signal_danger
|
||||
property color signal_danger_hover
|
||||
property color signal_danger_active
|
||||
property color signal_warning
|
||||
property color signal_warning_hover
|
||||
property color signal_warning_active
|
||||
property color signal_success
|
||||
property color signal_success_hover
|
||||
property color signal_success_active
|
||||
property color signal_info
|
||||
property color signal_info_hover
|
||||
property color signal_info_active
|
||||
|
||||
// Shadows
|
||||
property color shadow_norm
|
||||
property color shadow_lifted
|
||||
|
||||
// Backdrop
|
||||
property color backdrop_norm
|
||||
|
||||
// Images
|
||||
property string welcome_img
|
||||
property string logo_img
|
||||
}
|
||||
195
internal/frontend/qt6/qml/Proton/ComboBox.qml
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
|
||||
T.ComboBox {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding,
|
||||
implicitIndicatorHeight + topPadding + bottomPadding)
|
||||
|
||||
leftPadding: 12 + (!root.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing)
|
||||
rightPadding: 12 + (root.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing)
|
||||
|
||||
topPadding: 5
|
||||
bottomPadding: 5
|
||||
|
||||
spacing: 8
|
||||
|
||||
font.family: Style.font_family
|
||||
font.weight: Style.fontWeight_400
|
||||
font.pixelSize: Style.body_font_size
|
||||
font.letterSpacing: Style.body_letter_spacing
|
||||
|
||||
contentItem: T.TextField {
|
||||
padding: 5
|
||||
|
||||
text: root.editable ? root.editText : root.displayText
|
||||
font: root.font
|
||||
|
||||
enabled: root.editable
|
||||
autoScroll: root.editable
|
||||
readOnly: root.down
|
||||
inputMethodHints: root.inputMethodHints
|
||||
validator: root.validator
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||
selectionColor: root.colorScheme.interaction_norm
|
||||
selectedTextColor: root.colorScheme.text_invert
|
||||
placeholderTextColor: root.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled
|
||||
|
||||
background: Rectangle {
|
||||
radius: Style.context_item_radius
|
||||
visible: root.enabled && root.editable && !root.flat
|
||||
border.color: {
|
||||
if (root.activeFocus) {
|
||||
return root.colorScheme.interaction_norm
|
||||
}
|
||||
|
||||
if (root.hovered || root.activeFocus) {
|
||||
return root.colorScheme.field_hover
|
||||
}
|
||||
|
||||
return root.colorScheme.field_norm
|
||||
}
|
||||
border.width: 1
|
||||
color: root.colorScheme.background_norm
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 140
|
||||
implicitHeight: 36
|
||||
radius: Style.context_item_radius
|
||||
color: {
|
||||
if (root.down) {
|
||||
return root.colorScheme.interaction_default_active
|
||||
}
|
||||
|
||||
if (root.enabled && root.hovered || root.activeFocus) {
|
||||
return root.colorScheme.interaction_default_hover
|
||||
}
|
||||
|
||||
if (!root.enabled) {
|
||||
return root.colorScheme.interaction_default
|
||||
}
|
||||
|
||||
return root.colorScheme.background_norm
|
||||
}
|
||||
|
||||
border.color: root.colorScheme.border_norm
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
indicator: ColorImage {
|
||||
x: root.mirrored ? 12 : root.width - width - 12
|
||||
y: root.topPadding + (root.availableHeight - height) / 2
|
||||
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||
source: popup.visible ? "../icons/ic-chevron-up.svg" : "../icons/ic-chevron-down.svg"
|
||||
|
||||
sourceSize.width: 16
|
||||
sourceSize.height: 16
|
||||
}
|
||||
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: parent.width
|
||||
text: root.textRole ? (Array.isArray(root.model) ? modelData[root.textRole] : model[root.textRole]) : modelData
|
||||
|
||||
palette.text: {
|
||||
if (!root.enabled) {
|
||||
return root.colorScheme.text_disabled
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
return root.colorScheme.text_invert
|
||||
}
|
||||
|
||||
return root.colorScheme.text_norm
|
||||
}
|
||||
font: root.font
|
||||
|
||||
hoverEnabled: root.hoverEnabled
|
||||
|
||||
property bool selected: root.currentIndex === index
|
||||
|
||||
highlighted: root.highlightedIndex === index
|
||||
palette.highlightedText: selected ? root.colorScheme.text_invert : root.colorScheme.text_norm
|
||||
|
||||
background: PaddedRectangle {
|
||||
radius: Style.context_item_radius
|
||||
color: {
|
||||
if (parent.down) {
|
||||
return root.colorScheme.interaction_default_active
|
||||
}
|
||||
|
||||
if (parent.selected) {
|
||||
return root.colorScheme.interaction_norm
|
||||
}
|
||||
|
||||
if (parent.hovered || parent.highlighted) {
|
||||
return root.colorScheme.interaction_default_hover
|
||||
}
|
||||
|
||||
return root.colorScheme.interaction_default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popup: T.Popup {
|
||||
y: root.height
|
||||
width: root.width
|
||||
height: Math.min(contentItem.implicitHeight, root.Window.height - topMargin - bottomMargin)
|
||||
topMargin: 8
|
||||
bottomMargin: 8
|
||||
|
||||
contentItem: Item {
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
|
||||
implicitHeight: contentHeight
|
||||
model: root.delegateModel
|
||||
currentIndex: root.highlightedIndex
|
||||
spacing: 4
|
||||
|
||||
T.ScrollIndicator.vertical: ScrollIndicator { }
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: root.colorScheme.background_norm
|
||||
radius: Style.dialog_radius
|
||||
border.color: root.colorScheme.border_weak
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
80
internal/frontend/qt6/qml/Proton/Dialog.qml
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
T.Dialog {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!ApplicationWindow.window) {
|
||||
return
|
||||
}
|
||||
|
||||
if (ApplicationWindow.window.popups === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
var obj = this
|
||||
ApplicationWindow.window.popups.append( { obj } )
|
||||
}
|
||||
|
||||
readonly property int popupType: ApplicationWindow.PopupType.Dialog
|
||||
|
||||
property bool shouldShow: false
|
||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||
function open() {
|
||||
root.shouldShow = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
root.shouldShow = false
|
||||
}
|
||||
|
||||
anchors.centerIn: Overlay.overlay
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
contentWidth + leftPadding + rightPadding,
|
||||
implicitHeaderWidth,
|
||||
implicitFooterWidth)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
contentHeight + topPadding + bottomPadding
|
||||
+ (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
|
||||
+ (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
|
||||
|
||||
padding: 24
|
||||
|
||||
background: Rectangle {
|
||||
color: root.colorScheme.background_norm
|
||||
radius: Style.dialog_radius
|
||||
}
|
||||
|
||||
// TODO: Add DropShadow here
|
||||
|
||||
T.Overlay.modal: Rectangle {
|
||||
color: root.colorScheme.backdrop_norm
|
||||
}
|
||||
|
||||
T.Overlay.modeless: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
142
internal/frontend/qt6/qml/Proton/Label.qml
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
|
||||
import "." as Proton
|
||||
|
||||
T.Label {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
enum LabelType {
|
||||
// weight 700, size 28, height 36
|
||||
Heading,
|
||||
// weight 700, size 20, height 24
|
||||
Title,
|
||||
// weight 400, size 18, height 26
|
||||
Lead,
|
||||
// weight 400, size 14, height 20, spacing 0.2
|
||||
Body,
|
||||
// weight 600, size 14, height 20, spacing 0.2
|
||||
Body_semibold,
|
||||
// weight 700, size 14, height 20, spacing 0.2
|
||||
Body_bold,
|
||||
// weight 400, size 12, height 16, spacing 0.4
|
||||
Caption,
|
||||
// weight 600, size 12, height 16, spacing 0.4
|
||||
Caption_semibold,
|
||||
// weight 700, size 12, height 16, spacing 0.4
|
||||
Caption_bold
|
||||
}
|
||||
property int type: Proton.Label.LabelType.Body
|
||||
|
||||
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||
linkColor: root.colorScheme.interaction_norm
|
||||
palette.link: linkColor
|
||||
|
||||
font.family: Style.font_family
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
font.weight: {
|
||||
switch (root.type) {
|
||||
case Proton.Label.LabelType.Heading:
|
||||
return Style.fontWeight_700
|
||||
case Proton.Label.LabelType.Title:
|
||||
return Style.fontWeight_700
|
||||
case Proton.Label.LabelType.Lead:
|
||||
return Style.fontWeight_400
|
||||
case Proton.Label.LabelType.Body:
|
||||
return Style.fontWeight_400
|
||||
case Proton.Label.LabelType.Body_semibold:
|
||||
return Style.fontWeight_600
|
||||
case Proton.Label.LabelType.Body_bold:
|
||||
return Style.fontWeight_700
|
||||
case Proton.Label.LabelType.Caption:
|
||||
return Style.fontWeight_400
|
||||
case Proton.Label.LabelType.Caption_semibold:
|
||||
return Style.fontWeight_600
|
||||
case Proton.Label.LabelType.Caption_bold:
|
||||
return Style.fontWeight_700
|
||||
}
|
||||
}
|
||||
|
||||
font.pixelSize: {
|
||||
switch (root.type) {
|
||||
case Proton.Label.LabelType.Heading:
|
||||
return Style.heading_font_size
|
||||
case Proton.Label.LabelType.Title:
|
||||
return Style.title_font_size
|
||||
case Proton.Label.LabelType.Lead:
|
||||
return Style.lead_font_size
|
||||
case Proton.Label.LabelType.Body:
|
||||
case Proton.Label.LabelType.Body_semibold:
|
||||
case Proton.Label.LabelType.Body_bold:
|
||||
return Style.body_font_size
|
||||
case Proton.Label.LabelType.Caption:
|
||||
case Proton.Label.LabelType.Caption_semibold:
|
||||
case Proton.Label.LabelType.Caption_bold:
|
||||
return Style.caption_font_size
|
||||
}
|
||||
}
|
||||
|
||||
lineHeight: {
|
||||
switch (root.type) {
|
||||
case Proton.Label.LabelType.Heading:
|
||||
return Style.heading_line_height
|
||||
case Proton.Label.LabelType.Title:
|
||||
return Style.title_line_height
|
||||
case Proton.Label.LabelType.Lead:
|
||||
return Style.lead_line_height
|
||||
case Proton.Label.LabelType.Body:
|
||||
case Proton.Label.LabelType.Body_semibold:
|
||||
case Proton.Label.LabelType.Body_bold:
|
||||
return Style.body_line_height
|
||||
case Proton.Label.LabelType.Caption:
|
||||
case Proton.Label.LabelType.Caption_semibold:
|
||||
case Proton.Label.LabelType.Caption_bold:
|
||||
return Style.caption_line_height
|
||||
}
|
||||
}
|
||||
|
||||
font.letterSpacing: {
|
||||
switch (root.type) {
|
||||
case Proton.Label.LabelType.Heading:
|
||||
case Proton.Label.LabelType.Title:
|
||||
case Proton.Label.LabelType.Lead:
|
||||
return 0
|
||||
case Proton.Label.LabelType.Body:
|
||||
case Proton.Label.LabelType.Body_semibold:
|
||||
case Proton.Label.LabelType.Body_bold:
|
||||
return Style.body_letter_spacing
|
||||
case Proton.Label.LabelType.Caption:
|
||||
case Proton.Label.LabelType.Caption_semibold:
|
||||
case Proton.Label.LabelType.Caption_bold:
|
||||
return Style.caption_letter_spacing
|
||||
}
|
||||
}
|
||||
|
||||
verticalAlignment: Text.AlignBottom
|
||||
|
||||
function link(url, text) {
|
||||
return `<a href="${url}">${text}</a>`
|
||||
}
|
||||
}
|
||||
72
internal/frontend/qt6/qml/Proton/Menu.qml
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import QtQuick.Window 2.12
|
||||
import "."
|
||||
|
||||
T.Menu {
|
||||
id: control
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
implicitWidth: Math.max(
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
contentWidth + leftPadding + rightPadding
|
||||
)
|
||||
implicitHeight: Math.max(
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
contentHeight + topPadding + bottomPadding
|
||||
)
|
||||
|
||||
margins: 0
|
||||
overlap: 1
|
||||
|
||||
delegate: MenuItem {
|
||||
colorScheme: control.colorScheme
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
|
||||
implicitHeight: contentHeight
|
||||
model: control.contentModel
|
||||
interactive: Window.window ? contentHeight > Window.window.height : false
|
||||
clip: true
|
||||
currentIndex: control.currentIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 200
|
||||
implicitHeight: 40
|
||||
color: colorScheme.background_norm
|
||||
border.width: 1
|
||||
border.color: colorScheme.border_weak
|
||||
radius: Style.account_row_radius
|
||||
}
|
||||
}
|
||||
72
internal/frontend/qt6/qml/Proton/MenuItem.qml
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import "."
|
||||
|
||||
T.MenuItem {
|
||||
id: control
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding,
|
||||
implicitIndicatorHeight + topPadding + bottomPadding)
|
||||
|
||||
padding: 6
|
||||
spacing: 6
|
||||
|
||||
icon.width: 24
|
||||
icon.height: 24
|
||||
icon.color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled
|
||||
|
||||
font.family: Style.font_family
|
||||
font.weight: Style.fontWeight_400
|
||||
font.pixelSize: Style.body_font_size
|
||||
font.letterSpacing: Style.body_letter_spacing
|
||||
|
||||
contentItem: IconLabel {
|
||||
id: iconLabel
|
||||
readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0
|
||||
readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0
|
||||
leftPadding: !control.mirrored ? indicatorPadding : arrowPadding
|
||||
rightPadding: control.mirrored ? indicatorPadding : arrowPadding
|
||||
|
||||
spacing: control.spacing
|
||||
mirrored: control.mirrored
|
||||
display: control.display
|
||||
alignment: Qt.AlignLeft
|
||||
|
||||
icon: control.icon
|
||||
text: control.text
|
||||
font: control.font
|
||||
|
||||
color: control.enabled ? control.colorScheme.text_norm : control.colorScheme.text_disabled
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 164
|
||||
implicitHeight: 36
|
||||
radius: Style.button_radius
|
||||
color: control.down ? control.colorScheme.interaction_default_active : control.highlighted ? control.colorScheme.interaction_default_hover : control.colorScheme.interaction_default
|
||||
}
|
||||
}
|
||||
67
internal/frontend/qt6/qml/Proton/Popup.qml
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
|
||||
T.Popup {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!ApplicationWindow.window) {
|
||||
return
|
||||
}
|
||||
|
||||
if (ApplicationWindow.window.popups === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
var obj = this
|
||||
ApplicationWindow.window.popups.append( { obj } )
|
||||
}
|
||||
|
||||
property int popupType: ApplicationWindow.PopupType.Banner
|
||||
|
||||
property bool shouldShow: false
|
||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||
function open() {
|
||||
root.shouldShow = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
root.shouldShow = false
|
||||
}
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
contentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
contentHeight + topPadding + bottomPadding)
|
||||
|
||||
// TODO: Add DropShadow here
|
||||
|
||||
T.Overlay.modal: Rectangle {
|
||||
color: root.colorScheme.backdrop_norm
|
||||
}
|
||||
|
||||
T.Overlay.modeless: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
115
internal/frontend/qt6/qml/Proton/RadioButton.qml
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
|
||||
T.RadioButton {
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property bool error: false
|
||||
|
||||
id: control
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding,
|
||||
implicitIndicatorHeight + topPadding + bottomPadding)
|
||||
|
||||
padding: 0
|
||||
spacing: 8
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
radius: width / 2
|
||||
|
||||
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
|
||||
y: control.topPadding + (control.availableHeight - height) / 2
|
||||
|
||||
color: control.colorScheme.background_norm
|
||||
border.width: 1
|
||||
border.color: {
|
||||
if (!control.enabled) {
|
||||
return control.colorScheme.field_disabled
|
||||
}
|
||||
|
||||
if (control.error) {
|
||||
return control.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
if (control.hovered || control.activeFocus) {
|
||||
return control.colorScheme.interaction_norm_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.field_norm
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
width: 8
|
||||
height: 8
|
||||
radius: width / 2
|
||||
color: {
|
||||
if (!control.enabled) {
|
||||
return control.colorScheme.field_disabled
|
||||
}
|
||||
|
||||
if (control.error) {
|
||||
return control.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
if (control.hovered || control.activeFocus) {
|
||||
return control.colorScheme.interaction_norm_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.interaction_norm
|
||||
}
|
||||
visible: control.checked
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: CheckLabel {
|
||||
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
|
||||
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
|
||||
|
||||
text: control.text
|
||||
|
||||
color: {
|
||||
if (!enabled) {
|
||||
return control.colorScheme.text_disabled
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return control.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
return control.colorScheme.text_norm
|
||||
}
|
||||
|
||||
font.family: Style.font_family
|
||||
font.weight: Style.fontWeight_400
|
||||
font.pixelSize: Style.body_font_size
|
||||
lineHeight: Style.body_line_height
|
||||
lineHeightMode: Text.FixedHeight
|
||||
font.letterSpacing: Style.body_letter_spacing
|
||||
}
|
||||
}
|
||||
394
internal/frontend/qt6/qml/Proton/Style.qml
Normal file
@ -0,0 +1,394 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
pragma Singleton
|
||||
import QtQml 2.13
|
||||
import QtQuick 2.12
|
||||
|
||||
import "./"
|
||||
|
||||
// https://wiki.qt.io/Qml_Styling
|
||||
// http://imaginativethinking.ca/make-qml-component-singleton/
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
// TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows:
|
||||
// https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components
|
||||
|
||||
// component ColorScheme: QtObject {
|
||||
// property color primay_norm
|
||||
// ...
|
||||
// }
|
||||
|
||||
property ColorScheme lightStyle: ColorScheme {
|
||||
id: _lightStyle
|
||||
|
||||
prominent: lightProminentStyle
|
||||
|
||||
// Primary
|
||||
primay_norm: "#6D4AFF"
|
||||
|
||||
// Interaction-norm
|
||||
interaction_norm: "#6D4AFF"
|
||||
interaction_norm_hover: "#4D34B3"
|
||||
interaction_norm_active: "#372580"
|
||||
|
||||
// Text
|
||||
text_norm: "#0C0C14"
|
||||
text_weak: "#706D6B"
|
||||
text_hint: "#8F8D8A"
|
||||
text_disabled: "#C2BFBC"
|
||||
text_invert: "#FFFFFF"
|
||||
|
||||
// Field
|
||||
field_norm: "#ADABA8"
|
||||
field_hover: "#8F8D8A"
|
||||
field_disabled: "#D1CFCD"
|
||||
|
||||
// Border
|
||||
border_norm: "#D1CFCD"
|
||||
border_weak: "#EAE7E4"
|
||||
|
||||
// Background
|
||||
background_norm: "#FFFFFF"
|
||||
background_weak: "#F5F4F2"
|
||||
background_strong: "#EAE7E4"
|
||||
background_avatar: "#C2BFBC"
|
||||
|
||||
// Interaction-weak
|
||||
interaction_weak: "#D1CFCD"
|
||||
interaction_weak_hover: "#C2BFBC"
|
||||
interaction_weak_active: "#A8A6A3"
|
||||
|
||||
// Interaction-default
|
||||
interaction_default: Qt.rgba(0,0,0,0)
|
||||
interaction_default_hover: Qt.rgba(194./255., 191./255., 188./255., 0.2)
|
||||
interaction_default_active: Qt.rgba(194./255., 191./255., 188./255., 0.4)
|
||||
|
||||
// Scrollbar
|
||||
scrollbar_norm: "#D1CFCD"
|
||||
scrollbar_hover: "#C2BFBC"
|
||||
|
||||
// Signal
|
||||
signal_danger: "#DC3251"
|
||||
signal_danger_hover: "#F74F6D"
|
||||
signal_danger_active: "#B72346"
|
||||
signal_warning: "#FF9900"
|
||||
signal_warning_hover: "#FFB800"
|
||||
signal_warning_active: "#FF851A"
|
||||
signal_success: "#1EA885"
|
||||
signal_success_hover: "#23C299"
|
||||
signal_success_active: "#198F71"
|
||||
signal_info: "#239ECE"
|
||||
signal_info_hover: "#27B1E8"
|
||||
signal_info_active: "#1F83B5"
|
||||
|
||||
// Shadows
|
||||
shadow_norm: Qt.rgba(0,0,0, 0.1) // #000000 10% x:0 y:1 blur:4
|
||||
shadow_lifted: Qt.rgba(0,0,0, 0.16) // #000000 16% x:0 y:8 blur:24
|
||||
|
||||
// Backdrop
|
||||
backdrop_norm: Qt.rgba(12./255., 12./255., 20./255., 0.32)
|
||||
|
||||
// Images
|
||||
welcome_img: "icons/img-welcome.png"
|
||||
logo_img: "icons/product_logos.svg"
|
||||
}
|
||||
|
||||
property ColorScheme lightProminentStyle: ColorScheme {
|
||||
id: _lightProminentStyle
|
||||
|
||||
prominent: this
|
||||
|
||||
// Primary
|
||||
primay_norm: "#8A6EFF"
|
||||
|
||||
// Interaction-norm
|
||||
interaction_norm: "#6D4AFF"
|
||||
interaction_norm_hover: "#7C5CFF"
|
||||
interaction_norm_active: "#8A6EFF"
|
||||
|
||||
// Text
|
||||
text_norm: "#FFFFFF"
|
||||
text_weak: "#9282D4"
|
||||
text_hint: "#544399"
|
||||
text_disabled: "#4A398F"
|
||||
text_invert: "#1B1340"
|
||||
|
||||
// Field
|
||||
field_norm: "#9282D4"
|
||||
field_hover: "#7C5CFF"
|
||||
field_disabled: "#38277A"
|
||||
|
||||
// Border
|
||||
border_norm: "#413085"
|
||||
border_weak: "#3C2B80"
|
||||
|
||||
// Background
|
||||
background_norm: "#1B1340"
|
||||
background_weak: "#271C57"
|
||||
background_strong: "#38277A"
|
||||
background_avatar: "#6D4AFF"
|
||||
|
||||
// Interaction-weak
|
||||
interaction_weak: "#4A398F"
|
||||
interaction_weak_hover: "#6D4AFF"
|
||||
interaction_weak_active: "#8A6EFF"
|
||||
|
||||
// Interaction-default
|
||||
interaction_default: Qt.rgba(0,0,0,0)
|
||||
interaction_default_hover: Qt.rgba(68./255., 78./255., 114./255., 0.2)
|
||||
interaction_default_active: Qt.rgba(68./255., 78./255., 114./255., 0.3)
|
||||
|
||||
// Scrollbar
|
||||
scrollbar_norm: "#413085"
|
||||
scrollbar_hover: "#4A398F"
|
||||
|
||||
// Signal
|
||||
signal_danger: "#F5385A"
|
||||
signal_danger_hover: "#FF5473"
|
||||
signal_danger_active: "#DC3251"
|
||||
signal_warning: "#FF9900"
|
||||
signal_warning_hover: "#FFB800"
|
||||
signal_warning_active: "#FF8419"
|
||||
signal_success: "#1EA885"
|
||||
signal_success_hover: "#23C299"
|
||||
signal_success_active: "#198F71"
|
||||
signal_info: "#2C89DB"
|
||||
signal_info_hover: "#3491E3"
|
||||
signal_info_active: "#1F83B5"
|
||||
|
||||
// Shadows
|
||||
shadow_norm: Qt.rgba(0,0,0, 0.32) // #000000 32% x:0 y:1 blur:4
|
||||
shadow_lifted: Qt.rgba(0,0,0, 0.40) // #000000 40% x:0 y:8 blur:24
|
||||
|
||||
// Backdrop
|
||||
backdrop_norm: Qt.rgba(0,0,0, 0.32)
|
||||
|
||||
// Images
|
||||
welcome_img: "icons/img-welcome-dark.png"
|
||||
logo_img: "icons/product_logos_dark.svg"
|
||||
}
|
||||
|
||||
property ColorScheme darkStyle: ColorScheme {
|
||||
id: _darkStyle
|
||||
|
||||
prominent: darkProminentStyle
|
||||
|
||||
// Primary
|
||||
primay_norm: "#8A6EFF"
|
||||
|
||||
// Interaction-norm
|
||||
interaction_norm: "#6D4AFF"
|
||||
interaction_norm_hover: "#7C5CFF"
|
||||
interaction_norm_active: "#8A6EFF"
|
||||
|
||||
// Text
|
||||
text_norm: "#FFFFFF"
|
||||
text_weak: "#A7A4B5"
|
||||
text_hint: "#6D697D"
|
||||
text_disabled: "#5B576B"
|
||||
text_invert: "#1C1B24"
|
||||
|
||||
// Field
|
||||
field_norm: "#5B576B"
|
||||
field_hover: "#6D697D"
|
||||
field_disabled: "#3F3B4C"
|
||||
|
||||
// Border
|
||||
border_norm: "#4A4658"
|
||||
border_weak: "#343140"
|
||||
|
||||
// Background
|
||||
background_norm: "#1C1B24"
|
||||
background_weak: "#292733"
|
||||
background_strong: "#3F3B4C"
|
||||
background_avatar: "#6D4AFF"
|
||||
|
||||
// Interaction-weak
|
||||
interaction_weak: "#4A4658"
|
||||
interaction_weak_hover: "#5B576B"
|
||||
interaction_weak_active: "#6D697D"
|
||||
|
||||
// Interaction-default
|
||||
interaction_default: "#00000000"
|
||||
interaction_default_hover: Qt.rgba(91./255.,87./255.,107./255.,0.2)
|
||||
interaction_default_active: Qt.rgba(91./255.,87./255.,107./255.,0.4)
|
||||
|
||||
// Scrollbar
|
||||
scrollbar_norm: "#4A4658"
|
||||
scrollbar_hover: "#5B576B"
|
||||
|
||||
// Signal
|
||||
signal_danger: "#F5385A"
|
||||
signal_danger_hover: "#FF5473"
|
||||
signal_danger_active: "#DC3251"
|
||||
signal_warning: "#FF9900"
|
||||
signal_warning_hover: "#FFB800"
|
||||
signal_warning_active: "#FF8419"
|
||||
signal_success: "#1EA885"
|
||||
signal_success_hover: "#23C299"
|
||||
signal_success_active: "#198F71"
|
||||
signal_info: "#239ECE"
|
||||
signal_info_hover: "#27B1E8"
|
||||
signal_info_active: "#1F83B5"
|
||||
|
||||
// Shadows
|
||||
shadow_norm: Qt.rgba(0,0,0,0.4) // #000000 40% x+0 y+1 blur:4
|
||||
shadow_lifted: Qt.rgba(0,0,0,0.48) // #000000 48% x+0 y+8 blur:24
|
||||
|
||||
// Backdrop
|
||||
backdrop_norm: Qt.rgba(0,0,0,0.32)
|
||||
|
||||
// Images
|
||||
welcome_img: "icons/img-welcome-dark.png"
|
||||
logo_img: "icons/product_logos_dark.svg"
|
||||
}
|
||||
|
||||
property ColorScheme darkProminentStyle: ColorScheme {
|
||||
id: _darkProminentStyle
|
||||
|
||||
prominent: this
|
||||
|
||||
// Primary
|
||||
primay_norm: "#8A6EFF"
|
||||
|
||||
// Interaction-norm
|
||||
interaction_norm: "#6D4AFF"
|
||||
interaction_norm_hover: "#7C5CFF"
|
||||
interaction_norm_active: "#8A6EFF"
|
||||
|
||||
// Text
|
||||
text_norm: "#FFFFFF"
|
||||
text_weak: "#A7A4B5"
|
||||
text_hint: "#6D697D"
|
||||
text_disabled: "#5B576B"
|
||||
text_invert: "#1C1B24"
|
||||
|
||||
// Field
|
||||
field_norm: "#5B576B"
|
||||
field_hover: "#6D697D"
|
||||
field_disabled: "#3F3B4C"
|
||||
|
||||
// Border
|
||||
border_norm: "#4A4658"
|
||||
border_weak: "#343140"
|
||||
|
||||
// Background
|
||||
background_norm: "#16141c"
|
||||
background_weak: "#292733"
|
||||
background_strong: "#3F3B4C"
|
||||
background_avatar: "#6D4AFF"
|
||||
|
||||
// Interaction-weak
|
||||
interaction_weak: "#4A4658"
|
||||
interaction_weak_hover: "#5B576B"
|
||||
interaction_weak_active: "#6D697D"
|
||||
|
||||
// Interaction-default
|
||||
interaction_default: "#00000000"
|
||||
interaction_default_hover: Qt.rgba(91./255.,87./255.,107./255.,0.2)
|
||||
interaction_default_active: Qt.rgba(91./255.,87./255.,107./255.,0.4)
|
||||
|
||||
// Scrollbar
|
||||
scrollbar_norm: "#4A4658"
|
||||
scrollbar_hover: "#5B576B"
|
||||
|
||||
// Signal
|
||||
signal_danger: "#F5385A"
|
||||
signal_danger_hover: "#FF5473"
|
||||
signal_danger_active: "#DC3251"
|
||||
signal_warning: "#FF9900"
|
||||
signal_warning_hover: "#FFB800"
|
||||
signal_warning_active: "#FF8419"
|
||||
signal_success: "#1EA885"
|
||||
signal_success_hover: "#23C299"
|
||||
signal_success_active: "#198F71"
|
||||
signal_info: "#239ECE"
|
||||
signal_info_hover: "#27B1E8"
|
||||
signal_info_active: "#1F83B5"
|
||||
|
||||
// Shadows
|
||||
shadow_norm: Qt.rgba(0,0,0,0.4) // #000000 40% x+0 y+1 blur:4
|
||||
shadow_lifted: Qt.rgba(0,0,0,0.48) // #000000 48% x+0 y+8 blur:24
|
||||
|
||||
// Backdrop
|
||||
backdrop_norm: Qt.rgba(0,0,0,0.32)
|
||||
|
||||
// Images
|
||||
welcome_img: "icons/img-welcome-dark.png"
|
||||
logo_img: "icons/product_logos_dark.svg"
|
||||
}
|
||||
|
||||
property ColorScheme currentStyle: lightStyle
|
||||
|
||||
property string font_family: {
|
||||
switch (Qt.platform.os) {
|
||||
case "windows":
|
||||
return "Segoe UI"
|
||||
case "osx":
|
||||
return "SF Pro Display"
|
||||
case "linux":
|
||||
return "Ubuntu"
|
||||
default:
|
||||
console.error("Unknown platform")
|
||||
}
|
||||
}
|
||||
|
||||
property real px : 1.00 // px
|
||||
|
||||
property real input_radius : 8 * root.px // px
|
||||
property real button_radius : 8 * root.px // px
|
||||
property real checkbox_radius : 4 * root.px // px
|
||||
property real avatar_radius : 8 * root.px // px
|
||||
property real big_avatar_radius : 12 * root.px // px
|
||||
property real account_hover_radius : 12 * root.px // px
|
||||
property real account_row_radius : 12 * root.px // px
|
||||
property real context_item_radius : 8 * root.px // px
|
||||
property real banner_radius : 12 * root.px // px
|
||||
property real dialog_radius : 12 * root.px // px
|
||||
property real card_radius : 12 * root.px // px
|
||||
property real storage_bar_radius : 3 * root.px // px
|
||||
property real tooltip_radius : 8 * root.px // px
|
||||
|
||||
property int heading_font_size: 28
|
||||
property int heading_line_height: 36
|
||||
|
||||
property int title_font_size: 20
|
||||
property int title_line_height: 24
|
||||
|
||||
property int lead_font_size: 18
|
||||
property int lead_line_height: 26
|
||||
|
||||
property int body_font_size: 14
|
||||
property int body_line_height: 20
|
||||
property real body_letter_spacing: 0.2 * root.px
|
||||
|
||||
property int caption_font_size: 12
|
||||
property int caption_line_height: 16
|
||||
property real caption_letter_spacing: 0.4 * root.px
|
||||
|
||||
property int fontWeight_100: Font.Thin
|
||||
property int fontWeight_200: Font.Light
|
||||
property int fontWeight_300: Font.ExtraLight
|
||||
property int fontWeight_400: Font.Normal
|
||||
property int fontWeight_500: Font.Medium
|
||||
property int fontWeight_600: Font.DemiBold
|
||||
property int fontWeight_700: Font.Bold
|
||||
property int fontWeight_800: Font.ExtraBold
|
||||
property int fontWeight_900: Font.Black
|
||||
}
|
||||
150
internal/frontend/qt6/qml/Proton/Switch.qml
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
T.Switch {
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property bool loading: false
|
||||
|
||||
// TODO: store previous enabled state and restore it?
|
||||
// For now assuming that only enabled buttons could have loading state
|
||||
onLoadingChanged: {
|
||||
if (loading) {
|
||||
enabled = false
|
||||
} else {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
id: control
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding,
|
||||
implicitIndicatorHeight + topPadding + bottomPadding)
|
||||
|
||||
padding: 0
|
||||
spacing: 7
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 40
|
||||
implicitHeight: 24
|
||||
|
||||
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
|
||||
y: control.topPadding + (control.availableHeight - height) / 2
|
||||
|
||||
radius: height / 2.
|
||||
color: control.enabled || control.loading ? control.colorScheme.background_norm : control.colorScheme.background_strong
|
||||
border.width: control.enabled && !loading ? 1 : 0
|
||||
border.color: control.hovered ? control.colorScheme.field_hover : control.colorScheme.field_norm
|
||||
|
||||
Rectangle {
|
||||
x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2)))
|
||||
y: (parent.height - height) / 2
|
||||
width: 24
|
||||
height: 24
|
||||
radius: parent.radius
|
||||
|
||||
visible: !loading
|
||||
|
||||
color: {
|
||||
if (!control.enabled) {
|
||||
return control.colorScheme.field_disabled
|
||||
}
|
||||
|
||||
if (control.checked) {
|
||||
if (control.hovered || control.activeFocus) {
|
||||
return control.colorScheme.interaction_norm_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.interaction_norm
|
||||
}
|
||||
|
||||
if (control.hovered || control.activeFocus) {
|
||||
return control.colorScheme.field_hover
|
||||
}
|
||||
|
||||
return control.colorScheme.field_norm
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
width: 16
|
||||
height: 16
|
||||
sourceSize.width: 16
|
||||
sourceSize.height: 16
|
||||
color: "#FFFFFF"
|
||||
source: "../icons/ic-check.svg"
|
||||
visible: control.checked
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
enabled: !control.down
|
||||
SmoothedAnimation { velocity: 200 }
|
||||
}
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
id: loadingImage
|
||||
x: parent.width - width
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
width: 18
|
||||
height: 18
|
||||
sourceSize.width: 18
|
||||
sourceSize.height: 18
|
||||
color: control.colorScheme.interaction_norm_hover
|
||||
source: "../icons/Loader_16.svg"
|
||||
visible: control.loading
|
||||
|
||||
RotationAnimation {
|
||||
target: loadingImage
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
from: 0
|
||||
to: 360
|
||||
direction: RotationAnimation.Clockwise
|
||||
running: control.loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: CheckLabel {
|
||||
id: label
|
||||
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
|
||||
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
|
||||
|
||||
text: control.text
|
||||
|
||||
color: control.enabled || control.loading ? control.colorScheme.text_norm : control.colorScheme.text_disabled
|
||||
|
||||
font.family: Style.font_family
|
||||
font.weight: Style.fontWeight_400
|
||||
font.pixelSize: Style.body_font_size
|
||||
lineHeight: Style.body_line_height
|
||||
lineHeightMode: Text.FixedHeight
|
||||
font.letterSpacing: Style.body_letter_spacing
|
||||
}
|
||||
}
|
||||
370
internal/frontend/qt6/qml/Proton/TextArea.qml
Normal file
@ -0,0 +1,370 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import "." as Proton
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property alias background: control.background
|
||||
property alias bottomInset: control.bottomInset
|
||||
//property alias flickable: control.flickable
|
||||
property alias focusReason: control.focusReason
|
||||
property alias hoverEnabled: control.hoverEnabled
|
||||
property alias hovered: control.hovered
|
||||
property alias implicitBackgroundHeight: control.implicitBackgroundHeight
|
||||
property alias implicitBackgroundWidth: control.implicitBackgroundWidth
|
||||
property alias leftInset: control.leftInset
|
||||
property alias palette: control.palette
|
||||
property alias placeholderText: control.placeholderText
|
||||
property alias placeholderTextColor: control.placeholderTextColor
|
||||
property alias rightInset: control.rightInset
|
||||
property alias topInset: control.topInset
|
||||
property alias activeFocusOnPress: control.activeFocusOnPress
|
||||
property alias baseUrl: control.baseUrl
|
||||
property alias bottomPadding: control.bottomPadding
|
||||
property alias canPaste: control.canPaste
|
||||
property alias canRedo: control.canRedo
|
||||
property alias canUndo: control.canUndo
|
||||
property alias color: control.color
|
||||
property alias contentHeight: control.contentHeight
|
||||
property alias contentWidth: control.contentWidth
|
||||
property alias cursorDelegate: control.cursorDelegate
|
||||
property alias cursorPosition: control.cursorPosition
|
||||
property alias cursorRectangle: control.cursorRectangle
|
||||
property alias cursorVisible: control.cursorVisible
|
||||
property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment
|
||||
property alias font: control.font
|
||||
property alias horizontalAlignment: control.horizontalAlignment
|
||||
property alias hoveredLink: control.hoveredLink
|
||||
property alias inputMethodComposing: control.inputMethodComposing
|
||||
property alias inputMethodHints: control.inputMethodHints
|
||||
property alias leftPadding: control.leftPadding
|
||||
property alias length: control.length
|
||||
property alias lineCount: control.lineCount
|
||||
property alias mouseSelectionMode: control.mouseSelectionMode
|
||||
property alias overwriteMode: control.overwriteMode
|
||||
property alias padding: control.padding
|
||||
property alias persistentSelection: control.persistentSelection
|
||||
property alias preeditText: control.preeditText
|
||||
property alias readOnly: control.readOnly
|
||||
property alias renderType: control.renderType
|
||||
property alias rightPadding: control.rightPadding
|
||||
property alias selectByKeyboard: control.selectByKeyboard
|
||||
property alias selectByMouse: control.selectByMouse
|
||||
property alias selectedText: control.selectedText
|
||||
property alias selectedTextColor: control.selectedTextColor
|
||||
property alias selectionColor: control.selectionColor
|
||||
property alias selectionEnd: control.selectionEnd
|
||||
property alias selectionStart: control.selectionStart
|
||||
property alias tabStopDistance: control.tabStopDistance
|
||||
property alias text: control.text
|
||||
property alias textDocument: control.textDocument
|
||||
property alias textFormat: control.textFormat
|
||||
property alias textMargin: control.textMargin
|
||||
property alias topPadding: control.topPadding
|
||||
// We are using our own type of validators. It should be a function
|
||||
// returning an error string in case of error and undefined if no error
|
||||
property var validator
|
||||
property alias verticalAlignment: control.verticalAlignment
|
||||
property alias wrapMode: control.wrapMode
|
||||
|
||||
implicitWidth: children[0].implicitWidth
|
||||
implicitHeight: children[0].implicitHeight
|
||||
|
||||
property alias label: label.text
|
||||
property alias hint: hint.text
|
||||
property string assistiveText
|
||||
property string errorString
|
||||
|
||||
property bool error: false
|
||||
|
||||
signal editingFinished()
|
||||
|
||||
function append(text) { return control.append(text) }
|
||||
function clear() { return control.clear() }
|
||||
function copy() { return control.copy() }
|
||||
function cut() { return control.cut() }
|
||||
function deselect() { return control.deselect() }
|
||||
function getFormattedText(start, end) { return control.getFormattedText(start, end) }
|
||||
function getText(start, end) { return control.getText(start, end) }
|
||||
function insert(position, text) { return control.insert(position, text) }
|
||||
function isRightToLeft(start, end) { return control.isRightToLeft(start, end) }
|
||||
function linkAt(x, y) { return control.linkAt(x, y) }
|
||||
function moveCursorSelection(position, mode) { return control.moveCursorSelection(position, mode) }
|
||||
function paste() { return control.paste() }
|
||||
function positionAt(x, y) { return control.positionAt(x, y) }
|
||||
function positionToRectangle(position) { return control.positionToRectangle(position) }
|
||||
function redo() { return control.redo() }
|
||||
function remove(start, end) { return control.remove(start, end) }
|
||||
function select(start, end) { return control.select(start, end) }
|
||||
function selectAll() { return control.selectAll() }
|
||||
function selectWord() { return control.selectWord() }
|
||||
function undo() { return control.undo() }
|
||||
|
||||
// Calculates the height of the component to make exactly lineNum visible in edit area
|
||||
function heightForLinesVisible(lineNum) {
|
||||
var totalHeight = 0
|
||||
totalHeight += headerLayout.height
|
||||
totalHeight += footerLayout.height
|
||||
totalHeight += control.topPadding + control.bottomPadding
|
||||
totalHeight += lineNum * fontMetrics.height
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: control.font
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
id: headerLayout
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||
|
||||
type: Proton.Label.LabelType.Body_semibold
|
||||
}
|
||||
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: hint
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
|
||||
horizontalAlignment: Text.AlignRight
|
||||
type: Proton.Label.LabelType.Caption
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: controlView
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
clip: true
|
||||
|
||||
T.TextArea {
|
||||
id: control
|
||||
|
||||
implicitWidth: Math.max(
|
||||
contentWidth + leftPadding + rightPadding,
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
placeholder.implicitWidth + leftPadding + rightPadding
|
||||
)
|
||||
implicitHeight: Math.max(
|
||||
contentHeight + topPadding + bottomPadding,
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
placeholder.implicitHeight + topPadding + bottomPadding
|
||||
)
|
||||
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
|
||||
font.family: Style.font_family
|
||||
font.weight: Style.fontWeight_400
|
||||
font.pixelSize: Style.body_font_size
|
||||
font.letterSpacing: Style.body_letter_spacing
|
||||
|
||||
color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||
placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled
|
||||
selectionColor: control.palette.highlight
|
||||
selectedTextColor: control.palette.highlightedText
|
||||
|
||||
onEditingFinished: root.editingFinished()
|
||||
|
||||
wrapMode: TextInput.Wrap
|
||||
|
||||
// enforcing default focus here within component
|
||||
focus: root.focus
|
||||
|
||||
KeyNavigation.priority: root.KeyNavigation.priority
|
||||
KeyNavigation.backtab: root.KeyNavigation.backtab
|
||||
KeyNavigation.tab: root.KeyNavigation.tab
|
||||
KeyNavigation.up: root.KeyNavigation.up
|
||||
KeyNavigation.down: root.KeyNavigation.down
|
||||
KeyNavigation.left: root.KeyNavigation.left
|
||||
KeyNavigation.right: root.KeyNavigation.right
|
||||
|
||||
selectByMouse: true
|
||||
|
||||
cursorDelegate: Rectangle {
|
||||
id: cursor
|
||||
width: 1
|
||||
color: root.colorScheme.interaction_norm
|
||||
visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
onCursorPositionChanged: {
|
||||
// keep a moving cursor visible
|
||||
cursor.opacity = 1
|
||||
timer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
running: control.activeFocus && !control.readOnly
|
||||
repeat: true
|
||||
interval: Qt.styleHints.cursorFlashTime / 2
|
||||
onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0
|
||||
// force the cursor visible when gaining focus
|
||||
onRunningChanged: cursor.opacity = 1
|
||||
}
|
||||
}
|
||||
|
||||
PlaceholderText {
|
||||
id: placeholder
|
||||
x: control.leftPadding
|
||||
y: control.topPadding
|
||||
width: control.width - (control.leftPadding + control.rightPadding)
|
||||
height: control.height - (control.topPadding + control.bottomPadding)
|
||||
|
||||
text: control.placeholderText
|
||||
font: control.font
|
||||
color: control.placeholderTextColor
|
||||
verticalAlignment: control.verticalAlignment
|
||||
visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter)
|
||||
elide: Text.ElideRight
|
||||
renderType: control.renderType
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
radius: Style.input_radius
|
||||
visible: true
|
||||
color: root.colorScheme.background_norm
|
||||
border.color: {
|
||||
if (!control.enabled) {
|
||||
return root.colorScheme.field_disabled
|
||||
}
|
||||
|
||||
if (control.activeFocus) {
|
||||
return root.colorScheme.interaction_norm
|
||||
}
|
||||
|
||||
if (root.error) {
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
if (control.hovered) {
|
||||
return root.colorScheme.field_hover
|
||||
}
|
||||
|
||||
return root.colorScheme.field_norm
|
||||
}
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: footerLayout
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
ColorImage {
|
||||
id: errorIcon
|
||||
|
||||
Layout.rightMargin: 4
|
||||
|
||||
visible: root.error && (assistiveText.text.length > 0)
|
||||
source: "../icons/ic-exclamation-circle-filled.svg"
|
||||
color: root.colorScheme.signal_danger
|
||||
height: assistiveText.height
|
||||
sourceSize.height: assistiveText.height
|
||||
}
|
||||
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: assistiveText
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: root.error ? root.errorString : root.assistiveText
|
||||
|
||||
color: {
|
||||
if (!root.enabled) {
|
||||
return root.colorScheme.text_disabled
|
||||
}
|
||||
|
||||
if (root.error) {
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
return root.colorScheme.text_weak
|
||||
}
|
||||
|
||||
type: root.error ? Proton.Label.LabelType.Caption_semibold : Proton.Label.LabelType.Caption
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property bool validateOnEditingFinished: true
|
||||
onEditingFinished: {
|
||||
if (!validateOnEditingFinished) {
|
||||
return
|
||||
}
|
||||
validate()
|
||||
}
|
||||
|
||||
function validate() {
|
||||
if (validator === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
var error = validator(text)
|
||||
|
||||
if (error) {
|
||||
root.error = true
|
||||
root.errorString = error
|
||||
} else {
|
||||
root.error = false
|
||||
root.errorString = ""
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
root.error = false
|
||||
root.errorString = ""
|
||||
}
|
||||
}
|
||||
381
internal/frontend/qt6/qml/Proton/TextField.qml
Normal file
@ -0,0 +1,381 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import "." as Proton
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property alias background: control.background
|
||||
property alias bottomInset: control.bottomInset
|
||||
property alias focusReason: control.focusReason
|
||||
property alias hoverEnabled: control.hoverEnabled
|
||||
property alias hovered: control.hovered
|
||||
property alias implicitBackgroundHeight: control.implicitBackgroundHeight
|
||||
property alias implicitBackgroundWidth: control.implicitBackgroundWidth
|
||||
property alias leftInset: control.leftInset
|
||||
property alias palette: control.palette
|
||||
property alias placeholderText: control.placeholderText
|
||||
property alias placeholderTextColor: control.placeholderTextColor
|
||||
property alias rightInset: control.rightInset
|
||||
property alias topInset: control.topInset
|
||||
property alias acceptableInput: control.acceptableInput
|
||||
property alias activeFocusOnPress: control.activeFocusOnPress
|
||||
property alias autoScroll: control.autoScroll
|
||||
property alias bottomPadding: control.bottomPadding
|
||||
property alias canPaste: control.canPaste
|
||||
property alias canRedo: control.canRedo
|
||||
property alias canUndo: control.canUndo
|
||||
property alias color: control.color
|
||||
//property alias contentHeight: control.contentHeight
|
||||
//property alias contentWidth: control.contentWidth
|
||||
property alias cursorDelegate: control.cursorDelegate
|
||||
property alias cursorPosition: control.cursorPosition
|
||||
property alias cursorRectangle: control.cursorRectangle
|
||||
property alias cursorVisible: control.cursorVisible
|
||||
property alias displayText: control.displayText
|
||||
property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment
|
||||
property alias font: control.font
|
||||
property alias horizontalAlignment: control.horizontalAlignment
|
||||
property alias inputMask: control.inputMask
|
||||
property alias inputMethodComposing: control.inputMethodComposing
|
||||
property alias inputMethodHints: control.inputMethodHints
|
||||
property alias leftPadding: control.leftPadding
|
||||
property alias length: control.length
|
||||
property alias maximumLength: control.maximumLength
|
||||
property alias mouseSelectionMode: control.mouseSelectionMode
|
||||
property alias overwriteMode: control.overwriteMode
|
||||
property alias padding: control.padding
|
||||
property alias passwordCharacter: control.passwordCharacter
|
||||
property alias passwordMaskDelay: control.passwordMaskDelay
|
||||
property alias persistentSelection: control.persistentSelection
|
||||
property alias preeditText: control.preeditText
|
||||
property alias readOnly: control.readOnly
|
||||
property alias renderType: control.renderType
|
||||
property alias rightPadding: control.rightPadding
|
||||
property alias selectByMouse: control.selectByMouse
|
||||
property alias selectedText: control.selectedText
|
||||
property alias selectedTextColor: control.selectedTextColor
|
||||
property alias selectionColor: control.selectionColor
|
||||
property alias selectionEnd: control.selectionEnd
|
||||
property alias selectionStart: control.selectionStart
|
||||
property alias text: control.text
|
||||
// We are using our own type of validators. It should be a function
|
||||
// returning an error string in case of error and undefined if no error
|
||||
property var validator
|
||||
property alias verticalAlignment: control.verticalAlignment
|
||||
property alias wrapMode: control.wrapMode
|
||||
|
||||
implicitWidth: children[0].implicitWidth
|
||||
implicitHeight: children[0].implicitHeight
|
||||
|
||||
property alias label: label.text
|
||||
property alias hint: hint.text
|
||||
property string assistiveText
|
||||
property string errorString
|
||||
|
||||
property int echoMode: TextInput.Normal
|
||||
|
||||
property bool error: false
|
||||
|
||||
signal accepted()
|
||||
signal editingFinished()
|
||||
signal textEdited()
|
||||
|
||||
function clear() { control.clear() }
|
||||
function copy() { control.copy() }
|
||||
function cut() { control.cut() }
|
||||
function deselect() { control.deselect() }
|
||||
function ensureVisible(position) { control.ensureVisible(position) }
|
||||
function getText(start, end) { control.getText(start, end) }
|
||||
function insert(position, text) { control.insert(position, text) }
|
||||
function isRightToLeft(start, end) { control.isRightToLeft(start, end) }
|
||||
function moveCursorSelection(position, mode) { control.moveCursorSelection(position, mode) }
|
||||
function paste() { control.paste() }
|
||||
function positionAt(x, y, position) { control.positionAt(x, y, position) }
|
||||
function positionToRectangle(pos) { control.positionToRectangle(pos) }
|
||||
function redo() { control.redo() }
|
||||
function remove(start, end) { control.remove(start, end) }
|
||||
function select(start, end) { control.select(start, end) }
|
||||
function selectAll() { control.selectAll() }
|
||||
function selectWord() { control.selectWord() }
|
||||
function undo() { control.undo() }
|
||||
function forceActiveFocus() { control.forceActiveFocus() }
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: label
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
type: Proton.Label.LabelType.Body_semibold
|
||||
}
|
||||
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: hint
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled
|
||||
horizontalAlignment: Text.AlignRight
|
||||
type: Proton.Label.LabelType.Caption
|
||||
}
|
||||
}
|
||||
|
||||
// Background is moved away from within control to cover eye button as well.
|
||||
// In case it will remain as control background property - control's width
|
||||
// will be adjusted to background's width making text field and eye button overlap
|
||||
Rectangle {
|
||||
id: background
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
radius: Style.input_radius
|
||||
visible: true
|
||||
color: root.colorScheme.background_norm
|
||||
border.color: {
|
||||
if (!control.enabled) {
|
||||
return root.colorScheme.field_disabled
|
||||
}
|
||||
|
||||
if (control.activeFocus) {
|
||||
return root.colorScheme.interaction_norm
|
||||
}
|
||||
|
||||
if (root.error) {
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
if (control.hovered) {
|
||||
return root.colorScheme.field_hover
|
||||
}
|
||||
|
||||
return root.colorScheme.field_norm
|
||||
}
|
||||
border.width: 1
|
||||
|
||||
implicitWidth: children[0].implicitWidth
|
||||
implicitHeight: children[0].implicitHeight
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
T.TextField {
|
||||
id: control
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitWidth: implicitBackgroundWidth + leftInset + rightInset
|
||||
|| Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
contentHeight + topPadding + bottomPadding,
|
||||
placeholder.implicitHeight + topPadding + bottomPadding)
|
||||
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
|
||||
font.family: Style.font_family
|
||||
font.weight: Style.fontWeight_400
|
||||
font.pixelSize: Style.body_font_size
|
||||
font.letterSpacing: Style.body_letter_spacing
|
||||
|
||||
color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||
placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled
|
||||
selectionColor: control.palette.highlight
|
||||
selectedTextColor: control.palette.highlightedText
|
||||
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
echoMode: eyeButton.checked ? TextInput.Normal : root.echoMode
|
||||
|
||||
// enforcing default focus here within component
|
||||
focus: true
|
||||
|
||||
KeyNavigation.priority: root.KeyNavigation.priority
|
||||
KeyNavigation.backtab: root.KeyNavigation.backtab
|
||||
KeyNavigation.tab: root.KeyNavigation.tab
|
||||
KeyNavigation.up: root.KeyNavigation.up
|
||||
KeyNavigation.down: root.KeyNavigation.down
|
||||
KeyNavigation.left: root.KeyNavigation.left
|
||||
KeyNavigation.right: root.KeyNavigation.right
|
||||
|
||||
selectByMouse: true
|
||||
|
||||
cursorDelegate: Rectangle {
|
||||
id: cursor
|
||||
width: 1
|
||||
color: root.colorScheme.interaction_norm
|
||||
visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
onCursorPositionChanged: {
|
||||
// keep a moving cursor visible
|
||||
cursor.opacity = 1
|
||||
timer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
running: control.activeFocus && !control.readOnly
|
||||
repeat: true
|
||||
interval: Qt.styleHints.cursorFlashTime / 2
|
||||
onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0
|
||||
// force the cursor visible when gaining focus
|
||||
onRunningChanged: cursor.opacity = 1
|
||||
}
|
||||
}
|
||||
|
||||
PlaceholderText {
|
||||
id: placeholder
|
||||
x: control.leftPadding
|
||||
y: control.topPadding
|
||||
width: control.width - (control.leftPadding + control.rightPadding)
|
||||
height: control.height - (control.topPadding + control.bottomPadding)
|
||||
|
||||
text: control.placeholderText
|
||||
font: control.font
|
||||
color: control.placeholderTextColor
|
||||
verticalAlignment: control.verticalAlignment
|
||||
visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter)
|
||||
elide: Text.ElideRight
|
||||
renderType: control.renderType
|
||||
}
|
||||
|
||||
background: Item {
|
||||
implicitWidth: 80
|
||||
implicitHeight: 36
|
||||
visible: false
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
root.accepted()
|
||||
}
|
||||
onEditingFinished: {
|
||||
root.editingFinished()
|
||||
}
|
||||
onTextEdited: {
|
||||
root.textEdited()
|
||||
}
|
||||
}
|
||||
|
||||
Proton.Button {
|
||||
colorScheme: root.colorScheme
|
||||
id: eyeButton
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
visible: root.echoMode === TextInput.Password
|
||||
icon.color: control.color
|
||||
checkable: true
|
||||
icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
ColorImage {
|
||||
id: errorIcon
|
||||
|
||||
Layout.rightMargin: 4
|
||||
|
||||
visible: root.error && (assistiveText.text.length > 0)
|
||||
source: "../icons/ic-exclamation-circle-filled.svg"
|
||||
color: root.colorScheme.signal_danger
|
||||
height: assistiveText.height
|
||||
sourceSize.height: assistiveText.height
|
||||
}
|
||||
|
||||
Proton.Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: assistiveText
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: root.error ? root.errorString : root.assistiveText
|
||||
|
||||
color: {
|
||||
if (!root.enabled) {
|
||||
return root.colorScheme.text_disabled
|
||||
}
|
||||
|
||||
if (root.error) {
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
|
||||
return root.colorScheme.text_weak
|
||||
}
|
||||
|
||||
type: root.error ? Proton.Label.LabelType.Caption_semibold : Proton.Label.LabelType.Caption
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property bool validateOnEditingFinished: true
|
||||
onEditingFinished: {
|
||||
if (!validateOnEditingFinished) {
|
||||
return
|
||||
}
|
||||
validate()
|
||||
}
|
||||
|
||||
function validate() {
|
||||
if (validator === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
var error = validator(text)
|
||||
|
||||
if (error) {
|
||||
root.error = true
|
||||
root.errorString = error
|
||||
} else {
|
||||
root.error = false
|
||||
root.errorString = ""
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
root.error = false
|
||||
root.errorString = ""
|
||||
}
|
||||
}
|
||||
113
internal/frontend/qt6/qml/Proton/Toggle.qml
Normal file
@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var colorScheme
|
||||
property bool checked
|
||||
property bool hovered
|
||||
property bool loading
|
||||
|
||||
signal clicked
|
||||
|
||||
property bool _disabled: !enabled
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
implicitWidth: 40
|
||||
implicitHeight: 24
|
||||
|
||||
radius: width/2
|
||||
color: {
|
||||
if (root.loading) return "transparent"
|
||||
if (root._disabled) return root.colorScheme.background_strong
|
||||
return root.colorScheme.background_norm
|
||||
}
|
||||
border {
|
||||
width: 1
|
||||
color: (root._disabled || root.loading) ? "transparent" : colorScheme.field_norm
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: indicator.verticalCenter
|
||||
anchors.left: indicator.left
|
||||
anchors.leftMargin: root.checked ? 16 : 0
|
||||
width: 24
|
||||
height: 24
|
||||
radius: width/2
|
||||
color: {
|
||||
if (root.loading) return "transparent"
|
||||
if (root._disabled) return root.colorScheme.field_disabled
|
||||
|
||||
if (root.checked) {
|
||||
if (root.hovered) return root.colorScheme.interaction_norm_hover
|
||||
return root.colorScheme.interaction_norm
|
||||
} else {
|
||||
if (root.hovered) return root.colorScheme.field_hover
|
||||
return root.colorScheme.field_norm
|
||||
}
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
anchors.centerIn: parent
|
||||
source: "../icons/ic-check.svg"
|
||||
color: root.colorScheme.background_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
sourceSize.height: root.colorScheme.body_font_size
|
||||
visible: root.checked
|
||||
}
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
id: loader
|
||||
anchors.centerIn: parent
|
||||
source: "../icons/Loader_16.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
sourceSize.height: root.colorScheme.body_font_size
|
||||
visible: root.loading
|
||||
|
||||
RotationAnimation {
|
||||
target: loader
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
from: 0
|
||||
to: 360
|
||||
direction: RotationAnimation.Clockwise
|
||||
running: root.loading
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: indicator
|
||||
hoverEnabled: true
|
||||
onEntered: {root.hovered = true }
|
||||
onExited: {root.hovered = false }
|
||||
onClicked: { if (root.enabled) root.clicked();}
|
||||
onPressed: {root.hovered = true }
|
||||
onReleased: { root.hovered = containsMouse }
|
||||
}
|
||||
}
|
||||
}
|
||||
37
internal/frontend/qt6/qml/Proton/qmldir
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2021 Proton AG
|
||||
#
|
||||
# This file is part of Proton Mail Bridge.
|
||||
#
|
||||
# Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
module QQtQuick.Controls.Proton
|
||||
depends QtQuick.Controls 2.12
|
||||
|
||||
singleton ProtonStyle 4.0 Style.qml
|
||||
ColorScheme 4.0 ColorScheme.qml
|
||||
|
||||
ApplicationWindow 4.0 ApplicationWindow.qml
|
||||
Button 4.0 Button.qml
|
||||
CheckBox 4.0 CheckBox.qml
|
||||
ComboBox 4.0 ComboBox.qml
|
||||
Dialog 4.0 Dialog.qml
|
||||
Label 4.0 Label.qml
|
||||
Menu 4.0 Menu.qml
|
||||
MenuItem 4.0 MenuItem.qml
|
||||
Popup 4.0 Popup.qml
|
||||
RadioButton 4.0 RadioButton.qml
|
||||
Switch 4.0 Switch.qml
|
||||
TextArea 4.0 TextArea.qml
|
||||
TextField 4.0 TextField.qml
|
||||
Toggle 4.0 Toggle.qml
|
||||
120
internal/frontend/qt6/qml/SMTPSettings.qml
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
fillHeight: false
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection mode")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Changes require reconfiguration of email client. Bridge will automatically restart.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: this.parent.Layout.maximumWidth
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
ButtonGroup{ id: protocolSelection }
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection security")
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: sslButton
|
||||
colorScheme: root.colorScheme
|
||||
ButtonGroup.group: protocolSelection
|
||||
text: qsTr("SSL")
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: starttlsButton
|
||||
colorScheme: root.colorScheme
|
||||
ButtonGroup.group: protocolSelection
|
||||
text: qsTr("STARTTLS")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
onClicked: {
|
||||
submitButton.loading = true
|
||||
root.submit()
|
||||
}
|
||||
|
||||
enabled: sslButton.checked !== root.backend.useSSLforSMTP
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onToggleUseSSLFinished: submitButton.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
function submit(){
|
||||
submitButton.loading = true
|
||||
root.backend.toggleUseSSLforSMTP(sslButton.checked)
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
sslButton.checked = root.backend.useSSLforSMTP
|
||||
starttlsButton.checked = !root.backend.useSSLforSMTP
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
root.setDefaultValues()
|
||||
}
|
||||
}
|
||||
118
internal/frontend/qt6/qml/SettingsItem.qml
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var colorScheme
|
||||
|
||||
property string text: "Text"
|
||||
property string actionText: "Action"
|
||||
property string actionIcon: ""
|
||||
property string description: "Lorem ipsum dolor sit amet"
|
||||
property alias descriptionWrap: descriptionLabel.wrapMode
|
||||
property var type: SettingsItem.ActionType.Toggle
|
||||
|
||||
property bool checked: true
|
||||
property bool loading: false
|
||||
property bool showSeparator: true
|
||||
|
||||
property var _bottomMargin: 20
|
||||
property var _lineWidth: 1
|
||||
property var _toggleTopMargin: 6
|
||||
|
||||
signal clicked
|
||||
|
||||
enum ActionType {
|
||||
Toggle = 1, Button = 2, PrimaryButton = 3
|
||||
}
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 16
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: root._bottomMargin
|
||||
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
id: mainLabel
|
||||
colorScheme: root.colorScheme
|
||||
text: root.text
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Label {
|
||||
id: descriptionLabel
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
colorScheme: root.colorScheme
|
||||
text: root.description
|
||||
color: root.colorScheme.text_weak
|
||||
}
|
||||
}
|
||||
|
||||
Toggle {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: root._toggleTopMargin
|
||||
id: toggle
|
||||
colorScheme: root.colorScheme
|
||||
visible: root.type === SettingsItem.ActionType.Toggle
|
||||
|
||||
checked: root.checked
|
||||
loading: root.loading
|
||||
onClicked: { if (!root.loading) root.clicked() }
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
id: button
|
||||
colorScheme: root.colorScheme
|
||||
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
|
||||
text: root.actionText + (root.actionIcon != "" ? " " : "")
|
||||
loading: root.loading
|
||||
icon.source: root.actionIcon
|
||||
onClicked: { if (!root.loading) root.clicked() }
|
||||
secondary: root.type !== SettingsItem.PrimaryButton
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
color: colorScheme.border_weak
|
||||
height: root._lineWidth
|
||||
visible: root.showSeparator
|
||||
}
|
||||
}
|
||||
99
internal/frontend/qt6/qml/SettingsView.qml
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var colorScheme
|
||||
property var backend
|
||||
default property alias items: content.children
|
||||
|
||||
signal back()
|
||||
|
||||
property int _leftMargin: 64
|
||||
property int _rightMargin: 64
|
||||
property int _topMargin: 32
|
||||
property int _bottomMargin: 32
|
||||
property int _spacing: 20
|
||||
|
||||
// fillHeight indicates whether the SettingsView should fill all available explicit height set
|
||||
property bool fillHeight: false
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
clip: true
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
// can't use parent here because parent is not ScrollView (Flickable inside contentItem inside ScrollView)
|
||||
width: scrollView.availableWidth
|
||||
height: scrollView.availableHeight
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
// do not set implicitWidth because implicit width of ColumnLayout will be equal to maximum implicit width of
|
||||
// internal items. And if one of internal items would be a Text or Label - implicit width of those is always
|
||||
// equal to non-wrapped text (i.e. one line only). That will lead to enabling horizontal scroll when not needed
|
||||
implicitWidth: width
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: root._spacing
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.topMargin: root._topMargin
|
||||
Layout.bottomMargin: root._bottomMargin
|
||||
Layout.leftMargin: root._leftMargin
|
||||
Layout.rightMargin: root._rightMargin
|
||||
}
|
||||
|
||||
Item {
|
||||
id: filler
|
||||
Layout.fillHeight: true
|
||||
visible: !root.fillHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: root._topMargin
|
||||
leftMargin: (root._leftMargin-backButton.width) / 2
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: root.back()
|
||||
icon.source: "icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
horizontalPadding: 8
|
||||
}
|
||||
}
|
||||
317
internal/frontend/qt6/qml/SetupGuide.qml
Normal file
@ -0,0 +1,317 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id:root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
property var user
|
||||
property string address
|
||||
|
||||
signal dismissed()
|
||||
signal finished()
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
|
||||
ListModel {
|
||||
id: clients
|
||||
property string name : "Apple Mail"
|
||||
property string iconSource : "./icons/ic-apple-mail.svg"
|
||||
property bool haveAutoSetup: true
|
||||
property string link: "https://protonmail.com/bridge/applemail"
|
||||
|
||||
Component.onCompleted : {
|
||||
if (root.backend.goos == "darwin") {
|
||||
append({
|
||||
"name" : "Apple Mail",
|
||||
"iconSource" : "./icons/ic-apple-mail.svg",
|
||||
"haveAutoSetup" : true,
|
||||
"link" : "https://protonmail.com/bridge/applemail"
|
||||
})
|
||||
append({
|
||||
"name" : "Microsoft Outlook",
|
||||
"iconSource" : "./icons/ic-microsoft-outlook.svg",
|
||||
"haveAutoSetup" : false,
|
||||
"link" : "https://protonmail.com/bridge/outlook2019-mac"
|
||||
})
|
||||
}
|
||||
if (root.backend.goos == "windows") {
|
||||
append({
|
||||
"name" : "Microsoft Outlook",
|
||||
"iconSource" : "./icons/ic-microsoft-outlook.svg",
|
||||
"haveAutoSetup" : false,
|
||||
"link" : "https://protonmail.com/bridge/outlook2019"
|
||||
})
|
||||
}
|
||||
|
||||
append({
|
||||
"name" : "Mozilla Thunderbird",
|
||||
"iconSource" : "./icons/ic-mozilla-thunderbird.svg",
|
||||
"haveAutoSetup" : false,
|
||||
"link" : "https://protonmail.com/bridge/thunderbird"
|
||||
})
|
||||
|
||||
append({
|
||||
"name" : "Other",
|
||||
"iconSource" : "./icons/ic-other-mail-clients.svg",
|
||||
"haveAutoSetup" : false,
|
||||
"link" : "https://protonmail.com/bridge/clients"
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: root
|
||||
color: root.colorScheme.background_norm
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: guidePages
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 80
|
||||
anchors.rightMargin: 80
|
||||
anchors.topMargin: 30
|
||||
anchors.bottomMargin: 70
|
||||
|
||||
|
||||
ColumnLayout { // 0: Client selection
|
||||
id: clientView
|
||||
Layout.fillHeight: true
|
||||
|
||||
property int columnWidth: 268
|
||||
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Setting up email client")
|
||||
type: Label.LabelType.Heading
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: address
|
||||
color: root.colorScheme.text_weak
|
||||
type: Label.LabelType.Lead
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 32-clientView.spacing
|
||||
spacing: 24
|
||||
|
||||
ColumnLayout {
|
||||
id: clientColumn
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Label {
|
||||
id: labelA
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Choose an email client")
|
||||
type: Label.LabelType.Body_semibold
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: clientList
|
||||
Layout.fillHeight: true
|
||||
width: clientView.columnWidth
|
||||
|
||||
model: clients
|
||||
|
||||
highlight: Rectangle {
|
||||
color: root.colorScheme.interaction_default_active
|
||||
radius: ProtonStyle.context_item_radius
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: clientRow.width
|
||||
implicitHeight: clientRow.height
|
||||
|
||||
ColumnLayout {
|
||||
id: clientRow
|
||||
width: clientList.width
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ColorImage {
|
||||
source: model.iconSource
|
||||
height: 36
|
||||
sourceSize.height: 36
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.leftMargin: 12
|
||||
text: model.name
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
clientList.currentIndex = index
|
||||
if (!model.haveAutoSetup) {
|
||||
root.setupAction(1,index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: actionColumn
|
||||
visible: clientList.currentIndex >= 0 && clients.get(clientList.currentIndex).haveAutoSetup
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Choose configuration mode")
|
||||
type: Label.LabelType.Body_semibold
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: actionList
|
||||
Layout.fillHeight: true
|
||||
width: clientView.columnWidth
|
||||
|
||||
model: [
|
||||
qsTr("Configure automatically"),
|
||||
qsTr("Configure manually"),
|
||||
]
|
||||
|
||||
highlight: Rectangle {
|
||||
color: root.colorScheme.interaction_default_active
|
||||
radius: ProtonStyle.context_item_radius
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: children[0].width
|
||||
implicitHeight: children[0].height
|
||||
|
||||
ColumnLayout {
|
||||
width: actionList.width
|
||||
|
||||
Label {
|
||||
Layout.topMargin: 20
|
||||
Layout.bottomMargin: 20
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
colorScheme: root.colorScheme
|
||||
text: modelData
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
actionList.currentIndex = index
|
||||
root.setupAction(index,clientList.currentIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Set up later")
|
||||
flat: true
|
||||
|
||||
onClicked: {
|
||||
root.setupAction(-1,-1)
|
||||
if (user) {
|
||||
user.setupGuideSeen = true
|
||||
}
|
||||
root.dismissed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupAction(actionID,clientID){
|
||||
if (user) {
|
||||
user.setupGuideSeen = true
|
||||
}
|
||||
|
||||
switch (actionID) {
|
||||
case -1: root.dismissed(); break; // dismiss
|
||||
case 0: // automatic
|
||||
if (user) {
|
||||
switch (clientID) {
|
||||
case 0:
|
||||
root.user.configureAppleMail(root.address)
|
||||
break;
|
||||
}
|
||||
}
|
||||
root.finished()
|
||||
break;
|
||||
case 1: // manual
|
||||
var clientObj = clients.get(clientID)
|
||||
if (clientObj != undefined && clientObj.link != "" ) {
|
||||
Qt.openUrlExternally(clientObj.link)
|
||||
} else {
|
||||
console.log("unexpected client index", actionID, clientID)
|
||||
}
|
||||
root.finished();
|
||||
break;
|
||||
default:
|
||||
console.log("unexpected client setup action", actionID, clientID)
|
||||
}
|
||||
}
|
||||
|
||||
function reset(){
|
||||
guidePages.currentIndex = 0
|
||||
clientList.currentIndex = -1
|
||||
actionList.currentIndex = -1
|
||||
}
|
||||
}
|
||||
457
internal/frontend/qt6/qml/SignIn.qml
Normal file
@ -0,0 +1,457 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
function reset() {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
|
||||
}
|
||||
|
||||
function abort() {
|
||||
root.reset()
|
||||
root.backend.loginAbort(usernameTextField.text)
|
||||
}
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
property var backend
|
||||
|
||||
property alias username: usernameTextField.text
|
||||
state: "Page 1"
|
||||
|
||||
property alias currentIndex: stackLayout.currentIndex
|
||||
|
||||
StackLayout {
|
||||
id: stackLayout
|
||||
anchors.fill: parent
|
||||
|
||||
function loginFailed() {
|
||||
signInButton.loading = false
|
||||
|
||||
usernameTextField.enabled = true
|
||||
usernameTextField.error = true
|
||||
|
||||
passwordTextField.enabled = true
|
||||
passwordTextField.error = true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
console.assert(stackLayout.currentIndex == 0, "Unexpected loginUsernamePasswordError")
|
||||
console.assert(signInButton.loading == true, "Unexpected loginUsernamePasswordError")
|
||||
|
||||
stackLayout.loginFailed()
|
||||
if (errorMsg!="") errorLabel.text = errorMsg
|
||||
else errorLabel.text = qsTr("Incorrect login credentials")
|
||||
}
|
||||
|
||||
onLoginFreeUserError: {
|
||||
console.assert(stackLayout.currentIndex == 0, "Unexpected loginFreeUserError")
|
||||
stackLayout.loginFailed()
|
||||
}
|
||||
|
||||
onLoginConnectionError: {
|
||||
if (stackLayout.currentIndex == 0 ) {
|
||||
stackLayout.loginFailed()
|
||||
}
|
||||
}
|
||||
|
||||
onLogin2FARequested: {
|
||||
console.assert(stackLayout.currentIndex == 0, "Unexpected login2FARequested")
|
||||
twoFactorUsernameLabel.text = username
|
||||
stackLayout.currentIndex = 1
|
||||
}
|
||||
onLogin2FAError: {
|
||||
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAError")
|
||||
|
||||
twoFAButton.loading = false
|
||||
|
||||
twoFactorPasswordTextField.enabled = true
|
||||
twoFactorPasswordTextField.error = true
|
||||
twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect")
|
||||
}
|
||||
onLogin2FAErrorAbort: {
|
||||
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAErrorAbort")
|
||||
root.reset()
|
||||
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
||||
}
|
||||
|
||||
onLogin2PasswordRequested: {
|
||||
console.assert(stackLayout.currentIndex == 0 || stackLayout.currentIndex == 1, "Unexpected login2PasswordRequested")
|
||||
stackLayout.currentIndex = 2
|
||||
}
|
||||
onLogin2PasswordError: {
|
||||
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordError")
|
||||
|
||||
secondPasswordButton.loading = false
|
||||
|
||||
secondPasswordTextField.enabled = true
|
||||
secondPasswordTextField.error = true
|
||||
secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect")
|
||||
}
|
||||
onLogin2PasswordErrorAbort: {
|
||||
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordErrorAbort")
|
||||
root.reset()
|
||||
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
||||
}
|
||||
|
||||
onLoginFinished: {
|
||||
stackLayout.currentIndex = 0
|
||||
root.reset()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: loginNormalLayout
|
||||
|
||||
function reset() {
|
||||
signInButton.loading = false
|
||||
|
||||
errorLabel.text = ""
|
||||
|
||||
usernameTextField.enabled = true
|
||||
usernameTextField.error = false
|
||||
usernameTextField.errorString = ""
|
||||
|
||||
passwordTextField.enabled = true
|
||||
passwordTextField.error = false
|
||||
passwordTextField.errorString = ""
|
||||
passwordTextField.text = ""
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign in")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 16
|
||||
type: Label.LabelType.Title
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: subTitle
|
||||
text: qsTr("Enter your Proton Account details.")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 8
|
||||
color: root.colorScheme.text_weak
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 36
|
||||
|
||||
spacing: 0
|
||||
visible: errorLabel.text.length > 0
|
||||
|
||||
ColorImage {
|
||||
color: root.colorScheme.signal_danger
|
||||
source: "./icons/ic-exclamation-circle-filled.svg"
|
||||
height: errorLabel.height
|
||||
sourceSize.height: errorLabel.height
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: errorLabel
|
||||
Layout.leftMargin: 4
|
||||
color: root.colorScheme.signal_danger
|
||||
|
||||
type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
id: usernameTextField
|
||||
label: qsTr("Username or email")
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
onTextChanged: {
|
||||
// remove "invalid username / password error"
|
||||
if (error || errorLabel.text.length > 0) {
|
||||
errorLabel.text = ""
|
||||
usernameTextField.error = false
|
||||
passwordTextField.error = false
|
||||
}
|
||||
}
|
||||
|
||||
validator: function(str) {
|
||||
if (str.length === 0) {
|
||||
return qsTr("Enter username or email")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
onAccepted: passwordTextField.forceActiveFocus()
|
||||
}
|
||||
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
id: passwordTextField
|
||||
label: qsTr("Password")
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
echoMode: TextInput.Password
|
||||
|
||||
onTextChanged: {
|
||||
// remove "invalid username / password error"
|
||||
if (error || errorLabel.text.length > 0) {
|
||||
errorLabel.text = ""
|
||||
usernameTextField.error = false
|
||||
passwordTextField.error = false
|
||||
}
|
||||
}
|
||||
|
||||
validator: function(str) {
|
||||
if (str.length === 0) {
|
||||
return qsTr("Enter password")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
onAccepted: signInButton.checkAndSignIn()
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
id: signInButton
|
||||
text: qsTr("Sign in")
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
|
||||
onClicked: checkAndSignIn()
|
||||
|
||||
function checkAndSignIn() {
|
||||
usernameTextField.validate()
|
||||
passwordTextField.validate()
|
||||
|
||||
if (usernameTextField.error || passwordTextField.error) {
|
||||
return
|
||||
}
|
||||
|
||||
usernameTextField.enabled = false
|
||||
passwordTextField.enabled = false
|
||||
|
||||
enabled = false
|
||||
loading = true
|
||||
|
||||
root.backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text))
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
textFormat: Text.StyledText
|
||||
text: link("https://protonmail.com/signup", qsTr("Create or upgrade your account"))
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 24
|
||||
type: Label.LabelType.Body
|
||||
|
||||
onLinkActivated: {
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: login2FALayout
|
||||
|
||||
function reset() {
|
||||
twoFAButton.loading = false
|
||||
|
||||
twoFactorPasswordTextField.enabled = true
|
||||
twoFactorPasswordTextField.error = false
|
||||
twoFactorPasswordTextField.errorString = ""
|
||||
twoFactorPasswordTextField.text = ""
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Two-factor authentication")
|
||||
Layout.topMargin: 16
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
type: Label.LabelType.Heading
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: twoFactorUsernameLabel
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: 8
|
||||
type: Label.LabelType.Lead
|
||||
color: root.colorScheme.text_weak
|
||||
}
|
||||
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
id: twoFactorPasswordTextField
|
||||
label: qsTr("Two-factor code")
|
||||
assistiveText: qsTr("Enter the 6-digit code")
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
|
||||
validator: function(str) {
|
||||
if (str.length === 0) {
|
||||
return qsTr("Enter username or email")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
id: twoFAButton
|
||||
text: loading ? qsTr("Authenticating") : qsTr("Authenticate")
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
onClicked: {
|
||||
twoFactorPasswordTextField.validate()
|
||||
|
||||
if (twoFactorPasswordTextField.error) {
|
||||
return
|
||||
}
|
||||
|
||||
twoFactorPasswordTextField.enabled = false
|
||||
|
||||
enabled = false
|
||||
loading = true
|
||||
|
||||
root.backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: login2PasswordLayout
|
||||
|
||||
function reset() {
|
||||
secondPasswordButton.loading = false
|
||||
|
||||
secondPasswordTextField.enabled = true
|
||||
secondPasswordTextField.error = false
|
||||
secondPasswordTextField.errorString = ""
|
||||
secondPasswordTextField.text = ""
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Unlock your mailbox")
|
||||
Layout.topMargin: 16
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
type: Label.LabelType.Heading
|
||||
}
|
||||
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
id: secondPasswordTextField
|
||||
label: qsTr("Mailbox password")
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight
|
||||
echoMode: TextInput.Password
|
||||
|
||||
validator: function(str) {
|
||||
if (str.length === 0) {
|
||||
return qsTr("Enter username or email")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
id: secondPasswordButton
|
||||
text: loading ? qsTr("Unlocking") : qsTr("Unlock")
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
onClicked: {
|
||||
secondPasswordTextField.validate()
|
||||
|
||||
if (secondPasswordTextField.error) {
|
||||
return
|
||||
}
|
||||
|
||||
secondPasswordTextField.enabled = false
|
||||
|
||||
enabled = false
|
||||
loading = true
|
||||
|
||||
root.backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "Page 1"
|
||||
PropertyChanges {
|
||||
target: stackLayout
|
||||
currentIndex: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "Page 2"
|
||||
PropertyChanges {
|
||||
target: stackLayout
|
||||
currentIndex: 1
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "Page 3"
|
||||
PropertyChanges {
|
||||
target: stackLayout
|
||||
currentIndex: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
108
internal/frontend/qt6/qml/SplashScreen.qml
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
property var backend
|
||||
|
||||
shouldShow: root.backend.showSplashScreen
|
||||
modal: true
|
||||
|
||||
topPadding : 0
|
||||
leftPadding : 0
|
||||
rightPadding : 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 20
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
sourceSize.width: 400
|
||||
sourceSize.height: 225
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.preferredHeight: 225
|
||||
|
||||
source: "./icons/img-splash.png"
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme;
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter;
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
Layout.preferredWidth: 336
|
||||
|
||||
type: Label.Title
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("Updated Proton, unified protection")
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter;
|
||||
Layout.preferredWidth: 336
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
type: Label.Body
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
textFormat: Text.StyledText
|
||||
text: qsTr("Introducing Proton’s refreshed look.<br/>") +
|
||||
qsTr("Many services, one mission. Welcome to an Internet where privacy is the default. ") +
|
||||
link("https://proton.me/news/updated-proton",qsTr("Learn More"))
|
||||
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
colorScheme: root.colorScheme
|
||||
text: "Got it"
|
||||
onClicked: root.backend.showSplashScreen = false
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
sourceSize.width: 164
|
||||
sourceSize.height: 32
|
||||
|
||||
Layout.preferredWidth: 164
|
||||
Layout.preferredHeight: 32
|
||||
|
||||
source: "./icons/img-proton-logos.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
112
internal/frontend/qt6/qml/Status.qml
Normal file
@ -0,0 +1,112 @@
|
||||
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var backend
|
||||
property var notifications
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property int notificationWhitelist: NotificationFilter.FilterConsts.All
|
||||
property int notificationBlacklist: NotificationFilter.FilterConsts.None
|
||||
|
||||
readonly property Notification activeNotification: notificationFilter.topmost
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
NotificationFilter {
|
||||
id: notificationFilter
|
||||
|
||||
source: root.notifications ? root.notifications.all : undefined
|
||||
whitelist: root.notificationWhitelist
|
||||
blacklist: root.notificationBlacklist
|
||||
|
||||
onTopmostChanged: {
|
||||
if (!topmost) {
|
||||
image.source = "./icons/ic-connected.svg"
|
||||
image.color = root.colorScheme.signal_success
|
||||
label.text = qsTr("Connected")
|
||||
label.color = root.colorScheme.signal_success
|
||||
return;
|
||||
}
|
||||
|
||||
image.source = topmost.icon
|
||||
label.text = topmost.brief
|
||||
|
||||
switch (topmost.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
image.color = root.colorScheme.signal_danger
|
||||
label.color = root.colorScheme.signal_danger
|
||||
break;
|
||||
case Notification.NotificationType.Warning:
|
||||
image.color = root.colorScheme.signal_warning
|
||||
label.color = root.colorScheme.signal_warning
|
||||
break;
|
||||
case Notification.NotificationType.Success:
|
||||
image.color = root.colorScheme.signal_success
|
||||
label.color = root.colorScheme.signal_success
|
||||
break;
|
||||
case Notification.NotificationType.Info:
|
||||
image.color = root.colorScheme.signal_info
|
||||
label.color = root.colorScheme.signal_info
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 8
|
||||
|
||||
ColorImage {
|
||||
id: image
|
||||
width: 16
|
||||
height: 16
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
source: "./icons/ic-connected.svg"
|
||||
color: root.colorScheme.signal_success
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: label
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
text: qsTr("Connected")
|
||||
color: root.colorScheme.signal_success
|
||||
}
|
||||
}
|
||||
}
|
||||
328
internal/frontend/qt6/qml/StatusWindow.qml
Normal file
@ -0,0 +1,328 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
Window {
|
||||
id: root
|
||||
|
||||
height: contentLayout.implicitHeight
|
||||
width: contentLayout.implicitWidth
|
||||
|
||||
flags: (Qt.platform.os === "linux" ? Qt.Tool : 0) | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint | Qt.WindowStaysOnTopHint | Qt.WA_TranslucentBackground
|
||||
color: "transparent"
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.currentStyle
|
||||
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
signal showMainWindow()
|
||||
signal showHelp()
|
||||
signal showSettings()
|
||||
signal showSignIn(string username)
|
||||
signal quit()
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
|
||||
Layout.minimumHeight: 201
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
ColumnLayout {
|
||||
Layout.minimumWidth: 448
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
implicitHeight: 12
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: parent.height * 2
|
||||
radius: ProtonStyle.dialog_radius
|
||||
|
||||
color: {
|
||||
if (!statusItem.activeNotification) {
|
||||
return root.colorScheme.signal_success
|
||||
}
|
||||
|
||||
switch (statusItem.activeNotification.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
color: colorScheme.background_norm
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.topMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
anchors.leftMargin: 24
|
||||
anchors.rightMargin: 24
|
||||
|
||||
spacing: 8
|
||||
|
||||
Status {
|
||||
id: statusItem
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
notificationWhitelist: Notifications.Group.Connection | Notifications.Group.Update | Notifications.Group.Configuration
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
secondary: true
|
||||
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
visible: statusItem.activeNotification && statusItem.activeNotification.action.length > 0
|
||||
action: statusItem.activeNotification && statusItem.activeNotification.action.length > 0 ? statusItem.activeNotification.action[0] : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 24
|
||||
anchors.rightMargin: 24
|
||||
color: root.colorScheme.border_norm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Layout.maximumHeight: accountListView.count ?
|
||||
accountListView.contentHeight / accountListView.count * 3 + accountListView.anchors.topMargin + accountListView.anchors.bottomMargin :
|
||||
Number.POSITIVE_INFINITY
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
clip: true
|
||||
|
||||
implicitHeight: children[0].contentHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].contentWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
ListView {
|
||||
id: accountListView
|
||||
|
||||
model: root.backend.users
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.topMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
anchors.leftMargin: 24
|
||||
anchors.rightMargin: 24
|
||||
|
||||
interactive: contentHeight > parent.height
|
||||
snapMode: ListView.SnapToItem
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
spacing: 4
|
||||
|
||||
delegate: Item {
|
||||
id: viewItem
|
||||
width: ListView.view.width
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
property var user: root.backend.users.get(index)
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
user: viewItem.user
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
visible: viewItem.user ? !viewItem.user.loggedIn : false
|
||||
text: qsTr("Sign in")
|
||||
onClicked: {
|
||||
root.showSignIn(viewItem.username)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: children[1].implicitHeight + children[1].anchors.topMargin + children[1].anchors.bottomMargin
|
||||
implicitWidth: children[1].implicitWidth + children[1].anchors.leftMargin + children[1].anchors.rightMargin
|
||||
|
||||
// background:
|
||||
clip: true
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: parent.height * 2
|
||||
radius: ProtonStyle.dialog_radius
|
||||
|
||||
color: root.colorScheme.background_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 0
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
secondary: true
|
||||
text: qsTr("Open Bridge")
|
||||
|
||||
borderless: true
|
||||
labelType: Label.LabelType.Caption_semibold
|
||||
|
||||
onClicked: {
|
||||
root.showMainWindow()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
secondary: true
|
||||
icon.source: "./icons/ic-three-dots-vertical.svg"
|
||||
borderless: true
|
||||
checkable: true
|
||||
|
||||
onClicked: {
|
||||
menu.open()
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: menu
|
||||
colorScheme: root.colorScheme
|
||||
modal: true
|
||||
|
||||
y: 0 - height
|
||||
|
||||
MenuItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Help")
|
||||
onClicked: {
|
||||
root.showHelp()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Settings")
|
||||
onClicked: {
|
||||
root.showSettings()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Quit Bridge")
|
||||
onClicked: {
|
||||
root.quit()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
parent.checked = false
|
||||
}
|
||||
onOpened: {
|
||||
parent.checked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) root.close()
|
||||
}
|
||||
|
||||
function showAndRise() {
|
||||
root.show()
|
||||
root.raise()
|
||||
if (!root.active) {
|
||||
root.requestActivate()
|
||||
}
|
||||
}
|
||||
}
|
||||
278
internal/frontend/qt6/qml/WelcomeGuide.qml
Normal file
@ -0,0 +1,278 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property var backend
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
visible: signInItem.currentIndex == 0
|
||||
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
columnSpacing: 0
|
||||
rowSpacing: 0
|
||||
|
||||
columns: 3
|
||||
|
||||
// top margin
|
||||
Item {
|
||||
Layout.columnSpan: 3
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Using binding component here instead of direct binding to avoid binding loop during construction of element
|
||||
Binding on Layout.preferredHeight {
|
||||
value: (parent.height - welcomeContentItem.height) / 4
|
||||
}
|
||||
}
|
||||
|
||||
// left margin
|
||||
Item {
|
||||
Layout.minimumWidth: 48
|
||||
Layout.maximumWidth: 80
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: welcomeContentItem.height
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: welcomeContentItem
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Image {
|
||||
source: colorScheme.welcome_img
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 16
|
||||
sourceSize.height: 148
|
||||
sourceSize.width: 264
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Welcome to\nProton Mail Bridge")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
type: Label.LabelType.Heading
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: longTextLabel
|
||||
text: qsTr("Add your Proton Mail account to securely access and manage your messages in your favorite email client. Bridge runs in the background and encrypts and decrypts your messages seamlessly.")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.preferredWidth: 320
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
}
|
||||
|
||||
// Right margin
|
||||
Item {
|
||||
Layout.minimumWidth: 48
|
||||
Layout.maximumWidth: 80
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: welcomeContentItem.height
|
||||
}
|
||||
|
||||
// bottom margin
|
||||
Item {
|
||||
Layout.columnSpan: 3
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
Image {
|
||||
id: logoImage
|
||||
source: colorScheme.logo_img
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: 48
|
||||
anchors.bottomMargin: 48
|
||||
sourceSize.height: 25
|
||||
sourceSize.width: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
anchors.leftMargin: 80
|
||||
anchors.rightMargin: 80
|
||||
anchors.topMargin: 80
|
||||
anchors.bottomMargin: 80
|
||||
|
||||
visible: signInItem.currentIndex != 0
|
||||
|
||||
secondary: true
|
||||
text: qsTr("Back")
|
||||
|
||||
onClicked: {
|
||||
signInItem.abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
columnSpacing: 0
|
||||
rowSpacing: 0
|
||||
|
||||
columns: 3
|
||||
|
||||
// top margin
|
||||
Item {
|
||||
Layout.columnSpan: 3
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Using binding component here instead of direct binding to avoid binding loop during construction of element
|
||||
Binding on Layout.preferredHeight {
|
||||
value: (parent.height - signInItem.height) / 4
|
||||
}
|
||||
}
|
||||
|
||||
// left margin
|
||||
Item {
|
||||
Layout.minimumWidth: 48
|
||||
Layout.maximumWidth: 80
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: signInItem.height
|
||||
}
|
||||
|
||||
|
||||
SignIn {
|
||||
id: signInItem
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Layout.preferredWidth: 320
|
||||
Layout.fillWidth: true
|
||||
|
||||
username: root.backend.users.count === 1 && root.backend.users.get(0) && root.backend.users.get(0).loggedIn === false ? root.backend.users.get(0).username : ""
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
// Right margin
|
||||
Item {
|
||||
Layout.minimumWidth: 48
|
||||
Layout.maximumWidth: 80
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: signInItem.height
|
||||
}
|
||||
|
||||
// bottom margin
|
||||
Item {
|
||||
Layout.columnSpan: 3
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "Page 1"
|
||||
PropertyChanges {
|
||||
target: signInItem
|
||||
currentIndex: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "Page 2"
|
||||
PropertyChanges {
|
||||
target: signInItem
|
||||
currentIndex: 1
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "Page 3"
|
||||
PropertyChanges {
|
||||
target: signInItem
|
||||
currentIndex: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
37
internal/frontend/qt6/qml/bridgeqml.qmlproject
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QmlProject 1.1
|
||||
|
||||
Project {
|
||||
mainFile: "./MainWindow.qml"
|
||||
|
||||
/* Include .qml, .js, and image files from current directory and subdirectories */
|
||||
QmlFiles {
|
||||
directory: "./"
|
||||
}
|
||||
JavaScriptFiles {
|
||||
directory: "./"
|
||||
}
|
||||
ImageFiles {
|
||||
directory: "./"
|
||||
}
|
||||
/* List of plugin directories passed to QML runtime */
|
||||
importPaths: [
|
||||
"./"
|
||||
]
|
||||
}
|
||||
4
internal/frontend/qt6/qml/icons/Loader_16.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.2" d="M8 2.75C8.68944 2.75 9.37213 2.8858 10.0091 3.14963C10.646 3.41347 11.2248 3.80018 11.7123 4.28769C12.1998 4.7752 12.5865 5.35395 12.8504 5.99091C13.1142 6.62787 13.25 7.31056 13.25 8C13.25 8.68944 13.1142 9.37213 12.8504 10.0091C12.5865 10.646 12.1998 11.2248 11.7123 11.7123C11.2248 12.1998 10.646 12.5865 10.0091 12.8504C9.37213 13.1142 8.68944 13.25 8 13.25C7.31056 13.25 6.62787 13.1142 5.99091 12.8504C5.35395 12.5865 4.7752 12.1998 4.28769 11.7123C3.80018 11.2248 3.41347 10.646 3.14963 10.0091C2.88579 9.37213 2.75 8.68944 2.75 8C2.75 7.31056 2.8858 6.62787 3.14963 5.99091C3.41347 5.35395 3.80018 4.77519 4.28769 4.28769C4.7752 3.80018 5.35396 3.41347 5.99092 3.14963C6.62787 2.88579 7.31056 2.75 8 2.75L8 2.75Z" stroke="#5064B6" stroke-width="1.5"/>
|
||||
<path d="M11.7123 11.7123C11.1018 12.3228 10.3502 12.7733 9.52399 13.0239C8.6978 13.2746 7.82255 13.3176 6.97578 13.1491C6.129 12.9807 5.33683 12.606 4.66944 12.0583C4.00204 11.5106 3.48003 10.8067 3.14963 10.0091C2.81924 9.21144 2.69066 8.34462 2.77528 7.48541C2.85991 6.6262 3.15512 5.80112 3.63478 5.08326C4.11445 4.36539 4.76374 3.7769 5.52517 3.36991C6.28659 2.96292 7.13663 2.75 8 2.75" stroke="#5064B6" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
4
internal/frontend/qt6/qml/icons/Loader_48.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.2" d="M24 1C27.0204 1 30.0112 1.59491 32.8017 2.75077C35.5922 3.90663 38.1277 5.6008 40.2635 7.73655C42.3992 9.87229 44.0934 12.4078 45.2492 15.1983C46.4051 17.9888 47 20.9796 47 24C47 27.0204 46.4051 30.0112 45.2492 32.8017C44.0934 35.5922 42.3992 38.1277 40.2635 40.2635C38.1277 42.3992 35.5922 44.0934 32.8017 45.2492C30.0112 46.4051 27.0204 47 24 47C20.9796 47 17.9888 46.4051 15.1983 45.2492C12.4078 44.0934 9.87228 42.3992 7.73654 40.2634C5.60079 38.1277 3.90662 35.5922 2.75077 32.8017C1.59491 30.0112 0.999998 27.0204 1 24C1 20.9796 1.59492 17.9888 2.75078 15.1983C3.90664 12.4078 5.6008 9.87228 7.73655 7.73653C9.8723 5.60079 12.4078 3.90662 15.1983 2.75077C17.9888 1.59491 20.9796 0.999998 24 1L24 1Z" stroke="#5064B6" stroke-width="2"/>
|
||||
<path d="M40.2635 40.2635C37.5889 42.938 34.2961 44.9117 30.6765 46.0096C27.057 47.1076 23.2226 47.296 19.5129 46.5581C15.8032 45.8202 12.3328 44.1788 9.40895 41.7792C6.48514 39.3797 4.19822 36.2962 2.75077 32.8017C1.30332 29.3073 0.740014 25.5098 1.11075 21.7456C1.48149 17.9814 2.77483 14.3668 4.8762 11.2219C6.97757 8.07696 9.82212 5.49881 13.1579 3.71581C16.4936 1.93281 20.2176 1 24 1" stroke="#5064B6" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
11
internal/frontend/qt6/qml/icons/ic-alert.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M25.9729 8.06214C28.6889 3.51461 35.2762 3.51461 37.9923 8.06214L62.464 49.0356C65.2508 53.7015 61.8891 59.625 56.4543 59.625H7.51078C2.07602 59.625 -1.28569 53.7015 1.50106 49.0356L25.9729 8.06214Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M29.0775 24.3888C29.0864 22.7908 30.3843 21.5 31.9824 21.5C33.5805 21.5 34.8785 22.7908 34.8874 24.3888L34.9658 38.5C34.9749 40.1542 33.6366 41.5 31.9824 41.5C30.3283 41.5 28.9899 40.1542 28.9991 38.5L29.0775 24.3888Z" fill="white"/>
|
||||
<path d="M34.9824 47.5C34.9824 49.1569 33.6393 50.5 31.9824 50.5C30.3256 50.5 28.9824 49.1569 28.9824 47.5C28.9824 45.8431 30.3256 44.5 31.9824 44.5C33.6393 44.5 34.9824 45.8431 34.9824 47.5Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="31.9827" y1="4.18421" x2="31.9827" y2="84.2072" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF7171"/>
|
||||
<stop offset="1" stop-color="#E63757"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1013 B |
10
internal/frontend/qt6/qml/icons/ic-apple-mail.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.29416 0.0598755H27.7073C32.2688 0.0598755 35.9409 3.73205 35.9409 8.29347V27.7067C35.9409 32.2681 32.2688 35.9403 27.7073 35.9403H8.29416C3.73273 35.9403 0.0605469 32.2681 0.0605469 27.7067V8.29347C0.0605469 3.73205 3.73273 0.0598755 8.29416 0.0598755Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M7.92234 10.2633C7.73329 10.2633 7.55519 10.2959 7.38662 10.363L10.7629 13.8389L14.1765 17.3771L14.2388 17.4518L14.3385 17.5515L14.4381 17.6512L14.6375 17.863L17.5652 20.8655C17.6139 20.8958 17.7552 21.0266 17.8656 21.0818C18.0077 21.1529 18.1618 21.2183 18.3207 21.224C18.492 21.2302 18.6672 21.1811 18.8215 21.106C18.937 21.0498 18.9883 20.9693 19.1225 20.8655L22.5112 17.3646L25.9373 13.8389L29.2388 10.4377C29.0268 10.3229 28.7921 10.2633 28.5411 10.2633H7.92234ZM6.88828 10.6869C6.52824 11.028 6.30273 11.5409 6.30273 12.1196V23.5316C6.30273 24.0002 6.45323 24.4258 6.70141 24.7525L7.17483 24.304L10.7006 20.8779L13.8277 17.8505L13.7654 17.7758L10.3393 14.25L6.9132 10.7118L6.88828 10.6869ZM29.6873 10.799L26.3484 14.25L22.9348 17.7758L22.8725 17.8381L26.1242 20.9901L29.6499 24.4161L29.8617 24.6155C30.0516 24.3109 30.1607 23.9357 30.1607 23.5316V12.1196C30.1607 11.6036 29.9819 11.1357 29.6873 10.799ZM14.2263 18.2617L11.1117 21.2891L7.5735 24.7151L7.12499 25.1512C7.36144 25.3035 7.63053 25.4004 7.92234 25.4004H28.5411C28.8919 25.4004 29.2101 25.2654 29.4755 25.0515L29.2513 24.8273L25.713 21.4012L22.4614 18.2617L19.5336 21.2766C19.3752 21.3817 19.2693 21.4982 19.1146 21.5697C18.8656 21.6849 18.5926 21.7823 18.3183 21.7781C18.0433 21.7739 17.7736 21.6662 17.5267 21.5448C17.4028 21.4839 17.3368 21.4233 17.1914 21.3015L14.2263 18.2617Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="18.252" y1="35.7962" x2="18.2869" y2="0.493036" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#70EFFF"/>
|
||||
<stop offset="1" stop-color="#5770FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
5
internal/frontend/qt6/qml/icons/ic-arrow-left.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic-arrow-left">
|
||||
<path id="icon" fill-rule="evenodd" clip-rule="evenodd" d="M14 8H3.20998L7.68998 3.52L6.97998 2.81L1.28998 8.5L6.97998 14.19L7.68998 13.48L3.20998 9H14V8Z" fill="#17181C"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 304 B |
62
internal/frontend/qt6/qml/icons/ic-card-identity.svg
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg12"
|
||||
sodipodi:docname="ic-card-identity.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs16" />
|
||||
<sodipodi:namedview
|
||||
id="namedview14"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="40.25"
|
||||
inkscape:cx="7.9875776"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-width="2556"
|
||||
inkscape:window-height="1401"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg12" />
|
||||
<g
|
||||
id="ic-card-identity"
|
||||
transform="translate(0,-3.9095641)">
|
||||
<g
|
||||
id="icon">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 5.32,9.64 C 5.96894,9.64404 6.55605,9.25571 6.80626,8.65693 7.05647,8.05815 6.92022,7.36754 6.46134,6.90866 6.00246,6.44978 5.31185,6.31353 4.71307,6.56374 4.11429,6.81395 3.72596,7.40106 3.73,8.05 c 0,0.87813 0.71187,1.59 1.59,1.59 z m 0,-2.19 C 5.61898,7.48884 5.8427,7.74351 5.8427,8.045 5.8427,8.34649 5.61898,8.60116 5.32,8.64 5.02102,8.60116 4.7973,8.34649 4.7973,8.045 4.7973,7.74351 5.02102,7.48884 5.32,7.45 Z"
|
||||
fill="#0c0c14"
|
||||
id="path2" />
|
||||
<path
|
||||
d="m 9,8 h 4 V 9 H 9 Z"
|
||||
fill="#0c0c14"
|
||||
id="path4" />
|
||||
<path
|
||||
d="M 13,10 H 9 v 1 h 4 z"
|
||||
fill="#0c0c14"
|
||||
id="path6" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 1,14 V 4 H 15 V 14 Z M 3.64,12.49 V 13 H 7 V 12.49 L 6.1,11.57 H 4.54 Z M 14,13 H 8 V 12.08 L 6.52,10.57 H 4.12 L 2.64,12.08 V 13 H 2 V 5 h 12 z"
|
||||
fill="#0c0c14"
|
||||
id="path8" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
3
internal/frontend/qt6/qml/icons/ic-check.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 8.5L2.7 7.8L6.05 11.14L13.2 4L13.9 4.7L6.05 12.56L2 8.5Z" fill="#17181C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 230 B |
5
internal/frontend/qt6/qml/icons/ic-chevron-down.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic-chevron-down">
|
||||
<path id="icon" fill-rule="evenodd" clip-rule="evenodd" d="M2.3 6.30001L8 12L13.7 6.30001L13 5.60001L8 10.58L3 5.60001L2.3 6.30001Z" fill="#17181C"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
5
internal/frontend/qt6/qml/icons/ic-chevron-up.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic-chevron-up">
|
||||
<path id="icon" fill-rule="evenodd" clip-rule="evenodd" d="M13.7 9.7L7.99999 4L2.29999 9.7L2.99999 10.4L7.99999 5.42L13 10.4L13.7 9.7Z" fill="#17181C"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
3
internal/frontend/qt6/qml/icons/ic-cog-wheel.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.7166 1H9.29278L9.83484 2.72079C10.0427 2.79404 10.2448 2.87886 10.4428 2.97503L12.04 2.14318L13.8662 3.97057L13.035 5.56869C13.1312 5.7671 13.2161 5.96972 13.2894 6.178L15 6.72052V9.29956L13.2799 9.83276C13.2068 10.0403 13.1222 10.2423 13.0262 10.4401L13.8574 12.0382L12.0312 13.8656L10.434 13.0338C10.2359 13.13 10.0336 13.2149 9.82558 13.2882L9.2834 15H6.70782L6.16564 13.2882C5.95765 13.2149 5.75532 13.13 5.55718 13.0338L3.95998 13.8656L2.13382 12.0382L2.96503 10.4401C2.86877 10.2417 2.78388 10.0391 2.71059 9.83078L1 9.28826V6.71174L2.71059 6.16922C2.78388 5.96094 2.86877 5.75832 2.96503 5.55991L2.13323 3.96064L3.96989 2.13499L5.56596 2.96625C5.76409 2.87 5.96643 2.78513 6.17441 2.71184L6.7166 1ZM7.44883 2L6.97047 3.51034L6.71736 3.58632C6.40057 3.68142 6.09845 3.8085 5.80203 3.96884L5.56858 4.09511L4.15637 3.35961L3.35968 4.15152L4.09358 5.56254L3.9676 5.79574C3.80736 6.0924 3.68034 6.39477 3.58529 6.71183L3.5094 6.96496L2 7.44367V8.55633L3.5094 9.03504L3.58529 9.28817C3.68034 9.60523 3.80736 9.9076 3.9676 10.2043L4.09358 10.4375L3.35909 11.8496L4.14873 12.6398L5.5598 11.9049L5.79325 12.0312C6.08967 12.1915 6.39179 12.3186 6.70858 12.4137L6.96169 12.4897L7.44005 14H8.55117L9.02953 12.4897L9.28264 12.4137C9.59943 12.3186 9.90155 12.1915 10.198 12.0312L10.4314 11.9049L11.8425 12.6398L12.6321 11.8496L11.8976 10.4375L12.0236 10.2043C12.1839 9.9076 12.3109 9.60523 12.4059 9.28817L12.4824 9.03304L14 8.5626V7.45245L12.4906 6.97374L12.4147 6.72061C12.3197 6.40355 12.1926 6.10118 12.0324 5.80453L11.9064 5.57132L12.6409 4.15916L11.8513 3.36898L10.4402 4.1039L10.2067 3.97762C9.91033 3.81729 9.60821 3.6902 9.29142 3.5951L9.03783 3.51898L8.55935 2H7.44883ZM7.99561 5.81216C6.78861 5.81216 5.80959 6.79136 5.80959 8C5.80959 9.20864 6.78861 10.1878 7.99561 10.1878C9.20261 10.1878 10.1816 9.20864 10.1816 8C10.1816 6.79136 9.20261 5.81216 7.99561 5.81216ZM4.80959 8C4.80959 6.23972 6.23569 4.81216 7.99561 4.81216C9.75553 4.81216 11.1816 6.23972 11.1816 8C11.1816 9.76028 9.75553 11.1878 7.99561 11.1878C6.23569 11.1878 4.80959 9.76028 4.80959 8Z" fill="#17181C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
5
internal/frontend/qt6/qml/icons/ic-connected.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.9257 5.48656C13.8921 3.271 11.0022 2 8 2C4.99776 2 2.10791 3.271 0.0743047 5.48656C-0.0273754 5.59626 -0.0246996 5.76751 0.0823321 5.87455L1.67175 7.4637C1.78146 7.57341 1.95806 7.57341 2.06777 7.4637C2.0865 7.44524 2.09988 7.42383 2.11058 7.39975C3.61705 5.74584 5.76036 4.7962 8 4.7962C10.2396 4.7962 12.3829 5.74611 13.8894 7.39975C13.9001 7.42383 13.9135 7.44524 13.9322 7.4637C13.9857 7.51989 14.058 7.54692 14.1302 7.54692C14.2025 7.54692 14.2747 7.51989 14.3282 7.4637L15.9177 5.87455C16.0247 5.76751 16.0274 5.59653 15.9257 5.48656Z" fill="#349172"/>
|
||||
<path d="M12.7547 8.56633C11.5533 7.20168 9.82473 6.41794 7.99984 6.41794C6.17495 6.41794 4.44639 7.20195 3.24228 8.56633C3.2289 8.57436 3.21285 8.58265 3.19947 8.59577C3.08976 8.70547 3.08976 8.88208 3.19947 8.99151L4.72735 10.5194C4.7541 10.5488 4.78621 10.5678 4.821 10.5809C4.83705 10.589 4.85311 10.589 4.86649 10.5916C4.88789 10.5943 4.90662 10.6023 4.92535 10.6023C4.92803 10.6023 4.93071 10.5997 4.93338 10.5997C4.93606 10.5997 4.93873 10.6023 4.94141 10.6023C4.95211 10.6023 4.96281 10.5943 4.97352 10.5916C4.9976 10.589 5.02168 10.5836 5.04309 10.5729C5.05914 10.5676 5.06985 10.5569 5.08323 10.5462C5.0966 10.5381 5.11266 10.5328 5.12336 10.5191C5.12871 10.5164 5.12871 10.5087 5.13407 10.5057C5.13674 10.5004 5.14209 10.5004 5.14477 10.495C5.83512 9.62006 6.87601 9.11969 7.99984 9.11969C9.12367 9.11969 10.1646 9.62006 10.8549 10.495C10.8576 10.5007 10.8629 10.5007 10.8656 10.5057C10.871 10.5114 10.871 10.5164 10.8763 10.5191C10.887 10.5328 10.9031 10.5379 10.9165 10.5462C10.9298 10.5569 10.9405 10.5676 10.9566 10.5729C10.978 10.5836 11.0021 10.5892 11.0262 10.5916C11.0369 10.5943 11.0476 10.6023 11.0583 10.6023H11.0743C11.1118 10.6023 11.1466 10.5943 11.1814 10.5809C11.2161 10.5676 11.2456 10.5461 11.2723 10.5194L12.8002 8.99151C12.9099 8.88208 12.9099 8.70547 12.8002 8.59577C12.7868 8.58265 12.7708 8.57436 12.7547 8.56633Z" fill="#349172"/>
|
||||
<path d="M7.99991 10.2626C7.17309 10.2626 6.50146 10.9371 6.50146 11.7637C6.50146 12.5902 7.17309 13.2621 7.99991 13.2621C8.82673 13.2621 9.49835 12.5905 9.49835 11.7637C9.49835 10.9369 8.82673 10.2626 7.99991 10.2626Z" fill="#349172"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
4
internal/frontend/qt6/qml/icons/ic-copy.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 2V0H12V10H9V9H11V1H5V2H4Z" fill="#262A33"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 3V13H8V3H0ZM7 4V12H1V4H7Z" fill="#262A33"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 255 B |
3
internal/frontend/qt6/qml/icons/ic-cross-close.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.85 2.8499L13.15 2.1499L8 7.2899L2.85 2.1499L2.15 2.8499L7.29 7.9999L2.15 13.1499L2.85 13.8499L8 8.7099L13.15 13.8499L13.85 13.1499L8.71 7.9999L13.85 2.8499Z" fill="#17181C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 331 B |
3
internal/frontend/qt6/qml/icons/ic-drive.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.8561 3.06675L13.9015 7.66688H2.09846L4.14391 3.06675H11.8561ZM14 8.68913H2V12.267H14V8.68913ZM12.5 2.04449H3.5L1 7.66688V13.2893H15V7.66688L12.5 2.04449ZM12 10.9381V9.91584H13V10.9381H12ZM10 10.9381H11V9.91584H10V10.9381Z" fill="#0C0C14"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 396 B |