GODT-22: Frontend-backend
- GODT-1246 Implement settings view. - GODT-1257 GODT-1246: Account and Help view - GODT-1298: Minimal working build (panics) - GODT-1298: loading QML (needs Popup window) - GODT-1298: WARN: Adding PopupWindow not possible! In therecipe qt the `quickwidgets` classes are within `quick` module, but forgot to add library and include paths into cgo flags. Therefore compilation fails and it would be hard to patch therecipe in order to fix it. I am not sure if rewrite PopupWindow into go would make any difference, therefore I decided to use normal QML Window without borders. - GODT-1298: Rework status window, add backend props, slots and signals. - GODT-1298: Users - GODT-1298: Login - GODT-1298: WIP Help and bug report - GODT-1178: MacOS dock icon control - GODT-1298: Help, bug report, update and events - GODT-1298: Apple Mail config and Settings (without cache on disk)
|
@ -6,9 +6,6 @@
|
|||
.*.sw?
|
||||
*~
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
vendor
|
||||
|
||||
# Test files
|
||||
godog.test
|
||||
debug.test
|
||||
|
@ -17,17 +14,12 @@ coverage.html
|
|||
# Run files
|
||||
mem.pprof
|
||||
|
||||
# Auto generated frontend
|
||||
internal/frontend/qml/BridgeUI/*.qmlc
|
||||
internal/frontend/qml/ImportExportUI/*.qmlc
|
||||
internal/frontend/qml/ProtonUI/*.qmlc
|
||||
internal/frontend/qml/ProtonUI/fontawesome.ttf
|
||||
internal/frontend/qml/ProtonUI/images
|
||||
internal/frontend/qml/ImportExportUI/images
|
||||
frontend/qml/*.qmlc
|
||||
|
||||
# Credits files (generated).
|
||||
# Auto generated
|
||||
internal/**/credits.go
|
||||
vendor
|
||||
vendor-cache
|
||||
/main.go
|
||||
|
||||
|
||||
# Build files
|
||||
/launcher-*
|
||||
|
@ -37,18 +29,3 @@ internal/**/credits.go
|
|||
/hasher
|
||||
cmd/Desktop-Bridge/deploy
|
||||
cmd/Import-Export/deploy
|
||||
internal/frontend/qt*/moc.cpp
|
||||
internal/frontend/qt*/moc.go
|
||||
internal/frontend/qt*/moc.h
|
||||
internal/frontend/qt*/moc_cgo_*.go
|
||||
internal/frontend/qt*/moc_moc.h
|
||||
internal/frontend/qt*/rcc.cpp
|
||||
internal/frontend/qt*/rcc.qrc
|
||||
internal/frontend/qt*/rcc_cgo_*.go
|
||||
|
||||
internal/frontend/rcc.cpp
|
||||
internal/frontend/rcc.qrc
|
||||
internal/frontend/rcc_cgo_*.go
|
||||
vendor-cache/
|
||||
|
||||
/main.go
|
||||
|
|
|
@ -126,6 +126,7 @@ build-linux-qa:
|
|||
extends: .build-base
|
||||
only:
|
||||
- web
|
||||
- branches
|
||||
script:
|
||||
- BUILD_TAGS="build_qa" make build
|
||||
artifacts:
|
||||
|
@ -161,6 +162,7 @@ build-darwin-qa:
|
|||
extends: .build-darwin-base
|
||||
only:
|
||||
- web
|
||||
- branches
|
||||
script:
|
||||
- BUILD_TAGS="build_qa" make build
|
||||
artifacts:
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
---
|
||||
run:
|
||||
timeout: 10m
|
||||
build-tags:
|
||||
- nogui
|
||||
skip-dirs:
|
||||
- pkg/mime
|
||||
|
||||
|
|
16
Makefile
|
@ -88,7 +88,7 @@ ${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
|
|||
cd ${DEPLOY_DIR}/${TARGET_OS} && tar czf ../../../../$@ .
|
||||
|
||||
${DEPLOY_DIR}/linux: ${EXE_TARGET}
|
||||
cp -pf ./internal/frontend/share/icons/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||
cp -pf ./internal/frontend/share/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||
cp -pf ./LICENSE ${DEPLOY_DIR}/linux/
|
||||
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
|
||||
cp -pf ./dist/${EXE_NAME}.desktop ${DEPLOY_DIR}/linux/
|
||||
|
@ -98,7 +98,7 @@ ${DEPLOY_DIR}/darwin: ${EXE_TARGET}
|
|||
mv ${EXE_TARGET}/Contents/MacOS/{${DIRNAME},${EXE_NAME}}; \
|
||||
perl -i -pe"s/>${DIRNAME}/>${EXE_NAME}/g" ${EXE_TARGET}/Contents/Info.plist; \
|
||||
fi
|
||||
cp ./internal/frontend/share/icons/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS}
|
||||
cp ./internal/frontend/share/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS}
|
||||
cp LICENSE ${DARWINAPP_CONTENTS}/Resources/
|
||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework"
|
||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework"
|
||||
|
@ -106,7 +106,7 @@ ${DEPLOY_DIR}/darwin: ${EXE_TARGET}
|
|||
./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET}${EXE_BINARY_DARWIN}"
|
||||
|
||||
${DEPLOY_DIR}/windows: ${EXE_TARGET}
|
||||
cp ./internal/frontend/share/icons/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
|
||||
cp ./internal/frontend/share/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
|
||||
cp LICENSE ${DEPLOY_DIR}/windows/
|
||||
|
||||
QT_BUILD_TARGET:=build desktop
|
||||
|
@ -127,9 +127,9 @@ ${EXE_TARGET}: check-has-go gofiles ${RESOURCE_FILE} ${VENDOR_TARGET}
|
|||
|
||||
WINDRES_YEAR:=$(shell date +%Y)
|
||||
APP_VERSION_COMMA:=$(shell echo "${APP_VERSION}" | sed -e 's/[^0-9,.]*//g' -e 's/\./,/g')
|
||||
resource.syso: ./internal/frontend/share/info.rc ./internal/frontend/share/icons/${SRC_ICO} .FORCE
|
||||
resource.syso: ./internal/frontend/share/info.rc ./internal/frontend/share/${SRC_ICO} .FORCE
|
||||
rm -f ./*.syso
|
||||
windres --target=pe-x86-64 -I ./internal/frontend/share/icons/ -D ICO_FILE=${SRC_ICO} -D EXE_NAME="${EXE_NAME}" -D FILE_VERSION="${APP_VERSION}" -D ORIGINAL_FILE_NAME="${EXE}" -D PRODUCT_VERSION="${APP_VERSION}" -D FILE_VERSION_COMMA=${APP_VERSION_COMMA} -D YEAR=${WINDRES_YEAR} -o $@ $<
|
||||
windres --target=pe-x86-64 -I ./internal/frontend/share/ -D ICO_FILE=${SRC_ICO} -D EXE_NAME="${EXE_NAME}" -D FILE_VERSION="${APP_VERSION}" -D ORIGINAL_FILE_NAME="${EXE}" -D PRODUCT_VERSION="${APP_VERSION}" -D FILE_VERSION_COMMA=${APP_VERSION_COMMA} -D YEAR=${WINDRES_YEAR} -o $@ $<
|
||||
|
||||
## Rules for therecipe/qt
|
||||
.PHONY: prepare-vendor update-vendor update-qt-docs
|
||||
|
@ -278,7 +278,7 @@ RUN_FLAGS?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
|
|||
run: run-nogui-cli
|
||||
|
||||
run-qt: ${EXE_TARGET}
|
||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} | tee last.log
|
||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} 2>&1 | tee last.log
|
||||
run-qt-cli: ${EXE_TARGET}
|
||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} -c
|
||||
|
||||
|
@ -296,9 +296,7 @@ run-qml-preview:
|
|||
|
||||
|
||||
clean-frontend-qt:
|
||||
# TODO: $(MAKE) -C internal/frontend/qt -f Makefile.local clean
|
||||
clean-frontend-qt-common:
|
||||
# TODO: $(MAKE) -C internal/frontend/qt-common -f Makefile.local clean
|
||||
$(MAKE) -C internal/frontend -f Makefile.local clean
|
||||
|
||||
clean-vendor: clean-frontend-qt clean-frontend-qt-common
|
||||
rm -rf ./vendor
|
||||
|
|
4
go.mod
|
@ -56,8 +56,10 @@ require (
|
|||
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.1.3
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
|
|
18
go.sum
|
@ -42,8 +42,6 @@ github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1
|
|||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||
github.com/ProtonMail/go-rfc5322 v0.8.0 h1:7emrf75n3CDIduQflx7aT1nJa5h/kGsiFKUYX/+IAkU=
|
||||
github.com/ProtonMail/go-rfc5322 v0.8.0/go.mod h1:BwpTbkJxkMGkc+pC84AXZnwuWOisEULBpfPIyIKS/Us=
|
||||
github.com/ProtonMail/go-srp v0.0.0-20210910093455-a843a0b9adff h1:eiue56XAPSkOpsy5Fwnyz4+Vd7i2cN5D4orc++Irt1g=
|
||||
github.com/ProtonMail/go-srp v0.0.0-20210910093455-a843a0b9adff/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
|
||||
github.com/ProtonMail/go-srp v0.0.1 h1:J0O9Zb5XTC6iDrB7feH41cu+TUEB+l7uHctXIK6oS2o=
|
||||
github.com/ProtonMail/go-srp v0.0.1/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
|
||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
||||
|
@ -195,6 +193,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
|
@ -264,6 +264,7 @@ github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
|
|||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
|
@ -361,9 +362,11 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
|||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
|
@ -393,6 +396,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d h1:hAZyEG2swPRWjF0kqqdGERXUazYnRJdAk4a58f14z7Y=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d h1:AJRoBel/g9cDS+yE8BcN3E+TDD/xNAguG21aoR8DAIE=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
|
@ -430,6 +439,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -473,6 +483,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
|
|||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
|
@ -505,7 +516,9 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -539,6 +552,7 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# Auto generated
|
||||
moc.cpp
|
||||
moc.go
|
||||
moc.h
|
||||
moc_cgo_*.go
|
||||
moc_moc.h
|
||||
rcc.cpp
|
||||
rcc.qrc
|
||||
rcc_cgo_*.go
|
||||
*.qmlc
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
FILES=$(shell find . -iname 'rcc.qrc')
|
||||
FILES+=$(shell find . -iname 'rcc.cpp')
|
||||
FILES+=$(shell find . -iname 'rcc_cgo*.go')
|
||||
|
||||
FILES+=$(shell find . -iname 'moc.go')
|
||||
FILES+=$(shell find . -iname 'moc.cpp')
|
||||
FILES+=$(shell find . -iname 'moc.h')
|
||||
FILES+=$(shell find . -iname 'moc_cgo*.go')
|
||||
|
||||
FILES+=$(shell find ./qml -iname '*.qmlc')
|
||||
|
||||
clean:
|
||||
rm -f ${FILES}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package clientconfig provides automatic config of IMAP and SMTP.
|
||||
// For now only for Apple Mail.
|
||||
package clientconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type AutoConfig interface {
|
||||
Name() string
|
||||
Configure(imapPort int, smtpPort int, imapSSl, smtpSSL bool, user types.User, address string) error
|
||||
}
|
||||
|
||||
var (
|
||||
available = map[string]AutoConfig{} //nolint[gochecknoglobals]
|
||||
ErrNotAvailable = errors.New("configuration not available")
|
||||
)
|
||||
|
||||
const AppleMailClient = "Apple Mail"
|
||||
|
||||
func ConfigureAppleMail(user types.User, address string, s *settings.Settings) (needRestart bool, err error) {
|
||||
return configure(AppleMailClient, user, address, s)
|
||||
}
|
||||
|
||||
func configure(configName string, user types.User, address string, s *settings.Settings) (needRestart bool, err error) {
|
||||
log := logrus.WithField("pkg", "client_config").WithField("client", configName)
|
||||
|
||||
config, ok := available[configName]
|
||||
if !ok {
|
||||
return false, ErrNotAvailable
|
||||
}
|
||||
|
||||
imapPort := s.GetInt(settings.IMAPPortKey)
|
||||
imapSSL := false
|
||||
smtpPort := s.GetInt(settings.SMTPPortKey)
|
||||
smtpSSL := s.GetBool(settings.SMTPSSLKey)
|
||||
|
||||
if address == "" {
|
||||
address = user.GetPrimaryAddress()
|
||||
}
|
||||
|
||||
if configName == AppleMailClient {
|
||||
// If configuring apple mail for Catalina or newer, users should use SSL.
|
||||
needRestart = false
|
||||
if !smtpSSL && useragent.IsCatalinaOrNewer() {
|
||||
smtpSSL = true
|
||||
s.SetBool(settings.SMTPSSLKey, true)
|
||||
log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart")
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
|
||||
return needRestart, config.Configure(imapPort, smtpPort, imapSSL, smtpSSL, user, address)
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build darwin
|
||||
|
||||
package clientconfig
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/mobileconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
bigSurPreferncesPane = "/System/Library/PreferencePanes/Profiles.prefPane"
|
||||
)
|
||||
|
||||
func init() { //nolint[gochecknoinit]
|
||||
available[AppleMailClient] = &appleMail{}
|
||||
}
|
||||
|
||||
type appleMail struct{}
|
||||
|
||||
func (c *appleMail) Name() string { return AppleMailClient }
|
||||
|
||||
func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, address string) error {
|
||||
mc := prepareMobileConfig(imapPort, smtpPort, imapSSL, smtpSSL, user, address)
|
||||
|
||||
confPath, err := saveConfigTemporarily(mc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if useragent.IsBigSurOrNewer() {
|
||||
return exec.Command("open", bigSurPreferncesPane, confPath).Run() //nolint[gosec] G204: open command is safe, mobileconfig is generated by us
|
||||
}
|
||||
|
||||
return exec.Command("open", confPath).Run() //nolint[gosec] G204: open command is safe, mobileconfig is generated by us
|
||||
}
|
||||
|
||||
func prepareMobileConfig(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, address string) *mobileconfig.Config {
|
||||
displayName := address
|
||||
addresses := address
|
||||
|
||||
if user.IsCombinedAddressMode() {
|
||||
displayName = user.GetPrimaryAddress()
|
||||
addresses = strings.Join(user.GetAddresses(), ",")
|
||||
}
|
||||
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
|
||||
return &mobileconfig.Config{
|
||||
EmailAddress: addresses,
|
||||
DisplayName: displayName,
|
||||
Identifier: "protonmail " + displayName + timestamp,
|
||||
IMAP: &mobileconfig.IMAP{
|
||||
Hostname: bridge.Host,
|
||||
Port: imapPort,
|
||||
TLS: imapSSL,
|
||||
Username: displayName,
|
||||
Password: user.GetBridgePassword(),
|
||||
},
|
||||
SMTP: &mobileconfig.SMTP{
|
||||
Hostname: bridge.Host,
|
||||
Port: smtpPort,
|
||||
TLS: smtpSSL,
|
||||
Username: displayName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) {
|
||||
dir, err := ioutil.TempDir("", "protonmail-autoconfig")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure the temporary file is deleted.
|
||||
go (func() {
|
||||
<-time.After(10 * time.Minute)
|
||||
_ = os.RemoveAll(dir)
|
||||
})()
|
||||
|
||||
// Make sure the file is only readable for the current user.
|
||||
fname = filepath.Clean(filepath.Join(dir, "protonmail.mobileconfig"))
|
||||
f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = mc.WriteOut(f); err != nil {
|
||||
_ = f.Close()
|
||||
return
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
return
|
||||
}
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/cli"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/qt"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
|
@ -59,6 +60,23 @@ func New(
|
|||
) Frontend {
|
||||
bridgeWrap := types.NewBridgeWrap(bridge)
|
||||
switch frontendType {
|
||||
case "qt":
|
||||
return qt.New(
|
||||
version,
|
||||
buildVersion,
|
||||
programName,
|
||||
showWindowOnStart,
|
||||
panicHandler,
|
||||
locations,
|
||||
settings,
|
||||
eventListener,
|
||||
updater,
|
||||
userAgent,
|
||||
bridgeWrap,
|
||||
noEncConfirmator,
|
||||
autostart,
|
||||
restarter,
|
||||
)
|
||||
case "cli":
|
||||
return cli.New(
|
||||
panicHandler,
|
||||
|
|
|
@ -27,12 +27,61 @@ Item {
|
|||
property ColorScheme colorScheme
|
||||
property var user
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
property var _spacing: 12
|
||||
property var _leftRightMargins: {
|
||||
switch(root.type) {
|
||||
case AccountDelegate.SmallView: return 12
|
||||
case AccountDelegate.LargeView: return 0
|
||||
}
|
||||
}
|
||||
property var _topBottomMargins: {
|
||||
switch(root.type) {
|
||||
case AccountDelegate.SmallView: return 10
|
||||
case AccountDelegate.LargeView: return 0
|
||||
}
|
||||
}
|
||||
|
||||
property color usedSpaceColor : {
|
||||
if (!root.enabled) return root.colorScheme.text_weak
|
||||
if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak
|
||||
if (root.usedFraction < .50) return root.colorScheme.signal_success
|
||||
if (root.usedFraction < .75) return root.colorScheme.signal_warning
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
property real usedFraction: root.user.totalBytes ? root.user.usedBytes / root.user.totalBytes : 0
|
||||
property string totalSpace: root.spaceWithUnits(root.user.totalBytes)
|
||||
property string usedSpace: root.spaceWithUnits(root.user.usedBytes)
|
||||
|
||||
function spaceWithUnits(bytes){
|
||||
if (bytes*1 !== bytes ) return "0 kB"
|
||||
var units = ['B',"kB", "MB", "TB"];
|
||||
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));
|
||||
|
||||
return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i]
|
||||
}
|
||||
|
||||
signal clicked()
|
||||
|
||||
// width expected to be set by parent object
|
||||
implicitHeight : children[0].implicitHeight + 2*root._topBottomMargins
|
||||
|
||||
enum ViewType{
|
||||
SmallView, LargeView
|
||||
}
|
||||
property var type : AccountDelegate.SmallView
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 12
|
||||
spacing: root._spacing
|
||||
|
||||
anchors {
|
||||
top: root.top
|
||||
left: root.left
|
||||
right: root.rigth
|
||||
leftMargin : root._leftRightMargins
|
||||
rightMargin : root._leftRightMargins
|
||||
topMargin : root._topBottomMargins
|
||||
bottomMargin : root._topBottomMargins
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: avatar
|
||||
|
@ -48,8 +97,19 @@ Item {
|
|||
colorScheme: root.colorScheme
|
||||
anchors.fill: parent
|
||||
text: root.user.avatarText.toUpperCase()
|
||||
type: Label.LabelType.Body
|
||||
color: root.colorScheme.text_invert
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
case AccountDelegate.LargeView: return Label.Title
|
||||
}
|
||||
}
|
||||
font.weight: Font.Normal
|
||||
color: {
|
||||
switch(root.type) {
|
||||
case AccountDelegate.SmallView: return root.colorScheme.text_norm
|
||||
case AccountDelegate.LargeView: return root.colorScheme.text_invert
|
||||
}
|
||||
}
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
}
|
||||
|
@ -63,16 +123,78 @@ Item {
|
|||
spacing: 0
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: root.width - (
|
||||
root._spacing + avatar.width + 2*root._leftRightMargins
|
||||
)
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: user.username
|
||||
type: Label.LabelType.Body
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
case AccountDelegate.LargeView: return Label.Title
|
||||
}
|
||||
}
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: user.captionText
|
||||
type: Label.LabelType.Caption
|
||||
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 6 : 0 }
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.usedSpace
|
||||
color: root.usedSpaceColor
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
case AccountDelegate.LargeView: return Label.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: " / " + root.totalSpace
|
||||
color: root.colorScheme.text_weak
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
case AccountDelegate.LargeView: return Label.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
visible: root.type == AccountDelegate.LargeView
|
||||
|
||||
width: 140
|
||||
height: 4
|
||||
radius: 3
|
||||
color: root.colorScheme.border_weak
|
||||
|
||||
Rectangle {
|
||||
radius: 3
|
||||
color: root.usedSpaceColor
|
||||
anchors {
|
||||
top : parent.top
|
||||
bottom : parent.bottom
|
||||
left : parent.left
|
||||
}
|
||||
width: Math.min(1,Math.max(0.02,root.usedFraction)) * parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: root
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,34 +21,245 @@ import QtQuick.Controls 2.12
|
|||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
ScrollView {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
property var notifications
|
||||
property var user
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
clip: true
|
||||
contentWidth: pane.width
|
||||
contentHeight: pane.height
|
||||
|
||||
property int _leftRightMargins: 64
|
||||
property int _topBottomMargins: 68
|
||||
property int _spacing: 22
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
bottom: pane.bottom
|
||||
}
|
||||
color: root.colorScheme.background_weak
|
||||
width: root.width
|
||||
height: configuration.height + root._topBottomMargins
|
||||
}
|
||||
|
||||
signal showSignIn()
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
id: pane
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 277
|
||||
Layout.maximumHeight: 277
|
||||
width: root.width
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
ColumnLayout {
|
||||
spacing: root._spacing
|
||||
Layout.topMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
|
||||
ColumnLayout {
|
||||
RowLayout { // account delegate with action buttons
|
||||
Layout.fillWidth: true
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
user: root.user
|
||||
type: AccountDelegate.LargeView
|
||||
enabled: root.user.loggedIn
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign out")
|
||||
secondary: true
|
||||
visible: root.user.loggedIn
|
||||
onClicked: root.user.logout()
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
icon.source: "icons/ic-trash.svg"
|
||||
secondary: true
|
||||
visible: root.user.loggedIn
|
||||
onClicked: root.user.remove()
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign in")
|
||||
secondary: true
|
||||
visible: !root.user.loggedIn
|
||||
onClicked: root.parent.rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Email clients")
|
||||
actionText: qsTr("Configure")
|
||||
description: "MISSING WIREFRAME" // TODO
|
||||
type: SettingsItem.Button
|
||||
enabled: root.user.loggedIn
|
||||
visible: !root.user.splitMode
|
||||
onClicked: root.showSetupGuide(root.user,user.addresses[0])
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: splitMode
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Split addresses")
|
||||
description: qsTr("Split addresses allows you to configure multiple email addresses individually. Changing its mode will require you to delete your accounts(s) from your email client and begin the setup process from scratch.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.user.splitMode
|
||||
visible: root.user.addresses.length > 1
|
||||
enabled: root.user.loggedIn
|
||||
onClicked: {
|
||||
if (!splitMode.checked){
|
||||
root.notifications.askEnableSplitMode(user)
|
||||
} else {
|
||||
root.user.toggleSplitMode(!splitMode.checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: root.user.loggedIn
|
||||
|
||||
visible: root.user.splitMode
|
||||
|
||||
ComboBox {
|
||||
id: addressSelector
|
||||
Layout.fillWidth: true
|
||||
model: root.user.addresses
|
||||
|
||||
property var _topBottomMargins : 8
|
||||
property var _leftRightMargins : 16
|
||||
|
||||
background: RoundedRectangle {
|
||||
radiusTopLeft : 6
|
||||
radiusTopRight : 6
|
||||
radiusBottomLeft : addressSelector.down ? 0 : 6
|
||||
radiusBottomRight : addressSelector.down ? 0 : 6
|
||||
|
||||
height: addressSelector.contentItem.height
|
||||
//width: addressSelector.contentItem.width
|
||||
|
||||
fillColor : root.colorScheme.background_norm
|
||||
strokeColor : root.colorScheme.border_norm
|
||||
strokeWidth : 1
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: listItem
|
||||
width: root.width
|
||||
height: children[0].height + 4 + 2*addressSelector._topBottomMargins
|
||||
|
||||
Label {
|
||||
anchors {
|
||||
top : parent.top
|
||||
left : parent.left
|
||||
topMargin : addressSelector._topBottomMargins + 4
|
||||
leftMargin : addressSelector._leftRightMargins
|
||||
}
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: modelData
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
property bool isOver: false
|
||||
color: {
|
||||
if (listItem.isOver) return root.colorScheme.interaction_weak_hover
|
||||
if (addressSelector.highlightedIndex === index) return root.colorScheme.interaction_weak
|
||||
return root.colorScheme.background_norm
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: listItem.isOver = true
|
||||
onExited: listItem.isOver = false
|
||||
onClicked : {
|
||||
addressSelector.currentIndex = index
|
||||
addressSelector.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label {
|
||||
topPadding : addressSelector._topBottomMargins+4
|
||||
bottomPadding : addressSelector._topBottomMargins
|
||||
leftPadding : addressSelector._leftRightMargins
|
||||
rightPadding : addressSelector._leftRightMargins
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: addressSelector.displayText
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Configure")
|
||||
secondary: true
|
||||
onClicked: root.showSetupGuide(root.user, addressSelector.displayText)
|
||||
}
|
||||
}
|
||||
|
||||
Item {implicitHeight: 1}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ColumnLayout {
|
||||
id: configuration
|
||||
Layout.bottomMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
spacing: root._spacing
|
||||
visible: root.user.loggedIn
|
||||
|
||||
color: root.colorScheme.background_weak
|
||||
property string currentAddress: addressSelector.displayText
|
||||
|
||||
Item {height: 1}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Mailbox details")
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("IMAP")
|
||||
hostname: root.backend.hostname
|
||||
port: root.backend.portIMAP.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user.password
|
||||
security: "STARTTLS"
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("SMTP")
|
||||
hostname : root.backend.hostname
|
||||
port : root.backend.portSMTP.toString()
|
||||
username : configuration.currentAddress
|
||||
password : root.user.password
|
||||
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,13 @@ Popup {
|
|||
|
||||
property ColorScheme colorScheme
|
||||
property Notification notification
|
||||
property var mainWindow
|
||||
|
||||
topMargin: 37
|
||||
leftMargin: (mainWindow.width - root.implicitWidth)/2
|
||||
|
||||
implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin
|
||||
implicitWidth: contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||
|
||||
popupType: ApplicationWindow.PopupType.Banner
|
||||
|
||||
|
@ -74,13 +78,13 @@ Popup {
|
|||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
}
|
||||
|
@ -109,13 +113,13 @@ Popup {
|
|||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
return "./icons/ic-info-circle-filled.svg"
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
return "./icons/ic-info-circle-filled.svg"
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
return "./icons/ic-exclamation-circle-filled.svg"
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
return "./icons/ic-exclamation-circle-filled.svg"
|
||||
}
|
||||
}
|
||||
|
@ -145,13 +149,13 @@ Popup {
|
|||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info_active
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success_active
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning_active
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger_active
|
||||
}
|
||||
}
|
||||
|
@ -183,22 +187,22 @@ Popup {
|
|||
var active
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
norm = root.colorScheme.signal_info
|
||||
hover = root.colorScheme.signal_info_hover
|
||||
active = root.colorScheme.signal_info_active
|
||||
break;
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
norm = root.colorScheme.signal_success
|
||||
hover = root.colorScheme.signal_success_hover
|
||||
active = root.colorScheme.signal_success_active
|
||||
break;
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
norm = root.colorScheme.signal_warning
|
||||
hover = root.colorScheme.signal_warning_hover
|
||||
active = root.colorScheme.signal_warning_active
|
||||
break;
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
norm = root.colorScheme.signal_danger
|
||||
hover = root.colorScheme.signal_danger_hover
|
||||
active = root.colorScheme.signal_danger_active
|
||||
|
|
|
@ -25,12 +25,7 @@ import Notifications 1.0
|
|||
QtObject {
|
||||
id: root
|
||||
|
||||
property var backend
|
||||
|
||||
signal login(string username, string password)
|
||||
signal login2FA(string username, string code)
|
||||
signal login2Password(string username, string password)
|
||||
signal loginAbort(string username)
|
||||
property var backend: go
|
||||
|
||||
property Notifications _notifications: Notifications {
|
||||
id: notifications
|
||||
|
@ -45,19 +40,23 @@ QtObject {
|
|||
visible: false
|
||||
|
||||
backend: root.backend
|
||||
notifications: notifications
|
||||
notifications: root._notifications
|
||||
|
||||
onLogin: {
|
||||
root.login(username, password)
|
||||
backend.login(username, password)
|
||||
}
|
||||
onLogin2FA: {
|
||||
root.login2FA(username, code)
|
||||
backend.login2FA(username, code)
|
||||
}
|
||||
onLogin2Password: {
|
||||
root.login2Password(username, password)
|
||||
backend.login2Password(username, password)
|
||||
}
|
||||
onLoginAbort: {
|
||||
root.loginAbort(username)
|
||||
backend.loginAbort(username)
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
backend.dockIconVisible = visible
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,20 +65,45 @@ QtObject {
|
|||
visible: false
|
||||
|
||||
backend: root.backend
|
||||
notifications: notifications
|
||||
notifications: root._notifications
|
||||
|
||||
property var x_center: 10
|
||||
property var x_min: 0
|
||||
property var x_max: 100
|
||||
property var y_center: 1000
|
||||
property var y_min: 0
|
||||
property var y_max: 10000
|
||||
|
||||
x: bound(x_center,x_min, x_max-statusWindow.width)
|
||||
y: bound(y_center,y_min, y_max-statusWindow.height)
|
||||
|
||||
|
||||
onShowMainWindow: {
|
||||
mainWindow.visible = true
|
||||
}
|
||||
|
||||
onShowHelp: {
|
||||
|
||||
mainWindow.showHelp()
|
||||
mainWindow.visible = true
|
||||
}
|
||||
|
||||
onShowSettings: {
|
||||
|
||||
mainWindow.showSettings()
|
||||
mainWindow.visible = true
|
||||
}
|
||||
|
||||
onShowSignIn: {
|
||||
mainWindow.showSignIn(username)
|
||||
mainWindow.visible = true
|
||||
}
|
||||
|
||||
onQuit: {
|
||||
backend.quit()
|
||||
}
|
||||
|
||||
function bound(num, lower_limit, upper_limit) {
|
||||
return Math.max(lower_limit, Math.min(upper_limit, num))
|
||||
}
|
||||
}
|
||||
|
||||
property SystemTrayIcon _trayIcon: SystemTrayIcon {
|
||||
|
@ -88,103 +112,59 @@ QtObject {
|
|||
iconSource: "./icons/ic-systray.svg"
|
||||
onActivated: {
|
||||
function calcStatusWindowPosition(statusWidth, statusHeight) {
|
||||
function bound(num, lower_limit, upper_limit) {
|
||||
return Math.max(lower_limit, Math.min(upper_limit, num))
|
||||
function isInInterval(num, lower_limit, upper_limit) {
|
||||
return lower_limit <= num && num <= upper_limit
|
||||
}
|
||||
// checks if rect1 fits within rect2
|
||||
function isRectFit(rect1, rect2) {
|
||||
//if (rect2.)
|
||||
if ((rect2.left > rect1.left) ||
|
||||
(rect2.right < rect1.right) ||
|
||||
(rect2.top > rect1.top) ||
|
||||
(rect2.bottom < rect1.bottom)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// First we get icon center position.
|
||||
// On some platforms (X11 / Wayland) Qt does not provide icon geometry info.
|
||||
// In this case we rely on cursor position
|
||||
var iconWidth = geometry.width *1.2
|
||||
var iconHeight = geometry.height *1.2
|
||||
var iconCenter = Qt.point(geometry.x + (geometry.width / 2), geometry.y + (geometry.height / 2))
|
||||
|
||||
if (geometry.width == 0 && geometry.height == 0) {
|
||||
iconCenter = backend.getCursorPos()
|
||||
// fallback: simple guess, no data to estimate
|
||||
iconWidth = 25
|
||||
iconHeight = 25
|
||||
}
|
||||
|
||||
// Now bound this position to virtual screen available rect
|
||||
// TODO: here we should detect which screen mouse is on and use that screen available geometry to bound
|
||||
iconCenter.x = bound(iconCenter.x, 0, Qt.application.screens[0].desktopAvailableWidth)
|
||||
iconCenter.y = bound(iconCenter.y, 0, Qt.application.screens[0].desktopAvailableHeight)
|
||||
// Find screen
|
||||
var screen = Qt.application.screens[0]
|
||||
|
||||
var x = 0
|
||||
var y = 0
|
||||
|
||||
// Check if window may fit above
|
||||
x = iconCenter.x - statusWidth / 2
|
||||
y = iconCenter.y - statusHeight
|
||||
if (isRectFit(
|
||||
Qt.rect(x, y, statusWidth, statusHeight),
|
||||
// TODO: we should detect which screen mouse is on and use that screen available geometry to bound
|
||||
Qt.rect(0, 0, Qt.application.screens[0].desktopAvailableWidth, Qt.application.screens[0].desktopAvailableHeight)
|
||||
)) {
|
||||
return Qt.point(x, y)
|
||||
for (var i in Qt.application.screens) {
|
||||
screen = Qt.application.screens[i]
|
||||
if (
|
||||
isInInterval(iconCenter.x, screen.virtualX, screen.virtualX+screen.width) &&
|
||||
isInInterval(iconCenter.y, screen.virtualY, screen.virtualY+screen.heigh)
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check if window may fit below
|
||||
x = iconCenter.x - statusWidth / 2
|
||||
y = iconCenter.y
|
||||
if (isRectFit(
|
||||
Qt.rect(x, y, statusWidth, statusHeight),
|
||||
// TODO: we should detect which screen mouse is on and use that screen available geometry to bound
|
||||
Qt.rect(0, 0, Qt.application.screens[0].desktopAvailableWidth, Qt.application.screens[0].desktopAvailableHeight)
|
||||
)) {
|
||||
return Qt.point(x, y)
|
||||
}
|
||||
|
||||
// Check if window may fit to the left
|
||||
x = iconCenter.x - statusWidth
|
||||
y = iconCenter.y - statusHeight / 2
|
||||
if (isRectFit(
|
||||
Qt.rect(x, y, statusWidth, statusHeight),
|
||||
// TODO: we should detect which screen mouse is on and use that screen available geometry to bound
|
||||
Qt.rect(0, 0, Qt.application.screens[0].desktopAvailableWidth, Qt.application.screens[0].desktopAvailableHeight)
|
||||
)) {
|
||||
return Qt.point(x, y)
|
||||
}
|
||||
|
||||
// Check if window may fit to the right
|
||||
x = iconCenter.x
|
||||
y = iconCenter.y - statusHeight / 2
|
||||
if (isRectFit(
|
||||
Qt.rect(x, y, statusWidth, statusHeight),
|
||||
// TODO: we should detect which screen mouse is on and use that screen available geometry to bound
|
||||
Qt.rect(0, 0, Qt.application.screens[0].desktopAvailableWidth, Qt.application.screens[0].desktopAvailableHeight)
|
||||
)) {
|
||||
return Qt.point(x, y)
|
||||
}
|
||||
|
||||
// TODO: add fallback
|
||||
// Calculate allowed square where status window top left corner can be positioned
|
||||
statusWindow.x_center = iconCenter.x
|
||||
statusWindow.y_center = iconCenter.y
|
||||
statusWindow.x_min = screen.virtualX + iconWidth
|
||||
statusWindow.x_max = screen.virtualX + screen.width - iconWidth
|
||||
statusWindow.y_min = screen.virtualY + iconHeight
|
||||
statusWindow.y_max = screen.virtualY + screen.height - iconHeight
|
||||
}
|
||||
|
||||
switch (reason) {
|
||||
case SystemTrayIcon.Unknown:
|
||||
case SystemTrayIcon.Unknown:
|
||||
break;
|
||||
case SystemTrayIcon.Context:
|
||||
case SystemTrayIcon.Trigger:!statusWindow.visible
|
||||
if (!statusWindow.visible) {
|
||||
var point = calcStatusWindowPosition(statusWindow.width, statusWindow.height)
|
||||
statusWindow.x = point.x
|
||||
statusWindow.y = point.y
|
||||
}
|
||||
case SystemTrayIcon.Context:
|
||||
case SystemTrayIcon.Trigger:
|
||||
calcStatusWindowPosition()
|
||||
statusWindow.visible = !statusWindow.visible
|
||||
break
|
||||
case SystemTrayIcon.DoubleClick:
|
||||
case SystemTrayIcon.MiddleClick:
|
||||
case SystemTrayIcon.DoubleClick:
|
||||
case SystemTrayIcon.MiddleClick:
|
||||
mainWindow.visible = !mainWindow.visible
|
||||
break;
|
||||
default:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -236,6 +236,53 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Login Finished"
|
||||
|
||||
onClicked: {
|
||||
root.backend.loginFinished()
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
label: "used:"
|
||||
text: user && user.usedBytes ? user.usedBytes : 0
|
||||
validator: DoubleValidator {bottom: 1; top: 1024*1024*1024*1024*1024}
|
||||
onEditingFinished: {
|
||||
user.usedBytes = parseFloat(text)
|
||||
}
|
||||
implicitWidth: 200
|
||||
}
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
label: "total:"
|
||||
text: user && user.totalBytes ? user.totalBytes : 0
|
||||
validator: DoubleValidator {bottom: 1; top: 1024*1024*1024*1024*1024}
|
||||
onEditingFinished: {
|
||||
user.totalBytes = parseFloat(text)
|
||||
}
|
||||
implicitWidth: 200
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Split mode"}
|
||||
Toggle { colorScheme: root.colorScheme; checked: user ? user.splitMode : false; onClicked: {user.splitMode = !user.splitMode}}
|
||||
Button { colorScheme: root.colorScheme; text: "Toggle Finished"; onClicked: {user.toggleSplitModeFinished()}}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
colorScheme: root.colorScheme
|
||||
text: user && user.addresses ? user.addresses.join("\n") : "user@protonmail.com"
|
||||
Layout.fillWidth: true
|
||||
onEditingFinished: {
|
||||
user.addresses = text.split("\n")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
|
|
|
@ -33,8 +33,10 @@ import Notifications 1.0
|
|||
Window {
|
||||
id: root
|
||||
|
||||
width: 640
|
||||
height: 480
|
||||
x: 10
|
||||
y: 10
|
||||
width: 800
|
||||
height: 600
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.darkStyle
|
||||
|
||||
|
@ -103,12 +105,21 @@ Window {
|
|||
QtObject {
|
||||
property string username: ""
|
||||
property bool loggedIn: false
|
||||
property bool splitMode: false
|
||||
|
||||
property bool setupGuideSeen: true
|
||||
|
||||
property string captionText: "50.3 MB / 20 GB"
|
||||
property var usedBytes: 5350*1024*1024
|
||||
property var totalBytes: 20*1024*1024*1024
|
||||
property string avatarText: "jd"
|
||||
|
||||
property string password: "SMj975NnEYYsqu55GGmlpv"
|
||||
property var addresses: [
|
||||
"janedoe@protonmail.com",
|
||||
"jane@pm.me",
|
||||
"jdoe@pm.me"
|
||||
]
|
||||
|
||||
signal loginUsernamePasswordError()
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError()
|
||||
|
@ -130,6 +141,30 @@ Window {
|
|||
root.log("<- User (" + username + "): " + msg)
|
||||
}
|
||||
|
||||
function toggleSplitMode(makeActive) {
|
||||
userSignal("toggle split mode "+makeActive)
|
||||
}
|
||||
signal toggleSplitModeFinished()
|
||||
|
||||
function configureAppleMail(address){
|
||||
userSignal("confugure apple mail "+address)
|
||||
}
|
||||
|
||||
function logout(){
|
||||
userSignal("logout")
|
||||
loggedIn = false
|
||||
}
|
||||
function remove(){
|
||||
console.log("remove this", users.count)
|
||||
for (var i=0; i<users.count; i++) {
|
||||
if (users.get(i) === this) {
|
||||
users.remove(i,1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
userSignal("loginUsernamePasswordError")
|
||||
}
|
||||
|
@ -193,6 +228,17 @@ Window {
|
|||
newLoginUser.login2PasswordRequested.connect(root.login2PasswordRequested)
|
||||
newLoginUser.login2PasswordError.connect(root.login2PasswordError)
|
||||
newLoginUser.login2PasswordErrorAbort.connect(root.login2PasswordErrorAbort)
|
||||
|
||||
|
||||
// add one user on start
|
||||
var haveUserOnStart = false
|
||||
if (haveUserOnStart) {
|
||||
var newUserObject = root.userComponent.createObject(root)
|
||||
newUserObject.username = "LerooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooyJenkins@protonmail.com"
|
||||
newUserObject.loggedIn = true
|
||||
newUserObject.setupGuideSeen = true
|
||||
root.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -216,6 +262,10 @@ Window {
|
|||
TabButton {
|
||||
text: "Log"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Settings signals"
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
|
@ -284,6 +334,7 @@ Window {
|
|||
enabled: bridge === undefined || bridge === null
|
||||
onClicked: {
|
||||
bridge = bridgeComponent.createObject()
|
||||
if (true) bridge._mainWindow.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,9 +419,8 @@ Window {
|
|||
spacing: 5
|
||||
|
||||
Switch {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Internet connection"
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
onCheckedChanged: {
|
||||
checked ? root.internetOn() : root.internetOff()
|
||||
|
@ -378,115 +428,124 @@ Window {
|
|||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Update manual ready"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualReady("3.14.1592")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Update manual done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualRestartNeeded()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Update manual error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualError()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Update force"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForce("3.14.1592")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Update force error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForceError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Update silent done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateSilentRestartNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update silent error"
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Update solent error"
|
||||
onClicked: {
|
||||
root.updateSilentError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update is latest version"
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Bug report send OK"
|
||||
onClicked: {
|
||||
root.updateIsLatestVersion()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Bug report send OK"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Bug report send error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Cache anavailable"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheAnavailable()
|
||||
root.cacheUnavailable()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
Button {
|
||||
text: "Cache can't move"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheCantMove()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache location change success"
|
||||
onClicked: {
|
||||
root.cacheLocationChangeSuccess()
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Disk full"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.diskFull()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
colorScheme: root.colorScheme
|
||||
id: logTextArea
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
@ -496,20 +555,90 @@ Window {
|
|||
textFormat: TextEdit.RichText
|
||||
//readOnly: true
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: settingsTab
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Automatic updates:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutomaticUpdateOn; onClicked: root.isAutomaticUpdateOn = !root.isAutomaticUpdateOn}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Autostart:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutostartOn; onClicked: root.isAutostartOn = !root.isAutostartOn}
|
||||
Button {colorScheme: root.colorScheme; text: "Toggle finished"; onClicked: root.toggleAutostartFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Beta:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isBetaEnabled; onClicked: root.isBetaEnabled = !root.isBetaEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "DoH:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDoHEnabled; onClicked: root.isDoHEnabled = !root.isDoHEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Ports:"}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "IMAP"
|
||||
text: root.portIMAP
|
||||
onEditingFinished: root.portIMAP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "SMTP"
|
||||
text: root.portSMTP
|
||||
onEditingFinished: root.portSMTP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished"; onClicked: root.changePortFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "SMTP using SSL:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.useSSLforSMTP; onClicked: root.useSSLforSMTP = !root.useSSLforSMTP}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Local cache:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDiskCacheEnabled; onClicked: root.isDiskCacheEnabled = !root.isDiskCacheEnabled}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "Path"
|
||||
text: root.diskCachePath
|
||||
implicitWidth: 160
|
||||
onEditingFinished: root.diskCachePath = this.text
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished:"; onClicked: root.changeLocalCacheFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Reset:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.resetFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Check update:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.checkUpdatesFinished()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Bridge bridge
|
||||
|
||||
property string goos: "linux"
|
||||
|
||||
property bool dockIconVisible: false
|
||||
|
||||
// this signals are used only when trying to login with new user (i.e. not in users model)
|
||||
signal loginUsernamePasswordError()
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError()
|
||||
signal loginUsernamePasswordError(string errorMsg)
|
||||
signal loginFreeUserError(string errorMsg)
|
||||
signal loginConnectionError(string errorMsg)
|
||||
signal login2FARequested()
|
||||
signal login2FAError()
|
||||
signal login2FAErrorAbort()
|
||||
signal login2FAError(string errorMsg)
|
||||
signal login2FAErrorAbort(string errorMsg)
|
||||
signal login2PasswordRequested()
|
||||
signal login2PasswordError()
|
||||
signal login2PasswordErrorAbort()
|
||||
signal login2PasswordError(string errorMsg)
|
||||
signal login2PasswordErrorAbort(string errorMsg)
|
||||
signal loginFinished()
|
||||
|
||||
signal internetOff()
|
||||
signal internetOn()
|
||||
|
@ -521,14 +650,140 @@ Window {
|
|||
signal updateForceError()
|
||||
signal updateSilentRestartNeeded()
|
||||
signal updateSilentError()
|
||||
signal updateIsLatestVersion()
|
||||
function checkUpdates(){
|
||||
console.log("check updates")
|
||||
}
|
||||
signal checkUpdatesFinished()
|
||||
|
||||
|
||||
property bool isDiskCacheEnabled: true
|
||||
property string diskCachePath: "/home/bridge"
|
||||
signal cacheUnavailable()
|
||||
signal cacheCantMove()
|
||||
signal cacheLocationChangeSuccess()
|
||||
signal diskFull()
|
||||
function changeLocalCache(enableDiskCache, diskCachePath) {
|
||||
console.debug("-> disk cache", enableDiskCache, diskCachePath)
|
||||
}
|
||||
signal changeLocalCacheFinished()
|
||||
|
||||
|
||||
// Settings
|
||||
property bool isAutomaticUpdateOn : true
|
||||
function toggleAutomaticUpdate(makeItActive) {
|
||||
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
root.isAutomaticUpdateOn = makeItActive
|
||||
}
|
||||
|
||||
property bool isAutostartOn : true // Example of settings with loading state
|
||||
function toggleAutostart(makeItActive) {
|
||||
console.debug("-> autostart", makeItActive, root.isAutomaticUpdateOn)
|
||||
}
|
||||
signal toggleAutostartFinished()
|
||||
|
||||
property bool isBetaEnabled : false
|
||||
function toggleBeta(makeItActive){
|
||||
console.debug("-> beta", makeItActive, root.isBetaEnabled)
|
||||
root.isBetaEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool isDoHEnabled : true
|
||||
function toggleDoH(makeItActive){
|
||||
console.debug("-> DoH", makeItActive, root.isDoHEnabled)
|
||||
root.isDoHEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool useSSLforSMTP: false
|
||||
function toggleUseSSLforSMTP(makeItActive){
|
||||
console.debug("-> SMTP SSL", makeItActive, root.useSSLforSMTP)
|
||||
}
|
||||
signal toggleUseSSLFinished()
|
||||
|
||||
property string hostname: "127.0.0.1"
|
||||
property int portIMAP: 1143
|
||||
property int portSMTP: 1025
|
||||
function changePorts(imapPort, smtpPort){
|
||||
console.debug("-> ports", imapPort, smtpPort)
|
||||
}
|
||||
function isPortFree(port){
|
||||
if (port == portIMAP) return false
|
||||
if (port == portSMTP) return false
|
||||
if (port == 12345) return false
|
||||
return true
|
||||
}
|
||||
signal changePortFinished()
|
||||
signal portIssueIMAP()
|
||||
signal portIssueSMTP()
|
||||
|
||||
function triggerReset() {
|
||||
console.debug("-> trigger reset")
|
||||
}
|
||||
signal resetFinished()
|
||||
|
||||
property string logsPath: "/home/cuto" // StandardPaths.locate(StandardPaths.DesktopLocation)
|
||||
property string version: "v2.0.X"
|
||||
property string licensePath: "/home/cuto" // StandardPaths.locate(StandardPaths.DesktopLocation)
|
||||
property string releaseNotesLink: "https://protonmail.com/download/bridge/early_releases.html"
|
||||
|
||||
property string currentEmailClient: "" // "Apple Mail 14.0"
|
||||
function updateCurrentMailClient(){
|
||||
currentEmailClient = "Apple Mail 14.0"
|
||||
}
|
||||
|
||||
function reportBug(description,address,emailClient,includeLogs){
|
||||
console.log("report bug")
|
||||
console.log(" description",description)
|
||||
console.log(" address",address)
|
||||
console.log(" emailClient",emailClient)
|
||||
console.log(" includeLogs",includeLogs)
|
||||
}
|
||||
signal reportBugFinished()
|
||||
signal bugReportSendSuccess()
|
||||
signal bugReportSendError()
|
||||
|
||||
signal cacheAnavailable()
|
||||
signal cacheCantMove()
|
||||
property var availableKeychain: ["gnome-keyring", "pass"]
|
||||
property string selectedKeychain
|
||||
function selectKeychain(wantedKeychain){
|
||||
selectedKeychain = wantedKeychain
|
||||
}
|
||||
signal hasNoKeychain()
|
||||
|
||||
signal noActiveKeyForRecipient(string email)
|
||||
signal showMainWindow()
|
||||
|
||||
signal addressChanged(string address)
|
||||
signal addressChangedLogout(string address)
|
||||
signal userDisconnected(string username)
|
||||
signal apiCertIssue()
|
||||
|
||||
|
||||
|
||||
function login(username, password) {
|
||||
root.log("-> login(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.username = username
|
||||
loginUser.isLoginRequested = true
|
||||
}
|
||||
|
||||
function login2FA(username, code) {
|
||||
root.log("-> login2FA(" + username + ", " + code + ")")
|
||||
|
||||
loginUser.isLogin2FAProvided = true
|
||||
}
|
||||
|
||||
function login2Password(username, password) {
|
||||
root.log("-> login2FA(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.isLogin2PasswordProvided = true
|
||||
}
|
||||
|
||||
function loginAbort(username) {
|
||||
root.log("-> loginAbort(" + username + ")")
|
||||
|
||||
loginUser.resetLoginRequests()
|
||||
}
|
||||
|
||||
signal diskFull()
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
console.debug("<- loginUsernamePasswordError")
|
||||
|
@ -557,6 +812,9 @@ Window {
|
|||
onLogin2PasswordErrorAbort: {
|
||||
console.debug("<- login2PasswordErrorAbort")
|
||||
}
|
||||
onLoginFinished: {
|
||||
console.debug("<- loginFinished")
|
||||
}
|
||||
|
||||
onInternetOff: {
|
||||
console.debug("<- internetOff")
|
||||
|
@ -571,30 +829,6 @@ Window {
|
|||
Bridge {
|
||||
backend: root
|
||||
|
||||
onLogin: {
|
||||
root.log("-> login(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.username = username
|
||||
loginUser.isLoginRequested = true
|
||||
}
|
||||
|
||||
onLogin2FA: {
|
||||
root.log("-> login2FA(" + username + ", " + code + ")")
|
||||
|
||||
loginUser.isLogin2FAProvided = true
|
||||
}
|
||||
|
||||
onLogin2Password: {
|
||||
root.log("-> login2FA(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.isLogin2PasswordProvided = true
|
||||
}
|
||||
|
||||
onLoginAbort: {
|
||||
root.log("-> loginAbort(" + username + ")")
|
||||
|
||||
loginUser.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property var selectedAddress
|
||||
|
||||
Label {
|
||||
text: qsTr("Report a problem")
|
||||
colorScheme: root.colorScheme
|
||||
type: Label.Heading
|
||||
}
|
||||
|
||||
|
||||
TextArea {
|
||||
id: description
|
||||
property int _minChars: 150
|
||||
property bool _inputOK: description.text.length>=description._minChars
|
||||
|
||||
label: qsTr("Description")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 100
|
||||
hint: description.text.length + "/800"
|
||||
placeholderText: qsTr("Tell us what went wrong or isn't working (min. 150 characters).")
|
||||
onEditingFinished: {
|
||||
if (!description._inputOK) {
|
||||
description.error = true
|
||||
description.assistiveText = qsTr("Enter a problem description (min. 150 characters)")
|
||||
} else {
|
||||
description.error = false
|
||||
description.assistiveText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TextField {
|
||||
id: address
|
||||
property bool _inputOK: root.isValidEmail(address.text)
|
||||
|
||||
label: qsTr("Your contact email")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. jane.doe@protonmail.com")
|
||||
|
||||
onEditingFinished: {
|
||||
if (!address._inputOK) {
|
||||
address.error = true
|
||||
address.assistiveText = qsTr("Enter valid email address")
|
||||
} else {
|
||||
address.assistiveText = ""
|
||||
address.error = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: emailClient
|
||||
property bool _inputOK: emailClient.text.length > 0
|
||||
|
||||
label: qsTr("Your email client (including version)")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. Apple Mail 14.0")
|
||||
onEditingFinished: {
|
||||
if (!emailClient._inputOK) {
|
||||
emailClient.assistiveText = qsTr("Enter an email client name and version")
|
||||
emailClient.error = true
|
||||
} else {
|
||||
emailClient.assistiveText = ""
|
||||
emailClient.error = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: includeLogs
|
||||
text: qsTr("Include my recent logs")
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
}
|
||||
Button {
|
||||
Layout.leftMargin: 12
|
||||
text: qsTr("View logs")
|
||||
secondary: true
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: Qt.openUrlExternally("file://"+root.backend.logsPath)
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: {
|
||||
var address = "bridge@protonmail.com"
|
||||
var mailTo = `<a href="mailto://${address}">${address}</a>`
|
||||
return qsTr("These reports are not end-to-end encrypted. In case of sensitive information, contact us at %1.").arg(mailTo)
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
type: Label.Caption
|
||||
color: root.colorScheme.text_weak
|
||||
}
|
||||
|
||||
Button {
|
||||
id: sendButton
|
||||
text: qsTr("Send")
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: root.submit()
|
||||
enabled: description._inputOK && address._inputOK && emailClient._inputOK
|
||||
|
||||
Connections {target: root.backend; onReportBugFinished: sendButton.loading = false }
|
||||
}
|
||||
|
||||
function setDefaultValue() {
|
||||
description.text = ""
|
||||
address.text = root.selectedAddress
|
||||
emailClient.text = root.backend.currentEmailClient
|
||||
includeLogs.checked = true
|
||||
}
|
||||
|
||||
function isValidEmail(text){
|
||||
var reEmail = /\w+@\w+\.\w+/
|
||||
return reEmail.test(text)
|
||||
}
|
||||
|
||||
function submit() {
|
||||
sendButton.loading = true
|
||||
root.backend.reportBug(
|
||||
description.text,
|
||||
address.text,
|
||||
emailClient.text,
|
||||
includeLogs.checked
|
||||
)
|
||||
}
|
||||
|
||||
Component.onCompleted: root.setDefaultValue()
|
||||
|
||||
|
||||
onBack: {
|
||||
root.setDefaultValue()
|
||||
root.parent.showHelpView()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property string title
|
||||
property string hostname
|
||||
property string port
|
||||
property string username
|
||||
property string password
|
||||
property string security
|
||||
|
||||
implicitWidth: 304
|
||||
implicitHeight: content.height + 2*root._margin
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
radius: 9
|
||||
|
||||
property int _margin: 24
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
width: root.width - 2*root._margin
|
||||
anchors{
|
||||
top: root.top
|
||||
left: root.left
|
||||
leftMargin : root._margin
|
||||
rightMargin : root._margin
|
||||
topMargin : root._margin
|
||||
bottomMargin : root._margin
|
||||
}
|
||||
|
||||
spacing: 12
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.title
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Item{}
|
||||
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Hostname") ; value: root.hostname }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Port") ; value: root.port }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Username") ; value: root.username }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Password") ; value: root.password }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Security") ; value: root.security }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var colorScheme
|
||||
property string label
|
||||
property string value
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.label
|
||||
type: Label.Body
|
||||
}
|
||||
TextEdit {
|
||||
id: valueText
|
||||
text: root.value
|
||||
color: root.colorScheme.text_weak
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
selectionColor: root.colorScheme.text_weak
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
source: "icons/ic-copy.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked : {
|
||||
valueText.select(0, valueText.length)
|
||||
valueText.copy()
|
||||
valueText.deselect()
|
||||
}
|
||||
onPressed: parent.scale = 0.90
|
||||
onReleased: parent.scale = 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_norm
|
||||
}
|
||||
}
|
|
@ -26,12 +26,26 @@ Item {
|
|||
property ColorScheme colorScheme
|
||||
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
signal login(string username, string password)
|
||||
signal login2FA(string username, string code)
|
||||
signal login2Password(string username, string password)
|
||||
signal loginAbort(string username)
|
||||
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
property var noUser: QtObject {
|
||||
property var avatarText: ""
|
||||
property var username: ""
|
||||
property var password: ""
|
||||
property var usedBytes: 1
|
||||
property var totalBytes: 1
|
||||
property var loggedIn: false
|
||||
property var splitMode: false
|
||||
property var addresses: []
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
@ -91,6 +105,8 @@ Item {
|
|||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-question-circle.svg"
|
||||
|
||||
onClicked: rightContent.showHelpView()
|
||||
}
|
||||
|
||||
Button {
|
||||
|
@ -109,10 +125,14 @@ Item {
|
|||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-cog-wheel.svg"
|
||||
|
||||
onClicked: rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Item {implicitHeight:10}
|
||||
|
||||
// Separator line
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 1
|
||||
|
@ -122,14 +142,20 @@ Item {
|
|||
|
||||
ListView {
|
||||
id: accounts
|
||||
|
||||
property var _topBottomMargins: 24
|
||||
property var _leftRightMargins: 16
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 24
|
||||
Layout.leftMargin: accounts._leftRightMargins
|
||||
Layout.rightMargin: accounts._leftRightMargins
|
||||
Layout.topMargin: accounts._topBottomMargins
|
||||
Layout.bottomMargin: accounts._topBottomMargins
|
||||
|
||||
spacing: 12
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
header: Rectangle {
|
||||
height: headerLabel.height+16
|
||||
|
@ -142,11 +168,28 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
highlight: Rectangle {
|
||||
color: leftBar.colorScheme.interaction_default_active
|
||||
radius: 4
|
||||
}
|
||||
|
||||
model: root.backend.users
|
||||
delegate: AccountDelegate{
|
||||
width: leftBar.width - 2*accounts._leftRightMargins
|
||||
|
||||
id: accountDelegate
|
||||
colorScheme: leftBar.colorScheme
|
||||
user: modelData
|
||||
user: root.backend.users.get(index)
|
||||
onClicked: {
|
||||
var user = root.backend.users.get(index)
|
||||
accounts.currentIndex = index
|
||||
if (user.loggedIn) {
|
||||
rightContent.showAccount()
|
||||
} else {
|
||||
signIn.username = user.username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,15 +224,16 @@ Item {
|
|||
|
||||
icon.source: "./icons/ic-plus.svg"
|
||||
|
||||
onClicked: root.showSignIn()
|
||||
onClicked: {
|
||||
signIn.username = ""
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rightPlane
|
||||
|
||||
Rectangle { // right content background
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
|
@ -199,14 +243,44 @@ Item {
|
|||
id: rightContent
|
||||
anchors.fill: parent
|
||||
|
||||
AccountView {
|
||||
AccountView { // 0
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
user: {
|
||||
if (accounts.currentIndex < 0) return root.noUser
|
||||
if (root.backend.users.count == 0) return root.noUser
|
||||
return root.backend.users.get(accounts.currentIndex)
|
||||
}
|
||||
onShowSignIn: {
|
||||
signIn.username = this.user.username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
onShowSetupGuide: {
|
||||
root.showSetupGuide(user,address)
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
GridLayout { // 1
|
||||
columns: 2
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
Layout.leftMargin: 18
|
||||
Layout.topMargin: 10
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: rightContent.showAccount()
|
||||
icon.source: "icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
horizontalPadding: 8
|
||||
}
|
||||
|
||||
SignIn {
|
||||
id: signIn
|
||||
Layout.topMargin: 68
|
||||
Layout.leftMargin: 80
|
||||
Layout.leftMargin: 80 - backButton.width - 18
|
||||
Layout.rightMargin: 80
|
||||
Layout.bottomMargin: 68
|
||||
Layout.preferredWidth: 320
|
||||
|
@ -214,21 +288,70 @@ Item {
|
|||
Layout.fillHeight: true
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
user: (root.backend.users.count === 1 && root.backend.users.get(0).loggedIn === false) ? root.backend.users.get(0) : undefined
|
||||
backend: root.backend
|
||||
|
||||
onLogin : { root.login ( username , password ) }
|
||||
onLogin2FA : { root.login2FA ( username , code ) }
|
||||
onLogin2Password : { root.login2Password ( username , password ) }
|
||||
onLoginAbort : { root.loginAbort ( username ) }
|
||||
onLogin : { root.backend.login ( username , password ) }
|
||||
onLogin2FA : { root.backend.login2FA ( username , code ) }
|
||||
onLogin2Password : { root.backend.login2Password ( username , password ) }
|
||||
onLoginAbort : { root.backend.loginAbort ( username ) }
|
||||
}
|
||||
}
|
||||
|
||||
GeneralSettings { // 2
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
}
|
||||
|
||||
PortSettings { // 3
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
SMTPSettings { // 4
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
LocalCacheSettings { // 5
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
}
|
||||
|
||||
HelpView { // 6
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
BugReportView { // 7
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
selectedAddress: {
|
||||
if (accounts.currentIndex < 0) return ""
|
||||
if (root.backend.users.count == 0) return ""
|
||||
return root.backend.users.get(accounts.currentIndex).addresses[0]
|
||||
}
|
||||
}
|
||||
|
||||
function showAccount () { rightContent.currentIndex = 0 }
|
||||
function showSignIn () { rightContent.currentIndex = 1 }
|
||||
function showGeneralSettings () { rightContent.currentIndex = 2 }
|
||||
function showPortSettings () { rightContent.currentIndex = 3 }
|
||||
function showSMTPSettings () { rightContent.currentIndex = 4 }
|
||||
function showLocalCacheSettings () { rightContent.currentIndex = 5 }
|
||||
function showHelpView () { rightContent.currentIndex = 6 }
|
||||
function showBugReport () { rightContent.currentIndex = 7 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function showSignIn() {
|
||||
rightContent.currentIndex = 1
|
||||
function showLocalCacheSettings(){rightContent.showLocalCacheSettings() }
|
||||
function showSettings(){rightContent.showGeneralSettings() }
|
||||
function showHelp(){rightContent.showHelpView() }
|
||||
function showSignIn(username){
|
||||
signIn.username = username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property bool _isAdvancedShown: false
|
||||
property var notifications
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Settings")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: autoUpdate
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Automatic updates")
|
||||
description: qsTr("Bridge will automatically update in the background.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isAutomaticUpdateOn
|
||||
onClicked: root.backend.toggleAutomaticUpdate(!autoUpdate.checked)
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: autostart
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Automatically start Bridge")
|
||||
description: qsTr("The app will autostart everytime you reset your device.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isAutostartOn
|
||||
onClicked: {
|
||||
autostart.loading = true
|
||||
root.backend.toggleAutostart(!autoUpdate.checked)
|
||||
}
|
||||
Connections{
|
||||
target: root.backend
|
||||
onToggleAutostartFinished: {
|
||||
autostart.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: beta
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Enable Beta access")
|
||||
description: qsTr("Be the first one to see new features.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isBetaEnabled
|
||||
onClicked: {
|
||||
if (!beta.checked) {
|
||||
root.notifications.askEnableBeta()
|
||||
} else {
|
||||
root.notifications.askDisableBeta()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ColorImage {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
source: root._isAdvancedShown ? "icons/ic-chevron-up.svg" : "icons/ic-chevron-down.svg"
|
||||
color: root.colorScheme.interaction_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root._isAdvancedShown = !root._isAdvancedShown
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: advSettLabel
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Advanced settings")
|
||||
color: root.colorScheme.interaction_norm
|
||||
type: Label.Body
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root._isAdvancedShown = !root._isAdvancedShown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: doh
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Alternative routing")
|
||||
description: qsTr("If Proton’s servers are blocked in your location, alternative network routing will be used to reach Proton.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.backend.isDoHEnabled
|
||||
onClicked: root.backend.toggleDoH(!doh.checked)
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: ports
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Default ports")
|
||||
actionText: qsTr("Change")
|
||||
description: qsTr("Choose which ports are used by default.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showPortSettings()
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: smtp
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection mode")
|
||||
actionText: qsTr("Change")
|
||||
description: qsTr("Change the protocol Bridge and your client use to connect.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showSMTPSettings()
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: cache
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Local cache")
|
||||
actionText: qsTr("Configure")
|
||||
description: qsTr("Configure Bridge's local cache settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: root.parent.showLocalCacheSettings()
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: reset
|
||||
visible: root._isAdvancedShown
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Reset Bridge")
|
||||
actionText: qsTr("Reset")
|
||||
description: qsTr("Remove all accounts, clear cached data, and restore the original settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
root.notifications.askResetBridge()
|
||||
}
|
||||
}
|
||||
|
||||
onBack: root.parent.showAccount()
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Help")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: setupPage
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Installation and setup")
|
||||
actionText: qsTr("Go to help topics")
|
||||
actionIcon: "./icons/ic-external-link.svg"
|
||||
description: qsTr("Get help setting up your client with our instructions and FAQs.")
|
||||
type: SettingsItem.PrimaryButton
|
||||
onClicked: {Qt.openUrlExternally("https://protonmail.com/bridge/install")}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: checkUpdates
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Updates")
|
||||
actionText: qsTr("Check now")
|
||||
description: qsTr("Check that you're using the latest version of Bridge. To stay up to date, enable auto-updates in settings.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
checkUpdates.loading = true
|
||||
root.backend.checkUpdates()
|
||||
}
|
||||
|
||||
Connections {target: root.backend; onCheckUpdatesFinished: checkUpdates.loading = false}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: logs
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Logs")
|
||||
actionText: qsTr("View logs")
|
||||
description: qsTr("Open and review logs to troubleshoot.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {Qt.openUrlExternally(root.backend.logsPath)}
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: reportBug
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Report a problem")
|
||||
actionText: qsTr("Report a problem")
|
||||
description: qsTr("Something not working as expected? Let us know.")
|
||||
type: SettingsItem.Button
|
||||
onClicked: {
|
||||
root.backend.updateCurrentMailClient()
|
||||
root.parent.showBugReport()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
colorScheme: root.colorScheme
|
||||
type: Label.Caption
|
||||
color: root.colorScheme.text_weak
|
||||
textFormat: Text.RichText
|
||||
linkColor: root.colorScheme.interaction_norm_active
|
||||
|
||||
text: {
|
||||
var version = root.backend.version
|
||||
var license = qsTr("License")
|
||||
var licensePath = root.backend.licensePath
|
||||
var release= qsTr("Release notes")
|
||||
var releaseNotesLink = root.backend.releaseNotesLink
|
||||
return `<p style="text-align:center;">Proton Mail Bridge v${version}<br>
|
||||
© 2021 Proton Technologies AG<br>
|
||||
<a style="color: ${linkColor};" href="${licensePath}">${license}</a>
|
||||
<a style="color: ${linkColor};" href="${releaseNotesLink}">${release}</a>
|
||||
</p>`
|
||||
}
|
||||
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.parent.showAccount()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
import QtQuick.Dialogs 1.1
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property var notifications
|
||||
property bool _diskCacheEnabled: true
|
||||
property string _diskCachePath: "/home"
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Local cache")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Bridge caches your encrypted messages localy to optimise the communication with the local client. Disabling this feature might have a nevative impact on performance.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: this.parent.Layout.maximumWidth
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Enable local cache")
|
||||
description: "When enabled messages are stored on disk." // TODO: wrong text in wireframe
|
||||
type: SettingsItem.Toggle
|
||||
checked: root._diskCacheEnabled
|
||||
onClicked: root._diskCacheEnabled = !root._diskCacheEnabled
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Current cache location")
|
||||
actionText: qsTr("Change location")
|
||||
description: root._diskCachePath
|
||||
type: SettingsItem.Button
|
||||
enabled: root._diskCacheEnabled
|
||||
onClicked: {
|
||||
pathDialog.open()
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: pathDialog
|
||||
title: qsTr("Select cache location")
|
||||
folder: shortcuts.home
|
||||
onAccepted: root.sanitizePath(pathDialog.fileUrl.toString())
|
||||
selectFolder: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
enabled: (
|
||||
root.backend.diskCachePath != root._diskCachePath ||
|
||||
root.backend.isDiskCacheEnabled != root._diskCacheEnabled
|
||||
)
|
||||
onClicked: {
|
||||
root.submit()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onChangeLocalCacheFinished: {
|
||||
submitButton.loading = false
|
||||
root.setDefaultValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.parent.showGeneralSettings()
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function submit(){
|
||||
console.log("submit")
|
||||
if (!root._diskCacheEnabled && root.backend.isDiskCacheEnabled) {
|
||||
root.notifications.askDisableLocalCache()
|
||||
return
|
||||
}
|
||||
|
||||
if (root._diskCacheEnabled && !root.backend.isDiskCacheEnabled) {
|
||||
root.notifications.askEnableLocalCache(root._diskCachePath)
|
||||
return
|
||||
}
|
||||
|
||||
// Not asking, only changing path
|
||||
submitButton.loading = true
|
||||
root.backend.changeLocalCache(root.backend.isDiskCacheEnabled, root._diskCachePath)
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
root._diskCacheEnabled = root.backend.isDiskCacheEnabled
|
||||
root._diskCachePath = root.backend.diskCachePath
|
||||
}
|
||||
|
||||
function sanitizePath(path) {
|
||||
var pattern = "file://"
|
||||
if (root.backend.goos=="windows") pattern+="/"
|
||||
root._diskCachePath = path.replace(pattern, "")
|
||||
}
|
||||
|
||||
Component.onCompleted: root.setDefaultValues()
|
||||
}
|
|
@ -62,7 +62,7 @@ ApplicationWindow {
|
|||
return
|
||||
}
|
||||
|
||||
root.showSetup(user)
|
||||
root.showSetup(user,user.addresses[0])
|
||||
}
|
||||
|
||||
onRowsAboutToBeRemoved: {
|
||||
|
@ -78,15 +78,6 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
|
||||
function showSetup(user) {
|
||||
setupGuide.user = user
|
||||
if (setupGuide.user) {
|
||||
contentLayout._showSetup = true
|
||||
} else {
|
||||
contentLayout._showSetup = false
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: contentLayout
|
||||
|
||||
|
@ -111,12 +102,18 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
ContentWrapper {
|
||||
id: contentWrapper
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
onShowSetupGuide: {
|
||||
root.showSetup(user,address)
|
||||
}
|
||||
|
||||
onLogin: {
|
||||
root.login(username, password)
|
||||
}
|
||||
|
@ -161,7 +158,7 @@ ApplicationWindow {
|
|||
Layout.fillWidth: true
|
||||
|
||||
onDismissed: {
|
||||
root.showSetup(null)
|
||||
root.showSetup(null,"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,5 +166,25 @@ ApplicationWindow {
|
|||
NotificationPopups {
|
||||
colorScheme: root.colorScheme
|
||||
notifications: root.notifications
|
||||
mainWindow: root
|
||||
}
|
||||
|
||||
function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() }
|
||||
function showSettings() { contentWrapper.showSettings() }
|
||||
function showHelp() { contentWrapper.showHelp() }
|
||||
|
||||
function showSignIn(username) {
|
||||
if (contentLayout.currentIndex == 1) return
|
||||
contentWrapper.showSignIn(username)
|
||||
}
|
||||
|
||||
function showSetup(user, address) {
|
||||
setupGuide.user = user
|
||||
setupGuide.address = address
|
||||
if (setupGuide.user) {
|
||||
contentLayout._showSetup = true
|
||||
} else {
|
||||
contentLayout._showSetup = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,13 +55,12 @@ Dialog {
|
|||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
// TODO: Add info icon?
|
||||
return ""
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Info:
|
||||
return "./icons/ic-info.svg"
|
||||
case Notification.NotificationType.Success:
|
||||
return "./icons/ic-success.svg"
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Danger:
|
||||
return "./icons/ic-alert.svg"
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +109,8 @@ Dialog {
|
|||
action: modelData
|
||||
|
||||
secondary: index > 0
|
||||
|
||||
loading: notification.loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ Item {
|
|||
|
||||
property ColorScheme colorScheme
|
||||
property var notifications
|
||||
property var mainWindow
|
||||
|
||||
property int notificationWhitelist: NotificationFilter.FilterConsts.All
|
||||
property int notificationBlacklist: NotificationFilter.FilterConsts.None
|
||||
|
@ -42,6 +43,7 @@ Item {
|
|||
Banner {
|
||||
colorScheme: root.colorScheme
|
||||
notification: bannerNotificationFilter.topmost
|
||||
mainWindow: root.mainWindow
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
|
@ -66,17 +68,17 @@ Item {
|
|||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.bugReportSendSuccess
|
||||
notification: root.notifications.disableBeta
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.bugReportSendError
|
||||
notification: root.notifications.enableBeta
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.cacheAnavailable
|
||||
notification: root.notifications.cacheUnavailable
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
|
@ -88,4 +90,24 @@ Item {
|
|||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.diskFull
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.enableSplitMode
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.disableLocalCache
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.enableLocalCache
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.resetBridge
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ QtObject {
|
|||
|
||||
property bool dismissed: false
|
||||
property bool active: false
|
||||
property bool loading: false
|
||||
readonly property var occurred: active ? new Date() : undefined
|
||||
|
||||
property var data
|
||||
|
|
|
@ -29,6 +29,13 @@ QtObject {
|
|||
property StatusWindow frontendStatus
|
||||
property SystemTrayIcon frontendTray
|
||||
|
||||
signal askDisableBeta()
|
||||
signal askEnableBeta()
|
||||
signal askEnableSplitMode(var user)
|
||||
signal askDisableLocalCache()
|
||||
signal askEnableLocalCache(var path)
|
||||
signal askResetBridge()
|
||||
|
||||
enum Group {
|
||||
Connection = 1,
|
||||
Update = 2,
|
||||
|
@ -48,12 +55,20 @@ QtObject {
|
|||
root.updateForceError,
|
||||
root.updateSilentRestartNeeded,
|
||||
root.updateSilentError,
|
||||
root.updateIsLatestVersion,
|
||||
root.disableBeta,
|
||||
root.enableBeta,
|
||||
root.bugReportSendSuccess,
|
||||
root.bugReportSendError,
|
||||
root.cacheAnavailable,
|
||||
root.cacheUnavailable,
|
||||
root.cacheCantMove,
|
||||
root.accountChanged,
|
||||
root.diskFull
|
||||
root.diskFull,
|
||||
root.cacheLocationChangeSuccess,
|
||||
root.enableSplitMode,
|
||||
root.disableLocalCache,
|
||||
root.enableLocalCache,
|
||||
root.resetBridge
|
||||
]
|
||||
|
||||
// Connection
|
||||
|
@ -93,10 +108,18 @@ QtObject {
|
|||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Update")
|
||||
text: qsTr("Install update")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: call update from backend
|
||||
root.backend.installUpdate()
|
||||
root.updateManualReady.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateManualReady.active = false
|
||||
}
|
||||
},
|
||||
|
@ -104,7 +127,6 @@ QtObject {
|
|||
text: qsTr("Remind me later")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: start timer here
|
||||
root.updateManualReady.active = false
|
||||
}
|
||||
}
|
||||
|
@ -128,14 +150,14 @@ QtObject {
|
|||
text: qsTr("Restart Bridge")
|
||||
|
||||
onTriggered: {
|
||||
// TODO
|
||||
root.backend.restart()
|
||||
root.updateManualRestartNeeded.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Notification updateManualError: Notification {
|
||||
text: qsTr("Bridge couldn’t update")
|
||||
text: qsTr("Bridge couldn’t update. Please update manually.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Update
|
||||
|
@ -147,19 +169,28 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
action: Action {
|
||||
text: qsTr("Update manually")
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
// TODO
|
||||
root.updateManualError.active = false
|
||||
onTriggered: {
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateManualError.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Remind me later")
|
||||
|
||||
onTriggered: {
|
||||
root.updateManualReady.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification updateForce: Notification {
|
||||
text: qsTr("Update to ProtonMail Bridge") + " " + (data ? data.version : "")
|
||||
description: qsTr("This version of Bridge is no longer supported, please update. Learn why. To update manually, go to: https:/protonmail.com/bridge/download")
|
||||
description: qsTr("This version of Bridge is no longer supported, please update.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
|
@ -175,18 +206,26 @@ QtObject {
|
|||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Update")
|
||||
text: qsTr("Install update")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: trigger update here
|
||||
root.backend.installUpdate()
|
||||
root.updateForce.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Quite Bridge")
|
||||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: quit Bridge here
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateForce.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Quit Bridge")
|
||||
|
||||
onTriggered: {
|
||||
root.backend.quit()
|
||||
root.updateForce.active = false
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +234,7 @@ QtObject {
|
|||
|
||||
property Notification updateForceError: Notification {
|
||||
text: qsTr("Bridge coudn’t update")
|
||||
description: qsTr("You must update manually. Go to: https:/protonmail.com/bridge/download")
|
||||
description: qsTr("You must update manually.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
|
@ -213,15 +252,15 @@ QtObject {
|
|||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: trigger update here
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateForceError.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Quite Bridge")
|
||||
text: qsTr("Quit Bridge")
|
||||
|
||||
onTriggered: {
|
||||
// TODO: quit Bridge here
|
||||
root.backend.quit()
|
||||
root.updateForce.active = false
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +284,7 @@ QtObject {
|
|||
text: qsTr("Restart Bridge")
|
||||
|
||||
onTriggered: {
|
||||
// TODO
|
||||
root.backend.restart()
|
||||
root.updateSilentRestartNeeded.active = false
|
||||
}
|
||||
}
|
||||
|
@ -268,18 +307,105 @@ QtObject {
|
|||
text: qsTr("Update manually")
|
||||
|
||||
onTriggered: {
|
||||
// TODO
|
||||
Qt.openUrlExternally(root.backend.getLandingPage())
|
||||
root.updateSilentError.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Notification updateIsLatestVersion: Notification {
|
||||
text: qsTr("Bridge is up to date")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Info
|
||||
group: Notifications.Group.Update
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onUpdateIsLatestVersion: {
|
||||
root.updateIsLatestVersion.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: Action {
|
||||
text: qsTr("Ok")
|
||||
|
||||
onTriggered: {
|
||||
root.updateIsLatestVersion.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Notification disableBeta: Notification {
|
||||
text: qsTr("Disable beta access?")
|
||||
description: qsTr("This resets Bridge to the current release and will restart the app. Your preferences, cached data, and email client configurations will be cleared. ")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskDisableBeta: {
|
||||
root.disableBeta.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Remind me later")
|
||||
|
||||
onTriggered: {
|
||||
root.disableBeta.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Disable and restart")
|
||||
onTriggered: {
|
||||
root.backend.toggleBeta(false)
|
||||
root.disableBeta.loading = true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification enableBeta: Notification {
|
||||
text: qsTr("Enable beta access?")
|
||||
description: qsTr("Bridge will update to the latest beta version according to your update preferences. Disabling beta access later on will reset Bridge and require you to reconfigure your client.")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Info
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskEnableBeta: {
|
||||
root.enableBeta.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Enable")
|
||||
onTriggered: {
|
||||
root.backend.toggleBeta(true)
|
||||
root.enableBeta.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onTriggered: {
|
||||
root.enableBeta.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
// Bug reports
|
||||
property Notification bugReportSendSuccess: Notification {
|
||||
text: qsTr("Bug report sent")
|
||||
description: qsTr("We’ve received your report, thank you! Our team will get back to you as soon as we can.")
|
||||
text: qsTr("Thank you for the report. We'll get back to you as soon as we can.")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Success
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
group: Notifications.Group.Configuration
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
@ -302,10 +428,10 @@ QtObject {
|
|||
}
|
||||
|
||||
property Notification bugReportSendError: Notification {
|
||||
text: qsTr("There was a problem")
|
||||
description: qsTr("There was a problem with sending your report. Please try again later or contact us directly at security@protonmail.com")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
text: qsTr("Report could not be sent. Try again or email us directly.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Configuration
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
@ -323,7 +449,7 @@ QtObject {
|
|||
}
|
||||
|
||||
// Cache
|
||||
property Notification cacheAnavailable: Notification {
|
||||
property Notification cacheUnavailable: Notification {
|
||||
text: qsTr("Cache location is unavailable")
|
||||
description: qsTr("Check the directory or change it in your settings.")
|
||||
type: Notification.NotificationType.Warning
|
||||
|
@ -331,8 +457,8 @@ QtObject {
|
|||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onCacheAnavailable: {
|
||||
root.cacheAnavailable.active = true
|
||||
onCacheUnavailable: {
|
||||
root.cacheUnavailable.active = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,13 +466,15 @@ QtObject {
|
|||
Action {
|
||||
text: qsTr("Quit Bridge")
|
||||
onTriggered: {
|
||||
root.cacheAnavailable.active = false
|
||||
root.backend.quit()
|
||||
root.cacheUnavailable.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Change location")
|
||||
onTriggered: {
|
||||
root.cacheAnavailable.active = false
|
||||
root.cacheUnavailable.active = false
|
||||
root.frontendMain.showLocalCacheSettings()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -376,6 +504,31 @@ QtObject {
|
|||
text: qsTr("Change location")
|
||||
onTriggered: {
|
||||
root.cacheCantMove.active = false
|
||||
root.frontendMain.showLocalCacheSettings()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification cacheLocationChangeSuccess: Notification {
|
||||
text: qsTr("Cache location successfully changed")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Success
|
||||
group: Notifications.Group.Configuration
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onCacheLocationChangeSuccess: {
|
||||
console.log("notify location changed succesfully")
|
||||
root.cacheLocationChangeSuccess.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Ok")
|
||||
onTriggered: {
|
||||
root.cacheLocationChangeSuccess.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -414,6 +567,7 @@ QtObject {
|
|||
Action {
|
||||
text: qsTr("Quit Bridge")
|
||||
onTriggered: {
|
||||
root.backend.quit()
|
||||
root.diskFull.active = false
|
||||
}
|
||||
},
|
||||
|
@ -421,6 +575,171 @@ QtObject {
|
|||
text: qsTr("Settings")
|
||||
onTriggered: {
|
||||
root.diskFull.active = false
|
||||
root.frontendMain.showLocalCacheSettings()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification enableSplitMode: Notification {
|
||||
text: qsTr("Enable split mode?")
|
||||
description: qsTr("Changing between split and combined address mode will require you to delete your accounts(s) from your email client and begin the setup process from scratch.")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
||||
property var user
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskEnableSplitMode: {
|
||||
root.enableSplitMode.user = user
|
||||
root.enableSplitMode.active = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: (root && root.enableSplitMode && root.enableSplitMode.user ) ? root.enableSplitMode.user : null
|
||||
onToggleSplitModeFinished: {
|
||||
root.enableSplitMode.active = false
|
||||
root.enableSplitMode.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
onTriggered: {
|
||||
root.enableSplitMode.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Enable split mode")
|
||||
onTriggered: {
|
||||
root.enableSplitMode.loading = true
|
||||
root.enableSplitMode.user.toggleSplitMode(true)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification disableLocalCache: Notification {
|
||||
text: qsTr("Disable local cache?")
|
||||
description: qsTr("This action will clear your local cache, including locally stored messages. Bridge will restart.")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskDisableLocalCache: {
|
||||
root.disableLocalCache.active = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onChangeLocalCacheFinished: {
|
||||
root.disableLocalCache.active = false
|
||||
root.disableLocalCache.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
onTriggered: {
|
||||
root.disableLocalCache.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Disable and restart")
|
||||
onTriggered: {
|
||||
root.disableLocalCache.loading = true
|
||||
root.backend.changeLocalCache(false, root.backend.diskCachePath)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification enableLocalCache: Notification {
|
||||
text: qsTr("Enable local cache?")
|
||||
description: qsTr("Bridge will restart.")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
||||
property var path
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskEnableLocalCache: {
|
||||
root.enableLocalCache.active = true
|
||||
root.enableLocalCache.path = path
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onChangeLocalCacheFinished: {
|
||||
root.enableLocalCache.active = false
|
||||
root.enableLocalCache.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Enable and restart")
|
||||
onTriggered: {
|
||||
root.enableLocalCache.loading = true
|
||||
root.backend.changeLocalCache(true, root.enableLocalCache.path)
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
onTriggered: {
|
||||
root.enableLocalCache.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification resetBridge: Notification {
|
||||
text: qsTr("Reset Bridge?")
|
||||
description: qsTr("This will clear your accounts, preferences, and cached data. You will need to reconfigure your email client. Bridge will automatically restart")
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
||||
property var user
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onAskResetBridge: {
|
||||
root.resetBridge.active = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onResetFinished: {
|
||||
root.resetBridge.active = false
|
||||
root.resetBridge.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Cancel")
|
||||
onTriggered: {
|
||||
root.resetBridge.active = false
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: qsTr("Reset and restart")
|
||||
onTriggered: {
|
||||
root.resetBridge.loading = true
|
||||
root.backend.triggerReset()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
property bool _valuesOK: !imapField.error && !smtpField.error
|
||||
property bool _valuesChanged: (
|
||||
imapField.text*1 != root.backend.portIMAP ||
|
||||
smtpField.text*1 != root.backend.portSMTP
|
||||
)
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Default ports")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Changes require reconfiguration of your email client. Bridge will automatically restart.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 16
|
||||
|
||||
TextField {
|
||||
id: imapField
|
||||
colorScheme: root.colorScheme
|
||||
label: qsTr("IMAP port")
|
||||
Layout.preferredWidth: 160
|
||||
onEditingFinished: root.validate(imapField)
|
||||
}
|
||||
TextField {
|
||||
id: smtpField
|
||||
colorScheme: root.colorScheme
|
||||
label: qsTr("SMTP port")
|
||||
Layout.preferredWidth: 160
|
||||
onEditingFinished: root.validate(smtpField)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
enabled: root._valuesOK && root._valuesChanged
|
||||
onClicked: {
|
||||
submitButton.loading = true
|
||||
root.submit()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onChangePortFinished: submitButton.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.parent.showGeneralSettings()
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function validate(field) {
|
||||
var num = field.text*1
|
||||
if (! (num > 1 && num < 65536) ) {
|
||||
field.error = true
|
||||
field.assistiveText = qsTr("Invalid port number.")
|
||||
return
|
||||
}
|
||||
|
||||
if (imapField.text == smtpField.text) {
|
||||
field.error = true
|
||||
field.assistiveText = qsTr("Port numbers must be different.")
|
||||
return
|
||||
}
|
||||
|
||||
field.error = false
|
||||
field.assistiveText = ""
|
||||
}
|
||||
|
||||
function isPortFree(field) {
|
||||
field.error = false
|
||||
field.assistiveText = ""
|
||||
|
||||
var num = field.text*1
|
||||
if (num == root.backend.portIMAP) return true
|
||||
if (num == root.backend.portSMTP) return true
|
||||
if (!root.backend.isPortFree(num)) {
|
||||
field.error = true
|
||||
field.assistiveText = qsTr("Port occupied.")
|
||||
submitButton.loading = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function submit(){
|
||||
submitButton.loading = true
|
||||
if (!isPortFree(imapField)) return
|
||||
if (!isPortFree(smtpField)) return
|
||||
root.backend.changePorts(imapField.text, smtpField.text)
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
imapField.text = backend.portIMAP
|
||||
smtpField.text = backend.portSMTP
|
||||
}
|
||||
|
||||
Component.onCompleted: root.setDefaultValues()
|
||||
}
|
|
@ -246,9 +246,25 @@ T.Button {
|
|||
}
|
||||
}
|
||||
|
||||
border.color: control.colorScheme.border_norm
|
||||
border.color: {
|
||||
return control.colorScheme.border_norm
|
||||
}
|
||||
border.width: secondary && !borderless ? 1 : 0
|
||||
|
||||
opacity: control.enabled || control.loading ? 1.0 : 0.5
|
||||
}
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!control.colorScheme) {
|
||||
console.trace()
|
||||
var next = root
|
||||
for (var i = 0; i<1000; i++) {
|
||||
console.log(i, next, "colorscheme", next.colorScheme)
|
||||
next = next.parent
|
||||
if (!next) break
|
||||
}
|
||||
console.error("ColorScheme not defined")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import QtQuick 2.8
|
|||
Rectangle {
|
||||
id: root
|
||||
|
||||
color: Style.transparent
|
||||
color: "transparent"
|
||||
|
||||
property color fillColor : Style.currentStyle.background_norm
|
||||
property color strokeColor : Style.currentStyle.background_strong
|
||||
|
|
|
@ -20,6 +20,7 @@ import QtQuick 2.12
|
|||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
import QtQuick.Templates 2.12 as T
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
@ -86,9 +87,10 @@ Item {
|
|||
property alias wrapMode: control.wrapMode
|
||||
|
||||
implicitWidth: background.width
|
||||
implicitHeight: control.implicitHeight +
|
||||
Math.max(label.implicitHeight + label.anchors.topMargin + label.anchors.bottomMargin, hint.implicitHeight + hint.anchors.topMargin + hint.anchors.bottomMargin) +
|
||||
assistiveText.implicitHeight
|
||||
implicitHeight: control.implicitHeight + Math.max(
|
||||
label.implicitHeight + label.anchors.topMargin + label.anchors.bottomMargin,
|
||||
hint.implicitHeight + hint.anchors.topMargin + hint.anchors.bottomMargin
|
||||
) + assistiveText.implicitHeight
|
||||
|
||||
property alias label: label.text
|
||||
property alias hint: hint.text
|
||||
|
@ -96,6 +98,8 @@ Item {
|
|||
|
||||
property bool error: false
|
||||
|
||||
signal editingFinished()
|
||||
|
||||
// Backgroud is moved away from within control as it will be clipped with scrollview
|
||||
Rectangle {
|
||||
id: background
|
||||
|
@ -200,12 +204,16 @@ Item {
|
|||
T.TextArea {
|
||||
id: control
|
||||
|
||||
implicitWidth: Math.max(contentWidth + leftPadding + rightPadding,
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
placeholder.implicitWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(contentHeight + topPadding + bottomPadding,
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
placeholder.implicitHeight + topPadding + bottomPadding)
|
||||
implicitWidth: Math.max(
|
||||
contentWidth + leftPadding + rightPadding,
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
placeholder.implicitWidth + leftPadding + rightPadding
|
||||
)
|
||||
implicitHeight: Math.max(
|
||||
contentHeight + topPadding + bottomPadding,
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
placeholder.implicitHeight + topPadding + bottomPadding
|
||||
)
|
||||
|
||||
padding: 8
|
||||
leftPadding: 12
|
||||
|
@ -216,6 +224,8 @@ Item {
|
|||
selectionColor: control.palette.highlight
|
||||
selectedTextColor: control.palette.highlightedText
|
||||
|
||||
onEditingFinished: root.editingFinished()
|
||||
|
||||
cursorDelegate: Rectangle {
|
||||
id: cursor
|
||||
width: 1
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
RowLayout{
|
||||
id: root
|
||||
property var colorScheme
|
||||
property bool checked
|
||||
property bool disabled
|
||||
property bool hovered
|
||||
property bool loading
|
||||
|
||||
signal clicked
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
implicitWidth: 40
|
||||
implicitHeight: 24
|
||||
|
||||
radius: 20
|
||||
color: {
|
||||
if (root.loading) return "transparent"
|
||||
if (root.disabled) return root.colorScheme.background_strong
|
||||
return root.colorScheme.background_norm
|
||||
}
|
||||
border {
|
||||
width: 1
|
||||
color: (root.disabled || root.loading) ? "transparent" : colorScheme.field_norm
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: indicator.verticalCenter
|
||||
anchors.left: indicator.left
|
||||
anchors.leftMargin: root.checked ? 16 : 0
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
color: {
|
||||
if (root.loading) return "transparent"
|
||||
if (root.disabled) return root.colorScheme.field_disabled
|
||||
|
||||
if (root.checked) {
|
||||
if (root.hovered) return root.colorScheme.interaction_norm_hover
|
||||
return root.colorScheme.interaction_norm
|
||||
} else {
|
||||
if (root.hovered) return root.colorScheme.field_hover
|
||||
return root.colorScheme.field_norm
|
||||
}
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
anchors.centerIn: parent
|
||||
source: "../icons/ic-check.svg"
|
||||
color: root.colorScheme.background_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
visible: root.checked
|
||||
}
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
id: loader
|
||||
anchors.centerIn: parent
|
||||
source: "../icons/Loader_16.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
visible: root.loading
|
||||
|
||||
RotationAnimation {
|
||||
target: loader
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
from: 0
|
||||
to: 360
|
||||
direction: RotationAnimation.Clockwise
|
||||
running: root.loading
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: indicator
|
||||
hoverEnabled: true
|
||||
onEntered: {root.hovered = true }
|
||||
onExited: {root.hovered = false }
|
||||
onClicked: { root.clicked();}
|
||||
onPressed: {root.hovered = true }
|
||||
onReleased: { root.hovered = containsMouse }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,3 +34,4 @@ RoundedRectangle 4.0 RoundedRectangle.qml
|
|||
Switch 4.0 Switch.qml
|
||||
TextArea 4.0 TextArea.qml
|
||||
TextField 4.0 TextField.qml
|
||||
Toggle 4.0 Toggle.qml
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection mode")
|
||||
type: Label.Heading
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Changes require reconfiguration of email client. Bridge will automatically restart.")
|
||||
type: Label.Body
|
||||
color: root.colorScheme.text_weak
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: this.parent.Layout.maximumWidth
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
ButtonGroup{ id: protocolSelection }
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("SMTP connection security")
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: sslButton
|
||||
colorScheme: root.colorScheme
|
||||
ButtonGroup.group: protocolSelection
|
||||
text: qsTr("SSL")
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: starttlsButton
|
||||
colorScheme: root.colorScheme
|
||||
ButtonGroup.group: protocolSelection
|
||||
text: qsTr("STARTLS")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
id: submitButton
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Save and restart")
|
||||
onClicked: {
|
||||
submitButton.loading = true
|
||||
root.submit()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.back()
|
||||
secondary: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onToggleUseSSLFinished: submitButton.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onBack: {
|
||||
root.parent.showGeneralSettings()
|
||||
root.setDefaultValues()
|
||||
}
|
||||
|
||||
function submit(){
|
||||
submitButton.loading = true
|
||||
root.backend.toggleUseSSLforSMTP(sslButton.checked)
|
||||
}
|
||||
|
||||
function setDefaultValues(){
|
||||
sslButton.checked = root.backend.useSSLforSMTP
|
||||
starttlsButton.checked = !root.backend.useSSLforSMTP
|
||||
}
|
||||
|
||||
|
||||
Component.onCompleted: root.setDefaultValues()
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
property var colorScheme
|
||||
|
||||
property string text: "Text"
|
||||
property string actionText: "Action"
|
||||
property string actionIcon: ""
|
||||
property string description: "Lorem ipsum dolor sit amet"
|
||||
property var type: SettingsItem.ActionType.Toggle
|
||||
|
||||
property bool checked: true
|
||||
property bool disabled: false
|
||||
property bool loading: false
|
||||
|
||||
signal clicked
|
||||
|
||||
spacing: 20
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: root.parent.Layout.maximumWidth
|
||||
|
||||
enum ActionType {
|
||||
Toggle = 1, Button = 2, PrimaryButton = 3
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
id:mainLabel
|
||||
colorScheme: root.colorScheme
|
||||
text: root.text
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.minimumWidth: mainLabel.width
|
||||
Layout.maximumWidth: root.Layout.maximumWidth - root.spacing - (
|
||||
toggle.visible ? toggle.width : button.width
|
||||
)
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
colorScheme: root.colorScheme
|
||||
text: root.description
|
||||
color: root.colorScheme.text_weak
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Toggle {
|
||||
id: toggle
|
||||
colorScheme: root.colorScheme
|
||||
visible: root.type == SettingsItem.ActionType.Toggle
|
||||
|
||||
checked: root.checked
|
||||
loading: root.loading
|
||||
onClicked: { if (!root.loading) root.clicked() }
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
colorScheme: root.colorScheme
|
||||
visible: root.type == SettingsItem.Button || root.type == SettingsItem.PrimaryButton
|
||||
text: root.actionText + (root.actionIcon != "" ? " " : "")
|
||||
loading: root.loading
|
||||
icon.source: root.actionIcon
|
||||
onClicked: { if (!root.loading) root.clicked() }
|
||||
secondary: root.type != SettingsItem.PrimaryButton
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: colorScheme.border_weak
|
||||
height: 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Controls.impl 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ScrollView {
|
||||
id: root
|
||||
|
||||
property var colorScheme
|
||||
property var backend
|
||||
default property alias items: content.children
|
||||
|
||||
signal back()
|
||||
|
||||
property int _leftRightMargins: 64
|
||||
property int _topBottomMargins: 68
|
||||
property int _spacing: 22
|
||||
|
||||
clip: true
|
||||
contentWidth: pane.width
|
||||
contentHeight: pane.height
|
||||
|
||||
RowLayout{
|
||||
id: pane
|
||||
width: root.width
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: root._spacing
|
||||
Layout.maximumWidth: root.width - 2*root._leftRightMargins
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: root._topBottomMargins
|
||||
Layout.bottomMargin: root._topBottomMargins
|
||||
Layout.leftMargin: root._leftRightMargins
|
||||
Layout.rightMargin: root._leftRightMargins
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: 10
|
||||
leftMargin: 18
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: root.back()
|
||||
icon.source: "icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
horizontalPadding: 8
|
||||
}
|
||||
}
|
|
@ -30,12 +30,14 @@ Item {
|
|||
property var backend
|
||||
|
||||
property var user
|
||||
property string address
|
||||
|
||||
signal dismissed()
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
@ -56,7 +58,7 @@ Item {
|
|||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: user ? user.username : ""
|
||||
text: address
|
||||
color: root.colorScheme.text_weak
|
||||
type: Label.LabelType.Lead
|
||||
}
|
||||
|
@ -80,30 +82,50 @@ Item {
|
|||
Repeater {
|
||||
model: clients
|
||||
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Rectangle {
|
||||
implicitWidth: clientRow.width
|
||||
implicitHeight: clientRow.height
|
||||
|
||||
ColorImage {
|
||||
source: model.iconSource
|
||||
height: 36
|
||||
ColumnLayout {
|
||||
id: clientRow
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ColorImage {
|
||||
source: model.iconSource
|
||||
height: 36
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.leftMargin: 12
|
||||
text: model.name
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.leftMargin: 12
|
||||
text: model.name
|
||||
type: Label.LabelType.Body
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: root.colorScheme.border_weak
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (model.name != "Apple Mail") {
|
||||
console.log(" TODO configure ", model.name)
|
||||
return
|
||||
}
|
||||
root.user.configureAppleMail(root.address)
|
||||
root.dismissed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,18 +42,9 @@ Item {
|
|||
property var backend
|
||||
property var window
|
||||
|
||||
// in case of adding new account this property should be undefined
|
||||
property var user
|
||||
property alias username: usernameTextField.text
|
||||
state: "Page 1"
|
||||
|
||||
onUserChanged: {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
passwordTextField.text = ""
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
}
|
||||
|
||||
onLoginAbort: {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
|
@ -78,15 +69,15 @@ Item {
|
|||
}
|
||||
|
||||
Connections {
|
||||
target: user !== undefined ? user : root.backend
|
||||
target: root.backend
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
console.assert(stackLayout.currentIndex == 0, "Unexpected loginUsernamePasswordError")
|
||||
console.assert(signInButton.loading == true, "Unexpected loginUsernamePasswordError")
|
||||
|
||||
stackLayout.loginFailed()
|
||||
errorLabel.text = qsTr("Your email and/or password are incorrect")
|
||||
|
||||
if (errorMsg!="") errorLabel.text = errorMsg
|
||||
else errorLabel.text = qsTr("Your email and/or password are incorrect")
|
||||
}
|
||||
|
||||
onLoginFreeUserError: {
|
||||
|
@ -152,6 +143,14 @@ Item {
|
|||
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
|
||||
passwordTextField.text = ""
|
||||
}
|
||||
|
||||
onLoginFinished: {
|
||||
stackLayout.currentIndex = 0
|
||||
loginNormalLayout.reset()
|
||||
passwordTextField.text = ""
|
||||
login2FALayout.reset()
|
||||
login2PasswordLayout.reset()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
@ -218,8 +217,6 @@ Item {
|
|||
id: usernameTextField
|
||||
label: qsTr("Username or email")
|
||||
|
||||
text: user !== undefined ? user.username : ""
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
|
@ -304,12 +301,7 @@ Item {
|
|||
enabled = false
|
||||
loading = true
|
||||
|
||||
if (root.user !== undefined) {
|
||||
root.user.login(usernameTextField.text, passwordTextField.text)
|
||||
return
|
||||
}
|
||||
|
||||
root.login(usernameTextField.text, passwordTextField.text)
|
||||
root.login(usernameTextField.text, Qt.btoa(passwordTextField.text))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,12 +386,7 @@ Item {
|
|||
enabled = false
|
||||
loading = true
|
||||
|
||||
if (root.user !== undefined) {
|
||||
root.user.login2FA(usernameTextField.text, twoFactorPasswordTextField.text)
|
||||
return
|
||||
}
|
||||
|
||||
root.login2FA(usernameTextField.text, twoFactorPasswordTextField.text)
|
||||
root.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -471,12 +458,7 @@ Item {
|
|||
enabled = false
|
||||
loading = true
|
||||
|
||||
if (root.user !== undefined) {
|
||||
root.user.login2Password(usernameTextField.text, secondPasswordTextField.text)
|
||||
return
|
||||
}
|
||||
|
||||
root.login2Password(usernameTextField.text, secondPasswordTextField.text)
|
||||
root.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,20 +22,16 @@ import QtQuick.Layouts 1.12
|
|||
import QtQuick.Controls 2.13
|
||||
|
||||
import Proton 4.0
|
||||
import ProtonBackend 1.0
|
||||
import Notifications 1.0
|
||||
|
||||
// Because of https://bugreports.qt.io/browse/QTBUG-69777 and other bugs alike it is impossible
|
||||
// to use Window with flags: Qt.Popup here since it won't close by it's own on click outside.
|
||||
PopupWindow {
|
||||
Window {
|
||||
id: root
|
||||
title: "ProtonMail Bridge"
|
||||
|
||||
height: contentLayout.implicitHeight
|
||||
width: contentLayout.implicitWidth
|
||||
|
||||
minimumHeight: 201
|
||||
minimumWidth: 448
|
||||
flags: Qt.FramelessWindowHint
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.currentStyle
|
||||
|
||||
|
@ -47,15 +43,19 @@ PopupWindow {
|
|||
signal showMainWindow()
|
||||
signal showHelp()
|
||||
signal showSettings()
|
||||
signal showSignIn(string username)
|
||||
signal quit()
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
|
||||
Layout.minimumHeight: 201
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
ColumnLayout {
|
||||
Layout.minimumWidth: 448
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
|
@ -76,13 +76,13 @@ PopupWindow {
|
|||
}
|
||||
|
||||
switch (statusItem.activeNotification.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning
|
||||
case Notification.NotificationType.Success:
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success
|
||||
case Notification.NotificationType.Info:
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info
|
||||
}
|
||||
}
|
||||
|
@ -149,8 +149,8 @@ PopupWindow {
|
|||
Layout.fillHeight: true
|
||||
|
||||
Layout.maximumHeight: accountListView.count ?
|
||||
accountListView.contentHeight / accountListView.count * 3 + accountListView.anchors.topMargin + accountListView.anchors.bottomMargin :
|
||||
Number.POSITIVE_INFINITY
|
||||
accountListView.contentHeight / accountListView.count * 3 + accountListView.anchors.topMargin + accountListView.anchors.bottomMargin :
|
||||
Number.POSITIVE_INFINITY
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
clip: true
|
||||
|
@ -171,13 +171,17 @@ PopupWindow {
|
|||
|
||||
interactive: contentHeight > parent.height
|
||||
snapMode: ListView.SnapToItem
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
delegate: Item {
|
||||
id: viewItem
|
||||
width: ListView.view.width
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
property var user: root.backend.users.get(index)
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
|
@ -187,15 +191,19 @@ PopupWindow {
|
|||
|
||||
Layout.margins: 12
|
||||
|
||||
user: modelData
|
||||
user: viewItem.user
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.margins: 12
|
||||
colorScheme: root.colorScheme
|
||||
visible: true
|
||||
text: "test"
|
||||
visible: !viewItem.user.loggedIn
|
||||
text: qsTr("Sign in")
|
||||
onClicked: {
|
||||
root.showSignIn(viewItem.username)
|
||||
root.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -297,4 +305,8 @@ PopupWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) root.close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ Item {
|
|||
root.loginAbort(username)
|
||||
}
|
||||
|
||||
user: (backend.users.count === 1 && backend.users.get(0).loggedIn === false) ? backend.users.get(0) : undefined
|
||||
username: (backend.users.count === 1 && backend.users.get(0).loggedIn === false) ? backend.users.get(0).username : ""
|
||||
backend: root.backend
|
||||
window: root.window
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic-chevron-down">
|
||||
<path id="icon" fill-rule="evenodd" clip-rule="evenodd" d="M2.3 6.30001L8 12L13.7 6.30001L13 5.60001L8 10.58L3 5.60001L2.3 6.30001Z" fill="#17181C"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 283 B |
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic-chevron-up">
|
||||
<path id="icon" fill-rule="evenodd" clip-rule="evenodd" d="M13.7 9.7L7.99999 4L2.29999 9.7L2.99999 10.4L7.99999 5.42L13 10.4L13.7 9.7Z" fill="#17181C"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 284 B |
|
@ -0,0 +1,4 @@
|
|||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 2V0H12V10H9V9H11V1H5V2H4Z" fill="#262A33"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 3V13H8V3H0ZM7 4V12H1V4H7Z" fill="#262A33"/>
|
||||
</svg>
|
After Width: | Height: | Size: 255 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 2L13.2427 2L13.2427 1.99998L13.2426 2L13 2L9 2V3H12.2426L5.76613 9.47651L6.47324 10.1836L13 3.65686V7H14V3V2ZM2 2H5V3H3L3 13L13 13V11H14V14H13H3H2V13V3V2Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 327 B |
|
@ -0,0 +1,12 @@
|
|||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="32" cy="32" r="30" fill="url(#paint0_linear)"/>
|
||||
<path d="M32 18C30.3431 18 29 19.3431 29 21C29 22.6569 30.3431 24 32 24C33.6569 24 35 22.6569 35 21C35 19.3431 33.6569 18 32 18Z" fill="white"/>
|
||||
<path d="M30 28C28.8954 28 28 28.8954 28 30C28 31.1046 28.8954 32 30 32V42C28.8954 42 28 42.8954 28 44C28 45.1046 28.8954 46 30 46H34C35.1046 46 36 45.1046 36 44C36 42.8954 35.1046 42 34 42V30C34 28.8954 33.1046 28 32 28H30Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="32" y1="62" x2="14.4192" y2="7.69125" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F6DE6"/>
|
||||
<stop offset="0.483234" stop-color="#63A1FE"/>
|
||||
<stop offset="1" stop-color="#82D2FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 807 B |
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 5H6V10H5V5Z" fill="#262A33"/>
|
||||
<path d="M8 5H9V10H8V5Z" fill="#262A33"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2H14V3H12V13H2V3H0V2H4V0H10V2ZM9 1H5V2H9V1ZM11 12H3V3H11V12Z" fill="#262A33"/>
|
||||
</svg>
|
After Width: | Height: | Size: 319 B |
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build darwin
|
||||
// +build build_qt
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void SetDockIconVisibleState(bool visible);
|
||||
bool GetDockIconVisibleState();
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build darwin
|
||||
// +build build_qt
|
||||
|
||||
#include "DockIcon.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
void SetDockIconVisibleState(bool visible) {
|
||||
if (visible) {
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
return;
|
||||
} else {
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool GetDockIconVisibleState() {
|
||||
switch ([NSApp activationPolicy]) {
|
||||
case NSApplicationActivationPolicyAccessory:
|
||||
case NSApplicationActivationPolicyProhibited:
|
||||
return false;
|
||||
case NSApplicationActivationPolicyRegular:
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build darwin
|
||||
// +build build_qt
|
||||
|
||||
package dockicon
|
||||
|
||||
// #cgo CFLAGS: -x objective-c
|
||||
// #cgo LDFLAGS: -framework Cocoa
|
||||
// #include "DockIcon.h"
|
||||
import "C"
|
||||
|
||||
func SetDockIconVisibleState(visible bool) {
|
||||
C.SetDockIconVisibleState(C.bool(visible))
|
||||
}
|
||||
func GetDockIconVisibleState() bool {
|
||||
return bool(C.GetDockIconVisibleState())
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build !darwin
|
||||
// +build build_qt
|
||||
|
||||
package dockicon
|
||||
|
||||
func SetDockIconVisibleState(visible bool) {}
|
||||
func GetDockIconVisibleState() bool {
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
// Package qt provides communication between Qt/QML frontend and Go backend
|
||||
package qt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ProtonMail/go-autostart"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/therecipe/qt/qml"
|
||||
"github.com/therecipe/qt/widgets"
|
||||
)
|
||||
|
||||
type FrontendQt struct {
|
||||
programName, programVersion string
|
||||
|
||||
panicHandler types.PanicHandler
|
||||
locations *locations.Locations
|
||||
settings *settings.Settings
|
||||
eventListener listener.Listener
|
||||
updater types.Updater
|
||||
userAgent *useragent.UserAgent
|
||||
bridge types.Bridger
|
||||
noEncConfirmator types.NoEncConfirmator
|
||||
autostart *autostart.App
|
||||
restarter types.Restarter
|
||||
|
||||
authClient pmapi.Client
|
||||
auth *pmapi.Auth
|
||||
password []byte
|
||||
|
||||
newVersionInfo updater.VersionInfo
|
||||
|
||||
log *logrus.Entry
|
||||
usersMtx sync.Mutex
|
||||
|
||||
app *widgets.QApplication
|
||||
engine *qml.QQmlApplicationEngine
|
||||
qml *QMLBackend
|
||||
}
|
||||
|
||||
func New(
|
||||
version,
|
||||
buildVersion,
|
||||
programName string,
|
||||
showWindowOnStart bool,
|
||||
panicHandler types.PanicHandler,
|
||||
locations *locations.Locations,
|
||||
settings *settings.Settings,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
userAgent *useragent.UserAgent,
|
||||
bridge types.Bridger,
|
||||
_ types.NoEncConfirmator,
|
||||
autostart *autostart.App,
|
||||
restarter types.Restarter,
|
||||
) *FrontendQt {
|
||||
return &FrontendQt{
|
||||
programName: "Proton Mail Bridge",
|
||||
programVersion: version,
|
||||
log: logrus.WithField("pkg", "frontend/qt"),
|
||||
|
||||
panicHandler: panicHandler,
|
||||
locations: locations,
|
||||
settings: settings,
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
userAgent: userAgent,
|
||||
bridge: bridge,
|
||||
autostart: autostart,
|
||||
restarter: restarter,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) Loop() error {
|
||||
err := f.initiateQtApplication()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer f.panicHandler.HandlePanic()
|
||||
f.watchEvents()
|
||||
}()
|
||||
|
||||
if ret := f.app.Exec(); ret != 0 {
|
||||
err := fmt.Errorf("Event loop ended with return value: %v", ret)
|
||||
f.log.Warn("App exec", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifyManualUpdate(version updater.VersionInfo, canInstall bool) {
|
||||
if canInstall {
|
||||
f.qml.UpdateManualReady(version.Version.String())
|
||||
} else {
|
||||
f.qml.UpdateManualError()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) SetVersion(version updater.VersionInfo) {
|
||||
f.newVersionInfo = version
|
||||
f.qml.SetReleaseNotesLink(version.ReleaseNotesPage)
|
||||
f.qml.SetLandingPageLink(version.LandingPage)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifySilentUpdateInstalled() {
|
||||
f.qml.UpdateSilentRestartNeeded()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifySilentUpdateError(err error) {
|
||||
f.log.WithError(err).Warn("Update failed, asking for manual.")
|
||||
f.qml.UpdateManualError()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) WaitUntilFrontendIsReady() {
|
||||
// TODO: Implement
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
// Package qt provides communication between Qt/QML frontend and Go backend
|
||||
package qt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) watchEvents() {
|
||||
f.WaitUntilFrontendIsReady()
|
||||
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
noActiveKeyForRecipientCh := f.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)
|
||||
internetOffCh := f.eventListener.ProvideChannel(events.InternetOffEvent)
|
||||
internetOnCh := f.eventListener.ProvideChannel(events.InternetOnEvent)
|
||||
secondInstanceCh := f.eventListener.ProvideChannel(events.SecondInstanceEvent)
|
||||
restartBridgeCh := f.eventListener.ProvideChannel(events.RestartBridgeEvent)
|
||||
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
||||
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
|
||||
updateApplicationCh := f.eventListener.ProvideChannel(events.UpgradeApplicationEvent)
|
||||
userChangedCh := f.eventListener.ProvideChannel(events.UserRefreshEvent)
|
||||
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
if strings.Contains(errorDetails, "IMAP failed") {
|
||||
f.qml.PortIssueIMAP()
|
||||
}
|
||||
if strings.Contains(errorDetails, "SMTP failed") {
|
||||
f.qml.PortIssueSMTP()
|
||||
}
|
||||
case <-credentialsErrorCh:
|
||||
f.qml.NotifyHasNoKeychain()
|
||||
case email := <-noActiveKeyForRecipientCh:
|
||||
f.qml.NoActiveKeyForRecipient(email)
|
||||
case <-internetOffCh:
|
||||
f.qml.InternetOff()
|
||||
case <-internetOnCh:
|
||||
f.qml.InternetOn()
|
||||
case <-secondInstanceCh:
|
||||
f.qml.ShowMainWindow()
|
||||
case <-restartBridgeCh:
|
||||
f.restart()
|
||||
case address := <-addressChangedCh:
|
||||
f.qml.AddressChanged(address)
|
||||
case address := <-addressChangedLogoutCh:
|
||||
f.qml.AddressChangedLogout(address)
|
||||
case userID := <-logoutCh:
|
||||
user, err := f.bridge.GetUser(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.qml.UserDisconnected(user.Username())
|
||||
case <-updateApplicationCh:
|
||||
f.updateForce()
|
||||
case userID := <-userChangedCh:
|
||||
f.userChanged(userID)
|
||||
case <-certIssue:
|
||||
f.qml.ApiCertIssue()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
package qt
|
||||
|
||||
func (f *FrontendQt) setVersion() {
|
||||
f.qml.SetVersion(f.programVersion)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setLogsPath() {
|
||||
path, err := f.locations.ProvideLogsPath()
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Cannot update path folder")
|
||||
return
|
||||
}
|
||||
f.qml.SetLogsPath(path)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setLicensePath() {
|
||||
f.qml.SetLicensePath(f.locations.GetLicenseFilePath())
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setCurrentEmailClient() {
|
||||
f.qml.SetCurrentEmailClient(f.userAgent.String())
|
||||
}
|
||||
|
||||
func (f *FrontendQt) reportBug(description, address, emailClient string, includeLogs bool) {
|
||||
//TODO
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
package qt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
qmlLog "github.com/ProtonMail/proton-bridge/internal/frontend/qt/log"
|
||||
"github.com/therecipe/qt/core"
|
||||
"github.com/therecipe/qt/qml"
|
||||
"github.com/therecipe/qt/quickcontrols2"
|
||||
"github.com/therecipe/qt/widgets"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) initiateQtApplication() error {
|
||||
qmlLog.InstallMessageHandler()
|
||||
|
||||
f.app = widgets.NewQApplication(len(os.Args), os.Args)
|
||||
|
||||
core.QCoreApplication_SetApplicationName(f.programName)
|
||||
core.QCoreApplication_SetApplicationVersion(f.programVersion)
|
||||
|
||||
// High DPI scaling for windows.
|
||||
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, false)
|
||||
// Software OpenGL: to avoid dedicated GPU.
|
||||
core.QCoreApplication_SetAttribute(core.Qt__AA_UseSoftwareOpenGL, true)
|
||||
|
||||
// Bridge runs background, no window is needed to be opened.
|
||||
f.app.SetQuitOnLastWindowClosed(false)
|
||||
|
||||
// QML Engine and path
|
||||
f.engine = qml.NewQQmlApplicationEngine(f.app)
|
||||
|
||||
f.qml = NewQMLBackend(nil)
|
||||
f.qml.setup(f)
|
||||
f.engine.RootContext().SetContextProperty("go", f.qml)
|
||||
|
||||
f.engine.AddImportPath("qrc:/qml/")
|
||||
f.engine.AddPluginPath("qrc:/qml/")
|
||||
|
||||
// Add style: if colorScheme / style is forgotten we should fallback to
|
||||
// default style and should be Proton
|
||||
quickcontrols2.QQuickStyle_AddStylePath("qrc:/qml/")
|
||||
quickcontrols2.QQuickStyle_SetStyle("Proton")
|
||||
|
||||
f.engine.Load(core.NewQUrl3("qrc:/qml/Bridge.qml", 0))
|
||||
|
||||
// Check QML is loaded properly.
|
||||
if len(f.engine.RootObjects()) == 0 {
|
||||
return errors.New("QML not loaded properly")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build !build_qt
|
||||
|
||||
package qt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ProtonMail/go-autostart"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log = logrus.WithField("pkg", "frontend-nogui") //nolint[gochecknoglobals]
|
||||
|
||||
type FrontendHeadless struct{}
|
||||
|
||||
func New(
|
||||
version,
|
||||
buildVersion,
|
||||
programName string,
|
||||
showWindowOnStart bool,
|
||||
panicHandler types.PanicHandler,
|
||||
locations *locations.Locations,
|
||||
settings *settings.Settings,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
userAgent *useragent.UserAgent,
|
||||
bridge types.Bridger,
|
||||
noEncConfirmator types.NoEncConfirmator,
|
||||
autostart *autostart.App,
|
||||
restarter types.Restarter,
|
||||
) *FrontendHeadless {
|
||||
return &FrontendHeadless{}
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) Loop() error {
|
||||
log.Info("Check status on localhost:8081")
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Bridge is running")
|
||||
})
|
||||
return http.ListenAndServe(":8081", nil)
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) WaitUntilFrontendIsReady() {
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) SetVersion(update updater.VersionInfo) {
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) NotifySilentUpdateInstalled() {
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) NotifySilentUpdateError(err error) {
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) InstanceExistAlert() {}
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
package qt
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/clientconfig"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) setIsDiskCacheEnabled() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setDiskCachePath() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (f *FrontendQt) changeLocalCache(enableDiskCache bool, diskCachePath string) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setIsAutostartOn() {
|
||||
f.qml.SetIsAutostartOn(f.autostart.IsEnabled())
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleAutostart(makeItEnabled bool) {
|
||||
defer f.qml.ToggleAutostartFinished()
|
||||
if makeItEnabled == f.autostart.IsEnabled() {
|
||||
f.setIsAutostartOn()
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if makeItEnabled {
|
||||
err = f.autostart.Enable()
|
||||
} else {
|
||||
err = f.autostart.Disable()
|
||||
}
|
||||
f.setIsAutostartOn()
|
||||
|
||||
if err != nil {
|
||||
f.log.
|
||||
WithField("makeItEnabled", makeItEnabled).
|
||||
WithField("isEnabled", f.qml.IsAutostartOn()).
|
||||
WithError(err).
|
||||
Error("Autostart change failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleDoH(makeItEnabled bool) {
|
||||
if f.settings.GetBool(settings.AllowProxyKey) == makeItEnabled {
|
||||
f.qml.SetIsDoHEnabled(makeItEnabled)
|
||||
return
|
||||
}
|
||||
f.settings.SetBool(settings.AllowProxyKey, makeItEnabled)
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleUseSSLforSMTP(makeItEnabled bool) {
|
||||
if f.settings.GetBool(settings.SMTPSSLKey) == makeItEnabled {
|
||||
f.qml.SetUseSSLforSMTP(makeItEnabled)
|
||||
return
|
||||
}
|
||||
f.settings.SetBool(settings.SMTPPortKey, makeItEnabled)
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) changePorts(imapPort, smtpPort int) {
|
||||
f.settings.SetInt(settings.IMAPPortKey, imapPort)
|
||||
f.settings.SetInt(settings.SMTPPortKey, smtpPort)
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) isPortFree(port int) bool {
|
||||
return ports.IsPortFree(port)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) configureAppleMail(userID, address string) {
|
||||
user, err := f.bridge.GetUser(userID)
|
||||
if err != nil {
|
||||
f.log.WithField("userID", userID).Error("Cannot configure AppleMail for user")
|
||||
return
|
||||
}
|
||||
|
||||
needRestart, err := clientconfig.ConfigureAppleMail(user, address, f.settings)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Apple Mail config failed")
|
||||
}
|
||||
|
||||
if needRestart {
|
||||
// There is delay needed for external window to open
|
||||
time.Sleep(2 * time.Second)
|
||||
f.restart()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) triggerReset() {
|
||||
defer f.qml.ResetFinished()
|
||||
//TODO
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setKeychain() {
|
||||
availableKeychain := []string{}
|
||||
for chain := range keychain.Helpers {
|
||||
availableKeychain = append(availableKeychain, chain)
|
||||
}
|
||||
f.qml.SetAvailableKeychain(availableKeychain)
|
||||
f.qml.SetSelectedKeychain(f.bridge.GetKeychainApp())
|
||||
}
|
||||
|
||||
func (f *FrontendQt) selectKeychain(wantKeychain string) {
|
||||
if f.bridge.GetKeychainApp() == wantKeychain {
|
||||
return
|
||||
}
|
||||
|
||||
f.bridge.SetKeychainApp(wantKeychain)
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) restart() {
|
||||
f.log.Info("Restarting bridge")
|
||||
f.restarter.SetToRestart()
|
||||
f.app.Exit(0)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) quit() {
|
||||
f.log.Warn("Your wish is my command.. I quit!")
|
||||
f.app.Exit(0)
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
package qt
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
)
|
||||
|
||||
var checkingUpdates = sync.Mutex{}
|
||||
|
||||
func (f *FrontendQt) checkUpdates() error {
|
||||
version, err := f.updater.Check()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.SetVersion(version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
||||
checkingUpdates.Lock()
|
||||
defer checkingUpdates.Lock()
|
||||
defer f.qml.CheckUpdatesFinished()
|
||||
|
||||
if err := f.checkUpdates(); err != nil {
|
||||
f.log.WithError(err).Error("An error occurred while checking updates")
|
||||
if isRequestFromUser {
|
||||
f.qml.UpdateManualError()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !f.updater.IsUpdateApplicable(f.newVersionInfo) {
|
||||
f.log.Debug("No need to update")
|
||||
if isRequestFromUser {
|
||||
f.qml.UpdateIsLatestVersion()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !f.updater.CanInstall(f.newVersionInfo) {
|
||||
f.log.Debug("A manual update is required")
|
||||
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) updateForce() {
|
||||
checkingUpdates.Lock()
|
||||
defer checkingUpdates.Lock()
|
||||
|
||||
version := ""
|
||||
if err := f.checkUpdates(); err == nil {
|
||||
version = f.newVersionInfo.Version.String()
|
||||
}
|
||||
|
||||
f.qml.UpdateForce(version)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setIsAutomaticUpdateOn() {
|
||||
f.qml.SetIsAutomaticUpdateOn(f.settings.GetBool(settings.AutoUpdateKey))
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleAutomaticUpdate(makeItEnabled bool) {
|
||||
f.qml.SetIsAutomaticUpdateOn(makeItEnabled)
|
||||
isEnabled := f.settings.GetBool(settings.AutoUpdateKey)
|
||||
if makeItEnabled == isEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
f.settings.SetBool(settings.AutoUpdateKey, makeItEnabled)
|
||||
|
||||
f.checkUpdatesAndNotify(false)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setIsBetaEnabled() {
|
||||
channel := f.bridge.GetUpdateChannel()
|
||||
f.qml.SetIsBetaEnabled(channel == updater.EarlyChannel)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleBeta(makeItEnabled bool) {
|
||||
channel := f.bridge.GetUpdateChannel()
|
||||
|
||||
if makeItEnabled == (channel == updater.EarlyChannel) {
|
||||
f.qml.SetIsBetaEnabled(makeItEnabled)
|
||||
return
|
||||
}
|
||||
|
||||
channel = updater.StableChannel
|
||||
if makeItEnabled {
|
||||
channel = updater.EarlyChannel
|
||||
}
|
||||
|
||||
needRestart, err := f.bridge.SetUpdateChannel(channel)
|
||||
f.setIsBetaEnabled()
|
||||
|
||||
if err != nil {
|
||||
f.log.WithError(err).Warn("Switching udpate channel failed.")
|
||||
f.qml.UpdateManualError()
|
||||
return
|
||||
}
|
||||
|
||||
if needRestart {
|
||||
f.restart()
|
||||
return
|
||||
}
|
||||
|
||||
f.checkUpdatesAndNotify(false)
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
package qt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) loadUsers() {
|
||||
f.usersMtx.Lock()
|
||||
defer f.usersMtx.Unlock()
|
||||
|
||||
f.qml.Users().clear()
|
||||
|
||||
for _, user := range f.bridge.GetUsers() {
|
||||
f.qml.Users().addUser(newQMLUserFromBacked(f, user))
|
||||
}
|
||||
|
||||
// If there are no active accounts.
|
||||
if f.qml.Users().Count() == 0 {
|
||||
f.log.Info("No active accounts")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) userChanged(userID string) {
|
||||
f.usersMtx.Lock()
|
||||
defer f.usersMtx.Unlock()
|
||||
|
||||
fUsers := f.qml.Users()
|
||||
|
||||
index := fUsers.indexByID(userID)
|
||||
user, err := f.bridge.GetUser(userID)
|
||||
|
||||
if user == nil || err != nil {
|
||||
if index >= 0 { // delete existing user
|
||||
fUsers.removeUser(index)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if index < 0 { // add non-existing user
|
||||
fUsers.addUser(newQMLUserFromBacked(f, user))
|
||||
return
|
||||
}
|
||||
|
||||
// update exiting user
|
||||
fUsers.users[index].update(user)
|
||||
}
|
||||
|
||||
func newQMLUserFromBacked(f *FrontendQt, user types.User) *QMLUser {
|
||||
qu := NewQMLUser(nil)
|
||||
qu.ID = user.ID()
|
||||
|
||||
qu.update(user)
|
||||
|
||||
qu.ConnectToggleSplitMode(func(activateSplitMode bool) {
|
||||
go func() {
|
||||
defer qu.ToggleSplitModeFinished()
|
||||
if activateSplitMode == user.IsCombinedAddressMode() {
|
||||
user.SwitchAddressMode()
|
||||
}
|
||||
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
||||
}()
|
||||
})
|
||||
|
||||
qu.ConnectLogout(func() {
|
||||
qu.SetLoggedIn(false)
|
||||
go user.Logout()
|
||||
})
|
||||
|
||||
qu.ConnectConfigureAppleMail(func(address string) {
|
||||
go f.configureAppleMail(qu.ID, address)
|
||||
})
|
||||
|
||||
return qu
|
||||
}
|
||||
|
||||
func (f *FrontendQt) login(username, password string) {
|
||||
var err error
|
||||
f.password, err = base64.StdEncoding.DecodeString(password)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Cannot decode password")
|
||||
f.qml.LoginUsernamePasswordError("Cannot decode password")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
f.authClient, f.auth, err = f.bridge.Login(username, f.password)
|
||||
if err != nil {
|
||||
f.qml.LoginUsernamePasswordError(err.Error())
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
if f.auth.HasTwoFactor() {
|
||||
f.qml.Login2FARequested()
|
||||
return
|
||||
}
|
||||
if f.auth.HasMailboxPassword() {
|
||||
f.qml.Login2PasswordRequested()
|
||||
return
|
||||
}
|
||||
|
||||
f.finishLogin()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) login2FA(username, code string) {
|
||||
if f.auth == nil || f.authClient == nil {
|
||||
f.log.Errorf("Login 2FA: authethication incomplete %p %p", f.auth, f.authClient)
|
||||
f.qml.Login2FAErrorAbort("Missing authentication, try again.")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
twoFA, err := base64.StdEncoding.DecodeString(code)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Cannot decode 2fa code")
|
||||
f.qml.LoginUsernamePasswordError("Cannot decode 2fa code")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
err = f.authClient.Auth2FA(context.Background(), string(twoFA))
|
||||
if err == pmapi.ErrBad2FACodeTryAgain {
|
||||
f.log.Warn("Login 2FA: retry 2fa")
|
||||
f.qml.Login2FAError("")
|
||||
return
|
||||
}
|
||||
|
||||
if err == pmapi.ErrBad2FACode {
|
||||
f.log.Warn("Login 2FA: abort 2fa")
|
||||
f.qml.Login2FAErrorAbort("")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
f.log.WithError(err).Warn("Login 2FA: failed.")
|
||||
f.qml.Login2FAErrorAbort(err.Error())
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
if f.auth.HasMailboxPassword() {
|
||||
f.qml.Login2PasswordRequested()
|
||||
return
|
||||
}
|
||||
|
||||
f.finishLogin()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) login2Password(username, mboxPassword string) {
|
||||
var err error
|
||||
f.password, err = base64.StdEncoding.DecodeString(mboxPassword)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Cannot decode mbox password")
|
||||
f.qml.LoginUsernamePasswordError("Cannot decode mbox password")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
f.finishLogin()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) finishLogin() {
|
||||
defer f.loginClean()
|
||||
|
||||
if f.auth == nil || f.authClient == nil {
|
||||
f.log.Errorf("Finish login: Authethication incomplete %p %p", f.auth, f.authClient)
|
||||
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password)
|
||||
if err != nil {
|
||||
f.log.Errorf("Authethication incomplete %p %p", f.auth, f.authClient)
|
||||
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
|
||||
return
|
||||
}
|
||||
|
||||
index := f.qml.Users().indexByID(user.ID())
|
||||
if index < 0 {
|
||||
qu := newQMLUserFromBacked(f, user)
|
||||
qu.SetSetupGuideSeen(false)
|
||||
f.qml.Users().addUser(qu)
|
||||
return
|
||||
}
|
||||
|
||||
f.qml.Users().users[index].update(user)
|
||||
f.qml.LoginFinished()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) loginAbort(username string) {
|
||||
f.loginClean()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) loginClean() {
|
||||
f.auth = nil
|
||||
f.authClient = nil
|
||||
for i := range f.password {
|
||||
f.password[i] = '\x00'
|
||||
}
|
||||
f.password = f.password[0:0]
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
package qt
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/therecipe/qt/core"
|
||||
"github.com/therecipe/qt/gui"
|
||||
)
|
||||
|
||||
// getCursorPos returns current mouse position to be able to use in QML
|
||||
func getCursorPos() *core.QPoint {
|
||||
return gui.QCursor_Pos()
|
||||
}
|
||||
|
||||
// newQByteArrayFromString is a wrapper for new QByteArray from string.
|
||||
func newQByteArrayFromString(name string) *core.QByteArray {
|
||||
return core.NewQByteArray2(name, len(name))
|
||||
}
|
||||
|
||||
var (
|
||||
reMultiSpaces = regexp.MustCompile(`\s{2,}`)
|
||||
reStartWithSymbol = regexp.MustCompile(`^[.,/#!$@%^&*;:{}=\-_` + "`" + `~()]`)
|
||||
)
|
||||
|
||||
// getInitials based on webapp implementation:
|
||||
// https://github.com/ProtonMail/WebClients/blob/55d96a8b4afaaa4372fc5f1ef34953f2070fd7ec/packages/shared/lib/helpers/string.ts#L145
|
||||
func getInitials(fullName string) string {
|
||||
words := strings.Split(
|
||||
reMultiSpaces.ReplaceAllString(fullName, " "),
|
||||
" ",
|
||||
)
|
||||
|
||||
n := 0
|
||||
for _, word := range words {
|
||||
if !reStartWithSymbol.MatchString(word) {
|
||||
words[n] = word
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return "?"
|
||||
}
|
||||
|
||||
initials := words[0][0:1]
|
||||
if n != 1 {
|
||||
initials += words[n-1][0:1]
|
||||
}
|
||||
return strings.ToUpper(initials)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
// +build build_qt
|
||||
|
||||
#include "log.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QtGlobal>
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
Q_UNUSED( type )
|
||||
Q_UNUSED( context )
|
||||
QByteArray localMsg = msg.toUtf8().prepend("WHITESPACE");
|
||||
logMsgPacked(
|
||||
const_cast<char*>( (localMsg.constData()) +10 ),
|
||||
localMsg.size()-10
|
||||
);
|
||||
}
|
||||
|
||||
void InstallMessageHandler() {
|
||||
qInstallMessageHandler(messageHandler);
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
// Package log redirects QML logs to logrus
|
||||
package log
|
||||
|
||||
//#include "log.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
var logQML = logrus.WithField("pkg", "frontent/qml")
|
||||
|
||||
// InstallMessageHandler is registering logQML as logger for QML calls.
|
||||
func InstallMessageHandler() {
|
||||
C.InstallMessageHandler()
|
||||
}
|
||||
|
||||
//export logMsgPacked
|
||||
func logMsgPacked(data *C.char, len C.int) {
|
||||
logQML.Warn(C.GoStringN(data, len))
|
||||
}
|
||||
|
||||
// logDummy is here to trigger qtmoc to create cgo instructions
|
||||
type logDummy struct {
|
||||
core.QObject
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef LOGRUS_QML_LOG_H
|
||||
#define LOGRUS_QML_LOG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // C++
|
||||
|
||||
void InstallMessageHandler();
|
||||
;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // C++
|
||||
|
||||
#endif // LOGRUS_QML_LOG_H
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
package qt
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
dockIcon "github.com/ProtonMail/proton-bridge/internal/frontend/qt/dockicon"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// QMLBackend connects QML frontend with Go backend.
|
||||
type QMLBackend struct {
|
||||
core.QObject
|
||||
|
||||
_ func() *core.QPoint `slot:"getCursorPos"`
|
||||
_ func() `slot:"quit"`
|
||||
_ func() `slot:"restart"`
|
||||
|
||||
_ bool `property:dockIconVisible`
|
||||
|
||||
_ QMLUserModel `property:"users"`
|
||||
|
||||
// TODO copy stuff from Bridge_test.qml backend object
|
||||
_ string `property:"goos"`
|
||||
|
||||
_ func(username, password string) `slot:"login"`
|
||||
_ func(username, code string) `slot:"login2FA"`
|
||||
_ func(username, password string) `slot:"login2Password"`
|
||||
_ func(username string) `slot:"loginAbort"`
|
||||
_ func(errorMsg string) `signal:"loginUsernamePasswordError"`
|
||||
_ func(errorMsg string) `signal:"loginFreeUserError"`
|
||||
_ func(errorMsg string) `signal:"loginConnectionError"`
|
||||
_ func() `signal:"login2FARequested"`
|
||||
_ func(errorMsg string) `signal:"login2FAError"`
|
||||
_ func(errorMsg string) `signal:"login2FAErrorAbort"`
|
||||
_ func() `signal:"login2PasswordRequested"`
|
||||
_ func(errorMsg string) `signal:"login2PasswordError"`
|
||||
_ func(errorMsg string) `signal:"login2PasswordErrorAbort"`
|
||||
_ func() `signal:"loginFinished"`
|
||||
|
||||
_ func() `signal:"internetOff"`
|
||||
_ func() `signal:"internetOn"`
|
||||
|
||||
_ func(version string) `signal:"updateManualReady"`
|
||||
_ func() `signal:"updateManualRestartNeeded"`
|
||||
_ func() `signal:"updateManualError"`
|
||||
_ func(version string) `signal:"updateForce"`
|
||||
_ func() `signal:"updateForceError"`
|
||||
_ func() `signal:"updateSilentRestartNeeded"`
|
||||
_ func() `signal:"updateSilentError"`
|
||||
_ func() `signal:"updateIsLatestVersion"`
|
||||
_ func() `slot:"checkUpdates"`
|
||||
_ func() `signal:"checkUpdatesFinished"`
|
||||
|
||||
_ bool `property:"isDiskCacheEnabled"`
|
||||
_ string `property:"diskCachePath"`
|
||||
_ func() `signal:"cacheUnavailable"`
|
||||
_ func() `signal:"cacheCantMove"`
|
||||
_ func() `signal:"cacheLocationChangeSuccess"`
|
||||
_ func() `signal:"diskFull"`
|
||||
_ func(enableDiskCache bool, diskCachePath string) `slot:"changeLocalCache"`
|
||||
_ func() `signal:"changeLocalCacheFinished"`
|
||||
|
||||
_ bool `property:"isAutomaticUpdateOn"`
|
||||
_ func(makeItActive bool) `slot:"toggleAutomaticUpdate"`
|
||||
|
||||
_ bool `property:"isAutostartOn"`
|
||||
_ func(makeItActive bool) `slot:"toggleAutostart"`
|
||||
_ func() `signal:"toggleAutostartFinished"`
|
||||
|
||||
_ bool `property:"isBetaEnabled"`
|
||||
_ func(makeItActive bool) `slot:"toggleBeta"`
|
||||
|
||||
_ bool `property:"isDoHEnabled"`
|
||||
_ func(makeItActive bool) `slot:"toggleDoH"`
|
||||
|
||||
_ bool `property:"useSSLforSMTP"`
|
||||
_ func(makeItActive bool) `slot:"toggleUseSSLforSMTP"`
|
||||
_ func() `signal:"toggleUseSSLFinished"`
|
||||
|
||||
_ string `property:"hostname"`
|
||||
_ int `property:"portIMAP"`
|
||||
_ int `property:"portSMTP"`
|
||||
_ func(imapPort, smtpPort int) `slot:"changePorts"`
|
||||
_ func(port int) bool `slot:"isPortFree"`
|
||||
_ func() `signal:"changePortFinished"`
|
||||
_ func() `signal:"portIssueIMAP"`
|
||||
_ func() `signal:"portIssueSMTP"`
|
||||
|
||||
_ func() `slot:"triggerReset"`
|
||||
_ func() `signal:"resetFinished"`
|
||||
|
||||
_ string `property:"version"`
|
||||
_ string `property:"logsPath"`
|
||||
_ string `property:"licensePath"`
|
||||
_ string `property:"releaseNotesLink"`
|
||||
_ string `property:"landingPageLink"`
|
||||
|
||||
_ string `property:"currentEmailClient"`
|
||||
_ func() `slot:"updateCurrentMailClient"`
|
||||
_ func(description, address, emailClient string, includeLogs bool) `slot:"reportBug"`
|
||||
_ func() `signal:"reportBugFinished"`
|
||||
_ func() `signal:"bugReportSendSuccess"`
|
||||
_ func() `signal:"bugReportSendError"`
|
||||
|
||||
_ []string `property:"availableKeychain"`
|
||||
_ string `property:"selectedKeychain"`
|
||||
_ func(keychain string) `slot:"selectKeychain"`
|
||||
_ func() `signal:"notifyHasNoKeychain"`
|
||||
|
||||
_ func(email string) `signal:noActiveKeyForRecipient`
|
||||
_ func() `signal:showMainWindow`
|
||||
|
||||
_ func(address string) `signal:addressChanged`
|
||||
_ func(address string) `signal:addressChangedLogout`
|
||||
_ func(username string) `signal:userDisconnected`
|
||||
_ func() `signal:apiCertIssue`
|
||||
}
|
||||
|
||||
func (q *QMLBackend) setup(f *FrontendQt) {
|
||||
q.ConnectGetCursorPos(getCursorPos)
|
||||
q.ConnectQuit(f.quit)
|
||||
q.ConnectRestart(f.restart)
|
||||
|
||||
q.ConnectIsDockIconVisible(func() bool {
|
||||
return dockIcon.GetDockIconVisibleState()
|
||||
})
|
||||
q.ConnectSetDockIconVisible(func(visible bool) {
|
||||
dockIcon.SetDockIconVisibleState(visible)
|
||||
})
|
||||
|
||||
q.SetUsers(NewQMLUserModel(nil))
|
||||
f.loadUsers()
|
||||
|
||||
q.SetGoos(runtime.GOOS)
|
||||
|
||||
q.ConnectLogin(func(u, p string) { go f.login(u, p) })
|
||||
q.ConnectLogin2FA(func(u, p string) { go f.login2FA(u, p) })
|
||||
q.ConnectLogin2Password(func(u, p string) { go f.login2Password(u, p) })
|
||||
q.ConnectLoginAbort(func(u string) { go f.loginAbort(u) })
|
||||
|
||||
go f.checkUpdatesAndNotify(false)
|
||||
q.ConnectCheckUpdates(func() { go f.checkUpdatesAndNotify(true) })
|
||||
|
||||
f.setIsDiskCacheEnabled()
|
||||
f.setDiskCachePath()
|
||||
q.ConnectChangeLocalCache(f.changeLocalCache)
|
||||
|
||||
f.setIsAutomaticUpdateOn()
|
||||
q.ConnectToggleAutomaticUpdate(func(m bool) { go f.toggleAutomaticUpdate(m) })
|
||||
|
||||
f.setIsAutostartOn()
|
||||
q.ConnectToggleAutostart(f.toggleAutostart)
|
||||
|
||||
f.setIsBetaEnabled()
|
||||
q.ConnectToggleBeta(func(m bool) { go f.toggleBeta(m) })
|
||||
|
||||
q.SetIsDoHEnabled(f.settings.GetBool(settings.AllowProxyKey))
|
||||
q.ConnectToggleDoH(f.toggleDoH)
|
||||
|
||||
q.SetUseSSLforSMTP(f.settings.GetBool(settings.SMTPSSLKey))
|
||||
q.ConnectToggleUseSSLforSMTP(f.toggleUseSSLforSMTP)
|
||||
|
||||
q.SetHostname(bridge.Host)
|
||||
q.SetPortIMAP(f.settings.GetInt(settings.IMAPPortKey))
|
||||
q.SetPortSMTP(f.settings.GetInt(settings.SMTPPortKey))
|
||||
q.ConnectChangePorts(f.changePorts)
|
||||
q.ConnectIsPortFree(f.isPortFree)
|
||||
|
||||
q.ConnectTriggerReset(func() { go f.triggerReset() })
|
||||
|
||||
f.setVersion()
|
||||
f.setLogsPath()
|
||||
// release notes link is set by update
|
||||
f.setLicensePath()
|
||||
|
||||
f.setCurrentEmailClient()
|
||||
q.ConnectUpdateCurrentMailClient(func() { go f.setCurrentEmailClient() })
|
||||
q.ConnectReportBug(func(d, a, e string, i bool) { go f.reportBug(d, a, e, i) })
|
||||
|
||||
f.setKeychain()
|
||||
q.ConnectSelectKeychain(func(k string) { go f.selectKeychain(k) })
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
|
||||
package qt
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// QMLUserModel stores list of of users
|
||||
type QMLUserModel struct {
|
||||
core.QAbstractListModel
|
||||
|
||||
_ map[int]*core.QByteArray `property:"roles"`
|
||||
_ int `property:"count"`
|
||||
_ func() `constructor:"init"`
|
||||
_ func(row int) *core.QVariant `slot:"get"`
|
||||
|
||||
users []*QMLUser
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) init() {
|
||||
um.SetRoles(map[int]*core.QByteArray{
|
||||
int(core.Qt__UserRole + 1): newQByteArrayFromString("object"),
|
||||
})
|
||||
um.ConnectRowCount(um.rowCount)
|
||||
um.ConnectData(um.data)
|
||||
um.ConnectGet(um.get)
|
||||
um.users = []*QMLUser{}
|
||||
um.setCount()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) data(index *core.QModelIndex, property int) *core.QVariant {
|
||||
if !index.IsValid() {
|
||||
return core.NewQVariant()
|
||||
}
|
||||
return um.get(index.Row())
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) get(index int) *core.QVariant {
|
||||
if index < 0 || index >= um.rowCount(nil) {
|
||||
return core.NewQVariant()
|
||||
}
|
||||
return um.users[index].ToVariant()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) rowCount(*core.QModelIndex) int {
|
||||
return len(um.users)
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) setCount() {
|
||||
um.SetCount(len(um.users))
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) addUser(user *QMLUser) {
|
||||
um.BeginInsertRows(core.NewQModelIndex(), um.rowCount(nil), um.rowCount(nil))
|
||||
um.users = append(um.users, user)
|
||||
um.setCount()
|
||||
um.EndInsertRows()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) removeUser(row int) {
|
||||
um.BeginRemoveRows(core.NewQModelIndex(), row, row)
|
||||
um.users = append(um.users[:row], um.users[row+1:]...)
|
||||
um.setCount()
|
||||
um.EndRemoveRows()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) clear() {
|
||||
um.BeginRemoveRows(core.NewQModelIndex(), 0, um.rowCount(nil))
|
||||
um.users = []*QMLUser{}
|
||||
um.setCount()
|
||||
um.EndRemoveRows()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) indexByID(id string) int {
|
||||
for i, qu := range um.users {
|
||||
if id == qu.ID {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// QMLUser holds data, slots and signals and for user.
|
||||
type QMLUser struct {
|
||||
core.QObject
|
||||
|
||||
_ string `property:"username"`
|
||||
_ string `property:"avatarText"`
|
||||
_ bool `property:"loggedIn"`
|
||||
_ bool `property:"splitMode"`
|
||||
_ bool `property:"setupGuideSeen"`
|
||||
_ float32 `property:"usedBytes"`
|
||||
_ float32 `property:"totalBytes"`
|
||||
_ string `property:"password"`
|
||||
_ []string `property:"addresses"`
|
||||
|
||||
_ func(makeItActive bool) `slot:"toggleSplitMode"`
|
||||
_ func() `signal:"toggleSplitModeFinished"`
|
||||
_ func() `slot:"logout"`
|
||||
_ func(address string) `slot:"configureAppleMail"`
|
||||
|
||||
ID string
|
||||
}
|
||||
|
||||
func (qu *QMLUser) update(user types.User) {
|
||||
username := user.Username()
|
||||
qu.SetAvatarText(getInitials(username))
|
||||
qu.SetUsername(username)
|
||||
qu.SetLoggedIn(user.IsConnected())
|
||||
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
||||
qu.SetSetupGuideSeen(true)
|
||||
qu.SetUsedBytes(1.0) // TODO
|
||||
qu.SetTotalBytes(10000.0) // TODO
|
||||
qu.SetPassword(user.GetBridgePassword())
|
||||
qu.SetAddresses(user.GetAddresses())
|
||||
}
|
After Width: | Height: | Size: 557 KiB |
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#9397CD;}
|
||||
.st1{fill:#262A33;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<circle class="st0" cx="512.2" cy="512.1" r="512"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle class="st1" cx="850" cy="850" r="174"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M784.4,773.1h90.7c15.1,0,26.7,3.7,34.8,11.2s12.1,16.8,12.1,27.8c0,9.3-2.9,17.2-8.7,23.8
|
||||
c-3.8,4.4-9.5,7.9-16.9,10.5c11.3,2.7,19.5,7.4,24.9,14c5.3,6.6,8,14.9,8,24.9c0,8.1-1.9,15.4-5.7,21.9s-8.9,11.6-15.5,15.4
|
||||
c-4.1,2.4-10.2,4.1-18.4,5.1c-10.9,1.4-18.1,2.1-21.7,2.1h-83.6L784.4,773.1L784.4,773.1z M833.3,834.6h21.1
|
||||
c7.6,0,12.8-1.3,15.8-3.9c3-2.6,4.4-6.4,4.4-11.3c0-4.6-1.5-8.1-4.4-10.7c-3-2.6-8.1-3.8-15.5-3.8h-21.4L833.3,834.6L833.3,834.6z
|
||||
M833.3,896.2H858c8.3,0,14.2-1.5,17.6-4.4c3.4-3,5.1-6.9,5.1-11.9c0-4.6-1.7-8.4-5.1-11.2c-3.4-2.8-9.3-4.2-17.8-4.2h-24.6
|
||||
L833.3,896.2L833.3,896.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M511,263c0,0-136.3-4.5-164.4,146.7v103c0,0,1.2,11,32.2,33.4c31,22.4,111.2,85.4,132.3,85.4
|
||||
c21,0,101.3-63,132.3-85.4c31-22.4,32.2-33.4,32.2-33.4v-103C647.3,258.5,511,263,511,263z M604.3,465.9H511h-93.3v-56.1
|
||||
c18.9-75.1,93.3-76.1,93.3-76.1s74.4,1,93.3,76.1V465.9z"/>
|
||||
<path class="st2" d="M511,654.7c0,0-21.1-2.1-37.7-13.5C456.8,629.7,346.6,551,346.6,551v155.9c0,0,0.9,18.1,20.9,18.1
|
||||
s143.5,0,143.5,0s123.5,0,143.5,0s20.9-18.1,20.9-18.1V551c0,0-110.2,78.8-126.8,90.2C532.1,652.7,511,654.7,511,654.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -349,6 +349,7 @@ func (u *User) CheckBridgeLogin(password string) error {
|
|||
func (u *User) UpdateUser(ctx context.Context) error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
defer u.listener.Emit(events.UserRefreshEvent, u.userID)
|
||||
|
||||
_, err := u.client.UpdateUser(ctx)
|
||||
if err != nil {
|
||||
|
@ -376,6 +377,7 @@ func (u *User) SwitchAddressMode() error {
|
|||
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
defer u.listener.Emit(events.UserRefreshEvent, u.userID)
|
||||
|
||||
u.CloseAllConnections()
|
||||
|
||||
|
@ -414,7 +416,6 @@ func (u *User) logout() error {
|
|||
|
||||
if wasConnected {
|
||||
u.listener.Emit(events.LogoutEvent, u.userID)
|
||||
u.listener.Emit(events.UserRefreshEvent, u.userID)
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -425,6 +426,7 @@ func (u *User) logout() error {
|
|||
func (u *User) Logout() error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
defer u.listener.Emit(events.UserRefreshEvent, u.userID)
|
||||
|
||||
u.log.Debug("Logging out user")
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ func TestUpdateUser(t *testing.T) {
|
|||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
|
||||
m.credentialsStore.EXPECT().UpdateEmails("user", []string{testPMAPIAddress.Email}).Return(testCredentials, nil),
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
)
|
||||
|
||||
r.NoError(t, user.UpdateUser(context.Background()))
|
||||
|
@ -68,6 +69,7 @@ func TestUserSwitchAddressMode(t *testing.T) {
|
|||
m.pmapiClient.EXPECT().CountMessages(gomock.Any(), "").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
m.credentialsStore.EXPECT().SwitchAddressMode("user").Return(testCredentialsSplit, nil),
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
)
|
||||
|
||||
// Check switch to split mode.
|
||||
|
@ -85,6 +87,7 @@ func TestUserSwitchAddressMode(t *testing.T) {
|
|||
m.pmapiClient.EXPECT().CountMessages(gomock.Any(), "").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
m.credentialsStore.EXPECT().SwitchAddressMode("user").Return(testCredentials, nil),
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
)
|
||||
|
||||
// Check switch to combined mode.
|
||||
|
@ -105,6 +108,7 @@ func TestLogoutUser(t *testing.T) {
|
|||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil),
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
)
|
||||
|
||||
err := user.Logout()
|
||||
|
@ -123,6 +127,7 @@ func TestLogoutUserFailsLogout(t *testing.T) {
|
|||
m.credentialsStore.EXPECT().Logout("user").Return(nil, errors.New("logout failed")),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
)
|
||||
|
||||
err := user.Logout()
|
||||
|
|
|
@ -52,8 +52,8 @@ func TestNewUserUnlockFails(t *testing.T) {
|
|||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil),
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user"),
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user"),
|
||||
)
|
||||
|
||||
checkNewUserHasCredentials(m, "failed to unlock user: bad password", testCredentialsDisconnected)
|
||||
|
|
|
@ -351,6 +351,7 @@ func (u *Users) ClearData() error {
|
|||
func (u *Users) DeleteUser(userID string, clearStore bool) error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
defer u.events.Emit(events.UserRefreshEvent, userID)
|
||||
|
||||
log := log.WithField("user", userID)
|
||||
|
||||
|
|
|
@ -32,6 +32,9 @@ func TestClearData(t *testing.T) {
|
|||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "users")
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "users@pm.me")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "anotheruser@pm.me")
|
||||
|
|
|
@ -38,7 +38,9 @@ func TestDeleteUser(t *testing.T) {
|
|||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
)
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
|
||||
err := users.DeleteUser("user", true)
|
||||
r.NoError(t, err)
|
||||
|
@ -61,7 +63,9 @@ func TestDeleteUserWithFailingLogout(t *testing.T) {
|
|||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
|
||||
err := users.DeleteUser("user", true)
|
||||
r.NoError(t, err)
|
||||
|
|
|
@ -91,6 +91,7 @@ func TestNewUsersWithConnectedUserWithBadToken(t *testing.T) {
|
|||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
|