mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-11-04 08:10:18 +08:00
Compare commits
76 Commits
beta/20250
...
beta/20250
| Author | SHA1 | Date | |
|---|---|---|---|
| 479da3ca54 | |||
| 02140a11c4 | |||
| b5dbe23c17 | |||
| ff5f94b34a | |||
| def2e19abe | |||
| 69430c4af3 | |||
| 911fdbc9e5 | |||
| 80b3336f1b | |||
| f8bafd4154 | |||
| b8b06ccfa1 | |||
| 6cb5a8206a | |||
| ae1a2462e2 | |||
| 31581db7f5 | |||
| c323dcfe13 | |||
| 8a31aca346 | |||
| 7eeabbe1c6 | |||
| 8c0ecaf3c5 | |||
| c91bff35b6 | |||
| cb05b4afd0 | |||
| 4419f7bfbc | |||
| deaa15a36e | |||
| 6b70ffcf3e | |||
| a407fa2c47 | |||
| 3143d60760 | |||
| 73eacd5125 | |||
| fc70ddc6dc | |||
| e2e5a0c8cc | |||
| dd9ed8d57d | |||
| 7eea1484c5 | |||
| 6fac40340b | |||
| 04a1544d56 | |||
| 91fd8a0295 | |||
| bad5698e71 | |||
| 7f0a94dd48 | |||
| 81112ff1f8 | |||
| b63eb384b6 | |||
| ebcc60c570 | |||
| c12a753979 | |||
| 386b578e47 | |||
| 76417edfa4 | |||
| 960fdc0f05 | |||
| 2567d81359 | |||
| e700d8160a | |||
| 37065cb7d6 | |||
| 03efee4c14 | |||
| 04e43b07f4 | |||
| a00a191371 | |||
| 65d3befad8 | |||
| 84ac5dba02 | |||
| a149a575a7 | |||
| 407561732f | |||
| 82c2a9d9c6 | |||
| 1414e1804f | |||
| 7146f20b18 | |||
| 5ee4990534 | |||
| 0d1bdfdfdd | |||
| f3dd3b4643 | |||
| d3c76eac8d | |||
| 33da15daba | |||
| 82c876c0fa | |||
| 83765dcebf | |||
| b76570b892 | |||
| ffb9bb8748 | |||
| 416137d6dd | |||
| ffcfaffc90 | |||
| a950a3f9e7 | |||
| 3c279b292d | |||
| c19af7f97f | |||
| 0087f1b8a8 | |||
| 19f7f43912 | |||
| c818eeedce | |||
| 0f08dbcf59 | |||
| 7d3331d235 | |||
| aa375a7f89 | |||
| 9b51f26e80 | |||
| 6b0adb7005 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,7 @@ doc/Doxyfile
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -21,9 +21,13 @@
|
||||
ignore = dirty
|
||||
[submodule "3rdparty/md4c"]
|
||||
path = 3rdparty/md4c
|
||||
url = https://github.com/mity/md4c.git
|
||||
url = https://github.com/fsimonfc/md4c.git
|
||||
ignore = dirty
|
||||
[submodule "3rdparty/tidy-html5"]
|
||||
path = 3rdparty/tidy-html5
|
||||
url = https://github.com/htacg/tidy-html5.git
|
||||
ignore = dirty
|
||||
[submodule "3rdparty/zxing-cpp"]
|
||||
path = 3rdparty/zxing-cpp
|
||||
url = https://github.com/nu-book/zxing-cpp.git
|
||||
ignore = dirty
|
||||
|
||||
2
3rdparty/md4c
vendored
2
3rdparty/md4c
vendored
Submodule 3rdparty/md4c updated: ad8d41127b...635f296735
1
3rdparty/zxing-cpp
vendored
Submodule
1
3rdparty/zxing-cpp
vendored
Submodule
Submodule 3rdparty/zxing-cpp added at a920817b6f
@ -76,6 +76,7 @@ list(APPEND QWINDOWKIT_OPTIONS
|
||||
QWINDOWKIT_BUILD_WIDGETS OFF
|
||||
QWINDOWKIT_INSTALL OFF
|
||||
QWINDOWKIT_BUILD_STATIC ON
|
||||
QWINDOWKIT_BUILD_QUICK ON
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
@ -93,19 +94,19 @@ if(WIN32)
|
||||
list(APPEND QWINDOWKIT_OPTIONS QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS OFF)
|
||||
endif()
|
||||
|
||||
# qmsetup uses the wrong package dir on some distributions
|
||||
# (including Fedora and openSUSE Leap at least)
|
||||
check_distro_needs_qmsetup_patch(DISTRO_NEEDS_QMSETUP_PATCH)
|
||||
if(DISTRO_NEEDS_QMSETUP_PATCH)
|
||||
list(APPEND QWINDOWKIT_PATCHES ${EXTRA_PATCHES_DIR}/0001-fix-fedora-fc-build.patch)
|
||||
set(qmsetup_cmake_path ${CMAKE_BINARY_DIR}/_install/lib64/cmake/qmsetup)
|
||||
# If qwindowkit can't find qmsetup via cmake's find_package function, it will install it and
|
||||
# then call find_package again. Unfortunately, even the second call to find_package sometimes
|
||||
# fails due to qmsetup having been installed in the wrong directory. The following patch
|
||||
# ensures that qmsetup is always installed in the directory where find_package looks for it.
|
||||
if(NOT WIN32)
|
||||
list(APPEND QWINDOWKIT_PATCHES ${EXTRA_PATCHES_DIR}/0001-fix-qm_install_package-function.patch)
|
||||
endif()
|
||||
|
||||
# qwindowkit (frameless window)
|
||||
add_fetch_content(
|
||||
TARGET qwindowkit
|
||||
URL https://github.com/stdware/qwindowkit.git
|
||||
BRANCH 79b1f3110754f9c21af2d7dacbd07b1a9dbaf6ef
|
||||
BRANCH 758b00cb6c2d924be3a1ea137ec366dc33a5132d
|
||||
PATCHES ${QWINDOWKIT_PATCHES}
|
||||
OPTIONS ${QWINDOWKIT_OPTIONS}
|
||||
)
|
||||
@ -364,6 +365,8 @@ set(COMMON_SOURCES
|
||||
${APP_SRC_DIR}/pluginversionmanager.cpp
|
||||
${APP_SRC_DIR}/connectioninfolistmodel.cpp
|
||||
${APP_SRC_DIR}/pluginversionmanager.cpp
|
||||
${APP_SRC_DIR}/linkdevicemodel.cpp
|
||||
${APP_SRC_DIR}/qrcodescannermodel.cpp
|
||||
)
|
||||
|
||||
set(COMMON_HEADERS
|
||||
@ -436,6 +439,8 @@ set(COMMON_HEADERS
|
||||
${APP_SRC_DIR}/pttlistener.h
|
||||
${APP_SRC_DIR}/crashreportclient.h
|
||||
${APP_SRC_DIR}/crashreporter.h
|
||||
${APP_SRC_DIR}/linkdevicemodel.h
|
||||
${APP_SRC_DIR}/qrcodescannermodel.h
|
||||
)
|
||||
|
||||
# For libavutil/avframe.
|
||||
@ -678,6 +683,15 @@ list(APPEND CLIENT_LINK_DIRS ${tidy_BINARY_DIR}/Release)
|
||||
list(APPEND CLIENT_INCLUDE_DIRS ${tidy_SOURCE_DIR}/include)
|
||||
list(APPEND CLIENT_LIBS tidy-static)
|
||||
|
||||
# ZXing-cpp configuration
|
||||
set(BUILD_EXAMPLES OFF CACHE BOOL "")
|
||||
set(BUILD_BLACKBOX_TESTS OFF CACHE BOOL "")
|
||||
add_subdirectory(3rdparty/zxing-cpp EXCLUDE_FROM_ALL)
|
||||
|
||||
# Add ZXing-cpp to includes and libraries
|
||||
list(APPEND CLIENT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zxing-cpp/core/src)
|
||||
list(APPEND CLIENT_LIBS ZXing)
|
||||
|
||||
# common executable sources
|
||||
qt_add_executable(
|
||||
${PROJECT_NAME}
|
||||
|
||||
2
build.py
2
build.py
@ -374,7 +374,7 @@ def run_install(args):
|
||||
# Prepare the build-windows.py script call
|
||||
build_windows = 'extras/scripts/build-windows.py'
|
||||
# Initialize build environment
|
||||
execute_script([f'python {build_windows} --init'])
|
||||
execute_script([f'python {build_windows} --init --qt={args.qt}'])
|
||||
|
||||
# Construct build command with options
|
||||
build_cmd = [
|
||||
|
||||
2
daemon
2
daemon
Submodule daemon updated: 5e2d9e027b...68fc552fca
@ -17,32 +17,6 @@
|
||||
include(FetchContent)
|
||||
include(CMakeParseArguments)
|
||||
|
||||
# Helper function to check if we're on a distribution that requires us
|
||||
# to apply a patch in order for qmsetup to use the right package directory
|
||||
function(check_distro_needs_qmsetup_patch DISTRO_NEEDS_QMSETUP_PATCH)
|
||||
set(${DISTRO_NEEDS_QMSETUP_PATCH} FALSE PARENT_SCOPE)
|
||||
# Check for the existence of /etc/os-release
|
||||
if(EXISTS "/etc/os-release")
|
||||
# Read the content of the file
|
||||
file(READ "/etc/os-release" OS_RELEASE_CONTENT)
|
||||
# Check if the distribution is Fedora or Red Hat-based
|
||||
string(REGEX MATCH "ID=fedora|ID_LIKE=\"rhel fedora\"|ID_LIKE=\"rhel centos fedora\"" RED_HAT_BASED "${OS_RELEASE_CONTENT}")
|
||||
# Check if the distribution is openSUSE Leap
|
||||
string(REGEX MATCH "ID=\"opensuse-leap\"" OPENSUSE_LEAP "${OS_RELEASE_CONTENT}")
|
||||
if(RED_HAT_BASED)
|
||||
set(${DISTRO_NEEDS_QMSETUP_PATCH} TRUE PARENT_SCOPE)
|
||||
message(STATUS "Running on a Red Hat-based distribution (Fedora, RHEL, CentOS, etc.)")
|
||||
elseif(OPENSUSE_LEAP)
|
||||
set(${DISTRO_NEEDS_QMSETUP_PATCH} TRUE PARENT_SCOPE)
|
||||
message(STATUS "Running on openSUSE Leap")
|
||||
else()
|
||||
message(STATUS "Distribution is not openSUSE Leap or Red Hat-based")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "Cannot determine the distribution type: /etc/os-release not found")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper function to add external content with patches and options.
|
||||
# Parameters:
|
||||
# TARGET: Name of the target to create
|
||||
|
||||
3
extras/packaging/gnu-linux/Jenkinsfile
vendored
3
extras/packaging/gnu-linux/Jenkinsfile
vendored
@ -33,7 +33,8 @@
|
||||
def SUBMODULES = ['daemon',
|
||||
'3rdparty/SortFilterProxyModel',
|
||||
'3rdparty/md4c',
|
||||
'3rdparty/tidy-html5']
|
||||
'3rdparty/tidy-html5',
|
||||
'3rdparty/zxing-cpp']
|
||||
def TARGETS = [:]
|
||||
def REMOTE_HOST = env.SSH_HOST_DL_RING_CX
|
||||
def REMOTE_BASE_DIR = '/srv/repository/ring'
|
||||
|
||||
@ -127,7 +127,8 @@ $(RELEASE_TARBALL_FILENAME): tarballs.manifest
|
||||
. \
|
||||
./3rdparty/SortFilterProxyModel \
|
||||
./3rdparty/md4c \
|
||||
./3rdparty/tidy-html5; do \
|
||||
./3rdparty/tidy-html5 \
|
||||
./3rdparty/zxing-cpp; do \
|
||||
(cd "$$m" && git archive --prefix "$$m/" HEAD \
|
||||
| tar xf - -C $(TMPDIR)/$(RELEASE_DIRNAME)); \
|
||||
done
|
||||
@ -166,14 +167,13 @@ DISTRIBUTIONS := \
|
||||
ubuntu_22.04 \
|
||||
ubuntu_24.04 \
|
||||
ubuntu_24.10 \
|
||||
fedora_37 \
|
||||
fedora_38 \
|
||||
ubuntu_25.04 \
|
||||
fedora_39 \
|
||||
fedora_40 \
|
||||
fedora_41 \
|
||||
alma_9 \
|
||||
opensuse-leap_15.4 \
|
||||
opensuse-leap_15.5 \
|
||||
opensuse-leap_15.6 \
|
||||
snap
|
||||
|
||||
IS_SHELL_INTERACTIVE := $(shell [ -t 0 ] && echo yes)
|
||||
|
||||
@ -1,106 +0,0 @@
|
||||
FROM fedora:37
|
||||
|
||||
RUN dnf clean all
|
||||
RUN dnf update -y
|
||||
|
||||
RUN dnf install -y dnf-command\(builddep\) rpmdevtools && \
|
||||
dnf install -y mock
|
||||
|
||||
RUN dnf groupinstall -y "X Software Development"
|
||||
|
||||
RUN dnf install -y \
|
||||
git \
|
||||
rpm-build \
|
||||
tar \
|
||||
make \
|
||||
autoconf \
|
||||
automake \
|
||||
nasm \
|
||||
speexdsp-devel \
|
||||
pulseaudio-libs-devel \
|
||||
libcanberra-devel \
|
||||
libcurl-devel \
|
||||
libtool \
|
||||
mesa-libgbm-devel \
|
||||
mesa-dri-drivers \
|
||||
dbus-devel \
|
||||
expat-devel \
|
||||
pcre-devel \
|
||||
yaml-cpp-devel \
|
||||
libXext-devel \
|
||||
libXfixes-devel \
|
||||
yasm \
|
||||
python2.7 \
|
||||
python3-html5lib \
|
||||
speex-devel \
|
||||
gsm-devel \
|
||||
chrpath \
|
||||
check \
|
||||
astyle \
|
||||
uuid-c++-devel \
|
||||
gettext-devel \
|
||||
gcc-c++ \
|
||||
which \
|
||||
alsa-lib-devel \
|
||||
systemd-devel \
|
||||
libuuid-devel \
|
||||
uuid-devel \
|
||||
gnutls-devel \
|
||||
nettle-devel \
|
||||
opus-devel \
|
||||
patch \
|
||||
jsoncpp-devel \
|
||||
libnatpmp-devel \
|
||||
webkitgtk4-devel \
|
||||
cryptopp-devel \
|
||||
libva-devel \
|
||||
libvdpau-devel \
|
||||
msgpack-devel \
|
||||
NetworkManager-libnm-devel \
|
||||
openssl-devel \
|
||||
clutter-devel \
|
||||
clutter-gtk-devel \
|
||||
libappindicator-gtk3-devel \
|
||||
libnotify-devel \
|
||||
libupnp-devel \
|
||||
qrencode-devel \
|
||||
libargon2-devel \
|
||||
libsndfile-devel \
|
||||
libdrm \
|
||||
gperf \
|
||||
bison \
|
||||
clang \
|
||||
clang-devel \
|
||||
llvm-devel \
|
||||
nodejs \
|
||||
flex \
|
||||
gstreamer1 gstreamer1-devel \
|
||||
gstreamer1-plugins-base-devel \
|
||||
gstreamer1-plugins-good \
|
||||
gstreamer1-plugins-bad-free-devel \
|
||||
nss-devel \
|
||||
libxcb* \
|
||||
libxkb* \
|
||||
libX11-devel \
|
||||
vulkan-devel \
|
||||
libXrender-devel \
|
||||
xcb-util-* \
|
||||
xz \
|
||||
xkeyboard-config \
|
||||
libnotify \
|
||||
wget \
|
||||
libstdc++-static \
|
||||
sqlite-devel \
|
||||
perl-generators \
|
||||
perl-English \
|
||||
libxshmfence-devel \
|
||||
ninja-build \
|
||||
clang \
|
||||
cmake \
|
||||
fmt-devel \
|
||||
pipewire-devel \
|
||||
cups-devel #Chromium for Qt
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
|
||||
|
||||
CMD ["/opt/build-package-rpm.sh"]
|
||||
@ -1,106 +0,0 @@
|
||||
FROM fedora:38
|
||||
|
||||
RUN dnf clean all
|
||||
RUN dnf update -y
|
||||
|
||||
RUN dnf install -y dnf-command\(builddep\) rpmdevtools && \
|
||||
dnf install -y mock
|
||||
|
||||
RUN dnf groupinstall -y "X Software Development"
|
||||
|
||||
RUN dnf install -y \
|
||||
git \
|
||||
rpm-build \
|
||||
tar \
|
||||
make \
|
||||
autoconf \
|
||||
automake \
|
||||
nasm \
|
||||
speexdsp-devel \
|
||||
pulseaudio-libs-devel \
|
||||
libcanberra-devel \
|
||||
libcurl-devel \
|
||||
libtool \
|
||||
mesa-libgbm-devel \
|
||||
mesa-dri-drivers \
|
||||
dbus-devel \
|
||||
expat-devel \
|
||||
pcre-devel \
|
||||
yaml-cpp-devel \
|
||||
libXext-devel \
|
||||
libXfixes-devel \
|
||||
yasm \
|
||||
python2.7 \
|
||||
speex-devel \
|
||||
gsm-devel \
|
||||
chrpath \
|
||||
check \
|
||||
astyle \
|
||||
uuid-c++-devel \
|
||||
gettext-devel \
|
||||
gcc-c++ \
|
||||
which \
|
||||
alsa-lib-devel \
|
||||
systemd-devel \
|
||||
libuuid-devel \
|
||||
uuid-devel \
|
||||
gnutls-devel \
|
||||
nettle-devel \
|
||||
opus-devel \
|
||||
patch \
|
||||
jsoncpp-devel \
|
||||
libnatpmp-devel \
|
||||
webkitgtk4-devel \
|
||||
cryptopp-devel \
|
||||
libva-devel \
|
||||
libvdpau-devel \
|
||||
msgpack-devel \
|
||||
NetworkManager-libnm-devel \
|
||||
openssl-devel \
|
||||
clutter-devel \
|
||||
clutter-gtk-devel \
|
||||
libappindicator-gtk3-devel \
|
||||
libnotify-devel \
|
||||
libupnp-devel \
|
||||
qrencode-devel \
|
||||
libargon2-devel \
|
||||
libsndfile-devel \
|
||||
libdrm \
|
||||
gperf \
|
||||
bison \
|
||||
clang \
|
||||
clang-devel \
|
||||
llvm-devel \
|
||||
nodejs \
|
||||
flex \
|
||||
gstreamer1 gstreamer1-devel \
|
||||
gstreamer1-plugins-base-devel \
|
||||
gstreamer1-plugins-good \
|
||||
gstreamer1-plugins-bad-free-devel \
|
||||
nss-devel \
|
||||
libxcb* \
|
||||
libxkb* \
|
||||
libX11-devel \
|
||||
vulkan-devel \
|
||||
libXrender-devel \
|
||||
xcb-util-* \
|
||||
xz \
|
||||
xkeyboard-config \
|
||||
libnotify \
|
||||
wget \
|
||||
libstdc++-static \
|
||||
sqlite-devel \
|
||||
perl-generators \
|
||||
perl-English \
|
||||
libxshmfence-devel \
|
||||
ninja-build \
|
||||
clang \
|
||||
cmake \
|
||||
fmt-devel \
|
||||
python3-html5lib \
|
||||
cups-devel \
|
||||
pipewire-devel
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
|
||||
|
||||
CMD ["/opt/build-package-rpm.sh"]
|
||||
@ -1,10 +1,9 @@
|
||||
FROM opensuse/leap:15.4
|
||||
FROM opensuse/leap:15.6
|
||||
|
||||
RUN zypper refresh
|
||||
RUN zypper --gpg-auto-import-keys refresh
|
||||
|
||||
RUN zypper --non-interactive install -y \
|
||||
dnf \
|
||||
dnf-command\(builddep\) \
|
||||
rpmdevtools \
|
||||
Mesa-dri-devel Mesa-dri \
|
||||
git \
|
||||
@ -29,11 +28,11 @@ RUN zypper --non-interactive install -y \
|
||||
speex-devel \
|
||||
libgsm-devel \
|
||||
chrpath \
|
||||
check \
|
||||
check-devel \
|
||||
astyle \
|
||||
gettext-devel \
|
||||
gettext-tools \
|
||||
which \
|
||||
alsa-lib-devel \
|
||||
alsa-devel \
|
||||
systemd-devel \
|
||||
libuuid-devel \
|
||||
uuid-devel \
|
||||
@ -44,9 +43,10 @@ RUN zypper --non-interactive install -y \
|
||||
libcryptopp-devel \
|
||||
libva-devel \
|
||||
libvdpau-devel \
|
||||
msgpack-devel \
|
||||
msgpack-c-devel \
|
||||
msgpack-cxx-devel \
|
||||
clutter-devel \
|
||||
openssl-devel \
|
||||
libopenssl-devel \
|
||||
clutter-gtk-devel \
|
||||
libnma-devel \
|
||||
libcryptopp-devel \
|
||||
@ -55,20 +55,20 @@ RUN zypper --non-interactive install -y \
|
||||
libgsm-devel \
|
||||
gtk3-devel \
|
||||
libappindicator-devel \
|
||||
sqlite-devel \
|
||||
ffmpeg-4-libavutil-devel \
|
||||
sqlite3-devel \
|
||||
gtk3-devel\
|
||||
qrencode-devel \
|
||||
python310 \
|
||||
python3-python-dateutil \
|
||||
python3-html5lib \
|
||||
libsndfile-devel \
|
||||
libdrm \
|
||||
libdrm-devel \
|
||||
gperf \
|
||||
bison \
|
||||
flex \
|
||||
ffmpeg ffmpeg-devel \
|
||||
nodejs18 \
|
||||
ffmpeg \
|
||||
ffmpeg-devel \
|
||||
nodejs20 \
|
||||
mozilla-nss-devel \
|
||||
python-xml \
|
||||
python3-six \
|
||||
@ -85,7 +85,7 @@ RUN zypper --non-interactive install -y \
|
||||
xorg-x11-devel \
|
||||
xz \
|
||||
xkeyboard-config \
|
||||
libnotify \
|
||||
libnotify-devel \
|
||||
argon2-devel \
|
||||
libxshmfence-devel \
|
||||
xproto-devel \
|
||||
@ -102,7 +102,7 @@ RUN zypper --non-interactive install -y \
|
||||
wget \
|
||||
pipewire-devel
|
||||
|
||||
# openSUSE Leap 15.4 comes with Python 3.6 by default,
|
||||
# openSUSE Leap 15.6 comes with Python 3.6 by default,
|
||||
# but we need at least 3.7 to compile Qt 6.6.1
|
||||
RUN rm /usr/bin/python3 && ln -s /usr/bin/python3.10 /usr/bin/python3
|
||||
|
||||
@ -113,10 +113,4 @@ ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-r
|
||||
|
||||
ENV CC=gcc
|
||||
ENV CXX=g++
|
||||
|
||||
# Setting this variable so that FFmpeg gets built without pipewiregrab
|
||||
# (see daemon/contrib/bootstrap and daemon/contrib/src/ffmpeg/rules.mak)
|
||||
# We rely on PipeWire for screen sharing on Wayland, but the version available on openSUSE Leap 15.4 is too old.
|
||||
ENV DISABLE_PIPEWIRE=true
|
||||
|
||||
CMD ["/opt/build-package-rpm.sh"]
|
||||
29
extras/packaging/gnu-linux/docker/Dockerfile_ubuntu_25.04
Normal file
29
extras/packaging/gnu-linux/docker/Dockerfile_ubuntu_25.04
Normal file
@ -0,0 +1,29 @@
|
||||
FROM ubuntu:25.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get clean
|
||||
RUN apt-get update && \
|
||||
apt-get install -y -o Acquire::Retries=10 \
|
||||
devscripts \
|
||||
equivs \
|
||||
python-is-python3 \
|
||||
wget
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/prebuild-package-debian.sh /opt/prebuild-package-debian.sh
|
||||
|
||||
COPY extras/packaging/gnu-linux/rules/debian-qt/control /tmp/builddeps/debian/control
|
||||
RUN /opt/prebuild-package-debian.sh qt-deps
|
||||
|
||||
COPY extras/packaging/gnu-linux/rules/debian/control /tmp/builddeps/debian/control
|
||||
RUN /opt/prebuild-package-debian.sh jami-deps
|
||||
|
||||
# Remove the libre2-dev package in order to force Qt to build using the bundled
|
||||
# version of the RE2 library. This is necessary because the system version of the
|
||||
# library on Ubuntu 25.04 (libre2-11) is not compatible with the one used in
|
||||
# Qt 6.6.1 due to an API change:
|
||||
# https://codereview.qt-project.org/c/qt/qtwebengine/+/516094
|
||||
RUN apt-get remove -y libre2-dev libre2-11
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-debian.sh /opt/build-package-debian.sh
|
||||
CMD ["/opt/build-package-debian.sh"]
|
||||
@ -103,6 +103,8 @@ if [ -f /etc/os-release ]; then
|
||||
ENDTAG="ubuntu_24.04"
|
||||
elif [ "${UBUNTU_CODENAME}" = "oracular" ] || [ "${ID}_${VERSION_ID}" = "ubuntu_24.10" ]; then
|
||||
ENDTAG="ubuntu_24.10"
|
||||
elif [ "${UBUNTU_CODENAME}" = "plucky" ] || [ "${ID}_${VERSION_ID}" = "ubuntu_25.04" ]; then
|
||||
ENDTAG="ubuntu_25.04"
|
||||
elif [ "${ID}" = "debian" ] && \
|
||||
[ "$(command -v lsb_release)" ] && \
|
||||
[ "$(lsb_release -rs)" = "testing" ]; then
|
||||
|
||||
@ -47,7 +47,11 @@ if [ ! -f "${qt_deb_path}" ] || [ "${FORCE_REBUILD_QT}" = "true" ]; then
|
||||
|
||||
# HACK: For now on ubuntu 24.04 there is no python3.10 package
|
||||
# So create a PyEnv environment to install the required packages
|
||||
if cat /etc/os-release | grep -Eq "24.04"; then
|
||||
# NOTE: We use this on Ubuntu 25.04 and Debian 13 ("trixie") too
|
||||
# because otherwise we get a ModuleNotFoundError when building
|
||||
# Qt 6.6.1 (specifically the chromium submodule in QtWebEngine)
|
||||
# due to the version of python used (3.13) being too recent.
|
||||
if cat /etc/os-release | grep -Eq "24.04|25.04|trixie"; then
|
||||
apt-get install git gcc make python3-pip libssl-dev curl libreadline-dev -y
|
||||
curl https://pyenv.run | bash
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
|
||||
@ -101,12 +101,6 @@ if [ ! -f "${RPM_PATH}" ]; then
|
||||
# Cache the built Qt RPM package.
|
||||
if [[ "${DISTRIBUTION:0:4}" == "rhel" ]]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.el8.x86_64.rpm "${RPM_PATH}"
|
||||
elif [[ "${DISTRIBUTION}" == "fedora_36" ]]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc36.x86_64.rpm "${RPM_PATH}"
|
||||
elif [[ "${DISTRIBUTION}" == "fedora_37" ]]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc37.x86_64.rpm "${RPM_PATH}"
|
||||
elif [[ "${DISTRIBUTION}" == "fedora_38" ]]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc38.x86_64.rpm "${RPM_PATH}"
|
||||
elif [[ "${DISTRIBUTION}" == "fedora_39" ]]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc39.x86_64.rpm "${RPM_PATH}"
|
||||
elif [[ "${DISTRIBUTION}" == "fedora_40" ]]; then
|
||||
|
||||
@ -80,9 +80,6 @@ EOF
|
||||
find ./extras/packaging/gnu-linux/packages -type f -name '*.ddeb' -print0 | xargs -0 -I{} mv {} {}.deb
|
||||
|
||||
for package in ./extras/packaging/gnu-linux/packages/${DISTRIBUTION}*/*.deb; do
|
||||
echo "## signing: ${package} ##"
|
||||
dpkg-sig -k ${KEYID} --sign builder ${package}
|
||||
|
||||
echo "## including ${package} ##"
|
||||
package_name=$(dpkg -I ${package} | grep -m 1 Package: | awk '{print $2}')
|
||||
package_arch=$(dpkg -I ${package} | grep -m 1 Architecture: | awk '{print $2}')
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
From 161d28abb6784115ad71fcb6977e112e9d5756d4 Mon Sep 17 00:00:00 2001
|
||||
From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||
Date: Tue, 23 Jan 2024 15:38:34 -0500
|
||||
Subject: [PATCH] fix-fedora-fc-build
|
||||
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 0fb89c8..3a6ad6d 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -65,7 +65,7 @@ if(NOT TARGET qmsetup::library)
|
||||
)
|
||||
|
||||
# Find package again
|
||||
- find_package(qmsetup REQUIRED PATHS ${_package_path})
|
||||
+ find_package(qmsetup REQUIRED PATHS ${_package_path} ${qmsetup_cmake_path})
|
||||
|
||||
# Update import path
|
||||
set(qmsetup_DIR ${_package_path} CACHE PATH "" FORCE)
|
||||
--
|
||||
2.34.1
|
||||
|
||||
32
extras/patches/0001-fix-qm_install_package-function.patch
Normal file
32
extras/patches/0001-fix-qm_install_package-function.patch
Normal file
@ -0,0 +1,32 @@
|
||||
From 56830725e641705e0113a068ee58df7029202439 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Fran=C3=A7ois-Simon=20Fauteux-Chapleau?=
|
||||
<francois-simon.fauteux-chapleau@savoirfairelinux.com>
|
||||
Date: Wed, 2 Apr 2025 20:54:02 -0400
|
||||
Subject: [PATCH] fix qm_install_package function
|
||||
|
||||
---
|
||||
cmake/modules/private/InstallPackage.cmake | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/qmsetup/cmake/modules/private/InstallPackage.cmake b/qmsetup/cmake/modules/private/InstallPackage.cmake
|
||||
index 70174bc..f067de5 100644
|
||||
--- a/qmsetup/cmake/modules/private/InstallPackage.cmake
|
||||
+++ b/qmsetup/cmake/modules/private/InstallPackage.cmake
|
||||
@@ -101,6 +101,7 @@ function(qm_install_package _name)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -S ${_src_dir} -B ${_build_dir}
|
||||
${_extra_args} ${_build_type}
|
||||
+ "-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}"
|
||||
"-DCMAKE_INSTALL_PREFIX=${_install_dir}" ${FUNC_CONFIGURE_ARGS}
|
||||
OUTPUT_FILE ${_log_file}
|
||||
ERROR_FILE ${_log_file}
|
||||
@@ -150,4 +151,4 @@ function(qm_install_package _name)
|
||||
if(FUNC_RESULT_PATH)
|
||||
set(${FUNC_RESULT_PATH} ${_install_cmake_dir} PARENT_SCOPE)
|
||||
endif()
|
||||
-endfunction()
|
||||
\ No newline at end of file
|
||||
+endfunction()
|
||||
--
|
||||
2.34.1
|
||||
|
||||
@ -211,6 +211,7 @@ def init_submodules():
|
||||
"3rdparty/SortFilterProxyModel",
|
||||
"3rdparty/md4c",
|
||||
"3rdparty/tidy-html5",
|
||||
"3rdparty/zxing-cpp",
|
||||
]
|
||||
if execute_cmd(["git", "submodule", "update", "--init" ] + submodules,
|
||||
False):
|
||||
|
||||
@ -52,7 +52,7 @@ for ARCH in "${ARCHS[@]}"; do
|
||||
# force to build every contrib
|
||||
for dir in "$DAEMON"/contrib/src/*/; do
|
||||
PKG=$(basename -- "$dir")
|
||||
if [ "$PKG" != "sdbus-cpp" ] && [ "$PKG" != "natpmp" ] &&
|
||||
if [ "$PKG" != "sdbus-cpp" ] && [ "$PKG" != "freetype" ] &&
|
||||
[ "$PKG" != "portaudio" ] && [ "$PKG" != "pthreads" ] &&
|
||||
[ "$PKG" != "lttng-ust" ] && [ "$PKG" != "openssl" ] &&
|
||||
[ "$PKG" != "media-sdk" ] && [ "$PKG" != "jack" ] &&
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 514 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 622 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 779 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 653 KiB |
BIN
resources/images/welcome_bg_dark.jpg
Normal file
BIN
resources/images/welcome_bg_dark.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 522 KiB |
BIN
resources/images/welcome_bg_light.jpg
Normal file
BIN
resources/images/welcome_bg_light.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 528 KiB |
@ -17,6 +17,7 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtWebEngine
|
||||
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Enums 1.1
|
||||
@ -37,9 +38,12 @@ QtObject {
|
||||
readonly property bool isHidden: visibility === Window.Hidden ||
|
||||
visibility === Window.Minimized
|
||||
|
||||
// Used to store if a OngoingCallPage component is fullscreened.
|
||||
// Used to store if a CallStackView component is fullscreened.
|
||||
property bool isCallFullscreen: false
|
||||
|
||||
// Used to store if a WebEngineView component is fullscreened.
|
||||
property bool isWebFullscreen: false
|
||||
|
||||
// QWK: Provide spacing for widgets that may be occluded by the system buttons.
|
||||
property QtObject qwkSystemButtonSpacing: QtObject {
|
||||
id: qwkSystemButtonSpacing
|
||||
@ -150,9 +154,8 @@ QtObject {
|
||||
// Adds an item to the fullscreen item stack. Automatically puts
|
||||
// the main window in fullscreen mode if needed. Callbacks should be used
|
||||
// to perform component-specific tasks upon successful transitions.
|
||||
function pushFullScreenItem(item, prevParent, pushedCb, removedCb) {
|
||||
if (item === null || item === undefined
|
||||
|| priv.fullScreenItems.length >= 3) {
|
||||
function pushFullScreenItem(item, removedCb=undefined) {
|
||||
if (!item || priv.fullScreenItems.length >= 3) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -162,15 +165,13 @@ QtObject {
|
||||
// Add the item to our list and reparent it to appContainer.
|
||||
priv.fullScreenItems.push({
|
||||
"item": item,
|
||||
"prevParent": prevParent,
|
||||
"prevParent": item.parent,
|
||||
"prevAnchorsFill": item.anchors.fill,
|
||||
"removedCb": removedCb
|
||||
})
|
||||
|
||||
item.parent = appContainer
|
||||
item.anchors.fill = item.parent
|
||||
if (pushedCb) {
|
||||
pushedCb()
|
||||
}
|
||||
item.anchors.fill = appContainer
|
||||
|
||||
// Reevaluate isCallFullscreen.
|
||||
priv.fullScreenItemsChanged()
|
||||
@ -178,34 +179,37 @@ QtObject {
|
||||
|
||||
// Remove an item if specified, or by default, the top item. Automatically
|
||||
// resets the main window to windowed mode if no items remain in the stack.
|
||||
function popFullScreenItem(obj=null) {
|
||||
function popFullScreenItem(obj = undefined) {
|
||||
// Remove the item and reparent it to its original parent.
|
||||
if (obj === null) {
|
||||
obj = priv.fullScreenItems.pop()
|
||||
if (obj === undefined) {
|
||||
obj = priv.fullScreenItems.pop();
|
||||
} else {
|
||||
const index = priv.fullScreenItems.indexOf(obj);
|
||||
if (index > -1) {
|
||||
priv.fullScreenItems.splice(index, 1);
|
||||
}
|
||||
}
|
||||
if (obj !== undefined) {
|
||||
if (obj && typeof obj === 'object') {
|
||||
if (obj.item !== appWindow) {
|
||||
obj.item.anchors.fill = obj.prevAnchorsFill
|
||||
obj.item.parent = obj.prevParent
|
||||
if (obj.removedCb) {
|
||||
obj.removedCb()
|
||||
// Clear anchors first, then set parent, then reset anchors.
|
||||
obj.item.anchors.fill = undefined;
|
||||
obj.item.parent = obj.prevParent;
|
||||
obj.item.anchors.fill = obj.prevAnchorsFill;
|
||||
|
||||
// Call removed callback if it's a function.
|
||||
if (typeof obj.removedCb === 'function') {
|
||||
obj.removedCb();
|
||||
}
|
||||
}
|
||||
|
||||
// Reevaluate isCallFullscreen.
|
||||
priv.fullScreenItemsChanged()
|
||||
priv.fullScreenItemsChanged();
|
||||
}
|
||||
|
||||
// Only leave fullscreen mode if our window isn't in fullscreen
|
||||
// mode already.
|
||||
// Only leave fullscreen mode if our window isn't in fullscreen mode already.
|
||||
if (priv.fullScreenItems.length === 0 && priv.windowedVisibility !== Window.Hidden) {
|
||||
// Simply recall the last visibility state.
|
||||
visibility = priv.windowedVisibility
|
||||
visibility = priv.windowedVisibility;
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,7 +251,10 @@ QtObject {
|
||||
// When fullScreenItems is changed, we can recompute isCallFullscreen.
|
||||
onFullScreenItemsChanged: {
|
||||
isCallFullscreen = fullScreenItems
|
||||
.filter(o => o.item instanceof OngoingCallPage)
|
||||
.filter(o => o.item.objectName === "callViewLoader")
|
||||
.length
|
||||
isWebFullscreen = fullScreenItems
|
||||
.filter(o => o.item instanceof WebEngineView)
|
||||
.length
|
||||
}
|
||||
|
||||
@ -258,7 +265,7 @@ QtObject {
|
||||
function onHasCallChanged() {
|
||||
if (!CallAdapter.hasCall && isCallFullscreen) {
|
||||
priv.fullScreenItems.forEach(o => {
|
||||
if (o.item instanceof OngoingCallPage) {
|
||||
if (o.item.objectName === "callViewLoader") {
|
||||
popFullScreenItem(o)
|
||||
return
|
||||
}
|
||||
|
||||
@ -19,18 +19,15 @@ import QtQuick.Window
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import net.jami.Helpers 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
import "mainview"
|
||||
import "mainview/components"
|
||||
import "wizardview"
|
||||
import "commoncomponents"
|
||||
|
||||
import QWindowKit
|
||||
|
||||
ApplicationWindow {
|
||||
@ -63,7 +60,7 @@ ApplicationWindow {
|
||||
sourceComponent: GenericErrorsRow {
|
||||
id: genericError
|
||||
text: CurrentAccount.enabled ? JamiStrings.noNetworkConnectivity : JamiStrings.disabledAccount
|
||||
height: visible? JamiTheme.qwkTitleBarHeight : 0
|
||||
height: visible ? JamiTheme.qwkTitleBarHeight : 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,9 +84,11 @@ ApplicationWindow {
|
||||
appContainer: fullscreenContainer
|
||||
}
|
||||
// Used to manage dynamic view loading and unloading.
|
||||
property ViewManager viewManager: ViewManager {}
|
||||
property ViewManager viewManager: ViewManager {
|
||||
}
|
||||
// Used to manage the view stack and the current view.
|
||||
property ViewCoordinator viewCoordinator: ViewCoordinator {}
|
||||
property ViewCoordinator viewCoordinator: ViewCoordinator {
|
||||
}
|
||||
|
||||
// Used to prevent the window from being visible until the
|
||||
// window geometry has been restored and the view stack has
|
||||
@ -199,7 +198,6 @@ ApplicationWindow {
|
||||
if (useFrameless) {
|
||||
windowAgent.setup(appWindow);
|
||||
}
|
||||
|
||||
mainViewLoader.active = true;
|
||||
|
||||
// Dbus error handler for Linux.
|
||||
@ -216,10 +214,14 @@ ApplicationWindow {
|
||||
"confirmLabel": JamiStrings.send,
|
||||
"rejectLabel": JamiStrings.dontSend,
|
||||
"textHAlign": Text.AlignLeft,
|
||||
"textMaxWidth": 400,
|
||||
"textMaxWidth": 400
|
||||
});
|
||||
dlg.accepted.connect(function () {
|
||||
crashReporter.uploadLastReport();
|
||||
});
|
||||
dlg.rejected.connect(function () {
|
||||
crashReporter.clearReports();
|
||||
});
|
||||
dlg.accepted.connect(function () { crashReporter.uploadLastReport(); });
|
||||
dlg.rejected.connect(function () { crashReporter.clearReports(); });
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,7 +295,7 @@ ApplicationWindow {
|
||||
target: MainApplication
|
||||
|
||||
function onAboutToQuit() {
|
||||
cleanupMainView()
|
||||
cleanupMainView();
|
||||
}
|
||||
|
||||
function onCloseRequested() {
|
||||
@ -331,7 +333,7 @@ ApplicationWindow {
|
||||
});
|
||||
}
|
||||
|
||||
function presentUpdateConfirmInstallDialog(switchToBeta=false) {
|
||||
function presentUpdateConfirmInstallDialog(switchToBeta = false) {
|
||||
return viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", {
|
||||
"title": JamiStrings.updateDialogTitle,
|
||||
"infoText": switchToBeta ? JamiStrings.confirmBeta : JamiStrings.updateFound,
|
||||
@ -382,7 +384,7 @@ ApplicationWindow {
|
||||
presentUpdateInfoDialog(JamiStrings.updateNotFound);
|
||||
} else {
|
||||
// Show a dialog describing that an update were found, and offering to install it.
|
||||
presentUpdateConfirmInstallDialog()
|
||||
presentUpdateConfirmInstallDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@ -393,4 +395,20 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
onClosing: appWindow.close()
|
||||
|
||||
// Capture the inputs to the main window while the File Dialog is open
|
||||
// This is used to mitigate modality issues on Ubuntu 22.04 systems that use wayland.
|
||||
Loader {
|
||||
active: JamiQmlUtils.openFileDialogCount > 0
|
||||
sourceComponent: Popup {
|
||||
modal: true
|
||||
visible: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
width: appWindow.width
|
||||
height: appWindow.height
|
||||
background: Rectangle {
|
||||
color: "#80808080" // Semi-transparent grey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,13 +49,13 @@ QtObject {
|
||||
// right side when not in RTL and should represent the main or content-type view.
|
||||
readonly property var visibleViews: {
|
||||
if (!currentView)
|
||||
return []
|
||||
return [];
|
||||
if (isDualPane) {
|
||||
if (isInSinglePaneMode)
|
||||
return [currentView.rightPaneItem]
|
||||
return [currentView.leftPaneItem, currentView.rightPaneItem]
|
||||
return [currentView.rightPaneItem];
|
||||
return [currentView.leftPaneItem, currentView.rightPaneItem];
|
||||
}
|
||||
return [currentView]
|
||||
return [currentView];
|
||||
}
|
||||
// Aggregate this info and expose it as a single string for convenience.
|
||||
// JSON indented by 2 spaces.
|
||||
@ -64,12 +64,12 @@ QtObject {
|
||||
currentViewName: currentViewName,
|
||||
isDualPane: isDualPane,
|
||||
isInSinglePaneMode: isInSinglePaneMode,
|
||||
visibleViews: visibleViews.map(function(view) {
|
||||
return view && view.objectName || null;
|
||||
}),
|
||||
visibleViewWidths: visibleViews.map(function(view) {
|
||||
return view && view.width || null;
|
||||
}),
|
||||
visibleViews: visibleViews.map(function (view) {
|
||||
return view && view.objectName || null;
|
||||
}),
|
||||
visibleViewWidths: visibleViews.map(function (view) {
|
||||
return view && view.width || null;
|
||||
})
|
||||
};
|
||||
return JSON.stringify(info, null, 2);
|
||||
}
|
||||
@ -96,11 +96,12 @@ QtObject {
|
||||
}
|
||||
|
||||
// Create, present, and return a dialog object.
|
||||
function presentDialog(parent, path, props = {}) {
|
||||
function presentDialog(parent, path, props = {}, singleInstance = false) {
|
||||
// Open the dialog once the object is created
|
||||
return viewManager.createUniqueView(path, parent, function (obj) {
|
||||
let createFunc = singleInstance ? viewManager.createView : viewManager.createUniqueView;
|
||||
return createFunc(path, parent, function (obj, viewName) {
|
||||
const doneCb = function () {
|
||||
viewManager.destroyView(path);
|
||||
viewManager.destroyView(viewName);
|
||||
};
|
||||
if (obj.closed !== undefined) {
|
||||
obj.closed.connect(doneCb);
|
||||
|
||||
@ -59,7 +59,7 @@ QtObject {
|
||||
if (views.hasOwnProperty(viewName)) {
|
||||
// an instance of the view already exists
|
||||
if (cb !== null) {
|
||||
cb(views[viewName])
|
||||
cb(views[viewName], viewName)
|
||||
}
|
||||
return views[viewName]
|
||||
}
|
||||
@ -76,7 +76,7 @@ QtObject {
|
||||
viewName.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, "")
|
||||
viewPaths[friendlyName] = viewName
|
||||
if (cb !== null) {
|
||||
cb(obj)
|
||||
cb(obj , viewName)
|
||||
}
|
||||
return views[viewName]
|
||||
}
|
||||
@ -103,6 +103,7 @@ QtObject {
|
||||
function destroyView(path) {
|
||||
// The view may already have been destroyed.
|
||||
if (!views.hasOwnProperty(path)) {
|
||||
console.warn("View not found:", path, "Available views:", Object.keys(views))
|
||||
return false
|
||||
}
|
||||
views[path].destroy()
|
||||
|
||||
@ -22,8 +22,11 @@
|
||||
#include "systemtray.h"
|
||||
#include "lrcinstance.h"
|
||||
#include "accountlistmodel.h"
|
||||
#include "wizardviewstepmodel.h"
|
||||
#include "global.h"
|
||||
#include "api/account.h"
|
||||
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QThreadPool>
|
||||
|
||||
AccountAdapter::AccountAdapter(AppSettingsManager* settingsManager,
|
||||
SystemTray* systemTray,
|
||||
@ -111,7 +114,10 @@ AccountAdapter::createJamiAccount(const QVariantMap& settings)
|
||||
&lrcInstance_->accountModel(),
|
||||
&lrc::api::AccountModel::accountAdded,
|
||||
[this, registeredName, settings](const QString& accountId) {
|
||||
lrcInstance_->accountModel().setAvatar(accountId, settings["avatar"].toString(), true,1);
|
||||
lrcInstance_->accountModel().setAvatar(accountId,
|
||||
settings["avatar"].toString(),
|
||||
true,
|
||||
1);
|
||||
Utils::oneShotConnect(&lrcInstance_->accountModel(),
|
||||
&lrc::api::AccountModel::accountDetailsChanged,
|
||||
[this](const QString& accountId) {
|
||||
@ -159,8 +165,9 @@ AccountAdapter::createJamiAccount(const QVariantMap& settings)
|
||||
|
||||
connectFailure();
|
||||
|
||||
auto futureResult = QtConcurrent::run([this, settings] {
|
||||
QThreadPool::globalInstance()->start([this, settings] {
|
||||
lrcInstance_->accountModel().createNewAccount(lrc::api::profile::Type::JAMI,
|
||||
{},
|
||||
settings["alias"].toString(),
|
||||
settings["archivePath"].toString(),
|
||||
settings["password"].toString(),
|
||||
@ -206,14 +213,14 @@ AccountAdapter::createSIPAccount(const QVariantMap& settings)
|
||||
|
||||
connectFailure();
|
||||
|
||||
auto futureResult = QtConcurrent::run([this, settings] {
|
||||
QThreadPool::globalInstance()->start([this, settings] {
|
||||
lrcInstance_->accountModel().createNewAccount(lrc::api::profile::Type::SIP,
|
||||
{},
|
||||
settings["alias"].toString(),
|
||||
settings["archivePath"].toString(),
|
||||
"",
|
||||
"",
|
||||
settings["username"].toString(),
|
||||
{});
|
||||
settings["username"].toString());
|
||||
});
|
||||
}
|
||||
|
||||
@ -250,7 +257,7 @@ AccountAdapter::createJAMSAccount(const QVariantMap& settings)
|
||||
|
||||
connectFailure();
|
||||
|
||||
auto futureResult = QtConcurrent::run([this, settings] {
|
||||
QThreadPool::globalInstance()->start([this, settings] {
|
||||
lrcInstance_->accountModel().connectToAccountManager(settings["username"].toString(),
|
||||
settings["password"].toString(),
|
||||
settings["manager"].toString());
|
||||
@ -293,7 +300,7 @@ AccountAdapter::setCurrAccDisplayName(const QString& text)
|
||||
void
|
||||
AccountAdapter::setCurrentAccountAvatarFile(const QString& source)
|
||||
{
|
||||
auto futureResult = QtConcurrent::run([this, source]() {
|
||||
QThreadPool::globalInstance()->start([this, source]() {
|
||||
QPixmap image;
|
||||
if (!image.load(source)) {
|
||||
qWarning() << "Not a valid image file";
|
||||
@ -308,7 +315,7 @@ AccountAdapter::setCurrentAccountAvatarFile(const QString& source)
|
||||
void
|
||||
AccountAdapter::setCurrentAccountAvatarBase64(const QString& data)
|
||||
{
|
||||
auto futureResult = QtConcurrent::run([this, data]() {
|
||||
QThreadPool::globalInstance()->start([this, data]() {
|
||||
auto accountId = lrcInstance_->get_currentAccountId();
|
||||
lrcInstance_->accountModel().setAvatar(accountId, data, true, 1);
|
||||
});
|
||||
@ -339,9 +346,73 @@ AccountAdapter::exportToFile(const QString& accountId,
|
||||
void
|
||||
AccountAdapter::setArchivePasswordAsync(const QString& accountID, const QString& password)
|
||||
{
|
||||
auto futureResult = QtConcurrent::run([this, accountID, password] {
|
||||
QThreadPool::globalInstance()->start([this, accountID, password] {
|
||||
auto config = lrcInstance_->accountModel().getAccountConfig(accountID);
|
||||
config.archivePassword = password;
|
||||
lrcInstance_->accountModel().setAccountConfig(accountID, config);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
AccountAdapter::startImportAccount()
|
||||
{
|
||||
auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
|
||||
wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
|
||||
wizardModel->set_deviceLinkDetails({});
|
||||
|
||||
// This will create an account with the ARCHIVE_URL configured to start the import process.
|
||||
importAccountId_ = lrcInstance_->accountModel().createDeviceImportAccount();
|
||||
}
|
||||
|
||||
void
|
||||
AccountAdapter::provideAccountAuthentication(const QString& password)
|
||||
{
|
||||
if (importAccountId_.isEmpty()) {
|
||||
qWarning() << "No import account to provide password to";
|
||||
return;
|
||||
}
|
||||
|
||||
auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
|
||||
wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::IN_PROGRESS);
|
||||
|
||||
Utils::oneShotConnect(
|
||||
&lrcInstance_->accountModel(),
|
||||
&lrc::api::AccountModel::accountAdded,
|
||||
[this](const QString& accountId) {
|
||||
Q_EMIT lrcInstance_->accountListChanged();
|
||||
Q_EMIT accountAdded(accountId,
|
||||
lrcInstance_->accountModel().getAccountList().indexOf(accountId));
|
||||
},
|
||||
this,
|
||||
&AccountAdapter::accountCreationFailed);
|
||||
|
||||
connectFailure();
|
||||
|
||||
QThreadPool::globalInstance()->start([this, password] {
|
||||
lrcInstance_->accountModel().provideAccountAuthentication(importAccountId_, password);
|
||||
});
|
||||
}
|
||||
|
||||
QString
|
||||
AccountAdapter::getImportErrorMessage(QVariantMap details)
|
||||
{
|
||||
QString errorString = details.value("error").toString();
|
||||
if (!errorString.isEmpty() && errorString != "none") {
|
||||
auto error = lrc::api::account::mapLinkDeviceError(errorString.toStdString());
|
||||
return lrc::api::account::getLinkDeviceString(error);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void
|
||||
AccountAdapter::cancelImportAccount()
|
||||
{
|
||||
auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
|
||||
wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
|
||||
wizardModel->set_deviceLinkDetails({});
|
||||
|
||||
// Remove the account if it was created
|
||||
lrcInstance_->accountModel().removeAccount(importAccountId_);
|
||||
importAccountId_.clear();
|
||||
}
|
||||
|
||||
@ -81,6 +81,13 @@ public:
|
||||
const bool& state);
|
||||
Q_INVOKABLE QStringList getDefaultModerators(const QString& accountId);
|
||||
|
||||
// New import account / link device functions
|
||||
// import: (note: Listen for: DeviceAuthStateChanged)
|
||||
Q_INVOKABLE void startImportAccount();
|
||||
Q_INVOKABLE void provideAccountAuthentication(const QString& password = {});
|
||||
Q_INVOKABLE QString getImportErrorMessage(QVariantMap details);
|
||||
Q_INVOKABLE void cancelImportAccount();
|
||||
|
||||
Q_SIGNALS:
|
||||
// Trigger other components to reconnect account related signals.
|
||||
void accountStatusChanged(QString accountId);
|
||||
@ -98,6 +105,9 @@ private:
|
||||
|
||||
QMetaObject::Connection registeredNameSavedConnection_;
|
||||
|
||||
// The account ID of the last used import account.
|
||||
QString importAccountId_;
|
||||
|
||||
AppSettingsManager* settingsManager_;
|
||||
SystemTray* systemTray_;
|
||||
};
|
||||
|
||||
@ -77,10 +77,10 @@ struct AppVersionManager::Impl : public QObject
|
||||
auto latestVersion = latestVersionString.toULongLong();
|
||||
const QString channelStr = isBeta ? "beta" : "stable";
|
||||
const auto newVersionFound = latestVersion > currentVersion;
|
||||
qInfo().noquote() << "--------- Version info ------------"
|
||||
<< QString("\n - Current: %1 (%2)").arg(currentVersion).arg(channelStr);
|
||||
qInfo().noquote() << "--------- Version info ------------";
|
||||
qInfo().noquote() << QString("\tCurrent: \t%1 (%2)").arg(currentVersion).arg(channelStr);
|
||||
if (newVersionFound) {
|
||||
qDebug() << " - Latest: " << latestVersion;
|
||||
qInfo().noquote() << QString("\tLatest: \t%1").arg(latestVersion);
|
||||
Q_EMIT parent_.updateCheckReplyReceived(true, true);
|
||||
} else if (!quiet) {
|
||||
Q_EMIT parent_.updateCheckReplyReceived(true, false);
|
||||
|
||||
@ -345,9 +345,11 @@ AvAdapter::shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned heig
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::shareWindow(const QString& windowProcessId, const QString& windowId)
|
||||
AvAdapter::shareWindow(const QString& windowProcessId, const QString& windowId, const int fps)
|
||||
{
|
||||
auto resource = lrcInstance_->getCurrentCallModel()->getDisplay(windowProcessId, windowId);
|
||||
auto resource = lrcInstance_->getCurrentCallModel()->getDisplay(windowProcessId,
|
||||
windowId,
|
||||
fps);
|
||||
auto callId = lrcInstance_->getCurrentCallId();
|
||||
|
||||
muteCamera_ = !isCapturing();
|
||||
@ -356,7 +358,10 @@ AvAdapter::shareWindow(const QString& windowProcessId, const QString& windowId)
|
||||
}
|
||||
|
||||
QString
|
||||
AvAdapter::getSharingResource(int screenId, const QString& windowProcessId, const QString& windowId)
|
||||
AvAdapter::getSharingResource(int screenId,
|
||||
const QString& windowProcessId,
|
||||
const QString& windowId,
|
||||
const int fps)
|
||||
{
|
||||
if (screenId == -1) {
|
||||
const auto arrangementRect = getAllScreensBoundingRect();
|
||||
@ -387,7 +392,7 @@ AvAdapter::getSharingResource(int screenId, const QString& windowProcessId, cons
|
||||
rect.height()
|
||||
* screen->devicePixelRatio());
|
||||
} else if (!windowId.isEmpty()) {
|
||||
return lrcInstance_->getCurrentCallModel()->getDisplay(windowProcessId, windowId);
|
||||
return lrcInstance_->getCurrentCallModel()->getDisplay(windowProcessId, windowId, fps);
|
||||
}
|
||||
|
||||
return "";
|
||||
|
||||
@ -96,7 +96,9 @@ protected:
|
||||
Q_INVOKABLE void shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned height);
|
||||
|
||||
// Select window to display (all platforms except Wayland).
|
||||
Q_INVOKABLE void shareWindow(const QString& windowProcessId, const QString& windowId);
|
||||
Q_INVOKABLE void shareWindow(const QString& windowProcessId,
|
||||
const QString& windowId,
|
||||
const int fps = -1);
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// Share a window on Wayland.
|
||||
@ -110,7 +112,8 @@ protected:
|
||||
// Returns the screensharing resource
|
||||
Q_INVOKABLE QString getSharingResource(int screenId = -2,
|
||||
const QString& windowProcessId = "",
|
||||
const QString& key = "");
|
||||
const QString& key = "",
|
||||
const int fps = -1);
|
||||
|
||||
Q_INVOKABLE void getListWindows();
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#include "lrcinstance.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QRegularExpression>
|
||||
|
||||
class AsyncAvatarImageResponseRunnable : public AsyncImageResponseRunnable
|
||||
{
|
||||
@ -69,6 +70,16 @@ public:
|
||||
image = Utils::accountPhoto(lrcInstance_, imageId, requestedSize_);
|
||||
} else if (type == "contact") {
|
||||
image = Utils::contactPhoto(lrcInstance_, imageId, requestedSize_);
|
||||
} else if (type == "temporaryAccount") {
|
||||
// Check if imageId is a SHA-1 hash (jamiId or registered name)
|
||||
static const QRegularExpression sha1Pattern("^[0-9a-fA-F]{40}$");
|
||||
if (sha1Pattern.match(imageId).hasMatch()) {
|
||||
// If we only have a jamiId use default avatar
|
||||
image = Utils::fallbackAvatar("jami:" + imageId, QString(), requestedSize_);
|
||||
} else {
|
||||
// For registered usernames, use fallbackAvatar avatar with the name
|
||||
image = Utils::fallbackAvatar(QString(), imageId, requestedSize_);
|
||||
}
|
||||
} else {
|
||||
qWarning() << Q_FUNC_INFO << "Missing valid prefix in the image url";
|
||||
return;
|
||||
|
||||
@ -28,7 +28,8 @@ Item {
|
||||
enum Mode {
|
||||
Account,
|
||||
Contact,
|
||||
Conversation
|
||||
Conversation,
|
||||
TemporaryAccount
|
||||
}
|
||||
property int mode: Avatar.Mode.Account
|
||||
property alias sourceSize: image.sourceSize
|
||||
@ -45,6 +46,8 @@ Item {
|
||||
return 'contact';
|
||||
case Avatar.Mode.Conversation:
|
||||
return 'conversation';
|
||||
case Avatar.Mode.TemporaryAccount:
|
||||
return 'temporaryAccount';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -56,11 +56,11 @@ Popup {
|
||||
id: container
|
||||
|
||||
property color color: JamiTheme.secondaryBackgroundColor
|
||||
leftPadding: popupMargins
|
||||
bottomPadding: action1.visible || action2.visible ? 10 :popupMargins
|
||||
bottomPadding: action1.visible || action2.visible ? 10 : popupMargins
|
||||
|
||||
background: Rectangle {
|
||||
id: bgRect
|
||||
|
||||
radius: 5
|
||||
color: container.color
|
||||
layer.enabled: true
|
||||
@ -99,6 +99,7 @@ Popup {
|
||||
Label {
|
||||
id: titleText
|
||||
|
||||
Layout.leftMargin: popupMargins
|
||||
Layout.rightMargin: popupMargins
|
||||
Layout.bottomMargin: 20
|
||||
Layout.topMargin: closeButtonVisible ? 0 : 30
|
||||
@ -115,9 +116,9 @@ Popup {
|
||||
id: flickable
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
Layout.preferredHeight: Math.min(contentHeight, root.height)
|
||||
Layout.preferredWidth: contentItem.childrenRect.width
|
||||
Layout.leftMargin: popupMargins
|
||||
Layout.rightMargin: popupMargins
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
@ -126,11 +127,13 @@ Popup {
|
||||
contentItem.children: Loader {
|
||||
id: containerSubContentLoader
|
||||
}
|
||||
|
||||
ScrollBar.horizontal.visible: false
|
||||
}
|
||||
|
||||
DialogButtonBox {
|
||||
id: buttonBox
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
spacing: 1.5
|
||||
|
||||
@ -179,7 +182,7 @@ Popup {
|
||||
color: JamiTheme.transparentColor
|
||||
|
||||
// Color animation for overlay when pop up is shown.
|
||||
ColorAnimation on color {
|
||||
ColorAnimation on color {
|
||||
to: JamiTheme.popupOverlayColor
|
||||
duration: 500
|
||||
}
|
||||
|
||||
@ -126,6 +126,20 @@ Loader {
|
||||
property bool canOpen: root.transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED || isOutgoing
|
||||
property real maxMsgWidth: root.width - senderMargin - 2 * hPadding - avatarBlockWidth - buttonsLoader.width - 24 - 6 - 24
|
||||
|
||||
// Timer to update the translation bar
|
||||
Loader {
|
||||
id: timerLoader
|
||||
active: root.transferStatus === Interaction.TransferStatus.TRANSFER_ONGOING
|
||||
sourceComponent: Timer {
|
||||
interval: 1000 // Update every second
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
transferStats = MessagesAdapter.getTransferStats(transferId, root.transferStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isOutgoing: Author === CurrentAccount.uri
|
||||
showTime: root.showTime
|
||||
seq: root.seq
|
||||
@ -136,7 +150,7 @@ Loader {
|
||||
timestamp: root.timestamp
|
||||
formattedTime: root.formattedTime
|
||||
formattedDay: root.formattedTime
|
||||
extraHeight: progressBar.visible ? 18 : 0
|
||||
extraHeight: progressBar.visible ? 25 : 0
|
||||
|
||||
innerContent.children: [
|
||||
RowLayout {
|
||||
@ -212,9 +226,10 @@ Loader {
|
||||
imageColor: JamiTheme.chatviewButtonColor
|
||||
onClicked: {
|
||||
if (root.transferStatus === Interaction.TransferStatus.TRANSFER_ONGOING) {
|
||||
return MessagesAdapter.cancelFile(transferId);
|
||||
MessagesAdapter.cancelFile(transferId);
|
||||
} else {
|
||||
return MessagesAdapter.acceptFile(transferId);
|
||||
buttonsLoader.iconSource = JamiResources.connecting_black_24dp_svg;
|
||||
MessagesAdapter.acceptFile(transferId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,14 @@ FileDialog {
|
||||
signal fileAccepted(string file)
|
||||
signal filesAccepted(var files)
|
||||
|
||||
Component.onCompleted: {
|
||||
JamiQmlUtils.openFileDialogCount++;
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
JamiQmlUtils.openFileDialogCount--;
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
switch (fileMode) {
|
||||
case FileDialog.OpenFile:
|
||||
|
||||
@ -40,6 +40,9 @@ Flickable {
|
||||
orientation: Qt.Horizontal
|
||||
}
|
||||
|
||||
// HACK: remove after migration to Qt 6.7+
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Keys.onLeftPressed: horizontalScrollBar.decrease()
|
||||
Keys.onRightPressed: horizontalScrollBar.increase()
|
||||
Keys.onUpPressed: verticalScrollBar.decrease()
|
||||
|
||||
@ -32,7 +32,7 @@ Item {
|
||||
property bool validated: false
|
||||
property bool outsideClic: false
|
||||
property bool justChanged: false
|
||||
property bool clic : false
|
||||
property bool clic: false
|
||||
height: getHeight()
|
||||
|
||||
function getHeight() {
|
||||
@ -45,7 +45,6 @@ Item {
|
||||
if (usernameTextEdit.editMode) {
|
||||
usernameTextEdit.editMode = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,10 +56,16 @@ Item {
|
||||
|
||||
RoundedBorderRectangle {
|
||||
id: leftRect
|
||||
|
||||
fillColor: JamiTheme.jamiIdBackgroundColor
|
||||
Layout.preferredWidth: usernameTextEdit.visible ? childrenRect.width + JamiTheme.pushButtonMargins : childrenRect.width
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
radius: {
|
||||
radius: isRTL ? {
|
||||
"tl": 0,
|
||||
"tr": 5,
|
||||
"br": 5,
|
||||
"bl": 0
|
||||
} : {
|
||||
"tl": 5,
|
||||
"tr": 0,
|
||||
"br": 0,
|
||||
@ -105,7 +110,7 @@ Item {
|
||||
dynamicText = '';
|
||||
}
|
||||
}
|
||||
Label{
|
||||
Label {
|
||||
id: usernameLabel
|
||||
|
||||
visible: !usernameTextEdit.editMode
|
||||
@ -118,7 +123,7 @@ Item {
|
||||
Layout.fillHeight: true
|
||||
elide: Text.ElideRight
|
||||
color: JamiTheme.tintedBlue
|
||||
font.pixelSize : text.length > 16 ? JamiTheme.jamiIdSmallFontSize : JamiTheme.bigFontSize
|
||||
font.pixelSize: text.length > 16 ? JamiTheme.jamiIdSmallFontSize : JamiTheme.bigFontSize
|
||||
property string registeredName: CurrentAccount.registeredName
|
||||
property string infohash: CurrentAccount.uri
|
||||
text: (btnId.clicked && registeredName) ? registeredName : infohash
|
||||
@ -132,7 +137,12 @@ Item {
|
||||
Layout.preferredWidth: childrenRect.width + 2 * JamiTheme.pushButtonMargins
|
||||
|
||||
Layout.preferredHeight: leftRect.height
|
||||
radius: {
|
||||
radius: isRTL ? {
|
||||
"tl": 5,
|
||||
"tr": 0,
|
||||
"br": 0,
|
||||
"bl": 5
|
||||
} : {
|
||||
"tl": 0,
|
||||
"tr": 5,
|
||||
"br": 5,
|
||||
@ -228,10 +238,14 @@ Item {
|
||||
toolTipText: JamiStrings.identifierURI
|
||||
onClicked: {
|
||||
if (clicked) {
|
||||
usernameTextEdit.staticText = Qt.binding(function() {return CurrentAccount.uri} );
|
||||
usernameTextEdit.staticText = Qt.binding(function () {
|
||||
return CurrentAccount.uri;
|
||||
});
|
||||
btnId.toolTipText = JamiStrings.identifierRegisterName;
|
||||
} else {
|
||||
usernameTextEdit.staticText = Qt.binding(function() {return CurrentAccount.registeredName} );
|
||||
usernameTextEdit.staticText = Qt.binding(function () {
|
||||
return CurrentAccount.registeredName;
|
||||
});
|
||||
btnId.toolTipText = JamiStrings.identifierURI;
|
||||
}
|
||||
clicked = !clicked;
|
||||
|
||||
@ -34,6 +34,9 @@ ListView {
|
||||
attachedFlickableMoving: root.moving
|
||||
}
|
||||
|
||||
// HACK: remove after migration to Qt 6.7+
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Keys.onUpPressed: verticalScrollBar.decrease()
|
||||
Keys.onDownPressed: verticalScrollBar.increase()
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ BaseModalDialog {
|
||||
property var buttonStyles: []
|
||||
property string infoText: ""
|
||||
property var innerContentData: []
|
||||
property int buttonRoles: []
|
||||
property var buttonRoles: []
|
||||
|
||||
function openWithParameters(title, info = "") {
|
||||
root.title = title;
|
||||
|
||||
@ -94,6 +94,8 @@ MenuItem {
|
||||
|
||||
Text {
|
||||
id: contextMenuItemText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
height: parent.height
|
||||
text: itemName
|
||||
color: dangerous ? JamiTheme.redColor : isActif ? JamiTheme.textColor : JamiTheme.chatViewFooterImgColor
|
||||
|
||||
@ -171,10 +171,20 @@ ConversationsAdapter::onNewUnreadInteraction(const QString& accountId,
|
||||
if (interaction.authorUri == accountInfo.profileInfo.uri)
|
||||
return;
|
||||
auto from = accountInfo.contactModel->bestNameForContact(interaction.authorUri);
|
||||
auto body_ = interaction.body;
|
||||
QString displayedString;
|
||||
|
||||
if (interaction.type == interaction::Type::DATA_TRANSFER) {
|
||||
body_ = interaction.commit.value("displayName");
|
||||
// Add special handling for member events
|
||||
if (interaction.type == interaction::Type::CONTACT) {
|
||||
auto action = interaction.commit.value("action");
|
||||
if (action == "join") {
|
||||
displayedString = tr("%1 has joined the conversation.").arg(from);
|
||||
} else if (action == "remove") {
|
||||
displayedString = tr("%1 has left the conversation.").arg(from);
|
||||
}
|
||||
} else if (interaction.type == interaction::Type::DATA_TRANSFER) {
|
||||
displayedString = from + ": " + interaction.commit.value("displayName");
|
||||
} else {
|
||||
displayedString = from + ": " + interaction.body;
|
||||
}
|
||||
|
||||
auto preferences = accountInfo.conversationModel->getConversationPreferences(convUid);
|
||||
@ -190,7 +200,7 @@ ConversationsAdapter::onNewUnreadInteraction(const QString& accountId,
|
||||
auto notifId = QString("%1;%2;%3").arg(accountId, convUid, interactionId);
|
||||
systemTray_->showNotification(notifId,
|
||||
tr("%1 received a new message").arg(to),
|
||||
from + ": " + body_,
|
||||
displayedString,
|
||||
SystemTray::NotificationType::CHAT,
|
||||
Utils::QImageToByteArray(contactPhoto));
|
||||
|
||||
|
||||
@ -109,6 +109,9 @@ protected:
|
||||
{"client_sha", APP_VERSION_STRING},
|
||||
{"jamicore_sha", CORE_VERSION_STRING},
|
||||
{"build_id", QString(VERSION_STRING)},
|
||||
#if defined(Q_OS_WIN) && defined(BETA)
|
||||
{"build_variant", "beta"},
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -313,15 +313,15 @@ CurrentConversation::updateErrors(const QString& convId)
|
||||
auto& convInfo = optConv->get();
|
||||
for (const auto& [code, error] : convInfo.errors) {
|
||||
if (code == 1) {
|
||||
newErrors.append(tr("An error occurred while fetching this repository"));
|
||||
newErrors.append(tr("An error occurred while fetching this repository."));
|
||||
} else if (code == 2) {
|
||||
newErrors.append(tr("Unrecognized conversation mode"));
|
||||
newErrors.append(tr("Unrecognized conversation mode."));
|
||||
} else if (code == 3) {
|
||||
newErrors.append(tr("An invalid message was detected"));
|
||||
newErrors.append(tr("An invalid message was detected."));
|
||||
} else if (code == 4) {
|
||||
newErrors.append(tr("Insufficient permission to update conversation information"));
|
||||
newErrors.append(tr("Insufficient permission to update conversation information."));
|
||||
} else if (code == 5) {
|
||||
newErrors.append(tr("An error occurred while committing a new message"));
|
||||
newErrors.append(tr("An error occurred while committing a new message."));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
140
src/app/linkdevicemodel.cpp
Normal file
140
src/app/linkdevicemodel.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "linkdevicemodel.h"
|
||||
#include "lrcinstance.h"
|
||||
#include "api/accountmodel.h"
|
||||
|
||||
#include "api/account.h"
|
||||
|
||||
using namespace lrc::api::account;
|
||||
|
||||
LinkDeviceModel::LinkDeviceModel(LRCInstance* lrcInstance, QObject* parent)
|
||||
: QObject(parent)
|
||||
, lrcInstance_(lrcInstance)
|
||||
{
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::INIT));
|
||||
connect(&lrcInstance_->accountModel(),
|
||||
&lrc::api::AccountModel::addDeviceStateChanged,
|
||||
this,
|
||||
[this](const QString& accountId,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details) {
|
||||
if (operationId != operationId_)
|
||||
return;
|
||||
|
||||
auto deviceState = static_cast<DeviceAuthState>(state);
|
||||
|
||||
switch (deviceState) {
|
||||
case DeviceAuthState::CONNECTING:
|
||||
handleConnectingSignal();
|
||||
break;
|
||||
case DeviceAuthState::AUTHENTICATING:
|
||||
handleAuthenticatingSignal(Utils::mapStringStringToVariantMap(details));
|
||||
break;
|
||||
case DeviceAuthState::IN_PROGRESS:
|
||||
handleInProgressSignal();
|
||||
break;
|
||||
case DeviceAuthState::DONE:
|
||||
handleDoneSignal(Utils::mapStringStringToVariantMap(details));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::addDevice(const QString& token)
|
||||
{
|
||||
set_tokenErrorMessage("");
|
||||
auto errorMessage = QObject::tr(
|
||||
"Unrecognized new device identifier. Please follow the instructions above.");
|
||||
|
||||
if (!token.startsWith("jami-auth://") || (token.length() != 59)) {
|
||||
set_tokenErrorMessage(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t result = lrcInstance_->accountModel().addDevice(lrcInstance_->getCurrentAccountInfo().id,
|
||||
token);
|
||||
if (result > 0) {
|
||||
operationId_ = result;
|
||||
} else {
|
||||
set_tokenErrorMessage(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::handleConnectingSignal()
|
||||
{
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::CONNECTING));
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::handleAuthenticatingSignal(const QVariantMap& details)
|
||||
{
|
||||
QString peerAddress = details.value("peer_address").toString();
|
||||
set_ipAddress(peerAddress);
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::AUTHENTICATING));
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::handleInProgressSignal()
|
||||
{
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::IN_PROGRESS));
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::handleDoneSignal(const QVariantMap& details)
|
||||
{
|
||||
QString errorString = details.value("error").toString();
|
||||
if (!errorString.isEmpty() && errorString != "none") {
|
||||
auto error = mapLinkDeviceError(errorString.toStdString());
|
||||
set_linkDeviceError(getLinkDeviceString(error));
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::DONE));
|
||||
} else {
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::DONE));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::confirmAddDevice()
|
||||
{
|
||||
handleInProgressSignal();
|
||||
lrcInstance_->accountModel().confirmAddDevice(lrcInstance_->getCurrentAccountInfo().id,
|
||||
operationId_);
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::cancelAddDevice()
|
||||
{
|
||||
handleInProgressSignal();
|
||||
lrcInstance_->accountModel().cancelAddDevice(lrcInstance_->getCurrentAccountInfo().id,
|
||||
operationId_);
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::reset()
|
||||
{
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::INIT));
|
||||
|
||||
set_linkDeviceError("");
|
||||
set_ipAddress("");
|
||||
set_tokenErrorMessage("");
|
||||
}
|
||||
57
src/app/linkdevicemodel.h
Normal file
57
src/app/linkdevicemodel.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api/account.h"
|
||||
|
||||
#include "qmladapterbase.h"
|
||||
#include "qtutils.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QMap>
|
||||
|
||||
class LRCInstance;
|
||||
|
||||
class LinkDeviceModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_PROPERTY(QString, tokenErrorMessage);
|
||||
QML_PROPERTY(QString, linkDeviceError);
|
||||
QML_PROPERTY(int, deviceAuthState);
|
||||
QML_PROPERTY(QString, ipAddress);
|
||||
|
||||
public:
|
||||
explicit LinkDeviceModel(LRCInstance* lrcInstance, QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void addDevice(const QString& token);
|
||||
|
||||
Q_INVOKABLE void confirmAddDevice();
|
||||
Q_INVOKABLE void cancelAddDevice();
|
||||
Q_INVOKABLE void reset();
|
||||
|
||||
private:
|
||||
bool checkNewStateValidity(lrc::api::account::DeviceAuthState newState) const;
|
||||
void handleConnectingSignal();
|
||||
void handleAuthenticatingSignal(const QVariantMap& details);
|
||||
void handleInProgressSignal();
|
||||
void handleDoneSignal(const QVariantMap& details);
|
||||
|
||||
LRCInstance* lrcInstance_ = nullptr;
|
||||
uint32_t operationId_;
|
||||
};
|
||||
@ -80,7 +80,7 @@ main(int argc, char* argv[])
|
||||
|
||||
MainApplication app(argc, argv);
|
||||
|
||||
app.setDesktopFileName(QStringLiteral("jami"));
|
||||
app.setDesktopFileName(QStringLiteral("net.jami.Jami"));
|
||||
#if defined(Q_OS_MACOS)
|
||||
if (macutils::isMetalSupported()) {
|
||||
QQuickWindow::setGraphicsApi(QSGRendererInterface::MetalRhi);
|
||||
|
||||
@ -431,7 +431,7 @@ MainApplication::initQmlLayer()
|
||||
// Register the crash reporter as a context property in the QML engine.
|
||||
engine_->rootContext()->setContextProperty("crashReporter", crashReporter_);
|
||||
|
||||
QUrl url = u"qrc:/MainApplicationWindow.qml"_qs;
|
||||
QUrl url = QStringLiteral("qrc:/MainApplicationWindow.qml");
|
||||
#ifdef QT_DEBUG
|
||||
if (parser_.isSet("test")) {
|
||||
// List the QML files in the project source tree.
|
||||
@ -445,7 +445,7 @@ MainApplication::initQmlLayer()
|
||||
const auto testHeight = parser_.isSet("height") ? parser_.value("height").toInt() : 0;
|
||||
engine_->rootContext()->setContextProperty("testWidth", testWidth);
|
||||
engine_->rootContext()->setContextProperty("testHeight", testHeight);
|
||||
url = u"qrc:/ComponentTestWindow.qml"_qs;
|
||||
url = QStringLiteral("qrc:/ComponentTestWindow.qml");
|
||||
}
|
||||
#endif
|
||||
QObject::connect(
|
||||
|
||||
@ -136,17 +136,12 @@ Rectangle {
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "F11"
|
||||
sequence: "Esc"
|
||||
context: Qt.ApplicationShortcut
|
||||
onActivated: layoutManager.toggleWindowFullScreen()
|
||||
}
|
||||
|
||||
Keys.onPressed: function (keyEvent) {
|
||||
if (keyEvent.key === Qt.Key_Escape) {
|
||||
onActivated: {
|
||||
MessagesAdapter.replyToId = "";
|
||||
MessagesAdapter.editId = "";
|
||||
layoutManager.popFullScreenItem();
|
||||
keyEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -32,219 +32,223 @@ BaseModalDialog {
|
||||
button1.text: JamiStrings.contribute
|
||||
button2.text: JamiStrings.feedback
|
||||
|
||||
button1.onClicked: { Qt.openUrlExternally("https://jami.net/contribute/")}
|
||||
button2.onClicked: { Qt.openUrlExternally("mailto:jami@gnu.org")}
|
||||
button1.onClicked: {
|
||||
Qt.openUrlExternally("https://jami.net/contribute/");
|
||||
}
|
||||
button2.onClicked: {
|
||||
Qt.openUrlExternally("mailto:jami@gnu.org");
|
||||
}
|
||||
|
||||
popupContent: JamiFlickable {
|
||||
id: aboutPopUpScrollView
|
||||
id: aboutPopUpScrollView
|
||||
|
||||
width: aboutPopUpContentRectColumnLayout.implicitWidth
|
||||
height: Math.min(root.implicitHeight, aboutPopUpContentRectColumnLayout.implicitHeight)
|
||||
width: aboutPopUpContentRectColumnLayout.implicitWidth
|
||||
height: Math.min(root.implicitHeight, aboutPopUpContentRectColumnLayout.implicitHeight)
|
||||
|
||||
contentHeight: aboutPopUpContentRectColumnLayout.implicitHeight
|
||||
contentHeight: aboutPopUpContentRectColumnLayout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: aboutPopUpContentRectColumnLayout
|
||||
anchors.centerIn: parent
|
||||
ColumnLayout {
|
||||
id: aboutPopUpContentRectColumnLayout
|
||||
anchors.centerIn: parent
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
spacing: 10
|
||||
|
||||
ResponsiveImage {
|
||||
id: aboutPopUPJamiLogoImage
|
||||
|
||||
RowLayout{
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
spacing: 10
|
||||
Layout.margins: 10
|
||||
Layout.preferredWidth: 150
|
||||
Layout.preferredHeight: 50
|
||||
|
||||
ResponsiveImage {
|
||||
id: aboutPopUPJamiLogoImage
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.margins: 10
|
||||
Layout.preferredWidth: 150
|
||||
Layout.preferredHeight: 50
|
||||
|
||||
source: JamiTheme.darkTheme ? JamiResources.logo_jami_standard_coul_white_svg : JamiResources.logo_jami_standard_coul_svg
|
||||
}
|
||||
|
||||
Control {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
color: JamiTheme.backgroundRectangleColor
|
||||
radius: 5
|
||||
}
|
||||
|
||||
padding: 10
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 4
|
||||
TextEdit {
|
||||
id: jamiSlogansText
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: JamiTheme.menuFontSize
|
||||
font.bold: true
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
text: textMetricsjamiSlogansText.text
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
color: JamiTheme.textColor
|
||||
|
||||
TextMetrics {
|
||||
id: textMetricsjamiSlogansText
|
||||
font: jamiSlogansText.font
|
||||
text: JamiStrings.slogan
|
||||
}
|
||||
}
|
||||
TextEdit {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
font.pixelSize: JamiTheme.textFontSize
|
||||
padding: 0
|
||||
readonly property bool isBeta: AppVersionManager.isCurrentVersionBeta()
|
||||
text: {
|
||||
// HACK: Only display the version string if it has been constructed properly.
|
||||
// This is a workaround for an issue that occurs due to the way Linux
|
||||
// packaging is done, where the git repository is not available in the
|
||||
// build source at configure time, which is when the version files are
|
||||
// generated, so we prevent a "." from being displayed if the version
|
||||
// string is not available.
|
||||
var contentStr = JamiStrings.buildID + ": " + UtilsAdapter.getBuildIDStr();
|
||||
const versionStr = UtilsAdapter.getVersionStr()
|
||||
if (versionStr.length > 1) {
|
||||
contentStr += "\n" + JamiStrings.version + ": " + (isBeta ? "(Beta) " : "") + versionStr
|
||||
}
|
||||
return contentStr
|
||||
}
|
||||
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
|
||||
color: JamiTheme.faddedFontColor
|
||||
}
|
||||
}
|
||||
}
|
||||
source: JamiTheme.darkTheme ? JamiResources.logo_jami_standard_coul_white_svg : JamiResources.logo_jami_standard_coul_svg
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: jamiDeclarationHyperText
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Control {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Strangely, hoveredLink works badly when width grows too large
|
||||
Layout.maximumWidth: JamiTheme.preferredDialogWidth - 2*JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: 15
|
||||
|
||||
color: JamiTheme.textColor
|
||||
|
||||
font.pixelSize: JamiTheme.menuFontSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
text: textMetricsjamiDeclarationHyperText.text
|
||||
textFormat: TextEdit.RichText
|
||||
wrapMode: TextEdit.WordWrap
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
|
||||
TextMetrics {
|
||||
id: textMetricsjamiDeclarationHyperText
|
||||
font: jamiDeclarationHyperText.font
|
||||
text: JamiStrings.declaration
|
||||
background: Rectangle {
|
||||
color: JamiTheme.backgroundRectangleColor
|
||||
radius: 5
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
padding: 10
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 4
|
||||
TextEdit {
|
||||
id: jamiSlogansText
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
// We don't want to eat clicks on the Text.
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: JamiTheme.menuFontSize
|
||||
font.bold: true
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
text: textMetricsjamiSlogansText.text
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
color: JamiTheme.textColor
|
||||
|
||||
TextMetrics {
|
||||
id: textMetricsjamiSlogansText
|
||||
font: jamiSlogansText.font
|
||||
text: JamiStrings.slogan
|
||||
}
|
||||
}
|
||||
TextEdit {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
font.pixelSize: JamiTheme.textFontSize
|
||||
padding: 0
|
||||
readonly property bool isBeta: AppVersionManager.isCurrentVersionBeta()
|
||||
text: {
|
||||
// HACK: Only display the version string if it has been constructed properly.
|
||||
// This is a workaround for an issue that occurs due to the way Linux
|
||||
// packaging is done, where the git repository is not available in the
|
||||
// build source at configure time, which is when the version files are
|
||||
// generated, so we prevent a "." from being displayed if the version
|
||||
// string is not available.
|
||||
var contentStr = JamiStrings.buildID + ": " + UtilsAdapter.getBuildIDStr();
|
||||
const versionStr = UtilsAdapter.getVersionStr();
|
||||
if (versionStr.length > 1) {
|
||||
contentStr += "\n" + JamiStrings.version + ": " + (isBeta ? "(Beta) " : "") + versionStr;
|
||||
}
|
||||
return contentStr;
|
||||
}
|
||||
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
|
||||
color: JamiTheme.faddedFontColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: jamiNoneWarrantyHyperText
|
||||
TextEdit {
|
||||
id: jamiDeclarationHyperText
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.maximumWidth: JamiTheme.preferredDialogWidth - 2*JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: 15
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: JamiTheme.menuFontSize
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
|
||||
verticalAlignment: Text.AlignTop
|
||||
color: JamiTheme.textColor
|
||||
// Strangely, hoveredLink works badly when width grows too large
|
||||
Layout.maximumWidth: JamiTheme.preferredDialogWidth - 2 * JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: 15
|
||||
|
||||
text: textMetricsjamiNoneWarrantyHyperText.text
|
||||
textFormat: TextEdit.RichText
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
color: JamiTheme.textColor
|
||||
|
||||
TextMetrics {
|
||||
id: textMetricsjamiNoneWarrantyHyperText
|
||||
font: jamiDeclarationHyperText.font
|
||||
text: JamiStrings.noWarranty
|
||||
}
|
||||
font.pixelSize: JamiTheme.menuFontSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
text: textMetricsjamiDeclarationHyperText.text
|
||||
textFormat: TextEdit.RichText
|
||||
wrapMode: TextEdit.WordWrap
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
|
||||
TextMetrics {
|
||||
id: textMetricsjamiDeclarationHyperText
|
||||
font: jamiDeclarationHyperText.font
|
||||
text: JamiStrings.declaration
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: jamiYears
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.maximumWidth: JamiTheme.preferredDialogWidth - 2*JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: 15
|
||||
// We don't want to eat clicks on the Text.
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: JamiTheme.menuFontSize
|
||||
verticalAlignment: Text.AlignTop
|
||||
TextEdit {
|
||||
id: jamiNoneWarrantyHyperText
|
||||
|
||||
color: JamiTheme.textColor
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.maximumWidth: JamiTheme.preferredDialogWidth - 2 * JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: 15
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: JamiTheme.menuFontSize
|
||||
|
||||
text: textMetricsYears.text
|
||||
textFormat: TextEdit.RichText
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
verticalAlignment: Text.AlignTop
|
||||
color: JamiTheme.textColor
|
||||
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
text: textMetricsjamiNoneWarrantyHyperText.text
|
||||
textFormat: TextEdit.RichText
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
|
||||
TextMetrics {
|
||||
id: textMetricsYears
|
||||
font: jamiDeclarationHyperText.font
|
||||
text: JamiStrings.declarationYear + " " + '<a href="https://savoirfairelinux.com/" style="color: ' + JamiTheme.buttonTintedBlue + '">Savoir-faire Linux Inc.</a><br>'
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
TextMetrics {
|
||||
id: textMetricsjamiNoneWarrantyHyperText
|
||||
font: jamiDeclarationHyperText.font
|
||||
text: JamiStrings.noWarranty
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: projectCreditsScrollView.width + 20
|
||||
height: projectCreditsScrollView.height + 20
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
color: JamiTheme.backgroundRectangleColor
|
||||
radius: 5
|
||||
TextEdit {
|
||||
id: jamiYears
|
||||
|
||||
ProjectCreditsScrollView {
|
||||
id: projectCreditsScrollView
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.maximumWidth: JamiTheme.preferredDialogWidth - 2 * JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: 15
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: JamiTheme.preferredDialogWidth - 2*JamiTheme.preferredMarginSize
|
||||
height: 140
|
||||
anchors.margins: 10
|
||||
}
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: JamiTheme.menuFontSize
|
||||
verticalAlignment: Text.AlignTop
|
||||
|
||||
color: JamiTheme.textColor
|
||||
|
||||
text: textMetricsYears.text
|
||||
textFormat: TextEdit.RichText
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
|
||||
TextMetrics {
|
||||
id: textMetricsYears
|
||||
font: jamiDeclarationHyperText.font
|
||||
text: JamiStrings.declarationYear + " " + '<a href="https://savoirfairelinux.com/" style="color: ' + JamiTheme.buttonTintedBlue + '">Savoir-faire Linux Inc.</a><br>'
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
width: JamiTheme.preferredDialogWidth - 2 * JamiTheme.preferredMarginSize
|
||||
height: 160
|
||||
|
||||
color: JamiTheme.backgroundRectangleColor
|
||||
radius: 5
|
||||
|
||||
ProjectCreditsScrollView {
|
||||
id: projectCreditsScrollView
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: JamiTheme.preferredMarginSize
|
||||
anchors.bottomMargin: JamiTheme.preferredMarginSize
|
||||
anchors.leftMargin: JamiTheme.preferredMarginSize / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ Label {
|
||||
anchors.fill: parent
|
||||
|
||||
color: JamiTheme.backgroundColor
|
||||
Behavior on color {
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: JamiTheme.shortFadeDuration
|
||||
}
|
||||
@ -88,7 +88,6 @@ Label {
|
||||
|
||||
spacing: 10
|
||||
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
objectName: "accountComboBoxAvatar"
|
||||
@ -147,7 +146,6 @@ Label {
|
||||
|
||||
spacing: 10
|
||||
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Layout.preferredHeight: parent.height
|
||||
|
||||
JamiPushButton {
|
||||
|
||||
@ -35,6 +35,14 @@ Item {
|
||||
property int imageFillMode: 0
|
||||
property alias image: image
|
||||
|
||||
// On darkTheme changed, reload the image
|
||||
Connections {
|
||||
target: JamiTheme
|
||||
function onDarkThemeChanged() {
|
||||
updateImageSource(downloadUrl, localPath, defaultImage);
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
id: image
|
||||
objectName: "image"
|
||||
|
||||
@ -171,8 +171,10 @@ ItemDelegate {
|
||||
Connections {
|
||||
target: menuAction !== undefined ? menuAction : null
|
||||
function onTriggered() {
|
||||
if (menuAction.popupMode !== CallActionBar.ActionPopupMode.ListElement)
|
||||
itemListView.currentIndex = menuAction.listModel.getCurrentIndex();
|
||||
if (menuAction.popupMode !== CallActionBar.ActionPopupMode.ListElement) {
|
||||
var index = menuAction.listModel.currentIndex;
|
||||
itemListView.currentIndex = index !== undefined ? index : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,11 +279,11 @@ ItemDelegate {
|
||||
if (isVertical) {
|
||||
// For a vertical layout, adjust the y position to center the item vertically
|
||||
// relative to the root's height, with an additional upward offset of 18 pixels.
|
||||
y = -(implicitHeight - root.height) / 2 - 18;
|
||||
return -(implicitHeight - root.height) / 2 - 18;
|
||||
} else {
|
||||
// For non-vertical layouts, position the item fully above its normal position
|
||||
// with an upward offset of 12 pixels from its implicit height.
|
||||
y = -implicitHeight - 12;
|
||||
return -implicitHeight - 12;
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,7 +292,7 @@ ItemDelegate {
|
||||
if (isVertical) {
|
||||
// If the layout is vertical, position the item to the left of its implicit width
|
||||
// with an additional offset of 12 pixels.
|
||||
x = -implicitWidth - 12;
|
||||
return -implicitWidth - 12;
|
||||
} else {
|
||||
// Note: isn't some of this logic built into the Popup?
|
||||
|
||||
@ -309,7 +311,7 @@ ItemDelegate {
|
||||
|
||||
// If the item extends beyond the overlay, adjust x value to the left to ensure
|
||||
// it fits within the overlay, with an extra leftward margin of 24 pixels.
|
||||
x = diff > 0 ? xValue - diff - 24 : xValue;
|
||||
return diff > 0 ? xValue - diff - 24 : xValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -40,6 +40,13 @@ Item {
|
||||
onActivatedAmbiguously: CallAdapter.hangUpThisCall()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "F11"
|
||||
context: Qt.ApplicationShortcut
|
||||
enabled: CurrentConversation.hasCall && !layoutManager.isWebFullscreen
|
||||
onActivated: toggleFullScreen();
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (LRCInstance.currentAccountType !== Profile.Type.SIP)
|
||||
return;
|
||||
@ -72,14 +79,15 @@ Item {
|
||||
|
||||
function toggleFullScreen() {
|
||||
if (!layoutManager.isCallFullscreen) {
|
||||
layoutManager.pushFullScreenItem(callStackMainView.item, callStackMainView, null, null);
|
||||
layoutManager.pushFullScreenItem(callStackMainView);
|
||||
} else {
|
||||
layoutManager.removeFullScreenItem(callStackMainView.item);
|
||||
layoutManager.removeFullScreenItem(callStackMainView);
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: callStackMainView
|
||||
objectName: "callViewLoader"
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ Rectangle {
|
||||
function updateMessageDraft() {
|
||||
// Store the current files that have not been sent, if any. Do the same for the message draft.
|
||||
var filePathDraft = [];
|
||||
while(messageBar.fileContainer.filesToSendCount > 0) {
|
||||
while (messageBar.fileContainer.filesToSendCount > 0) {
|
||||
var currentIndex = messageBar.fileContainer.filesToSendListModel.index(0, 0);
|
||||
var filePath = messageBar.fileContainer.filesToSendListModel.data(currentIndex, FilesToSend.FilePath);
|
||||
filePathDraft.push(filePath);
|
||||
@ -66,7 +66,6 @@ Rectangle {
|
||||
messageBar.fileContainer.filesToSendListModel.addToPending(restoredContent["files"][i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
@ -203,7 +202,7 @@ Rectangle {
|
||||
var dlg = viewCoordinator.presentDialog(appWindow, "commoncomponents/JamiFileDialog.qml", {
|
||||
"fileMode": JamiFileDialog.OpenFiles,
|
||||
"nameFilters": [JamiStrings.allFiles]
|
||||
});
|
||||
}, true); // is a single instance
|
||||
dlg.filesAccepted.connect(function (files) {
|
||||
setFilePathsToSend(files);
|
||||
});
|
||||
|
||||
@ -65,10 +65,10 @@ ContextMenuAutoLoader {
|
||||
}
|
||||
},
|
||||
GeneralMenuItem {
|
||||
id: clearConversation
|
||||
id: deleteConversation
|
||||
|
||||
canTrigger: mode === Conversation.Mode.NON_SWARM && !hasCall && !root.isBanned
|
||||
itemName: JamiStrings.clearConversation
|
||||
itemName: JamiStrings.deleteConversation
|
||||
iconSource: JamiResources.ic_clear_24dp_svg
|
||||
onClicked: MessagesAdapter.clearConversationHistory(responsibleAccountId, responsibleConvUid)
|
||||
},
|
||||
|
||||
@ -59,10 +59,6 @@ Window {
|
||||
shortcut: "Ctrl+F"
|
||||
description: qsTr("Search bar")
|
||||
}
|
||||
ListElement {
|
||||
shortcut: "F11"
|
||||
description: qsTr("Full screen")
|
||||
}
|
||||
ListElement {
|
||||
shortcut: "Ctrl++"
|
||||
description: qsTr("Increase font size")
|
||||
@ -131,6 +127,10 @@ Window {
|
||||
shortcut: "Ctrl+Shift+D"
|
||||
description: qsTr("Decline call")
|
||||
}
|
||||
ListElement {
|
||||
shortcut: "F11"
|
||||
description: qsTr("Full screen")
|
||||
}
|
||||
ListElement {
|
||||
shortcut: "M"
|
||||
description: qsTr("Mute microphone")
|
||||
|
||||
@ -82,8 +82,9 @@ JamiFlickable {
|
||||
ScrollBar.vertical.visible: text
|
||||
ScrollBar.horizontal.visible: text
|
||||
|
||||
boundsMovement: Flickable.StopAtBounds
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
// HACK: remove after migration to Qt 6.7+
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
interactive: true
|
||||
|
||||
function resetEditableText() {
|
||||
|
||||
@ -30,6 +30,10 @@ ListView {
|
||||
id: root
|
||||
|
||||
spacing: 10
|
||||
|
||||
// HACK: remove after migration to Qt 6.7+
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
model: SortFilterProxyModel {
|
||||
id: proxyModel
|
||||
|
||||
@ -49,6 +53,12 @@ ListView {
|
||||
MessagesAdapter.startSearch(prompt, false);
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
MessagesAdapter.startSearch(prompt, true);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: researchTabBar
|
||||
function onFilterTabChange() {
|
||||
@ -56,6 +66,27 @@ ListView {
|
||||
}
|
||||
}
|
||||
|
||||
// This function will take a filtered message and further format it to fit
|
||||
// into the research panel in a coherent way. Find the first occurence of the search term in the message
|
||||
// highlight it and wrap it with numChars characters on either side.
|
||||
function formatMessage(searchTerm, message, numChars) {
|
||||
var index = message.toLowerCase().indexOf(searchTerm.toLowerCase());
|
||||
if (index === -1)
|
||||
return message;
|
||||
var prefix = message.substring(Math.max(0, index - numChars), index);
|
||||
var suffix = message.substring(index + searchTerm.length, Math.min(index + searchTerm.length + numChars, message.length));
|
||||
var before = (Math.max(0, index - numChars) === 0);
|
||||
var after = (Math.min(index + searchTerm.length + numChars, message.length) === message.length);
|
||||
var highlightedTerm = '<span style="background-color: #48ffff00">' + message.substring(index, index + searchTerm.length) + "</span>";
|
||||
var result = "";
|
||||
if (!before)
|
||||
result += "... ";
|
||||
result += prefix + highlightedTerm + suffix;
|
||||
if (!after)
|
||||
result += " ...";
|
||||
return result;
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
width: root.width
|
||||
height: msgLayout.height
|
||||
@ -75,7 +106,6 @@ ListView {
|
||||
id: timestampItem
|
||||
|
||||
showDay: true
|
||||
showTime: true
|
||||
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
||||
formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
|
||||
}
|
||||
@ -94,55 +124,56 @@ ListView {
|
||||
showPresenceIndicator: false
|
||||
mode: contentRow.isMe ? Avatar.Mode.Account : Avatar.Mode.Contact
|
||||
Layout.leftMargin: 10
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
Text {
|
||||
text: contentRow.isMe ? CurrentAccount.bestName : UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author) + " :"
|
||||
Layout.preferredWidth: myText.width
|
||||
Layout.rightMargin: 10
|
||||
Layout.leftMargin: 10
|
||||
font.pixelSize: 0
|
||||
color: JamiTheme.chatviewSecondaryInformationColor
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
TextArea {
|
||||
id: myText
|
||||
|
||||
text: Body
|
||||
text: formatMessage(prompt, Body, 100)
|
||||
readOnly: true
|
||||
|
||||
background: Rectangle {
|
||||
radius: 5
|
||||
color: JamiTheme.messageInBgColor
|
||||
}
|
||||
color: JamiTheme.textColor
|
||||
Layout.preferredWidth: msgLayout.width - avatar.width - 30 - 10
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
Layout.rightMargin: 10
|
||||
Layout.leftMargin: 10
|
||||
textFormat: TextEdit.MarkdownText
|
||||
font.pixelSize: IsEmojiOnly ? JamiTheme.chatviewEmojiSize : JamiTheme.chatviewFontSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonJumpTo
|
||||
|
||||
visible: msgHover.hovered || hovered
|
||||
anchors.top: msgLayout.top
|
||||
anchors.right: msgLayout.right
|
||||
anchors.rightMargin: 20
|
||||
anchors.topMargin: timestampItem.height - 20
|
||||
anchors.topMargin: timestampItem.height - 21
|
||||
width: buttonJumpText.width + 10
|
||||
height: buttonJumpText.height + 10
|
||||
background.visible: false
|
||||
|
||||
onClicked: {
|
||||
CurrentConversation.scrollToMsg(Id);
|
||||
}
|
||||
|
||||
Text {
|
||||
id: buttonJumpText
|
||||
|
||||
text: JamiStrings.jumpTo
|
||||
color: buttonJumpTo.hovered ? JamiTheme.blueLinkColor : JamiTheme.chatviewSecondaryInformationColor
|
||||
font.underline: buttonJumpTo.hovered
|
||||
|
||||
@ -72,6 +72,9 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
QWKSetParentHitTestVisible {
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: nameTextMetrics
|
||||
text: bestName
|
||||
|
||||
@ -145,9 +145,9 @@ Popup {
|
||||
height: 300
|
||||
|
||||
Rectangle {
|
||||
id: previewWidget
|
||||
|
||||
radius: 5
|
||||
id: previewWidget
|
||||
anchors.centerIn: parent
|
||||
height: root.isAudio ? 100 : 300
|
||||
width: 300
|
||||
@ -210,7 +210,7 @@ Popup {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout{
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
|
||||
anchors.fill: parent
|
||||
@ -258,7 +258,6 @@ Popup {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
preferredSize: btnSize
|
||||
|
||||
|
||||
imageColor: JamiTheme.whiteColor
|
||||
|
||||
source: JamiResources.record_round_black_24dp_svg
|
||||
@ -295,7 +294,6 @@ Popup {
|
||||
preferredSize: btnSize
|
||||
source: JamiResources.record_black_24dp_svg
|
||||
|
||||
|
||||
imageContainerHeight: 20
|
||||
imageContainerWidth: 20
|
||||
imageColor: JamiTheme.whiteColor
|
||||
@ -314,7 +312,7 @@ Popup {
|
||||
|
||||
onClicked: {
|
||||
root.photo = videoProvider.captureVideoFrame(VideoDevices.getDefaultDevice());
|
||||
updateState(RecordBox.States.REC_SUCCESS);
|
||||
updateState(RecordBox.States.REC_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,7 +371,12 @@ Popup {
|
||||
background: RoundedBorderRectangle {
|
||||
opacity: btnRestart.hovered ? 1 : 0.7
|
||||
fillColor: btnRestart.hovered ? JamiTheme.recordBoxHoverColor : JamiTheme.recordBoxButtonColor
|
||||
radius: {
|
||||
radius: isRTL ? {
|
||||
"tl": 0,
|
||||
"tr": 5,
|
||||
"br": 5,
|
||||
"bl": 0
|
||||
} : {
|
||||
"tl": 5,
|
||||
"tr": 0,
|
||||
"br": 0,
|
||||
@ -381,7 +384,6 @@ Popup {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onClicked: {
|
||||
if (!root.isPhoto)
|
||||
stopRecording();
|
||||
@ -415,7 +417,6 @@ Popup {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
JamiPushButton {
|
||||
id: btnSend
|
||||
|
||||
@ -436,7 +437,12 @@ Popup {
|
||||
background: RoundedBorderRectangle {
|
||||
opacity: btnSend.hovered ? 1 : 0.7
|
||||
fillColor: JamiTheme.chatViewFooterSendButtonColor //btnSend.hovered ? JamiTheme.recordBoxHoverColor : JamiTheme.recordBoxButtonColor
|
||||
radius: {
|
||||
radius: isRTL ? {
|
||||
"tl": 5,
|
||||
"tr": 0,
|
||||
"br": 0,
|
||||
"bl": 5
|
||||
} : {
|
||||
"tl": 0,
|
||||
"tr": 5,
|
||||
"br": 5,
|
||||
@ -464,9 +470,8 @@ Popup {
|
||||
repeat: true
|
||||
onTriggered: updateTimer()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,34 +44,39 @@ Window {
|
||||
property var listModel: []
|
||||
property real componentMinWidth: 350
|
||||
property real marginSize: JamiTheme.preferredMarginSize
|
||||
property real elementWidth: {
|
||||
var layoutWidth = selectScreenWindowLayout.width;
|
||||
var minSize = componentMinWidth + 2 * marginSize;
|
||||
var numberElementPerRow = Math.floor(layoutWidth / minSize);
|
||||
if (numberElementPerRow == 1 && layoutWidth > componentMinWidth * 1.5) {
|
||||
numberElementPerRow = 2;
|
||||
}
|
||||
if (showWindows)
|
||||
numberElementPerRow = Math.min(listModel.length, numberElementPerRow);
|
||||
else
|
||||
numberElementPerRow = Math.min(listModel.length + 1, numberElementPerRow);
|
||||
var spacingLength = marginSize * (numberElementPerRow + 2);
|
||||
return (layoutWidth - spacingLength) / numberElementPerRow;
|
||||
}
|
||||
|
||||
// Function to safely populate screen/window list
|
||||
function calculateRepeaterModel() {
|
||||
listModel = [];
|
||||
var newModel = [];
|
||||
var idx;
|
||||
if (!showWindows) {
|
||||
for (idx in Qt.application.screens) {
|
||||
listModel.push(JamiStrings.screen.arg(idx));
|
||||
newModel.push({
|
||||
title: JamiStrings.screen.arg(idx),
|
||||
index: parseInt(idx),
|
||||
isAllScreens: false
|
||||
});
|
||||
}
|
||||
} else {
|
||||
AvAdapter.getListWindows();
|
||||
for (idx in AvAdapter.windowsNames) {
|
||||
listModel.push(AvAdapter.windowsNames[idx]);
|
||||
newModel.push({
|
||||
title: AvAdapter.windowsNames[idx],
|
||||
index: parseInt(idx),
|
||||
isAllScreens: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add "All Screens" option for non-Windows platforms when showing screens
|
||||
if (!showWindows && Qt.application.screens.length > 1 && Qt.platform.os.toString() !== "windows") {
|
||||
newModel.unshift({
|
||||
title: JamiStrings.allScreens,
|
||||
index: -1,
|
||||
isAllScreens: true
|
||||
});
|
||||
}
|
||||
listModel = newModel;
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
@ -80,23 +85,21 @@ Window {
|
||||
if (!active) {
|
||||
selectedScreenNumber = undefined;
|
||||
}
|
||||
screenSharePreviewRepeater.model = {};
|
||||
calculateRepeaterModel();
|
||||
screenSharePreviewRepeater.model = root.listModel;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: selectScreenWindowRect
|
||||
|
||||
anchors.fill: parent
|
||||
color: JamiTheme.backgroundColor
|
||||
|
||||
ColumnLayout {
|
||||
id: selectScreenWindowLayout
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: marginSize
|
||||
|
||||
Text {
|
||||
id: titleText
|
||||
font.pointSize: JamiTheme.menuFontSize
|
||||
font.bold: true
|
||||
text: showWindows ? JamiStrings.windows : JamiStrings.screens
|
||||
@ -107,54 +110,47 @@ Window {
|
||||
|
||||
ScrollView {
|
||||
id: screenSelectionScrollView
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredWidth: selectScreenWindowLayout.width
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
Flow {
|
||||
id: screenSelectionScrollViewFlow
|
||||
GridView {
|
||||
id: screenGrid
|
||||
anchors.fill: parent
|
||||
anchors.margins: marginSize
|
||||
|
||||
// https://bugreports.qt.io/browse/QTBUG-110323
|
||||
width: screenSelectionScrollView.width
|
||||
height: screenSelectionScrollView.height
|
||||
|
||||
topPadding: marginSize
|
||||
rightPadding: marginSize
|
||||
leftPadding: marginSize
|
||||
spacing: marginSize
|
||||
|
||||
Loader {
|
||||
// Show all screens
|
||||
active: !showWindows && Qt.application.screens.length > 1 && Qt.platform.os.toString() !== "windows"
|
||||
sourceComponent: ScreenSharePreview {
|
||||
id: screenSelectionRectAll
|
||||
|
||||
elementIndex: -1
|
||||
rectTitle: JamiStrings.allScreens
|
||||
rId: AvAdapter.getSharingResource(-1)
|
||||
}
|
||||
cellWidth: {
|
||||
var cellsPerRow = Math.floor(width / (componentMinWidth + marginSize));
|
||||
cellsPerRow = Math.max(1, cellsPerRow);
|
||||
var calculatedWidth = Math.floor(width / cellsPerRow);
|
||||
return Math.max(componentMinWidth, calculatedWidth);
|
||||
}
|
||||
cellHeight: cellWidth * 3 / 4 + marginSize * 2
|
||||
|
||||
Repeater {
|
||||
id: screenSharePreviewRepeater
|
||||
model: listModel
|
||||
|
||||
model: listModel.length
|
||||
delegate: Item {
|
||||
width: screenGrid.cellWidth - marginSize
|
||||
height: screenGrid.cellHeight - marginSize
|
||||
|
||||
delegate: ScreenSharePreview {
|
||||
visible: JamiStrings.selectScreen !== modelData.title && JamiStrings.selectWindow !== modelData.title
|
||||
|
||||
ScreenSharePreview {
|
||||
id: screenItem
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height - marginSize
|
||||
|
||||
visible: JamiStrings.selectScreen !== listModel[index] && JamiStrings.selectWindow !== listModel[index]
|
||||
elementIndex: index
|
||||
rectTitle: listModel[index] ? listModel[index] : ""
|
||||
elementIndex: modelData.index
|
||||
rectTitle: modelData.title
|
||||
rId: {
|
||||
if (showWindows)
|
||||
return rId = AvAdapter.getSharingResource(-2, AvAdapter.windowsIds[index], AvAdapter.windowsNames[index]);
|
||||
return rId = AvAdapter.getSharingResource(index);
|
||||
if (modelData.isAllScreens)
|
||||
return AvAdapter.getSharingResource(-1);
|
||||
else if (showWindows)
|
||||
return AvAdapter.getSharingResource(-2, AvAdapter.windowsIds[modelData.index], AvAdapter.windowsNames[modelData.index], 1);
|
||||
return AvAdapter.getSharingResource(modelData.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,19 +159,18 @@ Window {
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: marginSize
|
||||
Layout.preferredWidth: selectScreenWindowLayout.width
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
spacing: marginSize
|
||||
|
||||
MaterialButton {
|
||||
id: selectButton
|
||||
|
||||
Layout.maximumWidth: 200
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.leftMargin: marginSize
|
||||
|
||||
enabled: selectedScreenNumber != undefined
|
||||
enabled: selectedScreenNumber !== undefined
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
|
||||
color: JamiTheme.buttonTintedBlack
|
||||
@ -193,7 +188,7 @@ Window {
|
||||
if (!showWindows)
|
||||
AvAdapter.shareEntireScreen(selectedScreenNumber);
|
||||
else {
|
||||
AvAdapter.shareWindow(AvAdapter.windowsIds[selectedScreenNumber], AvAdapter.windowsNames[selectedScreenNumber - Qt.application.screens.length]);
|
||||
AvAdapter.shareWindow(AvAdapter.windowsIds[selectedScreenNumber], AvAdapter.windowsNames[selectedScreenNumber]);
|
||||
}
|
||||
}
|
||||
root.close();
|
||||
|
||||
@ -618,7 +618,7 @@ Rectangle {
|
||||
width: JamiTheme.smartListAvatarSize
|
||||
height: JamiTheme.smartListAvatarSize
|
||||
Layout.leftMargin: JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: JamiTheme.preferredMarginSize / 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
z: -index
|
||||
opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED) ? 0.5 : 1
|
||||
|
||||
@ -632,7 +632,7 @@ Rectangle {
|
||||
id: nameTextEdit
|
||||
|
||||
Layout.preferredHeight: JamiTheme.preferredFieldHeight
|
||||
Layout.topMargin: JamiTheme.preferredMarginSize / 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
eText: UtilsAdapter.getContactBestName(CurrentAccount.id, MemberUri)
|
||||
@ -654,8 +654,7 @@ Rectangle {
|
||||
id: roleLabel
|
||||
|
||||
Layout.preferredHeight: JamiTheme.preferredFieldHeight
|
||||
Layout.topMargin: JamiTheme.preferredMarginSize / 2
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
eText: {
|
||||
if (MemberRole === Member.Role.ADMIN)
|
||||
return JamiStrings.administrator;
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
|
||||
@ -83,7 +83,8 @@ Item {
|
||||
function onDonationCampaignSettingsChanged() {
|
||||
// Changing any of the donation campaign settings will trigger a recompute
|
||||
// of the banner visibility.
|
||||
updateIsDonationBannerVisible(); }
|
||||
updateIsDonationBannerVisible();
|
||||
}
|
||||
}
|
||||
|
||||
function updateIsDonationBannerVisible() {
|
||||
@ -99,4 +100,7 @@ Item {
|
||||
const now = new Date();
|
||||
return isVisible && now < endDate && now >= startDate;
|
||||
}
|
||||
|
||||
// Track if a fileDialog is opened. Is int to account for eventual future features including multiple FileDialog
|
||||
property int openFileDialogCount: 0
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ Item {
|
||||
property string buildID: qsTr("Build ID")
|
||||
property string version: qsTr("Version")
|
||||
property string declarationYear: "Copyright © 2015–2025"
|
||||
property string slogan: "Astarte"
|
||||
property string slogan: "Εἰρήνη"
|
||||
property string declaration: qsTr('Jami, a GNU package, is software for universal and distributed peer-to-peer communication that respects the freedom and privacy of its users. Visit <a href="https://jami.net" style="color: ' + JamiTheme.buttonTintedBlue + '">jami.net</a>' + ' to learn more.')
|
||||
property string noWarranty: qsTr('This program comes with absolutely no warranty. See the <a href="https://www.gnu.org/licenses/gpl-3.0.html" style="color: ' + JamiTheme.buttonTintedBlue + '">GNU General Public License</a>, version 3 or later for details.')
|
||||
property string contribute: qsTr('Contribute')
|
||||
@ -70,6 +70,21 @@ Item {
|
||||
property string transferThisCall: qsTr("Transfer this call")
|
||||
property string transferTo: qsTr("Transfer to")
|
||||
|
||||
// Device import/linking
|
||||
property string scanToImportAccount: qsTr("To continue the import account operation, scan the following QR code on the source device.")
|
||||
property string waitingForToken: qsTr("Please wait…")
|
||||
property string scanQRCode: qsTr("Scan QR code")
|
||||
property string connectingToDevice: qsTr("Action required. Please confirm account on the source device.")
|
||||
property string confirmAccountImport: qsTr("Authenticating device")
|
||||
property string transferringAccount: qsTr("Transferring account…")
|
||||
property string cantScanQRCode: qsTr("If you are unable to scan the QR code, enter the following token on the source device.")
|
||||
property string optionConfirm: qsTr("Confirm")
|
||||
property string optionTryAgain: qsTr("Try again")
|
||||
property string importFailed: qsTr("An error occurred while importing the account.")
|
||||
property string importFromAnotherAccount: qsTr("Import from another account")
|
||||
property string connectToAccount: qsTr("Connect to account")
|
||||
property string authenticationError: qsTr("An authentication error occurred while linking the device. Please check credentials and try again.")
|
||||
|
||||
// AccountMigrationDialog
|
||||
property string authenticationRequired: qsTr("Authentication required")
|
||||
property string migrationReason: qsTr("Your session has expired or been revoked on this device. Please enter your password.")
|
||||
@ -264,7 +279,7 @@ Item {
|
||||
// ConversationContextMenu
|
||||
property string startAudioCall: qsTr("Start audio call")
|
||||
property string startVideoCall: qsTr("Start video call")
|
||||
property string clearConversation: qsTr("Clear conversation")
|
||||
property string deleteConversation: qsTr("Delete conversation")
|
||||
property string confirmAction: qsTr("Confirm action")
|
||||
property string removeConversation: qsTr("Leave conversation")
|
||||
property string confirmLeaveConversation: qsTr("Do you want to leave this conversation?")
|
||||
@ -520,10 +535,10 @@ Item {
|
||||
property string enableAutoUpdates: qsTr("Enable/Disable automatic updates")
|
||||
property string updatesTitle: qsTr("Updates")
|
||||
property string updateDialogTitle: qsTr("Update")
|
||||
property string updateFound: qsTr("A new version of Jami is available.\nDo you want to update Jami now?\nTo continue, click Update.")
|
||||
property string updateNotFound: qsTr("No new version of Jami was found")
|
||||
property string updateCheckError: qsTr("An error occurred while checking for a new version.")
|
||||
property string updateNetworkError: qsTr("A network error occurred.")
|
||||
property string updateFound: qsTr("A new version of the Jami application is available. Do you want to update now? To continue, click Update.")
|
||||
property string updateNotFound: qsTr("The application is up to date.")
|
||||
property string updateCheckError: qsTr("An error occurred while checking for updates.")
|
||||
property string updateNetworkError: qsTr("A network error occurred while checking for updates.")
|
||||
property string updateSSLError: qsTr("An SSL error occurred.")
|
||||
property string updateDownloadCanceled: qsTr("Installer download was canceled by user.")
|
||||
property string updateDownloading: "Downloading"
|
||||
@ -579,29 +594,23 @@ Item {
|
||||
// ImportFromDevicePage
|
||||
property string importButton: qsTr("Import")
|
||||
property string pin: qsTr("Enter the PIN code")
|
||||
property string importFromDeviceDescription: qsTr("A PIN code is required to use an existing Jami account on this device.")
|
||||
property string importStep1: qsTr("Step 1")
|
||||
property string importStep2: qsTr("Step 2")
|
||||
property string importStep3: qsTr("Step 3")
|
||||
property string importStep4: qsTr("Step 4")
|
||||
property string importStep1Desc: qsTr("Open the manage account tab in the settings of the previous device.")
|
||||
property string importStep2Desc: qsTr("Select the account to link.")
|
||||
property string importStep3Desc: qsTr("Select “Link new device.”")
|
||||
property string importStep4Desc: qsTr("The PIN code will expire in 10 minutes.")
|
||||
property string importPasswordDesc: qsTr("Fill if the account is password-encrypted.")
|
||||
|
||||
// LinkDevicesDialog
|
||||
property string pinTimerInfos: qsTr("The PIN code and the account password should be entered in the device within 10 minutes.")
|
||||
property string close: qsTr("Close")
|
||||
property string enterAccountPassword: qsTr("Enter account password")
|
||||
property string enterPasswordPinCode: qsTr("This account is password encrypted, enter the password to generate a PIN code.")
|
||||
property string addDevice: qsTr("Add Device")
|
||||
property string pinExpired: qsTr("PIN code has expired.")
|
||||
property string onAnotherDevice: qsTr("On another device")
|
||||
property string onAnotherDeviceInstruction: qsTr("Install and launch Jami, select “Import from another device” and scan the QR code.")
|
||||
property string linkNewDevice: qsTr("Link new device")
|
||||
property string linkingInstructions: qsTr("In Jami, scan the QR code or manually enter the PIN code.")
|
||||
property string pinValidity: qsTr("The PIN code will expire in: ")
|
||||
property string linkDeviceConnecting: qsTr("Connecting to your new device…")
|
||||
property string linkDeviceInProgress: qsTr("The export account operation to the new device is in progress.\nPlease confirm the import on the new device.")
|
||||
property string linkDeviceScanQR: qsTr("On the new device, initiate a new account.\nSelect Add account -> Connect from another device.\nWhen ready, scan the QR code.")
|
||||
property string linkDeviceEnterManually: qsTr("Alternatively you could enter the authentication code manually.")
|
||||
property string linkDeviceEnterCodePlaceholder: qsTr("Enter authentication code")
|
||||
property string linkDeviceAllSet: qsTr("The account was imported successfully.")
|
||||
property string linkDeviceFoundAddress: qsTr("New device found at address below. Is that you?\nClicking on confirm will continue transfering account.")
|
||||
property string linkDeviceNewDeviceIP: qsTr("New device IP address: %1")
|
||||
property string linkDeviceCloseWarningTitle: qsTr("Do you want to exit?")
|
||||
property string linkDeviceCloseWarningMessage: qsTr("Exiting will cancel the import account operation.")
|
||||
|
||||
// PasswordDialog
|
||||
property string enterPassword: qsTr("Enter password")
|
||||
@ -741,7 +750,7 @@ Item {
|
||||
property string removeDefaultModerator: qsTr("Remove default moderator")
|
||||
|
||||
// Daemon reconnection
|
||||
property string reconnectDaemon: qsTr("Reconnection of the Jami daemon (jamid) is in progress. Please wait…")
|
||||
property string reconnectDaemon: qsTr("Jami daemon (jamid) reconnection is in progress. Please wait…")
|
||||
property string reconnectionFailed: qsTr("An error occurred while reconnecting to the Jami daemon (jamid).\nThe application will now exit.")
|
||||
|
||||
// Message view
|
||||
|
||||
@ -573,7 +573,7 @@ Item {
|
||||
property real welcomeGridWidth: 3 * JamiTheme.tipBoxWidth + 2 * JamiTheme.welcomePageSpacing
|
||||
property real welcomeThirdGridWidth: (welcomeGridWidth - JamiTheme.welcomePageSpacing) / 3
|
||||
property real welcomeShortGridWidth: 2 * JamiTheme.tipBoxWidth + JamiTheme.welcomePageSpacing
|
||||
readonly property string welcomeBg: darkTheme ? JamiResources.background_don_dark_jpg : JamiResources.background_don_white_jpg
|
||||
readonly property string welcomeBg: darkTheme ? JamiResources.welcome_bg_dark_jpg : JamiResources.welcome_bg_light_jpg
|
||||
property color welcomeBlockColor: darkTheme ? "#4D000000" : "#4DFFFFFF"
|
||||
|
||||
// WizardView Advanced Account Settings
|
||||
|
||||
@ -36,6 +36,8 @@ translateErrorCode(QNetworkReply::NetworkError error)
|
||||
static auto inRange = [](int value, int min, int max) -> bool {
|
||||
return (value >= min && value <= max);
|
||||
};
|
||||
if (error == QNetworkReply::OperationCanceledError)
|
||||
return NetworkManager::CANCELED;
|
||||
if (inRange(error, 1, 199))
|
||||
return NetworkManager::NETWORK_ERROR;
|
||||
if (inRange(error, 201, 201))
|
||||
@ -187,8 +189,8 @@ NetworkManager::downloadFile(const QUrl& url,
|
||||
[this, uuid, reply](QNetworkReply::NetworkError error) {
|
||||
reply->disconnect();
|
||||
resetDownload(uuid);
|
||||
qWarning() << Q_FUNC_INFO
|
||||
<< QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(error);
|
||||
qDebug() << Q_FUNC_INFO
|
||||
<< QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(error);
|
||||
Q_EMIT errorOccurred(translateErrorCode(error));
|
||||
});
|
||||
|
||||
@ -215,7 +217,9 @@ void
|
||||
NetworkManager::cancelDownload(int replyId)
|
||||
{
|
||||
if (downloadReplies_.value(replyId) != NULL) {
|
||||
Q_EMIT errorOccurred(GetError::CANCELED);
|
||||
// Aborting the download will trigger the emission of a QNetworkReply error
|
||||
// (`QNetworkReply::OperationCanceledError`), and be caught, translated to our internal
|
||||
// error `GetError::CANCELED`, and re-emitted.
|
||||
downloadReplies_[replyId]->abort();
|
||||
resetDownload(replyId);
|
||||
}
|
||||
|
||||
@ -62,6 +62,8 @@
|
||||
#include "pluginlistpreferencemodel.h"
|
||||
#include "preferenceitemlistmodel.h"
|
||||
#include "wizardviewstepmodel.h"
|
||||
#include "linkdevicemodel.h"
|
||||
#include "qrcodescannermodel.h"
|
||||
|
||||
#include "api/peerdiscoverymodel.h"
|
||||
#include "api/codecmodel.h"
|
||||
@ -179,6 +181,18 @@ registerTypes(QQmlEngine* engine,
|
||||
QQmlEngine::setObjectOwnership(pluginStoreListModel, QQmlEngine::CppOwnership);
|
||||
REG_QML_SINGLETON<PluginStoreListModel>(REG_MODEL, "PluginStoreListModel", CREATE(pluginStoreListModel));
|
||||
|
||||
// WizardViewStepModel
|
||||
auto wizardViewStepModel = new WizardViewStepModel(lrcInstance, settingsManager, app);
|
||||
qApp->setProperty("WizardViewStepModel", QVariant::fromValue(wizardViewStepModel));
|
||||
QQmlEngine::setObjectOwnership(wizardViewStepModel, QQmlEngine::CppOwnership);
|
||||
REG_QML_SINGLETON<WizardViewStepModel>(REG_MODEL, "WizardViewStepModel", CREATE(wizardViewStepModel));
|
||||
|
||||
// LinkDeviceModel
|
||||
auto linkdevicemodel = new LinkDeviceModel(lrcInstance);
|
||||
qApp->setProperty("LinkDeviceModel", QVariant::fromValue(linkdevicemodel));
|
||||
QQmlEngine::setObjectOwnership(linkdevicemodel, QQmlEngine::CppOwnership);
|
||||
REG_QML_SINGLETON<LinkDeviceModel>(REG_MODEL, "LinkDeviceModel", CREATE(linkdevicemodel));
|
||||
|
||||
// Register app-level objects that are used by QML created objects.
|
||||
// These MUST be set prior to loading the initial QML file, in order to
|
||||
// be available to the QML adapter class factory creation methods.
|
||||
@ -189,6 +203,7 @@ registerTypes(QQmlEngine* engine,
|
||||
qApp->setProperty("PreviewEngine", QVariant::fromValue(previewEngine));
|
||||
|
||||
// qml adapter registration
|
||||
QML_REGISTERSINGLETON_TYPE(NS_HELPERS, QRCodeScannerModel);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_HELPERS, AvatarRegistry);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, AccountAdapter);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CallAdapter);
|
||||
@ -205,7 +220,6 @@ registerTypes(QQmlEngine* engine,
|
||||
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, TipsModel);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, VideoDevices);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CurrentAccountToMigrate);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_MODELS, WizardViewStepModel);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_HELPERS, ImageDownloader);
|
||||
|
||||
// TODO: remove these
|
||||
@ -263,12 +277,12 @@ registerTypes(QQmlEngine* engine,
|
||||
// Enums
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, Settings)
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, NetworkManager)
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, WizardViewStepModel)
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, DeviceItemListModel)
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, ModeratorListModel)
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, VideoInputDeviceModel)
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, VideoFormatResolutionModel)
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, VideoFormatFpsModel)
|
||||
QML_REGISTERUNCREATABLE(NS_ENUMS, DeviceAuthStateEnum)
|
||||
|
||||
engine->addImageProvider(QLatin1String("qrImage"), new QrImageProvider(lrcInstance));
|
||||
engine->addImageProvider(QLatin1String("avatarimage"), new AvatarImageProvider(lrcInstance));
|
||||
|
||||
63
src/app/qrcodescannermodel.cpp
Normal file
63
src/app/qrcodescannermodel.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qrcodescannermodel.h"
|
||||
|
||||
#include <Barcode.h>
|
||||
#include <MultiFormatReader.h>
|
||||
#include <ReadBarcode.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
QRCodeScannerModel::QRCodeScannerModel(QObject* parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
QString
|
||||
QRCodeScannerModel::scanImage(const QImage& image)
|
||||
{
|
||||
if (image.isNull())
|
||||
return QString();
|
||||
|
||||
// Convert QImage to grayscale and get raw data
|
||||
QImage grayImage = image.convertToFormat(QImage::Format_Grayscale8);
|
||||
int width = grayImage.width();
|
||||
int height = grayImage.height();
|
||||
|
||||
try {
|
||||
// Create ZXing image
|
||||
ZXing::ImageView imageView(grayImage.bits(), width, height, ZXing::ImageFormat::Lum);
|
||||
|
||||
// Configure reader
|
||||
ZXing::ReaderOptions options;
|
||||
options.setTryHarder(true);
|
||||
options.setTryRotate(true);
|
||||
options.setFormats(ZXing::BarcodeFormat::QRCode);
|
||||
|
||||
// Try to detect QR code
|
||||
auto result = ZXing::ReadBarcode(imageView, options);
|
||||
|
||||
if (result.isValid()) {
|
||||
QString text = QString::fromStdString(result.text());
|
||||
return text;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
qWarning() << "QR code scanning error:" << e.what();
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
39
src/app/qrcodescannermodel.h
Normal file
39
src/app/qrcodescannermodel.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include <QQmlEngine> // QML registration
|
||||
|
||||
class QRCodeScannerModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static QRCodeScannerModel* create(QQmlEngine*, QJSEngine*)
|
||||
{
|
||||
return new QRCodeScannerModel();
|
||||
}
|
||||
|
||||
explicit QRCodeScannerModel(QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE QString scanImage(const QImage& image);
|
||||
};
|
||||
@ -18,7 +18,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickimageproviderbase.h"
|
||||
#include "accountlistmodel.h"
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
|
||||
@ -281,6 +281,9 @@ SidePanelBase {
|
||||
clip: true
|
||||
contentHeight: contentItem.childrenRect.height
|
||||
|
||||
// HACK: remove after migration to Qt 6.7+
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
model: getHeaders()
|
||||
delegate: ColumnLayout {
|
||||
id: col
|
||||
@ -329,6 +332,9 @@ SidePanelBase {
|
||||
clip: true
|
||||
visible: isChildSelected
|
||||
|
||||
// HACK: remove after migration to Qt 6.7+
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
model: modelData.children
|
||||
delegate: ColumnLayout {
|
||||
id: childCol
|
||||
|
||||
@ -25,7 +25,7 @@ import "../../commoncomponents"
|
||||
SettingsPageBase {
|
||||
id: root
|
||||
|
||||
property int itemWidth: 188
|
||||
property int itemWidth: 250
|
||||
title: JamiStrings.audio
|
||||
|
||||
flickableContent: ColumnLayout {
|
||||
@ -45,16 +45,34 @@ SettingsPageBase {
|
||||
target: UtilsAdapter
|
||||
|
||||
function onChangeLanguage() {
|
||||
inputAudioModel.reset();
|
||||
outputAudioModel.reset();
|
||||
ringtoneAudioModel.reset();
|
||||
rootLayout.resetDeviceModels();
|
||||
rootLayout.resetDeviceIndices();
|
||||
}
|
||||
}
|
||||
|
||||
function resetDeviceModels() {
|
||||
inputAudioModel.reset();
|
||||
outputAudioModel.reset();
|
||||
ringtoneAudioModel.reset();
|
||||
}
|
||||
|
||||
function resetDeviceIndices() {
|
||||
inputComboBoxSetting.modelIndex = inputComboBoxSetting.comboModel.getCurrentIndex();
|
||||
outputComboBoxSetting.modelIndex = outputComboBoxSetting.comboModel.getCurrentIndex();
|
||||
ringtoneComboBoxSetting.modelIndex = ringtoneComboBoxSetting.comboModel.getCurrentIndex();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AvAdapter
|
||||
|
||||
function onAudioDeviceListChanged(inputs, outputs) {
|
||||
rootLayout.resetDeviceModels();
|
||||
rootLayout.resetDeviceIndices();
|
||||
}
|
||||
}
|
||||
|
||||
function populateAudioSettings() {
|
||||
inputComboBoxSetting.modelIndex = inputComboBoxSetting.comboModel.getCurrentIndex();
|
||||
outputComboBoxSetting.modelIndex = outputComboBoxSetting.comboModel.getCurrentIndex();
|
||||
ringtoneComboBoxSetting.modelIndex = ringtoneComboBoxSetting.comboModel.getCurrentIndex();
|
||||
rootLayout.resetDeviceIndices();
|
||||
if (audioManagerComboBoxSetting.comboModel.rowCount() > 0) {
|
||||
audioManagerComboBoxSetting.modelIndex = audioManagerComboBoxSetting.comboModel.getCurrentSettingIndex();
|
||||
}
|
||||
|
||||
@ -37,6 +37,9 @@ ListView {
|
||||
spacing: 5
|
||||
cacheBuffer: 10
|
||||
|
||||
// HACK: remove after migration to Qt 6.7+
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
property int rota: 0
|
||||
|
||||
header: Rectangle {
|
||||
@ -141,6 +144,9 @@ ListView {
|
||||
|
||||
model: Count
|
||||
|
||||
// HACK: remove after migration to Qt 6.7+
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
delegate: RowLayout {
|
||||
id: rowLayoutDelegate
|
||||
height: 40
|
||||
|
||||
@ -20,6 +20,8 @@ import QtQuick.Layouts
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import Qt.labs.platform
|
||||
import "../../commoncomponents"
|
||||
import "../../mainview/components"
|
||||
|
||||
@ -32,388 +34,363 @@ BaseModalDialog {
|
||||
|
||||
property bool darkTheme: UtilsAdapter.useApplicationTheme()
|
||||
|
||||
popupContent: StackLayout {
|
||||
id: stackedWidget
|
||||
autoClose: false
|
||||
closeButtonVisible: false
|
||||
|
||||
function setGeneratingPage() {
|
||||
if (passwordEdit.length === 0 && CurrentAccount.hasArchivePassword) {
|
||||
setExportPage(NameDirectory.ExportOnRingStatus.WRONG_PASSWORD, "");
|
||||
return;
|
||||
}
|
||||
stackedWidget.currentIndex = exportingSpinnerPage.pageIndex;
|
||||
spinnerMovie.playing = true;
|
||||
timerForExport.restart();
|
||||
// Function to check if dialog can be closed directly
|
||||
function canCloseDirectly() {
|
||||
return LinkDeviceModel.deviceAuthState === DeviceAuthStateEnum.INIT ||
|
||||
LinkDeviceModel.deviceAuthState === DeviceAuthStateEnum.DONE
|
||||
}
|
||||
|
||||
// Close button. Use custom close button to show a confirmation dialog.
|
||||
JamiPushButton {
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
topMargin: 5
|
||||
rightMargin: 5
|
||||
}
|
||||
|
||||
function setExportPage(status, pin) {
|
||||
if (status === NameDirectory.ExportOnRingStatus.SUCCESS) {
|
||||
infoLabel.success = true;
|
||||
pinRectangle.visible = true
|
||||
exportedPIN.text = pin;
|
||||
Layout.preferredHeight: 20
|
||||
Layout.preferredWidth: 20
|
||||
|
||||
imageColor: hovered ? JamiTheme.textColor : JamiTheme.buttonTintedGreyHovered
|
||||
normalColor: "transparent"
|
||||
|
||||
source: JamiResources.round_close_24dp_svg
|
||||
onClicked: {
|
||||
if (canCloseDirectly()) {
|
||||
root.close();
|
||||
} else {
|
||||
infoLabel.success = false;
|
||||
infoLabel.visible = true;
|
||||
switch (status) {
|
||||
case NameDirectory.ExportOnRingStatus.WRONG_PASSWORD:
|
||||
infoLabel.text = JamiStrings.incorrectPassword;
|
||||
break;
|
||||
case NameDirectory.ExportOnRingStatus.NETWORK_ERROR:
|
||||
infoLabel.text = JamiStrings.linkDeviceNetWorkError;
|
||||
break;
|
||||
case NameDirectory.ExportOnRingStatus.INVALID:
|
||||
infoLabel.text = JamiStrings.somethingWentWrong;
|
||||
break;
|
||||
}
|
||||
}
|
||||
stackedWidget.currentIndex = exportingInfoPage.pageIndex;
|
||||
stackedWidget.height = exportingLayout.implicitHeight;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timerForExport
|
||||
|
||||
repeat: false
|
||||
interval: 200
|
||||
|
||||
onTriggered: {
|
||||
AccountAdapter.model.exportOnRing(LRCInstance.currentAccountId, passwordEdit.dynamicText);
|
||||
confirmCloseDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NameDirectory
|
||||
MessageDialog {
|
||||
id: confirmCloseDialog
|
||||
|
||||
function onExportOnRingEnded(status, pin) {
|
||||
stackedWidget.setExportPage(status, pin);
|
||||
}
|
||||
text: JamiStrings.linkDeviceCloseWarningTitle
|
||||
informativeText: JamiStrings.linkDeviceCloseWarningMessage
|
||||
buttons: MessageDialog.Ok | MessageDialog.Cancel
|
||||
|
||||
onOkClicked: function(button) {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
if (CurrentAccount.hasArchivePassword) {
|
||||
stackedWidget.currentIndex = enterPasswordPage.pageIndex;
|
||||
} else {
|
||||
setGeneratingPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
popupContent: Item {
|
||||
id: content
|
||||
width: 400
|
||||
height: 450
|
||||
|
||||
// Index = 0
|
||||
Item {
|
||||
id: enterPasswordPage
|
||||
// Scrollable container for StackLayout
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
readonly property int pageIndex: 0
|
||||
anchors.fill: parent
|
||||
|
||||
Component.onCompleted: passwordEdit.forceActiveFocus()
|
||||
anchors.leftMargin: 20
|
||||
anchors.rightMargin: 20
|
||||
anchors.bottomMargin: 20
|
||||
clip: true
|
||||
|
||||
onHeightChanged: {
|
||||
stackedWidget.height = passwordLayout.implicitHeight
|
||||
}
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
contentHeight: stackLayout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: passwordLayout
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
anchors.centerIn: parent
|
||||
StackLayout {
|
||||
id: stackLayout
|
||||
width: Math.min(scrollView.width, scrollView.availableWidth)
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.maximumWidth: root.width - 4 * JamiTheme.preferredMarginSize
|
||||
wrapMode: Text.Wrap
|
||||
currentIndex: scanAndEnterCodeView.index
|
||||
|
||||
text: JamiStrings.enterPasswordPinCode
|
||||
color: JamiTheme.textColor
|
||||
font.pointSize: JamiTheme.textFontSize
|
||||
font.kerning: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
Connections {
|
||||
target: LinkDeviceModel
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 10
|
||||
Layout.leftMargin: JamiTheme.cornerIconSize
|
||||
Layout.rightMargin: JamiTheme.cornerIconSize
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
Layout.bottomMargin: JamiTheme.preferredMarginSize
|
||||
|
||||
PasswordTextEdit {
|
||||
id: passwordEdit
|
||||
|
||||
firstEntry: true
|
||||
placeholderText: JamiStrings.password
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
|
||||
KeyNavigation.up: btnConfirm
|
||||
KeyNavigation.down: KeyNavigation.up
|
||||
|
||||
onDynamicTextChanged: {
|
||||
btnConfirm.enabled = dynamicText.length > 0;
|
||||
btnConfirm.hoverEnabled = dynamicText.length > 0;
|
||||
}
|
||||
onAccepted: btnConfirm.clicked()
|
||||
}
|
||||
|
||||
JamiPushButton {
|
||||
id: btnConfirm
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
height: 36
|
||||
width: 36
|
||||
|
||||
hoverEnabled: false
|
||||
enabled: false
|
||||
|
||||
imageColor: JamiTheme.secondaryBackgroundColor
|
||||
hoveredColor: JamiTheme.buttonTintedBlueHovered
|
||||
source: JamiResources.check_black_24dp_svg
|
||||
normalColor: JamiTheme.tintedBlue
|
||||
|
||||
onClicked: stackedWidget.setGeneratingPage()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Index = 1
|
||||
Item {
|
||||
id: exportingSpinnerPage
|
||||
|
||||
readonly property int pageIndex: 1
|
||||
|
||||
onHeightChanged: {
|
||||
stackedWidget.height = spinnerLayout.implicitHeight
|
||||
}
|
||||
onWidthChanged: stackedWidget.width = exportingLayout.implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: spinnerLayout
|
||||
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
anchors.centerIn: parent
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
text: JamiStrings.linkDevice
|
||||
color: JamiTheme.textColor
|
||||
font.pointSize: JamiTheme.headerFontSize
|
||||
font.kerning: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
id: spinnerMovie
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Layout.preferredWidth: 30
|
||||
Layout.preferredHeight: 30
|
||||
|
||||
source: JamiResources.jami_rolling_spinner_gif
|
||||
playing: visible
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Index = 2
|
||||
Item {
|
||||
id: exportingInfoPage
|
||||
|
||||
readonly property int pageIndex: 2
|
||||
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
|
||||
onHeightChanged: {
|
||||
stackedWidget.height = exportingLayout.implicitHeight
|
||||
}
|
||||
onWidthChanged: stackedWidget.width = exportingLayout.implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: exportingLayout
|
||||
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
|
||||
Label {
|
||||
id: instructionLabel
|
||||
|
||||
Layout.maximumWidth: Math.min(root.maximumPopupWidth, root.width) - 2 * root.popupMargins
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
color: JamiTheme.textColor
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
text: JamiStrings.linkingInstructions
|
||||
font.pointSize: JamiTheme.textFontSize
|
||||
font.kerning: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Layout.maximumWidth: Math.min(root.maximumPopupWidth, root.width) - 2 * root.popupMargins
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
radius: 5
|
||||
color: JamiTheme.backgroundRectangleColor
|
||||
width: 100
|
||||
height: 100
|
||||
|
||||
Rectangle {
|
||||
width: qrImage.width + 4
|
||||
height: qrImage.height + 4
|
||||
anchors.centerIn: parent
|
||||
radius: 5
|
||||
color: JamiTheme.whiteColor
|
||||
Image {
|
||||
id: qrImage
|
||||
anchors.centerIn: parent
|
||||
mipmap: false
|
||||
smooth: false
|
||||
source: "image://qrImage/raw_" + exportedPIN.text
|
||||
sourceSize.width: 80
|
||||
sourceSize.height: 80
|
||||
function onDeviceAuthStateChanged() {
|
||||
switch (LinkDeviceModel.deviceAuthState) {
|
||||
case DeviceAuthStateEnum.INIT:
|
||||
stackLayout.currentIndex = scanAndEnterCodeView.index
|
||||
break
|
||||
case DeviceAuthStateEnum.CONNECTING:
|
||||
stackLayout.currentIndex = deviceLinkLoadingView.index
|
||||
deviceLinkLoadingView.loadingText = JamiStrings.linkDeviceConnecting
|
||||
break
|
||||
case DeviceAuthStateEnum.AUTHENTICATING:
|
||||
stackLayout.currentIndex = deviceConfirmationView.index
|
||||
break
|
||||
case DeviceAuthStateEnum.IN_PROGRESS:
|
||||
stackLayout.currentIndex = deviceLinkLoadingView.index
|
||||
deviceLinkLoadingView.loadingText = JamiStrings.linkDeviceInProgress
|
||||
break
|
||||
case DeviceAuthStateEnum.DONE:
|
||||
if (LinkDeviceModel.linkDeviceError.length > 0) {
|
||||
stackLayout.currentIndex = deviceLinkErrorView.index
|
||||
} else {
|
||||
stackLayout.currentIndex = deviceLinkSuccessView.index
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: pinRectangle
|
||||
|
||||
radius: 5
|
||||
color: JamiTheme.backgroundRectangleColor
|
||||
Layout.fillWidth: true
|
||||
height: 100
|
||||
Layout.minimumWidth: exportedPIN.width + 20
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
MaterialLineEdit {
|
||||
id: exportedPIN
|
||||
|
||||
padding: 10
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: JamiStrings.pin
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
backgroundColor: JamiTheme.backgroundRectangleColor
|
||||
|
||||
color: darkTheme ? JamiTheme.editLineColor : JamiTheme.darkTintedBlue
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
font.pointSize: JamiTheme.tinyCreditsTextSize
|
||||
font.kerning: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
radius: 5
|
||||
color: JamiTheme.infoRectangleColor
|
||||
// Common base component for stack layout items
|
||||
component StackViewBase: Item {
|
||||
id: baseItem
|
||||
|
||||
required property string title
|
||||
default property alias content: contentLayout.data
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: infoLabels.height + 38
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitHeight: contentLayout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Layout.preferredWidth: scrollView.width
|
||||
spacing: 20
|
||||
}
|
||||
}
|
||||
|
||||
StackViewBase {
|
||||
id: deviceLinkErrorView
|
||||
property int index: 0
|
||||
title: "Error"
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: LinkDeviceModel.linkDeviceError
|
||||
Layout.preferredWidth: scrollView.width
|
||||
color: JamiTheme.textColor
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: JamiStrings.close
|
||||
toolTipText: JamiStrings.optionTryAgain
|
||||
primary: true
|
||||
onClicked: {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackViewBase {
|
||||
id: deviceLinkSuccessView
|
||||
property int index: 1
|
||||
title: "Success"
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceAllSet
|
||||
color: JamiTheme.textColor
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: JamiStrings.close
|
||||
toolTipText: JamiStrings.close
|
||||
primary: true
|
||||
onClicked: {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackViewBase {
|
||||
id: deviceLinkLoadingView
|
||||
property int index: 2
|
||||
title: "Loading"
|
||||
property string loadingText: ""
|
||||
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: 50
|
||||
Layout.preferredHeight: 50
|
||||
running: true
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: deviceLinkLoadingView.loadingText
|
||||
color: JamiTheme.textColor
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
}
|
||||
|
||||
StackViewBase {
|
||||
id: deviceConfirmationView
|
||||
property int index: 3
|
||||
title: "Confirmation"
|
||||
|
||||
Text {
|
||||
id: explanationConnect
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceFoundAddress
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceNewDeviceIP.arg(LinkDeviceModel.ipAddress)
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: infoLayout
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 16
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
spacing: 10
|
||||
|
||||
ResponsiveImage{
|
||||
Layout.fillWidth: true
|
||||
|
||||
source: JamiResources.outline_info_24dp_svg
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
color: darkTheme ? JamiTheme.editLineColor : JamiTheme.darkTintedBlue
|
||||
Layout.fillHeight: true
|
||||
MaterialButton {
|
||||
id: confirm
|
||||
primary: true
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: JamiStrings.optionConfirm
|
||||
toolTipText: JamiStrings.optionConfirm
|
||||
onClicked: {
|
||||
LinkDeviceModel.confirmAddDevice()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout{
|
||||
id: infoLabels
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
id: otherDeviceLabel
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
color: JamiTheme.textColor
|
||||
text: JamiStrings.onAnotherDevice
|
||||
|
||||
font.pointSize: JamiTheme.smallFontSize
|
||||
font.kerning: true
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Label {
|
||||
id: otherInstructionLabel
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
color: JamiTheme.textColor
|
||||
text: JamiStrings.onAnotherDeviceInstruction
|
||||
|
||||
font.pointSize: JamiTheme.smallFontSize
|
||||
font.kerning: true
|
||||
MaterialButton {
|
||||
id: cancel
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
secondary: true
|
||||
toolTipText: JamiStrings.cancel
|
||||
textLeftPadding: JamiTheme.buttontextWizzardPadding / 2
|
||||
textRightPadding: JamiTheme.buttontextWizzardPadding / 2
|
||||
text: JamiStrings.cancel
|
||||
onClicked: {
|
||||
LinkDeviceModel.cancelAddDevice()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Displays error messages
|
||||
Label {
|
||||
id: infoLabel
|
||||
StackViewBase {
|
||||
id: scanAndEnterCodeView
|
||||
property int index: 4
|
||||
title: "Scan"
|
||||
|
||||
visible: false
|
||||
Component.onDestruction: {
|
||||
if (qrScanner) {
|
||||
qrScanner.stopScanner()
|
||||
}
|
||||
}
|
||||
|
||||
property bool success: false
|
||||
property int borderWidth: success ? 1 : 0
|
||||
property int borderRadius: success ? 15 : 0
|
||||
property string backgroundColor: success ? "whitesmoke" : "transparent"
|
||||
property string borderColor: success ? "lightgray" : "transparent"
|
||||
Text {
|
||||
id: explanationScan
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceScanQR
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
Layout.maximumWidth: JamiTheme.preferredDialogWidth
|
||||
Layout.margins: JamiTheme.preferredMarginSize
|
||||
QRCodeScanner {
|
||||
id: qrScanner
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: 250
|
||||
height: width * aspectRatio
|
||||
visible: VideoDevices.listSize !== 0
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
onQrCodeDetected: function(code) {
|
||||
console.log("QR code detected:", code)
|
||||
LinkDeviceModel.addDevice(code)
|
||||
}
|
||||
}
|
||||
|
||||
color: success ? JamiTheme.successLabelColor : JamiTheme.redColor
|
||||
padding: success ? 8 : 0
|
||||
ColumnLayout {
|
||||
id: manualEntry
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
spacing: 10
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
font.pointSize: success ? JamiTheme.textFontSize : JamiTheme.textFontSize + 3
|
||||
font.kerning: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Text {
|
||||
id: explanation
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceEnterManually
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: infoLabelBackground
|
||||
ModalTextEdit {
|
||||
id: codeInput
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
Layout.preferredHeight: JamiTheme.preferredFieldHeight
|
||||
placeholderText: JamiStrings.linkDeviceEnterCodePlaceholder
|
||||
}
|
||||
|
||||
border.width: infoLabel.borderWidth
|
||||
border.color: infoLabel.borderColor
|
||||
radius: infoLabel.borderRadius
|
||||
color: JamiTheme.secondaryBackgroundColor
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: parent.width - 40
|
||||
visible: LinkDeviceModel.tokenErrorMessage.length > 0
|
||||
text: LinkDeviceModel.tokenErrorMessage
|
||||
font.pointSize: JamiTheme.tinyFontSize
|
||||
color: JamiTheme.redColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
id: connect
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
primary: true
|
||||
text: JamiStrings.connect
|
||||
toolTipText: JamiStrings.connect
|
||||
enabled: codeInput.dynamicText.length > 0
|
||||
onClicked: {
|
||||
LinkDeviceModel.addDevice(codeInput.dynamicText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Reset everything when dialog is closed
|
||||
onClosed: {
|
||||
LinkDeviceModel.reset()
|
||||
}
|
||||
}
|
||||
|
||||
134
src/app/settingsview/components/QRCodeScanner.qml
Normal file
134
src/app/settingsview/components/QRCodeScanner.qml
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Helpers 1.1
|
||||
import "../../commoncomponents"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool isScanning: false
|
||||
property real aspectRatio: 0.5625
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
startScanner()
|
||||
} else {
|
||||
stopScanner()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
stopScanner()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: cameraContainer
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: JamiTheme.primaryForegroundColor
|
||||
clip: true
|
||||
|
||||
LocalVideo {
|
||||
id: previewWidget
|
||||
anchors.fill: parent
|
||||
flip: true
|
||||
|
||||
// Camera not available
|
||||
underlayItems: Text {
|
||||
id: noCameraText
|
||||
anchors.centerIn: parent
|
||||
font.pointSize: 18
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: "white"
|
||||
text: JamiStrings.noCamera
|
||||
visible: false // Start hidden
|
||||
|
||||
// Delay "No Camera" message to avoid flashing it when camera is starting up.
|
||||
// If camera starts successfully within 5 seconds, user won't see this message.
|
||||
// If there's a camera issue, message will be shown after the delay.
|
||||
Timer {
|
||||
id: visibilityTimer
|
||||
interval: 5000
|
||||
running: true
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
noCameraText.visible = true
|
||||
destroy() // Remove the timer after it's done
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scanning line animation
|
||||
Rectangle {
|
||||
id: scanLine
|
||||
width: parent.width
|
||||
height: 2
|
||||
color: JamiTheme.whiteColor
|
||||
opacity: 0.8
|
||||
visible: root.isScanning && previewWidget.isRendering
|
||||
|
||||
SequentialAnimation on y {
|
||||
running: root.isScanning
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
from: 0
|
||||
to: cameraContainer.height
|
||||
duration: 2500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
from: cameraContainer.height
|
||||
to: 0
|
||||
duration: 2500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: scanTimer
|
||||
interval: 500
|
||||
repeat: true
|
||||
running: root.isScanning && previewWidget.isRendering
|
||||
onTriggered: {
|
||||
var result = QRCodeScannerModel.scanImage(videoProvider.captureRawVideoFrame(VideoDevices.getDefaultDevice()));
|
||||
if (result !== "") {
|
||||
root.isScanning = false
|
||||
root.qrCodeDetected(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal qrCodeDetected(string code)
|
||||
|
||||
function startScanner() {
|
||||
previewWidget.startWithId(VideoDevices.getDefaultDevice())
|
||||
root.isScanning = true
|
||||
}
|
||||
|
||||
function stopScanner() {
|
||||
previewWidget.startWithId("")
|
||||
root.isScanning = true
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,7 @@ SimpleMessageDialog {
|
||||
property alias progressBarValue: progressBar.value
|
||||
|
||||
closeButtonVisible: false
|
||||
autoClose: false
|
||||
|
||||
button1.text: JamiStrings.optionCancel
|
||||
button1Role: DialogButtonBox.RejectRole
|
||||
|
||||
@ -165,8 +165,7 @@ Utils::CreateStartupLink(const std::wstring& wstrAppName)
|
||||
#endif
|
||||
|
||||
if (desktopPath.isEmpty() || !(QFile::exists(desktopPath))) {
|
||||
qDebug() << "Error while attempting to locate .desktop file at"
|
||||
<< desktopPath;
|
||||
qDebug() << "Error while attempting to locate .desktop file at" << desktopPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -193,8 +192,7 @@ Utils::CreateStartupLink(const std::wstring& wstrAppName)
|
||||
if (QDir().mkdir(autoStartDir)) {
|
||||
qDebug() << "Created autostart directory:" << autoStartDir;
|
||||
} else {
|
||||
qWarning() << "Error while creating autostart directory:"
|
||||
<< autoStartDir;
|
||||
qWarning() << "Error while creating autostart directory:" << autoStartDir;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -283,7 +281,8 @@ Utils::CheckStartupLink(const std::wstring& wstrAppName)
|
||||
#else
|
||||
Q_UNUSED(wstrAppName)
|
||||
return (
|
||||
!QStandardPaths::locate(QStandardPaths::ConfigLocation, "autostart/net.jami.Jami.desktop").isEmpty());
|
||||
!QStandardPaths::locate(QStandardPaths::ConfigLocation, "autostart/net.jami.Jami.desktop")
|
||||
.isEmpty());
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -616,14 +615,16 @@ Utils::getProjectCredits()
|
||||
return {};
|
||||
}
|
||||
QTextStream in(&projectCreditsFile);
|
||||
return in.readAll().arg(
|
||||
QObject::tr("We would like to thank our contributors, whose efforts over many years have made this software what it is."),
|
||||
QObject::tr("Developers"),
|
||||
QObject::tr("Media"),
|
||||
QObject::tr("Community Management"),
|
||||
QObject::tr("Special thanks to"),
|
||||
QObject::tr("This is a list of people who have made a significant investment of time, with useful results, into Jami. Any such contributors who want to be added to the list should contact us.")
|
||||
);
|
||||
return in.readAll().arg(QObject::tr("We would like to thank our contributors, whose efforts "
|
||||
"over many years have made this software what it is."),
|
||||
QObject::tr("Developers"),
|
||||
QObject::tr("Media"),
|
||||
QObject::tr("Community Management"),
|
||||
QObject::tr("Special thanks to"),
|
||||
QObject::tr(
|
||||
"This is a list of people who have made a significant investment "
|
||||
"of time, with useful results, into Jami. Any such contributors "
|
||||
"who want to be added to the list should contact us."));
|
||||
}
|
||||
|
||||
inline QString
|
||||
@ -951,3 +952,13 @@ Utils::getTempSwarmAvatarPath()
|
||||
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QDir::separator()
|
||||
+ "tmpSwarmImage";
|
||||
}
|
||||
|
||||
QVariantMap
|
||||
Utils::mapStringStringToVariantMap(const MapStringString& map)
|
||||
{
|
||||
QVariantMap variantMap;
|
||||
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
|
||||
variantMap.insert(it.key(), it.value());
|
||||
}
|
||||
return variantMap;
|
||||
}
|
||||
|
||||
@ -120,4 +120,7 @@ QString generateUid();
|
||||
QString humanFileSize(qint64 fileSize);
|
||||
QString getDebugFilePath();
|
||||
|
||||
// Convert a MapStringString to a QVariantMap
|
||||
QVariantMap mapStringStringToVariantMap(const MapStringString& map);
|
||||
|
||||
} // namespace Utils
|
||||
|
||||
@ -811,18 +811,19 @@ UtilsAdapter::isRTL()
|
||||
pref = pref == "SYSTEM" ? QLocale::system().name() : pref;
|
||||
static const QStringList rtlLanguages {
|
||||
// as defined by ISO 639-1
|
||||
"ar", // Arabic
|
||||
"he", // Hebrew
|
||||
"fa", // Persian (Farsi)
|
||||
"ur", // Urdu
|
||||
"ps", // Pashto
|
||||
"ku", // Kurdish
|
||||
"sd", // Sindhi
|
||||
"dv", // Dhivehi (Maldivian)
|
||||
"yi", // Yiddish
|
||||
"am", // Amharic
|
||||
"ti", // Tigrinya
|
||||
"kk" // Kazakh (in Arabic script)
|
||||
"ar", // Arabic
|
||||
"he", // Hebrew
|
||||
"fa", // Persian (Farsi)
|
||||
"az_IR", // Azerbaijani
|
||||
"ur", // Urdu
|
||||
"ps", // Pashto
|
||||
"ku", // Kurdish
|
||||
"sd", // Sindhi
|
||||
"dv", // Dhivehi (Maldivian)
|
||||
"yi", // Yiddish
|
||||
"am", // Amharic
|
||||
"ti", // Tigrinya
|
||||
"kk" // Kazakh (in Arabic script)
|
||||
};
|
||||
return rtlLanguages.contains(pref);
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ WebEngineView {
|
||||
}
|
||||
onFullScreenRequested: function (request) {
|
||||
if (request.toggleOn) {
|
||||
layoutManager.pushFullScreenItem(this, localMediaCompLoader, null, function () {
|
||||
layoutManager.pushFullScreenItem(this, function () {
|
||||
wev.fullScreenCancelled();
|
||||
});
|
||||
} else if (!request.toggleOn) {
|
||||
|
||||
@ -38,7 +38,7 @@ Rectangle {
|
||||
Component.onCompleted: loadHtml(root.html, 'file:///')
|
||||
onFullScreenRequested: function (request) {
|
||||
if (request.toggleOn) {
|
||||
layoutManager.pushFullScreenItem(this, root, null, function () {
|
||||
layoutManager.pushFullScreenItem(this, function () {
|
||||
wev.fullScreenCancelled();
|
||||
});
|
||||
} else if (!request.toggleOn) {
|
||||
|
||||
@ -56,9 +56,11 @@ BaseView {
|
||||
case WizardViewStepModel.AccountCreationOption.CreateJamiAccount:
|
||||
case WizardViewStepModel.AccountCreationOption.CreateRendezVous:
|
||||
case WizardViewStepModel.AccountCreationOption.ImportFromBackup:
|
||||
case WizardViewStepModel.AccountCreationOption.ImportFromDevice:
|
||||
AccountAdapter.createJamiAccount(WizardViewStepModel.accountCreationInfo);
|
||||
break;
|
||||
case WizardViewStepModel.AccountCreationOption.ImportFromDevice:
|
||||
AccountAdapter.startImportAccount();
|
||||
break;
|
||||
case WizardViewStepModel.AccountCreationOption.ConnectToAccountManager:
|
||||
AccountAdapter.createJAMSAccount(WizardViewStepModel.accountCreationInfo);
|
||||
break;
|
||||
|
||||
@ -35,7 +35,7 @@ BaseModalDialog {
|
||||
InfoBox {
|
||||
id: info
|
||||
|
||||
width: root.width - 2 * root.popupMargins
|
||||
width: parent.width
|
||||
icoSource: JamiResources.laptop_black_24dp_svg
|
||||
title: JamiStrings.local
|
||||
description: JamiStrings.localAccount
|
||||
@ -43,7 +43,7 @@ BaseModalDialog {
|
||||
}
|
||||
|
||||
InfoBox {
|
||||
width: root.width - 2 * root.popupMargins
|
||||
width: parent.width
|
||||
icoSource: JamiResources.assignment_ind_black_24dp_svg
|
||||
title: JamiStrings.username
|
||||
description: JamiStrings.usernameRecommened
|
||||
@ -51,7 +51,7 @@ BaseModalDialog {
|
||||
}
|
||||
|
||||
InfoBox {
|
||||
width: root.width - 2 * root.popupMargins
|
||||
width: parent.width
|
||||
icoSource: JamiResources.lock_svg
|
||||
title: JamiStrings.encrypt
|
||||
description: JamiStrings.passwordOptional
|
||||
@ -59,7 +59,7 @@ BaseModalDialog {
|
||||
}
|
||||
|
||||
InfoBox {
|
||||
width: root.width - 2 * root.popupMargins
|
||||
width: parent.width
|
||||
icoSource: JamiResources.brush_black_24dp_svg
|
||||
title: JamiStrings.customize
|
||||
description: JamiStrings.customizeOptional
|
||||
@ -67,4 +67,3 @@ BaseModalDialog {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,9 +17,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import Qt.labs.platform
|
||||
import "../../commoncomponents"
|
||||
import "../../mainview/components"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@ -27,30 +31,97 @@ Rectangle {
|
||||
property string errorText: ""
|
||||
property int preferredHeight: importFromDevicePageColumnLayout.implicitHeight + 2 * JamiTheme.preferredMarginSize
|
||||
|
||||
signal showThisPage
|
||||
// The token is used to generate the QR code and is also provided to the user as a backup if the QR
|
||||
// code cannot be scanned. It is a URI using the scheme "jami-auth".
|
||||
readonly property string tokenUri: WizardViewStepModel.deviceLinkDetails["token"] || ""
|
||||
|
||||
function initializeOnShowUp() {
|
||||
clearAllTextFields();
|
||||
property string jamiId: ""
|
||||
|
||||
function isPasswordWrong() {
|
||||
return WizardViewStepModel.deviceLinkDetails["auth_error"] !== undefined &&
|
||||
WizardViewStepModel.deviceLinkDetails["auth_error"] !== "" &&
|
||||
WizardViewStepModel.deviceLinkDetails["auth_error"] !== "none"
|
||||
}
|
||||
|
||||
function requiresPassword() {
|
||||
return WizardViewStepModel.deviceLinkDetails["auth_scheme"] === "password"
|
||||
}
|
||||
|
||||
function requiresConfirmationBeforeClosing() {
|
||||
const state = WizardViewStepModel.deviceAuthState
|
||||
return state !== DeviceAuthStateEnum.INIT &&
|
||||
state !== DeviceAuthStateEnum.DONE
|
||||
}
|
||||
|
||||
function isLoadingState() {
|
||||
const state = WizardViewStepModel.deviceAuthState
|
||||
return state === DeviceAuthStateEnum.INIT ||
|
||||
state === DeviceAuthStateEnum.CONNECTING ||
|
||||
state === DeviceAuthStateEnum.IN_PROGRESS
|
||||
}
|
||||
|
||||
signal showThisPage
|
||||
|
||||
function clearAllTextFields() {
|
||||
connectBtn.spinnerTriggered = false;
|
||||
errorText = "";
|
||||
}
|
||||
|
||||
function errorOccurred(errorMessage) {
|
||||
errorText = errorMessage;
|
||||
connectBtn.spinnerTriggered = false;
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: confirmCloseDialog
|
||||
|
||||
text: JamiStrings.linkDeviceCloseWarningTitle
|
||||
informativeText: JamiStrings.linkDeviceCloseWarningMessage
|
||||
buttons: MessageDialog.Ok | MessageDialog.Cancel
|
||||
|
||||
onOkClicked: function(button) {
|
||||
AccountAdapter.cancelImportAccount();
|
||||
WizardViewStepModel.previousStep();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: WizardViewStepModel
|
||||
|
||||
function onMainStepChanged() {
|
||||
if (WizardViewStepModel.mainStep === WizardViewStepModel.MainSteps.AccountCreation && WizardViewStepModel.accountCreationOption === WizardViewStepModel.AccountCreationOption.ImportFromDevice) {
|
||||
if (WizardViewStepModel.mainStep === WizardViewStepModel.MainSteps.DeviceAuthorization) {
|
||||
clearAllTextFields();
|
||||
root.showThisPage();
|
||||
}
|
||||
}
|
||||
|
||||
function onDeviceAuthStateChanged() {
|
||||
switch (WizardViewStepModel.deviceAuthState) {
|
||||
case DeviceAuthStateEnum.TOKEN_AVAILABLE:
|
||||
// Token is available and displayed as QR code
|
||||
clearAllTextFields();
|
||||
break;
|
||||
case DeviceAuthStateEnum.CONNECTING:
|
||||
// P2P connection being established
|
||||
clearAllTextFields();
|
||||
break;
|
||||
case DeviceAuthStateEnum.AUTHENTICATING:
|
||||
jamiId = WizardViewStepModel.deviceLinkDetails["peer_id"] || "";
|
||||
if (jamiId.length > 0) {
|
||||
NameDirectory.lookupAddress(CurrentAccount.id, jamiId)
|
||||
}
|
||||
break;
|
||||
case DeviceAuthStateEnum.IN_PROGRESS:
|
||||
// Account archive is being transferred
|
||||
clearAllTextFields();
|
||||
break;
|
||||
case DeviceAuthStateEnum.DONE:
|
||||
// Final state - check for specific errors
|
||||
const error = AccountAdapter.getImportErrorMessage(WizardViewStepModel.deviceLinkDetails);
|
||||
if (error.length > 0) {
|
||||
errorOccurred(error)
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color: JamiTheme.secondaryBackgroundColor
|
||||
@ -65,184 +136,337 @@ Rectangle {
|
||||
width: Math.max(508, root.width - 100)
|
||||
|
||||
Text {
|
||||
|
||||
text: JamiStrings.importAccountFromAnotherDevice
|
||||
text: JamiStrings.importFromAnotherAccount
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: JamiTheme.preferredMarginSize
|
||||
Layout.preferredWidth: Math.min(360, root.width - JamiTheme.preferredMarginSize * 2)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: JamiTheme.textColor
|
||||
|
||||
color: JamiTheme.textColor
|
||||
font.pixelSize: JamiTheme.wizardViewTitleFontPixelSize
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Text {
|
||||
|
||||
text: JamiStrings.importFromDeviceDescription
|
||||
Layout.preferredWidth: Math.min(360, root.width - JamiTheme.preferredMarginSize * 2)
|
||||
Layout.topMargin: JamiTheme.wizardViewDescriptionMarginSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
font.weight: Font.Medium
|
||||
color: JamiTheme.textColor
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
}
|
||||
|
||||
Flow {
|
||||
spacing: 30
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
|
||||
Layout.preferredWidth: Math.min(step1.width * 2 + spacing, root.width - JamiTheme.preferredMarginSize * 2)
|
||||
|
||||
InfoBox {
|
||||
id: step1
|
||||
icoSource: JamiResources.settings_24dp_svg
|
||||
title: JamiStrings.importStep1
|
||||
description: JamiStrings.importStep1Desc
|
||||
icoColor: JamiTheme.buttonTintedBlue
|
||||
}
|
||||
|
||||
InfoBox {
|
||||
id: step2
|
||||
icoSource: JamiResources.person_24dp_svg
|
||||
title: JamiStrings.importStep2
|
||||
description: JamiStrings.importStep2Desc
|
||||
icoColor: JamiTheme.buttonTintedBlue
|
||||
}
|
||||
|
||||
InfoBox {
|
||||
id: step3
|
||||
icoSource: JamiResources.finger_select_svg
|
||||
title: JamiStrings.importStep3
|
||||
description: JamiStrings.importStep3Desc
|
||||
icoColor: JamiTheme.buttonTintedBlue
|
||||
}
|
||||
|
||||
InfoBox {
|
||||
id: step4
|
||||
icoSource: JamiResources.time_clock_svg
|
||||
title: JamiStrings.importStep4
|
||||
description: JamiStrings.importStep4Desc
|
||||
icoColor: JamiTheme.buttonTintedBlue
|
||||
}
|
||||
}
|
||||
|
||||
ModalTextEdit {
|
||||
id: pinFromDevice
|
||||
|
||||
objectName: "pinFromDevice"
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredWidth: Math.min(410, root.width - JamiTheme.preferredMarginSize * 2)
|
||||
Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
|
||||
|
||||
focus: visible
|
||||
|
||||
placeholderText: JamiStrings.pin
|
||||
staticText: ""
|
||||
|
||||
KeyNavigation.up: backButton
|
||||
KeyNavigation.down: passwordFromDevice
|
||||
KeyNavigation.tab: KeyNavigation.down
|
||||
|
||||
onAccepted: passwordFromDevice.forceActiveFocus()
|
||||
}
|
||||
|
||||
Text {
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
|
||||
|
||||
color: JamiTheme.textColor
|
||||
wrapMode: Text.WordWrap
|
||||
text: JamiStrings.importPasswordDesc
|
||||
Layout.maximumWidth: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
font.weight: Font.Medium
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
text: {
|
||||
switch (WizardViewStepModel.deviceAuthState) {
|
||||
case DeviceAuthStateEnum.INIT:
|
||||
return JamiStrings.waitingForToken;
|
||||
case DeviceAuthStateEnum.TOKEN_AVAILABLE:
|
||||
return JamiStrings.scanToImportAccount;
|
||||
case DeviceAuthStateEnum.CONNECTING:
|
||||
return JamiStrings.connectingToDevice;
|
||||
case DeviceAuthStateEnum.AUTHENTICATING:
|
||||
return JamiStrings.confirmAccountImport;
|
||||
case DeviceAuthStateEnum.IN_PROGRESS:
|
||||
return JamiStrings.transferringAccount;
|
||||
case DeviceAuthStateEnum.DONE:
|
||||
return errorText.length > 0 ? JamiStrings.importFailed : "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
PasswordTextEdit {
|
||||
id: passwordFromDevice
|
||||
// Confirmation form
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: Math.min(parent.width - 40, 400)
|
||||
visible: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.AUTHENTICATING
|
||||
spacing: JamiTheme.wizardViewPageLayoutSpacing
|
||||
|
||||
objectName: "passwordFromDevice"
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredWidth: Math.min(410, root.width - JamiTheme.preferredMarginSize * 2)
|
||||
Layout.topMargin: JamiTheme.wizardViewMarginSize
|
||||
|
||||
placeholderText: JamiStrings.enterPassword
|
||||
|
||||
KeyNavigation.up: pinFromDevice
|
||||
KeyNavigation.down: {
|
||||
if (connectBtn.enabled)
|
||||
return connectBtn;
|
||||
else if (connectBtn.spinnerTriggered)
|
||||
return passwordFromDevice;
|
||||
return backButton;
|
||||
}
|
||||
KeyNavigation.tab: KeyNavigation.down
|
||||
|
||||
onAccepted: pinFromDevice.forceActiveFocus()
|
||||
}
|
||||
|
||||
SpinnerButton {
|
||||
id: connectBtn
|
||||
|
||||
TextMetrics {
|
||||
id: textSize
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
|
||||
text: connectBtn.normalText
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
text: JamiStrings.connectToAccount
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: JamiTheme.textColor
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
objectName: "importFromDevicePageConnectBtn"
|
||||
// Account Widget (avatar + username + ID)
|
||||
Rectangle {
|
||||
id: accountContainer
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: accountLayout.implicitWidth + 40
|
||||
implicitHeight: accountLayout.implicitHeight + 40
|
||||
radius: 8
|
||||
color: JamiTheme.primaryBackgroundColor
|
||||
border.width: 1
|
||||
border.color: JamiTheme.textColorHovered
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
|
||||
Layout.bottomMargin: errorLabel.visible ? 0 : JamiTheme.wizardViewPageBackButtonMargins
|
||||
RowLayout {
|
||||
id: accountLayout
|
||||
anchors {
|
||||
centerIn: parent
|
||||
}
|
||||
spacing: 20
|
||||
|
||||
preferredWidth: textSize.width + 2 * JamiTheme.buttontextWizzardPadding + 1
|
||||
primary: true
|
||||
Avatar {
|
||||
id: accountAvatar
|
||||
showPresenceIndicator: false
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 48
|
||||
Layout.preferredHeight: 48
|
||||
mode: Avatar.Mode.TemporaryAccount
|
||||
imageId: name.text || jamiId
|
||||
}
|
||||
|
||||
spinnerTriggeredtext: JamiStrings.generatingAccount
|
||||
normalText: JamiStrings.importButton
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 4
|
||||
|
||||
enabled: pinFromDevice.dynamicText.length !== 0 && !spinnerTriggered
|
||||
Text {
|
||||
id: name
|
||||
color: JamiTheme.textColor
|
||||
visible: text !== undefined && text !== ""
|
||||
|
||||
KeyNavigation.tab: backButton
|
||||
KeyNavigation.up: passwordFromDevice
|
||||
KeyNavigation.down: backButton
|
||||
Connections {
|
||||
id: registeredNameFoundConnection
|
||||
target: NameDirectory
|
||||
enabled: jamiId.length > 0
|
||||
|
||||
onClicked: {
|
||||
spinnerTriggered = true;
|
||||
WizardViewStepModel.accountCreationInfo = JamiQmlUtils.setUpAccountCreationInputPara({
|
||||
"archivePin": pinFromDevice.dynamicText,
|
||||
"password": passwordFromDevice.dynamicText
|
||||
});
|
||||
WizardViewStepModel.nextStep();
|
||||
function onRegisteredNameFound(status, address, registeredName, requestedName) {
|
||||
if (address === jamiId && status === NameDirectory.LookupStatus.SUCCESS) {
|
||||
name.text = registeredName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: userId
|
||||
text: jamiId
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password
|
||||
PasswordTextEdit {
|
||||
id: passwordField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 10
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 10
|
||||
visible: requiresPassword()
|
||||
placeholderText: JamiStrings.enterPassword
|
||||
echoMode: TextInput.Password
|
||||
|
||||
onAccepted: confirmButton.clicked()
|
||||
}
|
||||
|
||||
Text {
|
||||
id: passwordErrorField
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: parent.width - 40
|
||||
visible: isPasswordWrong()
|
||||
text: JamiStrings.authenticationError
|
||||
font.pointSize: JamiTheme.tinyFontSize
|
||||
color: JamiTheme.redColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 16
|
||||
Layout.margins: 10
|
||||
|
||||
MaterialButton {
|
||||
id: confirmButton
|
||||
text: JamiStrings.optionConfirm
|
||||
primary: true
|
||||
enabled: !passwordField.visible || passwordField.dynamicText.length > 0
|
||||
onClicked: {
|
||||
AccountAdapter.provideAccountAuthentication(passwordField.visible ? passwordField.dynamicText : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: errorLabel
|
||||
// Show busy indicator when waiting for token
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: isLoadingState()
|
||||
Layout.preferredWidth: 50
|
||||
Layout.preferredHeight: 50
|
||||
running: visible
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.bottomMargin: JamiTheme.wizardViewPageBackButtonMargins
|
||||
// QR Code container with frame
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: qrLoader.Layout.preferredWidth + 40
|
||||
Layout.preferredHeight: qrLoader.Layout.preferredHeight + 40
|
||||
visible: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.TOKEN_AVAILABLE
|
||||
color: JamiTheme.whiteColor
|
||||
radius: 8
|
||||
border.width: 1
|
||||
border.color: JamiTheme.whiteColor
|
||||
|
||||
visible: errorText.length !== 0
|
||||
Loader {
|
||||
id: qrLoader
|
||||
anchors.centerIn: parent
|
||||
active: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.TOKEN_AVAILABLE
|
||||
Layout.preferredWidth: Math.min(parent.parent.width - 60, 250)
|
||||
Layout.preferredHeight: Layout.preferredWidth
|
||||
|
||||
text: errorText
|
||||
sourceComponent: Image {
|
||||
width: qrLoader.Layout.preferredWidth
|
||||
height: qrLoader.Layout.preferredHeight
|
||||
anchors.centerIn: parent
|
||||
smooth: false
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "image://qrImage/raw_" + tokenUri
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
font.pixelSize: JamiTheme.textEditError
|
||||
color: JamiTheme.redColor
|
||||
// Token URI backup text
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: tokenUri !== ""
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: parent.parent.width - 40
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: JamiStrings.cantScanQRCode
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
color: JamiTheme.textColor
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
// Container for TextArea and copy button
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: contentRow.implicitWidth + 40
|
||||
Layout.preferredHeight: contentRow.implicitHeight + 20
|
||||
color: JamiTheme.jamiIdBackgroundColor
|
||||
radius: 5
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
TextEdit {
|
||||
id: tokenUriTextArea
|
||||
text: tokenUri
|
||||
color: JamiTheme.textColor
|
||||
font.pointSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
cursorVisible: false
|
||||
}
|
||||
|
||||
// Copy button
|
||||
PushButton {
|
||||
id: copyButton
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
preferredSize: 30
|
||||
radius: 5
|
||||
normalColor: JamiTheme.transparentColor
|
||||
imageContainerWidth: JamiTheme.pushButtonSize
|
||||
imageContainerHeight: JamiTheme.pushButtonSize
|
||||
border.color: JamiTheme.transparentColor
|
||||
imageColor: JamiTheme.tintedBlue
|
||||
source: JamiResources.content_copy_24dp_svg
|
||||
toolTipText: JamiStrings.copy
|
||||
|
||||
onClicked: {
|
||||
UtilsAdapter.setClipboardText(tokenUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
parent: tokenUriTextArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
propagateComposedEvents: true
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
mouse.accepted = true
|
||||
contextMenu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: contextMenu
|
||||
MenuItem {
|
||||
text: JamiStrings.copy
|
||||
enabled: tokenUriTextArea.selectedText.length > 0
|
||||
onTriggered: {
|
||||
UtilsAdapter.setClipboardText(tokenUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error view
|
||||
ColumnLayout {
|
||||
id: errorColumn
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: parent.width - 40
|
||||
visible: errorText !== ""
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: parent.width
|
||||
text: errorText
|
||||
color: JamiTheme.textColor
|
||||
font.pointSize: JamiTheme.mediumFontSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: JamiStrings.optionTryAgain
|
||||
toolTipText: JamiStrings.optionTryAgain
|
||||
primary: true
|
||||
onClicked: {
|
||||
AccountAdapter.cancelImportAccount();
|
||||
WizardViewStepModel.previousStep();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackButton {
|
||||
// Back button
|
||||
JamiPushButton {
|
||||
id: backButton
|
||||
QWKSetParentHitTestVisible {
|
||||
}
|
||||
|
||||
objectName: "importFromDevicePageBackButton"
|
||||
|
||||
@ -250,12 +474,18 @@ Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.margins: JamiTheme.wizardViewPageBackButtonMargins
|
||||
|
||||
visible: !connectBtn.spinnerTriggered
|
||||
preferredSize: 36
|
||||
imageContainerWidth: 20
|
||||
source: JamiResources.ic_arrow_back_24dp_svg
|
||||
|
||||
KeyNavigation.tab: pinFromDevice
|
||||
KeyNavigation.up: connectBtn.enabled ? connectBtn : passwordFromDevice
|
||||
KeyNavigation.down: pinFromDevice
|
||||
visible: WizardViewStepModel.deviceAuthState !== DeviceAuthStateEnum.IN_PROGRESS
|
||||
|
||||
onClicked: WizardViewStepModel.previousStep()
|
||||
onClicked: {
|
||||
if (requiresConfirmationBeforeClosing()) {
|
||||
confirmCloseDialog.open();
|
||||
} else {
|
||||
WizardViewStepModel.previousStep();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
#include "appsettingsmanager.h"
|
||||
#include "lrcinstance.h"
|
||||
#include "global.h"
|
||||
|
||||
#include "api/accountmodel.h"
|
||||
|
||||
@ -46,17 +47,31 @@ WizardViewStepModel::WizardViewStepModel(LRCInstance* lrcInstance,
|
||||
|
||||
Q_EMIT accountIsReady(accountId);
|
||||
});
|
||||
|
||||
// Connect to account model signals to track import progress
|
||||
connect(&lrcInstance_->accountModel(),
|
||||
&AccountModel::deviceAuthStateChanged,
|
||||
this,
|
||||
[this](const QString& accountID, int state, const MapStringString& details) {
|
||||
set_deviceLinkDetails(Utils::mapStringStringToVariantMap(details));
|
||||
set_deviceAuthState(static_cast<lrc::api::account::DeviceAuthState>(state));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
WizardViewStepModel::startAccountCreationFlow(AccountCreationOption accountCreationOption)
|
||||
{
|
||||
using namespace lrc::api::account;
|
||||
set_accountCreationOption(accountCreationOption);
|
||||
if (accountCreationOption == AccountCreationOption::CreateJamiAccount
|
||||
|| accountCreationOption == AccountCreationOption::CreateRendezVous)
|
||||
if (accountCreationOption == AccountCreationOption::ImportFromDevice) {
|
||||
set_mainStep(MainSteps::DeviceAuthorization);
|
||||
Q_EMIT createAccountRequested(accountCreationOption);
|
||||
} else if (accountCreationOption == AccountCreationOption::CreateJamiAccount
|
||||
|| accountCreationOption == AccountCreationOption::CreateRendezVous) {
|
||||
set_mainStep(MainSteps::NameRegistration);
|
||||
else
|
||||
} else {
|
||||
set_mainStep(MainSteps::AccountCreation);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -80,6 +95,10 @@ WizardViewStepModel::previousStep()
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
case MainSteps::DeviceAuthorization: {
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,4 +107,6 @@ WizardViewStepModel::reset()
|
||||
{
|
||||
set_accountCreationOption(AccountCreationOption::None);
|
||||
set_mainStep(MainSteps::Initial);
|
||||
set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
|
||||
set_deviceLinkDetails({});
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "qtutils.h"
|
||||
#include "api/account.h" // Include for DeviceAuthState
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
@ -29,6 +30,21 @@ class AccountAdapter;
|
||||
class LRCInstance;
|
||||
class AppSettingsManager;
|
||||
|
||||
class DeviceAuthStateEnum : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum State {
|
||||
INIT = static_cast<int>(lrc::api::account::DeviceAuthState::INIT),
|
||||
TOKEN_AVAILABLE = static_cast<int>(lrc::api::account::DeviceAuthState::TOKEN_AVAILABLE),
|
||||
CONNECTING = static_cast<int>(lrc::api::account::DeviceAuthState::CONNECTING),
|
||||
AUTHENTICATING = static_cast<int>(lrc::api::account::DeviceAuthState::AUTHENTICATING),
|
||||
IN_PROGRESS = static_cast<int>(lrc::api::account::DeviceAuthState::IN_PROGRESS),
|
||||
DONE = static_cast<int>(lrc::api::account::DeviceAuthState::DONE)
|
||||
};
|
||||
Q_ENUM(State)
|
||||
};
|
||||
|
||||
class WizardViewStepModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -37,9 +53,10 @@ class WizardViewStepModel : public QObject
|
||||
|
||||
public:
|
||||
enum class MainSteps {
|
||||
Initial, // Initial welcome step.
|
||||
AccountCreation, // General account creation step.
|
||||
NameRegistration, // Name registration step : CreateJamiAccount, CreateRendezVous
|
||||
Initial, // Initial welcome step.
|
||||
AccountCreation, // General account creation step.
|
||||
NameRegistration, // Name registration step : CreateJamiAccount, CreateRendezVous
|
||||
DeviceAuthorization // Add new step for device authorization.
|
||||
};
|
||||
Q_ENUM(MainSteps)
|
||||
|
||||
@ -57,6 +74,8 @@ public:
|
||||
QML_PROPERTY(MainSteps, mainStep)
|
||||
QML_PROPERTY(AccountCreationOption, accountCreationOption)
|
||||
QML_PROPERTY(QVariantMap, accountCreationInfo)
|
||||
QML_PROPERTY(lrc::api::account::DeviceAuthState, deviceAuthState)
|
||||
QML_PROPERTY(QVariantMap, deviceLinkDetails)
|
||||
|
||||
public:
|
||||
static WizardViewStepModel* create(QQmlEngine*, QJSEngine*)
|
||||
|
||||
@ -117,12 +117,26 @@ public Q_SLOTS:
|
||||
void slotAccountStatusChanged(const QString& accountID, const api::account::Status status);
|
||||
|
||||
/**
|
||||
* Emit exportOnRingEnded.
|
||||
* Emit deviceAuthStateChanged.
|
||||
* @param accountId
|
||||
* @param status
|
||||
* @param pin
|
||||
* @param state
|
||||
* @param details map
|
||||
*/
|
||||
void slotExportOnRingEnded(const QString& accountID, int status, const QString& pin);
|
||||
void slotDeviceAuthStateChanged(const QString& accountID,
|
||||
int state,
|
||||
const MapStringString& details);
|
||||
|
||||
/**
|
||||
* Emit addDeviceStateChanged.
|
||||
* @param accountId
|
||||
* @param operationId
|
||||
* @param state
|
||||
* @param details
|
||||
*/
|
||||
void slotAddDeviceStateChanged(const QString& accountID,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details);
|
||||
|
||||
/**
|
||||
* @param accountId
|
||||
@ -282,11 +296,12 @@ AccountModel::setAlias(const QString& accountId, const QString& alias, bool save
|
||||
accountInfo.profileInfo.alias = alias;
|
||||
|
||||
if (save)
|
||||
ConfigurationManager::instance().updateProfile(accountId,
|
||||
alias,
|
||||
"",
|
||||
"",
|
||||
5);// flag out of range to avoid updating avatar
|
||||
ConfigurationManager::instance()
|
||||
.updateProfile(accountId,
|
||||
alias,
|
||||
"",
|
||||
"",
|
||||
5); // flag out of range to avoid updating avatar
|
||||
Q_EMIT profileUpdated(accountId);
|
||||
}
|
||||
|
||||
@ -323,9 +338,30 @@ AccountModel::exportToFile(const QString& accountId,
|
||||
}
|
||||
|
||||
bool
|
||||
AccountModel::exportOnRing(const QString& accountId, const QString& password) const
|
||||
AccountModel::provideAccountAuthentication(const QString& accountId,
|
||||
const QString& credentialsFromUser) const
|
||||
{
|
||||
return ConfigurationManager::instance().exportOnRing(accountId, password);
|
||||
return ConfigurationManager::instance().provideAccountAuthentication(accountId,
|
||||
credentialsFromUser,
|
||||
"password");
|
||||
}
|
||||
|
||||
int32_t
|
||||
AccountModel::addDevice(const QString& accountId, const QString& token) const
|
||||
{
|
||||
return ConfigurationManager::instance().addDevice(accountId, token);
|
||||
}
|
||||
|
||||
bool
|
||||
AccountModel::confirmAddDevice(const QString& accountId, uint32_t operationId) const
|
||||
{
|
||||
return ConfigurationManager::instance().confirmAddDevice(accountId, operationId);
|
||||
}
|
||||
|
||||
bool
|
||||
AccountModel::cancelAddDevice(const QString& accountId, uint32_t operationId) const
|
||||
{
|
||||
return ConfigurationManager::instance().cancelAddDevice(accountId, operationId);
|
||||
}
|
||||
|
||||
void
|
||||
@ -403,9 +439,13 @@ AccountModelPimpl::AccountModelPimpl(AccountModel& linked,
|
||||
this,
|
||||
&AccountModelPimpl::slotVolatileAccountDetailsChanged);
|
||||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::exportOnRingEnded,
|
||||
this,
|
||||
&AccountModelPimpl::slotExportOnRingEnded);
|
||||
&CallbacksHandler::deviceAuthStateChanged,
|
||||
&linked,
|
||||
&AccountModel::deviceAuthStateChanged);
|
||||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::addDeviceStateChanged,
|
||||
&linked,
|
||||
&AccountModel::addDeviceStateChanged);
|
||||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::nameRegistrationEnded,
|
||||
this,
|
||||
@ -594,23 +634,22 @@ AccountModelPimpl::slotVolatileAccountDetailsChanged(const QString& accountId,
|
||||
}
|
||||
|
||||
void
|
||||
AccountModelPimpl::slotExportOnRingEnded(const QString& accountID, int status, const QString& pin)
|
||||
AccountModelPimpl::slotDeviceAuthStateChanged(const QString& accountId,
|
||||
int state,
|
||||
const MapStringString& details)
|
||||
{
|
||||
account::ExportOnRingStatus convertedStatus = account::ExportOnRingStatus::INVALID;
|
||||
switch (status) {
|
||||
case 0:
|
||||
convertedStatus = account::ExportOnRingStatus::SUCCESS;
|
||||
break;
|
||||
case 1:
|
||||
convertedStatus = account::ExportOnRingStatus::WRONG_PASSWORD;
|
||||
break;
|
||||
case 2:
|
||||
convertedStatus = account::ExportOnRingStatus::NETWORK_ERROR;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Q_EMIT linked.exportOnRingEnded(accountID, convertedStatus, pin);
|
||||
// implement business logic here
|
||||
// can be bypassed with a signal to signal
|
||||
Q_EMIT linked.deviceAuthStateChanged(accountId, state, details);
|
||||
}
|
||||
|
||||
void
|
||||
AccountModelPimpl::slotAddDeviceStateChanged(const QString& accountId,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details)
|
||||
{
|
||||
Q_EMIT linked.addDeviceStateChanged(accountId, operationId, state, details);
|
||||
}
|
||||
|
||||
void
|
||||
@ -673,7 +712,11 @@ AccountModelPimpl::slotRegisteredNameFound(const QString& accountId,
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Q_EMIT linked.registeredNameFound(accountId, requestedName, convertedStatus, address, registeredName);
|
||||
Q_EMIT linked.registeredNameFound(accountId,
|
||||
requestedName,
|
||||
convertedStatus,
|
||||
address,
|
||||
registeredName);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1041,32 +1084,47 @@ account::ConfProperties_t::toDetails() const
|
||||
|
||||
QString
|
||||
AccountModel::createNewAccount(profile::Type type,
|
||||
const MapStringString& config,
|
||||
const QString& displayName,
|
||||
const QString& archivePath,
|
||||
const QString& password,
|
||||
const QString& pin,
|
||||
const QString& uri,
|
||||
const MapStringString& config)
|
||||
const QString& uri)
|
||||
{
|
||||
// Get the template for the account type to prefill the details
|
||||
MapStringString details = type == profile::Type::SIP
|
||||
? ConfigurationManager::instance().getAccountTemplate("SIP")
|
||||
: ConfigurationManager::instance().getAccountTemplate("RING");
|
||||
using namespace libjami::Account;
|
||||
details[ConfProperties::TYPE] = type == profile::Type::SIP ? "SIP" : "RING";
|
||||
details[ConfProperties::DISPLAYNAME] = displayName;
|
||||
details[ConfProperties::ALIAS] = displayName;
|
||||
details[ConfProperties::UPNP_ENABLED] = "true";
|
||||
details[ConfProperties::ARCHIVE_PASSWORD] = password;
|
||||
details[ConfProperties::ARCHIVE_PIN] = pin;
|
||||
details[ConfProperties::ARCHIVE_PATH] = archivePath;
|
||||
if (type == profile::Type::SIP)
|
||||
details[ConfProperties::USERNAME] = uri;
|
||||
|
||||
// Add the supplied config to the details
|
||||
if (!config.isEmpty()) {
|
||||
for (MapStringString::const_iterator it = config.begin(); it != config.end(); it++) {
|
||||
details[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
using namespace libjami::Account;
|
||||
|
||||
// Add the rest of the details if we are not creating an ephemeral account for linking
|
||||
// in which case the ARCHIVE_URL was set to "jami-auth" or the MANAGER_URI was set to
|
||||
// the account manager URI in the case of a remote account manager connection
|
||||
if (details[ConfProperties::ARCHIVE_URL].isEmpty()
|
||||
&& details[ConfProperties::MANAGER_URI].isEmpty()) {
|
||||
details[ConfProperties::TYPE] = type == profile::Type::SIP ? "SIP" : "RING";
|
||||
details[ConfProperties::DISPLAYNAME] = displayName;
|
||||
details[ConfProperties::ALIAS] = displayName;
|
||||
details[ConfProperties::UPNP_ENABLED] = "true";
|
||||
details[ConfProperties::ARCHIVE_PASSWORD] = password;
|
||||
details[ConfProperties::ARCHIVE_PIN] = pin;
|
||||
details[ConfProperties::ARCHIVE_PATH] = archivePath;
|
||||
|
||||
// Override the username with the provided URI if it's a SIP account
|
||||
if (type == profile::Type::SIP) {
|
||||
details[ConfProperties::USERNAME] = uri;
|
||||
}
|
||||
}
|
||||
|
||||
// Actually add the account and return the account ID
|
||||
QString accountId = ConfigurationManager::instance().addAccount(details);
|
||||
return accountId;
|
||||
}
|
||||
@ -1077,20 +1135,24 @@ AccountModel::connectToAccountManager(const QString& username,
|
||||
const QString& serverUri,
|
||||
const MapStringString& config)
|
||||
{
|
||||
MapStringString details = ConfigurationManager::instance().getAccountTemplate("RING");
|
||||
MapStringString details = config;
|
||||
using namespace libjami::Account;
|
||||
details[ConfProperties::TYPE] = "RING";
|
||||
details[ConfProperties::MANAGER_URI] = serverUri;
|
||||
details[ConfProperties::MANAGER_USERNAME] = username;
|
||||
details[ConfProperties::ARCHIVE_PASSWORD] = password;
|
||||
if (!config.isEmpty()) {
|
||||
for (MapStringString::const_iterator it = config.begin(); it != config.end(); it++) {
|
||||
details[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
return createNewAccount(profile::Type::JAMI, details);
|
||||
}
|
||||
|
||||
QString accountId = ConfigurationManager::instance().addAccount(details);
|
||||
return accountId;
|
||||
QString
|
||||
AccountModel::createDeviceImportAccount()
|
||||
{
|
||||
// auto details = ConfigurationManager::instance().getAccountTemplate("RING");
|
||||
MapStringString details;
|
||||
using namespace libjami::Account;
|
||||
details[ConfProperties::TYPE] = "RING";
|
||||
details[ConfProperties::ARCHIVE_URL] = "jami-auth";
|
||||
return createNewAccount(profile::Type::JAMI, details);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@ -188,9 +188,65 @@ struct ConfProperties_t
|
||||
MapStringString toDetails() const;
|
||||
};
|
||||
|
||||
// Possible account export status
|
||||
enum class ExportOnRingStatus { SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2, INVALID };
|
||||
Q_ENUM_NS(ExportOnRingStatus)
|
||||
// The following statuses are used to track the status of
|
||||
// device-linking and account-import
|
||||
enum class DeviceAuthState {
|
||||
INIT = 0,
|
||||
TOKEN_AVAILABLE = 1,
|
||||
CONNECTING = 2,
|
||||
AUTHENTICATING = 3,
|
||||
IN_PROGRESS = 4,
|
||||
DONE = 5
|
||||
};
|
||||
Q_ENUM_NS(DeviceAuthState)
|
||||
|
||||
enum class DeviceLinkError {
|
||||
WRONG_PASSWORD, // auth_error, invalid_credentials
|
||||
NETWORK, // network
|
||||
TIMEOUT, // timeout
|
||||
STATE, // state
|
||||
CANCELED, // canceled
|
||||
UNKNOWN // fallback
|
||||
};
|
||||
|
||||
Q_ENUM_NS(DeviceLinkError)
|
||||
|
||||
inline DeviceLinkError
|
||||
mapLinkDeviceError(const std::string& error)
|
||||
{
|
||||
if (error == "auth_error" || error == "invalid_credentials")
|
||||
return DeviceLinkError::WRONG_PASSWORD;
|
||||
if (error == "network")
|
||||
return DeviceLinkError::NETWORK;
|
||||
if (error == "timeout")
|
||||
return DeviceLinkError::TIMEOUT;
|
||||
if (error == "state")
|
||||
return DeviceLinkError::STATE;
|
||||
if (error == "canceled")
|
||||
return DeviceLinkError::CANCELED;
|
||||
return DeviceLinkError::UNKNOWN;
|
||||
}
|
||||
|
||||
inline QString
|
||||
getLinkDeviceString(DeviceLinkError error)
|
||||
{
|
||||
switch (error) {
|
||||
case DeviceLinkError::WRONG_PASSWORD:
|
||||
return QObject::tr(
|
||||
"An authentication error occurred.\nPlease check credentials and try again.");
|
||||
case DeviceLinkError::NETWORK:
|
||||
return QObject::tr("A network error occurred.\nPlease verify your connection.");
|
||||
case DeviceLinkError::TIMEOUT:
|
||||
return QObject::tr("The operation has timed out.\nPlease try again.");
|
||||
case DeviceLinkError::STATE:
|
||||
return QObject::tr("An error occurred while exporting the account.\nPlease try again.");
|
||||
case DeviceLinkError::CANCELED:
|
||||
return QObject::tr("Operation was canceled.");
|
||||
case DeviceLinkError::UNKNOWN:
|
||||
default:
|
||||
return QObject::tr("An unexpected error occurred.\nPlease try again.");
|
||||
}
|
||||
}
|
||||
|
||||
enum class RegisterNameStatus {
|
||||
SUCCESS = 0,
|
||||
|
||||
@ -112,13 +112,37 @@ public:
|
||||
Q_INVOKABLE bool exportToFile(const QString& accountId,
|
||||
const QString& path,
|
||||
const QString& password = {}) const;
|
||||
|
||||
/**
|
||||
* Call exportOnRing from the daemon
|
||||
* Provide authentication for an account
|
||||
* @param accountId
|
||||
* @param password
|
||||
* @param credentialsFromUser
|
||||
* @return if the authentication is successful
|
||||
*/
|
||||
Q_INVOKABLE bool provideAccountAuthentication(const QString& accountId,
|
||||
const QString& credentialsFromUser) const;
|
||||
|
||||
/**
|
||||
* @param accountId
|
||||
* @param uri
|
||||
* @return if the export is initialized
|
||||
*/
|
||||
Q_INVOKABLE bool exportOnRing(const QString& accountId, const QString& password) const;
|
||||
Q_INVOKABLE int32_t addDevice(const QString& accountId, const QString& token) const;
|
||||
|
||||
/**
|
||||
* Confirm the addition of a device
|
||||
* @param accountId
|
||||
* @param operationId
|
||||
*/
|
||||
Q_INVOKABLE bool confirmAddDevice(const QString& accountId, uint32_t operationId) const;
|
||||
|
||||
/**
|
||||
* Cancel the addition of a device
|
||||
* @param accountId
|
||||
* @param operationId
|
||||
*/
|
||||
Q_INVOKABLE bool cancelAddDevice(const QString& accountId, uint32_t operationId) const;
|
||||
|
||||
/**
|
||||
* Call removeAccount from the daemon
|
||||
* @param accountId to remove
|
||||
@ -141,7 +165,7 @@ public:
|
||||
* @param avatar
|
||||
* @throws out_of_range exception if account is not found
|
||||
*/
|
||||
void setAvatar(const QString& accountId, const QString& avatar, bool save = true, int flag =0);
|
||||
void setAvatar(const QString& accountId, const QString& avatar, bool save = true, int flag = 0);
|
||||
/**
|
||||
* Change the alias of an account
|
||||
* @param accountId
|
||||
@ -159,18 +183,7 @@ public:
|
||||
Q_INVOKABLE bool registerName(const QString& accountId,
|
||||
const QString& password,
|
||||
const QString& username);
|
||||
/**
|
||||
* Connect to JAMS to retrieve the account
|
||||
* @param username
|
||||
* @param password
|
||||
* @param serverUri
|
||||
* @param config
|
||||
* @return the account id
|
||||
*/
|
||||
static QString connectToAccountManager(const QString& username,
|
||||
const QString& password,
|
||||
const QString& serverUri,
|
||||
const MapStringString& config = MapStringString());
|
||||
|
||||
/**
|
||||
* Create a new Ring or SIP account
|
||||
* @param type determine if the new account will be a Ring account or a SIP one
|
||||
@ -184,12 +197,32 @@ public:
|
||||
* @return the created account
|
||||
*/
|
||||
static QString createNewAccount(profile::Type type,
|
||||
const MapStringString& config = MapStringString(),
|
||||
const QString& displayName = "",
|
||||
const QString& archivePath = "",
|
||||
const QString& password = "",
|
||||
const QString& pin = "",
|
||||
const QString& uri = "",
|
||||
const MapStringString& config = MapStringString());
|
||||
const QString& uri = "");
|
||||
|
||||
/**
|
||||
* Connect to JAMS to retrieve the account
|
||||
* @param username
|
||||
* @param password
|
||||
* @param serverUri
|
||||
* @param config
|
||||
* @return the account id
|
||||
*/
|
||||
static QString connectToAccountManager(const QString& username,
|
||||
const QString& password,
|
||||
const QString& serverUri,
|
||||
const MapStringString& config = MapStringString());
|
||||
|
||||
/**
|
||||
* Create a simple ephemeral account from a device import
|
||||
* @return the account id of the created account
|
||||
*/
|
||||
static QString createDeviceImportAccount();
|
||||
|
||||
/**
|
||||
* Set an account to the first position
|
||||
*/
|
||||
@ -296,14 +329,24 @@ Q_SIGNALS:
|
||||
void profileUpdated(const QString& accountID);
|
||||
|
||||
/**
|
||||
* Connect this signal to know when an account is exported on the DHT
|
||||
* Device authentication state has changed
|
||||
* @param accountID
|
||||
* @param status
|
||||
* @param pin
|
||||
* @param state
|
||||
* @param details map
|
||||
*/
|
||||
void exportOnRingEnded(const QString& accountID,
|
||||
account::ExportOnRingStatus status,
|
||||
const QString& pin);
|
||||
void deviceAuthStateChanged(const QString& accountID, int state, const MapStringString& details);
|
||||
|
||||
/**
|
||||
* Add device state has changed
|
||||
* @param accountID
|
||||
* @param operationId
|
||||
* @param state
|
||||
* @param details map
|
||||
*/
|
||||
void addDeviceStateChanged(const QString& accountID,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details);
|
||||
|
||||
/**
|
||||
* Name registration has ended
|
||||
|
||||
@ -406,7 +406,9 @@ public:
|
||||
* @param windowProcessId
|
||||
* @param windowId
|
||||
*/
|
||||
QString getDisplay(const QString& windowProcessId, const QString& windowId);
|
||||
QString getDisplay(const QString& windowProcessId,
|
||||
const QString& windowId,
|
||||
const int fps = -1);
|
||||
|
||||
void emplaceConversationConference(const QString& callId);
|
||||
|
||||
@ -481,7 +483,9 @@ Q_SIGNALS:
|
||||
* @param conversationId
|
||||
* @param confId
|
||||
*/
|
||||
void callAddedToConference(const QString& callId, const QString& conversationId, const QString& confId) const;
|
||||
void callAddedToConference(const QString& callId,
|
||||
const QString& conversationId,
|
||||
const QString& confId) const;
|
||||
|
||||
/**
|
||||
* Emitted when a voice mail notice arrives
|
||||
|
||||
@ -288,6 +288,9 @@ getContactInteractionString(const QString& authorUri, const ContactAction& actio
|
||||
}
|
||||
return QObject::tr("%1 has joined the conversation.").arg(authorUri);
|
||||
case ContactAction::LEAVE:
|
||||
if (authorUri.isEmpty()) {
|
||||
return QObject::tr("You left the conversation.");
|
||||
}
|
||||
return QObject::tr("%1 has left the conversation.").arg(authorUri);
|
||||
case ContactAction::BANNED:
|
||||
return QObject::tr("%1 was blocked from the conversation.").arg(authorUri);
|
||||
|
||||
@ -242,9 +242,15 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(&ConfigurationManager::instance(),
|
||||
&ConfigurationManagerInterface::exportOnRingEnded,
|
||||
&ConfigurationManagerInterface::deviceAuthStateChanged,
|
||||
this,
|
||||
&CallbacksHandler::slotExportOnRingEnded,
|
||||
&CallbacksHandler::slotDeviceAuthStateChanged,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(&ConfigurationManager::instance(),
|
||||
&ConfigurationManagerInterface::addDeviceStateChanged,
|
||||
this,
|
||||
&CallbacksHandler::slotAddDeviceStateChanged,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(&ConfigurationManager::instance(),
|
||||
@ -546,7 +552,9 @@ CallbacksHandler::slotIncomingMessage(const QString& accountId,
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotConferenceCreated(const QString& accountId, const QString& convId, const QString& callId)
|
||||
CallbacksHandler::slotConferenceCreated(const QString& accountId,
|
||||
const QString& convId,
|
||||
const QString& callId)
|
||||
{
|
||||
Q_EMIT conferenceCreated(accountId, convId, callId);
|
||||
}
|
||||
@ -678,9 +686,20 @@ CallbacksHandler::slotDeviceRevokationEnded(const QString& accountId,
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotExportOnRingEnded(const QString& accountId, int status, const QString& pin)
|
||||
CallbacksHandler::slotDeviceAuthStateChanged(const QString& accountId,
|
||||
int state,
|
||||
const MapStringString& details)
|
||||
{
|
||||
Q_EMIT exportOnRingEnded(accountId, status, pin);
|
||||
Q_EMIT deviceAuthStateChanged(accountId, state, details);
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotAddDeviceStateChanged(const QString& accountId,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details)
|
||||
{
|
||||
Q_EMIT addDeviceStateChanged(accountId, operationId, state, details);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user