diff --git a/3rdparty/qxmpp b/3rdparty/qxmpp index e6eb0b7..a7f8017 160000 --- a/3rdparty/qxmpp +++ b/3rdparty/qxmpp @@ -1 +1 @@ -Subproject commit e6eb0b78d0cb17fccd5ddb60966ba2a0a2d2b593 +Subproject commit a7f801700291b7a42dd8191e3fc6f5ffe1072550 diff --git a/CMakeLists.txt b/CMakeLists.txt index a49ecf2..579f021 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,445 +1,446 @@ cmake_minimum_required(VERSION 3.3) set(CMAKE_CXX_STANDARD 14) set(QT_MIN_VERSION "5.12.0") if(POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() project(kaidan) # application information set(APPLICATION_ID "im.kaidan.kaidan") set(APPLICATION_NAME "kaidan") set(APPLICATION_DISPLAY_NAME "Kaidan") set(APPLICATION_DESCRIPTION "A simple, user-friendly Jabber/XMPP client for every device!") if(UBUNTU_TOUCH) set(APPLICATION_NAME "${APPLICATION_ID}") endif() # Version set(VERSION_MAJOR 0) set(VERSION_MINOR 5) set(VERSION_PATCH 0) set(VERSION_CODE 6) set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") set(DEVELOPMENT_BUILD TRUE) if(DEVELOPMENT_BUILD) set(VERSION_EXTRA "${VERSION_EXTRA}-dev") endif() set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") if(VERSION_EXTRA) set(VERSION_STRING ${VERSION_STRING}${VERSION_EXTRA}) endif() # CMake options option(I18N "Enable i18n support" FALSE) option(STATIC_BUILD "Build Kaidan statically") option(APPIMAGE "Build Kaidan as AppImage (will only work in the appimage script)" FALSE) option(UBUNTU_TOUCH "Building an Ubuntu Touch click (internal use only!)" FALSE) option(CLICK_ARCH "Architecture that will be used in the click's manifest") option(CLICK_DATE "Date used in the version number in the click's manifest") option(QUICK_COMPILER "Use QtQuick compiler to improve performance" FALSE) option(USE_KNOTIFICATIONS "Use KNotifications for displaying notifications" TRUE) option(BUNDLE_ICONS "Bundle breeze icons" FALSE) # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) # Instruct CMake to run moc, uic and rcc automatically when needed. set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(AUTOMOC_MOC_OPTIONS -Muri=${APPLICATION_ID}) # # Dependecies # find_package(ECM 5.40.0 REQUIRED NO_MODULE) # CMake module path set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) # CMake modules include include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMInstallIcons) include(FeatureSummary) kde_enable_exceptions() # Find packages find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Qml Quick Svg Sql QuickControls2 Xml Multimedia Positioning Location) find_package(KF5Kirigami2 5.58.0 REQUIRED) find_package(ZXing REQUIRED COMPONENTS Core) find_package(QXmpp 1.0.0 REQUIRED) # Optional QtQuickCompiler if(QUICK_COMPILER) find_package(Qt5QuickCompiler) set_package_properties(Qt5QuickCompiler PROPERTIES DESCRIPTION "Compile QML at build time" TYPE OPTIONAL ) endif() if(USE_KNOTIFICATIONS) find_package(KF5Notifications REQUIRED) set(__KF5Notifications_LIBRARIES KF5::Notifications) add_definitions(-DHAVE_KNOTIFICATIONS) endif() # Optional QWidget style integration (not on mobile) if(NOT UBUNTU_TOUCH AND NOT ANDROID AND NOT IOS) find_package(Qt5Widgets) set_package_properties(Qt5Widgets PROPERTIES DESCRIPTION "Integration with QWidget based desktop styles" TYPE OPTIONAL ) endif() # Platform-specific if(Qt5Widgets_FOUND) add_definitions(-DHAVE_QWIDGETS -DQAPPLICATION_CLASS=QApplication) set(__Qt5Widgets_LIBRARIES Qt5::Widgets) else() add_definitions(-DQAPPLICATION_CLASS=QGuiApplication) endif() if(ANDROID) find_package(Qt5 REQUIRED COMPONENTS AndroidExtras) endif() if(ANDROID OR WIN32) find_package(PkgConfig REQUIRED) pkg_search_module(OPENSSL REQUIRED openssl IMPORTED_TARGET) message(STATUS "Using OpenSSL ${OPENSSL_VERSION}") endif() # # Load submodules # # Main kaidan sources include("${CMAKE_SOURCE_DIR}/src/CMakeLists.txt") # I18n support if(I18N) include("${CMAKE_SOURCE_DIR}/i18n/CMakeLists.txt") endif() # # Sources / Resources # # Include bundled icons on Ubuntu Touch, Android, Windows, macOS and iOS if(BUNDLE_ICONS OR UBUNTU_TOUCH OR ANDROID OR WIN32 OR APPLE) set(KAIDAN_ICONS_QRC kirigami-icons.qrc) endif() # Bundled knotifications configuration files on platforms that require it if(ANDROID) set(KAIDAN_NOTIFICATIONS_QRC "misc/notifications.qrc") endif() # Bundle images on Android, Windows, macOS and iOS if(ANDROID OR WIN32 OR APPLE) set(KAIDAN_IMAGES_QRC "data/images/images.qrc") endif() # Set app icon if(APPLE) set(KAIDAN_ICNS "${CMAKE_SOURCE_DIR}/misc/macos/kaidan.icns") set_source_files_properties(${KAIDAN_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") elseif(WIN32) include(ECMAddAppIcon) file(GLOB WIN_ICONS_SRCS "${CMAKE_SOURCE_DIR}/misc/windows/*kaidan.png") message(STATUS ${WIN_ICONS_SRCS}) ecm_add_app_icon(KAIDAN_ICNS ICONS ${WIN_ICONS_SRCS}) message(STATUS ${KAIDAN_ICNS}) endif() if(QUICK_COMPILER) qtquick_compiler_add_resources(KAIDAN_QML_QRC src/qml/qml.qrc) else() qt5_add_resources(KAIDAN_QML_QRC src/qml/qml.qrc) endif() # misc resources (e.g. qtquickcontrols2.conf) qt5_add_resources(KAIDAN_MISC_QRC misc/misc.qrc) add_executable(${PROJECT_NAME} MACOSX_BUNDLE WIN32 ${KAIDAN_ICNS} ${KAIDAN_SOURCES} ${KAIDAN_QML_QRC} ${KAIDAN_MISC_QRC} ${KAIDAN_ICONS_QRC} # only set if enabled ${KAIDAN_IMAGES_QRC} # ${I18N_QRC_CPP} # ${KAIDAN_NOTIFICATIONS_QRC} # + "data/data.qrc" ) target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Sql Qt5::Qml Qt5::Quick Qt5::Svg Qt5::Network Qt5::Xml Qt5::Multimedia Qt5::Positioning Qt5::Location ZXing::Core QXmpp::QXmpp ${__Qt5Widgets_LIBRARIES} ${__KF5Notifications_LIBRARIES} ) if(ANDROID OR WIN32 OR IOS) target_link_libraries(${PROJECT_NAME} Qt5::QuickControls2 KF5::Kirigami2) endif() if(ANDROID OR WIN32) target_link_libraries(${PROJECT_NAME} PkgConfig::OPENSSL) endif() if(ANDROID) target_link_libraries(${PROJECT_NAME} Qt5::AndroidExtras) endif() if(STATIC_BUILD) add_definitions(-DQXMPP_BUILD) find_package(Perl REQUIRED) set(STATIC_DEPENDENCIES_CMAKE_FILE "${CMAKE_BINARY_DIR}/QtStaticDependencies.cmake") if(EXISTS ${STATIC_DEPENDENCIES_CMAKE_FILE}) file(REMOVE ${STATIC_DEPENDENCIES_CMAKE_FILE}) endif() get_target_property(QT_LIBDIR Qt5::Core LOCATION) get_filename_component(QT_LIBDIR ${QT_LIBDIR} DIRECTORY) macro(CONVERT_PRL_LIBS_TO_CMAKE _qt_component) if(TARGET Qt5::${_qt_component}) get_target_property(_lib_location Qt5::${_qt_component} LOCATION) execute_process(COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/utils/convert-prl-libs-to-cmake.pl --lib ${_lib_location} --libdir ${QT_LIBDIR} --out ${STATIC_DEPENDENCIES_CMAKE_FILE} --component ${_qt_component} --compiler ${CMAKE_CXX_COMPILER_ID} ) endif() endmacro() link_directories(${_qt5_install_prefix}/../) foreach(qt_module Gui Quick QuickControls2 Network Qml Svg Sql QSQLiteDriverPlugin QJpegPlugin QGifPlugin QSvgPlugin QSvgIconPlugin QICOPlugin QGenericEnginePlugin QLocalClientConnectionFactory QTcpServerConnectionFactory) CONVERT_PRL_LIBS_TO_CMAKE(${qt_module}) endforeach() if(WIN32) CONVERT_PRL_LIBS_TO_CMAKE(QWindowsIntegrationPlugin) elseif(IOS) foreach(qt_module QIOSIntegrationPlugin QMacHeifPlugin QMacJp2Plugin QICNSPlugin QTgaPlugin QTiffPlugin QWbmpPlugin QWebpPlugin) CONVERT_PRL_LIBS_TO_CMAKE(${qt_module}) endforeach() endif() if(NOT EXISTS ${STATIC_DEPENDENCIES_CMAKE_FILE}) message(FATAL_ERROR "Unable to find ${STATIC_DEPENDENCIES_CMAKE_FILE}") endif() include(${STATIC_DEPENDENCIES_CMAKE_FILE}) set(QT_QML_PATH ${_qt5Quick_install_prefix}) find_library(KIRIGAMI_PLUGIN kirigamiplugin PATHS ${Kirigami2_INSTALL_PREFIX}/${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2) find_library(QUICK_PLUGIN qtquick2plugin PATHS ${QT_QML_PATH}/qml/QtQuick.2) find_library(LABS_PLATFORM_PLUGIN qtlabsplatformplugin PATHS ${QT_QML_PATH}/qml/Qt/labs/platform) find_library(GRAPHEFFECTS_PLUGIN qtgraphicaleffectsplugin PATHS ${QT_QML_PATH}/qml/QtGraphicalEffects) find_library(GRAPHEFFECTS_PRIVATE_PLUGIN qtgraphicaleffectsprivate PATHS ${QT_QML_PATH}/qml/QtGraphicalEffects/private) find_library(QQC2_PLUGIN qtquickcontrols2plugin PATHS ${QT_QML_PATH}/qml/QtQuick/Controls.2) find_library(QQC2_MATERIAL_PLUGIN qtquickcontrols2materialstyleplugin PATHS ${QT_QML_PATH}/qml/QtQuick/Controls.2/Material) find_library(QQC2_UNIVERSAL_PLUGIN qtquickcontrols2universalstyleplugin PATHS ${QT_QML_PATH}/qml/QtQuick/Controls.2/Universal) find_library(QLAYOUTS_PLUGIN qquicklayoutsplugin PATHS ${QT_QML_PATH}/qml/QtQuick/Layouts) find_library(QWINDOW_PLUGIN windowplugin PATHS ${QT_QML_PATH}/qml/QtQuick/Window.2) find_library(QSHAPES_PLUGIN qmlshapesplugin PATHS ${QT_QML_PATH}/qml/QtQuick/Shapes) find_library(QUICKSHAPES Qt5QuickShapes PATHS ${QT_LIBDIR}) find_library(QTEMPLATES_PLUGIN qtquicktemplates2plugin PATHS ${QT_QML_PATH}/qml/QtQuick/Templates.2) find_library(QMODELS_PLUGIN modelsplugin PATHS ${QT_QML_PATH}/qml/QtQml/Models.2) target_link_libraries(${PROJECT_NAME} ${plugin_libs} Qt5::QSQLiteDriverPlugin Qt5::QJpegPlugin Qt5::QGifPlugin Qt5::QSvgPlugin Qt5::QSvgIconPlugin Qt5::QICOPlugin Qt5::QGenericEnginePlugin Qt5::QLocalClientConnectionFactory Qt5::QTcpServerConnectionFactory ${KIRIGAMI_PLUGIN} ${QUICK_PLUGIN} ${LABS_PLATFORM_PLUGIN} ${GRAPHEFFECTS_PLUGIN} ${GRAPHEFFECTS_PRIVATE_PLUGIN} ${QQC2_PLUGIN} ${QQC2_MATERIAL_PLUGIN} ${QQC2_UNIVERSAL_PLUGIN} ${QLAYOUTS_PLUGIN} ${QWINDOW_PLUGIN} ${QSHAPES_PLUGIN} ${QUICKSHAPES} ${QTEMPLATES_PLUGIN} ${QMODELS_PLUGIN} ${__Qt5Widgets_LIBRARIES} ) if(WIN32) target_link_libraries(${PROJECT_NAME} Qt5::QWindowsIntegrationPlugin ) elseif(IOS) target_link_libraries(${PROJECT_NAME} Qt5::QIOSIntegrationPlugin Qt5::QMacHeifPlugin Qt5::QMacJp2Plugin Qt5::QICNSPlugin Qt5::QTgaPlugin Qt5::QTiffPlugin Qt5::QWbmpPlugin Qt5::QWebpPlugin ) endif() endif() # Set a custom plist file for the app bundle if(APPLE) if(IOS) set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/misc/ios/Info.plist) else() set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/misc/macos/Info.plist) endif() endif() # iOS-specific linker flags if(IOS) set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} "-Wl,-e,_qt_main_wrapper -u _qt_registerPlatformPlugin") endif() # # Global C++ variables # if(UBUNTU_TOUCH) set(KAIDAN_COMPILE_DEFINITIONS UBUNTU_TOUCH=True) elseif(APPIMAGE) set(KAIDAN_COMPILE_DEFINITIONS APPIMAGE=True TARGET_GSTREAMER_PLUGINS="${TARGET_GSTREAMER_PLUGINS}" ) endif() if(STATIC_BUILD) set(KAIDAN_COMPILE_DEFINITIONS STATIC_BUILD=True ${KAIDAN_COMPILE_DEFINITIONS} ) endif() target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG_SOURCE_PATH="${CMAKE_SOURCE_DIR}" VERSION_STRING="${VERSION_STRING}" APPLICATION_ID="${APPLICATION_ID}" APPLICATION_NAME="${APPLICATION_NAME}" APPLICATION_DISPLAY_NAME="${APPLICATION_DISPLAY_NAME}" APPLICATION_DESCRIPTION="${APPLICATION_DESCRIPTION}" ${KAIDAN_COMPILE_DEFINITIONS} ) # # Install Kaidan # if(ANDROID) configure_file(${CMAKE_SOURCE_DIR}/misc/android/AndroidManifest.xml.in ${CMAKE_SOURCE_DIR}/misc/android/AndroidManifest.xml) elseif(IOS) configure_file(${CMAKE_SOURCE_DIR}/misc/ios/Info.plist.in ${CMAKE_SOURCE_DIR}/misc/ios/Info.plist) endif() if(UBUNTU_TOUCH AND CLICK_ARCH) set(CLICK_VERSION ${VERSION_STRING}) if(DEVELOPMENT_BUILD) set(CLICK_VERSION "${CLICK_VERSION}.${CLICK_DATE}") endif() # will replace ${CLICK_ARCH} with its value configure_file(${CMAKE_SOURCE_DIR}/misc/ubuntu-touch/manifest.json.in ${CMAKE_SOURCE_DIR}/misc/ubuntu-touch/manifest.json) # install kaidan binary install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/bin/${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX} ) # install kaidan media - install(DIRECTORY "data/images" + install(DIRECTORY "data/" DESTINATION "${CMAKE_INSTALL_FULL_DATADIR}/${APPLICATION_ID}" PATTERN "*.qrc" EXCLUDE ) # install icon install(FILES "data/images/kaidan.svg" "misc/ubuntu-touch/apparmor.json" "misc/ubuntu-touch/kaidan.desktop" "misc/ubuntu-touch/manifest.json" DESTINATION "${CMAKE_INSTALL_PREFIX}" ) elseif(UNIX AND NOT APPLE) # install kaidan binary install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/bin/${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} ) # install kaidan media - install(DIRECTORY "data/images" + install(DIRECTORY "data/" DESTINATION "${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME}" PATTERN "*.qrc" EXCLUDE ) # install icon (scalable + 128x) install(FILES "data/images/kaidan.svg" DESTINATION "${CMAKE_INSTALL_FULL_DATADIR}/icons/hicolor/scalable/apps" ) install(FILES "misc/kaidan-128x128.png" DESTINATION "${CMAKE_INSTALL_FULL_DATADIR}/icons/hicolor/128x128/apps" RENAME "kaidan.png" ) # install desktop file install(FILES "misc/kaidan.desktop" DESTINATION "${KDE_INSTALL_APPDIR}" ) # install metainfo install(FILES "misc/metadata.xml" DESTINATION "${KDE_INSTALL_METAINFODIR}" RENAME "${APPLICATION_ID}.appdata.xml" ) endif() # KNotifications if(USE_KNOTIFICATIONS) install(FILES misc/kaidan.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/LICENSE b/LICENSE index bdd1403..7e74602 100644 --- a/LICENSE +++ b/LICENSE @@ -1,213 +1,225 @@ Upstream-Name: kaidan Source: https://invent.kde.org/kde/kaidan/ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Files: src/* utils/* misc/* Copyright: 2016-2020, Linus Jahn 2016-2020, Jonah Brüchert 2017-2019, Ilya Bizyaev 2018-2019, Nicolas Fella 2019-2020, Melvin Keskin 2019, Filipe Azevedo 2018, Allan Nordhøy 2019, Robert Maerkisch 2020, Andrea Scarpino 2019, caca hueto 2019, Mauricio Torres Mejía 2019, Yury Gubich 2019, Volker Krause 2019, Simon Schmeisser 2019, Nick Richards 2019, Simon Redman 2019, Xavier 2018, Bjarne Roß 2018, SohnyBohny 2018, Marco Martin 2017, probonopd 2016, Marzanna 2017-2018, Eike Hein License: GPL-3+ with OpenSSL exception Files: i18n/* Copyright: 2017-2019, Linus Jahn 2017-2020, Jonah Brüchert 2017-2020, Muhammad Nur Hidayat Yasuyoshi 2018-2019, Allan Nordhøy 2017-2019, Joeke de Graaf 2018-2019, advocatux 2018-2019, oiseauroch 2019-2020, Milan Korecky 2019-2020, Sylke Vicious 2019-2020, Anne Onyme 017 2018-2019, aitzol berasategi 2019, ssantos 2019-2020, Xosé M 2019-2020, Yuri Chornoivan 2017, Ilya Bizyaev 2017, Robert Maerkisch 2019, Joan CiberSheep 2020, Adolfo Jayme Barrientos 2019, Eiad Rostom 2019, caca hueto 2019, Clemens Riese 2019, Txaume 2019, Melvin Keskin 2018, Andreas Kleinert 2017, Emmanuel Gil Peyrot License: GPL-3+ with OpenSSL exception Files: src/StatusBar.cpp src/StatusBar.h src/singleapp/* src/hsluv-c/* utils/generate-license.py Copyright: 2016, J-P Nurmi 2018-2019, Linus Jahn 2015-2018, Itay Grudev 2015, Alexei Boronine 2015, Roger Tallada 2017, Martin Mitas License: MIT Files: src/EmojiModel.cpp src/EmojiModel.h qml/elements/EmojiPicker.qml Copyright: 2017, Konstantinos Sideris License: GPL-3+ Files: src/QrCodeVideoFrame.h src/QrCodeVideoFrame.cpp Copyright: 2017, QZXing authors License: apache-2.0 Files: data/images/check-mark.svg Copyright: 2019, Melvin Keskin License: CC-BY-SA-4.0 Files: misc/kaidan-128x128.png data/images/kaidan.svg data/images/kaidan-bw.svg Copyright: 2016-2017, Ilya Bizyaev 2020, Mathis Brüchert 2020, Melvin Keskin 2019, Melvin Keskin License: CC-BY-SA-4.0 Comment: Inspired by graphic from https://www.toptal.com/designers/subtlepatterns/ Files: data/images/global-drawer-banner.svg Copyright: 2020, Mathis Brüchert License: CC-BY-SA-4.0 Files: data/images/account-deletion-from-client.svg data/images/account-deletion-from-client-and-server.svg + data/images/onboarding/account-transfer.svg + data/images/onboarding/automatic-registration.svg + data/images/onboarding/custom-form.svg + data/images/onboarding/display-name.svg + data/images/onboarding/login.svg + data/images/onboarding/manual-registration.svg + data/images/onboarding/password.svg + data/images/onboarding/registration.svg + data/images/onboarding/result.svg + data/images/onboarding/server.svg + data/images/onboarding/username.svg + data/images/onboarding/web-registration.svg Copyright: 2020, Mathis Brüchert License: CC-BY-SA-4.0 Files: utils/convert-prl-libs-to-cmake.pl Copyright: 2016, Konstantin Tokarev License: MIT-Apple License: GPL-3+ with OpenSSL exception This program 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. . In addition, as a special exception, the author of this program gives permission to link the code of its release with the OpenSSL project's "OpenSSL" library (or with modified versions of it that use the same license as the "OpenSSL" library), and distribute the linked executables. You must obey the GNU General Public License in all respects for all of the code used other than "OpenSSL". If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. . This program 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 this package. If not, see . . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'. License: GPL-3+ This program 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. . This program 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 this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'. License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License: MIT-Apple Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License: apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/data/data.qrc b/data/data.qrc new file mode 100644 index 0000000..64a8dc4 --- /dev/null +++ b/data/data.qrc @@ -0,0 +1,5 @@ + + + servers.json + + diff --git a/data/images/images.qrc b/data/images/images.qrc index 2dcc127..f5c4a63 100644 --- a/data/images/images.qrc +++ b/data/images/images.qrc @@ -1,14 +1,26 @@ + + kaidan.svg + kaidan-bw.svg + account-deletion-from-client.svg account-deletion-from-client-and-server.svg global-drawer-banner.svg chat-page-background.svg check-mark.svg kaidan.svg - - - kaidan.svg - kaidan-bw.svg + onboarding/account-transfer.svg + onboarding/automatic-registration.svg + onboarding/custom-form.svg + onboarding/display-name.svg + onboarding/login.svg + onboarding/manual-registration.svg + onboarding/password.svg + onboarding/registration.svg + onboarding/result.svg + onboarding/server.svg + onboarding/username.svg + onboarding/web-registration.svg diff --git a/data/images/onboarding/account-transfer.svg b/data/images/onboarding/account-transfer.svg new file mode 100644 index 0000000..953a921 --- /dev/null +++ b/data/images/onboarding/account-transfer.svg @@ -0,0 +1 @@ +Kaidanalice@kaidan.im (Online)Invite friendsTransfer accountDelete accountSettingsAbout \ No newline at end of file diff --git a/data/images/onboarding/automatic-registration.svg b/data/images/onboarding/automatic-registration.svg new file mode 100644 index 0000000..be7b67f --- /dev/null +++ b/data/images/onboarding/automatic-registration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/images/onboarding/custom-form.svg b/data/images/onboarding/custom-form.svg new file mode 100644 index 0000000..7dd3a5c --- /dev/null +++ b/data/images/onboarding/custom-form.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/images/onboarding/display-name.svg b/data/images/onboarding/display-name.svg new file mode 100644 index 0000000..56b423f --- /dev/null +++ b/data/images/onboarding/display-name.svg @@ -0,0 +1 @@ +Bob diff --git a/data/images/onboarding/login.svg b/data/images/onboarding/login.svg new file mode 100644 index 0000000..631d831 --- /dev/null +++ b/data/images/onboarding/login.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/images/onboarding/manual-registration.svg b/data/images/onboarding/manual-registration.svg new file mode 100644 index 0000000..c5dc284 --- /dev/null +++ b/data/images/onboarding/manual-registration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/images/onboarding/password.svg b/data/images/onboarding/password.svg new file mode 100644 index 0000000..b282da5 --- /dev/null +++ b/data/images/onboarding/password.svg @@ -0,0 +1 @@ + diff --git a/data/images/onboarding/registration.svg b/data/images/onboarding/registration.svg new file mode 100644 index 0000000..087d7fc --- /dev/null +++ b/data/images/onboarding/registration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/images/onboarding/result.svg b/data/images/onboarding/result.svg new file mode 100644 index 0000000..e79eb72 --- /dev/null +++ b/data/images/onboarding/result.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/images/onboarding/server.svg b/data/images/onboarding/server.svg new file mode 100644 index 0000000..55a435d --- /dev/null +++ b/data/images/onboarding/server.svg @@ -0,0 +1 @@ +example.org \ No newline at end of file diff --git a/data/images/onboarding/username.svg b/data/images/onboarding/username.svg new file mode 100644 index 0000000..08fa197 --- /dev/null +++ b/data/images/onboarding/username.svg @@ -0,0 +1 @@ + diff --git a/data/images/onboarding/web-registration.svg b/data/images/onboarding/web-registration.svg new file mode 100644 index 0000000..62b4ed7 --- /dev/null +++ b/data/images/onboarding/web-registration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/servers.json b/data/servers.json new file mode 100644 index 0000000..c3e0cef --- /dev/null +++ b/data/servers.json @@ -0,0 +1,180 @@ +[ + { + "jid": "anoxinon.me", + "supportsInBandRegistration": true, + "registrationWebPage": "https://xmpp.anoxinon.me/register/new", + "hosting": "professional", + "passwordReset": true, + "mucSupport": true, + "ratingCompliance": 100, + "ratingObservatory": "A", + "commercial": false, + "language": "de", + "languageEnglish": false, + "country": "de", + "website": "https://anoxinon.de/dienste/anoxinonmessenger/", + "imprint": "https://anoxinon.de/impressum/index.html", + "onlineSince": 2018, + "httpUploadSize": 25, + "messageStorageDuration": 14, + "lastCheck": "2019-10-07" + }, + { + "jid": "blabber.im", + "supportsInBandRegistration": true, + "registrationWebPage": "", + "hosting": "professional", + "passwordReset": false, + "mucSupport": true, + "ratingCompliance": 100, + "ratingObservatory": "A", + "commercial": false, + "language": "de", + "languageEnglish": true, + "country": "de", + "website": "https://blabber.im", + "imprint": "https://blabber.im/impressum/", + "onlineSince": 2018, + "httpUploadSize": 500, + "messageStorageDuration": 33, + "lastCheck": "2019-06-20" + }, + { + "jid": "dismail.de", + "supportsInBandRegistration": true, + "hosting": "professional", + "passwordReset": false, + "mucSupport": true, + "ratingCompliance": 100, + "ratingObservatory": "A", + "commercial": false, + "language": "en", + "languageEnglish": true, + "country": "de", + "website": "https://dismail.de", + "imprint": "https://dismail.de/impressum.html", + "onlineSince": 2015, + "httpUploadSize": 50, + "messageStorageDuration": 31, + "lastCheck": "2019-07-08" + }, + { + "jid": "jabber.fr", + "supportsInBandRegistration": true, + "registrationWebPage": "", + "hosting": "professional", + "passwordReset": true, + "mucSupport": true, + "ratingCompliance": 100, + "ratingObservatory": "A", + "commercial": false, + "language": "fr", + "languageEnglish": false, + "country": "fr", + "website": "https://jabberfr.org/", + "imprint": "https://wiki.jabberfr.org/index.php?title=CGU&action=info", + "onlineSince": 2012, + "httpUploadSize": 0, + "messageStorageDuration": 0, + "lastCheck": "2019-10-15" + }, + { + "jid": "jabbers.one", + "supportsInBandRegistration": true, + "registrationWebPage": "", + "hosting": "professional", + "passwordReset": false, + "mucSupport": true, + "ratingCompliance": 100, + "ratingObservatory": "A", + "commercial": false, + "language": "de", + "languageEnglish": true, + "country": "de", + "website": "https://www.jabbers.one/de/index.php", + "imprint": "https://www.jabbers.one/de/impressum.php", + "onlineSince": 2014, + "httpUploadSize": 50, + "messageStorageDuration": 30, + "lastCheck": "2019-12-30" + }, + { + "jid": "macaw.me", + "supportsInBandRegistration": true, + "registrationWebPage": "", + "hosting": "professional", + "passwordReset": false, + "mucSupport": true, + "ratingCompliance": 94, + "ratingObservatory": "A", + "commercial": false, + "language": "en", + "languageEnglish": true, + "country": "us", + "website": "https://macaw.me/", + "imprint": false, + "onlineSince": 2019, + "httpUploadSize": 100, + "messageStorageDuration": 31, + "lastCheck": "2020-02-24" + }, + { + "jid": "openim.de", + "supportsInBandRegistration": false, + "registrationWebPage": "https://openim.de:5281/register_web", + "hosting": "professional", + "passwordReset": true, + "mucSupport": true, + "ratingCompliance": 100, + "ratingObservatory": "A", + "language": "de", + "languageEnglish": false, + "commercial": false, + "country": "de", + "website": "https://openim.de", + "imprint": "https://www.openim.de/datenschutz.html", + "onlineSince": 2019, + "messageStorageDuration": 28, + "lastCheck": "2019-07-06" + }, + { + "jid": "xmpp-hosting.de", + "supportsInBandRegistration": false, + "registrationWebPage": "https://jabber.hot-chilli.net/forms/create/", + "hosting": "private", + "passwordReset": true, + "mucSupport": true, + "ratingCompliance": 100, + "ratingObservatory": "A", + "commercial": false, + "language": "de", + "languageEnglish": true, + "country": "de", + "website": "https://jabber.hot-chilli.net/de/jabberhosting/", + "imprint": "https://jabber.hot-chilli.net/de/impressum/", + "onlineSince": 2005, + "httpUploadSize": 100, + "messageStorageDuration": 31, + "lastCheck": "2019-09-07" + }, + { + "jid": "yochat.eu", + "supportsInBandRegistration": false, + "registrationWebPage": "https://yochat.eu/#converse/register", + "hosting": "private", + "passwordReset": false, + "mucSupport": false, + "ratingCompliance": 100, + "ratingObservatory": "A", + "commercial": false, + "language": "de", + "languageEnglish": false, + "country": "de", + "website": "https://yochat.eu", + "imprint": false, + "onlineSince": 2019, + "httpUploadSize": 0, + "messageStorageDuration": 0, + "lastCheck": "2020-03-07" + } +] diff --git a/src/BitsOfBinaryImageProvider.cpp b/src/BitsOfBinaryImageProvider.cpp new file mode 100644 index 0000000..86523a7 --- /dev/null +++ b/src/BitsOfBinaryImageProvider.cpp @@ -0,0 +1,102 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +#include "BitsOfBinaryImageProvider.h" + +// Qt +#include +#include +#include +// QXmpp +#include + +BitsOfBinaryImageProvider *BitsOfBinaryImageProvider::s_instance; + +BitsOfBinaryImageProvider *BitsOfBinaryImageProvider::instance() +{ + if (s_instance == nullptr) + s_instance = new BitsOfBinaryImageProvider(); + + return s_instance; +} + +BitsOfBinaryImageProvider::BitsOfBinaryImageProvider() + : QQuickImageProvider(QQuickImageProvider::Image) +{ + Q_ASSERT(!s_instance); + s_instance = this; +} + +BitsOfBinaryImageProvider::~BitsOfBinaryImageProvider() +{ + s_instance = nullptr; +} + +QImage BitsOfBinaryImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) +{ + QMutexLocker locker(&m_cacheMutex); + + const auto item = std::find_if(m_cache.begin(), m_cache.end(), [=] (const QXmppBitsOfBinaryData &item) { + return item.cid().toCidUrl() == id; + }); + + if (item == m_cache.end()) + return {}; + + QImage image = QImage::fromData((*item).data()); + size->setWidth(image.width()); + size->setHeight(image.height()); + + if (requestedSize.isValid()) + image = image.scaled(requestedSize); + + return image; +} + +bool BitsOfBinaryImageProvider::addImage(const QXmppBitsOfBinaryData &data) +{ + QMutexLocker locker(&m_cacheMutex); + + if (!QImageReader::supportedMimeTypes().contains(data.contentType().name().toUtf8())) { + return false; + } + + m_cache << data; + return true; +} + +bool BitsOfBinaryImageProvider::removeImage(const QXmppBitsOfBinaryContentId &cid) +{ + QMutexLocker locker(&m_cacheMutex); + + return m_cache.end() != std::remove_if(m_cache.begin(), m_cache.end(), [&] (const QXmppBitsOfBinaryData &item) { + return item.cid() == cid; + }); +} diff --git a/src/BitsOfBinaryImageProvider.h b/src/BitsOfBinaryImageProvider.h new file mode 100644 index 0000000..6c9e3ec --- /dev/null +++ b/src/BitsOfBinaryImageProvider.h @@ -0,0 +1,90 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +#ifndef BITSOFBINARYIMAGEPROVIDER_H +#define BITSOFBINARYIMAGEPROVIDER_H + +// Qt +#include +#include +#include +// QXmpp +#include + +/** + * Provider for images received via XEP-0231: Bits of Binary + * + * @note This class is thread-safe. + */ +class BitsOfBinaryImageProvider : public QQuickImageProvider +{ +public: + static BitsOfBinaryImageProvider *instance(); + + BitsOfBinaryImageProvider(); + ~BitsOfBinaryImageProvider(); + + /** + * Creates a QImage from the cached data. + * + * @param id BitsOfBinary content URL of the requested image (starting with "cid:"). + * @param size size of the cached image. + * @param requestedSize size the image should be scaled to. If this is invalid the image + * is not scaled. + */ + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + + /** + * Adds @c QXmppBitsOfBinaryData to the cache used to provide images to QML. + * + * @param data Image in form of a Bits of Binary data element. + * + * @return True if the data has a supported image format. False if it could not be + * added to the cache. + */ + bool addImage(const QXmppBitsOfBinaryData &data); + + /** + * Removes the BitsOfBinary data associated with the given content ID from the cache. + * + * @param cid BitsOfBinary content ID to search for. + * + * @return True if the content ID could be found and the image was removed, False + * otherwise. + */ + bool removeImage(const QXmppBitsOfBinaryContentId &cid); + +private: + static BitsOfBinaryImageProvider *s_instance; + QMutex m_cacheMutex; + QVector m_cache; +}; + +#endif // BITSOFBINARYIMAGEPROVIDER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c8ae429..89114c2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,64 +1,71 @@ # set Kaidans sources (used in main cmake file) set(KAIDAN_SOURCES src/main.cpp src/Kaidan.cpp src/ClientWorker.cpp src/AvatarFileStorage.cpp src/Database.cpp src/RosterItem.cpp src/RosterModel.cpp src/RosterFilterProxyModel.cpp src/RosterDb.cpp src/RosterManager.cpp src/RegistrationManager.cpp src/Message.cpp src/MessageModel.cpp src/MessageDb.cpp src/MessageHandler.cpp src/Notifications.cpp src/PresenceCache.cpp src/DiscoveryManager.cpp src/VCardManager.cpp src/VCardModel.cpp src/LogHandler.cpp src/StatusBar.cpp src/UploadManager.cpp src/EmojiModel.cpp src/TransferCache.cpp src/DownloadManager.cpp src/ServerFeaturesCache.cpp src/QmlUtils.cpp src/Utils.cpp src/QrCodeDecoder.cpp src/QrCodeGenerator.cpp src/QrCodeScannerFilter.cpp src/QrCodeVideoFrame.cpp src/CameraModel.cpp src/AudioDeviceModel.cpp src/MediaSettings.cpp src/CameraImageCapture.cpp src/MediaUtils.cpp src/MediaRecorder.cpp + src/CredentialsGenerator.cpp src/CredentialsValidator.cpp + src/BitsOfBinaryImageProvider.cpp + src/DataFormModel.cpp + src/RegistrationDataFormFilterModel.cpp + src/RegistrationDataFormModel.cpp + src/ServerListModel.cpp + src/ServerListItem.cpp # needed to trigger moc generation / to be displayed in IDEs src/Enums.h src/Globals.h # kaidan QXmpp extensions (need to be merged into QXmpp upstream) src/qxmpp-exts/QXmppHttpUploadIq.cpp src/qxmpp-exts/QXmppUploadRequestManager.cpp src/qxmpp-exts/QXmppUploadManager.cpp src/qxmpp-exts/QXmppColorGenerator.cpp src/qxmpp-exts/QXmppUri.cpp # hsluv-c required for color generation src/hsluv-c/hsluv.c ) if(NOT ANDROID AND NOT IOS) set(KAIDAN_SOURCES ${KAIDAN_SOURCES} src/singleapp/singleapplication.cpp src/singleapp/singleapplication_p.cpp ) endif() diff --git a/src/ClientWorker.cpp b/src/ClientWorker.cpp index bf6466f..95a3d11 100644 --- a/src/ClientWorker.cpp +++ b/src/ClientWorker.cpp @@ -1,280 +1,355 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #include "ClientWorker.h" // Qt #include #include #include #include +#include #include // QXmpp #include #include #include #include #include // Kaidan #include "DiscoveryManager.h" #include "DownloadManager.h" #include "Kaidan.h" #include "LogHandler.h" #include "MessageDb.h" #include "MessageHandler.h" #include "RegistrationManager.h" #include "RosterDb.h" #include "RosterManager.h" #include "UploadManager.h" #include "VCardManager.h" -ClientWorker::ClientWorker(Caches *caches, Kaidan *kaidan, bool enableLogging, QGuiApplication *app, - QObject* parent) - : QObject(parent), caches(caches), kaidan(kaidan), enableLogging(enableLogging), app(app) +ClientWorker::ClientWorker(Caches *caches, Kaidan *kaidan, bool enableLogging, QGuiApplication *app, QObject* parent) + : QObject(parent), m_caches(caches), kaidan(kaidan), enableLogging(enableLogging), app(app) { client = new QXmppClient(this); logger = new LogHandler(client, this); logger->enableLogging(enableLogging); vCardManager = new VCardManager(client, caches->avatarStorage, this); registrationManager = new RegistrationManager(kaidan, this, client, caches->settings); rosterManager = new RosterManager(kaidan, client, caches->rosterModel, caches->avatarStorage, vCardManager, this); msgHandler = new MessageHandler(kaidan, client, caches->msgModel, this); discoManager = new DiscoveryManager(client, this); uploadManager = new UploadManager(client, rosterManager, this); downloadManager = new DownloadManager(kaidan, caches->transferCache, caches->msgModel, this); - client->addExtension(registrationManager); - connect(client, &QXmppClient::presenceReceived, caches->presCache, &PresenceCache::updatePresence); connect(client, &QXmppClient::disconnected, caches->presCache, &PresenceCache::clear); connect(this, &ClientWorker::credentialsUpdated, this, &ClientWorker::setCredentials); // publish kaidan version QXmppVersionManager* versionManager = client->findExtension(); versionManager->setClientName(APPLICATION_DISPLAY_NAME); versionManager->setClientVersion(VERSION_STRING); versionManager->setClientOs(QSysInfo::prettyProductName()); // Client State Indication connect(app, &QGuiApplication::applicationStateChanged, this, &ClientWorker::setCsiState); // account deletion + connect(kaidan, &Kaidan::deleteAccountFromClient, this, &ClientWorker::deleteAccountFromClient); + connect(kaidan, &Kaidan::deleteAccountFromClientAndServer, this, &ClientWorker::deleteAccountFromClientAndServer); connect(this, &ClientWorker::deleteAccountFromDatabase, kaidan->rosterDb(), &RosterDb::clearAll); connect(this, &ClientWorker::deleteAccountFromDatabase, kaidan->messageDb(), &MessageDb::removeAllMessages); + + connect(kaidan, &Kaidan::changePassword, this, &ClientWorker::changePassword); + connect(kaidan, &Kaidan::changeDisplayName, this, &ClientWorker::changeDisplayName); } VCardManager *ClientWorker::getVCardManager() const { return vCardManager; } +ClientWorker::Caches *ClientWorker::caches() const +{ + return m_caches; +} + void ClientWorker::main() { // Initialize the random number generator used by "qrand()" for QXmpp < 1.3.0 or QXmpp built with Qt < 5.10. // Please do not use that deprecated method for Kaidan. qsrand(time(nullptr)); connect(client, &QXmppClient::stateChanged, kaidan, &Kaidan::setConnectionState); connect(client, &QXmppClient::connected, this, &ClientWorker::onConnected); connect(client, &QXmppClient::disconnected, this, &ClientWorker::onDisconnected); connect(client, &QXmppClient::error, this, &ClientWorker::onConnectionError); connect(this, &ClientWorker::connectRequested, this, &ClientWorker::xmppConnect); + connect(this, &ClientWorker::registrationFormRequested, this, &ClientWorker::connectToRegister); connect(this, &ClientWorker::disconnectRequested, client, &QXmppClient::disconnectFromServer); } void ClientWorker::xmppConnect() { QXmppConfiguration config; config.setJid(creds.jid); config.setResource(generateJidResourceWithRandomSuffix(creds.jidResourcePrefix)); config.setPassword(creds.password); config.setAutoAcceptSubscriptions(false); + + connectToServer(config); +} + +void ClientWorker::connectToServer(QXmppConfiguration config) +{ + if (client->state() != QXmppClient::DisconnectedState) { + qWarning() << "[main] Tried to connect, even if still connected!" << "Requesting disconnect first and connecting then."; + m_isReconnecting = true; + m_configToBeUsedOnNextConnect = config; + client->disconnectFromServer(); + return; + } + + config.setJid(creds.jid); config.setStreamSecurityMode(QXmppConfiguration::TLSRequired); - config.setAutoReconnectionEnabled(true); // will automatically reconnect + // on first try we must be sure that we connect successfully // otherwise this could end in a reconnection loop - if (creds.isFirstTry) - config.setAutoReconnectionEnabled(false); + config.setAutoReconnectionEnabled(!creds.isFirstTry); + + // Reset the attribute for In-Band Registration support. + // That is needed when the attribute was true after the last logout but the server disabled the support until the next login. + // Without that reset, the attribute would stay "true". + m_caches->serverFeaturesCache->setInBandRegistrationSupported(false); + + client->connectToServer(config); +} - client->connectToServer(config, QXmppPresence(QXmppPresence::Available)); +void ClientWorker::connectToRegister() +{ + registrationManager->setRegisterOnConnectEnabled(true); + connectToServer(); } void ClientWorker::deleteAccountFromClientAndServer() { m_isAccountToBeDeletedFromClientAndServer = true; // If the client is already connected, delete the account directly from the server. // Otherwise, connect first and delete the account afterwards. if (client->isAuthenticated()) { registrationManager->deleteAccount(); } else { m_isClientConnectedBeforeAccountDeletionFromServer = false; xmppConnect(); } } void ClientWorker::deleteAccountFromClient() { // If the client is already disconnected, delete the account directly from the client. // Otherwise, disconnect first and delete the account afterwards. if (!client->isAuthenticated()) { emit deleteAccountFromDatabase(); kaidan->deleteCredentials(); m_isAccountToBeDeletedFromClient = false; } else { m_isAccountToBeDeletedFromClient = true; - emit disconnectRequested(); + client->disconnectFromServer(); } } -void ClientWorker::onAccountDeletedFromServer() +void ClientWorker::handleAccountDeletedFromServer() { m_isAccountDeletedFromServer = true; } -void ClientWorker::onAccountDeletionFromServerFailed(QXmppStanza::Error error) +void ClientWorker::handleAccountDeletionFromServerFailed(QXmppStanza::Error error) { emit kaidan->passiveNotificationRequested(tr("Your account could not be deleted from the server. Therefore, it was also not removed from this app: %1").arg(error.text())); m_isAccountToBeDeletedFromClientAndServer = false; if (!m_isClientConnectedBeforeAccountDeletionFromServer) - emit disconnectRequested(); + client->disconnectFromServer(); +} + +void ClientWorker::changePassword(const QString &newPassword) +{ + if (client->isAuthenticated()) { + registrationManager->changePassword(newPassword); + } else { + m_passwordToBeSetOnNextConnect = newPassword; + xmppConnect(); + } +} + +void ClientWorker::changeDisplayName(const QString &displayName) +{ + if (client->isAuthenticated()) { + vCardManager->updateNickname(displayName); + } else { + m_displayNameToBeSetOnNextConnect = displayName; + + // The check is needed when this method is called during account registration to avoid connecting to the server before the registration is finished. + // During registration, the display name is set first and the client connects to the server afterwards. + // The client should only connect to the server during normal usage. + if (!(creds.jid.isEmpty() || creds.password.isEmpty())) + xmppConnect(); + } } void ClientWorker::onConnected() { // no mutex needed, because this is called from updateClient() qDebug() << "[client] Connected successfully to server"; // Emit signal, that logging in with these credentials has worked for the first time if (creds.isFirstTry) emit kaidan->loggedInWithNewCredentials(); // accept credentials and save them creds.isFirstTry = false; - caches->settings->setValue(KAIDAN_SETTINGS_AUTH_JID, creds.jid); - caches->settings->setValue(KAIDAN_SETTINGS_AUTH_PASSWD, - QString::fromUtf8(creds.password.toUtf8().toBase64())); - - // after first log in we always want to automatically reconnect - client->configuration().setAutoReconnectionEnabled(true); + m_caches->settings->setValue(KAIDAN_SETTINGS_AUTH_JID, creds.jid); + m_caches->settings->setValue(KAIDAN_SETTINGS_AUTH_PASSWD, QString::fromUtf8(creds.password.toUtf8().toBase64())); // If the account could not be deleted from the server because the client was disconnected, delete it now. if (m_isAccountToBeDeletedFromClientAndServer) registrationManager->deleteAccount(); + else + // After the first login, the client should always try to reconnect automatically in case of a connection outage. + client->configuration().setAutoReconnectionEnabled(true); + + // If the display name could not be changed on the server because the client was disconnected, change it now and disconnect again. + if (!m_passwordToBeSetOnNextConnect.isEmpty()) { + registrationManager->changePassword(m_passwordToBeSetOnNextConnect); + m_passwordToBeSetOnNextConnect.clear(); + client->disconnectFromServer(); + } + + // If the display name could not be changed from the server because the client was disconnected, change it now. + if (!m_displayNameToBeSetOnNextConnect.isEmpty()) { + vCardManager->updateNickname(m_displayNameToBeSetOnNextConnect); + m_displayNameToBeSetOnNextConnect.clear(); + } } void ClientWorker::onDisconnected() { + if (m_isReconnecting) { + m_isReconnecting = false; + connectToServer(m_configToBeUsedOnNextConnect); + m_configToBeUsedOnNextConnect = {}; + return; + } + + registrationManager->setRegisterOnConnectEnabled(false); + // Delete the account from the client if the client was connected and had to disconnect first or if the account was deleted from the server. if (m_isAccountToBeDeletedFromClient || (m_isAccountToBeDeletedFromClientAndServer && m_isAccountDeletedFromServer)) { m_isAccountToBeDeletedFromClientAndServer = false; m_isAccountDeletedFromServer = false; deleteAccountFromClient(); } } void ClientWorker::onConnectionError(QXmppClient::Error error) { // no mutex needed, because this is called from updateClient() qDebug() << "[client] Disconnected:" << error; - // Check if first time connecting with these credentials - if (creds.isFirstTry || error == QXmppClient::XmppStreamError) { - // always request new credentials, when failed to connect on first time - emit kaidan->newCredentialsNeeded(); - } - QXmppStanza::Error::Condition xmppStreamError; QAbstractSocket::SocketError socketError; switch (error) { case QXmppClient::NoError: emit connectionErrorChanged(ClientWorker::NoError); break; case QXmppClient::KeepAliveError: emit connectionErrorChanged(ClientWorker::KeepAliveError); break; case QXmppClient::XmppStreamError: xmppStreamError = client->xmppStreamError(); + qDebug() << "[client] XMPP stream error:" << xmppStreamError; if (xmppStreamError == QXmppStanza::Error::NotAuthorized) { emit connectionErrorChanged(ClientWorker::AuthenticationFailed); } else { emit connectionErrorChanged(ClientWorker::NotConnected); } break; case QXmppClient::SocketError: socketError = client->socketError(); switch (socketError) { case QAbstractSocket::ConnectionRefusedError: case QAbstractSocket::RemoteHostClosedError: emit connectionErrorChanged(ClientWorker::ConnectionRefused); break; case QAbstractSocket::HostNotFoundError: emit connectionErrorChanged(ClientWorker::DnsError); break; case QAbstractSocket::SocketAccessError: emit connectionErrorChanged(ClientWorker::NoNetworkPermission); break; case QAbstractSocket::SocketTimeoutError: emit connectionErrorChanged(ClientWorker::KeepAliveError); break; case QAbstractSocket::SslHandshakeFailedError: case QAbstractSocket::SslInternalError: emit connectionErrorChanged(ClientWorker::TlsFailed); break; default: emit connectionErrorChanged(ClientWorker::NotConnected); } break; } } void ClientWorker::setCsiState(Qt::ApplicationState state) { if (state == Qt::ApplicationActive) client->setActive(true); else client->setActive(false); } QString ClientWorker::generateJidResourceWithRandomSuffix(const QString jidResourcePrefix , unsigned int length) const { return jidResourcePrefix % "." % QXmppUtils::generateStanzaHash(length); } diff --git a/src/ClientWorker.h b/src/ClientWorker.h index f660a30..cc10dcd 100644 --- a/src/ClientWorker.h +++ b/src/ClientWorker.h @@ -1,254 +1,288 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef CLIENTWORKER_H #define CLIENTWORKER_H // Qt #include #include #include class QGuiApplication; // QXmpp #include // Kaidan #include "AvatarFileStorage.h" +#include "BitsOfBinaryImageProvider.h" #include "Database.h" #include "Enums.h" #include "Globals.h" #include "MessageModel.h" #include "PresenceCache.h" #include "RosterModel.h" #include "ServerFeaturesCache.h" #include "TransferCache.h" class LogHandler; class Kaidan; class ClientWorker; class RegistrationManager; class RosterManager; class MessageHandler; class DiscoveryManager; class VCardManager; class UploadManager; class DownloadManager; /** * The ClientWorker is used as a QObject-based worker on the ClientThread. */ class ClientWorker : public QObject { Q_OBJECT public: /** * enumeration of possible connection errors */ enum ConnectionError { NoError, AuthenticationFailed, NotConnected, TlsFailed, TlsNotAvailable, DnsError, ConnectionRefused, NoSupportedAuth, KeepAliveError, NoNetworkPermission, RegistrationUnsupported }; Q_ENUM(ConnectionError) struct Caches { - Caches(Kaidan *kaidan, RosterDb *rosterDb, MessageDb *msgDb, - QObject *parent = nullptr) - : msgModel(new MessageModel(kaidan, msgDb, parent)), - rosterModel(new RosterModel(rosterDb, parent)), - avatarStorage(new AvatarFileStorage(parent)), - serverFeaturesCache(new ServerFeaturesCache(parent)), - presCache(new PresenceCache(parent)), - transferCache(new TransferCache(parent)), - settings(new QSettings(APPLICATION_NAME, APPLICATION_NAME)) + Caches(Kaidan *kaidan, RosterDb *rosterDb, MessageDb *msgDb, QObject *parent = nullptr) + : msgModel(new MessageModel(kaidan, msgDb, parent)), + rosterModel(new RosterModel(rosterDb, parent)), + avatarStorage(new AvatarFileStorage(parent)), + serverFeaturesCache(new ServerFeaturesCache(parent)), + presCache(new PresenceCache(parent)), + transferCache(new TransferCache(parent)), + settings(new QSettings(APPLICATION_NAME, APPLICATION_NAME)) { rosterModel->setMessageModel(msgModel); } ~Caches() { - delete msgModel; - delete rosterModel; - delete avatarStorage; - delete presCache; - delete transferCache; delete settings; } MessageModel *msgModel; RosterModel *rosterModel; AvatarFileStorage *avatarStorage; ServerFeaturesCache *serverFeaturesCache; PresenceCache *presCache; TransferCache* transferCache; QSettings *settings; }; struct Credentials { QString jid; QString jidResourcePrefix; QString password; // if never connected successfully before with these credentials bool isFirstTry; }; /** * @param caches All caches running in the main thread for communication with the UI. * @param kaidan Main back-end class, running in the main thread. * @param enableLogging If logging of the XMPP stream should be done. * @param app The QGuiApplication to determine if the window is active. * @param parent Optional QObject-based parent. */ - ClientWorker(Caches *caches, Kaidan *kaidan, bool enableLogging, QGuiApplication *app, - QObject *parent = nullptr); + ClientWorker(Caches *caches, Kaidan *kaidan, bool enableLogging, QGuiApplication *app, QObject *parent = nullptr); VCardManager *getVCardManager() const; + /** + * Returns all models and caches. + */ + Caches *caches() const; + public slots: /** * Main function of the client thread */ void main(); /** * Sets the new credentials for next connect. * * @param creds The new credentials for the next connect */ void setCredentials(ClientWorker::Credentials creds) { this->creds = creds; } /** - * Connects the client with the server. + * Connects to the server and logs in with all needed configuration variables. */ void xmppConnect(); + /** + * Connects to the server with a minimal configuration and adds additional variables to it before connecting if a configuration is passed. + * + * @param config + */ + void connectToServer(QXmppConfiguration config = QXmppConfiguration()); + + /** + * Connects to the server and requests a data form for account registration. + */ + void connectToRegister(); + /** * Deletes the account data from the client and server. */ void deleteAccountFromClientAndServer(); /** * Deletes the account data from the configuration file and database. */ void deleteAccountFromClient(); /** * Called when the account is deleted from the server. */ - void onAccountDeletedFromServer(); + void handleAccountDeletedFromServer(); /** * Called when the account could not be deleted from the server. * * @param error error of the failed account deletion */ - void onAccountDeletionFromServerFailed(QXmppStanza::Error error); + void handleAccountDeletionFromServerFailed(QXmppStanza::Error error); + + /** + * Changes the user's password. + * + * @param newPassword new password to set + */ + void changePassword(const QString &newPassword); + + /** + * Changes the user's display name. + * + * If the user is not logged in during account registration, the display name is changed on the next login. + * + * @param displayName new name that is shown to contacts + */ + void changeDisplayName(const QString &displayName); signals: - // emitted by 'Kaidan' to us: + // Those signals are emitted by Kaidan.cpp and are used by this class. void connectRequested(); void disconnectRequested(); void credentialsUpdated(ClientWorker::Credentials creds); + void registrationFormRequested(); /** * Emitted when the client failed to connect to the server. * * @param error new connection error */ void connectionErrorChanged(ClientWorker::ConnectionError error); /** * Deletes data related to the current account (messages, contacts etc.) from the database. */ void deleteAccountFromDatabase(); private slots: /** * Called when an authenticated connection to the server is established. */ void onConnected(); /** * Called when the connection to the server is closed. */ void onDisconnected(); /** * Sets a new connection error. */ void onConnectionError(QXmppClient::Error error); /** * Uses the QGuiApplication state to reduce network traffic when window is minimized */ void setCsiState(Qt::ApplicationState state); private: /** * Generates the resource part of a JID with a suffix consisting of a dot followed by random alphanumeric characters. * * @param length number of random alphanumeric characters the suffix should consist of after the dot */ QString generateJidResourceWithRandomSuffix(const QString jidResourcePrefix, unsigned int length = 4) const; - Caches *caches; + Caches *m_caches; Kaidan *kaidan; QXmppClient *client; LogHandler *logger; Credentials creds; bool enableLogging; QGuiApplication *app; RegistrationManager *registrationManager; RosterManager *rosterManager; MessageHandler *msgHandler; DiscoveryManager *discoManager; VCardManager *vCardManager; UploadManager *uploadManager; DownloadManager *downloadManager; + bool m_isReconnecting = false; + QXmppConfiguration m_configToBeUsedOnNextConnect; + // These variables are used for checking the state of an ongoing account deletion. bool m_isAccountToBeDeletedFromClient = false; bool m_isAccountToBeDeletedFromClientAndServer = false; bool m_isAccountDeletedFromServer = false; bool m_isClientConnectedBeforeAccountDeletionFromServer = true; + + QString m_passwordToBeSetOnNextConnect; + QString m_displayNameToBeSetOnNextConnect; }; #endif // CLIENTWORKER_H diff --git a/src/CredentialsGenerator.cpp b/src/CredentialsGenerator.cpp new file mode 100644 index 0000000..147f976 --- /dev/null +++ b/src/CredentialsGenerator.cpp @@ -0,0 +1,82 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +// Qt +#include +// Kaidan +#include "CredentialsGenerator.h" +#include "Globals.h" + +static const QLatin1String VOWELS = QLatin1String("aeiou"); +static const int VOWELS_LENGTH = VOWELS.size(); + +static const QLatin1String CONSONANTS = QLatin1String("bcdfghjklmnpqrstvwxyz"); +static const int CONSONANTS_LENGTH = CONSONANTS.size(); + +CredentialsGenerator::CredentialsGenerator(QObject *parent) + : QObject(parent) +{ +} + +QString CredentialsGenerator::generatePronounceableName(unsigned int length) +{ + QString randomString; + randomString.reserve(length); + bool startWithVowel = QRandomGenerator::global()->generate() % 2; + length += startWithVowel; + for (unsigned int i = startWithVowel; i < length; ++i) { + if (i % 2) + randomString.append(VOWELS.at(QRandomGenerator::global()->generate() % VOWELS_LENGTH)); + else + randomString.append(CONSONANTS.at(QRandomGenerator::global()->generate() % CONSONANTS_LENGTH)); + } + return randomString; +} + +QString CredentialsGenerator::generateUsername() +{ + return generatePronounceableName(GENERATED_USERNAME_LENGTH); +} + +QString CredentialsGenerator::generatePassword() +{ + return generatePassword(GENERATED_PASSWORD_LENGTH_LOWER_BOUND + QRandomGenerator::global()->generate() % (GENERATED_PASSWORD_LENGTH_UPPER_BOUND - GENERATED_PASSWORD_LENGTH_LOWER_BOUND + 1)); +} + +QString CredentialsGenerator::generatePassword(unsigned int length) +{ + QString password; + password.reserve(length); + + for (unsigned int i = 0; i < length; i++) + password.append(GENERATED_PASSWORD_ALPHABET.at(QRandomGenerator::global()->generate() % GENERATED_PASSWORD_ALPHABET_LENGTH)); + + return password; +} diff --git a/src/CredentialsValidator.h b/src/CredentialsGenerator.h similarity index 60% copy from src/CredentialsValidator.h copy to src/CredentialsGenerator.h index cf9121d..65c087c 100644 --- a/src/CredentialsValidator.h +++ b/src/CredentialsGenerator.h @@ -1,75 +1,73 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -#ifndef CREDENTIALSVALIDATOR_H -#define CREDENTIALSVALIDATOR_H +#ifndef CREDENTIALSGENERATOR_H +#define CREDENTIALSGENERATOR_H #include /** - * This is a validator for XMPP account credentials. + * This class contains generators for usernames and passwords. */ -class CredentialsValidator : public QObject +class CredentialsGenerator : public QObject { Q_OBJECT public: - CredentialsValidator(QObject *parent = nullptr); + explicit CredentialsGenerator(QObject *parent = nullptr); /** - * Returns true if the given string is a valid JID. + * Generates a random string with alternating consonants and vowels. * - * @param jid JID to be validated + * Whether the string starts with a consonant or vowel is random. */ - Q_INVOKABLE static bool isJidValid(const QString &jid); + Q_INVOKABLE static QString generatePronounceableName(unsigned int length); /** - * Returns true if the given string is a valid username. - * - * @param username username to be validated + * Generates a pronounceable username with @c GENERATED_USERNAME_LENGTH as fixed length. */ - Q_INVOKABLE static bool isUsernameValid(const QString &username); + Q_INVOKABLE static QString generateUsername(); /** - * Returns true if the given string is a valid server. - * - * @param username server to be validated + * Generates a random password containing characters from + * @c GENERATED_PASSWORD_ALPHABET with a length between + * @c GENERATED_PASSWORD_LOWER_BOUND (including) and + * @c GENERATED_PASSWORD_UPPER_BOUND (including). */ - Q_INVOKABLE static bool isServerValid(const QString &server); + Q_INVOKABLE static QString generatePassword(); /** - * Returns true if the given string is a valid password. - * - * @param password password to be validated + * Generates a random password containing characters from + * @c GENERATED_PASSWORD_ALPHABET. */ - Q_INVOKABLE static bool isPasswordValid(const QString &password); + Q_INVOKABLE static QString generatePassword(unsigned int length); }; -#endif // CREDENTIALSVALIDATOR_H +#endif // CREDENTIALSGENERATOR_H diff --git a/src/CredentialsValidator.cpp b/src/CredentialsValidator.cpp index 0ff2862..31d0a36 100644 --- a/src/CredentialsValidator.cpp +++ b/src/CredentialsValidator.cpp @@ -1,58 +1,58 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #include "CredentialsValidator.h" #include CredentialsValidator::CredentialsValidator(QObject *parent) : QObject(parent) { } -bool CredentialsValidator::isJidValid(const QString &jid) +bool CredentialsValidator::isAccountJidValid(const QString &jid) { return jid.count('@') == 1 && isUsernameValid(QXmppUtils::jidToUser(jid)) && isServerValid(QXmppUtils::jidToDomain(jid)); } bool CredentialsValidator::isUsernameValid(const QString &username) { return !(username.isEmpty() || username.contains(" ")); } bool CredentialsValidator::isServerValid(const QString &server) { return !(server.isEmpty() || server.contains(" ")); } bool CredentialsValidator::isPasswordValid(const QString &password) { return !password.isEmpty(); } diff --git a/src/CredentialsValidator.h b/src/CredentialsValidator.h index cf9121d..91f29a5 100644 --- a/src/CredentialsValidator.h +++ b/src/CredentialsValidator.h @@ -1,75 +1,75 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef CREDENTIALSVALIDATOR_H #define CREDENTIALSVALIDATOR_H #include /** * This is a validator for XMPP account credentials. */ class CredentialsValidator : public QObject { Q_OBJECT public: CredentialsValidator(QObject *parent = nullptr); /** - * Returns true if the given string is a valid JID. + * Returns true if the given string is a valid JID of an XMPP account. * * @param jid JID to be validated */ - Q_INVOKABLE static bool isJidValid(const QString &jid); + Q_INVOKABLE static bool isAccountJidValid(const QString &jid); /** * Returns true if the given string is a valid username. * * @param username username to be validated */ Q_INVOKABLE static bool isUsernameValid(const QString &username); /** * Returns true if the given string is a valid server. * * @param username server to be validated */ Q_INVOKABLE static bool isServerValid(const QString &server); /** * Returns true if the given string is a valid password. * * @param password password to be validated */ Q_INVOKABLE static bool isPasswordValid(const QString &password); }; #endif // CREDENTIALSVALIDATOR_H diff --git a/src/DataFormModel.cpp b/src/DataFormModel.cpp new file mode 100644 index 0000000..f5178de --- /dev/null +++ b/src/DataFormModel.cpp @@ -0,0 +1,174 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +#include "DataFormModel.h" +// Qt +#include +// QXmpp +#include +// Kaidan +#include "Globals.h" + +DataFormModel::DataFormModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +DataFormModel::DataFormModel(const QXmppDataForm &dataForm, QObject *parent) + : QAbstractListModel(parent), m_form(dataForm) +{ +} + +DataFormModel::~DataFormModel() = default; + +int DataFormModel::rowCount(const QModelIndex &parent) const +{ + // For list models only the root node (an invalid parent) should return the list's size. For all + // other (valid) parents, rowCount() should return 0 so that it does not become a tree model. + if (parent.isValid()) + return 0; + + return m_form.fields().size(); +} + +QVariant DataFormModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || !hasIndex(index.row(), index.column(), index.parent())) + return {}; + + const QXmppDataForm::Field &field = m_form.fields().at(index.row()); + + switch(role) { + case Key: + return field.key(); + case Type: + return field.type(); + case Label: + return field.label(); + case IsRequired: + return field.isRequired(); + case Value: + return field.value(); + case Description: + return field.description(); + case MediaUrl: + return mediaSourceUri(field); + } + + return {}; +} + +bool DataFormModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || !hasIndex(index.row(), index.column(), index.parent())) + return false; + + QXmppDataForm::Field field = m_form.fields().at(index.row()); + + switch(role) { + case Key: + field.setKey(value.toString()); + break; + case Type: + field.setType(static_cast(value.toInt())); + break; + case Label: + field.setLabel(value.toString()); + break; + case IsRequired: + field.setRequired(value.toBool()); + break; + case Value: + field.setValue(value); + break; + case Description: + field.setDescription(value.toString()); + break; + default: + return false; + } + + m_form.fields()[index.row()] = field; + emit dataChanged(index, index, QVector() << role); + return true; +} + +QHash DataFormModel::roleNames() const +{ + return { + {Key, QByteArrayLiteral("key")}, + {Type, QByteArrayLiteral("type")}, + {Label, QByteArrayLiteral("label")}, + {IsRequired, QByteArrayLiteral("isRequired")}, + {Value, QByteArrayLiteral("value")}, + {Description, QByteArrayLiteral("description")}, + {MediaUrl, QByteArrayLiteral("mediaUrl")} + }; +} + +QXmppDataForm DataFormModel::form() const +{ + return m_form; +} + +void DataFormModel::setForm(const QXmppDataForm &form) +{ + beginResetModel(); + m_form = form; + endResetModel(); + + emit formChanged(); +} + +QString DataFormModel::instructions() const +{ + return m_form.instructions(); +} + +QString DataFormModel::mediaSourceUri(const QXmppDataForm::Field &field) const +{ + QString mediaSourceUri; + const auto mediaSources = field.mediaSources(); + + for (const auto &mediaSource : mediaSources) { + mediaSourceUri = mediaSource.uri().toString(); + // Prefer Bits of Binary URIs. + // In most cases, the data has been received already then. + if (QXmppBitsOfBinaryContentId::isBitsOfBinaryContentId(mediaSourceUri, true)) + return QStringLiteral("image://%1/%2").arg(BITS_OF_BINARY_IMAGE_PROVIDER_NAME, mediaSourceUri); + } + + return mediaSourceUri; +} + +QString DataFormModel::title() const +{ + return m_form.title(); +} diff --git a/src/VCardManager.h b/src/DataFormModel.h similarity index 52% copy from src/VCardManager.h copy to src/DataFormModel.h index 3bc5297..d9a78cd 100644 --- a/src/VCardManager.h +++ b/src/DataFormModel.h @@ -1,72 +1,95 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -#ifndef VCARDMANAGER_H -#define VCARDMANAGER_H +#ifndef DATAFORMMODEL_H +#define DATAFORMMODEL_H -#include -#include -#include +#include +#include -class AvatarFileStorage; -class QXmppClient; +#include -class VCardManager : public QObject +class DataFormModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(QString title READ title NOTIFY formChanged) + Q_PROPERTY(QString instructions READ instructions NOTIFY formChanged) public: - VCardManager(QXmppClient *client, AvatarFileStorage *avatars, QObject *parent = nullptr); + enum DataFormFieldType : quint8 { + BooleanField, + FixedField, + HiddenField, + JidMultiField, + JidSingleField, + ListMultiField, + ListSingleField, + TextMultiField, + TextPrivateField, + TextSingleField + }; + Q_ENUM(DataFormFieldType) - /** - * Will request the VCard from the server - */ - void fetchVCard(const QString& jid); + enum FormRoles { + Key = Qt::UserRole + 1, + Type, + Label, + IsRequired, + Value, + Description, + MediaUrl + }; - /** - * Handles incoming VCards and processes them (save avatar, etc.) - */ - void handleVCard(const QXmppVCardIq &iq); + DataFormModel(QObject *parent = nullptr); + DataFormModel(const QXmppDataForm &dataForm, QObject *parent = nullptr); + ~DataFormModel(); - /** - * Handles incoming presences and checks if the avatar needs to be refreshed - */ - void handlePresence(const QXmppPresence &presence); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + QHash roleNames() const override; + + QXmppDataForm form() const; + void setForm(const QXmppDataForm &form); + + QString title() const; + QString instructions() const; signals: - void vCardReceived(const QXmppVCardIq &vCard); + void formChanged(); + +protected: + QXmppDataForm m_form; private: - QXmppClient *client; - QXmppVCardManager *manager; - AvatarFileStorage *avatarStorage; + QString mediaSourceUri(const QXmppDataForm::Field &field) const; }; -#endif // VCARDMANAGER_H +#endif // DATAFORMMODEL_H diff --git a/src/Globals.h b/src/Globals.h index c4b4838..600e36a 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -1,60 +1,106 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef GLOBALS_H #define GLOBALS_H +#include + // Kaidan settings #define KAIDAN_SETTINGS_AUTH_JID "auth/jid" #define KAIDAN_SETTINGS_AUTH_JID_RESOURCE_PREFIX "auth/jidResourcePrefix" #define KAIDAN_SETTINGS_AUTH_PASSWD "auth/password" #define KAIDAN_JID_RESOURCE_DEFAULT_PREFIX APPLICATION_DISPLAY_NAME #define INVITATION_URL "https://i.kaidan.im/#" #define APPLICATION_SOURCE_CODE_URL "https://invent.kde.org/kde/kaidan" #define ISSUE_TRACKING_URL "https://invent.kde.org/kde/kaidan/issues" #define MESSAGE_MAX_CHARS 1e4 // XML namespaces #define NS_SPOILERS "urn:xmpp:spoiler:0" #define NS_CARBONS "urn:xmpp:carbons:2" #define NS_REGISTER "jabber:iq:register" // SQL #define DB_CONNECTION "kaidan-messages" #define DB_FILENAME "messages.sqlite3" #define DB_MSG_QUERY_LIMIT 20 #define DB_TABLE_INFO "dbinfo" #define DB_TABLE_ROSTER "Roster" #define DB_TABLE_MESSAGES "Messages" +// +// Credential generation +// + +// Length of generated usernames +#define GENERATED_USERNAME_LENGTH 6 + +// Lower bound of the length for generated passwords (inclusive) +#define GENERATED_PASSWORD_LENGTH_LOWER_BOUND 20 + +// Upper bound of the length for generated passwords (inclusive) +#define GENERATED_PASSWORD_LENGTH_UPPER_BOUND 30 + +// Characters used for password generation +#define GENERATED_PASSWORD_ALPHABET QLatin1String( \ + "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "`1234567890-=" \ + "~!@#$%^&*()_+" \ + "[];'\\,./{}:\"|<>?" \ +) + +// Number of characters used for password generation +#define GENERATED_PASSWORD_ALPHABET_LENGTH GENERATED_PASSWORD_ALPHABET.size() + +// QXmpp version compatibility +#define QXMPP_REQUIRED_VERSION_FOR_REGISTRATION QT_VERSION_CHECK(1, 2, 0) + +/** + * Path of the JSON server list file + */ +#define SERVER_LIST_FILE_PATH QStringLiteral(":/data/servers.json") + +/** + * Number of servers required in a country so that only servers from that country are + * randomly selected. + */ +#define SERVER_LIST_MIN_SERVERS_FROM_COUNTRY 2 + +/** + * Name of the @c QQuickImageProvider for Bits of Binary. + */ +#define BITS_OF_BINARY_IMAGE_PROVIDER_NAME "bits-of-binary" + #endif // GLOBALS_H diff --git a/src/Kaidan.cpp b/src/Kaidan.cpp index f6eb26a..4a5f557 100644 --- a/src/Kaidan.cpp +++ b/src/Kaidan.cpp @@ -1,294 +1,286 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #include "Kaidan.h" // Qt #include #include #include #include // QXmpp #include #include "qxmpp-exts/QXmppColorGenerator.h" #include "qxmpp-exts/QXmppUri.h" // Kaidan #include "AvatarFileStorage.h" +#include "CredentialsValidator.h" #include "Database.h" #include "MessageDb.h" #include "MessageModel.h" #include "PresenceCache.h" #include "QmlUtils.h" #include "RosterDb.h" #include "RosterModel.h" Kaidan *Kaidan::s_instance; Kaidan::Kaidan(QGuiApplication *app, bool enableLogging, QObject *parent) : QObject(parent), m_database(new Database()), m_dbThrd(new QThread()), m_msgDb(new MessageDb()), m_rosterDb(new RosterDb(m_database)), m_cltThrd(new QThread()) { Q_ASSERT(!s_instance); s_instance = this; // Database setup m_database->moveToThread(m_dbThrd); m_msgDb->moveToThread(m_dbThrd); m_rosterDb->moveToThread(m_dbThrd); connect(m_dbThrd, &QThread::started, m_database, &Database::openDatabase); m_dbThrd->setObjectName("SqlDatabase"); m_dbThrd->start(); // Caching components m_caches = new ClientWorker::Caches(this, m_rosterDb, m_msgDb, this); // Connect the avatar changed signal of the avatarStorage with the NOTIFY signal // of the Q_PROPERTY for the avatar storage (so all avatars are updated in QML) connect(m_caches->avatarStorage, &AvatarFileStorage::avatarIdsChanged, this, &Kaidan::avatarStorageChanged); // // Load settings // setJid(m_caches->settings->value(KAIDAN_SETTINGS_AUTH_JID).toString()); setJidResourcePrefix(m_caches->settings->value(KAIDAN_SETTINGS_AUTH_JID_RESOURCE_PREFIX).toString()); setPassword(QByteArray::fromBase64( m_caches->settings->value(KAIDAN_SETTINGS_AUTH_PASSWD).toString().toUtf8() )); // Use a default prefix for the JID's resource part if no prefix is already set. if (creds.jidResourcePrefix.isEmpty()) setJidResourcePrefix(KAIDAN_JID_RESOURCE_DEFAULT_PREFIX); creds.isFirstTry = false; // // Start ClientWorker on new thread // m_client = new ClientWorker(m_caches, this, enableLogging, app); m_client->setCredentials(creds); m_client->moveToThread(m_cltThrd); connect(m_client, &ClientWorker::connectionErrorChanged, this, &Kaidan::setConnectionError); connect(m_cltThrd, &QThread::started, m_client, &ClientWorker::main); m_client->setObjectName("XmppClient"); m_cltThrd->start(); - - // account deletion - connect(this, &Kaidan::deleteAccountFromClient, m_client, &ClientWorker::deleteAccountFromClient); - connect(this, &Kaidan::deleteAccountFromClientAndServer, m_client, &ClientWorker::deleteAccountFromClientAndServer); } Kaidan::~Kaidan() { delete m_caches; delete m_database; s_instance = nullptr; } void Kaidan::start() { if (creds.jid.isEmpty() || creds.password.isEmpty()) emit newCredentialsNeeded(); else mainConnect(); } void Kaidan::mainConnect() { emit m_client->credentialsUpdated(creds); emit m_client->connectRequested(); } +void Kaidan::requestRegistrationForm() +{ + emit m_client->credentialsUpdated(creds); + emit m_client->registrationFormRequested(); +} + void Kaidan::mainDisconnect() { // disconnect the client if connected or connecting if (connectionState != ConnectionState::StateDisconnected) emit m_client->disconnectRequested(); } void Kaidan::setConnectionState(QXmppClient::State state) { if (this->connectionState != static_cast(state)) { this->connectionState = static_cast(state); emit connectionStateChanged(); // Open the possibly cached URI when connected. // This is needed because the XMPP URIs can't be opened when Kaidan is not connected. if (connectionState == ConnectionState::StateConnected && !openUriCache.isEmpty()) { // delay is needed because sometimes the RosterPage needs to be loaded first QTimer::singleShot(300, [=] () { emit xmppUriReceived(openUriCache); openUriCache = ""; }); } } } void Kaidan::setConnectionError(ClientWorker::ConnectionError error) { connectionError = error; - emit connectionErrorChanged(); + emit connectionErrorChanged(error); } void Kaidan::deleteCredentials() { // Delete the JID. m_caches->settings->remove(KAIDAN_SETTINGS_AUTH_JID); setJid(QString()); // Delete the password. m_caches->settings->remove(KAIDAN_SETTINGS_AUTH_PASSWD); setPassword(QString()); - // Trigger the opening of the login page. emit newCredentialsNeeded(); } bool Kaidan::notificationsMuted(const QString &jid) { return m_caches->settings->value(QString("muted/") + jid, false).toBool(); } void Kaidan::setNotificationsMuted(const QString &jid, bool muted) { m_caches->settings->setValue(QString("muted/") + jid, muted); emit notificationsMutedChanged(jid); } void Kaidan::setJid(const QString &jid) { creds.jid = jid; // credentials were modified -> first try creds.isFirstTry = true; emit jidChanged(); } void Kaidan::setJidResourcePrefix(const QString &jidResourcePrefix) { // JID resource won't influence the authentication, so we don't need // to set the first try flag and can save it. creds.jidResourcePrefix = jidResourcePrefix; m_caches->settings->setValue(KAIDAN_SETTINGS_AUTH_JID_RESOURCE_PREFIX, jidResourcePrefix); emit jidResourcePrefixChanged(); } void Kaidan::setPassword(const QString &password) { creds.password = password; // credentials were modified -> first try creds.isFirstTry = true; emit passwordChanged(); } quint8 Kaidan::getConnectionError() const { return static_cast(connectionError); } void Kaidan::addOpenUri(const QString &uri) { if (!QXmppUri::isXmppUri(uri)) return; if (connectionState == ConnectionState::StateConnected) { emit xmppUriReceived(uri); } else { //: The link is an XMPP-URI (i.e. 'xmpp:kaidan@muc.kaidan.im?join' for joining a chat) emit passiveNotificationRequested(tr("The link will be opened after you have connected.")); openUriCache = uri; } } -void Kaidan::loginByUri(const QString &uri) +bool Kaidan::logInByUri(const QString &uri) { - // input does not start with 'xmpp:' if (!QXmppUri::isXmppUri(uri)) { - notifyLoginUriNotFound(); - return; + notifyForInvalidLoginUri(); + return false; } - // parse QXmppUri parsedUri(uri); - // no JID provided - if (parsedUri.jid().isEmpty()) { - notifyLoginUriNotFound(); - return; + if (!CredentialsValidator::isAccountJidValid(parsedUri.jid()) || !parsedUri.hasAction(QXmppUri::Action::Login) || !CredentialsValidator::isPasswordValid(parsedUri.password())) { + notifyForInvalidLoginUri(); + return false; } setJid(parsedUri.jid()); - - // URI has no login action or no password - if (!parsedUri.hasAction(QXmppUri::Action::Login) || parsedUri.password().isEmpty()) { - // reset password - setPassword(QString()); - emit passiveNotificationRequested(tr("No password found. Please enter it.")); - return; - } - setPassword(parsedUri.password()); - // try to connect + // Connect with the extracted credentials. mainConnect(); + + return true; } -void Kaidan::notifyLoginUriNotFound() +void Kaidan::notifyForInvalidLoginUri() { - qWarning() << "[main]" << "No valid login URI found."; - emit passiveNotificationRequested(tr("No valid login QR code found.")); + qWarning() << "[main]" << "No valid login URI found."; + emit passiveNotificationRequested(tr("No valid login QR code found.")); } ClientWorker *Kaidan::getClient() const { return m_client; } RosterDb *Kaidan::rosterDb() const { return m_rosterDb; } MessageDb *Kaidan::messageDb() const { return m_msgDb; } Kaidan *Kaidan::instance() { return s_instance; } diff --git a/src/Kaidan.h b/src/Kaidan.h index 62ad1de..6d5a744 100644 --- a/src/Kaidan.h +++ b/src/Kaidan.h @@ -1,430 +1,451 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef KAIDAN_H #define KAIDAN_H // Qt #include #include #include // Kaidan #include "ClientWorker.h" +#include "RegistrationDataFormModel.h" #include "Globals.h" class QGuiApplication; class Database; class QXmppClient; class QXmppStanza; /** * @class Kaidan Kaidan's Back-End Class * * @brief This class will initiate the complete back-end, including the @see Database * connection, viewing models (@see MessageModel, @see RosterModel), etc. * * This class will run in the main thread, the XMPP connection and the database managers * run in other threads. */ class Kaidan : public QObject { Q_OBJECT Q_PROPERTY(RosterModel* rosterModel READ getRosterModel CONSTANT) Q_PROPERTY(MessageModel* messageModel READ getMessageModel CONSTANT) Q_PROPERTY(AvatarFileStorage* avatarStorage READ getAvatarStorage NOTIFY avatarStorageChanged) Q_PROPERTY(PresenceCache* presenceCache READ getPresenceCache CONSTANT) Q_PROPERTY(TransferCache* transferCache READ getTransferCache CONSTANT) Q_PROPERTY(ServerFeaturesCache* serverFeaturesCache READ serverFeaturesCache CONSTANT) Q_PROPERTY(QSettings* settings READ getSettings CONSTANT) Q_PROPERTY(quint8 connectionState READ getConnectionState NOTIFY connectionStateChanged) Q_PROPERTY(quint8 connectionError READ getConnectionError NOTIFY connectionErrorChanged) Q_PROPERTY(QString jid READ getJid WRITE setJid NOTIFY jidChanged) Q_PROPERTY(QString jidResourcePrefix READ getJidResourcePrefix WRITE setJidResourcePrefix NOTIFY jidResourcePrefixChanged) Q_PROPERTY(QString password READ getPassword WRITE setPassword NOTIFY passwordChanged) public: static Kaidan *instance(); Kaidan(QGuiApplication *app, bool enableLogging = true, QObject *parent = nullptr); ~Kaidan(); /** * Starts connecting (called from QML when ready). */ Q_INVOKABLE void start(); /** * Connects to the XMPP server. * * The username and password are retrieved from the the settings file. */ Q_INVOKABLE void mainConnect(); + /** + * Connects to the server and requests a data form for account registration. + */ + Q_INVOKABLE void requestRegistrationForm(); + /** * Disconnects from the XMPP server. * - * When the client is disconnected from the server, the "connectionStateChanged" signal will be emitted. + * This disconnects the client from the server. + * When disconnected, the connectionStateChanged signal is emitted. */ Q_INVOKABLE void mainDisconnect(); /** * Returns the current ConnectionState */ Q_INVOKABLE quint8 getConnectionState() const { return (quint8) connectionState; } /** * Returns the last connection error. */ Q_INVOKABLE quint8 getConnectionError() const; /** * Set own JID used for connection * * To really change the JID of the current connection, you'll need to * reconnect. */ void setJid(const QString &jid); /** * Get the current JID */ QString getJid() const { return creds.jid; } /** * Sets the prefix of the JID's resource part. * * The remaining part of the resource is set randomly. */ void setJidResourcePrefix(const QString &jidResourcePrefix); /** * Provides the prefix of the JID's resource part. */ QString getJidResourcePrefix() const { return creds.jidResourcePrefix; } /** * Set the password for next connection */ void setPassword(const QString &password); /** * Get the currently used password */ QString getPassword() const { return creds.password; } RosterModel* getRosterModel() const { return m_caches->rosterModel; } MessageModel* getMessageModel() const { return m_caches->msgModel; } AvatarFileStorage* getAvatarStorage() const { return m_caches->avatarStorage; } PresenceCache* getPresenceCache() const { return m_caches->presCache; } TransferCache* getTransferCache() const { return m_caches->transferCache; } ServerFeaturesCache *serverFeaturesCache() const { return m_caches->serverFeaturesCache; } QSettings* getSettings() const { return m_caches->settings; } ClientWorker *getClient() const; RosterDb *rosterDb() const; MessageDb *messageDb() const; /** * Adds XMPP URI to open as soon as possible */ void addOpenUri(const QString &uri); - /** - * Connects to the server by the parsed credentials (bare JID and password) - * from a given XMPP URI (e.g. from scanning a QR code) - * like "xmpp:user@example.org?login;password=abc" - */ - Q_INVOKABLE void loginByUri(const QString &uri); + /** + * Connects to the server by the parsed credentials (bare JID and password) from a given XMPP URI (e.g. from scanning a QR code) like "xmpp:user@example.org?login;password=abc" + * + * @return true if the login worked + */ + Q_INVOKABLE bool logInByUri(const QString &uri); signals: void avatarStorageChanged(); /** * Emitted, when the client's connection state has changed (e.g. when * successfully connected or when disconnected) */ void connectionStateChanged(); /** * Emitted when the client failed to connect. */ - void connectionErrorChanged(); + void connectionErrorChanged(ClientWorker::ConnectionError error); /** * Emitted when the JID was changed */ void jidChanged(); /** * Emitted when the prefix of the JID's resource part changed. */ void jidResourcePrefixChanged(); /** * Emitted when the used password for logging in has changed */ void passwordChanged(); /** * Emitted when there are no (correct) credentials and new are needed * * The client will be in disconnected state, when this is emitted. */ void newCredentialsNeeded(); /** * Emitted when an authenticated connection to the server is established with new credentials for the first time. * * The client will be in connected state, when this is emitted. */ void loggedInWithNewCredentials(); /** * Show passive notification */ void passiveNotificationRequested(QString text); /** * Emitted, whan a subscription request was received */ void subscriptionRequestReceived(QString from, QString msg); /** * Incoming subscription request was accepted or declined by the user */ void subscriptionRequestAnswered(QString jid, bool accepted); /** * Request vCard of any JID * * Is required when the avatar (or other information) of a JID are * requested and the JID is not in the roster. */ void vCardRequested(const QString &jid); /** * XMPP URI received * * Is called when Kaidan was used to open an XMPP URI (i.e. 'xmpp:kaidan@muc.kaidan.im?join') */ void xmppUriReceived(QString uri); /** * The upload progress of a file upload has changed */ void uploadProgressMade(QString msgId, quint64 sent, quint64 total); /** * Send a text message to any JID * * Currently only contacts are displayed on the RosterPage (there is no * way to view a list of all chats -> for contacts and non-contacts), so * you should only send messages to JIDs from your roster, otherwise you * won't be able to see the message history. */ void sendMessage(QString jid, QString message, bool isSpoiler, QString spoilerHint); /** * Correct the last message * * To get/check the last message id, use `kaidan.messageModel.lastMessageId(jid)` */ void correctMessage(QString toJid, QString msgId, QString message); /** * Upload and send file */ void sendFile(const QString &jid, const QUrl &fileUrl, const QString &body); /** * Add a contact to your roster * * @param nick A simple nick name for the new contact, which should be * used to display in the roster. */ void addContact(QString jid, QString nick, QString msg); /** * Remove a contact from your roster * * Only the JID is needed. */ void removeContact(QString jid); /** * Change a contact's name */ void renameContact(const QString &jid, const QString &newContactName); /** * Downloads an attached media file of a message * * @param msgId The message * @param url the media url from the message */ void downloadMedia(QString msgId, QString url); + /** + * Changes the user's display name. + * + * @param displayName new name that is shown to contacts + */ + void changeDisplayName(const QString &displayName); + /** * Changes the user's password on the server * * @param newPassword The new password */ void changePassword(const QString &newPassword); /** * Emitted, when changing the password has succeeded. */ void passwordChangeSucceeded(); /** * Emitted, when changing the password has failed. */ void passwordChangeFailed(); /** * Emitted, when a contact was muted/unmuted. */ void notificationsMutedChanged(const QString& jid); /** * Deletes the account data from the client and server. */ void deleteAccountFromClientAndServer(); /** * Deletes the account data from the configuration file and database. */ void deleteAccountFromClient(); + void registrationFormReceived(DataFormModel *dataFormModel); + + void sendRegistrationForm(); + + void registrationSucceeded(); + void registrationFailed(quint8 error, const QString &errrorMessage); + public slots: /** * Set current connection state */ void setConnectionState(QXmppClient::State state); /** * Sets a new connection error. */ void setConnectionError(ClientWorker::ConnectionError error); /** * Deletes the username and password from the settings file. */ void deleteCredentials(); /** * Receives messages from another instance of the application */ void receiveMessage(quint32, const QByteArray &msg) { // currently we only send XMPP URIs addOpenUri(msg); } /** * Returns whether notifications are enabled for the given contact. */ bool notificationsMuted(const QString& jid); /** * Sets the notifications to muted/unmuted. * * @param muted true if notifications should be muted. * @param jid contains the current chatpartner's jid. */ void setNotificationsMuted(const QString& jid, bool muted); private: void connectDatabases(); - /** - * Notifies if no login URI was found - */ - void notifyLoginUriNotFound(); + /** + * Notifies if no valid login URI was found. + */ + void notifyForInvalidLoginUri(); Database *m_database; QThread *m_dbThrd; MessageDb *m_msgDb; RosterDb *m_rosterDb; QThread *m_cltThrd; ClientWorker::Caches *m_caches; ClientWorker *m_client; ClientWorker::Credentials creds; QString openUriCache; ConnectionState connectionState = ConnectionState::StateDisconnected; ClientWorker::ConnectionError connectionError = ClientWorker::NoError; static Kaidan *s_instance; }; #endif diff --git a/src/QmlUtils.cpp b/src/QmlUtils.cpp index 508ce85..26de864 100644 --- a/src/QmlUtils.cpp +++ b/src/QmlUtils.cpp @@ -1,263 +1,265 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #include "QmlUtils.h" +// Kaidan +#include "Globals.h" // Qt #include #include #include #include #include #include #include #include #include #include // QXmpp #include "qxmpp-exts/QXmppColorGenerator.h" static QmlUtils *s_instance; QmlUtils *QmlUtils::instance() { if (!s_instance) return new QmlUtils(QGuiApplication::instance()); return s_instance; } QmlUtils::QmlUtils(QObject *parent) : QObject(parent) { Q_ASSERT(!s_instance); s_instance = this; } QmlUtils::~QmlUtils() { s_instance = nullptr; } QString QmlUtils::presenceTypeToIcon(Enums::AvailabilityTypes type) { switch (type) { case AvailabilityTypes::PresOnline: return "im-user-online"; case AvailabilityTypes::PresChat: return "im-user-online"; case AvailabilityTypes::PresAway: return "im-user-away"; case AvailabilityTypes::PresDND: return "im-kick-user"; case AvailabilityTypes::PresXA: return "im-user-away"; case AvailabilityTypes::PresUnavailable: return "im-user-offline"; case AvailabilityTypes::PresError: return "im-ban-kick-user"; case AvailabilityTypes::PresInvisible: return "im-invisible-user"; } Q_UNREACHABLE(); return { }; } QString QmlUtils::presenceTypeToText(AvailabilityTypes type) { switch (type) { case AvailabilityTypes::PresOnline: return tr("Available"); case AvailabilityTypes::PresChat: return tr("Free for chat"); case AvailabilityTypes::PresAway: return tr("Away"); case AvailabilityTypes::PresDND: return tr("Do not disturb"); case AvailabilityTypes::PresXA: return tr("Away for longer"); case AvailabilityTypes::PresUnavailable: return tr("Offline"); case AvailabilityTypes::PresError: return tr("Error"); case AvailabilityTypes::PresInvisible: return tr("Invisible"); } Q_UNREACHABLE(); return { }; } QColor QmlUtils::presenceTypeToColor(AvailabilityTypes type) { switch (type) { case AvailabilityTypes::PresOnline: return {"green"}; case AvailabilityTypes::PresChat: return {"darkgreen"}; case AvailabilityTypes::PresAway: return {"orange"}; case AvailabilityTypes::PresDND: return QColor::fromRgb(218, 68, 83); case AvailabilityTypes::PresXA: return {"orange"}; case AvailabilityTypes::PresError: return {"red"}; case AvailabilityTypes::PresUnavailable: return {"silver"}; case AvailabilityTypes::PresInvisible: return {"grey"}; } Q_UNREACHABLE(); return { }; } QString QmlUtils::connectionErrorMessage(ClientWorker::ConnectionError error) { switch (error) { case ClientWorker::NoError: Q_UNREACHABLE(); case ClientWorker::AuthenticationFailed: return tr("Invalid username or password."); case ClientWorker::NotConnected: return tr("Cannot connect to the server. Please check your internet connection."); case ClientWorker::TlsFailed: return tr("Error while trying to connect securely."); case ClientWorker::TlsNotAvailable: return tr("The server doesn't support secure connections."); case ClientWorker::DnsError: return tr("Could not resolve the server's address. Please check your server name."); case ClientWorker::ConnectionRefused: return tr("The server is offline or blocked by a firewall."); case ClientWorker::NoSupportedAuth: return tr("Authentification protocol not supported by the server."); case ClientWorker::KeepAliveError: return tr("The connection could not be refreshed."); case ClientWorker::NoNetworkPermission: return tr("The internet access is not permitted. Please check your system's internet access configuration."); case ClientWorker::RegistrationUnsupported: return tr("This server does not support registration."); } Q_UNREACHABLE(); } QString QmlUtils::getResourcePath(const QString &name) { // We generally prefer to first search for files in application resources if (QFile::exists(":/" + name)) return QString("qrc:/" + name); // list of file paths where to search for the resource file QStringList pathList; // add relative path from binary (only works if installed) pathList << QCoreApplication::applicationDirPath() + QString("/../share/") + QString(APPLICATION_NAME); // get the standard app data locations for current platform pathList << QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); #ifdef UBUNTU_TOUCH pathList << QString("./share/") + QString(APPLICATION_NAME); #endif #ifndef NDEBUG #ifdef DEBUG_SOURCE_PATH // add source directory (only for debug builds) pathList << QString(DEBUG_SOURCE_PATH) + QString("/data"); #endif #endif // search for file in directories for (int i = 0; i < pathList.size(); i++) { // open directory QDir directory(pathList.at(i)); // look up the file if (directory.exists(name)) { // found the file, return the path return QUrl::fromLocalFile(directory.absoluteFilePath(name)).toString(); } } // no file found qWarning() << "[main] Could NOT find media file:" << name; return QString(); } QUrl QmlUtils::issueTrackingUrl() { return {QStringLiteral(ISSUE_TRACKING_URL)}; } bool QmlUtils::isImageFile(const QUrl &fileUrl) { QMimeType type = QMimeDatabase().mimeTypeForUrl(fileUrl); return type.inherits("image/jpeg") || type.inherits("image/png"); } void QmlUtils::copyToClipboard(const QString &text) { QGuiApplication::clipboard()->setText(text); } QString QmlUtils::fileNameFromUrl(const QUrl &url) { return QUrl(url).fileName(); } QString QmlUtils::fileSizeFromUrl(const QUrl &url) { return QLocale::system().formattedDataSize( QFileInfo(QUrl(url).toLocalFile()).size()); } QString QmlUtils::formatMessage(const QString &message) { // escape all special XML chars (like '<' and '>') // and spilt into words for processing return processMsgFormatting(message.toHtmlEscaped().split(" ")); } QColor QmlUtils::getUserColor(const QString &nickName) { QXmppColorGenerator::RGBColor color = QXmppColorGenerator::generateColor(nickName); return {color.red, color.green, color.blue}; } QString QmlUtils::processMsgFormatting(const QStringList &list, bool isFirst) { if (list.isEmpty()) return QString(); // link highlighting if (list.first().startsWith("https://") || list.first().startsWith("http://")) return (isFirst ? QString() : " ") + QString("%1").arg(list.first()) + processMsgFormatting(list.mid(1), false); // preserve newlines if (list.first().contains("\n")) return (isFirst ? QString() : " ") + QString(list.first()).replace("\n", "
") + processMsgFormatting(list.mid(1), false); return (isFirst ? QString() : " ") + list.first() + processMsgFormatting(list.mid(1), false); } diff --git a/src/ServerFeaturesCache.cpp b/src/RegistrationDataFormFilterModel.cpp similarity index 69% copy from src/ServerFeaturesCache.cpp copy to src/RegistrationDataFormFilterModel.cpp index e00699b..df5d981 100644 --- a/src/ServerFeaturesCache.cpp +++ b/src/RegistrationDataFormFilterModel.cpp @@ -1,54 +1,53 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -#include "ServerFeaturesCache.h" -// Qt -#include +#include "RegistrationDataFormFilterModel.h" +#include "RegistrationDataFormModel.h" -ServerFeaturesCache::ServerFeaturesCache(QObject *parent) - : QObject(parent) +RegistrationDataFormFilterModel::RegistrationDataFormFilterModel(QObject* parent) + : QSortFilterProxyModel(parent) { } -bool ServerFeaturesCache::httpUploadSupported() +bool RegistrationDataFormFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const { - QMutexLocker locker(&m_mutex); - return m_httpUploadSupported; + return !m_filteredRows.contains(sourceRow); } -void ServerFeaturesCache::setHttpUploadSupported(bool supported) +void RegistrationDataFormFilterModel::setSourceModel(QAbstractItemModel *sourceModel) { - QMutexLocker locker(&m_mutex); - if (m_httpUploadSupported != supported) { - m_httpUploadSupported = supported; - locker.unlock(); - emit httpUploadSupportedChanged(); - } + m_filteredRows.clear(); + + auto *dataFormModel = qobject_cast(sourceModel); + if (dataFormModel) + m_filteredRows = dataFormModel->indiciesToFilter(); + + QSortFilterProxyModel::setSourceModel(sourceModel); } diff --git a/src/qml/AccountDeletionFromClientConfirmationPage.qml b/src/RegistrationDataFormFilterModel.h similarity index 72% copy from src/qml/AccountDeletionFromClientConfirmationPage.qml copy to src/RegistrationDataFormFilterModel.h index 6f78f75..f30e5bf 100644 --- a/src/qml/AccountDeletionFromClientConfirmationPage.qml +++ b/src/RegistrationDataFormFilterModel.h @@ -1,52 +1,54 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 -import org.kde.kirigami 2.8 as Kirigami +#ifndef REGISTRATIONDATAFORMFILTERMODEL_H +#define REGISTRATIONDATAFORMFILTERMODEL_H -import im.kaidan.kaidan 1.0 - -import "elements" +#include /** - * This page is used for confirming the deletion of an account from the client. + * This class is used to filter the data of the registration form. */ -ConfirmationPage { - title: qsTr("Remove account from this app") - - topDescription: qsTr("Your account will be removed from this app.\nYou won't be able to get your credentials back!\nMake sure that you have backed up those if you want to use your account later.") - - topAction: Kirigami.Action { - text: qsTr("Remove") - onTriggered: { - Kaidan.deleteAccountFromClient() - } - } -} +class RegistrationDataFormFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + RegistrationDataFormFilterModel(QObject *parent = nullptr); + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + void setSourceModel(QAbstractItemModel *sourceModel) override; + +private: + QVector m_filteredRows; +}; + +#endif // REGISTRATIONDATAFORMFILTERMODEL_H diff --git a/src/RegistrationDataFormModel.cpp b/src/RegistrationDataFormModel.cpp new file mode 100644 index 0000000..48904f6 --- /dev/null +++ b/src/RegistrationDataFormModel.cpp @@ -0,0 +1,204 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +#include "RegistrationDataFormModel.h" +// Qt +#include +#include +// QXmpp +#include + +#define USERNAME "username" +#define PASSWORD "password" +#define EMAIL "email" + +// fields to be filtered out +#define FORM_TYPE "FORM_TYPE" +#define FROM "from" +#define CAPTCHA_FALLBACK_TEXT "captcha-fallback-text" +#define CAPTCHA_FALLBACK_URL "captcha-fallback-url" +#define CAPTCHAHIDDEN "captchahidden" +#define CHALLENGE "challenge" +#define SID "sid" + +RegistrationDataFormModel::RegistrationDataFormModel(QObject *parent) + : DataFormModel(parent) +{ + initializeFilteredDataFormFields(); +} + +RegistrationDataFormModel::RegistrationDataFormModel(const QXmppDataForm &dataForm, QObject *parent) + : DataFormModel(dataForm, parent) +{ + initializeFilteredDataFormFields(); +} + +bool RegistrationDataFormModel::hasUsernameField() const +{ + return usernameFieldIndex() != -1; +} + +bool RegistrationDataFormModel::hasPasswordField() const +{ + return passwordFieldIndex() != -1; +} + +bool RegistrationDataFormModel::hasEmailField() const +{ + return emailFieldIndex() != -1; +} + +void RegistrationDataFormModel::setUsername(const QString &username) +{ + setData(index(usernameFieldIndex()), username, DataFormModel::Value); +} + +void RegistrationDataFormModel::setPassword(const QString &password) +{ + setData(index(passwordFieldIndex()), password, DataFormModel::Value); +} + +void RegistrationDataFormModel::setEmail(const QString& email) +{ + setData(index(emailFieldIndex()), email, DataFormModel::Value); +} + +int RegistrationDataFormModel::usernameFieldIndex() const +{ + const QList &fields = m_form.fields(); + for (int i = 0; i < fields.size(); i++) { + if (fields.at(i).type() == QXmppDataForm::Field::TextSingleField && + fields.at(i).key().compare(USERNAME, Qt::CaseInsensitive) == 0) { + return i; + } + } + return -1; +} + +int RegistrationDataFormModel::passwordFieldIndex() const +{ + const QList &fields = m_form.fields(); + for (int i = 0; i < fields.size(); i++) { + if (fields.at(i).type() == QXmppDataForm::Field::TextPrivateField && + fields.at(i).key().compare(PASSWORD, Qt::CaseInsensitive) == 0) { + return i; + } + } + return -1; +} + +int RegistrationDataFormModel::emailFieldIndex() const +{ + const QList &fields = m_form.fields(); + for (int i = 0; i < fields.size(); i++) { + if (fields.at(i).type() == QXmppDataForm::Field::TextSingleField && + fields.at(i).key().compare(EMAIL, Qt::CaseInsensitive) == 0) { + return i; + } + } + return -1; +} + +QXmppDataForm::Field RegistrationDataFormModel::extractUsernameField() const +{ + int index = usernameFieldIndex(); + return index >= 0 ? m_form.fields().at(index) : QXmppDataForm::Field(); +} + +QXmppDataForm::Field RegistrationDataFormModel::extractPasswordField() const +{ + int index = passwordFieldIndex(); + return index >= 0 ? m_form.fields().at(index) : QXmppDataForm::Field(); +} + +QXmppDataForm::Field RegistrationDataFormModel::extractEmailField() const +{ + int index = emailFieldIndex(); + return index >= 0 ? m_form.fields().at(index) : QXmppDataForm::Field(); +} + +QString RegistrationDataFormModel::extractUsername() const +{ + return extractUsernameField().value().toString(); +} + +QString RegistrationDataFormModel::extractPassword() const +{ + return extractPasswordField().value().toString(); +} + +QString RegistrationDataFormModel::extractEmail() const +{ + return extractEmailField().value().toString(); +} + +bool RegistrationDataFormModel::isFakeForm() const +{ + return m_isFakeForm; +} + +void RegistrationDataFormModel::setIsFakeForm(bool isFakeForm) +{ + m_isFakeForm = isFakeForm; +} + +QVector RegistrationDataFormModel::indiciesToFilter() const +{ + QVector indicies; + + // username and password + // email is currently not filtered because we do not have an extra email view + for (const auto &index : {usernameFieldIndex(), passwordFieldIndex()}) { + if (index != -1) + indicies << index; + } + + // search for other common fields to filter for + for (int i = 0; i < m_form.fields().size(); i++) { + QString key = m_form.fields().at(i).key(); + if (filteredDataFormFields.contains(key)) + indicies << i; + } + + return indicies; +} + +void RegistrationDataFormModel::initializeFilteredDataFormFields() +{ + filteredDataFormFields = { + FORM_TYPE, + FROM, + CAPTCHA_FALLBACK_TEXT, + CAPTCHA_FALLBACK_URL, + CAPTCHAHIDDEN, + CHALLENGE, + SID + }; +} diff --git a/src/ServerFeaturesCache.h b/src/RegistrationDataFormModel.h similarity index 53% copy from src/ServerFeaturesCache.h copy to src/RegistrationDataFormModel.h index 5359fd0..4218090 100644 --- a/src/ServerFeaturesCache.h +++ b/src/RegistrationDataFormModel.h @@ -1,65 +1,84 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -#ifndef SERVERFEATURESCACHE_H -#define SERVERFEATURESCACHE_H +#ifndef REGISTRATIONDATAFORMMODEL_H +#define REGISTRATIONDATAFORMMODEL_H -#include -#include +#include "DataFormModel.h" +// Qt +#include +class QString; /** - * @brief The ServerFeaturesCache class temporarily stores the features of a server. This - * can be used to for example enable or disable certain features in the UI. - * - * All methods in the class are thread-safe. + * This class is used to store the data of the registration form. */ -class ServerFeaturesCache : public QObject +class RegistrationDataFormModel : public DataFormModel { Q_OBJECT - Q_PROPERTY(bool httpUploadSupported READ httpUploadSupported NOTIFY httpUploadSupportedChanged) public: - explicit ServerFeaturesCache(QObject *parent = nullptr); + using DataFormModel::DataFormModel; + + RegistrationDataFormModel(QObject *parent = nullptr); + RegistrationDataFormModel(const QXmppDataForm &dataForm, QObject *parent = nullptr); + + Q_INVOKABLE bool hasUsernameField() const; + Q_INVOKABLE bool hasPasswordField() const; + Q_INVOKABLE bool hasEmailField() const; + + Q_INVOKABLE void setUsername(const QString &username); + Q_INVOKABLE void setPassword(const QString &password); + Q_INVOKABLE void setEmail(const QString &email); - /** - * Returns whether HTTP File Upload is available and can be currently be used. - */ - bool httpUploadSupported(); - void setHttpUploadSupported(bool supported); + int usernameFieldIndex() const; + int passwordFieldIndex() const; + int emailFieldIndex() const; -signals: - void httpUploadSupportedChanged(); + QXmppDataForm::Field extractUsernameField() const; + QXmppDataForm::Field extractPasswordField() const; + QXmppDataForm::Field extractEmailField() const; + + QString extractUsername() const; + QString extractPassword() const; + QString extractEmail() const; + + bool isFakeForm() const; + void setIsFakeForm(bool isFakeForm); + + QVector indiciesToFilter() const; private: - QMutex m_mutex; - bool m_httpUploadSupported = false; + void initializeFilteredDataFormFields(); + + bool m_isFakeForm = false; + QList filteredDataFormFields; }; -#endif // SERVERFEATURESCACHE_H +#endif // REGISTRATIONDATAFORMMODEL_H diff --git a/src/RegistrationManager.cpp b/src/RegistrationManager.cpp index 194e0d8..3014e0f 100644 --- a/src/RegistrationManager.cpp +++ b/src/RegistrationManager.cpp @@ -1,162 +1,280 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -#include "ClientWorker.h" #include "RegistrationManager.h" +// Kaidan +#include "BitsOfBinaryImageProvider.h" +#include "ClientWorker.h" #include "Globals.h" #include "Kaidan.h" - -#include +#include "RegistrationDataFormModel.h" +// C++ +#include +// Qt +#include +#include +#include +#include +#include +// QXmpp +#include #include -#include -#include -#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 2, 0) +#include #include -#endif - -#include -#include RegistrationManager::RegistrationManager(Kaidan *kaidan, ClientWorker *clientWorker, QXmppClient *client, QSettings *settings) - : kaidan(kaidan), clientWorker(clientWorker), settings(settings), m_client(client) -#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 2, 0) - , m_manager(new QXmppRegistrationManager) -#endif + : QObject(clientWorker), kaidan(kaidan), m_clientWorker(clientWorker), m_client(client), settings(settings), m_manager(new QXmppRegistrationManager), m_dataFormModel(new RegistrationDataFormModel()) { -#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 2, 0) - m_client->addExtension(m_manager); + client->addExtension(m_manager); - connect(m_manager, &QXmppRegistrationManager::accountDeletionFailed, clientWorker, &ClientWorker::onAccountDeletionFromServerFailed); - connect(m_manager, &QXmppRegistrationManager::accountDeleted, clientWorker, &ClientWorker::onAccountDeletedFromServer); -#endif - connect(kaidan, &Kaidan::changePassword, this, &RegistrationManager::changePassword); - connect(this, &RegistrationManager::passwordChanged, kaidan, &Kaidan::setPassword); - connect(this, &RegistrationManager::passwordChanged, kaidan, &Kaidan::passwordChangeSucceeded); - connect(this, &RegistrationManager::passwordChangeFailed, kaidan, &Kaidan::passwordChangeFailed); + connect(m_manager, &QXmppRegistrationManager::supportedByServerChanged, this, &RegistrationManager::handleInBandRegistrationSupportedChanged); + + connect(kaidan, &Kaidan::sendRegistrationForm, this, &RegistrationManager::sendRegistrationForm); + + connect(m_manager, &QXmppRegistrationManager::registrationFormReceived, this, &RegistrationManager::handleRegistrationFormReceived); + connect(m_manager, &QXmppRegistrationManager::registrationSucceeded, this, &RegistrationManager::handleRegistrationSucceeded); + connect(m_manager, &QXmppRegistrationManager::registrationSucceeded, kaidan, &Kaidan::registrationSucceeded); + connect(m_manager, &QXmppRegistrationManager::registrationFailed, this, &RegistrationManager::handleRegistrationFailed); + + connect(m_manager, &QXmppRegistrationManager::accountDeletionFailed, m_clientWorker, &ClientWorker::handleAccountDeletionFromServerFailed); + connect(m_manager, &QXmppRegistrationManager::accountDeleted, m_clientWorker, &ClientWorker::handleAccountDeletedFromServer); + + connect(m_manager, &QXmppRegistrationManager::passwordChanged, kaidan, &Kaidan::setPassword); + + connect(m_manager, &QXmppRegistrationManager::passwordChanged, kaidan, &Kaidan::passwordChangeSucceeded); + connect(m_manager, &QXmppRegistrationManager::passwordChanged, this, &RegistrationManager::handlePasswordChangeSucceeded); + + connect(m_manager, &QXmppRegistrationManager::passwordChangeFailed, kaidan, &Kaidan::passwordChangeFailed); + connect(m_manager, &QXmppRegistrationManager::passwordChangeFailed, this, &RegistrationManager::handlePasswordChangeFailed); } -QStringList RegistrationManager::discoveryFeatures() const +void RegistrationManager::setRegisterOnConnectEnabled(bool registerOnConnect) { - return QStringList() << NS_REGISTER; + m_manager->setRegisterOnConnectEnabled(registerOnConnect); +} + +void RegistrationManager::sendRegistrationForm() +{ + if (m_dataFormModel->isFakeForm()) { + QXmppRegisterIq iq; + iq.setUsername(m_dataFormModel->extractUsername()); + iq.setPassword(m_dataFormModel->extractPassword()); + iq.setEmail(m_dataFormModel->extractEmail()); + + m_manager->setRegistrationFormToSend(iq); + } else { + m_manager->setRegistrationFormToSend(m_dataFormModel->form()); + } + + m_clientWorker->connectToRegister(); +} + +void RegistrationManager::deleteAccount() +{ + m_manager->deleteAccount(); } void RegistrationManager::changePassword(const QString &newPassword) { - m_newPasswordIqId = QXmppUtils::generateStanzaHash(); - m_newPassword = newPassword; - - QXmppRegisterIq iq; - iq.setType(QXmppIq::Set); - iq.setTo(client()->configuration().domain()); - iq.setFrom(client()->configuration().jid()); - iq.setUsername(QXmppUtils::jidToUser(client()->configuration().jid())); - iq.setPassword(newPassword); - iq.setId(m_newPasswordIqId); - - client()->sendPacket(iq); + m_manager->changePassword(newPassword); } -bool RegistrationManager::registrationSupported() const +void RegistrationManager::handleInBandRegistrationSupportedChanged() { - return m_registrationSupported; + if (m_client->isConnected()) { + m_clientWorker->caches()->serverFeaturesCache->setInBandRegistrationSupported(m_manager->supportedByServer()); + } } -void RegistrationManager::deleteAccount() +void RegistrationManager::handlePasswordChangeSucceeded(const QString &newPassword) { -#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 2, 0) - m_manager->deleteAccount(); -#else - emit kaidan->passiveNotificationRequested("Account deletion is not supported. If an update doesn't help, contact your distribution maintainers: QXmpp version >= 1.2 is required."); -#endif + settings->setValue( + KAIDAN_SETTINGS_AUTH_PASSWD, + QString::fromUtf8(newPassword.toUtf8().toBase64()) + ); + + emit kaidan->passiveNotificationRequested( + tr("Password changed successfully.") + ); +} + +void RegistrationManager::handlePasswordChangeFailed(const QXmppStanza::Error &error) +{ + emit kaidan->passiveNotificationRequested( + tr("Failed to change password: %1").arg(error.text()) + ); } -void RegistrationManager::setClient(QXmppClient *client) +void RegistrationManager::handleRegistrationFormReceived(const QXmppRegisterIq &iq) { - QXmppClientExtension::setClient(client); - // get service discovery manager - auto *disco = client->findExtension(); - if (disco) { - connect(disco, &QXmppDiscoveryManager::infoReceived, - this, &RegistrationManager::handleDiscoInfo); - - connect(client, &QXmppClient::disconnected, this, [=] () { - setRegistrationSupported(false); - }); + m_client->disconnectFromServer(); + + bool isFakeForm; + QXmppDataForm newDataForm = extractFormFromRegisterIq(iq, isFakeForm); + + // If the data form is not set, there is a problem with the server. + if (newDataForm.fields().isEmpty()) { + emit m_clientWorker->connectionErrorChanged(ClientWorker::RegistrationUnsupported); + return; } + + copyUserDefinedValuesToNewForm(m_dataFormModel->form(), newDataForm); + cleanUpLastForm(); + + m_dataFormModel = new RegistrationDataFormModel(newDataForm); + m_dataFormModel->setIsFakeForm(isFakeForm); + // Move to the main thread, so QML can connect signals to the model. + m_dataFormModel->moveToThread(kaidan->thread()); + + // Add the attached Bits of Binary data to the corresponding image provider. + const auto bobDataList = iq.bitsOfBinaryData(); + for (const auto &bobData : bobDataList) { + BitsOfBinaryImageProvider::instance()->addImage(bobData); + m_contentIdsToRemove << bobData.cid(); + } + + emit kaidan->registrationFormReceived(m_dataFormModel); +} + +void RegistrationManager::handleRegistrationSucceeded() +{ + kaidan->setJid(m_dataFormModel->extractUsername().append('@').append(m_client->configuration().domain())); + kaidan->setPassword(m_dataFormModel->extractPassword()); + + kaidan->mainDisconnect(); + kaidan->mainConnect(); + + cleanUpLastForm(); + m_dataFormModel = new RegistrationDataFormModel(); } -void RegistrationManager::handleDiscoInfo(const QXmppDiscoveryIq &iq) +void RegistrationManager::handleRegistrationFailed(const QXmppStanza::Error &error) { - // check features of own server - if (iq.from().isEmpty() || iq.from() == client()->configuration().domain()) { - if (iq.features().contains(NS_REGISTER)) - setRegistrationSupported(true); + RegistrationError registrationError = RegistrationError::UnknownError; + + switch(error.type()) { + case QXmppStanza::Error::Cancel: + if (error.condition() == QXmppStanza::Error::FeatureNotImplemented) + registrationError = RegistrationError::InBandRegistrationNotSupported; + else if (error.condition() == QXmppStanza::Error::Conflict) + registrationError = RegistrationError::UsernameConflict; + else if (error.condition() == QXmppStanza::Error::NotAllowed && error.text().contains("captcha", Qt::CaseInsensitive)) + registrationError = RegistrationError::CaptchaVerificationFailed; + break; + case QXmppStanza::Error::Modify: + if (error.condition() == QXmppStanza::Error::NotAcceptable) { + // TODO: Check error text in English (needs QXmpp change) + if (error.text().contains("password", Qt::CaseInsensitive) && (error.text().contains("weak", Qt::CaseInsensitive) || error.text().contains("short", Qt::CaseInsensitive))) + registrationError = RegistrationError::PasswordTooWeak; + else if (error.text().contains("ip", Qt::CaseInsensitive) || error.text().contains("quickly", Qt::CaseInsensitive) +) + registrationError = RegistrationError::TemporarilyBlocked; + else + registrationError = RegistrationError::RequiredInformationMissing; + } else if (error.condition() == QXmppStanza::Error::BadRequest && error.text().contains("captcha", Qt::CaseInsensitive)) { + registrationError = RegistrationError::CaptchaVerificationFailed; + } + break; + default: +// Workaround: Catch an error which is wrongly emitted by older QXmpp versions although the registration was succesful. +#if (QXMPP_VERSION) <= QT_VERSION_CHECK(1, 2, 0) + if (error.text().isEmpty()) + return; +#else + break; +#endif } + + emit kaidan->registrationFailed(quint8(registrationError), error.text()); } -bool RegistrationManager::handleStanza(const QDomElement &stanza) +QXmppDataForm RegistrationManager::extractFormFromRegisterIq(const QXmppRegisterIq& iq, bool &isFakeForm) { - // result of change password: - if (!m_newPassword.isEmpty() && stanza.attribute("id") == m_newPasswordIqId) { - QXmppRegisterIq iq; - iq.parse(stanza); - - if (iq.type() == QXmppIq::Result) { - // Success - client()->configuration().setPassword(m_newPassword); - settings->setValue(KAIDAN_SETTINGS_AUTH_PASSWD, - QString::fromUtf8(m_newPassword.toUtf8().toBase64())); - emit passwordChanged(m_newPassword); - emit kaidan->passiveNotificationRequested( - tr("Password changed successfully.") - ); - - } else if (iq.type() == QXmppIq::Error) { - // Error - emit passwordChangeFailed(); - emit kaidan->passiveNotificationRequested( - tr("Failed to change password: %1").arg(iq.error().text()) - ); - qWarning() << QString("Failed to change password: %1").arg(iq.error().text()); + QXmppDataForm newDataForm = iq.form(); + if (newDataForm.fields().isEmpty()) { + // This is a hack, so we only need to implement one way of registering in QML. + // A 'fake' data form model is created with a username and password field. + isFakeForm = true; + + if (!iq.username().isNull()) { + QXmppDataForm::Field field; + field.setKey(QStringLiteral("username")); + field.setRequired(true); + field.setType(QXmppDataForm::Field::TextSingleField); + newDataForm.fields().append(field); + } + + if (!iq.password().isNull()) { + QXmppDataForm::Field field; + field.setKey(QStringLiteral("password")); + field.setRequired(true); + field.setType(QXmppDataForm::Field::TextPrivateField); + newDataForm.fields().append(field); } - m_newPassword = ""; - m_newPasswordIqId = ""; - return true; + + if (!iq.email().isNull()) { + QXmppDataForm::Field field; + field.setKey(QStringLiteral("email")); + field.setRequired(true); + field.setType(QXmppDataForm::Field::TextPrivateField); + newDataForm.fields().append(field); + } + } else { + isFakeForm = false; } - return false; + + return newDataForm; } -void RegistrationManager::setRegistrationSupported(bool registrationSupported) +void RegistrationManager::copyUserDefinedValuesToNewForm(const QXmppDataForm &oldForm, QXmppDataForm& newForm) { - if (m_registrationSupported == registrationSupported) { - m_registrationSupported = registrationSupported; - emit registrationSupportedChanged(); + // Copy values from the last form. + const QList oldFields = oldForm.fields(); + for (const auto &field : oldFields) { + // Only copy fields which are required, visible to the user and do not have a media element (e.g. CAPTCHA). + if (field.isRequired() && field.type() != QXmppDataForm::Field::HiddenField && field.media().isNull()) { + for (auto &fieldFromNewForm : newForm.fields()) { + if (fieldFromNewForm.key() == field.key()) { + fieldFromNewForm.setValue(field.value()); + break; + } + } + } } } + +void RegistrationManager::cleanUpLastForm() +{ + delete m_dataFormModel; + + // Remove content IDs from the last form. + for (const auto &cid : qAsConst(m_contentIdsToRemove)) + BitsOfBinaryImageProvider::instance()->removeImage(cid); +} diff --git a/src/RegistrationManager.h b/src/RegistrationManager.h index c7e0f3d..487c8ea 100644 --- a/src/RegistrationManager.h +++ b/src/RegistrationManager.h @@ -1,96 +1,158 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef REGISTRATIONMANAGER_H #define REGISTRATIONMANAGER_H -#include +// Kaidan class ClientWorker; +#include "Globals.h" class Kaidan; +class RegistrationDataFormModel; +// Qt +#include +#include class QSettings; -#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 2, 0) +// QXmpp +#include +class QXmppClient; +class QXmppDataForm; +class QXmppDiscoveryIq; +class QXmppRegisterIq; +#include class QXmppRegistrationManager; -#endif -class RegistrationManager : public QXmppClientExtension +class RegistrationManager : public QObject { Q_OBJECT - Q_PROPERTY(bool registrationSupported READ registrationSupported - NOTIFY registrationSupportedChanged) public: - RegistrationManager(Kaidan *kaidan, ClientWorker *clientWorker, QXmppClient *client, QSettings *settings); + enum RegistrationError { + UnknownError, + InBandRegistrationNotSupported, + UsernameConflict, + PasswordTooWeak, + CaptchaVerificationFailed, + RequiredInformationMissing, + TemporarilyBlocked + }; + Q_ENUM(RegistrationError) - QStringList discoveryFeatures() const override; + RegistrationManager(Kaidan *kaidan, ClientWorker *clientWorker, QXmppClient *client, QSettings *settings); /** - * @brief Changes the user's password - * @param newPassword The requested new password + * Sets whether a registration is requested for the next time when the client connects to the server. + * + * @param registerOnConnect true for requesting a registration on connecting, false otherwise */ - void changePassword(const QString &newPassword); - - bool registrationSupported() const; + void setRegisterOnConnectEnabled(bool registerOnConnect); public slots: + /** + * Sends the form containing information to register an account. + */ + void sendRegistrationForm(); + /** * Deletes the account from the server. */ void deleteAccount(); -signals: - void passwordChanged(const QString &newPassword); - void passwordChangeFailed(); - void registrationSupportedChanged(); + /** + * Changes the user's password. + * + * @param newPassword new password to set + */ + void changePassword(const QString &newPassword); + +private: + /** + * Called when the In-Band Registration support changed. + * + * The server feature state for In-Band Registration is only changed when the server disables it, not on disconnect. + * That way, the last known state can be cached while being offline and operations like deleting an account from the server can be offered to the user even if Kaidan is not connected to the user's server. + */ + void handleInBandRegistrationSupportedChanged(); -protected: - void setClient(QXmppClient *client) override; + void handlePasswordChangeSucceeded(const QString &newPassword); + void handlePasswordChangeFailed(const QXmppStanza::Error &error); -private slots: - void handleDiscoInfo(const QXmppDiscoveryIq &iq); + /** + * Handles an incoming form used to register an account. + * + * @param iq IQ stanza to be handled + */ + void handleRegistrationFormReceived(const QXmppRegisterIq &iq); -private: - bool handleStanza(const QDomElement &stanza) override; - void setRegistrationSupported(bool registrationSupported); + /** + * Handles a succeeded registration. + */ + void handleRegistrationSucceeded(); + + /** + * Handles a failed registration. + * + * @param error error describing the reason for the failure + */ + void handleRegistrationFailed(const QXmppStanza::Error &error); + + /** + * Extracts a form from an IQ stanza for registration. + * + * @param iq IQ stanza containing the form + * @param isFakeForm true if the form is used to TODO: what? + * + * @return data form with extracted key-value pairs + */ + QXmppDataForm extractFormFromRegisterIq(const QXmppRegisterIq &iq, bool &isFakeForm); + + /** + * Copies values set by the user to a new form. + * + * @param oldForm form with old values + * @param newForm form with new values + */ + void copyUserDefinedValuesToNewForm(const QXmppDataForm &oldForm, QXmppDataForm &newForm); + + /** + * Cleans up the last form used for registration. + */ + void cleanUpLastForm(); Kaidan *kaidan; - ClientWorker *clientWorker; - QSettings *settings; + ClientWorker *m_clientWorker; QXmppClient *m_client; -#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 2, 0) + QSettings *settings; QXmppRegistrationManager *m_manager; -#endif - bool m_registrationSupported = false; - - // caching - QString m_newPasswordIqId; - QString m_newPassword; + RegistrationDataFormModel *m_dataFormModel; + QVector m_contentIdsToRemove; }; #endif // REGISTRATIONMANAGER_H diff --git a/src/ServerFeaturesCache.cpp b/src/ServerFeaturesCache.cpp index e00699b..3ff64bf 100644 --- a/src/ServerFeaturesCache.cpp +++ b/src/ServerFeaturesCache.cpp @@ -1,54 +1,70 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #include "ServerFeaturesCache.h" // Qt #include ServerFeaturesCache::ServerFeaturesCache(QObject *parent) : QObject(parent) { } +bool ServerFeaturesCache::inBandRegistrationSupported() +{ + QMutexLocker locker(&m_mutex); + return m_inBandRegistrationSupported; +} + +void ServerFeaturesCache::setInBandRegistrationSupported(bool supported) +{ + QMutexLocker locker(&m_mutex); + if (m_inBandRegistrationSupported != supported) { + m_inBandRegistrationSupported = supported; + locker.unlock(); + emit inBandRegistrationSupportedChanged(); + } +} + bool ServerFeaturesCache::httpUploadSupported() { QMutexLocker locker(&m_mutex); return m_httpUploadSupported; } void ServerFeaturesCache::setHttpUploadSupported(bool supported) { QMutexLocker locker(&m_mutex); if (m_httpUploadSupported != supported) { m_httpUploadSupported = supported; locker.unlock(); emit httpUploadSupportedChanged(); } } diff --git a/src/ServerFeaturesCache.h b/src/ServerFeaturesCache.h index 5359fd0..b53fc3a 100644 --- a/src/ServerFeaturesCache.h +++ b/src/ServerFeaturesCache.h @@ -1,65 +1,82 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef SERVERFEATURESCACHE_H #define SERVERFEATURESCACHE_H #include #include /** * @brief The ServerFeaturesCache class temporarily stores the features of a server. This * can be used to for example enable or disable certain features in the UI. * * All methods in the class are thread-safe. */ class ServerFeaturesCache : public QObject { Q_OBJECT + Q_PROPERTY(bool inBandRegistrationSupported READ inBandRegistrationSupported NOTIFY inBandRegistrationSupportedChanged) Q_PROPERTY(bool httpUploadSupported READ httpUploadSupported NOTIFY httpUploadSupportedChanged) public: explicit ServerFeaturesCache(QObject *parent = nullptr); + /** + * Returns whether In-Band Registration features after login on the server are supported by it. + */ + bool inBandRegistrationSupported(); + + /** + * Sets whether In-Band Registration is supported. + */ + void setInBandRegistrationSupported(bool supported); + /** * Returns whether HTTP File Upload is available and can be currently be used. */ bool httpUploadSupported(); void setHttpUploadSupported(bool supported); signals: + /** + * Emitted when In-Band Registration support changed. + */ + bool inBandRegistrationSupportedChanged(); + void httpUploadSupportedChanged(); private: QMutex m_mutex; + bool m_inBandRegistrationSupported = false; bool m_httpUploadSupported = false; }; #endif // SERVERFEATURESCACHE_H diff --git a/src/ServerListItem.cpp b/src/ServerListItem.cpp new file mode 100644 index 0000000..1f889fe --- /dev/null +++ b/src/ServerListItem.cpp @@ -0,0 +1,257 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +#include "ServerListItem.h" +// Qt +#include +#include +#include + +#define REGIONAL_INDICATOR_SYMBOL_BASE 0x1F1A5 + +class ServerListItemPrivate : public QSharedData +{ +public: + ServerListItemPrivate(); + + bool isCustomServer; + QString jid; + bool supportsInBandRegistration; + QUrl registrationWebPage; + QString language; + QString country; + QUrl website; + int onlineSince; + int httpUploadSize; + int messageStorageDuration; +}; + +ServerListItemPrivate::ServerListItemPrivate() + : isCustomServer(false), supportsInBandRegistration(false), onlineSince(-1), httpUploadSize(-1), messageStorageDuration(-1) +{ +} + +ServerListItem ServerListItem::fromJson(const QJsonObject &object) +{ + ServerListItem item; + item.setIsCustomServer(false); + item.setJid(object.value(QLatin1String("jid")).toString()); + item.setSupportsInBandRegistration(object.value(QLatin1String("supportsInBandRegistration")).toBool()); + item.setRegistrationWebPage(QUrl(object.value(QLatin1String("registrationWebPage")).toString())); + item.setLanguage(object.value(QLatin1String("language")).toString().toUpper()); + item.setCountry(object.value(QLatin1String("country")).toString().toUpper()); + item.setWebsite(QUrl(object.value(QLatin1String("website")).toString())); + item.setOnlineSince(object.value(QLatin1String("onlineSince")).toInt(-1)); + item.setHttpUploadSize(object.value(QLatin1String("httpUploadSize")).toInt(-1)); + item.setMessageStorageDuration(object.value(QLatin1String("messageStorageDuration")).toInt(-1)); + return item; +} + +ServerListItem::ServerListItem(bool isCustomServer) + : d(new ServerListItemPrivate) +{ + d->isCustomServer = isCustomServer; +} + +ServerListItem::ServerListItem(const ServerListItem& other) = default; + +ServerListItem::~ServerListItem() = default; + +ServerListItem & ServerListItem::operator=(const ServerListItem& other) = default; + +bool ServerListItem::isCustomServer() const +{ + return d->isCustomServer; +} + +void ServerListItem::setIsCustomServer(bool isCustomServer) +{ + d->isCustomServer = isCustomServer; +} + +QString ServerListItem::jid() const +{ + return d->jid; +} + +void ServerListItem::setJid(const QString &jid) +{ + d->jid = jid; +} + +bool ServerListItem::supportsInBandRegistration() const +{ + return d->supportsInBandRegistration; +} + +void ServerListItem::setSupportsInBandRegistration(bool supportsInBandRegistration) +{ + d->supportsInBandRegistration = supportsInBandRegistration; +} + +QUrl ServerListItem::registrationWebPage() const +{ + return d->registrationWebPage; +} + +void ServerListItem::setRegistrationWebPage(const QUrl ®istrationWebPage) +{ + d->registrationWebPage = registrationWebPage; +} + +QString ServerListItem::language() const +{ + return d->language; +} + +void ServerListItem::setLanguage(const QString &language) +{ + d->language = language; +} + +QString ServerListItem::country() const +{ + return d->country; +} + +void ServerListItem::setCountry(const QString &country) +{ + d->country = country; +} + +QString ServerListItem::flag() const +{ + // If this object is the custom server, no flag should be shown. + if (d->isCustomServer) + return {}; + + // If the country is not specified, return a flag for an unknown country. + if (d->country.isEmpty()) + return QStringLiteral("🏳️‍🌈"); + + QString flag; + + // Iterate over the characters of the country string. + // Example: For the country string "DE" the loop iterates over the characters "D" and "E". + // An emoji flag sequence (i.e. the flag of the corresponding country / region) is represented by two regional indicator symbols. + // Example: 🇩 (U+1F1E9 = 0x1F1E9 = 127465) and 🇪 (U+1F1EA = 127466) concatenated result in 🇩🇪. + // Each regional indicator symbol is created by a string which has the following Unicode code point: + // REGIONAL_INDICATOR_SYMBOL_BASE + unicode code point of the character of the country string. + // Example: 127397 (REGIONAL_INDICATOR_SYMBOL_BASE) + 68 (unicode code point of "D") = 127465 for 🇩 + // + // QString does not provide to create a string by its corresponding Unicode code point. + // Therefore, QChar must be used to create a character by its Unicode code point. + // Unfortunately, that can be done in one step because QChar does not support creating Unicode characters greater than 16 bits. + // For this reason, each character of the country string is split into two parts. + // Each part consists of 16 bits of the original character. + // The first and the second part are then merged into one string. + // + // Finally, the string consisting of the first regional indicator symbol and the string consisting the second one are concatenated. + // The resulting string represents the emoji flag sequence. + for (const auto &character : d->country) { + uint32_t regionalIncidatorSymbolCodePoint = REGIONAL_INDICATOR_SYMBOL_BASE + character.unicode(); + QString regionalIncidatorSymbol; + + QChar regionalIncidatorSymbolParts[2]; + regionalIncidatorSymbolParts[0] = QChar::highSurrogate(regionalIncidatorSymbolCodePoint); + regionalIncidatorSymbolParts[1] = QChar::lowSurrogate(regionalIncidatorSymbolCodePoint); + + regionalIncidatorSymbol = QString(regionalIncidatorSymbolParts, 2); + + flag.append(regionalIncidatorSymbol); + } + + return flag; +} + +QUrl ServerListItem::website() const +{ + return d->website; +} + +void ServerListItem::setWebsite(const QUrl &website) +{ + d->website = website; +} + +int ServerListItem::onlineSince() const +{ + return d->onlineSince; +} + +void ServerListItem::setOnlineSince(int onlineSince) +{ + d->onlineSince = onlineSince; +} + +int ServerListItem::httpUploadSize() const +{ + return d->httpUploadSize; +} + +void ServerListItem::setHttpUploadSize(int httpUploadSize) +{ + d->httpUploadSize = httpUploadSize; +} + +int ServerListItem::messageStorageDuration() const +{ + return d->messageStorageDuration; +} + +void ServerListItem::setMessageStorageDuration(int messageStorageDuration) +{ + d->messageStorageDuration = messageStorageDuration; +} + +bool ServerListItem::operator<(const ServerListItem& other) const +{ + return d->jid < other.jid(); +} + +bool ServerListItem::operator>(const ServerListItem& other) const +{ + return d->jid > other.jid(); +} + +bool ServerListItem::operator<=(const ServerListItem& other) const +{ + return d->jid <= other.jid(); +} + +bool ServerListItem::operator>=(const ServerListItem& other) const +{ + return d->jid >= other.jid(); +} + +bool ServerListItem::operator==(const ServerListItem& other) const +{ + return d == other.d; +} diff --git a/src/ServerListItem.h b/src/ServerListItem.h new file mode 100644 index 0000000..6303906 --- /dev/null +++ b/src/ServerListItem.h @@ -0,0 +1,96 @@ +/* +* Kaidan - A user-friendly XMPP client for every device! +* +* Copyright (C) 2016-2020 Kaidan developers and contributors +* (see the LICENSE file for a full list of copyright authors) +* +* Kaidan 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. +* +* In addition, as a special exception, the author of Kaidan gives +* permission to link the code of its release with the OpenSSL +* project's "OpenSSL" library (or with modified versions of it that +* use the same license as the "OpenSSL" library), and distribute the +* linked executables. You must obey the GNU General Public License in +* all respects for all of the code used other than "OpenSSL". If you +* modify this file, you may extend this exception to your version of +* the file, but you are not obligated to do so. If you do not wish to +* do so, delete this exception statement from your version. +* +* Kaidan 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 Kaidan. If not, see . +*/ + +#ifndef SERVERLISTITEM_H +#define SERVERLISTITEM_H + +#include +#include + +class QUrl; +class QJsonObject; + +class ServerListItemPrivate; + +class ServerListItem +{ +public: + static ServerListItem fromJson(const QJsonObject &object); + + ServerListItem(bool isCustomServer = false); + ServerListItem(const ServerListItem &other); + ~ServerListItem(); + + ServerListItem &operator=(const ServerListItem &other); + + bool isCustomServer() const; + void setIsCustomServer(bool isCustomServer); + + QString jid() const; + void setJid(const QString &jid); + + bool supportsInBandRegistration() const; + void setSupportsInBandRegistration(bool supportsInBandRegistration); + + QUrl registrationWebPage() const; + void setRegistrationWebPage(const QUrl ®istrationWebPage); + + QString language() const; + void setLanguage(const QString &language); + + QString country() const; + void setCountry(const QString &country); + + QString flag() const; + + QUrl website() const; + void setWebsite(const QUrl &website); + + int onlineSince() const; + void setOnlineSince(int onlineSince); + + int httpUploadSize() const; + void setHttpUploadSize(int httpUploadSize); + + int messageStorageDuration() const; + void setMessageStorageDuration(int messageStorageDuration); + + bool operator<(const ServerListItem &other) const; + bool operator>(const ServerListItem &other) const; + bool operator<=(const ServerListItem &other) const; + bool operator>=(const ServerListItem &other) const; + + bool operator==(const ServerListItem &other) const; + +private: + QSharedDataPointer d; +}; + +#endif // SERVERLISTITEM_H diff --git a/src/ServerListModel.cpp b/src/ServerListModel.cpp new file mode 100644 index 0000000..4d03488 --- /dev/null +++ b/src/ServerListModel.cpp @@ -0,0 +1,216 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +#include "ServerListModel.h" +// Qt +#include +#include +#include +#include +#include +#include +// Kaidan +#include "ServerListItem.h" +#include "Globals.h" +#include "QmlUtils.h" + +ServerListModel::ServerListModel(QObject *parent) + : QAbstractListModel(parent) +{ + ServerListItem customServer(true); + customServer.setJid(tr("Custom server")); + m_items << customServer; + + readItemsFromJsonFile(SERVER_LIST_FILE_PATH); +} + +QHash ServerListModel::roleNames() const +{ + return { + {DisplayRole, QByteArrayLiteral("display")}, + {JidRole, QByteArrayLiteral("jid")}, + {SupportsInBandRegistrationRole, QByteArrayLiteral("supportsInBandRegistration")}, + {RegistrationWebPageRole, QByteArrayLiteral("registrationWebPage")}, + {LanguageRole, QByteArrayLiteral("language")}, + {CountryRole, QByteArrayLiteral("country")}, + {FlagRole, QByteArrayLiteral("flag")}, + {IsCustomServerRole, QByteArrayLiteral("isCustomServer")}, + {WebsiteRole, QByteArrayLiteral("website")}, + {HttpUploadSizeRole, QByteArrayLiteral("httpUploadSize")}, + {MessageStorageDurationRole, QByteArrayLiteral("messageStorageDuration")} + }; +} + +int ServerListModel::rowCount(const QModelIndex &parent) const +{ + // For list models, only the root node (an invalid parent) should return the list's size. + // For all other (valid) parents, rowCount() should return 0 so that it does not become a tree model. + if (parent.isValid()) + return 0; + + return m_items.size(); +} + +QVariant ServerListModel::data(const QModelIndex &index, int role) const +{ + Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid)); + + const ServerListItem &item = m_items.at(index.row()); + + switch (role) { + case DisplayRole: + return QStringLiteral("%1 %2").arg(item.flag(), item.jid()); + case JidRole: + return item.jid(); + case SupportsInBandRegistrationRole: + return item.supportsInBandRegistration(); + case RegistrationWebPageRole: + return item.registrationWebPage(); + case LanguageRole: + return item.language(); + case CountryRole: + return item.country(); + case FlagRole: + return item.flag(); + case IsCustomServerRole: + return item.isCustomServer(); + case WebsiteRole: + return QmlUtils::formatMessage(item.website().toString()); + case OnlineSinceRole: + if (item.onlineSince() == -1) + return QString(); + return QString::number(item.onlineSince()); + case HttpUploadSizeRole: + switch (item.httpUploadSize()) { + case -1: + return QString(); + case 0: + //: Unlimited file size for uploading files + return tr("No limitation"); + default: + return QLocale::system().formattedDataSize(item.httpUploadSize() * 1024LL * 1024LL, 0); + } + case MessageStorageDurationRole: + switch (item.messageStorageDuration()) { + case -1: + return QString(); + case 0: + //: Deletion of message history saved on server + return tr("No limitation"); + default: + return tr("%1 days").arg(item.messageStorageDuration()); + } + } + + return {}; +} + +QVariant ServerListModel::data(int row, ServerListModel::Role role) const +{ + return data(index(row), role); +} + +int ServerListModel::randomlyChooseIndex() const +{ + QVector serversWithInBandRegistration = serversSupportingInBandRegistration(); + + QString systemCountryCode = QLocale::system().name().split(QStringLiteral("_")).last(); + QVector serversWithInBandRegistrationFromCountry = serversFromCountry(serversWithInBandRegistration, systemCountryCode); + + if (serversWithInBandRegistrationFromCountry.size() < SERVER_LIST_MIN_SERVERS_FROM_COUNTRY) + return indexOfRandomlySelectedServer(serversWithInBandRegistration); + + return indexOfRandomlySelectedServer(serversWithInBandRegistrationFromCountry); +} + +void ServerListModel::readItemsFromJsonFile(const QString &filePath) +{ + QFile file(filePath); + if (!file.exists()) { + qWarning() << "[ServerListModel] Could not parse server list:" + << filePath + << "- file does not exist!"; + return; + } + + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "[ServerListModel] Could not open file for reading:" << filePath; + return; + } + + QByteArray content = file.readAll(); + + QJsonParseError parseError; + QJsonArray jsonServerArray = QJsonDocument::fromJson(content, &parseError).array(); + if (jsonServerArray.isEmpty()) { + qWarning() << "[ServerListModel] Could not parse server list JSON file or no servers defined."; + qWarning() << "[ServerListModel] QJsonParseError:" << parseError.errorString() << "at" << parseError.offset; + return; + } + + for (auto jsonServerItem : jsonServerArray) { + if (!jsonServerItem.isNull() && jsonServerItem.isObject()) + m_items << ServerListItem::fromJson(jsonServerItem.toObject()); + } + + // Sort the parsed servers. + // The first item ("Custom server") is not sorted. + if (m_items.size() > 1) + std::sort(m_items.begin() + 1, m_items.end()); +} + +QVector ServerListModel::serversSupportingInBandRegistration() const +{ + QVector servers; + + // The search starts at index 1 to exclude the custom server. + std::copy_if(m_items.begin() + 1, m_items.end(), std::back_inserter(servers), [](const ServerListItem &item) { + return item.supportsInBandRegistration(); + }); + + return servers; +} + +QVector ServerListModel::serversFromCountry(const QVector &preSelectedServers, const QString &country) const +{ + QVector servers; + + for (const auto &server : preSelectedServers) { + if (server.country() == country) + servers << server; + } + + return servers; +} + +int ServerListModel::indexOfRandomlySelectedServer(const QVector &preSelectedServers) const +{ + return m_items.indexOf(preSelectedServers.at(QRandomGenerator::global()->generate() % preSelectedServers.size())); +} diff --git a/src/VCardManager.h b/src/ServerListModel.h similarity index 53% copy from src/VCardManager.h copy to src/ServerListModel.h index 3bc5297..7c2f3c8 100644 --- a/src/VCardManager.h +++ b/src/ServerListModel.h @@ -1,72 +1,80 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -#ifndef VCARDMANAGER_H -#define VCARDMANAGER_H +#ifndef SERVERLISTMODEL_H +#define SERVERLISTMODEL_H -#include -#include -#include +#include +#include -class AvatarFileStorage; -class QXmppClient; +#include "ServerListItem.h" -class VCardManager : public QObject +class ServerListModel : public QAbstractListModel { Q_OBJECT public: - VCardManager(QXmppClient *client, AvatarFileStorage *avatars, QObject *parent = nullptr); + enum Role { + DisplayRole = Qt::DisplayRole, + JidRole = Qt::UserRole + 1, + SupportsInBandRegistrationRole, + RegistrationWebPageRole, + LanguageRole, + CountryRole, + FlagRole, + IsCustomServerRole, + WebsiteRole, + OnlineSinceRole, + HttpUploadSizeRole, + MessageStorageDurationRole + }; + Q_ENUM(Role) - /** - * Will request the VCard from the server - */ - void fetchVCard(const QString& jid); + explicit ServerListModel(QObject *parent = nullptr); - /** - * Handles incoming VCards and processes them (save avatar, etc.) - */ - void handleVCard(const QXmppVCardIq &iq); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - /** - * Handles incoming presences and checks if the avatar needs to be refreshed - */ - void handlePresence(const QXmppPresence &presence); + // overloaded method for QML + Q_INVOKABLE QVariant data(int row, ServerListModel::Role role) const; -signals: - void vCardReceived(const QXmppVCardIq &vCard); + Q_INVOKABLE int randomlyChooseIndex() const; private: - QXmppClient *client; - QXmppVCardManager *manager; - AvatarFileStorage *avatarStorage; + void readItemsFromJsonFile(const QString &filePath); + QVector serversSupportingInBandRegistration() const; + QVector serversFromCountry(const QVector &preSelectedServers, const QString &country) const; + int indexOfRandomlySelectedServer(const QVector &preSelectedServers) const; + + QVector m_items; }; -#endif // VCARDMANAGER_H +#endif // SERVERLISTMODEL_H diff --git a/src/VCardManager.cpp b/src/VCardManager.cpp index 3ba552d..b040049 100644 --- a/src/VCardManager.cpp +++ b/src/VCardManager.cpp @@ -1,86 +1,112 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #include "VCardManager.h" #include "AvatarFileStorage.h" #include "Kaidan.h" #include #include #include VCardManager::VCardManager(QXmppClient *client, AvatarFileStorage *avatars, QObject *parent) : QObject(parent), client(client), manager(client->findExtension()), avatarStorage(avatars) { connect(manager, &QXmppVCardManager::vCardReceived, this, &VCardManager::handleVCard); connect(client, &QXmppClient::presenceReceived, this, &VCardManager::handlePresence); + connect(manager, &QXmppVCardManager::clientVCardReceived, this, &VCardManager::handleClientVCardReceived); connect(Kaidan::instance(), &Kaidan::vCardRequested, this, &VCardManager::fetchVCard); // Currently we're not requesting the own VCard on every connection because it is probably // way too resource intensive on mobile connections with many reconnects. // Actually we would need to request our own avatar, calculate the hash of it and publish // that in our presence. // // XEP-0084: User Avatar - probably best option (as long as the servers support XEP-0398: // User Avatar to vCard-Based Avatars Conversion) } void VCardManager::fetchVCard(const QString& jid) { if (client->state() == QXmppClient::ConnectedState) client->findExtension()->requestVCard(jid); else qWarning() << "[VCardManager] Could not fetch VCard: Not connected to a server"; } void VCardManager::handleVCard(const QXmppVCardIq &iq) { if (!iq.photo().isEmpty()) { avatarStorage->addAvatar(QXmppUtils::jidToBareJid(iq.from().isEmpty() ? client->configuration().jid() : iq.from()), iq.photo()); } emit vCardReceived(iq); } +void VCardManager::fetchClientVCard() +{ + manager->requestClientVCard(); +} + +void VCardManager::handleClientVCardReceived() +{ + if (!m_nicknameToBeSetAfterFetchingCurrentVCard.isEmpty()) + updateNicknameAfterFetchingCurrentVCard(); +} + void VCardManager::handlePresence(const QXmppPresence &presence) { if (presence.vCardUpdateType() == QXmppPresence::VCardUpdateValidPhoto) { QString hash = avatarStorage->getHashOfJid(QXmppUtils::jidToBareJid(presence.from())); QString newHash = presence.photoHash().toHex(); // check if hash differs and we need to refetch the avatar if (hash != newHash) manager->requestVCard(QXmppUtils::jidToBareJid(presence.from())); } else if (presence.vCardUpdateType() == QXmppPresence::VCardUpdateNoPhoto) { QString bareJid = QXmppUtils::jidToBareJid(presence.from()); avatarStorage->clearAvatar(bareJid); } // ignore VCardUpdateNone (protocol unsupported) and VCardUpdateNotReady } + +void VCardManager::updateNickname(const QString &nickname) +{ + m_nicknameToBeSetAfterFetchingCurrentVCard = nickname; + fetchClientVCard(); +} + +void VCardManager::updateNicknameAfterFetchingCurrentVCard() +{ + QXmppVCardIq vCardIq = manager->clientVCard(); + vCardIq.setNickName(m_nicknameToBeSetAfterFetchingCurrentVCard); + manager->setClientVCard(vCardIq); + m_nicknameToBeSetAfterFetchingCurrentVCard = QString(); +} diff --git a/src/VCardManager.h b/src/VCardManager.h index 3bc5297..386147d 100644 --- a/src/VCardManager.h +++ b/src/VCardManager.h @@ -1,72 +1,96 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef VCARDMANAGER_H #define VCARDMANAGER_H #include +#include #include #include class AvatarFileStorage; class QXmppClient; class VCardManager : public QObject { Q_OBJECT public: VCardManager(QXmppClient *client, AvatarFileStorage *avatars, QObject *parent = nullptr); /** * Will request the VCard from the server */ void fetchVCard(const QString& jid); /** * Handles incoming VCards and processes them (save avatar, etc.) */ void handleVCard(const QXmppVCardIq &iq); + /** + * Requests the user's vCard from the server. + */ + void fetchClientVCard(); + + /** + * Handles the receiving of the user's vCard. + */ + void handleClientVCardReceived(); + /** * Handles incoming presences and checks if the avatar needs to be refreshed */ void handlePresence(const QXmppPresence &presence); + /** + * Updates the user's nickname. + * + * @param nickname name that is shown to contacts after the update + */ + void updateNickname(const QString &nickname); + signals: void vCardReceived(const QXmppVCardIq &vCard); private: + /** + * Updates the nickname which was set to be updated after fetching the current vCard. + */ + void updateNicknameAfterFetchingCurrentVCard(); + QXmppClient *client; QXmppVCardManager *manager; AvatarFileStorage *avatarStorage; + QString m_nicknameToBeSetAfterFetchingCurrentVCard; }; #endif // VCARDMANAGER_H diff --git a/src/main.cpp b/src/main.cpp index 2c3e662..022d64d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,394 +1,408 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ // Qt +#include #include #include #include #include #include #include #include #include #include #include #include #include // QXmpp #include "qxmpp-exts/QXmppUploadManager.h" #include +#include // Kaidan #include "AvatarFileStorage.h" +#include "BitsOfBinaryImageProvider.h" +#include "CredentialsGenerator.h" +#include "CredentialsValidator.h" +#include "DataFormModel.h" #include "EmojiModel.h" #include "Enums.h" #include "Kaidan.h" #include "Message.h" #include "MessageModel.h" #include "PresenceCache.h" #include "QmlUtils.h" +#include "RegistrationDataFormFilterModel.h" +#include "RegistrationManager.h" #include "RosterModel.h" #include "RosterFilterProxyModel.h" #include "StatusBar.h" +#include "ServerListModel.h" #include "UploadManager.h" -#include "EmojiModel.h" #include "Utils.h" #include "QrCodeGenerator.h" #include "QrCodeScannerFilter.h" #include "VCardModel.h" #include "CameraModel.h" #include "AudioDeviceModel.h" #include "MediaSettingModel.h" #include "MediaUtils.h" #include "MediaRecorder.h" -#include "CredentialsValidator.h" #ifdef STATIC_BUILD #include "static_plugins.h" #endif #ifndef QAPPLICATION_CLASS #define QAPPLICATION_CLASS QApplication #endif #include QT_STRINGIFY(QAPPLICATION_CLASS) #if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) // SingleApplication (Qt5 replacement for QtSingleApplication) #include "singleapp/singleapplication.h" #endif #ifdef STATIC_BUILD #define KIRIGAMI_BUILD_TYPE_STATIC #include "./3rdparty/kirigami/src/kirigamiplugin.h" #endif #ifdef Q_OS_ANDROID #include #endif #ifdef Q_OS_WIN #include #endif enum CommandLineParseResult { CommandLineOk, CommandLineError, CommandLineVersionRequested, CommandLineHelpRequested }; CommandLineParseResult parseCommandLine(QCommandLineParser &parser, QString *errorMessage) { // application description parser.setApplicationDescription(QString(APPLICATION_DISPLAY_NAME) + " - " + QString(APPLICATION_DESCRIPTION)); // add all possible arguments QCommandLineOption helpOption = parser.addHelpOption(); QCommandLineOption versionOption = parser.addVersionOption(); parser.addOption({"disable-xml-log", "Disable output of full XMPP XML stream."}); parser.addOption({{"m", "multiple"}, "Allow multiple instances to be started."}); parser.addPositionalArgument("xmpp-uri", "An XMPP-URI to open (i.e. join a chat).", "[xmpp-uri]"); // parse arguments if (!parser.parse(QGuiApplication::arguments())) { *errorMessage = parser.errorText(); return CommandLineError; } // check for special cases if (parser.isSet(versionOption)) return CommandLineVersionRequested; if (parser.isSet(helpOption)) return CommandLineHelpRequested; // if nothing special happened, return OK return CommandLineOk; } Q_DECL_EXPORT int main(int argc, char *argv[]) { #ifdef Q_OS_WIN if (AttachConsole(ATTACH_PARENT_PROCESS)) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); } #endif // // App // #ifdef UBUNTU_TOUCH qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "true"); qputenv("QT_QUICK_CONTROLS_MOBILE", "true"); #endif #ifdef APPIMAGE qputenv("OPENSSL_CONF", ""); #endif // name, display name, description QGuiApplication::setApplicationName(APPLICATION_NAME); QGuiApplication::setApplicationDisplayName(APPLICATION_DISPLAY_NAME); QGuiApplication::setApplicationVersion(VERSION_STRING); // attributes QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); // create a qt app #if defined(Q_OS_IOS) || defined(Q_OS_ANDROID) QGuiApplication app(argc, argv); #else SingleApplication app(argc, argv, true); #endif #ifdef APPIMAGE QFileInfo executable(QCoreApplication::applicationFilePath()); if (executable.isSymLink()) { executable.setFile(executable.symLinkTarget()); } QString gstreamerPluginsPath; // Try to use deployed plugins if any... #if defined(TARGET_GSTREAMER_PLUGINS) gstreamerPluginsPath = QString::fromLocal8Bit(TARGET_GSTREAMER_PLUGINS); if (!gstreamerPluginsPath.isEmpty()) { gstreamerPluginsPath = QDir::cleanPath(QString::fromLatin1("%1/../..%2") .arg(executable.absolutePath(), gstreamerPluginsPath)); } qDebug() << "Looking for gstreamer in " << gstreamerPluginsPath; #else qFatal("Please provide the unified directory containing the gstreamer plugins and gst-plugin-scanner."); #endif #if defined(QT_DEBUG) qputenv("GST_DEBUG", "ERROR:5,WARNING:5,INFO:5,DEBUG:5,LOG:5"); #endif qputenv("GST_PLUGIN_PATH_1_0", QByteArray()); qputenv("GST_PLUGIN_SYSTEM_PATH_1_0", gstreamerPluginsPath.toLocal8Bit()); qputenv("GST_PLUGIN_SCANNER_1_0", QString::fromLatin1("%1/gst-plugin-scanner").arg(gstreamerPluginsPath).toLocal8Bit()); #endif // APPIMAGE // register qMetaTypes qRegisterMetaType("RosterItem"); qRegisterMetaType("RosterModel*"); qRegisterMetaType("Message"); qRegisterMetaType("MessageModel*"); qRegisterMetaType("AvatarFileStorage*"); qRegisterMetaType("PresenceCache*"); qRegisterMetaType("QXmppPresence"); qRegisterMetaType("Credentials"); qRegisterMetaType("Qt::ApplicationState"); qRegisterMetaType("QXmppClient::State"); qRegisterMetaType("MessageType"); qRegisterMetaType("TransferJob*"); qRegisterMetaType("QmlUtils*"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QHash"); qRegisterMetaType>("std::function"); qRegisterMetaType>("std::function"); qRegisterMetaType("ClientWorker::Credentials"); qRegisterMetaType("QXmppVCardIq"); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("QXmppStanza::Error"); // Enums for c++ member calls using enums qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); // Qt-Translator QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); QCoreApplication::installTranslator(&qtTranslator); // Kaidan-Translator QTranslator kaidanTranslator; // load the systems locale or none from resources kaidanTranslator.load(QLocale::system().name(), ":/i18n"); QCoreApplication::installTranslator(&kaidanTranslator); // // Command line arguments // // create parser and add a description QCommandLineParser parser; // parse the arguments QString commandLineErrorMessage; switch (parseCommandLine(parser, &commandLineErrorMessage)) { case CommandLineError: qWarning() << commandLineErrorMessage; return 1; case CommandLineVersionRequested: parser.showVersion(); return 0; case CommandLineHelpRequested: parser.showHelp(); return 0; case CommandLineOk: break; } #if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) // check if another instance already runs if (app.isSecondary() && !parser.isSet("multiple")) { qDebug().noquote() << QString("Another instance of %1 is already running.") .arg(APPLICATION_DISPLAY_NAME) << "You can enable multiple instances by specifying '--multiple'."; // send a possible link to the primary instance if (!parser.positionalArguments().isEmpty()) app.sendMessage(parser.positionalArguments().first().toUtf8()); return 0; } #endif // // Kaidan back-end // auto *kaidan = new Kaidan(&app, !parser.isSet("disable-xml-log")); #if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) // receive messages from other instances of Kaidan Kaidan::connect(&app, &SingleApplication::receivedMessage, kaidan, &Kaidan::receiveMessage); #endif // open the XMPP-URI/link (if given) if (!parser.positionalArguments().isEmpty()) kaidan->addOpenUri(parser.positionalArguments().first()); // // QML-GUI // if (QIcon::themeName().isEmpty()) { QIcon::setThemeName("breeze"); } QQmlApplicationEngine engine; + + engine.addImageProvider(QLatin1String(BITS_OF_BINARY_IMAGE_PROVIDER_NAME), BitsOfBinaryImageProvider::instance()); + // QtQuickControls2 Style if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) { #ifdef Q_OS_WIN const QString defaultStyle = QStringLiteral("Universal"); #else const QString defaultStyle = QStringLiteral("Material"); #endif qDebug() << "QT_QUICK_CONTROLS_STYLE not set, setting to" << defaultStyle; qputenv("QT_QUICK_CONTROLS_STYLE", defaultStyle.toLatin1()); } // QML type bindings #ifdef STATIC_BUILD KirigamiPlugin::getInstance().registerTypes(); #endif qmlRegisterType("StatusBar", 0, 1, "StatusBar"); qmlRegisterType("EmojiModel", 0, 1, "EmojiModel"); qmlRegisterType("EmojiModel", 0, 1, "EmojiProxyModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "QrCodeScannerFilter"); qmlRegisterType(APPLICATION_ID, 1, 0, "VCardModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "RosterFilterProxyModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "CameraModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "AudioDeviceModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaSettingsContainerModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaSettingsResolutionModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaSettingsQualityModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaSettingsImageCodecModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaSettingsAudioCodecModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaSettingsAudioSampleRateModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaSettingsVideoCodecModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaSettingsVideoFrameRateModel"); qmlRegisterType(APPLICATION_ID, 1, 0, "MediaRecorder"); + qmlRegisterType(APPLICATION_ID, 1, 0, "CredentialsGenerator"); qmlRegisterType(APPLICATION_ID, 1, 0, "CredentialsValidator"); qmlRegisterType(APPLICATION_ID, 1, 0, "QrCodeGenerator"); + qmlRegisterType(APPLICATION_ID, 1, 0, "RegistrationDataFormFilterModel"); + qmlRegisterType(APPLICATION_ID, 1, 0, "ServerListModel"); qmlRegisterUncreatableType("EmojiModel", 0, 1, "QAbstractItemModel", "Used by proxy models"); qmlRegisterUncreatableType("EmojiModel", 0, 1, "Emoji", "Used by emoji models"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "TransferJob", "TransferJob type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "QMimeType", "QMimeType type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "CameraInfo", "CameraInfo type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "AudioDeviceInfo", "AudioDeviceInfo type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "MediaSettings", "MediaSettings type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "CommonEncoderSettings", "CommonEncoderSettings type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "ImageEncoderSettings", "ImageEncoderSettings type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "AudioEncoderSettings", "AudioEncoderSettings type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "VideoEncoderSettings", "VideoEncoderSettings type usable"); qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "ClientWorker", "Cannot create object; only enums defined!"); + qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "DataFormModel", "Cannot create object; only enums defined!"); + qmlRegisterUncreatableType(APPLICATION_ID, 1, 0, "RegistrationManager", "Cannot create object; only enums defined!"); - - qmlRegisterUncreatableMetaObject(Enums::staticMetaObject, APPLICATION_ID, - 1, 0, "Enums", "Can't create object; only enums defined!"); + qmlRegisterUncreatableMetaObject(Enums::staticMetaObject, APPLICATION_ID, 1, 0, "Enums", "Can't create object; only enums defined!"); qmlRegisterSingletonType("MediaUtils", 0, 1, "MediaUtilsInstance", [](QQmlEngine *, QJSEngine *) { QObject *instance = new MediaUtils(qApp); return instance; }); qmlRegisterSingletonType(APPLICATION_ID, 1, 0, "Utils", [](QQmlEngine *, QJSEngine *) { return static_cast(QmlUtils::instance()); }); qmlRegisterSingletonType(APPLICATION_ID, 1, 0, "Kaidan", [](QQmlEngine *, QJSEngine *) { return static_cast(Kaidan::instance()); }); engine.load(QUrl("qrc:/qml/main.qml")); if (engine.rootObjects().isEmpty()) return -1; #ifdef Q_OS_ANDROID QtAndroid::hideSplashScreen(); #endif // enter qt main loop return app.exec(); } diff --git a/src/qml/AccountDeletionFromClientAndServerConfirmationPage.qml b/src/qml/AccountDeletionFromClientAndServerConfirmationPage.qml index 834a715..ee5efde 100644 --- a/src/qml/AccountDeletionFromClientAndServerConfirmationPage.qml +++ b/src/qml/AccountDeletionFromClientAndServerConfirmationPage.qml @@ -1,52 +1,52 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 +import QtQuick 2.12 import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 import "elements" /** * This page is used for confirming the deletion of an account from the client and server. */ ConfirmationPage { title: qsTr("Delete account completely") topDescription: qsTr("Your account will be deleted completely, which means from this app and from the server.\nYou will not be able to use your account again!") topAction: Kirigami.Action { text: qsTr("Delete") onTriggered: { Kaidan.deleteAccountFromClientAndServer() } } } diff --git a/src/qml/AccountDeletionFromClientConfirmationPage.qml b/src/qml/AccountDeletionFromClientConfirmationPage.qml index 6f78f75..ddd7616 100644 --- a/src/qml/AccountDeletionFromClientConfirmationPage.qml +++ b/src/qml/AccountDeletionFromClientConfirmationPage.qml @@ -1,52 +1,52 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 +import QtQuick 2.12 import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 import "elements" /** * This page is used for confirming the deletion of an account from the client. */ ConfirmationPage { title: qsTr("Remove account from this app") topDescription: qsTr("Your account will be removed from this app.\nYou won't be able to get your credentials back!\nMake sure that you have backed up those if you want to use your account later.") topAction: Kirigami.Action { text: qsTr("Remove") onTriggered: { Kaidan.deleteAccountFromClient() } } } diff --git a/src/qml/AccountDeletionPage.qml b/src/qml/AccountDeletionPage.qml index bf1c117..5d39fbe 100644 --- a/src/qml/AccountDeletionPage.qml +++ b/src/qml/AccountDeletionPage.qml @@ -1,60 +1,60 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 +import QtQuick 2.12 import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 import "elements" /** * This page is used for deleting an account. * * An account can be deleted only from the client or from the client and the server. */ BinaryDecisionPage { title: qsTr("Delete account") topDescription: qsTr("You can remove your account %1 only from this app or delete it completely. If you delete your account completely, you won't be able to use it with another app because it is also removed from the server.").arg(Kaidan.jid) topImageSource: Utils.getResourcePath("images/account-deletion-from-client.svg") bottomImageSource: Utils.getResourcePath("images/account-deletion-from-client-and-server.svg") topAction: Kirigami.Action { text: qsTr("Remove from this app") onTriggered: pageStack.layers.push(accountDeletionFromClientConfirmationPage) } bottomAction: Kirigami.Action { text: qsTr("Delete completely") onTriggered: pageStack.layers.push(accountDeletionFromClientAndServerConfirmationPage) } } diff --git a/src/qml/AccountTransferPage.qml b/src/qml/AccountTransferPage.qml index 1adbb9d..93845a6 100644 --- a/src/qml/AccountTransferPage.qml +++ b/src/qml/AccountTransferPage.qml @@ -1,153 +1,153 @@ /* * Kaidan - A user-friendly XMPP client for every device! * - * Copyright (C) 2016-2019 Kaidan developers and contributors + * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 -import QtQuick.Controls 2.3 as Controls -import QtQuick.Layouts 1.3 -import org.kde.kirigami 2.4 as Kirigami +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 +import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 import "elements" /** * This page shows the user's credentials as a QR code or as cleartext, which allows the user to log in on another device. */ Kirigami.Page { id: root title: qsTr("Transfer account to another device") leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 QrCodeGenerator { id: qrCodeGenerator } ColumnLayout { z: 1 anchors.margins: 18 anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom CenteredAdaptiveText { id: explanation text: qsTr("Scan the QR code or enter the credentials as text on another device to log in on it.\n\nAttention:\nNever show this QR code to anyone else. It would allow unlimited access to your account!") width: parent.width Layout.topMargin: 10 scaleFactor: 2 } // placeholder Item { Layout.fillHeight: true } Kirigami.Icon { id: qrCode width: parent.width height: width visible: false source: visible ? qrCodeGenerator.generateLoginUriQrCode(width) : "" Layout.fillWidth: true Layout.fillHeight: true } Kirigami.FormLayout { id: cleartext visible: false Controls.Label { text: Kaidan.jid Kirigami.FormData.label: qsTr("Chat address:") } Controls.Label { text: Kaidan.password Kirigami.FormData.label: qsTr("Password:") } } // placeholder Item { Layout.fillHeight: true } ColumnLayout { Layout.alignment: Qt.AlignHCenter Layout.maximumWidth: largeButtonWidth // button for showing or hiding the credentials as a QR code CenteredAdaptiveHighlightedButton { label.text: checked ? qsTr("Hide QR code") : qsTr("Show as QR code") checkable: true // If that was not used, this button would change its label text but not its checked state when the button for showing the cleartext is clicked right after it. checked: qrCode.visible onClicked: { if (qrCode.visible) { qrCode.visible = false cleartext.visible = false explanation.visible = true } else { qrCode.visible = true cleartext.visible = false explanation.visible = false } } } // button for showing or hiding the credentials as cleartext CenteredAdaptiveButton { label.text: checked ? qsTr("Hide text") : qsTr("Show as text") checkable: true // If that was not used, this button would change its label text but not its checked state when the button for showing the QR code is clicked right after it. checked: cleartext.visible onClicked: { if (cleartext.visible) { cleartext.visible = false qrCode.visible = false explanation.visible = true } else { cleartext.visible = true qrCode.visible = false explanation.visible = false } } } } } } diff --git a/src/qml/GlobalDrawer.qml b/src/qml/GlobalDrawer.qml index 7684bc4..a1cba70 100644 --- a/src/qml/GlobalDrawer.qml +++ b/src/qml/GlobalDrawer.qml @@ -1,129 +1,129 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 as Controls import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 import "settings" Kirigami.GlobalDrawer { id: globalDrawer title: Utils.applicationDisplayName() titleIcon: Utils.getResourcePath("images/kaidan.svg") bannerImageSource: Utils.getResourcePath("images/global-drawer-banner.svg") SettingsSheet { id: settingsSheet } topContent: [ // This item is used to disable an account temporarily. RowLayout { spacing: -4 property bool disconnected: Kaidan.connectionState === Enums.StateDisconnected property bool connected: Kaidan.connectionState === Enums.StateConnected Controls.Switch { checked: !parent.disconnected onClicked: parent.disconnected ? Kaidan.mainConnect() : Kaidan.mainDisconnect() } Text { text: { var jidAndStatus = Kaidan.jid + " (" if (parent.disconnected) jidAndStatus += qsTr("Offline"); else if (parent.connected) jidAndStatus += qsTr("Online"); else jidAndStatus += qsTr("Connecting…"); jidAndStatus += ")" return jidAndStatus } color: parent.connected ? Utils.presenceTypeToColor(Enums.PresOnline) : Utils.presenceTypeToColor(Enums.PresUnavailable) } } ] actions: [ Kirigami.Action { text: qsTr("Invite friends") icon.name: "mail-invitation" onTriggered: { Utils.copyToClipboard(Utils.invitationUrl(Kaidan.jid)) passiveNotification(qsTr("Invitation link copied to clipboard")) } }, Kirigami.Action { text: qsTr("Transfer account") icon.name: "send-to-symbolic" onTriggered: { pageStack.layers.push(accountTransferPage) } }, Kirigami.Action { text: qsTr("Delete account") icon.name: "delete" - onTriggered: pageStack.layers.push(accountDeletionPage) + onTriggered: Kaidan.serverFeaturesCache.inBandRegistrationSupported ? pageStack.layers.push(accountDeletionPage) : pageStack.layers.push(accountDeletionFromClientConfirmationPage) }, Kirigami.Action { text: qsTr("Settings") icon.name: "settings-configure" onTriggered: { // open settings page if (Kirigami.Settings.isMobile) { if (pageStack.layers.depth < 2) pageStack.layers.push(settingsPage) } else { settingsSheet.open() } } }, Kirigami.Action { text: qsTr("About") icon.name: "help-about" onTriggered: { popLayersAboveLowest() // open about sheet aboutDialog.open() } } ] } diff --git a/src/qml/LoginPage.qml b/src/qml/LoginPage.qml index 61c81d0..c7d386e 100644 --- a/src/qml/LoginPage.qml +++ b/src/qml/LoginPage.qml @@ -1,142 +1,134 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 as Controls import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 import "elements" import "elements/fields" Kirigami.Page { title: qsTr("Log in") - contextualActions: [ - Kirigami.Action { - text: qsTr("Log in using a QR-Code") - icon.name: "view-barcode" - onTriggered: pageStack.layers.push(qrCodeScannerPage) - } - ] - ColumnLayout { anchors.fill: parent Kirigami.Heading { text: qsTr("Log in to your XMPP account") wrapMode: Text.WordWrap Layout.fillWidth: true horizontalAlignment: Qt.AlignHCenter } ColumnLayout { width: parent.width Layout.fillWidth: true // For desktop or tablet devices Layout.alignment: Qt.AlignCenter Layout.maximumWidth: Kirigami.Units.gridUnit * 25 // JID field JidField { id: jidField // Simulate the pressing of the connect button. inputField { onAccepted: connectButton.clicked() } } // password field PasswordField { id: passwordField // Simulate the pressing of the connect button. inputField { onAccepted: connectButton.clicked() } } // Connect button CenteredAdaptiveHighlightedButton { id: connectButton label.text: qsTr("Connect") state: Kaidan.connectionState !== Enums.StateDisconnected ? "connecting" : "" states: [ State { name: "connecting" PropertyChanges { target: connectButton label.text: "" + qsTr("Connecting…") + "" label.color: "black" label.textFormat: Text.StyledText enabled: false } } ] // Connect to the server and authenticate by the entered credentials if the JID is valid and a password entered. onClicked: { // If the JID is invalid, focus its field. if (!jidField.valid) { jidField.forceFocus() // If the password is invalid, focus its field. // This also implies that if the JID field is focused and the password invalid, the password field will be focused instead of immediately trying to connect. } else if (!passwordField.valid) { passwordField.forceFocus() } else { Kaidan.jid = jidField.text Kaidan.password = passwordField.text Kaidan.mainConnect() } } } } // placeholder Item { Layout.preferredHeight: Kirigami.Units.gridUnit * 3 } } Component.onCompleted: { jidField.forceFocus() } Connections { target: Kaidan onConnectionErrorChanged: showPassiveNotificationForConnectionError() } } diff --git a/src/qml/QrCodeScannerPage.qml b/src/qml/QrCodeScannerPage.qml index 604eec3..b357c01 100644 --- a/src/qml/QrCodeScannerPage.qml +++ b/src/qml/QrCodeScannerPage.qml @@ -1,103 +1,234 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 +import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 as Controls +import QtQuick.Controls.Material 2.12 as Material +import QtGraphicalEffects 1.12 import QtMultimedia 5.12 import org.kde.kirigami 2.8 as Kirigami + import im.kaidan.kaidan 1.0 -// QR code scanner output and decoding for logging in by a decoded XMPP URI +import "elements" + +/** + * This page is used for logging in by scanning a QR code which contains an XMPP login URI. + */ Kirigami.Page { + id: root + title: qsTr("Scan QR code") leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 - title: qsTr("Scan QR code") + property bool cameraEnabled: false + property bool loggingIn: false - // message to be shown if no camera can be found + // hint for camera issues Kirigami.InlineMessage { - visible: { - camera.availability === Camera.Unavailable || - camera.availability === Camera.ResourceMissing - } + visible: cameraEnabled && text !== "" anchors.centerIn: parent width: 300 height: 60 - text: qsTr("There is no camera available.") + text: { + switch (camera.availability) { + case Camera.Unavailable: + case Camera.ResourceMissing: + // message to be shown if no camera can be found + return qsTr("There is no camera available.") + case Camera.Busy: + // message to be shown if the found camera is not usable + return qsTr("Your camera is busy.\nTry to close other applications using the camera.") + default: + // no message if no issue could be found + return "" + } + } } - // message to be shown if the found camera is not usable - Kirigami.InlineMessage { - visible: camera.availability === Camera.Busy - anchors.centerIn: parent - width: 300 - height: 60 - text: qsTr("Your camera is busy.\nTry to close other applications using the camera.") + // camera with continuous focus in the center of the video + Camera { + id: camera + focus.focusMode: Camera.FocusContinuous + focus.focusPointMode: Camera.FocusPointCenter + + // Show camera output if this page is visible and the camera enabled. + cameraState: { + if (visible && cameraEnabled) + return Camera.ActiveState + return Camera.LoadedState + } + + Component.onCompleted: { + scannerFilter.setCameraDefaultVideoFormat(camera); + } + } + + // filter which converts the video frames to images and decodes a containing QR code + QrCodeScannerFilter { + id: scannerFilter + + onScanningSucceeded: { + if (!loggingIn) { + // Login by the data from the decoded QR code. + loggingIn = Kaidan.logInByUri(result) + } + } + + onUnsupportedFormatReceived: { + pageStack.layers.pop() + passiveNotification(qsTr("The camera format '%1' is not supported.").arg(format)) + } } // video output from the camera which is shown on the screen and decoded by a filter VideoOutput { - id: viewfinder anchors.fill: parent fillMode: VideoOutput.PreserveAspectCrop source: camera autoOrientation: true filters: [scannerFilter] } - // filter which converst the video frames to images and decodes a containing QR code - QrCodeScannerFilter { - id: scannerFilter - onScanningSucceeded: { - pageStack.layers.pop() - // login by the data from the decoded QR code - Kaidan.loginByUri(result) + ColumnLayout { + id: loadingArea + z: 2 + anchors.centerIn: parent + visible: loggingIn + + Controls.BusyIndicator { + Layout.alignment: Qt.AlignHCenter } - onUnsupportedFormatReceived: { - pageStack.layers.pop() - passiveNotification(qsTr("The camera format '%1' is not supported.").arg(format)) + + Controls.Label { + text: "" + qsTr("Logging in…") + "" + color: Kirigami.Theme.textColor + } + + Connections { + target: Kaidan + + onConnectionStateChanged: { + if (loggingIn) { + switch (Kaidan.connectionState) { + case Enums.StateConnected: + popAllLayers() + break + case Enums.StateDisconnected: + showPassiveNotificationForConnectionError() + loggingIn = false + break + } + } + } } } - // camera with continuous focus in the center of the video - Camera { - id: camera - focus { - focusMode: Camera.FocusContinuous - focusPointMode: Camera.FocusPointCenter + Rectangle { + z: 1 + anchors.fill: loadingArea + anchors.margins: -8 + radius: 3 + opacity: 0.90 + color: Kirigami.Theme.backgroundColor + + visible: loadingArea.visible + } + + ColumnLayout { + id: explanationArea + z: 1 + anchors.margins: 18 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + CenteredAdaptiveText { + id: explanation + text: qsTr("Scan the QR code from your existing device to transfer your account.") + width: parent.width + Layout.topMargin: 10 + scaleFactor: 2 } - Component.onCompleted: { - scannerFilter.setCameraDefaultVideoFormat(camera); + Image { + source: Utils.getResourcePath("images/onboarding/account-transfer.svg") + sourceSize.height: parent.height + visible: explanation.visible + fillMode: Image.PreserveAspectFit + Layout.fillWidth: true + Layout.fillHeight: true + } + + // placeholder for the explanation text and image when they are invisible + Item { + Layout.fillHeight: true } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: largeButtonWidth + + // button for showing or hiding the explanation + CenteredAdaptiveHighlightedButton { + label.text: checked ? qsTr("Show explanation") : qsTr("Scan") + checkable: true + + onClicked: { + if (!cameraEnabled) { + camera.start() + cameraEnabled = true + } + explanation.visible = !explanation.visible + } + } + + // button for skipping the scanning + CenteredAdaptiveButton { + label.text: qsTr("Continue without QR code") + onClicked: pageStack.layers.push(registrationLoginDecisionPage) + } + } + } + + // background of the explanation area + Rectangle { + anchors.fill: explanationArea + anchors.margins: -8 + visible: explanation.visible + radius: roundedCornersRadius + opacity: 0.90 + color: Kirigami.Theme.backgroundColor } } diff --git a/src/qml/AccountDeletionFromClientConfirmationPage.qml b/src/qml/RegistrationLoginDecisionPage.qml similarity index 76% copy from src/qml/AccountDeletionFromClientConfirmationPage.qml copy to src/qml/RegistrationLoginDecisionPage.qml index 6f78f75..dd0a717 100644 --- a/src/qml/AccountDeletionFromClientConfirmationPage.qml +++ b/src/qml/RegistrationLoginDecisionPage.qml @@ -1,52 +1,55 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 import "elements" /** - * This page is used for confirming the deletion of an account from the client. + * This page is used for deciding between registration or login. */ -ConfirmationPage { - title: qsTr("Remove account from this app") +BinaryDecisionPage { + title: qsTr("Set up") - topDescription: qsTr("Your account will be removed from this app.\nYou won't be able to get your credentials back!\nMake sure that you have backed up those if you want to use your account later.") + topImageSource: Utils.getResourcePath("images/onboarding/registration.svg") + bottomImageSource: Utils.getResourcePath("images/onboarding/login.svg") topAction: Kirigami.Action { - text: qsTr("Remove") - onTriggered: { - Kaidan.deleteAccountFromClient() - } + text: qsTr("Register a new account") + onTriggered: pageStack.layers.push(registrationDecisionPage) + } + + bottomAction: Kirigami.Action { + text: qsTr("Use an existing account") + onTriggered: pageStack.layers.push(loginPage) } } diff --git a/src/qml/settings/SettingsContent.qml b/src/qml/StartPage.qml similarity index 60% copy from src/qml/settings/SettingsContent.qml copy to src/qml/StartPage.qml index 53dbcf0..2da80fe 100644 --- a/src/qml/settings/SettingsContent.qml +++ b/src/qml/StartPage.qml @@ -1,72 +1,94 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 -import QtQuick.Controls 2.12 as Controls import QtQuick.Layouts 1.12 import org.kde.kirigami 2.8 as Kirigami + import im.kaidan.kaidan 1.0 +import "elements" + /** - * The settings page contains options to configure Kaidan. + * This page is the first page. * - * It is used on a new layer on mobile and inside of a Sheet on desktop. + * It is displayed if no account is available. */ -ColumnLayout { - Controls.StackView { - Layout.fillHeight: true - Layout.fillWidth: true +Kirigami.Page { + title: "Kaidan" - id: stack - Layout.preferredHeight: currentItem.height - initialItem: settingsContent - clip: true - } + ColumnLayout { + anchors.fill: parent + + Image { + source: Utils.getResourcePath("images/kaidan.svg") + Layout.alignment: Qt.AlignCenter + Layout.fillWidth: true + Layout.fillHeight: true + fillMode: Image.PreserveAspectFit + mipmap: true + sourceSize.width: width + sourceSize.height: height + Layout.bottomMargin: Kirigami.Units.gridUnit * 1.5 + } + + CenteredAdaptiveText { + text: "Kaidan" + scaleFactor: 6 + } - Component { - id: settingsContent - Column { - height: root.height * 0.9 - spacing: 0 - SettingsItem { - name: qsTr("Change password") - description: qsTr("Changes your account's password. You will need to re-enter it on your other devices.") - onClicked: stack.push("ChangePassword.qml") - icon: "lock" + CenteredAdaptiveText { + text: qsTr("Enjoy free communication on every device!") + scaleFactor: 2 + } + + // placeholder + Item { + Layout.fillHeight: true + Layout.minimumHeight: root.height * 0.08 + } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: largeButtonWidth + + CenteredAdaptiveHighlightedButton { + id: startButton + label.text: qsTr("Let's start") + onClicked: pageStack.layers.push(qrCodeScannerPage) } - SettingsItem { - name: qsTr("Multimedia Settings") - description: qsTr("Configure photo, video and audio recording settings") - onClicked: stack.push("MultimediaSettings.qml") - icon: "settings-configure" + + // placeholder + Item { + height: startButton.height } } } } diff --git a/src/qml/elements/BinaryDecisionPage.qml b/src/qml/elements/BinaryDecisionPage.qml index 21873f6..7176cd5 100644 --- a/src/qml/elements/BinaryDecisionPage.qml +++ b/src/qml/elements/BinaryDecisionPage.qml @@ -1,118 +1,118 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 -import QtQuick.Layouts 1.3 +import QtQuick 2.12 +import QtQuick.Layouts 1.12 import org.kde.kirigami 2.8 as Kirigami /** * This page is the base of decision pages with two actions. * * Each action has an own image for describing its purpose. */ Kirigami.Page { property alias topDescription: topDescription.text property alias bottomDescription: bottomDescription.text property alias topImageSource: topImage.source property alias bottomImageSource: bottomImage.source property Kirigami.Action topAction property Kirigami.Action bottomAction property bool topActionAsMainAction: false property int descriptionMargin: 10 ColumnLayout { anchors.fill: parent ColumnLayout { Layout.maximumWidth: largeButtonWidth Layout.alignment: Qt.AlignCenter // image to show above the top action Kirigami.Icon { id: topImage Layout.fillWidth: true Layout.fillHeight: true } // description for the top action CenteredAdaptiveText { id: topDescription Layout.bottomMargin: descriptionMargin } // button for the top action CenteredAdaptiveButton { visible: !topActionAsMainAction label.text: topAction.text icon.name: topAction.icon.name onClicked: topAction.trigger() } // button for the top action as main action CenteredAdaptiveHighlightedButton { visible: topActionAsMainAction label.text: topAction.text icon.name: topAction.icon.name onClicked: topAction.trigger() } // horizontal line to separate the two actions Kirigami.Separator { Layout.fillWidth: true Layout.leftMargin: - (root.width - parent.width) / 4 Layout.rightMargin: Layout.leftMargin } // button for the bottom action CenteredAdaptiveButton { label.text: bottomAction.text icon.name: bottomAction.icon.name onClicked: bottomAction.trigger() } // description for the bottom action CenteredAdaptiveText { id: bottomDescription Layout.topMargin: descriptionMargin } // image to show below the bottom action Kirigami.Icon { id: bottomImage Layout.fillWidth: true Layout.fillHeight: true } } } } diff --git a/src/qml/elements/CenteredAdaptiveText.qml b/src/qml/elements/CenteredAdaptiveText.qml index 47a7d2c..26d63c1 100644 --- a/src/qml/elements/CenteredAdaptiveText.qml +++ b/src/qml/elements/CenteredAdaptiveText.qml @@ -1,47 +1,47 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 -import QtQuick.Layouts 1.3 +import QtQuick 2.12 +import QtQuick.Layouts 1.12 import org.kde.kirigami 2.8 as Kirigami /** * This is a centered and adaptive text. */ Text { // factor to scale the text property int scaleFactor: 1 Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap elide: Text.ElideRight font.pointSize: Kirigami.Theme.defaultFont.pointSize * scaleFactor } diff --git a/src/qml/elements/ConfirmationPage.qml b/src/qml/elements/ConfirmationPage.qml index 5290dd1..97951bd 100644 --- a/src/qml/elements/ConfirmationPage.qml +++ b/src/qml/elements/ConfirmationPage.qml @@ -1,49 +1,49 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 -import QtQuick.Layouts 1.3 +import QtQuick 2.12 +import QtQuick.Layouts 1.12 import org.kde.kirigami 2.8 as Kirigami /** * This page is the base for confirmation pages. * * It provides an adjustable description, a configurable confirmation button and a predefined cancel button. */ BinaryDecisionPage { topImageSource: "dialog-ok" bottomImageSource: "dialog-cancel" topActionAsMainAction: true bottomAction: Kirigami.Action { text: qsTr("Cancel") onTriggered: pageStack.layers.pop() } } diff --git a/src/qml/elements/DataForm.qml b/src/qml/elements/DataForm.qml new file mode 100644 index 0000000..b68236d --- /dev/null +++ b/src/qml/elements/DataForm.qml @@ -0,0 +1,114 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 +import org.kde.kirigami 2.8 as Kirigami + +import im.kaidan.kaidan 1.0 + +ColumnLayout { + id: root + + property QtObject firstVisibleTextField + property QtObject model + + // base model if model is a filter model + property QtObject baseModel: model + + property bool displayTitle: true + property bool displayInstructions: true + + Kirigami.Heading { + visible: displayTitle + text: baseModel ? baseModel.title : "" + textFormat: Text.PlainText + wrapMode: Text.WordWrap + } + + Controls.Label { + visible: displayInstructions + text: baseModel ? baseModel.instructions : "" + textFormat: Text.PlainText + wrapMode: Text.WordWrap + } + + Kirigami.FormLayout { + Repeater { + id: repeater + model: root.model + delegate: Column { + visible: model.type !== DataFormModel.HiddenField + Kirigami.FormData.label: model.label + + Loader { + id: imageLoader + } + + Kirigami.ActionTextField { + id: textField + visible: model.isRequired && (model.type === DataFormModel.TextSingleField || model.type === DataFormModel.TextPrivateField) + echoMode: model.type === DataFormModel.TextPrivateField ? TextInput.Password : TextInput.Normal + + onTextChanged: model.value = text + + Component.onCompleted: { + text = model.value + + if (!root.firstVisibleTextField && visible) + root.firstVisibleTextField = textField + } + } + + Controls.Label { + visible: !textField.visible + text: Utils.formatMessage(model.value) + textFormat: Text.StyledText + wrapMode: Text.WordWrap + onLinkActivated: Qt.openUrlExternally(link) + } + + Component { + id: imageComponent + + Image { + source: model.mediaUrl + } + } + + Component.onCompleted: { + if (model.mediaUrl) + imageLoader.sourceComponent = imageComponent + } + } + } + } +} diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/elements/fields/JidField.qml index a53d4f3..fcb6fad 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/elements/fields/JidField.qml @@ -1,49 +1,49 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 import im.kaidan.kaidan 1.0 /** * This is a JID field with a hint for invalid JIDs. */ CredentialsField { labelText: qsTr("Chat address") placeholderText: qsTr("user@example.org") inputMethodHints: Qt.ImhEmailCharactersOnly invalidHintText: qsTr("The chat address must have the form username@server") // Validate the entered JID and show a hint if it is not valid. onTextChanged: { - valid = credentialsValidator.isJidValid(text) + valid = credentialsValidator.isAccountJidValid(text) toggleHintForInvalidText() } } diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/elements/fields/RegistrationPasswordField.qml similarity index 74% copy from src/qml/elements/fields/JidField.qml copy to src/qml/elements/fields/RegistrationPasswordField.qml index a53d4f3..fe41f7f 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/elements/fields/RegistrationPasswordField.qml @@ -1,49 +1,50 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 - +import QtQuick.Layouts 1.12 import im.kaidan.kaidan 1.0 /** - * This is a JID field with a hint for invalid JIDs. + * This is a password field for registration with an option for showing the password and extras regarding registration. + * The password strength is indicated by a colored bar. + * A hint is shown for improving the password strength. */ -CredentialsField { - labelText: qsTr("Chat address") - placeholderText: qsTr("user@example.org") - inputMethodHints: Qt.ImhEmailCharactersOnly - invalidHintText: qsTr("The chat address must have the form username@server") +PasswordField { + valid: true - // Validate the entered JID and show a hint if it is not valid. - onTextChanged: { - valid = credentialsValidator.isJidValid(text) - toggleHintForInvalidText() + placeholderText: { + if (field.inputField.echoMode === TextInput.Password) + return "●".repeat(generatedPassword.length) + return generatedPassword } + + property string generatedPassword: credentialsGenerator.generatePassword() } diff --git a/src/qml/main.qml b/src/qml/main.qml index aa150f2..52d3137 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -1,166 +1,170 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 import QtQuick.Controls.Material 2.12 import org.kde.kirigami 2.8 as Kirigami import StatusBar 0.1 import im.kaidan.kaidan 1.0 import "elements" +import "registration" import "settings" Kirigami.ApplicationWindow { id: root minimumHeight: 300 minimumWidth: 280 // radius for using rounded corners readonly property int roundedCornersRadius: Kirigami.Units.smallSpacing * 1.5 readonly property int largeButtonWidth: Kirigami.Units.gridUnit * 25 StatusBar { color: Material.color(Material.Green, Material.Shade700) } // Global and Contextual Drawers globalDrawer: GlobalDrawer {} contextDrawer: Kirigami.ContextDrawer { id: contextDrawer } AboutDialog { id: aboutDialog focus: true x: (parent.width - width) / 2 y: (parent.height - height) / 2 } SubRequestAcceptSheet { id: subReqAcceptSheet } // when the window was closed, disconnect from jabber server onClosing: { Kaidan.mainDisconnect() } - // load all pages - Component {id: chatPage; ChatPage {}} + // components for all main pages + Component {id: startPage; StartPage {}} + Component {id: registrationLoginDecisionPage; RegistrationLoginDecisionPage {}} + Component {id: registrationDecisionPage; RegistrationDecisionPage {}} + Component {id: automaticRegistrationPage; AutomaticRegistrationPage {}} + Component {id: manualRegistrationPage; ManualRegistrationPage {}} Component {id: loginPage; LoginPage {}} Component {id: rosterPage; RosterPage {}} + Component {id: chatPage; ChatPage {}} Component {id: emptyChatPage; EmptyChatPage {}} Component {id: settingsPage; SettingsPage {}} Component {id: qrCodeScannerPage; QrCodeScannerPage {}} Component {id: userProfilePage; UserProfilePage {}} Component {id: accountTransferPage; AccountTransferPage {}} Component {id: accountDeletionPage; AccountDeletionPage {}} Component {id: accountDeletionFromClientConfirmationPage; AccountDeletionFromClientConfirmationPage {}} Component {id: accountDeletionFromClientAndServerConfirmationPage; AccountDeletionFromClientAndServerConfirmationPage {}} /** * Shows a passive notification for a long period. */ function passiveNotification(text) { showPassiveNotification(text, "long") } function showPassiveNotificationForConnectionError() { passiveNotification(Utils.connectionErrorMessage(Kaidan.connectionError)) } - function openLoginPage() { + function openStartPage() { globalDrawer.enabled = false globalDrawer.visible = false popLayersAboveLowest() popAllPages() - pageStack.push(loginPage) + pageStack.push(startPage) } /** * Opens the view with the roster and chat page. */ function openChatView() { globalDrawer.enabled = true + popLayersAboveLowest() popAllPages() pageStack.push(rosterPage) if (!Kirigami.Settings.isMobile) pageStack.push(emptyChatPage) } /** * Pops all layers except the layer with index 0 from the page stack. */ function popLayersAboveLowest() { while (pageStack.layers.depth > 1) pageStack.layers.pop() } /** * Pops all pages from the page stack. */ function popAllPages() { while (pageStack.depth > 0) pageStack.pop() } function handleSubRequest(from, message) { Kaidan.vCardRequested(from) subReqAcceptSheet.from = from subReqAcceptSheet.message = message subReqAcceptSheet.open() } - Component.onCompleted: { - Kaidan.passiveNotificationRequested.connect(passiveNotification) - Kaidan.newCredentialsNeeded.connect(openLoginPage) - Kaidan.loggedInWithNewCredentials.connect(openChatView) - Kaidan.subscriptionRequestReceived.connect(handleSubRequest) + Connections { + target: Kaidan + onPassiveNotificationRequested: passiveNotification(text) + onNewCredentialsNeeded: openStartPage() + onLoggedInWithNewCredentials: openChatView() + onSubscriptionRequestReceived: handleSubRequest(from, msg) + } + + Component.onCompleted: { openChatView() // Announce that the user interface is ready and the application can start connecting. Kaidan.start() } - - Component.onDestruction: { - Kaidan.passiveNotificationRequested.disconnect(passiveNotification) - Kaidan.newCredentialsNeeded.disconnect(openLoginPage) - Kaidan.loggedInWithNewCredentials.disconnect(openChatView) - Kaidan.subscriptionRequestReceived.disconnect(handleSubRequest) - } } diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc index 97fb4d8..ddebc87 100644 --- a/src/qml/qml.qrc +++ b/src/qml/qml.qrc @@ -1,64 +1,87 @@ main.qml + StartPage.qml + RegistrationLoginDecisionPage.qml RosterPage.qml LoginPage.qml ChatPageBase.qml ChatPage.qml EmptyChatPage.qml AboutDialog.qml GlobalDrawer.qml QrCodeScannerPage.qml UserProfilePage.qml AccountDeletionPage.qml AccountDeletionFromClientConfirmationPage.qml AccountDeletionFromClientAndServerConfirmationPage.qml AccountTransferPage.qml elements/SubRequestAcceptSheet.qml elements/RosterAddContactSheet.qml elements/RosterRenameContactSheet.qml elements/RosterRemoveContactSheet.qml elements/RosterListItem.qml elements/MessageCounter.qml elements/ChatMessage.qml elements/RoundedImage.qml elements/RoundImage.qml elements/Button.qml elements/CenteredAdaptiveButton.qml elements/CenteredAdaptiveHighlightedButton.qml elements/CenteredAdaptiveText.qml elements/FileChooser.qml elements/SendMediaSheet.qml elements/BinaryDecisionPage.qml elements/ConfirmationPage.qml + elements/DataForm.qml elements/MediaPreview.qml elements/MediaPreviewImage.qml elements/MediaPreviewAudio.qml elements/MediaPreviewVideo.qml elements/MediaPreviewOther.qml elements/MediaPreviewLocation.qml elements/MediaPreviewLoader.qml elements/NewMedia.qml elements/NewMediaLocation.qml elements/NewMediaLoader.qml elements/EmojiPicker.qml elements/TextAvatar.qml elements/Avatar.qml elements/fields/Field.qml elements/fields/CredentialsField.qml elements/fields/JidField.qml elements/fields/PasswordField.qml + elements/fields/RegistrationPasswordField.qml + + registration/AutomaticRegistrationPage.qml + registration/CustomFormView.qml + registration/CustomFormViewManualRegistration.qml + registration/CustomFormViewAutomaticRegistration.qml + registration/DisplayNameView.qml + registration/FieldView.qml + registration/LoadingView.qml + registration/ManualRegistrationPage.qml + registration/NavigationBar.qml + registration/PasswordView.qml + registration/RegistrationButton.qml + registration/RegistrationDecisionPage.qml + registration/RegistrationPage.qml + registration/ResultView.qml + registration/ServerView.qml + registration/UsernameView.qml + registration/View.qml + registration/WebRegistrationView.qml settings/SettingsItem.qml settings/SettingsPage.qml settings/SettingsSheet.qml settings/SettingsContent.qml settings/ChangePassword.qml settings/MultimediaSettings.qml diff --git a/src/qml/registration/AutomaticRegistrationPage.qml b/src/qml/registration/AutomaticRegistrationPage.qml new file mode 100644 index 0000000..cab2ba0 --- /dev/null +++ b/src/qml/registration/AutomaticRegistrationPage.qml @@ -0,0 +1,236 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 as Controls +import org.kde.kirigami 2.8 as Kirigami + +import im.kaidan.kaidan 1.0 +import "../elements" + +/** + * This page is used for the automatic registration. + * + * Everything that can be automated is automated. + * The server is randomly selected. + * The username and password are randomly generated. + * Therefore, only custom information requested by the server is shown. + * Only required information must be entered to register an account. + */ +RegistrationPage { + title: qsTr("Register automatically") + + property int maximumAttemptsForRequestingRegistrationFormFromAnotherServer: 3 + property int remainingAttemptsForRequestingRegistrationFormFromAnotherServer: maximumAttemptsForRequestingRegistrationFormFromAnotherServer + + property int maximumRegistrationAttemptsAfterUsernameConflictOccurred: 2 + property int remainingRegistrationAttemptsAfterUsernameConflictOccurred: maximumRegistrationAttemptsAfterUsernameConflictOccurred + + property int maximumRegistrationAttemptsAfterCaptchaVerificationFailedOccurred: 2 + property int remainingRegistrationAttemptsAfterCaptchaVerificationFailedOccurred: maximumRegistrationAttemptsAfterCaptchaVerificationFailedOccurred + + property int maximumRegistrationAttemptsAfterRequiredInformationMissingOccurred: 2 + property int remainingRegistrationAttemptsAfterRequiredInformationMissingOccurred: maximumRegistrationAttemptsAfterRequiredInformationMissingOccurred + + ServerListModel { + id: serverListModel + } + + ColumnLayout { + anchors.fill: parent + + Controls.StackView { + id: stackView + clip: true + Layout.fillWidth: true + Layout.fillHeight: true + initialItem: loadingViewComponent + } + } + + Component { id: loadingViewComponent; LoadingView {} } + + Component { id: customFormViewComponent; CustomFormViewAutomaticRegistration {} } + + Component.onCompleted: { + chooseServerRandomly() + chooseUsernameRandomly() + choosePasswordRandomly() + requestRegistrationForm() + } + + Connections { + target: Kaidan + + onRegistrationFormReceived: { + formModel = dataFormModel + formFilterModel.sourceModel = dataFormModel + + // If the received registration data form does not contain custom fields, request the registration directly. + // Otherwise, remove the loading view to show the custom form view. + if (!customFormFieldsAvailable()) + sendRegistrationForm() + else + removeLoadingView() + } + + onConnectionErrorChanged: { + showPassiveNotificationForConnectionError() + removeLoadingView() + popLayerIfNoCustomFormFieldsAvailable() + } + + onRegistrationFailed: { + switch (error) { + case RegistrationManager.InBandRegistrationNotSupported: + requestRegistrationFormFromAnotherServer(errrorMessage) + break + case RegistrationManager.UsernameConflict: + if (remainingRegistrationAttemptsAfterUsernameConflictOccurred > 0) { + remainingRegistrationAttemptsAfterUsernameConflictOccurred-- + // Try to register again with another username on the same server. + chooseUsernameRandomly() + requestRegistrationForm() + } else { + remainingRegistrationAttemptsAfterUsernameConflictOccurred = maximumRegistrationAttemptsAfterUsernameConflictOccurred + requestRegistrationFormFromAnotherServer(errrorMessage) + } + break + case RegistrationManager.CaptchaVerificationFailed: + if (remainingRegistrationAttemptsAfterCaptchaVerificationFailedOccurred > 0) { + remainingRegistrationAttemptsAfterCaptchaVerificationFailedOccurred-- + requestRegistrationForm() + showPassiveNotificationForCaptchaVerificationFailedError() + } else { + remainingRegistrationAttemptsAfterCaptchaVerificationFailedOccurred = maximumRegistrationAttemptsAfterCaptchaVerificationFailedOccurred + requestRegistrationFormFromAnotherServer(errrorMessage) + } + break + case RegistrationManager.RequiredInformationMissing: + if (remainingRegistrationAttemptsAfterRequiredInformationMissingOccurred > 0 && customFormFieldsAvailable()) { + remainingRegistrationAttemptsAfterRequiredInformationMissingOccurred-- + requestRegistrationForm() + showPassiveNotificationForRequiredInformationMissingError(errrorMessage) + } else { + remainingRegistrationAttemptsAfterRequiredInformationMissingOccurred = remainingRegistrationAttemptsAfterRequiredInformationMissingOccurred + requestRegistrationFormFromAnotherServer(errrorMessage) + } + break + default: + requestRegistrationFormFromAnotherServer(errrorMessage) + } + } + } + + // Simulate the pressing of the confirmation button. + Keys.onPressed: { + if (stackView.currentItem.registrationButton && (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)) + stackView.currentItem.registrationButton.clicked() + } + + /** + * Adds the loading view to the stack view. + */ + function addLoadingView() { + stackView.push(loadingViewComponent) + } + + /** + * Removes the loading view from the stack view. + */ + function removeLoadingView() { + // Push the custom form view on the stack view if the loading view is the only item on it. + // That is the case during the first registration form request. + // When the loading view was pushed on the stack view after submitting custom form fields and the registration fails or a connection error occurs, that loading view is popped to show the custom form view again. + if (stackView.depth === 1) { + stackView.push(customFormViewComponent) + stackView.currentItem.registrationButton.parent = stackView.currentItem.contentArea + } else { + stackView.pop() + } + } + + /** + * Sets a randomly chosen server for registration. + */ + function chooseServerRandomly() { + server = serverListModel.data(serverListModel.randomlyChooseIndex(), ServerListModel.JidRole) + } + + /** + * Sets a randomly generated username for registration. + */ + function chooseUsernameRandomly() { + username = credentialsGenerator.generateUsername() + } + + /** + * Sets a randomly generated password for registration. + */ + function choosePasswordRandomly() { + password = credentialsGenerator.generatePassword() + } + + /** + * Requests a registration form from another randomly chosen server if an error occurred. + * + * That is done multiple times and after a maximum number of attempts, the user has to intervene. + * + * @param errrorMessage message describing the error + */ + function requestRegistrationFormFromAnotherServer(errrorMessage) { + if (remainingAttemptsForRequestingRegistrationFormFromAnotherServer > 0) { + remainingAttemptsForRequestingRegistrationFormFromAnotherServer-- + chooseServerRandomly() + requestRegistrationForm() + } else { + remainingAttemptsForRequestingRegistrationFormFromAnotherServer = maximumAttemptsForRequestingRegistrationFormFromAnotherServer + showPassiveNotificationForUnknownError(errrorMessage) + popLayerIfNoCustomFormFieldsAvailable() + } + } + + /** + * Sends the completed registration form and adds the loading view. + */ + function sendRegistrationFormAndShowLoadingView() { + sendRegistrationForm() + addLoadingView() + } + + /** + * Pops this layer if the received registration form does not contain any custom field. + */ + function popLayerIfNoCustomFormFieldsAvailable() { + if (!customFormFieldsAvailable()) + pageStack.layers.pop() + } +} diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/registration/CustomFormView.qml similarity index 70% copy from src/qml/elements/fields/JidField.qml copy to src/qml/registration/CustomFormView.qml index a53d4f3..1112df2 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/registration/CustomFormView.qml @@ -1,49 +1,54 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls -import im.kaidan.kaidan 1.0 +import "../elements" /** - * This is a JID field with a hint for invalid JIDs. + * This is the base for views used entering the values into the custom fields of the received registration form. */ -CredentialsField { - labelText: qsTr("Chat address") - placeholderText: qsTr("user@example.org") - inputMethodHints: Qt.ImhEmailCharactersOnly - invalidHintText: qsTr("The chat address must have the form username@server") +View { + descriptionText: qsTr("The server has requested more information.\nNot everything may be required.\nIf a CAPTCHA is shown, the displayed text must be entered.") + imageSource: "custom-form" - // Validate the entered JID and show a hint if it is not valid. - onTextChanged: { - valid = credentialsValidator.isJidValid(text) - toggleHintForInvalidText() + DataForm { + id: registrationForm + parent: contentArea + model: formFilterModel + baseModel: formModel + displayTitle: false + displayInstructions: false + } + + function forceFocus() { + registrationForm.firstVisibleTextField.forceActiveFocus() } } diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/registration/CustomFormViewAutomaticRegistration.qml similarity index 75% copy from src/qml/elements/fields/JidField.qml copy to src/qml/registration/CustomFormViewAutomaticRegistration.qml index a53d4f3..d10e00f 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/registration/CustomFormViewAutomaticRegistration.qml @@ -1,49 +1,48 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.12 - -import im.kaidan.kaidan 1.0 +import QtQuick.Controls 2.12 as Controls /** - * This is a JID field with a hint for invalid JIDs. + * This view is used for entering the values into the custom fields of the received registration form during the automatic registration. */ -CredentialsField { - labelText: qsTr("Chat address") - placeholderText: qsTr("user@example.org") - inputMethodHints: Qt.ImhEmailCharactersOnly - invalidHintText: qsTr("The chat address must have the form username@server") +CustomFormView { + property alias registrationButton: registrationButton + + RegistrationButton { + id: registrationButton + } - // Validate the entered JID and show a hint if it is not valid. - onTextChanged: { - valid = credentialsValidator.isJidValid(text) - toggleHintForInvalidText() + // This is used for automatically focusing the first field of the form. + Controls.StackView.onStatusChanged: { + if (Controls.StackView.status === Controls.StackView.Active) + forceFocus() } } diff --git a/src/qml/elements/CenteredAdaptiveText.qml b/src/qml/registration/CustomFormViewManualRegistration.qml similarity index 79% copy from src/qml/elements/CenteredAdaptiveText.qml copy to src/qml/registration/CustomFormViewManualRegistration.qml index 47a7d2c..4e89a45 100644 --- a/src/qml/elements/CenteredAdaptiveText.qml +++ b/src/qml/registration/CustomFormViewManualRegistration.qml @@ -1,47 +1,43 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 -import QtQuick.Layouts 1.3 -import org.kde.kirigami 2.8 as Kirigami +import QtQuick.Controls 2.12 as Controls /** - * This is a centered and adaptive text. + * This view is used for entering the values into the custom fields of the received registration form during the manual registration. */ -Text { - // factor to scale the text - property int scaleFactor: 1 - - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - elide: Text.ElideRight - font.pointSize: Kirigami.Theme.defaultFont.pointSize * scaleFactor +CustomFormView { + // This is used for automatically focusing the first field of the form. + Controls.SwipeView.onIsCurrentItemChanged: { + if (Controls.SwipeView.isCurrentItem) { + forceFocus() + } + } } diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/registration/DisplayNameView.qml similarity index 75% copy from src/qml/elements/fields/JidField.qml copy to src/qml/registration/DisplayNameView.qml index a53d4f3..ae5df2c 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/registration/DisplayNameView.qml @@ -1,49 +1,49 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.12 - import im.kaidan.kaidan 1.0 +import "../elements/fields" + /** - * This is a JID field with a hint for invalid JIDs. + * This view is used for entering the display name. */ -CredentialsField { - labelText: qsTr("Chat address") - placeholderText: qsTr("user@example.org") - inputMethodHints: Qt.ImhEmailCharactersOnly - invalidHintText: qsTr("The chat address must have the form username@server") +FieldView { + descriptionText: qsTr("Your display name is what your contacts will see.\nIn case you don't want to use a display name, you can leave this field empty.\nUsing a display name may let new contacts recognize you easier but also could decrease your privacy!") + imageSource: "display-name" + + property alias text: field.text - // Validate the entered JID and show a hint if it is not valid. - onTextChanged: { - valid = credentialsValidator.isJidValid(text) - toggleHintForInvalidText() + Field { + id: field + parent: contentArea + labelText: qsTr("Display Name") } } diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/registration/FieldView.qml similarity index 71% copy from src/qml/elements/fields/JidField.qml copy to src/qml/registration/FieldView.qml index a53d4f3..45331af 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/registration/FieldView.qml @@ -1,49 +1,65 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls -import im.kaidan.kaidan 1.0 +import "../elements/fields" /** - * This is a JID field with a hint for invalid JIDs. + * This view is the base for views containing fields. */ -CredentialsField { - labelText: qsTr("Chat address") - placeholderText: qsTr("user@example.org") - inputMethodHints: Qt.ImhEmailCharactersOnly - invalidHintText: qsTr("The chat address must have the form username@server") +View { + /** + * Disallows the swiping and shows or hides the hint for invalid text input. + */ + function handleInvalidText() { + swipeView.interactive = valid + field.toggleHintForInvalidText() + } + + function forceFocus() { + field.forceFocus() + } + + Controls.SwipeView.onIsCurrentItemChanged: { + if (Controls.SwipeView.isCurrentItem) { + if (!field.focus) { + forceFocus() + } + } + } - // Validate the entered JID and show a hint if it is not valid. - onTextChanged: { - valid = credentialsValidator.isJidValid(text) - toggleHintForInvalidText() + Component.onCompleted: { + if (Controls.SwipeView.isCurrentItem) { + forceFocus() + } } } diff --git a/src/qml/elements/ConfirmationPage.qml b/src/qml/registration/LoadingView.qml similarity index 76% copy from src/qml/elements/ConfirmationPage.qml copy to src/qml/registration/LoadingView.qml index 5290dd1..28991a0 100644 --- a/src/qml/elements/ConfirmationPage.qml +++ b/src/qml/registration/LoadingView.qml @@ -1,49 +1,51 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 -import QtQuick.Layouts 1.3 -import org.kde.kirigami 2.8 as Kirigami +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 /** - * This page is the base for confirmation pages. + * This view shows a busy indicator. * - * It provides an adjustable description, a configurable confirmation button and a predefined cancel button. + * It is displayed during network interaction. */ -BinaryDecisionPage { - topImageSource: "dialog-ok" - bottomImageSource: "dialog-cancel" - topActionAsMainAction: true +View { + descriptionText: qsTr("Requesting the server...") - bottomAction: Kirigami.Action { - text: qsTr("Cancel") - onTriggered: pageStack.layers.pop() + Controls.BusyIndicator { + parent: contentArea + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.fillHeight: true + Layout.maximumWidth: parent.width * 0.2 + Layout.maximumHeight: Layout.maximumWidth } } diff --git a/src/qml/registration/ManualRegistrationPage.qml b/src/qml/registration/ManualRegistrationPage.qml new file mode 100644 index 0000000..7c0875a --- /dev/null +++ b/src/qml/registration/ManualRegistrationPage.qml @@ -0,0 +1,408 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 as Controls + +import im.kaidan.kaidan 1.0 + +/** + * This page is used for the manual registration. + * + * Everything can be manually chosen. + * In case of no input, random values are used. + * Only required information must be entered to create an account. + */ +RegistrationPage { + title: qsTr("Register manually") + + // These views are created and inserted into the swipe view dynamically. + // When they are not required, they are removed. + // The default ones are added to the SwipeView via Component.onCompleted. + property WebRegistrationView webRegistrationView + property LoadingView loadingView + property UsernameView usernameView + property PasswordView passwordView + property CustomFormViewManualRegistration customFormView + property ResultView resultView + + property bool loadingViewActive: loadingView ? loadingView.Controls.SwipeView.isCurrentItem : false + property bool jumpingToViewsEnabled: !(loadingViewActive || ((usernameView && usernameView.Controls.SwipeView.isCurrentItem || passwordView && passwordView.Controls.SwipeView.isCurrentItem) && !swipeView.currentItem.valid)) + property bool registrationErrorOccurred: false + property bool connectionErrorOccurred: false + + property alias displayName: displayNameView.text + + server: serverView.text + username: usernameView ? usernameView.text : "" + password: passwordView ? passwordView.text : "" + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Controls.SwipeView { + id: swipeView + interactive: !loadingViewActive + Layout.fillWidth: true + Layout.fillHeight: true + property int lastIndex: 0 + + onCurrentIndexChanged: { + if (connectionErrorOccurred) { + connectionErrorOccurred = false + } else if (!loadingViewActive && currentIndex > lastIndex && currentIndex === serverView.Controls.SwipeView.index + 1 && !webRegistrationView) { + addLoadingView(currentIndex) + requestRegistrationForm() + } + + lastIndex = currentIndex + } + + DisplayNameView { + id: displayNameView + } + + ServerView { + id: serverView + } + + // All dynamically loaded views are inserted here when needed. + } + + NavigationBar { + id: navigationBar + } + } + + Component {id: webRegistrationViewComponent; WebRegistrationView {}} + Component {id: loadingViewComponent; LoadingView {}} + Component {id: usernameViewComponent; UsernameView {}} + Component {id: passwordViewComponent; PasswordView {}} + Component {id: customFormViewComponent; CustomFormViewManualRegistration {}} + Component {id: resultViewComponent; ResultView {}} + + Component.onCompleted: addDynamicallyLoadedInBandRegistrationViews() + + Connections { + target: Kaidan + + onRegistrationFormReceived: { + formModel = dataFormModel + formFilterModel.sourceModel = dataFormModel + + var indexToInsert = loadingView.Controls.SwipeView.index + + // There are three cases here: + // + // 1. The server did not include a "username" field. + // The username view needs to be removed. + if (!formModel.hasUsernameField()) { + swipeView.removeItem(usernameView) + // 2. The server did include a "username" field, but the server selected before did not include it and the username view has been removed. + // The view needs to be added again. + } else if (!usernameView) { + addUsernameView(++indexToInsert) + // 3. The server did include a "username" field and the username view is already loaded. + } else { + indexToInsert++ + } + + // Same logic as for the username view. See above. + if (!formModel.hasPasswordField()) { + swipeView.removeItem(passwordView) + } else if (!passwordView) { + addPasswordView(++indexToInsert) + } else { + indexToInsert++ + } + + // Same logic as for the username view. See above. + if (!customFormFieldsAvailable()) { + swipeView.removeItem(customFormView) + } else if (!customFormView) { + addCustomFormView(++indexToInsert) + } else { + indexToInsert++ + } + + // Only jump to the next view if the registration form was not loaded because a registration error occurred. + // Depending on the error, the swipe view jumps to a particular view (see onRegistrationFailed). + if (registrationErrorOccurred) + registrationErrorOccurred = false + else if (serverView.Controls.SwipeView.isPreviousItem) + jumpToNextView() + + removeLoadingView() + focusFieldViews() + } + + onConnectionErrorChanged: { + connectionErrorOccurred = true + showPassiveNotificationForConnectionError() + jumpToPreviousView() + removeLoadingView() + } + + // Depending on the error, the swipe view jumps to the view where the input should be corrected. + // For all remaining errors, the swipe view jumps to the server view. + onRegistrationFailed: { + registrationErrorOccurred = true + + switch(error) { + case RegistrationManager.InBandRegistrationNotSupported: + handleInBandRegistrationNotSupportedError() + break + case RegistrationManager.UsernameConflict: + requestRegistrationForm() + handleUsernameConflictError() + jumpToView(usernameView) + break + case RegistrationManager.PasswordTooWeak: + requestRegistrationForm() + passiveNotification(qsTr("The server requires a stronger password.")) + jumpToView(passwordView) + break + case RegistrationManager.CaptchaVerificationFailed: + requestRegistrationForm() + showPassiveNotificationForCaptchaVerificationFailedError() + jumpToView(customFormView) + break + case RegistrationManager.RequiredInformationMissing: + requestRegistrationForm() + if (customFormView) { + showPassiveNotificationForRequiredInformationMissingError(errrorMessage) + jumpToView(customFormView) + } else { + showPassiveNotificationForUnknownError(errrorMessage) + } + break + default: + requestRegistrationForm() + showPassiveNotificationForUnknownError(errrorMessage) + jumpToView(serverView) + } + } + } + + // Simulate the pressing of the currently clickable confirmation button. + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + if (resultView && resultView.Controls.SwipeView.isCurrentItem) + resultView.registrationButton.clicked() + else if (jumpingToViewsEnabled) + navigationBar.nextButton.clicked() + } + } + + /** + * Shows a passive notification if the server does not support In-Band Registration. + * If the server supports web registration, the corresponding view is opened. + * If the server does not support web registration and it is not a custom server, another one is automatically selected. + */ + function handleInBandRegistrationNotSupportedError() { + var notificationText = serverView.customServerSelected ? qsTr("The server does not support registration via this app.") : qsTr("The server does currently not support registration via this app.") + + if (serverView.inBandRegistrationSupported) { + addWebRegistrationView() + notificationText += " " + qsTr("But you can use the server's web registration.") + } else { + if (!serverView.customServerSelected) { + serverView.selectServerRandomly() + notificationText += " " + qsTr("A new server has been randomly selected.") + } + + jumpToPreviousView() + } + + passiveNotification(notificationText) + removeLoadingView() + } + + /** + * Shows a passive notification if a username is already taken on the server. + * If a random username was used for registration, a new one is generated. + */ + function handleUsernameConflictError() { + var notificationText = qsTr("The username is already taken.") + + if (usernameView.enteredText.length === 0) { + usernameView.regenerateUsername() + notificationText += " " + qsTr("A new random username has been generated.") + } + + passiveNotification(notificationText) + } + + /** + * Focuses the input field of the currently shown field view. + * + * This is necessary to execute after a registration form is received because the normal focussing within FieldView does not work then. + */ + function focusFieldViews() { + if (swipeView.currentItem === usernameView || swipeView.currentIndex === passwordView || swipeView.currentIndex === customFormView) + swipeView.currentItem.forceFocus() + } + + /** + * Adds the web registration view to the swipe view. + */ + function addWebRegistrationView() { + removeDynamicallyLoadedInBandRegistrationViews() + + webRegistrationView = webRegistrationViewComponent.createObject(webRegistrationView) + swipeView.insertItem(serverView.Controls.SwipeView.index + 1, webRegistrationView) + } + + /** + * Removes the web registration view from the swipe view. + */ + function removeWebRegistrationView() { + if (webRegistrationView) { + swipeView.removeItem(webRegistrationView) + addDynamicallyLoadedInBandRegistrationViews() + } + } + + /** + * Adds the dynamically loaded views used for the In-Band Registration to the swipe view. + */ + function addDynamicallyLoadedInBandRegistrationViews() { + var indexToInsert = serverView.Controls.SwipeView.index + + addUsernameView(++indexToInsert) + addPasswordView(++indexToInsert) + addCustomFormView(++indexToInsert) + addResultView(++indexToInsert) + } + + /** + * Removes the dynamically loaded views from the swipe view. + */ + function removeDynamicallyLoadedInBandRegistrationViews() { + for (var view of [usernameView, passwordView, customFormView, resultView]) { + swipeView.removeItem(view) + } + } + + /** + * Adds the loading view to the swipe view. + * + * @param index index of the swipe view at which the loading view will be inserted + */ + function addLoadingView(index) { + loadingView = loadingViewComponent.createObject(swipeView) + swipeView.insertItem(index, loadingView) + } + + /** + * Removes the loading view from the swipe view after jumping to the next page. + */ + function removeLoadingView() { + swipeView.removeItem(loadingView) + } + + /** + * Adds the username view to the swipe view. + * + * @param index position in the swipe view to insert the username view + */ + function addUsernameView(index) { + usernameView = usernameViewComponent.createObject(swipeView) + swipeView.insertItem(index, usernameView) + } + + /** + * Adds the password view to the swipe view. + * + * @param index position in the swipe view to insert the password view + */ + function addPasswordView(index) { + passwordView = passwordViewComponent.createObject(swipeView) + swipeView.insertItem(index, passwordView) + } + + /** + * Adds the custom form view to the swipe view. + * + * @param index position in the swipe view to insert the custom form view + */ + function addCustomFormView(index) { + customFormView = customFormViewComponent.createObject(swipeView) + swipeView.insertItem(index, customFormView) + } + + /** + * Adds the result view to the swipe view. + * + * @param index position in the swipe view to insert the result view + */ + function addResultView(index) { + resultView = resultViewComponent.createObject(swipeView) + swipeView.insertItem(index, resultView) + } + + /** + * Jumps to the previous view. + */ + function jumpToPreviousView() { + swipeView.decrementCurrentIndex() + } + + /** + * Jumps to the next view. + */ + function jumpToNextView() { + swipeView.incrementCurrentIndex() + } + + /** + * Jumps to a given view. + * + * @param view view to be jumped to + */ + function jumpToView(view) { + swipeView.setCurrentIndex(view.Controls.SwipeView.index) + } + + /** + * Requests a registration and shows the loading view. + */ + function sendRegistrationFormAndShowLoadingView() { + addLoadingView(swipeView.currentIndex + 1) + jumpToNextView() + + Kaidan.changeDisplayName(displayName) + sendRegistrationForm() + } +} diff --git a/src/qml/registration/NavigationBar.qml b/src/qml/registration/NavigationBar.qml new file mode 100644 index 0000000..fa6a3df --- /dev/null +++ b/src/qml/registration/NavigationBar.qml @@ -0,0 +1,108 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Controls.Material 2.12 as Material +import org.kde.kirigami 2.8 as Kirigami + +/** + * This is the navigation bar for the swipe view. + * + * It contains buttons for jumping to the previous and the next view. + * In between the navigation buttons there is an indicator for the current view. + */ +RowLayout { + Layout.fillWidth: true + Layout.margins: 15 + + property alias nextButton: nextButton + + // button for jumping to the previous view + Controls.RoundButton { + id: previousButton + Layout.alignment: Qt.AlignLeft + icon.name: "go-previous-symbolic" + highlighted: true + visible: swipeView.currentIndex !== 0 + enabled: jumpingToViewsEnabled + onClicked: jumpToPreviousView() + } + + // placeholder for the previous button when it is invisible + Item { + width: previousButton.width + height: previousButton.height + visible: !previousButton.visible + } + + // placeholder + Item { + Layout.fillWidth: true + width: { + if (previousButton.visible) + return previousButton.width + } + } + + // indicator for showing the current postion (index) of the siwpe view + Controls.PageIndicator { + id: indicator + Layout.alignment: Qt.AlignCenter + + count: swipeView.count + currentIndex: swipeView.currentIndex + } + + // placeholder + Item { + Layout.fillWidth: true + } + + // placeholder for the next button when it is invisible + Item { + width: nextButton.width + height: nextButton.height + visible: !nextButton.visible + } + + // button for jumping to the next view + Controls.RoundButton { + id: nextButton + Layout.alignment: Qt.AlignRight + icon.name: "go-next-symbolic" + highlighted: true + Kirigami.Theme.backgroundColor: Material.accent + visible: swipeView.currentIndex !== (swipeView.count - 1) + enabled: jumpingToViewsEnabled + onClicked: jumpToNextView() + } +} diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/registration/PasswordView.qml similarity index 64% copy from src/qml/elements/fields/JidField.qml copy to src/qml/registration/PasswordView.qml index a53d4f3..63be19e 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/registration/PasswordView.qml @@ -1,49 +1,67 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 as Controls import im.kaidan.kaidan 1.0 +import "../elements/fields" + /** - * This is a JID field with a hint for invalid JIDs. + * This view is used for entering a password. */ -CredentialsField { - labelText: qsTr("Chat address") - placeholderText: qsTr("user@example.org") - inputMethodHints: Qt.ImhEmailCharactersOnly - invalidHintText: qsTr("The chat address must have the form username@server") - - // Validate the entered JID and show a hint if it is not valid. - onTextChanged: { - valid = credentialsValidator.isJidValid(text) - toggleHintForInvalidText() +FieldView { + descriptionText: qsTr("Your password is used to log in to your account.\nIf you don't enter a password, the randomly generated and already displayed one is used.\nDon't use passwords your're already using somewhere else!") + imageSource: "password" + + property string text: field.text.length > 0 ? field.text : field.generatedPassword + + property alias valid: field.valid + + ColumnLayout { + parent: contentArea + + RegistrationPasswordField { + id: field + + // Validate the entered password and handle that if it is invalid. + onTextChanged: { + if (text === "") + valid = true + else + valid = credentialsValidator.isPasswordValid(text) + + handleInvalidText() + } + } } } diff --git a/src/qml/elements/CenteredAdaptiveText.qml b/src/qml/registration/RegistrationButton.qml similarity index 79% copy from src/qml/elements/CenteredAdaptiveText.qml copy to src/qml/registration/RegistrationButton.qml index 47a7d2c..b52d63d 100644 --- a/src/qml/elements/CenteredAdaptiveText.qml +++ b/src/qml/registration/RegistrationButton.qml @@ -1,47 +1,39 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 -import QtQuick.Layouts 1.3 -import org.kde.kirigami 2.8 as Kirigami +import "../elements" /** - * This is a centered and adaptive text. + * This button is used for confirming the registration. */ -Text { - // factor to scale the text - property int scaleFactor: 1 - - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - elide: Text.ElideRight - font.pointSize: Kirigami.Theme.defaultFont.pointSize * scaleFactor +CenteredAdaptiveHighlightedButton { + label.text: qsTr("Register") + onClicked: sendRegistrationFormAndShowLoadingView() } diff --git a/src/qml/AccountDeletionFromClientConfirmationPage.qml b/src/qml/registration/RegistrationDecisionPage.qml similarity index 73% copy from src/qml/AccountDeletionFromClientConfirmationPage.qml copy to src/qml/registration/RegistrationDecisionPage.qml index 6f78f75..b96faec 100644 --- a/src/qml/AccountDeletionFromClientConfirmationPage.qml +++ b/src/qml/registration/RegistrationDecisionPage.qml @@ -1,52 +1,55 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 -import "elements" +import "../elements" /** - * This page is used for confirming the deletion of an account from the client. + * This page is used for deciding between the automatic or manual registration. */ -ConfirmationPage { - title: qsTr("Remove account from this app") +BinaryDecisionPage { + title: qsTr("Register") - topDescription: qsTr("Your account will be removed from this app.\nYou won't be able to get your credentials back!\nMake sure that you have backed up those if you want to use your account later.") + topImageSource: Utils.getResourcePath("images/onboarding/automatic-registration.svg") + bottomImageSource: Utils.getResourcePath("images/onboarding/manual-registration.svg") topAction: Kirigami.Action { - text: qsTr("Remove") - onTriggered: { - Kaidan.deleteAccountFromClient() - } + text: qsTr("Generate an account automatically") + onTriggered: pageStack.layers.push(automaticRegistrationPage) + } + + bottomAction: Kirigami.Action { + text: qsTr("Create an account manually") + onTriggered: pageStack.layers.push(manualRegistrationPage) } } diff --git a/src/qml/registration/RegistrationPage.qml b/src/qml/registration/RegistrationPage.qml new file mode 100644 index 0000000..83e3a52 --- /dev/null +++ b/src/qml/registration/RegistrationPage.qml @@ -0,0 +1,128 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +import QtQuick 2.12 +import org.kde.kirigami 2.8 as Kirigami + +import im.kaidan.kaidan 1.0 + +/** + * This is the base of a registration page. + */ +Kirigami.Page { + // This model contains all fields from the registration form of the requested server. + property DataFormModel formModel + + // This model only contains the custom fields from the registration form of the requested server. + // It may contain e.g. a CAPTCHA or an email address. + // The server may not use the standard way for requesting the username and the password. + // In that case, this model could also include fields for those values. + property alias formFilterModel: formFilterModel + + // generator for random usernames and passwords + property alias credentialsGenerator: credentialsGenerator + + // JID of the server from whom the registration form is requested + property string server + + // username of the user to be registered + property string username + + // password of the user to be registered + property string password + + leftPadding: 0 + topPadding: 0 + rightPadding: 0 + bottomPadding: 0 + + RegistrationDataFormFilterModel { + id: formFilterModel + } + + CredentialsGenerator { + id: credentialsGenerator + } + + /** + * Shows a passive notification if the CAPTCHA verification failed. + */ + function showPassiveNotificationForCaptchaVerificationFailedError() { + passiveNotification(qsTr("CAPTCHA input was wrong")) + } + + /** + * Shows a passive notification if required information is missing. + * + * @param errrorMessage text describing the error and the required missing information + */ + function showPassiveNotificationForRequiredInformationMissingError(errrorMessage) { + passiveNotification(qsTr("Required information is missing: ") + errrorMessage) + } + + /** + * Shows a passive notification for an unknown error. + * + * @param errrorMessage text describing the error + */ + function showPassiveNotificationForUnknownError(errrorMessage) { + passiveNotification(qsTr("Registration failed:") + " " + errrorMessage) + } + + /** + * Returns true if the registration form received from the server contains custom fields. + */ + function customFormFieldsAvailable() { + return formFilterModel.rowCount() > 0 + } + + /** + * Requests a registration form from the server. + */ + function requestRegistrationForm() { + // Set the server's JID. + Kaidan.jid = server + + // Request a registration form. + Kaidan.requestRegistrationForm() + } + + /** + * Sends the completed registration form to the server. + */ + function sendRegistrationForm() { + if (formModel.hasUsernameField()) + formModel.setUsername(username) + if (formModel.hasPasswordField()) + formModel.setPassword(password) + + Kaidan.sendRegistrationForm() + } +} diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/registration/ResultView.qml similarity index 70% copy from src/qml/elements/fields/JidField.qml copy to src/qml/registration/ResultView.qml index a53d4f3..ede7396 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/registration/ResultView.qml @@ -1,49 +1,62 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 +import QtQuick.Layouts 1.12 import im.kaidan.kaidan 1.0 +import "../elements" + /** - * This is a JID field with a hint for invalid JIDs. + * This view is used for presenting the result of the registration process and confirming the registration. */ -CredentialsField { - labelText: qsTr("Chat address") - placeholderText: qsTr("user@example.org") - inputMethodHints: Qt.ImhEmailCharactersOnly - invalidHintText: qsTr("The chat address must have the form username@server") - - // Validate the entered JID and show a hint if it is not valid. - onTextChanged: { - valid = credentialsValidator.isJidValid(text) - toggleHintForInvalidText() +View { + descriptionText: jid ? qsTr("Your chat address is used to chat with you, like your email address is used to exchange email messages.\n\nYour chat address will be:") : "" + imageSource: "result" + + property alias registrationButton: registrationButton + + property string jid: username && server ? username + "@" + server : "" + + ColumnLayout { + parent: contentArea + spacing: 40 + + CenteredAdaptiveText { + text: jid + scaleFactor: 2 + } + + RegistrationButton { + id: registrationButton + } } } diff --git a/src/qml/registration/ServerView.qml b/src/qml/registration/ServerView.qml new file mode 100644 index 0000000..131e7d1 --- /dev/null +++ b/src/qml/registration/ServerView.qml @@ -0,0 +1,167 @@ +/* + * Kaidan - A user-friendly XMPP client for every device! + * + * Copyright (C) 2016-2020 Kaidan developers and contributors + * (see the LICENSE file for a full list of copyright authors) + * + * Kaidan 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. + * + * In addition, as a special exception, the author of Kaidan gives + * permission to link the code of its release with the OpenSSL + * project's "OpenSSL" library (or with modified versions of it that + * use the same license as the "OpenSSL" library), and distribute the + * linked executables. You must obey the GNU General Public License in + * all respects for all of the code used other than "OpenSSL". If you + * modify this file, you may extend this exception to your version of + * the file, but you are not obligated to do so. If you do not wish to + * do so, delete this exception statement from your version. + * + * Kaidan 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 Kaidan. If not, see . + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 as Controls +import org.kde.kirigami 2.8 as Kirigami + +import im.kaidan.kaidan 1.0 + +import "../elements" + +/** + * This view is used for choosing a server. + */ +View { + descriptionText: qsTr("The server is the provider your communication will be managed by.\nThe selectable servers are hand-picked by our community!") + imageSource: "server" + + property string text: customServerSelected ? customServerField.text : serverListModel.data(comboBox.currentIndex, ServerListModel.JidRole) + property bool customServerSelected: serverListModel.data(comboBox.currentIndex, ServerListModel.IsCustomServerRole) + property bool inBandRegistrationSupported: serverListModel.data(comboBox.currentIndex, ServerListModel.SupportsInBandRegistrationRole) + property string registrationWebPage: serverListModel.data(comboBox.currentIndex, ServerListModel.RegistrationWebPageRole) + property bool shouldWebRegistrationViewBeShown: !customServerSelected && !inBandRegistrationSupported + + ColumnLayout { + parent: contentArea + spacing: Kirigami.Units.largeSpacing + + Controls.Label { + text: qsTr("Server") + } + + Controls.ComboBox { + id: comboBox + Layout.fillWidth: true + model: ServerListModel { + id: serverListModel + } + textRole: "display" + currentIndex: indexOfRandomlySelectedServer() + onCurrentIndexChanged: customServerField.text = "" + + onActivated: { + if (index === 0) { + editText = "" + + // Focus the whole combo box. + forceActiveFocus() + + // Focus the input text field of the combo box. + nextItemInFocusChain().forceActiveFocus() + } + } + } + + Controls.TextField { + id: customServerField + visible: customServerSelected + Layout.fillWidth: true + placeholderText: "example.org" + } + + Controls.ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + + Kirigami.FormLayout { + Controls.Label { + visible: !customServerSelected && text + Kirigami.FormData.label: qsTr("Web registration only:") + text: inBandRegistrationSupported ? qsTr("No") : qsTr("Yes") + } + + Controls.Label { + visible: !customServerSelected && text + Kirigami.FormData.label: qsTr("Region:") + text: serverListModel.data(comboBox.currentIndex, ServerListModel.CountryRole) + } + + Controls.Label { + visible: !customServerSelected && text + Kirigami.FormData.label: qsTr("Server language:") + text: serverListModel.data(comboBox.currentIndex, ServerListModel.LanguageRole) + } + + Controls.Label { + visible: !customServerSelected && text + Kirigami.FormData.label: qsTr("Server website:") + text: serverListModel.data(comboBox.currentIndex, ServerListModel.WebsiteRole) + } + + Controls.Label { + visible: !customServerSelected && text + Kirigami.FormData.label: qsTr("Online since:") + text: serverListModel.data(comboBox.currentIndex, ServerListModel.OnlineSinceRole) + } + + Controls.Label { + visible: !customServerSelected && text + Kirigami.FormData.label: qsTr("Maximum size for sending files:") + text: serverListModel.data(comboBox.currentIndex, ServerListModel.HttpUploadSizeRole) + } + + Controls.Label { + visible: !customServerSelected && text + Kirigami.FormData.label: qsTr("Duration of message storage:") + text: serverListModel.data(comboBox.currentIndex, ServerListModel.MessageStorageDurationRole) + } + } + } + + // placeholder + Item { + Layout.fillHeight: true + } + } + + onShouldWebRegistrationViewBeShownChanged: { + // Show the web registration view for non-custom servers if only web registration is supported or hides the view otherwise. + if (shouldWebRegistrationViewBeShown) + addWebRegistrationView() + else + removeWebRegistrationView() + } + + /** + * Randomly sets a new server as selected for registration. + */ + function selectServerRandomly() { + comboBox.currentIndex = indexOfRandomlySelectedServer() + } + + /** + * Returns the index of a randomly selected server for registration. + */ + function indexOfRandomlySelectedServer() { + return serverListModel.randomlyChooseIndex() + } +} diff --git a/src/qml/settings/SettingsContent.qml b/src/qml/registration/UsernameView.qml similarity index 54% copy from src/qml/settings/SettingsContent.qml copy to src/qml/registration/UsernameView.qml index 53dbcf0..893aa46 100644 --- a/src/qml/settings/SettingsContent.qml +++ b/src/qml/registration/UsernameView.qml @@ -1,72 +1,81 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 -import QtQuick.Controls 2.12 as Controls import QtQuick.Layouts 1.12 -import org.kde.kirigami 2.8 as Kirigami +import QtQuick.Controls 2.12 as Controls + import im.kaidan.kaidan 1.0 +import "../elements/fields" + /** - * The settings page contains options to configure Kaidan. - * - * It is used on a new layer on mobile and inside of a Sheet on desktop. + * This view is used for entering a username. */ -ColumnLayout { - Controls.StackView { - Layout.fillHeight: true - Layout.fillWidth: true +FieldView { + descriptionText: qsTr("Your username is used to log in to your account and to chat with you.\nIf you don't enter a username, the randomly generated and already displayed one is used.\nUsing an own username may let it be more recognizable but also could decrease your privacy!") + imageSource: "username" + + property string text: enteredText.length > 0 ? enteredText : field.placeholderText + + property alias enteredText: field.text + property alias valid: field.valid - id: stack - Layout.preferredHeight: currentItem.height - initialItem: settingsContent - clip: true + function regenerateUsername() { + placeholderText = credentialsGenerator.generateUsername() } - Component { - id: settingsContent - Column { - height: root.height * 0.9 - spacing: 0 - SettingsItem { - name: qsTr("Change password") - description: qsTr("Changes your account's password. You will need to re-enter it on your other devices.") - onClicked: stack.push("ChangePassword.qml") - icon: "lock" - } - SettingsItem { - name: qsTr("Multimedia Settings") - description: qsTr("Configure photo, video and audio recording settings") - onClicked: stack.push("MultimediaSettings.qml") - icon: "settings-configure" + ColumnLayout { + parent: contentArea + + CredentialsField { + id: field + labelText: qsTr("Username") + + // Set the display name as the entered text while replacing all whitespaces by dots. + text: displayNameView.text.replace(/ /g, ".").toLowerCase() + + placeholderText: credentialsGenerator.generateUsername() + inputMethodHints: Qt.ImhPreferLowercase + invalidHintText: qsTr("Please enter a valid username or leave the field empty for a random one.") + valid: true + + // Validate the entered username and handle that if it is invalid. + onTextChanged: { + if (text === "") + valid = true + else + valid = credentialsValidator.isUsernameValid(text) + + handleInvalidText() } } } } diff --git a/src/qml/AccountDeletionFromClientConfirmationPage.qml b/src/qml/registration/View.qml similarity index 68% copy from src/qml/AccountDeletionFromClientConfirmationPage.qml copy to src/qml/registration/View.qml index 6f78f75..665af6a 100644 --- a/src/qml/AccountDeletionFromClientConfirmationPage.qml +++ b/src/qml/registration/View.qml @@ -1,52 +1,69 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ -import QtQuick 2.7 +import QtQuick 2.12 +import QtQuick.Layouts 1.12 import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 -import "elements" +import "../elements" /** - * This page is used for confirming the deletion of an account from the client. + * This is the base for views of the swipe view. */ -ConfirmationPage { - title: qsTr("Remove account from this app") +ColumnLayout { + property alias descriptionText: description.text + property alias contentArea: contentArea + property string imageSource - topDescription: qsTr("Your account will be removed from this app.\nYou won't be able to get your credentials back!\nMake sure that you have backed up those if you want to use your account later.") + GridLayout { + id: contentArea + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: largeButtonWidth + Layout.margins: 15 + columns: 1 + rowSpacing: root.height * 0.05 - topAction: Kirigami.Action { - text: qsTr("Remove") - onTriggered: { - Kaidan.deleteAccountFromClient() + Image { + id: image + source: imageSource ? Utils.getResourcePath("images/onboarding/" + imageSource + ".svg") : "" + visible: imageSource + Layout.fillWidth: true + Layout.fillHeight: true + fillMode: Image.PreserveAspectFit + } + + CenteredAdaptiveText { + id: description + lineHeight: 1.5 } } } diff --git a/src/qml/elements/fields/JidField.qml b/src/qml/registration/WebRegistrationView.qml similarity index 64% copy from src/qml/elements/fields/JidField.qml copy to src/qml/registration/WebRegistrationView.qml index a53d4f3..510b8be 100644 --- a/src/qml/elements/fields/JidField.qml +++ b/src/qml/registration/WebRegistrationView.qml @@ -1,49 +1,65 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 +import QtQuick.Layouts 1.12 import im.kaidan.kaidan 1.0 +import "../elements" + /** - * This is a JID field with a hint for invalid JIDs. + * This view is used for creating an account via web registration. */ -CredentialsField { - labelText: qsTr("Chat address") - placeholderText: qsTr("user@example.org") - inputMethodHints: Qt.ImhEmailCharactersOnly - invalidHintText: qsTr("The chat address must have the form username@server") - - // Validate the entered JID and show a hint if it is not valid. - onTextChanged: { - valid = credentialsValidator.isJidValid(text) - toggleHintForInvalidText() +View { + descriptionText: qsTr("The selected server only provides web registration. After creating an account via the web page, you can log in via Kaidan.") + imageSource: "web-registration" + + ColumnLayout { + parent: contentArea + + CenteredAdaptiveHighlightedButton { + label.text: qsTr("Open registration web page") + onClicked: Qt.openUrlExternally(serverView.registrationWebPage) + } + + CenteredAdaptiveButton { + label.text: qsTr("Copy registration web page address") + onClicked: Utils.copyToClipboard(serverView.registrationWebPage) + } + + CenteredAdaptiveHighlightedButton { + id: loginButton + label.text: qsTr("Log in with your new account") + Layout.topMargin: height + onClicked: pageStack.layers.push(loginPage) + } } } diff --git a/src/qml/settings/SettingsContent.qml b/src/qml/settings/SettingsContent.qml index 53dbcf0..cd3172c 100644 --- a/src/qml/settings/SettingsContent.qml +++ b/src/qml/settings/SettingsContent.qml @@ -1,72 +1,73 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ import QtQuick 2.12 import QtQuick.Controls 2.12 as Controls import QtQuick.Layouts 1.12 import org.kde.kirigami 2.8 as Kirigami import im.kaidan.kaidan 1.0 /** * The settings page contains options to configure Kaidan. * * It is used on a new layer on mobile and inside of a Sheet on desktop. */ ColumnLayout { Controls.StackView { Layout.fillHeight: true Layout.fillWidth: true id: stack Layout.preferredHeight: currentItem.height initialItem: settingsContent clip: true } Component { id: settingsContent Column { height: root.height * 0.9 spacing: 0 SettingsItem { name: qsTr("Change password") description: qsTr("Changes your account's password. You will need to re-enter it on your other devices.") + visible: Kaidan.serverFeaturesCache.inBandRegistrationSupported onClicked: stack.push("ChangePassword.qml") icon: "lock" } SettingsItem { name: qsTr("Multimedia Settings") description: qsTr("Configure photo, video and audio recording settings") onClicked: stack.push("MultimediaSettings.qml") icon: "settings-configure" } } } } diff --git a/src/qxmpp-exts/QXmppUri.cpp b/src/qxmpp-exts/QXmppUri.cpp index 4ac9150..16539e6 100644 --- a/src/qxmpp-exts/QXmppUri.cpp +++ b/src/qxmpp-exts/QXmppUri.cpp @@ -1,307 +1,306 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2020 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #include "QXmppUri.h" #include const QString URI_SCHEME = QStringLiteral("xmpp"); const QChar URI_QUERY_SEPARATOR = '?'; const QChar URI_QUERY_VALUE_DELIMITER = '='; const QChar URI_QUERY_PAIR_DELIMITER = ';'; /// actions, e.g. "join" in "xmpp:group@example.org?join" for joining a group chat const QStringList ACTION_STRINGS = QStringList() << "" << "command" << "disco" << "invite" << "join" << "login" << "message" << "pubsub" << "recvfile" << "register" << "remove" << "roster" << "sendfile" << "subscribe" << "unregister" << "unsubscribe" << "vcard"; /// types of a "message" action const QStringList MESSAGE_TYPE_STRINGS = QStringList() << "error" << "normal" << "chat" << "groupchat" << "headline"; /// keys of a message action's key-value pair const QStringList MESSAGE_ATTRIBUTES = QStringList() << "subject" << "body" << "thread" << "from" << "id" << "type"; /// Adds a key-value pair to the given pairs if val is not empty. /// @param pairs which will be extended by the key-value pair with the given key /// @param key key of the given value /// @param val value of given key void helperToAddPair(QUrlQuery &query, const QString &key, const QString &val) { if (!val.isEmpty()) query.addQueryItem(key, val); } /// @return value of a given key in a given query /// @param query query to search for the key /// @param key to get the corresponding value QString helperToGetQueryItemValue(const QUrlQuery &query, const QString &key) { return query.queryItemValue(key, QUrl::FullyDecoded); } /// Parses the URI from a string. /// /// @param input string which may present an XMPP URI QXmppUri::QXmppUri(QString input) { QUrl url(input); if (!url.isValid() || url.scheme() != URI_SCHEME) return; - // set JID setJid(url.path()); if (!url.hasQuery()) return; QUrlQuery query; query.setQueryDelimiters(URI_QUERY_VALUE_DELIMITER, URI_QUERY_PAIR_DELIMITER); query.setQuery(url.query(QUrl::FullyEncoded)); // check that there are query items (key-value pairs) if (!query.queryItems().size()) return; m_action = Action(ACTION_STRINGS.indexOf(query.queryItems().first().first)); switch (m_action) { case Message: m_message.setSubject(helperToGetQueryItemValue(query, "subject")); m_message.setBody(helperToGetQueryItemValue(query, "body")); m_message.setThread(helperToGetQueryItemValue(query, "thread")); m_message.setId(helperToGetQueryItemValue(query, "id")); m_message.setFrom(helperToGetQueryItemValue(query, "from")); if (!helperToGetQueryItemValue(query, "type").isEmpty()) m_message.setType(QXmppMessage::Type( MESSAGE_TYPE_STRINGS.indexOf(helperToGetQueryItemValue(query, "type")) )); else m_hasMessageType = false; break; case Login: m_password = helperToGetQueryItemValue(query, "password"); break; default: break; } } /// Decodes the URI to a string. /// /// @return full XMPP URI QString QXmppUri::toString() { QUrl url; url.setScheme(URI_SCHEME); url.setPath(m_jid); // Create query items (parameters) QUrlQuery query; query.setQueryDelimiters(URI_QUERY_VALUE_DELIMITER, URI_QUERY_PAIR_DELIMITER); switch (m_action) { case Message: helperToAddPair(query, "body", m_message.body()); helperToAddPair(query, "from", m_message.from()); helperToAddPair(query, "id", m_message.id()); helperToAddPair(query, "thread", m_message.thread()); if (m_hasMessageType) helperToAddPair(query, "type", MESSAGE_TYPE_STRINGS.at( int(m_message.type()))); helperToAddPair(query, "subject", m_message.subject()); break; case Login: helperToAddPair(query, "password", m_password); break; default: break; } QString output = url.toEncoded(); if (m_action != None) { // add action output += URI_QUERY_SEPARATOR; output += ACTION_STRINGS.at(int(m_action)); // add parameters QString queryStr = query.toString(QUrl::FullyEncoded); if (!query.isEmpty()) { output += URI_QUERY_PAIR_DELIMITER; output += queryStr; } } return output; } /// Returns the JID this URI is about. /// /// This can also be e.g. a MUC room in case of a Join action. QString QXmppUri::jid() const { return m_jid; } /// Sets the JID this XMPP URI links to. /// /// @param jid JID to be set void QXmppUri::setJid(const QString &jid) { m_jid = jid; } /// Returns the action of this XMPP URI. /// /// This is None in case no action is included. QXmppUri::Action QXmppUri::action() const { return m_action; } /// Sets the action of this XMPP URI, e.g. Join for a URI ending with /// \c ?join". /// /// @param action action to be set void QXmppUri::setAction(const Action &action) { m_action = action; } /// Returns true if this XMPP URI has the given action. /// /// @param action action to be checked for bool QXmppUri::hasAction(const Action &action) { return m_action == action; } /// Returns the password of a login action QString QXmppUri::password() const { return m_password; } /// Sets the password of a login action /// /// @param password void QXmppUri::setPassword(const QString &password) { m_password = password; } /// In case the URI has a message query, this can be used to get the attached /// message content directly as \c QXmppMessage. QXmppMessage QXmppUri::message() const { return m_message; } /// Sets the attached message for a Message action. /// /// Supported properties are: body, from, id, thread, type, subject. /// If you want to include the message type, ensure that \c hasMessageType is /// set to true. /// /// @param message message to be set void QXmppUri::setMessage(const QXmppMessage &message) { setAction(Message); m_message = message; } /// Returns true, if the attached message's type is included. /// /// This is required because \c QXmppMessage has no option to set no type. bool QXmppUri::hasMessageType() const { return m_hasMessageType; } /// Sets whether to include the message's type. /// /// This is required because \c QXmppMessage has no option to set an empty type. /// /// @param hasMessageType true if the message's type should be included void QXmppUri::setHasMessageType(bool hasMessageType) { m_hasMessageType = hasMessageType; } /// Checks whether the string starts with the XMPP scheme. /// /// @param uri URI to check for XMPP scheme bool QXmppUri::isXmppUri(const QString &uri) { return uri.startsWith("xmpp:"); } diff --git a/utils/generate-license.py b/utils/generate-license.py index 8b2f66b..478bfe1 100755 --- a/utils/generate-license.py +++ b/utils/generate-license.py @@ -1,403 +1,418 @@ #!/usr/bin/env python3 # # Copyright (C) 2018-2019 Linus Jahn # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of # the Software, and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # """ This script generates a debian-formatted, machine-readable copyright file for Kaidan. It uses git to get a list of contributors for the source code and the translations. """ import git import datetime # These user ids will be replaced for the LICENSE file # If you want to be added to this list, please open an issue or pull request! REPLACE_USER_IDS = [ ("Ellenjott [LNJ] ", "Linus Jahn "), ("LNJ ", "Linus Jahn "), ("Linus Jahn ", "Linus Jahn "), ("JBBgameich ", "Jonah Brüchert "), ("JBBgameich ", "Jonah Brüchert "), ("JBBgameich ", "Jonah Brüchert "), ("JBB ", "Jonah Brüchert "), ("Jonah Brüchert ", "Jonah Brüchert "), ("Jonah Brüchert ", "Jonah Brüchert "), ("Jonah Bruechert ", "Jonah Brüchert "), ("Georg ", "geobra "), ("Muhammad Nur Hidayat Yasuyoshi (MNH48.com) ", "Muhammad Nur Hidayat Yasuyoshi "), ("Joeke de Graaf ", "Joeke de Graaf "), ("X advocatux ", "advocatux "), ("ZatroxDE ", "Robert Maerkisch "), ("X oiseauroch ", "oiseauroch "), ("X ssantos ", "ssantos "), ("X Txaume ", "Txaume ") ] # These user ids will be excluded from any targets EXCLUDE_USER_IDS = [ "Weblate ", "Hosted Weblate ", "anonymous <>", "anonymous <> ", "Kaidan Translations ", "Kaidan translations ", "Hosted Weblate ", "l10n daemon script " ] GPL3_OPENSSL_LICENSE = """This program 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. In addition, as a special exception, the author of this program gives permission to link the code of its release with the OpenSSL project's "OpenSSL" library (or with modified versions of it that use the same license as the "OpenSSL" library), and distribute the linked executables. You must obey the GNU General Public License in all respects for all of the code used other than "OpenSSL". If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. This program 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 this package. If not, see . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'.""" GPL3_LICENSE = """This program 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. This program 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 this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'.""" MIT_LICENSE = """Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.""" MIT_APPLE_LICENSE = """Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.""" APACHE2_LICENSE = """Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" class CopyrightAuthor: def __init__(self, score = 0, dates = None, years = "", uid = ""): self.dates = dates or list([]); self.score = score; self.years = years; self.uid = uid; def addTimestamp(self, x): self.dates.append(x); def createField(name, contentList, fullIndent = True): retvar = "{}: {}\n".format(name, contentList[0]); if len(contentList) < 2: return retvar; indent = len(name) * " "; for i in range(1, len(contentList)): retvar += "{} {}\n".format(indent, contentList[i]); return retvar; def createLongField(name, heading, content): retvar = "{}: {}\n".format(name, heading); if not content: return retvar; for line in content.split('\n'): if not line.strip(): # if line is empty line = "."; retvar += " {}\n".format(line); return retvar; class CopyrightTarget: def __init__(self, directories = None, files = None, licenseName = "", licenseContent = "", replaceUids = None, excludeUids = None, authorList = None, additionalAuthors = None, comment = ""): self.repo = git.Repo("."); self.files = files or list([]); self.directories = directories or list([]); self.licenseName = licenseName; self.licenseContent = licenseContent self.authorList = authorList or dict({}); self.comment = comment self.replaceUids = replaceUids or list([]); self.excludeUids = excludeUids or list([]); self.replaceUids.extend(REPLACE_USER_IDS); self.excludeUids.extend(EXCLUDE_USER_IDS); self.authorList = authorList or self.getAuthorList(); if additionalAuthors: self.authorList.update(additionalAuthors) def replaceUid(self, uid): for pair in self.replaceUids: if uid == pair[0]: uid = pair[1]; return uid; def getAuthorList(self): paths = list(self.files); paths.extend(self.directories); commitList = self.repo.iter_commits(paths=paths); authorList = {}; for commit in commitList: # create user id and check replacements and excludes uid = "{} <{}>".format(commit.author.name, commit.author.email); uid = self.replaceUid(uid); if uid in self.excludeUids: continue; if not uid in authorList.keys(): authorList[uid] = CopyrightAuthor(uid = uid); authorList[uid].addTimestamp(commit.authored_date); for uid in authorList: minT = min(int(t) for t in authorList[uid].dates); maxT = max(int(t) for t in authorList[uid].dates); authorList[uid].score = maxT - minT; minYear = datetime.datetime.fromtimestamp(minT).year; maxYear = datetime.datetime.fromtimestamp(maxT).year; if minYear == maxYear: authorList[uid].years = str(minYear); else: authorList[uid].years = "{}-{}".format(minYear, maxYear); authorList[uid].dates = []; return authorList; def toDebianCopyright(self): # Create copyright list copyrights = []; for item in sorted(self.authorList.items(), key=lambda x: x[1].score, reverse=True): copyrights.append("{}, {}".format(item[1].years, item[0])); files = list(self.files); for directory in self.directories: files.append(directory + "/*"); retvar = createField("Files", files); retvar += createField("Copyright", copyrights); retvar += createLongField("License", self.licenseName, self.licenseContent); if self.comment: retvar += createLongField("Comment", "", self.comment); return retvar; class LicenseTarget: def __init__(self, name = "", content = ""): self.name = name; self.content = content; def toDebianCopyright(self): return createLongField("License", self.name, self.content); def main(): copyrightTargets = [ CopyrightTarget( directories = ["src", "utils", "misc"], licenseName = "GPL-3+ with OpenSSL exception", additionalAuthors = { "Eike Hein ": CopyrightAuthor(years = "2017-2018") } ), CopyrightTarget( directories = ["i18n"], licenseName = "GPL-3+ with OpenSSL exception" ), CopyrightTarget( files = ["src/StatusBar.cpp", "src/StatusBar.h", "src/singleapp/*", "src/hsluv-c/*", "utils/generate-license.py"], licenseName = "MIT", authorList = { "J-P Nurmi ": CopyrightAuthor(years = "2016"), "Linus Jahn ": CopyrightAuthor(years = "2018-2019"), "Itay Grudev ": CopyrightAuthor(years = "2015-2018"), "Alexei Boronine ": CopyrightAuthor(years = "2015"), "Roger Tallada ": CopyrightAuthor(years = "2015"), "Martin Mitas ": CopyrightAuthor(years = "2017"), } ), CopyrightTarget( files = ["src/EmojiModel.cpp", "src/EmojiModel.h", "qml/elements/EmojiPicker.qml"], licenseName = "GPL-3+", authorList = { "Konstantinos Sideris ": CopyrightAuthor(years = "2017"), }, ), CopyrightTarget( files = ["src/QrCodeVideoFrame.h", "src/QrCodeVideoFrame.cpp"], licenseName = "apache-2.0", authorList = { "QZXing authors": CopyrightAuthor(years = "2017"), }, ), CopyrightTarget( files = ["data/images/check-mark.svg"], licenseName = "CC-BY-SA-4.0", authorList = { "Melvin Keskin ": CopyrightAuthor(years = "2019"), }, ), CopyrightTarget( files = [ "misc/kaidan-128x128.png", "data/images/kaidan.svg", "data/images/kaidan-bw.svg" ], licenseName = "CC-BY-SA-4.0", authorList = { "Ilya Bizyaev ": CopyrightAuthor(years = "2016-2017"), "Mathis Brüchert ": CopyrightAuthor(years = "2020"), "Melvin Keskin ": CopyrightAuthor(years = "2019"), "Melvin Keskin ": CopyrightAuthor(years = "2019") }, comment = "Inspired by graphic from https://www.toptal.com/designers/subtlepatterns/" ), CopyrightTarget( files = ["data/images/global-drawer-banner.svg"], licenseName = "CC-BY-SA-4.0", authorList = { "Mathis Brüchert ": CopyrightAuthor(years = "2020") } ), CopyrightTarget( - files = ["data/images/account-deletion-from-client.svg", "data/images/account-deletion-from-client-and-server.svg"], + files = [ + "data/images/account-deletion-from-client.svg", + "data/images/account-deletion-from-client-and-server.svg", + "data/images/onboarding/account-transfer.svg", + "data/images/onboarding/automatic-registration.svg", + "data/images/onboarding/custom-form.svg", + "data/images/onboarding/display-name.svg", + "data/images/onboarding/login.svg", + "data/images/onboarding/manual-registration.svg", + "data/images/onboarding/password.svg", + "data/images/onboarding/registration.svg", + "data/images/onboarding/result.svg", + "data/images/onboarding/server.svg", + "data/images/onboarding/username.svg", + "data/images/onboarding/web-registration.svg" + ], licenseName = "CC-BY-SA-4.0", authorList = { "Mathis Brüchert ": CopyrightAuthor(years = "2020") } ), CopyrightTarget( files = ["utils/convert-prl-libs-to-cmake.pl"], licenseName = "MIT-Apple", authorList = { "Konstantin Tokarev ": CopyrightAuthor(years = "2016") } ), LicenseTarget( name = "GPL-3+ with OpenSSL exception", content = GPL3_OPENSSL_LICENSE ), LicenseTarget( name = "GPL-3+", content = GPL3_LICENSE ), LicenseTarget( name = "MIT", content = MIT_LICENSE ), LicenseTarget( name = "MIT-Apple", content = MIT_APPLE_LICENSE ), LicenseTarget( name = "apache-2.0", content = APACHE2_LICENSE ) ]; print("Upstream-Name: kaidan") print("Source: https://invent.kde.org/kde/kaidan/") print("Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/") print() for target in copyrightTargets: print(target.toDebianCopyright()); if __name__ == "__main__": main();