Compare commits
191 Commits
Author | SHA1 | Date |
---|---|---|
Dan Ballard | bfae3b577f | |
Dan Ballard | b20a82fb1a | |
Dan Ballard | 302e504d76 | |
Dan Ballard | 00c77c97d2 | |
Dan Ballard | d12efa2204 | |
Dan Ballard | ee10637261 | |
Sarah Jamie Lewis | 3cf87e825b | |
Dan Ballard | 96d9090b1d | |
Dan Ballard | ca58e063b6 | |
Dan Ballard | 2d537df810 | |
Dan Ballard | 94117e88d2 | |
Dan Ballard | 034a856c05 | |
Dan Ballard | 69c748dcc4 | |
Dan Ballard | 9ae38f6870 | |
Dan Ballard | 97c4deb4bf | |
Sarah Jamie Lewis | 8d6d5a23b9 | |
Sarah Jamie Lewis | 9a6dbae0c4 | |
erinn | 6eccece360 | |
Sarah Jamie Lewis | d538d8442e | |
Sarah Jamie Lewis | 62ef3549b5 | |
Dan Ballard | e33a543e66 | |
Sarah Jamie Lewis | 0090e86ade | |
Dan Ballard | 82fe74937c | |
Dan Ballard | 23b0af77f1 | |
Sarah Jamie Lewis | dad995cd3c | |
Sarah Jamie Lewis | 17518b1e6d | |
Sarah Jamie Lewis | 0c80577f72 | |
Sarah Jamie Lewis | a47a17b3a9 | |
Dan Ballard | 5ca0712d5e | |
Dan Ballard | b61f59643f | |
erinn | 82ee06d840 | |
erinn | eb12f57135 | |
Sarah Jamie Lewis | ddb671cf6a | |
Sarah Jamie Lewis | 93dbba5f15 | |
Dan Ballard | 1c961c2c4c | |
Dan Ballard | 040bb585a1 | |
Dan Ballard | e89c4bdb0e | |
Dan Ballard | f8e5bd29f3 | |
Dan Ballard | a18a522f81 | |
Dan Ballard | 16ce38da75 | |
Dan Ballard | 126c68e185 | |
Dan Ballard | c533580b70 | |
Dan Ballard | ca303ba41c | |
Dan Ballard | ff4a8a02ab | |
Dan Ballard | 6c0c31a74f | |
Dan Ballard | 4e0be20930 | |
Dan Ballard | 0a93dd40b6 | |
Dan Ballard | 5f6008a152 | |
Dan Ballard | 901293377e | |
Dan Ballard | a54d50a642 | |
Dan Ballard | f7c30755d0 | |
Dan Ballard | 66568a7983 | |
Dan Ballard | da8a35458f | |
Dan Ballard | ae2ae51d2d | |
Dan Ballard | b7a4486536 | |
Dan Ballard | 3e56f8c917 | |
erinn | c4203477de | |
erinn | 1c1a1ec68d | |
Dan Ballard | 225301bda6 | |
Sarah Jamie Lewis | 47394f5183 | |
Sarah Jamie Lewis | 0c0a8501c9 | |
Dan Ballard | 7268fc362f | |
Dan Ballard | e0d1e1cb2b | |
Dan Ballard | f0e0af3537 | |
Sarah Jamie Lewis | e266d6328a | |
Sarah Jamie Lewis | 94014ede01 | |
Dan Ballard | 547c2de725 | |
Sarah Jamie Lewis | 066c1965e6 | |
Sarah Jamie Lewis | ee2fd2ae98 | |
Dan Ballard | 607802d4e4 | |
Sarah Jamie Lewis | ecd1b71a8c | |
Sarah Jamie Lewis | 4e63694ebc | |
erinn | a7fe5f0f80 | |
erinn | 4b67879f64 | |
erinn | d0ba17b0e9 | |
Dan Ballard | c5154a91d1 | |
erinn | 886e0956f6 | |
Sarah Jamie Lewis | 4671a15c27 | |
erinn | a03a665a66 | |
erinn | b85f1464d3 | |
erinn | 09d9771845 | |
erinn | 24139a4683 | |
Dan Ballard | 398d7a286a | |
Dan Ballard | bb739027ea | |
Dan Ballard | 506ddeeb10 | |
Sarah Jamie Lewis | 219cd3889a | |
Dan Ballard | d3a6ec85cf | |
erinn | 8b8b20e7b6 | |
Sarah Jamie Lewis | 0cbc4298e2 | |
erinn | 4377118629 | |
erinn | 2d49c50de5 | |
erinn | 4d3777be81 | |
erinn | 88c470016b | |
Dan Ballard | a10da52767 | |
Sarah Jamie Lewis | 4b6f022a92 | |
Dan Ballard | 92577aef07 | |
erinn | cc4ec567fb | |
erinn | 0b7f2b13bd | |
erinn | 24854c24f5 | |
erinn | 564556d8fa | |
erinn | ed621e7795 | |
erinn | 30ae6ca331 | |
Sarah Jamie Lewis | 5b96d1c794 | |
Sarah Jamie Lewis | 87732a2ba2 | |
Sarah Jamie Lewis | 213a29c691 | |
Sarah Jamie Lewis | 6b93f059d7 | |
Sarah Jamie Lewis | 052649ef9d | |
erinn | 7940c27709 | |
Dan Ballard | 1888d3f032 | |
Dan Ballard | 650148d731 | |
Dan Ballard | f08062c0fb | |
Sarah Jamie Lewis | e55a7e940e | |
Sarah Jamie Lewis | 1e2c69eb4c | |
Sarah Jamie Lewis | 2a3945639d | |
Sarah Jamie Lewis | 16f9928275 | |
Sarah Jamie Lewis | 07e8c49d7b | |
Sarah Jamie Lewis | 38c59c5f82 | |
erinn | 5681a43d69 | |
erinn | c3e3c4f91e | |
Dan Ballard | 43bf1c19ae | |
Sarah Jamie Lewis | 28603b6fed | |
Dan Ballard | 0a9583e54e | |
Sarah Jamie Lewis | c606002541 | |
erinn | 2abdcdeae0 | |
erinn | 5c8d448a37 | |
erinn | 2a97616382 | |
erinn | 97f1e43009 | |
Sarah Jamie Lewis | 9dd9fa98fc | |
erinn | 37996b5d96 | |
erinn | 9c69275fe6 | |
Sarah Jamie Lewis | 421d34e1a7 | |
Sarah Jamie Lewis | 0d56c5a3bd | |
Sarah Jamie Lewis | b3dd5e2fee | |
Dan Ballard | 2df34a0192 | |
Dan Ballard | 0e8efc0b6b | |
Dan Ballard | 27f8dd289e | |
Sarah Jamie Lewis | aa0a1d9a26 | |
Sarah Jamie Lewis | a4c7fe6ebf | |
Sarah Jamie Lewis | b0f4a085b8 | |
Dan Ballard | 332fad4108 | |
Dan Ballard | 403eb04124 | |
Dan Ballard | 22eb994c52 | |
Sarah Jamie Lewis | 2c4e65e348 | |
Dan Ballard | f04404386c | |
Sarah Jamie Lewis | 1bf7438a26 | |
Sarah Jamie Lewis | 3f4c313d73 | |
Sarah Jamie Lewis | d27d9b6741 | |
Sarah Jamie Lewis | b2cee259cb | |
Dan Ballard | edb6ca4e94 | |
Sarah Jamie Lewis | dcefa30580 | |
Dan Ballard | a41e6f12b0 | |
Sarah Jamie Lewis | f4f885fcea | |
Sarah Jamie Lewis | 7351bd322a | |
Dan Ballard | 0858e4fd2b | |
Sarah Jamie Lewis | d45dedc76b | |
Sarah Jamie Lewis | 70a049e6fe | |
Dan Ballard | dbdb62b8e2 | |
Dan Ballard | c6c9899a78 | |
Dan Ballard | c37bca5f34 | |
Sarah Jamie Lewis | 1eaec8b00a | |
erinn | 89507a8aa6 | |
erinn | 6972028ecf | |
erinn | 8a69ae9c1c | |
Sarah Jamie Lewis | 01819067f3 | |
erinn | 9261119009 | |
Sarah Jamie Lewis | d7148919ac | |
Sarah Jamie Lewis | 0ead306278 | |
Sarah Jamie Lewis | ff8dc30f0a | |
Sarah Jamie Lewis | 05c80a82d6 | |
Sarah Jamie Lewis | e98ca88359 | |
Sarah Jamie Lewis | ce6cc4c8b7 | |
Dan Ballard | 3e27be0b45 | |
Dan Ballard | 0a2b6b1773 | |
Dan Ballard | 405280f075 | |
Dan Ballard | ceaa72205c | |
Sarah Jamie Lewis | cd3af06d3d | |
Dan Ballard | 1e3756daec | |
Dan Ballard | 6428882ce2 | |
Dan Ballard | faeb0b8cac | |
Dan Ballard | 9aee5237ca | |
Sarah Jamie Lewis | ae8e92c87d | |
Sarah Jamie Lewis | c19efbb3d2 | |
Sarah Jamie Lewis | 0500773b6c | |
Sarah Jamie Lewis | 7f33dc8502 | |
Dan Ballard | 6c60a93958 | |
erinn | ee303f9d11 | |
erinn | 599993d2d2 | |
erinn | 3b35b35bce | |
Sarah Jamie Lewis | 6a01e94846 | |
erinn | dfe5f500c6 | |
erinn | a99a00de30 |
242
.drone.yml
|
@ -1,174 +1,3 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: linux-android-test
|
|
||||||
|
|
||||||
clone:
|
|
||||||
disable: true
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: clone
|
|
||||||
image: cirrusci/flutter:dev
|
|
||||||
environment:
|
|
||||||
buildbot_key_b64:
|
|
||||||
from_secret: buildbot_key_b64
|
|
||||||
commands:
|
|
||||||
- mkdir ~/.ssh
|
|
||||||
- echo $buildbot_key_b64 > ~/.ssh/id_rsa.b64
|
|
||||||
- base64 -d ~/.ssh/id_rsa.b64 > ~/.ssh/id_rsa
|
|
||||||
- chmod 400 ~/.ssh/id_rsa
|
|
||||||
# force by pass of ssh host key check, less secure
|
|
||||||
- ssh-keyscan -H git.openprivacy.ca >> ~/.ssh/known_hosts
|
|
||||||
- git clone gogs@git.openprivacy.ca:flutter/flutter_app.git .
|
|
||||||
- git checkout $DRONE_COMMIT
|
|
||||||
|
|
||||||
- name: fetch
|
|
||||||
image: cirrusci/flutter:dev
|
|
||||||
volumes:
|
|
||||||
- name: deps
|
|
||||||
path: /root/.pub-cache
|
|
||||||
commands:
|
|
||||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
|
|
||||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
|
|
||||||
- chmod a+x tor
|
|
||||||
- echo `git describe --tags` > VERSION
|
|
||||||
- echo `date +%G-%m-%d-%H-%M` > BUILDDATE
|
|
||||||
- flutter pub get
|
|
||||||
- mkdir deploy
|
|
||||||
- ./fetch-libcwtch-go.sh
|
|
||||||
|
|
||||||
#- name: quality
|
|
||||||
# image: golang
|
|
||||||
# volumes:
|
|
||||||
# - name: deps
|
|
||||||
# path: /go
|
|
||||||
# commands:
|
|
||||||
# - go list ./... | xargs go vet
|
|
||||||
# - go list ./... | xargs golint
|
|
||||||
# #Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting
|
|
||||||
|
|
||||||
- name: build-linux
|
|
||||||
image: openpriv/flutter-desktop:linux-dev
|
|
||||||
volumes:
|
|
||||||
- name: deps
|
|
||||||
path: /root/.pub-cache
|
|
||||||
commands:
|
|
||||||
- flutter build linux --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE`
|
|
||||||
- mkdir deploy/linux
|
|
||||||
- cp -r build/linux/x64/release/bundle/* deploy/linux
|
|
||||||
- cp linux/cwtch.desktop deploy/linux
|
|
||||||
- cp linux/cwtch.png deploy/linux
|
|
||||||
- cp linux/libCwtch.so deploy/linux/lib/
|
|
||||||
- cp /sdks/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat deploy/linux
|
|
||||||
- cp tor deploy/linux
|
|
||||||
- cd deploy
|
|
||||||
- mv linux cwtch
|
|
||||||
- tar -czf cwtch-`cat ../VERSION`.tar.gz cwtch
|
|
||||||
- rm -r cwtch
|
|
||||||
|
|
||||||
- name: build-android
|
|
||||||
image: cirrusci/flutter:dev
|
|
||||||
when:
|
|
||||||
event: push
|
|
||||||
environment:
|
|
||||||
upload_jks_file_b64:
|
|
||||||
from_secret: upload_jks_file_b64
|
|
||||||
upload_jks_pass:
|
|
||||||
from_secret: upload_jks_pass
|
|
||||||
volumes:
|
|
||||||
- name: deps
|
|
||||||
path: /root/.pub-cache
|
|
||||||
commands:
|
|
||||||
- echo $upload_jks_file_b64 > upload-keystore.jks.b64
|
|
||||||
- base64 -i --decode upload-keystore.jks.b64 > android/app/upload-keystore.jks
|
|
||||||
- sed -i "s/%jks-password%/$upload_jks_pass/g" android/key.properties
|
|
||||||
- flutter build appbundle --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE`
|
|
||||||
# cant do debug for final release, this is just a stop gap
|
|
||||||
- flutter build apk --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE`
|
|
||||||
# or build apk --split-per-abi ?
|
|
||||||
- cp build/app/outputs/bundle/release/app-release.aab deploy/
|
|
||||||
- cp build/app/outputs/apk/release/app-release.apk deploy/
|
|
||||||
#- cp build/app/outputs/flutter-apk/app-debug.apk deploy/android
|
|
||||||
|
|
||||||
- name: widget-tests
|
|
||||||
image: cirrusci/flutter:dev
|
|
||||||
volumes:
|
|
||||||
- name: deps
|
|
||||||
path: /root/.pub-cache
|
|
||||||
commands:
|
|
||||||
# - flutter config --enable-linux-desktop
|
|
||||||
- flutter test --coverage
|
|
||||||
- genhtml coverage/lcov.info -o coverage/html
|
|
||||||
|
|
||||||
# Todo: gonna need more work on container
|
|
||||||
# https://flutter.dev/desktop
|
|
||||||
# requirements: Visual Studio 2019 (not to be confused with Visual Studio Code) with the “Desktop development with C++” workload installed, including all of its default components
|
|
||||||
#- name: build-windows
|
|
||||||
# image: cirrusci/flutter:dev
|
|
||||||
#- volumes:
|
|
||||||
# - name: deps
|
|
||||||
# path: /root/.pub-cache
|
|
||||||
# commands:
|
|
||||||
# - flutter config --enable-windows-desktop
|
|
||||||
# - flutter build windows
|
|
||||||
|
|
||||||
- name: deploy-buildfiles
|
|
||||||
image: kroniak/ssh-client
|
|
||||||
environment:
|
|
||||||
BUILDFILES_KEY:
|
|
||||||
from_secret: buildfiles_key
|
|
||||||
secrets: [gogs_account_token]
|
|
||||||
when:
|
|
||||||
event: push
|
|
||||||
status: [ success ]
|
|
||||||
commands:
|
|
||||||
- echo $BUILDFILES_KEY > ~/id_rsab64
|
|
||||||
- base64 -d ~/id_rsab64 > ~/id_rsa
|
|
||||||
- chmod 400 ~/id_rsa
|
|
||||||
- export DIR=flwtch-`cat VERSION`-`cat BUILDDATE`
|
|
||||||
- mv deploy $DIR
|
|
||||||
- cp -r coverage/html $DIR/coverage-tests
|
|
||||||
- cp -r test/failures $DIR/test-failures || true
|
|
||||||
- cd $DIR
|
|
||||||
- find . -type f -exec sha256sum {} \; > ./../sha256s.txt
|
|
||||||
- mv ./../sha256s.txt .
|
|
||||||
- cd ..
|
|
||||||
# TODO: do deployment once files actaully compile
|
|
||||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@openprivacy.ca:/home/buildfiles/buildfiles/
|
|
||||||
|
|
||||||
- name: notify-email
|
|
||||||
image: drillster/drone-email
|
|
||||||
settings:
|
|
||||||
host: build.openprivacy.ca
|
|
||||||
port: 25
|
|
||||||
skip_verify: true
|
|
||||||
from: drone@openprivacy.ca
|
|
||||||
when:
|
|
||||||
status: [ failure ]
|
|
||||||
|
|
||||||
- name: notify-gogs
|
|
||||||
image: openpriv/drone-gogs
|
|
||||||
when:
|
|
||||||
event: pull_request
|
|
||||||
status: [ success, changed, failure ]
|
|
||||||
environment:
|
|
||||||
GOGS_ACCOUNT_TOKEN:
|
|
||||||
from_secret: gogs_account_token
|
|
||||||
settings:
|
|
||||||
gogs_url: https://git.openprivacy.ca
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: deps
|
|
||||||
temp: {}
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
repo: flutter/flutter_app
|
|
||||||
branch: trunk
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: windows
|
name: windows
|
||||||
|
@ -203,45 +32,68 @@ steps:
|
||||||
- name: fetch
|
- name: fetch
|
||||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||||
commands:
|
commands:
|
||||||
- powershell -command "Invoke-WebRequest -Uri https://www.torproject.org/dist/torbrowser/10.0.16/tor-win32-0.4.5.7.zip -OutFile tor.zip"
|
- powershell -command "Invoke-WebRequest -Uri https://dist.torproject.org/torbrowser/10.0.18/tor-win64-0.4.5.9.zip -OutFile tor.zip"
|
||||||
- powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '2b7d683f036d0fec149f1d2bdfcf5b7ef4c337005a2b685c056b00047fdb2b57d4c25b8559ad7ef5c7a030b273934be82a9f83ef6e391f5d7d13d8d6c83e8048' ) { Write-Error 'tor.zip sha512sum mismatch' }"
|
- powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '72764eb07ad8ab511603aba0734951ca003989f5f4686af91ba220217b4a8a4bcc5f571b59f52c847932f6efedf847b111621983050fcddbb8099d43ca66fb07' ) { Write-Error 'tor.zip sha512sum mismatch' }"
|
||||||
- git describe --tags > VERSION
|
- git describe --tags > VERSION
|
||||||
- powershell -command "Get-Date -Format 'yyyy-MM-dd-HH-mm'" > BUILDDATE
|
- powershell -command "Get-Date -Format 'yyyy-MM-dd-HH-mm'" > BUILDDATE
|
||||||
- .\fetch-libcwtch-go.ps1
|
- .\fetch-libcwtch-go.ps1
|
||||||
-
|
|
||||||
- name: build-windows
|
- name: build-windows
|
||||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||||
commands:
|
commands:
|
||||||
- flutter pub get
|
- flutter pub get
|
||||||
# flwtch-`cat VERSION`-`cat BUILDDATE`
|
|
||||||
- $Env:buildname = 'flwtch-win-'
|
|
||||||
- $Env:version += type .\VERSION
|
- $Env:version += type .\VERSION
|
||||||
- $Env:buildname += $Env:version
|
|
||||||
- $Env:buildname += '-'
|
|
||||||
- $Env:builddate += type .\BUILDDATE
|
- $Env:builddate += type .\BUILDDATE
|
||||||
- $Env:buildname += $Env:builddate
|
- $Env:releasedir = "build\\windows\\runner\\Release\\"
|
||||||
- $Env:builddir += $Env:buildname
|
|
||||||
- $Env:zip = 'cwtch-'
|
|
||||||
- $Env:zip += $Env:version
|
|
||||||
- $Env:zip += '.zip'
|
|
||||||
- $Env:sha = $Env:zip
|
|
||||||
- $Env:sha += '.sha512'
|
|
||||||
- flutter build windows --dart-define BUILD_VER=$Env:version --dart-define BUILD_DATE=$Env:builddate
|
- flutter build windows --dart-define BUILD_VER=$Env:version --dart-define BUILD_DATE=$Env:builddate
|
||||||
|
- copy windows\libCwtch.dll $Env:releasedir
|
||||||
|
# flutter hasn't worked out it's packaging of required dll's so we have to resort to this manual nonsense
|
||||||
|
# https://github.com/google/flutter-desktop-embedding/issues/587
|
||||||
|
# https://github.com/flutter/flutter/issues/53167
|
||||||
|
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\vcruntime140.dll $Env:releasedir
|
||||||
|
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\vcruntime140_1.dll $Env:releasedir
|
||||||
|
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\msvcp140.dll $Env:releasedir
|
||||||
|
- powershell -command "Expand-Archive -Path tor.zip -DestinationPath $Env:releasedir\Tor"
|
||||||
|
|
||||||
|
- name: package-windows
|
||||||
|
image: openpriv/nsis:latest
|
||||||
|
environment:
|
||||||
|
pfx:
|
||||||
|
from_secret: pfx
|
||||||
|
pfx_pass:
|
||||||
|
from_secret: pfx_pass
|
||||||
|
commands:
|
||||||
|
- $Env:version += type .\VERSION
|
||||||
|
- $Env:builddate += type .\BUILDDATE
|
||||||
|
- $Env:releasedir = "build\\windows\\runner\\Release\\"
|
||||||
|
- $Env:zip = 'cwtch-' + $Env:version + '.zip'
|
||||||
|
- $Env:zipsha = $Env:zip + '.sha512'
|
||||||
|
- $Env:msix = 'cwtch-install-' + $Env:version + '.msix'
|
||||||
|
- $Env:msixsha = $Env:msix + '.sha512'
|
||||||
|
- $Env:buildname = 'flwtch-win-' + $Env:version + '-' + $Env:builddate
|
||||||
|
- $Env:builddir = $Env:buildname
|
||||||
|
- echo $Env:pfx > codesign.pfx.b64
|
||||||
|
- certutil -decode codesign.pfx.b64 codesign.pfx
|
||||||
|
- signtool sign /v /fd sha256 /a /f codesign.pfx /p $Env:pfx_pass /tr http://timestamp.digicert.com $Env:releasedir\cwtch.exe
|
||||||
|
- copy windows\runner\resources\knot_128.ico $Env:releasedir\cwtch.ico
|
||||||
|
- makensis windows\nsis\cwtch-installer.nsi
|
||||||
|
- move windows\nsis\cwtch-installer.exe cwtch-installer.exe
|
||||||
|
- signtool sign /v /fd sha256 /a /f codesign.pfx /p $Env:pfx_pass /tr http://timestamp.digicert.com cwtch-installer.exe
|
||||||
|
- powershell -command "(Get-FileHash cwtch-installer.exe -Algorithm sha512).Hash" > cwtch-installer.sha512
|
||||||
- mkdir deploy
|
- mkdir deploy
|
||||||
- move build\\windows\\runner\\Release $Env:builddir
|
|
||||||
- copy windows\libCwtch.dll $Env:builddir
|
|
||||||
- powershell -command "Expand-Archive -Path tor.zip -DestinationPath $Env:builddir\Tor"
|
|
||||||
- powershell -command "Compress-Archive -Path $Env:builddir -DestinationPath $Env:zip"
|
|
||||||
- powershell -command "(Get-FileHash *.zip -Algorithm sha512).Hash" > $Env:sha
|
|
||||||
- mkdir deploy\$Env:builddir
|
- mkdir deploy\$Env:builddir
|
||||||
- move $Env:zip deploy\$Env:builddir
|
- move $Env:releasedir $Env:builddir
|
||||||
- move $Env:sha deploy\$Env:builddir
|
- powershell -command "Compress-Archive -Path $Env:builddir -DestinationPath cwtch.zip"
|
||||||
|
- powershell -command "(Get-FileHash cwtch.zip -Algorithm sha512).Hash" > $Env:zipsha
|
||||||
|
- move cwtch-installer.exe deploy\$Env:builddir\cwtch-installer.exe
|
||||||
|
- move cwtch.zip deploy\$Env:builddir\$Env:zip
|
||||||
|
- move *.sha512 deploy\$Env:builddir
|
||||||
|
|
||||||
- name: deploy-windows
|
- name: deploy-windows
|
||||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||||
when:
|
when:
|
||||||
event: push
|
event: push
|
||||||
status: [ success ]
|
status: [ success ]
|
||||||
environment:
|
environment:
|
||||||
BUILDFILES_KEY:
|
BUILDFILES_KEY:
|
||||||
from_secret: buildfiles_key
|
from_secret: buildfiles_key
|
||||||
|
@ -254,4 +106,4 @@ trigger:
|
||||||
repo: flutter/flutter_app
|
repo: flutter/flutter_app
|
||||||
branch: trunk
|
branch: trunk
|
||||||
event:
|
event:
|
||||||
- push
|
- push
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
v0.0.2-45-g4f625c7-2021-05-31-23-30
|
v0.0.2-108-g3964348-2021-06-24-17-42
|
|
@ -0,0 +1,9 @@
|
||||||
|
MIT License
|
||||||
|
Copyright (c) 2021 Open Privacy Research Society
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
41
README.md
|
@ -1,6 +1,6 @@
|
||||||
# flwtch
|
# Cwtch UI
|
||||||
|
|
||||||
A new Flutter application.
|
A Flutter based Cwtch UI
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
|
@ -8,13 +8,19 @@ click the play button in android studio
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
- libCwtch-go: the result of `make linux`, `libCwtch.so` should be in the link path
|
- libCwtch-go: required to be on the link path (linux/cwtch.destktop demonstrates with `env LD_LIBRARY_PATH=./lib/` on the front of the comman)
|
||||||
|
- fetch-libcwtch-go.sh will fetch a prebuilt version
|
||||||
|
- or compile from source from libcwtch-go with `make linux`
|
||||||
- `tor` should be in the PATH
|
- `tor` should be in the PATH
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
- libCwtch-go: the result of `make windows`, `libCwtch.dll` should be placed in the source root
|
- run `fetch-libcwtch-go.ps1` to get `libCwtch.dll` which is required to run
|
||||||
- tor is bundled in `windors/Tor`
|
- run `fetch-tor-win.ps1` to fetch Tor for windows
|
||||||
|
|
||||||
|
#### Issues
|
||||||
|
|
||||||
|
- Flutter engine has a [known bug](https://github.com/flutter/flutter/issues/75675) around the Right Shift key being sticky. We have implemented the mostly work around, but until it is fixed, right shift occasionally acts permenent. If this happens, just tap left shift and it will reset
|
||||||
|
|
||||||
## l10n
|
## l10n
|
||||||
|
|
||||||
|
@ -26,14 +32,24 @@ After adding a new key and providing/obtaining translations for it, follow the n
|
||||||
|
|
||||||
### Updating translations
|
### Updating translations
|
||||||
|
|
||||||
Only Open Privacy staff members can update translations automatically:
|
Only Open Privacy staff members can update translations.
|
||||||
|
|
||||||
```
|
In Lokalise, hit Download and make sure:
|
||||||
flutter pub run flutter_lokalise download -v --api-token "<X>" --project-id "<Y>"
|
|
||||||
```
|
|
||||||
|
|
||||||
This will download a bundle of translations from Lokalise and convert it to resource files in `lib/l10n/intl_*.arb`.
|
* Format is set to "Flutter (.arb)
|
||||||
The next time Flwtch is built, Flutter will notice the changes and update `app_localizations.dart` accordingly (thanks to `generate:true` in `pubspec.yaml`).
|
* Output filename is set to `l10n/intl_%LANG_ISO%.%FORMAT%`
|
||||||
|
* Empty translations is set to "Replace with base language"
|
||||||
|
|
||||||
|
Build, download and unzip the output, overwriting `lib/l10n`. The next time Flwtch is built, Flutter will notice the changes and update `app_localizations.dart` accordingly (thanks to `generate:true` in `pubspec.yaml`).
|
||||||
|
|
||||||
|
### Adding a language
|
||||||
|
|
||||||
|
If a new language has been added to the Lokalise project, two additional manual steps need to be done:
|
||||||
|
|
||||||
|
* Create a new key called `localeXX` for the name of the language
|
||||||
|
* Add it to the settings pane by updating `getLanguageFull()` in `lib/views/globalsettingsview.dart`
|
||||||
|
|
||||||
|
Then rebuild as normal.
|
||||||
|
|
||||||
### Using a string
|
### Using a string
|
||||||
|
|
||||||
|
@ -51,6 +67,5 @@ Text(AppLocalizations.of(context)!.stringIdentifer),
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
API tokens are only available to Open Privacy staff at this time, who will perform the translation updates for you as part of merging your PRs.
|
|
||||||
|
|
||||||
With `generate: true` in `pubspec.yaml`, the Flutter build process checks `l10n.yaml` for input/output filenames.
|
With `generate: true` in `pubspec.yaml`, the Flutter build process checks `l10n.yaml` for input/output filenames.
|
||||||
|
|
||||||
|
|
50
SPEC.md
|
@ -8,7 +8,7 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
||||||
|
|
||||||
# Functional Requirements
|
# Functional Requirements
|
||||||
- [ ] Kill all processes / isolates on exit (Blocked - P1)
|
- [ ] Kill all processes / isolates on exit (Blocked - P1)
|
||||||
- [ ] Android Service? (P1)
|
- [X] Android Service? (P1)
|
||||||
|
|
||||||
# Splash Screen
|
# Splash Screen
|
||||||
- [X] Android
|
- [X] Android
|
||||||
|
@ -16,9 +16,9 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
||||||
- [ ] Desktop (P2)
|
- [ ] Desktop (P2)
|
||||||
|
|
||||||
# Custom Styled Widgets
|
# Custom Styled Widgets
|
||||||
- [/] Label Widget
|
- [X] Label Widget
|
||||||
- [X] Initial
|
- [X] Initial
|
||||||
- [ ] With Accessibility / Zoom Integration (P1)
|
- [X] With Accessibility / Zoom Integration (P1)
|
||||||
- [X] Text Field Widget
|
- [X] Text Field Widget
|
||||||
- [X] Password Widget
|
- [X] Password Widget
|
||||||
- [X] Text Button Widget (for Copy)
|
- [X] Text Button Widget (for Copy)
|
||||||
|
@ -33,10 +33,10 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
||||||
- [X] Profile Picture
|
- [X] Profile Picture
|
||||||
- [X] default images
|
- [X] default images
|
||||||
- [ ] custom images (P3)
|
- [ ] custom images (P3)
|
||||||
- [ ] coloured ring border (P2)
|
- [X] coloured ring border (P2)
|
||||||
- [X] Profile Name
|
- [X] Profile Name
|
||||||
- [X] Edit Button
|
- [X] Edit Button
|
||||||
- [ ] Unread messages badge (P2)
|
- [X Unread messages badge (P2)
|
||||||
- [X] Navigate to a specific Profile Contacts Pane (when clicking on a Profile row)
|
- [X] Navigate to a specific Profile Contacts Pane (when clicking on a Profile row)
|
||||||
- [X] Navigate to a specific Profile Management Pane (edit Button)
|
- [X] Navigate to a specific Profile Management Pane (edit Button)
|
||||||
- [X] Navigate to the Settings Pane (Settings Button in Action bar)
|
- [X] Navigate to the Settings Pane (Settings Button in Action bar)
|
||||||
|
@ -54,12 +54,12 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
||||||
|
|
||||||
- [X] Update Profile Name
|
- [X] Update Profile Name
|
||||||
- [X] Update Profile Password
|
- [X] Update Profile Password
|
||||||
- [ ] Error Message When Attempting to Update Password with Wrong Old Password (P2)
|
- [X] Error Message When Attempting to Update Password with Wrong Old Password (P2)
|
||||||
- [ ] Easy Transition from Unencrypted Profile -> Encrypted Profile (P3)
|
- [ ] Easy Transition from Unencrypted Profile -> Encrypted Profile (P3)
|
||||||
- [ ] Delete a Profile (P2)
|
- [X] Delete a Profile (P2)
|
||||||
- [ ] Dialog Acknowledgement (P2)
|
- [X] Dialog Acknowledgement (P2)
|
||||||
- [ ] Require Old Password Gate (P2)
|
- [X] Require Old Password Gate (P2)
|
||||||
- [ ] Async Checking of Password (P2)
|
- [X] Async Checking of Password (P2)
|
||||||
- [X] Copy Profile Onion Address
|
- [X] Copy Profile Onion Address
|
||||||
|
|
||||||
## Profile Pane (formally Contacts Pane)
|
## Profile Pane (formally Contacts Pane)
|
||||||
|
@ -76,11 +76,11 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
||||||
- [X] Name
|
- [X] Name
|
||||||
- [X] Onion
|
- [X] Onion
|
||||||
- [X] Online Status
|
- [X] Online Status
|
||||||
- [ ] Unread Messages Badge (P1)
|
- [X] Unread Messages Badge (P1)
|
||||||
- [ ] In Order of Most Recent Message / Activity (P1)
|
- [X] In Order of Most Recent Message / Activity (P1)
|
||||||
- [ ] With Accept / Reject Heart/Trash Bin Option (P1)
|
- [X] With Accept / Reject Heart/Trash Bin Option (P1)
|
||||||
- [ ] Separate list area for Blocked Contacts (P1)
|
- [X] Separate list area for Blocked Contacts (P1)
|
||||||
- [ ] Display all Group Contacts (if experiment is enabled)
|
- [X] Display all Group Contacts (if experiment is enabled)
|
||||||
- [X] Navigate to a specific Contact or Group Message Pane (Contact Row)
|
- [X] Navigate to a specific Contact or Group Message Pane (Contact Row)
|
||||||
- [X] Pressing Back should go back to the home pane
|
- [X] Pressing Back should go back to the home pane
|
||||||
|
|
||||||
|
@ -88,22 +88,22 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
||||||
- [X] Allowing Copying the Profile Onion Address for Sharing
|
- [X] Allowing Copying the Profile Onion Address for Sharing
|
||||||
- [X] Allowing Pasting a Peer Onion Address for adding to Contacts
|
- [X] Allowing Pasting a Peer Onion Address for adding to Contacts
|
||||||
- [ ] (with optional name field)
|
- [ ] (with optional name field)
|
||||||
- [ ] Allowing Pasting a Group Invite / Server Address
|
- [X] Allowing Pasting a Group Invite / Server Address
|
||||||
- [X] (if group experiment is enabled)
|
- [X] (if group experiment is enabled)
|
||||||
|
|
||||||
## Message Overlay
|
## Message Overlay
|
||||||
|
|
||||||
- [X] Display Messages from Contacts
|
- [X] Display Messages from Contacts
|
||||||
- [ ] Allowing copying the text of a specific message (on mobile) (P2)
|
- [X] Allowing copying the text of a specific message (on mobile) (P2)
|
||||||
- [X] Send a message to the specific Contact / Group
|
- [X] Send a message to the specific Contact / Group
|
||||||
- [~] Display the Acknowledgement status of a message (P1)
|
- [~] Display the Acknowledgement status of a message (P1)
|
||||||
- [ ] Navigate to the specific Contact or Group Settings Pane ( Settings Button in Action bar)
|
- [X] Navigate to the specific Contact or Group Settings Pane ( Settings Button in Action bar)
|
||||||
- [ ] Emoji Support (P1)
|
- [ ] Emoji Support (P1)
|
||||||
- [ ] Display in-message emoji text labels e.g. `:label:` as emoji. (P1)
|
- [ ] Display in-message emoji text labels e.g. `:label:` as emoji. (P1)
|
||||||
- [ ] Functional Emoji Drawer Widget for Selection (P2)
|
- [ ] Functional Emoji Drawer Widget for Selection (P2)
|
||||||
- [ ] Mutant Standard? (P2)
|
- [ ] Mutant Standard? (P2)
|
||||||
- [ ] Display a warning if Contact / Server is offline (Broken Heart) (P1)
|
- [X] Display a warning if Contact / Server is offline (Broken Heart) (P1)
|
||||||
- [ ] Display a warning for configuring peer history (P2)
|
- [X] Display a warning for configuring peer history (P2)
|
||||||
- [X] Pressing Back should go back to the contacts pane
|
- [X] Pressing Back should go back to the contacts pane
|
||||||
|
|
||||||
## List Overlay (P3)
|
## List Overlay (P3)
|
||||||
|
@ -123,11 +123,11 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
||||||
- [X] Pressing Back should go back to the message pane
|
- [X] Pressing Back should go back to the message pane
|
||||||
|
|
||||||
## Group Settings Pane (experimental - P3)
|
## Group Settings Pane (experimental - P3)
|
||||||
- [ ] Gated behind group experiment
|
- [X] Gated behind group experiment
|
||||||
- [ ] Update local name of group
|
- [X] Update local name of group
|
||||||
- [ ] Get Group Invite
|
- [X] Get Group Invite
|
||||||
- [ ] Leave Group
|
- [X] Leave Group
|
||||||
- [ ] Pressing Back should go back to the message pane for the group
|
- [X] Pressing Back should go back to the message pane for the group
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,15 @@ android {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
|
@ -82,4 +91,30 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
|
||||||
implementation "com.airbnb.android:lottie:3.5.0"
|
implementation "com.airbnb.android:lottie:3.5.0"
|
||||||
implementation "com.android.support.constraint:constraint-layout:2.0.4"
|
implementation "com.android.support.constraint:constraint-layout:2.0.4"
|
||||||
|
|
||||||
|
// WorkManager
|
||||||
|
|
||||||
|
// (Java only)
|
||||||
|
//implementation("androidx.work:work-runtime:$work_version")
|
||||||
|
|
||||||
|
// Kotlin + coroutines
|
||||||
|
implementation("androidx.work:work-runtime-ktx:2.5.0")
|
||||||
|
|
||||||
|
// optional - RxJava2 support
|
||||||
|
//implementation("androidx.work:work-rxjava2:$work_version")
|
||||||
|
|
||||||
|
// optional - GCMNetworkManager support
|
||||||
|
//implementation("androidx.work:work-gcm:$work_version")
|
||||||
|
|
||||||
|
// optional - Test helpers
|
||||||
|
//androidTestImplementation("androidx.work:work-testing:$work_version")
|
||||||
|
|
||||||
|
// optional - Multiprocess support
|
||||||
|
implementation "androidx.work:work-multiprocess:2.5.0"
|
||||||
|
|
||||||
|
// end of workmanager deps
|
||||||
|
|
||||||
|
// needed to prevent a ListenableFuture dependency conflict/bug
|
||||||
|
// see https://github.com/google/ExoPlayer/issues/7905#issuecomment-692637059
|
||||||
|
implementation 'com.google.guava:guava:any'
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,4 +43,9 @@
|
||||||
<!--Needed to access Tor socket-->
|
<!--Needed to access Tor socket-->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<!--Needed to run in background (lol)-->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
|
<!--Meeded to check if activity is foregrounded or if messages from the service should be queued-->
|
||||||
|
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
package im.cwtch.flwtch
|
||||||
|
|
||||||
|
import android.app.*
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import androidx.work.*
|
||||||
|
import cwtch.Cwtch
|
||||||
|
import io.flutter.FlutterInjector
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
||||||
|
class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
|
CoroutineWorker(context, parameters) {
|
||||||
|
private val notificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as
|
||||||
|
NotificationManager
|
||||||
|
|
||||||
|
private var notificationID: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
private var notificationIDnext: Int = 1
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val method = inputData.getString(KEY_METHOD)
|
||||||
|
?: return Result.failure()
|
||||||
|
val args = inputData.getString(KEY_ARGS)
|
||||||
|
?: return Result.failure()
|
||||||
|
// Mark the Worker as important
|
||||||
|
val progress = "Cwtch is keeping Tor running in the background"//todo:translate
|
||||||
|
setForeground(createForegroundInfo(progress))
|
||||||
|
return handleCwtch(method, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNotificationID(profile: String, contact: String): Int {
|
||||||
|
val k = "$profile $contact"
|
||||||
|
if (!notificationID.containsKey(k)) {
|
||||||
|
notificationID[k] = notificationIDnext++
|
||||||
|
}
|
||||||
|
return notificationID[k] ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCwtch(method: String, args: String): Result {
|
||||||
|
val a = JSONObject(args)
|
||||||
|
when (method) {
|
||||||
|
"Start" -> {
|
||||||
|
Log.i("FlwtchWorker.kt", "handleAppInfo Start")
|
||||||
|
val appDir = (a.get("appDir") as? String) ?: ""
|
||||||
|
val torPath = (a.get("torPath") as? String) ?: "tor"
|
||||||
|
Log.i("FlwtchWorker.kt", "appDir: '$appDir' torPath: '$torPath'")
|
||||||
|
|
||||||
|
if (Cwtch.startCwtch(appDir, torPath) != 0.toLong()) return Result.failure()
|
||||||
|
|
||||||
|
Log.i("FlwtchWorker.kt", "startCwtch success, starting coroutine AppbusEvent loop...")
|
||||||
|
while(true) {
|
||||||
|
Log.i("FlwtchWorker.kt", "while(true)getAppbusEvent()")
|
||||||
|
val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent())
|
||||||
|
if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") {
|
||||||
|
val data = JSONObject(evt.Data)
|
||||||
|
val channelId =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createMessageNotificationChannel(data.getString("RemotePeer"), data.getString("RemotePeer"))
|
||||||
|
} else {
|
||||||
|
// If earlier version channel ID is not used
|
||||||
|
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
val loader = FlutterInjector.instance().flutterLoader()
|
||||||
|
val key = loader.getLookupKeyForAsset("assets/"+data.getString("Picture"))//"assets/profiles/001-centaur.png")
|
||||||
|
val fh = applicationContext.assets.open(key)
|
||||||
|
|
||||||
|
val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent ->
|
||||||
|
intent.action = Intent.ACTION_RUN
|
||||||
|
intent.putExtra("EventType", "NotificationClicked")
|
||||||
|
intent.putExtra("ProfileOnion", data.getString("ProfileOnion"))
|
||||||
|
intent.putExtra("RemotePeer", if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
|
||||||
|
.setContentTitle(data.getString("Nick"))
|
||||||
|
.setContentText("New message")//todo: translate
|
||||||
|
.setLargeIcon(BitmapFactory.decodeStream(fh))
|
||||||
|
.setSmallIcon(R.mipmap.knott)
|
||||||
|
.setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.build()
|
||||||
|
notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), data.getString("RemotePeer")), newNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent().also { intent ->
|
||||||
|
intent.action = "im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS"
|
||||||
|
intent.putExtra("EventType", evt.EventType)
|
||||||
|
intent.putExtra("Data", evt.Data)
|
||||||
|
intent.putExtra("EventID", evt.EventID)
|
||||||
|
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"ReconnectCwtchForeground" -> {
|
||||||
|
Cwtch.reconnectCwtchForeground()
|
||||||
|
}
|
||||||
|
"CreateProfile" -> {
|
||||||
|
val nick = (a.get("nick") as? String) ?: ""
|
||||||
|
val pass = (a.get("pass") as? String) ?: ""
|
||||||
|
Cwtch.createProfile(nick, pass)
|
||||||
|
}
|
||||||
|
"LoadProfiles" -> {
|
||||||
|
val pass = (a.get("pass") as? String) ?: ""
|
||||||
|
Cwtch.loadProfiles(pass)
|
||||||
|
}
|
||||||
|
"GetMessage" -> {
|
||||||
|
val profile = (a.get("profile") as? String) ?: ""
|
||||||
|
val handle = (a.get("contact") as? String) ?: ""
|
||||||
|
val indexI = a.getInt("index")
|
||||||
|
Log.i("FlwtchWorker.kt", "indexI = $indexI")
|
||||||
|
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build())
|
||||||
|
}
|
||||||
|
"UpdateMessageFlags" -> {
|
||||||
|
val profile = (a.get("profile") as? String) ?: ""
|
||||||
|
val handle = (a.get("contact") as? String) ?: ""
|
||||||
|
val midx = (a.get("midx") as? Long) ?: 0
|
||||||
|
val flags = (a.get("flags") as? Long) ?: 0
|
||||||
|
Cwtch.updateMessageFlags(profile, handle, midx, flags)
|
||||||
|
}
|
||||||
|
"AcceptContact" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val handle = (a.get("handle") as? String) ?: ""
|
||||||
|
Cwtch.acceptContact(profile, handle)
|
||||||
|
}
|
||||||
|
"BlockContact" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val handle = (a.get("handle") as? String) ?: ""
|
||||||
|
Cwtch.blockContact(profile, handle)
|
||||||
|
}
|
||||||
|
"SendMessage" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val handle = (a.get("handle") as? String) ?: ""
|
||||||
|
val message = (a.get("message") as? String) ?: ""
|
||||||
|
Cwtch.sendMessage(profile, handle, message)
|
||||||
|
}
|
||||||
|
"SendInvitation" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val handle = (a.get("handle") as? String) ?: ""
|
||||||
|
val target = (a.get("target") as? String) ?: ""
|
||||||
|
Cwtch.sendInvitation(profile, handle, target)
|
||||||
|
}
|
||||||
|
"SendProfileEvent" -> {
|
||||||
|
val onion = (a.get("onion") as? String) ?: ""
|
||||||
|
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
|
||||||
|
Cwtch.sendProfileEvent(onion, jsonEvent)
|
||||||
|
}
|
||||||
|
"SendAppEvent" -> {
|
||||||
|
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
|
||||||
|
Cwtch.sendAppEvent(jsonEvent)
|
||||||
|
}
|
||||||
|
"ResetTor" -> {
|
||||||
|
Cwtch.resetTor()
|
||||||
|
}
|
||||||
|
"ImportBundle" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val bundle = (a.get("bundle") as? String) ?: ""
|
||||||
|
Cwtch.importBundle(profile, bundle)
|
||||||
|
}
|
||||||
|
"SetGroupAttribute" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val groupHandle = (a.get("groupHandle") as? String) ?: ""
|
||||||
|
val key = (a.get("key") as? String) ?: ""
|
||||||
|
val value = (a.get("value") as? String) ?: ""
|
||||||
|
Cwtch.setGroupAttribute(profile, groupHandle, key, value)
|
||||||
|
}
|
||||||
|
"CreateGroup" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val server = (a.get("server") as? String) ?: ""
|
||||||
|
val groupName = (a.get("groupname") as? String) ?: ""
|
||||||
|
Cwtch.createGroup(profile, server, groupName)
|
||||||
|
}
|
||||||
|
"DeleteProfile" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val pass = (a.get("pass") as? String) ?: ""
|
||||||
|
Cwtch.deleteProfile(profile, pass)
|
||||||
|
}
|
||||||
|
"LeaveConversation" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val contactHandle = (a.get("contactHandle") as? String) ?: ""
|
||||||
|
Cwtch.leaveConversation(profile, contactHandle)
|
||||||
|
}
|
||||||
|
"LeaveGroup" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val groupHandle = (a.get("groupHandle") as? String) ?: ""
|
||||||
|
Cwtch.leaveGroup(profile, groupHandle)
|
||||||
|
}
|
||||||
|
"RejectInvite" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val groupHandle = (a.get("groupHandle") as? String) ?: ""
|
||||||
|
Cwtch.rejectInvite(profile, groupHandle)
|
||||||
|
}
|
||||||
|
"Shutdown" -> {
|
||||||
|
Cwtch.shutdownCwtch();
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
else -> return Result.failure()
|
||||||
|
}
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates an instance of ForegroundInfo which can be used to update the
|
||||||
|
// ongoing notification.
|
||||||
|
private fun createForegroundInfo(progress: String): ForegroundInfo {
|
||||||
|
val id = "flwtch"
|
||||||
|
val title = "Flwtch"
|
||||||
|
val cancel = "Shut down"//todo: translate
|
||||||
|
val channelId =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createForegroundNotificationChannel(id, id)
|
||||||
|
} else {
|
||||||
|
// If earlier version channel ID is not used
|
||||||
|
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
// This PendingIntent can be used to cancel the worker
|
||||||
|
val intent = WorkManager.getInstance(applicationContext)
|
||||||
|
.createCancelPendingIntent(getId())
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(applicationContext, channelId)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setTicker(title)
|
||||||
|
.setContentText(progress)
|
||||||
|
.setSmallIcon(R.mipmap.knott)
|
||||||
|
.setOngoing(true)
|
||||||
|
// Add the cancel action to the notification which can
|
||||||
|
// be used to cancel the worker
|
||||||
|
.addAction(android.R.drawable.ic_delete, cancel, intent)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return ForegroundInfo(101, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createForegroundNotificationChannel(channelId: String, channelName: String): String{
|
||||||
|
val chan = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
|
||||||
|
chan.lightColor = Color.MAGENTA
|
||||||
|
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||||
|
notificationManager.createNotificationChannel(chan)
|
||||||
|
return channelId
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createMessageNotificationChannel(channelId: String, channelName: String): String{
|
||||||
|
val chan = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
|
||||||
|
chan.lightColor = Color.MAGENTA
|
||||||
|
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||||
|
notificationManager.createNotificationChannel(chan)
|
||||||
|
return channelId
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY_METHOD = "KEY_METHOD"
|
||||||
|
const val KEY_ARGS = "KEY_ARGS"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +1,29 @@
|
||||||
package im.cwtch.flwtch
|
package im.cwtch.flwtch
|
||||||
|
|
||||||
import SplashView
|
import SplashView
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.Window
|
||||||
import kotlinx.coroutines.Dispatchers
|
import androidx.lifecycle.Observer
|
||||||
import kotlinx.coroutines.GlobalScope
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import kotlinx.coroutines.launch
|
import androidx.work.*
|
||||||
|
|
||||||
import io.flutter.embedding.android.SplashScreen
|
import io.flutter.embedding.android.SplashScreen
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
|
||||||
import io.flutter.plugin.common.MethodChannel.Result
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
|
|
||||||
import cwtch.Cwtch
|
|
||||||
import io.flutter.plugin.common.EventChannel
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
|
|
||||||
override fun provideSplashScreen(): SplashScreen? = SplashView()
|
override fun provideSplashScreen(): SplashScreen? = SplashView()
|
||||||
|
|
||||||
// Channel to get app info
|
// Channel to get app info
|
||||||
|
@ -39,13 +36,37 @@ class MainActivity: FlutterActivity() {
|
||||||
// Channel to send eventbus events on
|
// Channel to send eventbus events on
|
||||||
private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus"
|
private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus"
|
||||||
|
|
||||||
|
// Channel to trigger contactview when an external notification is clicked
|
||||||
|
private val CHANNEL_NOTIF_CLICK = "im.cwtch.flwtch/notificationClickHandler"
|
||||||
|
|
||||||
|
// WorkManager tag applied to all Start() infinite coroutines
|
||||||
|
val WORKER_TAG = "cwtchEventBusWorker"
|
||||||
|
|
||||||
|
private var myReceiver: MyBroadcastReceiver? = null
|
||||||
|
private var methodChan: MethodChannel? = null
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
if (methodChan == null || intent.extras == null) return
|
||||||
|
if (!intent.extras!!.containsKey("ProfileOnion") || !intent.extras!!.containsKey("RemotePeer")) {
|
||||||
|
Log.i("onNewIntent", "got intent with no onions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val profile = intent.extras!!.getString("ProfileOnion")
|
||||||
|
val handle = intent.extras!!.getString("RemotePeer")
|
||||||
|
val mappo = mapOf("ProfileOnion" to profile, "RemotePeer" to handle)
|
||||||
|
val j = JSONObject(mappo)
|
||||||
|
methodChan!!.invokeMethod("NotificationClicked", j.toString())
|
||||||
|
}
|
||||||
|
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
// Note: this methods are invoked on the main thread.
|
// Note: this methods are invoked on the main thread.
|
||||||
|
//note to self: ask someone if this does anything ^ea
|
||||||
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_APP_INFO).setMethodCallHandler { call, result -> handleAppInfo(call, result) }
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_APP_INFO).setMethodCallHandler { call, result -> handleAppInfo(call, result) }
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_CWTCH).setMethodCallHandler { call, result -> handleCwtch(call, result) }
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_CWTCH).setMethodCallHandler { call, result -> handleCwtch(call, result) }
|
||||||
|
methodChan = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NOTIF_CLICK)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) {
|
private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||||
|
@ -63,139 +84,88 @@ class MainActivity: FlutterActivity() {
|
||||||
return ainfo.nativeLibraryDir
|
return ainfo.nativeLibraryDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// receives messages from the ForegroundService (which provides, ironically enough, the backend)
|
||||||
private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) {
|
private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||||
when (call.method) {
|
var method = call.method
|
||||||
"Start" -> {
|
val argmap: Map<String, String> = call.arguments as Map<String, String>
|
||||||
Log.i("MainActivity.kt", "handleAppInfo Start")
|
|
||||||
val appDir = (call.argument("appDir") as? String) ?: "";
|
|
||||||
val torPath = (call.argument("torPath") as? String) ?: "tor";
|
|
||||||
Log.i("MainActivity.kt", " appDir: '" + appDir + "' torPath: '" + torPath + "'")
|
|
||||||
Cwtch.startCwtch(appDir, torPath)
|
|
||||||
|
|
||||||
// seperate coroutine to poll event bus and send to dart
|
// the frontend calls Start every time it fires up, but we don't want to *actually* call Cwtch.Start()
|
||||||
val eventbus_chan = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
|
// in case the ForegroundService is still running. in both cases, however, we *do* want to re-register
|
||||||
Log.i("MainActivity.kt", "got event chan: " + eventbus_chan + " launching corouting...")
|
// the eventbus listener.
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
if (call.method == "Start") {
|
||||||
while(true) {
|
val uniqueTag = argmap["torPath"] ?: "nullEventBus"
|
||||||
val evt = AppbusEvent(Cwtch.getAppBusEvent())
|
|
||||||
Log.i("MainActivity.kt", "got appbusEvent: " + evt)
|
// note: because the ForegroundService is specified as UniquePeriodicWork, it can't actually get
|
||||||
launch(Dispatchers.Main) {
|
// accidentally duplicated. however, we still need to manually check if it's running or not, so
|
||||||
//todo: this elides evt.EventID which may be needed at some point?
|
// that we can divert this method call to ReconnectCwtchForeground instead if so.
|
||||||
eventbus_chan.invokeMethod(evt.EventType, evt.Data)
|
val works = WorkManager.getInstance(this).getWorkInfosByTag(WORKER_TAG).get()
|
||||||
}
|
for (workInfo in works) {
|
||||||
}
|
Log.i("handleCwtch:WorkManager", "$workInfo")
|
||||||
|
if (!workInfo.tags.contains(uniqueTag)) {
|
||||||
|
Log.i("handleCwtch:WorkManager", "canceling ${workInfo.id} bc tags don't include $uniqueTag")
|
||||||
|
WorkManager.getInstance(this).cancelWorkById(workInfo.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"SelectProfile" -> {
|
WorkManager.getInstance(this).pruneWork()
|
||||||
val onion = (call.argument("profile") as? String) ?: "";
|
|
||||||
Cwtch.selectProfile(onion)
|
|
||||||
}
|
|
||||||
"CreateProfile" -> {
|
|
||||||
val nick = (call.argument("nick") as? String) ?: "";
|
|
||||||
val pass = (call.argument("pass") as? String) ?: "";
|
|
||||||
Cwtch.createProfile(nick, pass)
|
|
||||||
}
|
|
||||||
"LoadProfiles" -> {
|
|
||||||
val pass = (call.argument("pass") as? String) ?: "";
|
|
||||||
Cwtch.loadProfiles(pass)
|
|
||||||
}
|
|
||||||
"GetProfiles" -> result.success(Cwtch.getProfiles())
|
|
||||||
// "ACNEvents" -> result.success(Cwtch.acnEvents())
|
|
||||||
"ContactEvents" -> result.success(Cwtch.contactEvents())
|
|
||||||
"NumMessages" -> {
|
|
||||||
val profile = (call.argument("profile") as? String) ?: "";
|
|
||||||
val handle = (call.argument("contact") as? String) ?: "";
|
|
||||||
result.success(Cwtch.numMessages(profile, handle))
|
|
||||||
}
|
|
||||||
"GetMessage" -> {
|
|
||||||
//Log.i("MainActivivity.kt", (call.argument("index")));
|
|
||||||
|
|
||||||
// var args : HashMap<String, dynamic> = call.arguments();
|
Log.i("MainActivity.kt", "Start() launching foregroundservice")
|
||||||
// Log.i("MainActivity.kt", args);
|
// this is where the eventbus ForegroundService gets launched. WorkManager should keep it alive after this
|
||||||
|
val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, call.method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build()
|
||||||
|
// 15 minutes is the shortest interval you can request
|
||||||
val profile = (call.argument("profile") as? String) ?: "";
|
val workRequest = PeriodicWorkRequestBuilder<FlwtchWorker>(15, TimeUnit.MINUTES).setInputData(data).addTag(WORKER_TAG).addTag(uniqueTag).build()
|
||||||
val handle = (call.argument("contact") as? String) ?: "";
|
WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.REPLACE, workRequest)
|
||||||
val indexI = call.argument<Int>("index") ?: 0;
|
return
|
||||||
Log.i("MainActivity.kt", "indexI = " + indexI)
|
|
||||||
result.success(Cwtch.getMessage(profile, handle, indexI.toLong()))
|
|
||||||
}
|
|
||||||
"GetMessages" -> {
|
|
||||||
val profile = (call.argument("profile") as? String) ?: "";
|
|
||||||
val handle = (call.argument("contact") as? String) ?: "";
|
|
||||||
val start = (call.argument("start") as? Long) ?: 0;
|
|
||||||
val end = (call.argument("end") as? Long) ?: 0;
|
|
||||||
result.success(Cwtch.getMessages(profile, handle, start, end))
|
|
||||||
}
|
|
||||||
"AcceptContact" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val handle = (call.argument("handle") as? String) ?: "";
|
|
||||||
Cwtch.acceptContact(profile, handle);
|
|
||||||
}
|
|
||||||
"BlockContact" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val handle = (call.argument("handle") as? String) ?: "";
|
|
||||||
Cwtch.blockContact(profile, handle);
|
|
||||||
}
|
|
||||||
"DebugResetContact" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val handle = (call.argument("handle") as? String) ?: "";
|
|
||||||
Cwtch.debugResetContact(profile, handle);
|
|
||||||
}
|
|
||||||
"SendMessage" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val handle = (call.argument("handle") as? String) ?: "";
|
|
||||||
val message = (call.argument("message") as? String) ?: "";
|
|
||||||
Cwtch.sendMessage(profile, handle, message);
|
|
||||||
}
|
|
||||||
"SendInvitation" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val handle = (call.argument("handle") as? String) ?: "";
|
|
||||||
val target = (call.argument("target") as? String) ?: "";
|
|
||||||
Cwtch.sendInvitation(profile, handle, target);
|
|
||||||
}
|
|
||||||
"SendProfileEvent" -> {
|
|
||||||
val onion = (call.argument("onion") as? String) ?: "";
|
|
||||||
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
|
|
||||||
Cwtch.sendProfileEvent(onion, jsonEvent);
|
|
||||||
}
|
|
||||||
"SendAppEvent" -> {
|
|
||||||
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
|
|
||||||
Cwtch.sendAppEvent(jsonEvent);
|
|
||||||
}
|
|
||||||
"ResetTor" -> {
|
|
||||||
Cwtch.resetTor();
|
|
||||||
}
|
|
||||||
"ImportBundle" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val bundle = (call.argument("bundle") as? String) ?: "";
|
|
||||||
Cwtch.importBundle(profile, bundle);
|
|
||||||
}
|
|
||||||
"SetGroupAttribute" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val groupHandle = (call.argument("groupHandle") as? String) ?: "";
|
|
||||||
val key = (call.argument("key") as? String) ?: "";
|
|
||||||
val value = (call.argument("value") as? String) ?: "";
|
|
||||||
Cwtch.setGroupAttribute(profile, groupHandle, key, value);
|
|
||||||
}
|
|
||||||
"CreateGroup" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val server = (call.argument("server") as? String) ?: "";
|
|
||||||
val groupName = (call.argument("groupname") as? String) ?: "";
|
|
||||||
Cwtch.createGroup(profile, server, groupName);
|
|
||||||
}
|
|
||||||
"LeaveGroup" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val groupHandle = (call.argument("groupHandle") as? String) ?: "";
|
|
||||||
Cwtch.leaveGroup(profile, groupHandle);
|
|
||||||
}
|
|
||||||
"RejectInvite" -> {
|
|
||||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
|
||||||
val groupHandle = (call.argument("groupHandle") as? String) ?: "";
|
|
||||||
Cwtch.rejectInvite(profile, groupHandle);
|
|
||||||
}
|
|
||||||
else -> result.notImplemented()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ...otherwise fallthru to a normal ffi method call (and return the result using the result callback)
|
||||||
|
val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build()
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<FlwtchWorker>().setInputData(data).build()
|
||||||
|
WorkManager.getInstance(this).enqueue(workRequest)
|
||||||
|
WorkManager.getInstance(applicationContext).getWorkInfoByIdLiveData(workRequest.id).observe(
|
||||||
|
this, Observer { workInfo ->
|
||||||
|
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
|
||||||
|
val res = workInfo.outputData.keyValueMap.toString()
|
||||||
|
result.success(workInfo.outputData.getString("result"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// using onresume/onstop for broadcastreceiver because of extended discussion on https://stackoverflow.com/questions/7439041/how-to-unregister-broadcastreceiver
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
Log.i("MainActivity.kt", "onResume")
|
||||||
|
if (myReceiver == null) {
|
||||||
|
Log.i("MainActivity.kt", "onResume registering local broadcast receiver / event bus forwarder")
|
||||||
|
val mc = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
|
||||||
|
val filter = IntentFilter("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
|
||||||
|
myReceiver = MyBroadcastReceiver(mc)
|
||||||
|
LocalBroadcastManager.getInstance(applicationContext).registerReceiver(myReceiver!!, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReconnectCwtchForeground which will resync counters and settings...
|
||||||
|
// We need to do this here because after a "pause" flutter is still running
|
||||||
|
// but we might have lost sync with the background process...
|
||||||
|
Log.i("MainActivity.kt", "Call ReconnectCwtchForeground")
|
||||||
|
val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, "ReconnectCwtchForeground").putString(FlwtchWorker.KEY_ARGS, "{}").build()
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<FlwtchWorker>().setInputData(data).build()
|
||||||
|
WorkManager.getInstance(applicationContext).enqueue(workRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
Log.i("MainActivity.kt", "onStop")
|
||||||
|
if (myReceiver != null) {
|
||||||
|
LocalBroadcastManager.getInstance(applicationContext).unregisterReceiver(myReceiver!!);
|
||||||
|
myReceiver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
Log.i("MainActivity.kt", "onDestroy - cancelling all WORKER_TAG and pruning old work")
|
||||||
|
WorkManager.getInstance(this).cancelAllWorkByTag(WORKER_TAG)
|
||||||
|
WorkManager.getInstance(this).pruneWork()
|
||||||
}
|
}
|
||||||
|
|
||||||
// source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095
|
// source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095
|
||||||
|
@ -217,4 +187,17 @@ class MainActivity: FlutterActivity() {
|
||||||
val EventID = this.optString("EventID")
|
val EventID = this.optString("EventID")
|
||||||
val Data = this.optString("Data")
|
val Data = this.optString("Data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MainActivity.MyBroadcastReceiver receives events from the Cwtch service via im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS Android local broadcast intents
|
||||||
|
// then it forwards them to the flutter ui engine using the CWTCH_EVENTBUS methodchannel
|
||||||
|
class MyBroadcastReceiver(mc: MethodChannel) : BroadcastReceiver() {
|
||||||
|
val eventBus: MethodChannel = mc
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val evtType = intent.getStringExtra("EventType") ?: ""
|
||||||
|
val evtData = intent.getStringExtra("Data") ?: ""
|
||||||
|
//val evtID = intent.getStringExtra("EventID") ?: ""//todo?
|
||||||
|
eventBus.invokeMethod(evtType, evtData)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:lottie_autoPlay="true"
|
app:lottie_autoPlay="true"
|
||||||
app:lottie_rawRes="@raw/cwtch_animated_logo"
|
app:lottie_rawRes="@raw/cwtch_animated_logo_op"
|
||||||
app:lottie_loop="true"
|
app:lottie_loop="true"
|
||||||
app:lottie_speed="1.00" />
|
app:lottie_speed="1.00" />
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ subprojects {
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
//removed due to gradle namespace conflicts that are beyond erinn's mere mortal understanding
|
||||||
delete rootProject.buildDir
|
//task clean(type: Delete) {
|
||||||
}
|
// delete rootProject.buildDir
|
||||||
|
//}
|
||||||
|
|
|
@ -2,3 +2,4 @@ org.gradle.jvmargs=-Xmx1536M
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.enableR8=true
|
android.enableR8=true
|
||||||
|
android.bundle.enableUncompressedNativeLibs=false
|
||||||
|
|
After Width: | Height: | Size: 20 KiB |
|
@ -17,10 +17,10 @@
|
||||||
style="enable-background:new 0 0 24 24;"
|
style="enable-background:new 0 0 24 24;"
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
sodipodi:docname="negative_heart_24px.svg"
|
sodipodi:docname="negative_heart_24px.svg"
|
||||||
inkscape:export-filename="/home/sarah/AndroidStudioProjects/flutter_app/assets/core/negative_heart_512px.png"
|
inkscape:export-filename="/home/sarah/PARA/projects/cwtch/assets/core/negative_heart_256px.png"
|
||||||
inkscape:export-xdpi="4096"
|
inkscape:export-xdpi="1024"
|
||||||
inkscape:export-ydpi="4096"
|
inkscape:export-ydpi="1024"
|
||||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
|
||||||
id="metadata14"><rdf:RDF><cc:Work
|
id="metadata14"><rdf:RDF><cc:Work
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
@ -34,14 +34,14 @@
|
||||||
inkscape:pageopacity="0"
|
inkscape:pageopacity="0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:window-width="1920"
|
inkscape:window-width="1920"
|
||||||
inkscape:window-height="1015"
|
inkscape:window-height="1020"
|
||||||
id="namedview10"
|
id="namedview10"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
inkscape:zoom="9.8333333"
|
inkscape:zoom="9.8333333"
|
||||||
inkscape:cx="12"
|
inkscape:cx="-5.8983051"
|
||||||
inkscape:cy="14.687942"
|
inkscape:cy="14.281162"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="0"
|
inkscape:window-y="31"
|
||||||
inkscape:window-maximized="1"
|
inkscape:window-maximized="1"
|
||||||
inkscape:current-layer="Layer_1" />
|
inkscape:current-layer="Layer_1" />
|
||||||
<style
|
<style
|
||||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 5.0 KiB |
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
Invoke-WebRequest -Uri https://www.torproject.org/dist/torbrowser/10.0.16/tor-win32-0.4.5.7.zip -OutFile tor.zip
|
Invoke-WebRequest -Uri https://dist.torproject.org/torbrowser/10.0.18/tor-win64-0.4.5.9.zip -OutFile tor.zip
|
||||||
|
|
||||||
if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '2b7d683f036d0fec149f1d2bdfcf5b7ef4c337005a2b685c056b00047fdb2b57d4c25b8559ad7ef5c7a030b273934be82a9f83ef6e391f5d7d13d8d6c83e8048' ) { Write-Error 'tor.zip sha512sum mismatch' }
|
if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '72764eb07ad8ab511603aba0734951ca003989f5f4686af91ba220217b4a8a4bcc5f571b59f52c847932f6efedf847b111621983050fcddbb8099d43ca66fb07' ) { Write-Error 'tor.zip sha512sum mismatch' }
|
||||||
|
|
||||||
Expand-Archive -Path tor.zip -DestinationPath Tor
|
Expand-Archive -Path tor.zip -DestinationPath Tor
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-0.4.5.9-linux-x86_64 -O linux/tor
|
||||||
|
chmod a+x linux/tor
|
||||||
|
|
||||||
|
mkdir -p android/app/src/main/jniLibs/arm64-v8a
|
||||||
|
wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-0.4.4.9-arm64_pie -O android/app/src/main/jniLibs/arm64-v8a/libtor.so
|
||||||
|
chmod a+x android/app/src/main/jniLibs/arm64-v8a/libtor.so
|
||||||
|
|
||||||
|
mkdir -p android/app/src/main/jniLibs/armeabi-v7a
|
||||||
|
wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-0.4.4.9-arm_pie -O android/app/src/main/jniLibs/armeabi-v7a/libtor.so
|
||||||
|
chmod a+x android/app/src/main/jniLibs/armeabi-v7a/libtor.so
|
|
@ -1,6 +1,10 @@
|
||||||
|
import 'package:flutter/src/services/text_input.dart';
|
||||||
|
|
||||||
abstract class Cwtch {
|
abstract class Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<void> Start();
|
Future<void> Start();
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<void> ReconnectCwtchForeground();
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SelectProfile(String onion);
|
void SelectProfile(String onion);
|
||||||
|
@ -8,6 +12,8 @@ abstract class Cwtch {
|
||||||
void CreateProfile(String nick, String pass);
|
void CreateProfile(String nick, String pass);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void LoadProfiles(String pass);
|
void LoadProfiles(String pass);
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void DeleteProfile(String onion, String pass);
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void ResetTor();
|
void ResetTor();
|
||||||
|
@ -22,28 +28,19 @@ abstract class Cwtch {
|
||||||
void AcceptContact(String profileOnion, String contactHandle);
|
void AcceptContact(String profileOnion, String contactHandle);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void BlockContact(String profileOnion, String contactHandle);
|
void BlockContact(String profileOnion, String contactHandle);
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
void DebugResetContact(String profileOnion, String contactHandle);
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<dynamic> ACNEvents();
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<dynamic> ContactEvents();
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<dynamic> GetProfiles();
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<dynamic> NumMessages(String profile, String handle);
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<dynamic> GetMessage(String profile, String handle, int index);
|
Future<dynamic> GetMessage(String profile, String handle, int index);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<dynamic> GetMessages(String profile, String handle, int start, int end);
|
void UpdateMessageFlags(String profile, String handle, int index, int flags);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SendMessage(String profile, String handle, String message);
|
void SendMessage(String profile, String handle, String message);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SendInvitation(String profile, String handle, String target);
|
void SendInvitation(String profile, String handle, String target);
|
||||||
|
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void LeaveConversation(String profile, String handle);
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void CreateGroup(String profile, String server, String groupName);
|
void CreateGroup(String profile, String server, String groupName);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
|
@ -56,5 +53,8 @@ abstract class Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void RejectInvite(String profileOnion, String groupHandle);
|
void RejectInvite(String profileOnion, String groupHandle);
|
||||||
|
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
void dispose();
|
void dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,20 +17,28 @@ class CwtchNotifier {
|
||||||
late ErrorHandler error;
|
late ErrorHandler error;
|
||||||
late TorStatus torStatus;
|
late TorStatus torStatus;
|
||||||
late NotificationsManager notificationManager;
|
late NotificationsManager notificationManager;
|
||||||
|
late AppState appState;
|
||||||
|
|
||||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP) {
|
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN) {
|
||||||
profileCN = pcn;
|
profileCN = pcn;
|
||||||
settings = settingsCN;
|
settings = settingsCN;
|
||||||
error = errorCN;
|
error = errorCN;
|
||||||
torStatus = torStatusCN;
|
torStatus = torStatusCN;
|
||||||
notificationManager = notificationManagerP;
|
notificationManager = notificationManagerP;
|
||||||
|
appState = appStateCN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleMessage(String type, dynamic data) {
|
void handleMessage(String type, dynamic data) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case "CwtchStarted":
|
||||||
|
appState.SetCwtchInit();
|
||||||
|
break;
|
||||||
|
case "CwtchStartError":
|
||||||
|
appState.SetAppError(data["Error"]);
|
||||||
|
break;
|
||||||
case "NewPeer":
|
case "NewPeer":
|
||||||
profileCN.add(ProfileInfoState(
|
// if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta...
|
||||||
onion: data["Identity"], nickname: data["name"], imagePath: data["picture"], contactsJson: data["ContactsJson"], serversJson: data["ServerList"], online: data["Online"] == "true"));
|
profileCN.add(data["Identity"], data["name"], data["picture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["tag"] != "v1-defaultPassword");
|
||||||
break;
|
break;
|
||||||
case "PeerCreated":
|
case "PeerCreated":
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(
|
||||||
|
@ -57,13 +65,20 @@ class CwtchNotifier {
|
||||||
if (serverInfoState != null) {
|
if (serverInfoState != null) {
|
||||||
status = serverInfoState.status;
|
status = serverInfoState.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) {
|
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) {
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"],
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"],
|
||||||
isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
|
isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "PeerDeleted":
|
||||||
|
profileCN.delete(data["Identity"]);
|
||||||
|
// todo standarize
|
||||||
|
error.handleUpdate("deleteprofile.success");
|
||||||
|
break;
|
||||||
|
case "DeleteContact":
|
||||||
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["RemotePeer"]);
|
||||||
|
break;
|
||||||
case "DeleteGroup":
|
case "DeleteGroup":
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["GroupID"]);
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["GroupID"]);
|
||||||
break;
|
break;
|
||||||
|
@ -83,7 +98,9 @@ class CwtchNotifier {
|
||||||
break;
|
break;
|
||||||
case "NewMessageFromPeer":
|
case "NewMessageFromPeer":
|
||||||
notificationManager.notify("New Message From Peer!");
|
notificationManager.notify("New Message From Peer!");
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++;
|
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["RemotePeer"]) {
|
||||||
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++;
|
||||||
|
}
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
||||||
break;
|
break;
|
||||||
|
@ -108,7 +125,9 @@ class CwtchNotifier {
|
||||||
case "NewMessageFromGroup":
|
case "NewMessageFromGroup":
|
||||||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||||
//not from me
|
//not from me
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
|
||||||
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
||||||
|
}
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages++;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages++;
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||||
} else {
|
} else {
|
||||||
|
@ -125,6 +144,11 @@ class CwtchNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "MessageCounterResync":
|
||||||
|
var contactHandle = data["RemotePeer"];
|
||||||
|
if (contactHandle == null || contactHandle == "") contactHandle = data["GroupID"];
|
||||||
|
profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages = int.parse(data["Data"]);
|
||||||
|
break;
|
||||||
case "IndexedFailure":
|
case "IndexedFailure":
|
||||||
print("IndexedFailure: $data");
|
print("IndexedFailure: $data");
|
||||||
var idx = data["Index"];
|
var idx = data["Index"];
|
||||||
|
@ -152,7 +176,10 @@ class CwtchNotifier {
|
||||||
break;
|
break;
|
||||||
case "AppError":
|
case "AppError":
|
||||||
print("New App Error: $data");
|
print("New App Error: $data");
|
||||||
if (data["Data"] != null) {
|
// special case for delete error (todo: standardize cwtch errors)
|
||||||
|
if (data["Error"] == "Password did not match") {
|
||||||
|
error.handleUpdate("deleteprofile.error");
|
||||||
|
} else if (data["Data"] != null) {
|
||||||
error.handleUpdate(data["Data"]);
|
error.handleUpdate(data["Data"]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -174,27 +201,32 @@ class CwtchNotifier {
|
||||||
print("acn status: $data");
|
print("acn status: $data");
|
||||||
torStatus.handleUpdate(int.parse(data["Progress"]), data["Status"]);
|
torStatus.handleUpdate(int.parse(data["Progress"]), data["Status"]);
|
||||||
break;
|
break;
|
||||||
|
case "ACNVersion":
|
||||||
|
print("acn version: $data");
|
||||||
|
torStatus.updateVersion(data["Data"]);
|
||||||
|
break;
|
||||||
case "UpdateServerInfo":
|
case "UpdateServerInfo":
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
|
profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
|
||||||
break;
|
break;
|
||||||
case "NewGroup":
|
case "NewGroup":
|
||||||
print("new group invite: $data");
|
print("new group: $data");
|
||||||
String invite = data["GroupInvite"].toString();
|
String invite = data["GroupInvite"].toString();
|
||||||
if (invite.startsWith("torv3")) {
|
if (invite.startsWith("torv3")) {
|
||||||
String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5)));
|
String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5)));
|
||||||
dynamic groupInvite = jsonDecode(inviteJson);
|
dynamic groupInvite = jsonDecode(inviteJson);
|
||||||
print("new group invite: $groupInvite");
|
print("group invite: $groupInvite");
|
||||||
|
|
||||||
// Retrieve Server Status from Cache...
|
// Retrieve Server Status from Cache...
|
||||||
String status = "";
|
String status = "";
|
||||||
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(groupInvite["ServerHost"]);
|
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])!.serverList.getServer(groupInvite["ServerHost"]);
|
||||||
if (serverInfoState != null) {
|
if (serverInfoState != null) {
|
||||||
|
print("Got server status: " + serverInfoState.status);
|
||||||
status = serverInfoState.status;
|
status = serverInfoState.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
|
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
||||||
isInvitation: true,
|
isInvitation: false,
|
||||||
imagePath: data["PicturePath"],
|
imagePath: data["PicturePath"],
|
||||||
nickname: groupInvite["GroupName"],
|
nickname: groupInvite["GroupName"],
|
||||||
server: groupInvite["ServerHost"],
|
server: groupInvite["ServerHost"],
|
||||||
|
@ -207,6 +239,7 @@ class CwtchNotifier {
|
||||||
break;
|
break;
|
||||||
case "AcceptGroupInvite":
|
case "AcceptGroupInvite":
|
||||||
print("accept group invite: $data");
|
print("accept group invite: $data");
|
||||||
|
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.isInvitation = false;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.isInvitation = false;
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import 'package:cwtch/cwtch/cwtchNotifier.dart';
|
import 'package:cwtch/cwtch/cwtchNotifier.dart';
|
||||||
|
import 'package:flutter/src/services/text_input.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
|
@ -15,8 +16,11 @@ import '../config.dart';
|
||||||
/// Cwtch API ///
|
/// Cwtch API ///
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
typedef start_cwtch_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
typedef start_cwtch_function = Int8 Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
||||||
typedef StartCwtchFn = void Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen);
|
typedef StartCwtchFn = int Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen);
|
||||||
|
|
||||||
|
typedef void_from_void_funtion = Void Function();
|
||||||
|
typedef VoidFromVoidFunction = void Function();
|
||||||
|
|
||||||
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||||
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||||
|
@ -27,6 +31,9 @@ typedef VoidFromStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer
|
||||||
typedef void_from_string_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
typedef void_from_string_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||||
typedef VoidFromStringStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
typedef VoidFromStringStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||||
|
|
||||||
|
typedef void_from_string_string_int_int_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int64, Int64);
|
||||||
|
typedef VoidFromStringStringIntIntFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
|
||||||
|
|
||||||
typedef access_cwtch_eventbus_function = Void Function();
|
typedef access_cwtch_eventbus_function = Void Function();
|
||||||
typedef NextEventFn = void Function();
|
typedef NextEventFn = void Function();
|
||||||
|
|
||||||
|
@ -54,13 +61,14 @@ typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int,
|
||||||
typedef get_json_blob_from_str_str_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32, Int32);
|
typedef get_json_blob_from_str_str_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32, Int32);
|
||||||
typedef GetJsonBlobFromStrStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
|
typedef GetJsonBlobFromStrStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
|
||||||
|
|
||||||
typedef acn_events_function = Pointer<Utf8> Function();
|
typedef appbus_events_function = Pointer<Utf8> Function();
|
||||||
typedef ACNEventsFn = Pointer<Utf8> Function();
|
typedef AppbusEventsFn = Pointer<Utf8> Function();
|
||||||
|
|
||||||
class CwtchFfi implements Cwtch {
|
class CwtchFfi implements Cwtch {
|
||||||
late DynamicLibrary library;
|
late DynamicLibrary library;
|
||||||
late CwtchNotifier cwtchNotifier;
|
late CwtchNotifier cwtchNotifier;
|
||||||
late Isolate cwtchIsolate;
|
late Isolate cwtchIsolate;
|
||||||
|
ReceivePort _receivePort = ReceivePort();
|
||||||
|
|
||||||
CwtchFfi(CwtchNotifier _cwtchNotifier) {
|
CwtchFfi(CwtchNotifier _cwtchNotifier) {
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
|
@ -82,6 +90,7 @@ class CwtchFfi implements Cwtch {
|
||||||
Map<String, String> envVars = Platform.environment;
|
Map<String, String> envVars = Platform.environment;
|
||||||
if (Platform.isLinux) {
|
if (Platform.isLinux) {
|
||||||
home = (envVars['HOME'])!;
|
home = (envVars['HOME'])!;
|
||||||
|
bundledTor = "./tor";
|
||||||
} else if (Platform.isWindows) {
|
} else if (Platform.isWindows) {
|
||||||
home = (envVars['UserProfile'])!;
|
home = (envVars['UserProfile'])!;
|
||||||
bundledTor = "Tor\\Tor\\tor.exe";
|
bundledTor = "Tor\\Tor\\tor.exe";
|
||||||
|
@ -100,7 +109,6 @@ class CwtchFfi implements Cwtch {
|
||||||
StartCwtch(ut8CwtchDir, ut8CwtchDir.length, bundledTor.toNativeUtf8(), bundledTor.length);
|
StartCwtch(ut8CwtchDir, ut8CwtchDir.length, bundledTor.toNativeUtf8(), bundledTor.length);
|
||||||
|
|
||||||
// Spawn an isolate to listen to events from libcwtch-go and then dispatch them when received on main thread to cwtchNotifier
|
// Spawn an isolate to listen to events from libcwtch-go and then dispatch them when received on main thread to cwtchNotifier
|
||||||
var _receivePort = ReceivePort();
|
|
||||||
cwtchIsolate = await Isolate.spawn(_checkAppbusEvents, _receivePort.sendPort);
|
cwtchIsolate = await Isolate.spawn(_checkAppbusEvents, _receivePort.sendPort);
|
||||||
_receivePort.listen((message) {
|
_receivePort.listen((message) {
|
||||||
var env = jsonDecode(message);
|
var env = jsonDecode(message);
|
||||||
|
@ -108,6 +116,14 @@ class CwtchFfi implements Cwtch {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<void> ReconnectCwtchForeground() async {
|
||||||
|
var reconnectCwtch = library.lookup<NativeFunction<Void Function()>>("c_ReconnectCwtchForeground");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final ReconnectCwtchForeground = reconnectCwtch.asFunction<void Function()>();
|
||||||
|
ReconnectCwtchForeground();
|
||||||
|
}
|
||||||
|
|
||||||
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
|
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@ -122,6 +138,7 @@ class CwtchFfi implements Cwtch {
|
||||||
await for (var value in stream) {
|
await for (var value in stream) {
|
||||||
sendPort.send(value);
|
sendPort.send(value);
|
||||||
}
|
}
|
||||||
|
print("checkAppBusEvents finished...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Steam of appbus events. Call blocks in libcwtch-go GetAppbusEvent. Static so the isolate can use it
|
// Steam of appbus events. Call blocks in libcwtch-go GetAppbusEvent. Static so the isolate can use it
|
||||||
|
@ -133,13 +150,18 @@ class CwtchFfi implements Cwtch {
|
||||||
library = DynamicLibrary.open("libCwtch.so");
|
library = DynamicLibrary.open("libCwtch.so");
|
||||||
}
|
}
|
||||||
|
|
||||||
var getAppbusEventC = library.lookup<NativeFunction<acn_events_function>>("c_GetAppBusEvent");
|
var getAppbusEventC = library.lookup<NativeFunction<appbus_events_function>>("c_GetAppBusEvent");
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
final GetAppbusEvent = getAppbusEventC.asFunction<ACNEventsFn>();
|
final GetAppbusEvent = getAppbusEventC.asFunction<AppbusEventsFn>();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Pointer<Utf8> result = GetAppbusEvent();
|
Pointer<Utf8> result = GetAppbusEvent();
|
||||||
String event = result.toDartString();
|
String event = result.toDartString();
|
||||||
|
|
||||||
|
if (event.startsWith("{\"EventType\":\"Shutdown\"")) {
|
||||||
|
print("Shutting down isolate thread: $event");
|
||||||
|
return;
|
||||||
|
}
|
||||||
yield event;
|
yield event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,50 +194,6 @@ class CwtchFfi implements Cwtch {
|
||||||
LoadProfiles(ut8pass, ut8pass.length);
|
LoadProfiles(ut8pass, ut8pass.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<String> ACNEvents() async {
|
|
||||||
var acnEventsC = library.lookup<NativeFunction<acn_events_function>>("c_ACNEvents");
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
final ACNEvents = acnEventsC.asFunction<ACNEventsFn>();
|
|
||||||
|
|
||||||
Pointer<Utf8> result = ACNEvents();
|
|
||||||
String event = result.toDartString();
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<String> ContactEvents() async {
|
|
||||||
var acnEventsC = library.lookup<NativeFunction<acn_events_function>>("c_ContactEvents");
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
final ContactEvents = acnEventsC.asFunction<ACNEventsFn>();
|
|
||||||
|
|
||||||
Pointer<Utf8> result = ContactEvents();
|
|
||||||
String event = result.toDartString();
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<String> GetProfiles() async {
|
|
||||||
var getProfilesC = library.lookup<NativeFunction<get_json_blob_void_function>>("c_GetProfiles");
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
final GetProfiles = getProfilesC.asFunction<GetJsonBlobVoidFn>();
|
|
||||||
|
|
||||||
Pointer<Utf8> jsonProfilesBytes = GetProfiles();
|
|
||||||
String jsonProfiles = jsonProfilesBytes.toDartString();
|
|
||||||
return jsonProfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<int> NumMessages(String profile, String handle) async {
|
|
||||||
var numMessagesC = library.lookup<NativeFunction<get_int_from_str_str_function>>("c_NumMessages");
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
final NumMessages = numMessagesC.asFunction<GetIntFromStrStrFn>();
|
|
||||||
final utf8profile = profile.toNativeUtf8();
|
|
||||||
final utf8handle = handle.toNativeUtf8();
|
|
||||||
int num = NumMessages(utf8profile, utf8profile.length, utf8handle, utf8handle.length);
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<String> GetMessage(String profile, String handle, int index) async {
|
Future<String> GetMessage(String profile, String handle, int index) async {
|
||||||
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_str_int_function>>("c_GetMessage");
|
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_str_int_function>>("c_GetMessage");
|
||||||
|
@ -228,18 +206,6 @@ class CwtchFfi implements Cwtch {
|
||||||
return jsonMessage;
|
return jsonMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<String> GetMessages(String profile, String handle, int start, int end) async {
|
|
||||||
var getMessagesC = library.lookup<NativeFunction<get_json_blob_from_str_str_int_int_function>>("c_GetMessages");
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
final GetMessages = getMessagesC.asFunction<GetJsonBlobFromStrStrIntIntFn>();
|
|
||||||
final utf8profile = profile.toNativeUtf8();
|
|
||||||
final utf8handle = handle.toNativeUtf8();
|
|
||||||
Pointer<Utf8> jsonMessagesBytes = GetMessages(utf8profile, utf8profile.length, utf8handle, utf8handle.length, start, end);
|
|
||||||
String jsonMessages = jsonMessagesBytes.toDartString();
|
|
||||||
return jsonMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SendProfileEvent(String onion, String json) {
|
void SendProfileEvent(String onion, String json) {
|
||||||
|
@ -283,17 +249,6 @@ class CwtchFfi implements Cwtch {
|
||||||
BlockContact(u1, u1.length, u2, u2.length);
|
BlockContact(u1, u1.length, u2, u2.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
void DebugResetContact(String profileOnion, String contactHandle) {
|
|
||||||
var debugResetContact = library.lookup<NativeFunction<string_string_to_void_function>>("c_DebugResetContact");
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
final DebugResetContact = debugResetContact.asFunction<VoidFromStringStringFn>();
|
|
||||||
final u1 = profileOnion.toNativeUtf8();
|
|
||||||
final u2 = contactHandle.toNativeUtf8();
|
|
||||||
DebugResetContact(u1, u1.length, u2, u2.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SendMessage(String profileOnion, String contactHandle, String message) {
|
void SendMessage(String profileOnion, String contactHandle, String message) {
|
||||||
|
@ -373,14 +328,63 @@ class CwtchFfi implements Cwtch {
|
||||||
CreateGroup(u1, u1.length, u2, u2.length, u3, u3.length);
|
CreateGroup(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void LeaveConversation(String profileOnion, String handle) {
|
||||||
|
var leaveConversation = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveConversation");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final LeaveConversation = leaveConversation.asFunction<VoidFromStringStringFn>();
|
||||||
|
final u1 = profileOnion.toNativeUtf8();
|
||||||
|
final u2 = handle.toNativeUtf8();
|
||||||
|
LeaveConversation(u1, u1.length, u2, u2.length);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void LeaveGroup(String profileOnion, String groupHandle) {
|
void LeaveGroup(String profileOnion, String groupHandle) {
|
||||||
var leaveGroup = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveGroup");
|
var leaveGroup = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveGroup");
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
final RejectInvite = leaveGroup.asFunction<VoidFromStringStringFn>();
|
final LeaveGroup = leaveGroup.asFunction<VoidFromStringStringFn>();
|
||||||
final u1 = profileOnion.toNativeUtf8();
|
final u1 = profileOnion.toNativeUtf8();
|
||||||
final u2 = groupHandle.toNativeUtf8();
|
final u2 = groupHandle.toNativeUtf8();
|
||||||
RejectInvite(u1, u1.length, u2, u2.length);
|
LeaveGroup(u1, u1.length, u2, u2.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void UpdateMessageFlags(String profile, String handle, int index, int flags) {
|
||||||
|
var updateMessageFlagsC = library.lookup<NativeFunction<void_from_string_string_int_int_function>>("c_UpdateMessageFlags");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final updateMessageFlags = updateMessageFlagsC.asFunction<VoidFromStringStringIntIntFn>();
|
||||||
|
final utf8profile = profile.toNativeUtf8();
|
||||||
|
final utf8handle = handle.toNativeUtf8();
|
||||||
|
updateMessageFlags(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void DeleteProfile(String onion, String currentPassword) {
|
||||||
|
var deleteprofile = library.lookup<NativeFunction<string_string_to_void_function>>("c_DeleteProfile");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final DeleteProfile = deleteprofile.asFunction<VoidFromStringStringFn>();
|
||||||
|
final u1 = onion.toNativeUtf8();
|
||||||
|
final u2 = currentPassword.toNativeUtf8();
|
||||||
|
DeleteProfile(u1, u1.length, u2, u2.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> Shutdown() async {
|
||||||
|
var shutdown = library.lookup<NativeFunction<void_from_void_funtion>>("c_ShutdownCwtch");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
|
||||||
|
// Shutdown Cwtch + Tor...
|
||||||
|
final Shutdown = shutdown.asFunction<VoidFromVoidFunction>();
|
||||||
|
Shutdown();
|
||||||
|
|
||||||
|
// Kill our Isolate
|
||||||
|
cwtchIsolate.kill(priority: Isolate.immediate);
|
||||||
|
print("Isolate killed");
|
||||||
|
|
||||||
|
_receivePort.close();
|
||||||
|
print("Receive Port Closed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:cwtch/config.dart';
|
import 'package:cwtch/config.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -51,7 +50,13 @@ class CwtchGomobile implements Cwtch {
|
||||||
}
|
}
|
||||||
String torPath = path.join(await androidLibraryDir, "libtor.so");
|
String torPath = path.join(await androidLibraryDir, "libtor.so");
|
||||||
print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)...");
|
print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)...");
|
||||||
cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath});
|
return cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<void> ReconnectCwtchForeground() async {
|
||||||
|
cwtchPlatform.invokeMethod("ReconnectCwtchForeground", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier
|
// Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier
|
||||||
|
@ -77,24 +82,8 @@ class CwtchGomobile implements Cwtch {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<dynamic> ACNEvents() {
|
void DeleteProfile(String onion, String pass) {
|
||||||
return cwtchPlatform.invokeMethod("ACNEvents");
|
cwtchPlatform.invokeMethod("DeleteProfile", {"ProfileOnion": onion, "pass": pass});
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<dynamic> ContactEvents() {
|
|
||||||
return cwtchPlatform.invokeMethod("ContactEvents");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<dynamic> GetProfiles() {
|
|
||||||
print("gomobile.dart: GetProfiles()");
|
|
||||||
return cwtchPlatform.invokeMethod("GetProfiles");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<dynamic> NumMessages(String profile, String handle) {
|
|
||||||
return cwtchPlatform.invokeMethod("NumMessages", {"profile": profile, "contact": handle});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
|
@ -103,11 +92,6 @@ class CwtchGomobile implements Cwtch {
|
||||||
return cwtchPlatform.invokeMethod("GetMessage", {"profile": profile, "contact": handle, "index": index});
|
return cwtchPlatform.invokeMethod("GetMessage", {"profile": profile, "contact": handle, "index": index});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
Future<dynamic> GetMessages(String profile, String handle, int start, int end) {
|
|
||||||
return cwtchPlatform.invokeMethod("GetMessage", {"profile": profile, "contact": handle, "start": start, "end": end});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SendProfileEvent(String onion, String jsonEvent) {
|
void SendProfileEvent(String onion, String jsonEvent) {
|
||||||
|
@ -135,12 +119,6 @@ class CwtchGomobile implements Cwtch {
|
||||||
cwtchPlatform.invokeMethod("BlockContact", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
cwtchPlatform.invokeMethod("BlockContact", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
void DebugResetContact(String profileOnion, String contactHandle) {
|
|
||||||
cwtchPlatform.invokeMethod("DebugResetContact", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SendMessage(String profileOnion, String contactHandle, String message) {
|
void SendMessage(String profileOnion, String contactHandle, String message) {
|
||||||
|
@ -174,7 +152,7 @@ class CwtchGomobile implements Cwtch {
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void RejectInvite(String profileOnion, String groupHandle) {
|
void RejectInvite(String profileOnion, String groupHandle) {
|
||||||
cwtchPlatform.invokeMethod("RejectInvite", {"ProfileOnion": profileOnion, "handle": groupHandle});
|
cwtchPlatform.invokeMethod("RejectInvite", {"ProfileOnion": profileOnion, "groupHandle": groupHandle});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -185,6 +163,24 @@ class CwtchGomobile implements Cwtch {
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void LeaveGroup(String profileOnion, String groupHandle) {
|
void LeaveGroup(String profileOnion, String groupHandle) {
|
||||||
cwtchPlatform.invokeMethod("LeaveGroup", {"ProfileOnion": profileOnion, "handle": groupHandle});
|
cwtchPlatform.invokeMethod("LeaveGroup", {"ProfileOnion": profileOnion, "groupHandle": groupHandle});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void LeaveConversation(String profileOnion, String contactHandle) {
|
||||||
|
cwtchPlatform.invokeMethod("LeaveConversation", {"ProfileOnion": profileOnion, "contactHandle": contactHandle});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void UpdateMessageFlags(String profile, String handle, int index, int flags) {
|
||||||
|
print("gomobile.dart UpdateMessageFlags " + index.toString());
|
||||||
|
cwtchPlatform.invokeMethod("UpdateMessageFlags", {"profile": profile, "contact": handle, "index": index, "flags": flags});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> Shutdown() async {
|
||||||
|
print("gomobile.dart Shutdown");
|
||||||
|
cwtchPlatform.invokeMethod("Shutdown", {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
/// Flutter icons CwtchIcons
|
||||||
|
/// Copyright (C) 2021 by Open Privacy Research Society via fluttericon.com, fontello.com
|
||||||
|
/// This font was generated by FlutterIcon.com, which is derived from Fontello.
|
||||||
|
///
|
||||||
|
/// To use this font, place it in your fonts/ directory and include the
|
||||||
|
/// following in your pubspec.yaml
|
||||||
|
///
|
||||||
|
/// flutter:
|
||||||
|
/// fonts:
|
||||||
|
/// - family: CwtchIcons
|
||||||
|
/// fonts:
|
||||||
|
/// - asset: assets/fonts/CwtchIcons.ttf
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class CwtchIcons {
|
||||||
|
CwtchIcons._();
|
||||||
|
|
||||||
|
static const _kFontFam = 'CwtchIcons';
|
||||||
|
static const String? _kFontPkg = null;
|
||||||
|
|
||||||
|
static const IconData arrow_back_24px = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData attach_file_24px = IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData block_peer = IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData block_unknown = IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData block_24px = IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData brightness_5_24px = IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData camera_alt_24px = IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData change_language = IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData change_theme = IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData chat_bubble_empty_24px = IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData chat_bubble_24px = IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData chat_seetings_24px = IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData check_24px = IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData chevron_left_24px = IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData clear_24px = IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData content_copy_24px = IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData create_group = IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData cwtch_knott = IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData dark_mode_24px = IconData(0xe812, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData delete_24px = IconData(0xe813, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData dns_24px = IconData(0xe814, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData drag_indicator_24px = IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData edit_24px = IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData enable_experiments = IconData(0xe817, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData enable_groups = IconData(0xe818, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData eye_closed = IconData(0xe819, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData eye_open = IconData(0xe81a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData favorite_24dp = IconData(0xe81b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData favorite_black_24dp_broken = IconData(0xe81c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData favorite_black_24dp_brokenhalf = IconData(0xe81d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData favorite_black_24dp_malformed = IconData(0xe81e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData favorite_black_24dp_sad = IconData(0xe81f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData group_add_24px = IconData(0xe820, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData group_settings_24px = IconData(0xe821, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData groups_24px = IconData(0xe822, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData info_24px = IconData(0xe823, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData join_group = IconData(0xe824, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData list_black_24dp = IconData(0xe825, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData lock_open_24px = IconData(0xe826, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData lock_24px = IconData(0xe827, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData maps_ugc_24px = IconData(0xe828, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData menu_24px = IconData(0xe829, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData message_24px = IconData(0xe82a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData mood_24px = IconData(0xe82b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData more_vert_24px = IconData(0xe82c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData negative_heart_24px = IconData(0xe82d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData onion_off = IconData(0xe82e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData onion_on = IconData(0xe82f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData onion_waiting = IconData(0xe830, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData peer_history = IconData(0xe831, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData peer_settings_24px = IconData(0xe832, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData person_add_alt_1_24px = IconData(0xe833, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData person_add_24px = IconData(0xe834, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData person_24px = IconData(0xe835, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData push_pin_black_24dp = IconData(0xe836, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData push_pin_24px = IconData(0xe837, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData search_24px = IconData(0xe838, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData send_24px = IconData(0xe839, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData settings_24px = IconData(0xe83a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData signal_cellular_4_bar_24px = IconData(0xe83b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData signal_cellular_alt_24px = IconData(0xe83c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData signal_cellular_connected_no_internet_4_bar_24px = IconData(0xe83d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData signal_cellular_off_24px = IconData(0xe83e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData swap_horiz_24px = IconData(0xe83f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData sync_disabled_24px = IconData(0xe840, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData sync_problem_24px = IconData(0xe841, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData sync_24px = IconData(0xe842, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData syncing_01 = IconData(0xe843, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData syncing_02 = IconData(0xe844, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData syncing_03 = IconData(0xe845, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData toggle_on_24px = IconData(0xe846, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData vpn_key_24px = IconData(0xe847, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData account_blocked = IconData(0xe848, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData account_circle_24px = IconData(0xe849, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData account_circle_24px_lines = IconData(0xe84a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData account_circle_24px_lines_thin___blocked = IconData(0xe84b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData account_circle_24px_user = IconData(0xe84c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData add_circle_24px = IconData(0xe84d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData add_group = IconData(0xe84e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData add_peer = IconData(0xe84f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData add_24px = IconData(0xe850, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData address_copy_2 = IconData(0xe852, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData address = IconData(0xe856, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData send_invite = IconData(0xe888, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData leave_group = IconData(0xe88a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
static const IconData leave_chat = IconData(0xe88b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||||
|
}
|
|
@ -17,6 +17,10 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
bool importBundleError = false;
|
bool importBundleError = false;
|
||||||
bool importBundleSuccess = false;
|
bool importBundleSuccess = false;
|
||||||
|
|
||||||
|
static const String deleteProfileErrorPrefix = "deleteprofile";
|
||||||
|
bool deleteProfileError = false;
|
||||||
|
bool deleteProfileSuccess = false;
|
||||||
|
|
||||||
/// Called by the event bus.
|
/// Called by the event bus.
|
||||||
handleUpdate(String error) {
|
handleUpdate(String error) {
|
||||||
var parts = error.split(".");
|
var parts = error.split(".");
|
||||||
|
@ -30,6 +34,9 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
case importBundleErrorPrefix:
|
case importBundleErrorPrefix:
|
||||||
handleImportBundleError(errorType);
|
handleImportBundleError(errorType);
|
||||||
break;
|
break;
|
||||||
|
case deleteProfileErrorPrefix:
|
||||||
|
handleDeleteProfileError(errorType);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -69,4 +76,19 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDeleteProfileError(String errorType) {
|
||||||
|
// Reset add contact errors
|
||||||
|
deleteProfileError = false;
|
||||||
|
deleteProfileSuccess = false;
|
||||||
|
|
||||||
|
switch (errorType) {
|
||||||
|
case successErrorType:
|
||||||
|
deleteProfileSuccess = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
deleteProfileError = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,160 +1,193 @@
|
||||||
{
|
{
|
||||||
"@@locale": "de",
|
"@@locale": "de",
|
||||||
"acceptGroupBtn": "Annehmen",
|
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||||
"acceptGroupInviteLabel": "Möchtest Du die Einladung annehmen",
|
"tooltipHidePassword": "Hide Password",
|
||||||
"acknowledgedLabel": "bestätigt",
|
"tooltipShowPassword": "Show Password",
|
||||||
"addListItem": "Liste hinzufügen",
|
"serverNotSynced": "Syncing New Messages (This can take some time)...",
|
||||||
"addListItemBtn": "Element hinzufügen",
|
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||||
"addNewItem": "Ein neues Element zur Liste hinzufügen",
|
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||||
"addNewProfileBtn": "Neues Profil hinzufügen",
|
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||||
"addPeer": "Peer hinzufügen",
|
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||||
"addPeerTab": "Einen Peer hinzufügen",
|
"shutdownCwtchTooltip": "Shutdown Cwtch",
|
||||||
"addProfileTitle": "Neues Profil hinzufügen",
|
"malformedMessage": "Malformed message",
|
||||||
"addressLabel": "Adresse",
|
"profileDeleteSuccess": "Successfully deleted profile",
|
||||||
"blockBtn": "Peer blockieren",
|
"debugLog": "Turn on console debug logging",
|
||||||
"blocked": "Blockiert",
|
"torNetworkStatus": "Tor network status",
|
||||||
"blockUnknownLabel": "Unbekannte Peers blockieren",
|
"addContactFirst": "Add or pick a contact to begin chatting.",
|
||||||
"builddate": "Aufgebaut auf: %2",
|
"createProfileToBegin": "Please create or unlock a profile to begin",
|
||||||
"bulletinsBtn": "Meldungen",
|
"nickChangeSuccess": "Profile nickname changed successfully",
|
||||||
"chatBtn": "Chat",
|
"addServerFirst": "You need to add a server before you can create a group",
|
||||||
"contactAlreadyExists": "",
|
"deleteProfileSuccess": "Successfully deleted profile",
|
||||||
"conversationSettings": "",
|
"sendInvite": "Send a contact or group invite",
|
||||||
"copiedClipboardNotification": "in die Zwischenablage kopiert",
|
"sendMessage": "Send Message",
|
||||||
"copiedToClipboardNotification": "in die Zwischenablage kopiert",
|
"cancel": "Cancel",
|
||||||
"copyBtn": "Kopieren",
|
"resetTor": "Reset",
|
||||||
"couldNotSendMsgError": "Nachricht konnte nicht gesendet werden",
|
"torStatus": "Tor Status",
|
||||||
"createGroup": "Gruppe erstellen",
|
"torVersion": "Tor Version",
|
||||||
"createGroupBtn": "Anlegen",
|
"sendAnInvitation": "You sent an invitation for: ",
|
||||||
"createGroupTab": "Eine Gruppe erstellen",
|
"contactSuggestion": "This is a contact suggestion for: ",
|
||||||
"createGroupTitle": "Gruppe Anlegen",
|
"rejected": "Rejected!",
|
||||||
"createProfileBtn": "Profil speichern",
|
"accepted": "Accepted!",
|
||||||
"currentPasswordLabel": "derzeitiges Passwort",
|
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
|
||||||
"cwtchSettingsTitle": "Cwtch Einstellungen",
|
"newPassword": "New Password",
|
||||||
"cycleCatsAndroid": "",
|
"yesLeave": "Yes, Leave This Conversation",
|
||||||
"cycleCatsDesktop": "",
|
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||||
"cycleColoursAndroid": "",
|
"leaveGroup": "Leave This Conversation",
|
||||||
"cycleColoursDesktop": "",
|
"inviteToGroup": "You have been invited to join a group:",
|
||||||
"cycleMorphsAndroid": "",
|
"pasteAddressToAddContact": "Adresse hier hinzufügen, um einen Kontakt aufzunehmen",
|
||||||
"cycleMorphsDesktop": "",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"dateDaysAgo": "",
|
"titleManageContacts": "Conversations",
|
||||||
"dateHoursAgo": "",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateLastMonth": "",
|
"dateMonthsAgo": "Months Ago",
|
||||||
"dateLastYear": "",
|
"dateNever": "Never",
|
||||||
"dateMinutesAgo": "",
|
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
||||||
"dateMonthsAgo": "",
|
"dateLastYear": "Last Year",
|
||||||
"dateNever": "",
|
"dateYesterday": "Yesterday",
|
||||||
"dateRightNow": "",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "",
|
"dateWeeksAgo": "Weeks Ago",
|
||||||
"dateYearsAgo": "",
|
"dateDaysAgo": "Days Ago",
|
||||||
"dateYesterday": "",
|
"dateHoursAgo": "Hours Ago",
|
||||||
"defaultGroupName": "Tolle Gruppe",
|
"dateMinutesAgo": "Minutes Ago",
|
||||||
"defaultProfileName": "Alice",
|
"dateRightNow": "Right Now",
|
||||||
"defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"deleteBtn": "Löschen",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
"deleteConfirmLabel": "Geben Sie LÖSCHEN zur Bestätigung ein",
|
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
|
||||||
"deleteConfirmText": "LÖSCHEN",
|
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
|
||||||
"deleteProfileBtn": "Profil löschen",
|
"titleManageProfiles": "Manage Cwtch Profiles",
|
||||||
"deleteProfileConfirmBtn": "Profil wirklich löschen",
|
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
||||||
"descriptionBlockUnknownConnections": "",
|
"tooltipOpenSettings": "Open the settings pane",
|
||||||
"descriptionExperiments": "",
|
"invalidImportString": "Invalid import string",
|
||||||
"descriptionExperimentsGroups": "",
|
"contactAlreadyExists": "Contact Already Exists",
|
||||||
"displayNameLabel": "Angezeigter Name",
|
"conversationSettings": "Conversation Settings",
|
||||||
"dmTooltip": "Klicken, um DM zu senden",
|
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
|
||||||
"dontSavePeerHistory": "Peer-Verlauf löschen",
|
"enableGroups": "Enable Group Chat",
|
||||||
"editProfile": "Profil bearbeiten",
|
"experimentsEnabled": "Experimente aktiviert",
|
||||||
"editProfileTitle": "Profil bearbeiten",
|
"localeIt": "Italiana",
|
||||||
"enableGroups": "",
|
"localeEs": "Espanol",
|
||||||
"enterCurrentPasswordForDelete": "",
|
"addListItem": "Liste hinzufügen",
|
||||||
"enterProfilePassword": "Geben Sie ein Passwort ein, um Ihre Profile anzuzeigen",
|
"addNewItem": "Ein neues Element zur Liste hinzufügen",
|
||||||
"error0ProfilesLoadedForPassword": "0 Profile mit diesem Passwort geladen",
|
"todoPlaceholder": "noch zu erledigen",
|
||||||
"experimentsEnabled": "Experimente aktiviert",
|
"newConnectionPaneTitle": "Neue Verbindung",
|
||||||
"groupAddr": "Adresse",
|
"networkStatusOnline": "Online",
|
||||||
"groupName": "Gruppenname",
|
"networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...",
|
||||||
"groupNameLabel": "Gruppenname",
|
"networkStatusAttemptingTor": "Versuche, eine Verbindung mit dem Tor-Netzwerk herzustellen",
|
||||||
"invalidImportString": "",
|
"networkStatusDisconnected": "Vom Internet getrennt, überprüfen Sie Ihre Verbindung",
|
||||||
"invitation": "Einladung",
|
"viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen",
|
||||||
"invitationLabel": "Einladung",
|
"loadingTor": "Tor wird geladen...",
|
||||||
"inviteBtn": "Einladen",
|
"smallTextLabel": "Klein",
|
||||||
"inviteToGroupLabel": "In die Gruppe einladen",
|
"defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:",
|
||||||
"joinGroup": "Gruppe beitreten",
|
"builddate": "Aufgebaut auf: %2",
|
||||||
"joinGroupTab": "Einer Gruppe beitreten",
|
"version": "Version %1",
|
||||||
"largeTextLabel": "Groß",
|
"versionTor": "Version %1 mit tor %2",
|
||||||
"listsBtn": "Listen",
|
"themeDark": "Dunkel",
|
||||||
"loadingTor": "Tor wird geladen...",
|
"themeLight": "Licht",
|
||||||
"localeDe": "Deutsche",
|
"settingTheme": "Thema",
|
||||||
"localeEn": "",
|
"largeTextLabel": "Groß",
|
||||||
"localeEs": "",
|
"settingInterfaceZoom": "Zoomstufe",
|
||||||
"localeFr": "",
|
"localeDe": "Deutsche",
|
||||||
"localeIt": "",
|
"localePt": "Portuguesa",
|
||||||
"localePt": "",
|
"localeFr": "Frances",
|
||||||
"membershipDescription": "Unten steht eine Liste der Benutzer, die Nachrichten an die Gruppe gesendet haben. Möglicherweise enthält diese Benutzerzliste nicht alle, die Zugang zur Gruppe haben.",
|
"localeEn": "English",
|
||||||
"networkStatusAttemptingTor": "Versuche, eine Verbindung mit dem Tor-Netzwerk herzustellen",
|
"settingLanguage": "Sprache",
|
||||||
"networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...",
|
"blockUnknownLabel": "Unbekannte Peers blockieren",
|
||||||
"networkStatusDisconnected": "Vom Internet getrennt, überprüfen Sie Ihre Verbindung",
|
"zoomLabel": "Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)",
|
||||||
"networkStatusOnline": "Online",
|
"versionBuilddate": "Version: %1 Aufgebaut auf: %2",
|
||||||
"newBulletinLabel": "Neue Meldung",
|
"cwtchSettingsTitle": "Cwtch Einstellungen",
|
||||||
"newConnectionPaneTitle": "Neue Verbindung",
|
"unlock": "Entsperren",
|
||||||
"newGroupBtn": "Neue Gruppe anlegen",
|
"yourServers": "Ihre Server",
|
||||||
"newProfile": "Neues Profil",
|
"yourProfiles": "Ihre Profile",
|
||||||
"noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.",
|
"error0ProfilesLoadedForPassword": "0 Profile mit diesem Passwort geladen",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"password1Label": "Passwort",
|
"enterProfilePassword": "Geben Sie ein Passwort ein, um Ihre Profile anzuzeigen",
|
||||||
"password2Label": "Passwort erneut eingeben",
|
"addNewProfileBtn": "Neues Profil hinzufügen",
|
||||||
"passwordChangeError": "Fehler beim Ändern des Passworts: Das Passwort wurde abgelehnt",
|
"deleteConfirmText": "LÖSCHEN",
|
||||||
"passwordErrorEmpty": "Passwort kann nicht leer sein",
|
"deleteProfileConfirmBtn": "Profil wirklich löschen",
|
||||||
"passwordErrorMatch": "Passwörter stimmen nicht überein",
|
"deleteConfirmLabel": "Geben Sie LÖSCHEN zur Bestätigung ein",
|
||||||
"pasteAddressToAddContact": "Adresse hier hinzufügen, um einen Kontakt aufzunehmen",
|
"deleteProfileBtn": "Profil löschen",
|
||||||
"peerAddress": "Adresse",
|
"passwordChangeError": "Fehler beim Ändern des Passworts: Das Passwort wurde abgelehnt",
|
||||||
"peerBlockedMessage": "Peer ist blockiert",
|
"passwordErrorMatch": "Passwörter stimmen nicht überein",
|
||||||
"peerName": "Namen",
|
"saveProfileBtn": "Profil speichern",
|
||||||
"peerNotOnline": "",
|
"createProfileBtn": "Profil speichern",
|
||||||
"peerOfflineMessage": "Peer ist offline, Nachrichten können derzeit nicht zugestellt werden",
|
"passwordErrorEmpty": "Passwort kann nicht leer sein",
|
||||||
"pendingLabel": "Bestätigung ausstehend",
|
"password2Label": "Passwort erneut eingeben",
|
||||||
"postNewBulletinLabel": "Neue Meldung veröffentlichen",
|
"password1Label": "Passwort",
|
||||||
"profileName": "Anzeigename",
|
"currentPasswordLabel": "derzeitiges Passwort",
|
||||||
"profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten",
|
"yourDisplayName": "Ihr Anzeigename",
|
||||||
"puzzleGameBtn": "Puzzlespiel",
|
"profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten",
|
||||||
"radioNoPassword": "Unverschlüsselt (kein Passwort)",
|
"noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.",
|
||||||
"radioUsePassword": "Passwort",
|
"radioNoPassword": "Unverschlüsselt (kein Passwort)",
|
||||||
"rejectGroupBtn": "Ablehnen",
|
"radioUsePassword": "Passwort",
|
||||||
"saveBtn": "Speichern",
|
"copiedToClipboardNotification": "in die Zwischenablage kopiert",
|
||||||
"savePeerHistory": "Peer-Verlauf speichern",
|
"copyBtn": "Kopieren",
|
||||||
"savePeerHistoryDescription": "Legt fest, ob ein mit dem Peer verknüpfter Verlauf gelöscht werden soll oder nicht.",
|
"editProfile": "Profil bearbeiten",
|
||||||
"saveProfileBtn": "Profil speichern",
|
"newProfile": "Neues Profil",
|
||||||
"search": "Suche...",
|
"defaultProfileName": "Alice",
|
||||||
"searchList": "",
|
"profileName": "Anzeigename",
|
||||||
"server": "Server",
|
"editProfileTitle": "Profil bearbeiten",
|
||||||
"serverConnectivityConnected": "Server verbunden",
|
"addProfileTitle": "Neues Profil hinzufügen",
|
||||||
"serverConnectivityDisconnected": "Server getrennt",
|
"deleteBtn": "löschen",
|
||||||
"serverInfo": "Server-Informationen",
|
"unblockBtn": "Peer entblockieren",
|
||||||
"serverLabel": "Server",
|
"dontSavePeerHistory": "Peer-Verlauf löschen",
|
||||||
"serverNotSynced": "",
|
"savePeerHistoryDescription": "Legt fest, ob ein mit dem Peer verknüpfter Verlauf gelöscht werden soll oder nicht.",
|
||||||
"serverSynced": "",
|
"savePeerHistory": "Peer-Verlauf speichern",
|
||||||
"settingInterfaceZoom": "Zoomstufe",
|
"blockBtn": "Peer blockieren",
|
||||||
"settingLanguage": "Sprache",
|
"saveBtn": "speichern",
|
||||||
"settingTheme": "Thema",
|
"displayNameLabel": "Angezeigter Name",
|
||||||
"smallTextLabel": "Klein",
|
"addressLabel": "Adresse",
|
||||||
"successfullAddedContact": "",
|
"puzzleGameBtn": "Puzzlespiel",
|
||||||
"themeDark": "Dunkel",
|
"bulletinsBtn": "Meldungen",
|
||||||
"themeLight": "Licht",
|
"listsBtn": "Listen",
|
||||||
"titleManageContacts": "",
|
"chatBtn": "Chat",
|
||||||
"titleManageProfiles": "",
|
"rejectGroupBtn": "Ablehnen",
|
||||||
"titleManageServers": "",
|
"acceptGroupBtn": "Annehmen",
|
||||||
"titlePlaceholder": "Titel...",
|
"acceptGroupInviteLabel": "Möchtest Du die Einladung annehmen",
|
||||||
"todoPlaceholder": "noch zu erledigen",
|
"newGroupBtn": "Neue Gruppe anlegen",
|
||||||
"tooltipAddContact": "",
|
"copiedClipboardNotification": "in die Zwischenablage kopiert",
|
||||||
"tooltipOpenSettings": "",
|
"peerOfflineMessage": "Peer ist offline, Nachrichten können derzeit nicht zugestellt werden",
|
||||||
"tooltipUnlockProfiles": "",
|
"peerBlockedMessage": "Peer ist blockiert",
|
||||||
"unblockBtn": "Peer entblockieren",
|
"pendingLabel": "Bestätigung ausstehend",
|
||||||
"unlock": "Entsperren",
|
"acknowledgedLabel": "bestätigt",
|
||||||
"update": "",
|
"couldNotSendMsgError": "Nachricht konnte nicht gesendet werden",
|
||||||
"version": "Version %1",
|
"dmTooltip": "Klicken, um DM zu senden",
|
||||||
"versionBuilddate": "Version: %1 Aufgebaut auf: %2",
|
"membershipDescription": "Unten steht eine Liste der Benutzer, die Nachrichten an die Gruppe gesendet haben. Möglicherweise enthält diese Benutzerzliste nicht alle, die Zugang zur Gruppe haben.",
|
||||||
"versionTor": "Version %1 mit tor %2",
|
"addListItemBtn": "Element hinzufügen",
|
||||||
"viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen",
|
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
|
||||||
"viewServerInfo": "",
|
"searchList": "Search List",
|
||||||
"yourDisplayName": "Ihr Anzeigename",
|
"update": "Update",
|
||||||
"yourProfiles": "Ihre Profile",
|
"inviteBtn": "Einladen",
|
||||||
"yourServers": "Ihre Server",
|
"inviteToGroupLabel": "In die Gruppe einladen",
|
||||||
"zoomLabel": "Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)"
|
"groupNameLabel": "Gruppenname",
|
||||||
|
"viewServerInfo": "Server Info",
|
||||||
|
"serverSynced": "Synced",
|
||||||
|
"serverConnectivityDisconnected": "Server getrennt",
|
||||||
|
"serverConnectivityConnected": "Server verbunden",
|
||||||
|
"serverInfo": "Server-Informationen",
|
||||||
|
"invitationLabel": "Einladung",
|
||||||
|
"serverLabel": "Server",
|
||||||
|
"search": "Suche...",
|
||||||
|
"cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
|
||||||
|
"cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
|
||||||
|
"cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
|
||||||
|
"cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
|
||||||
|
"cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
|
||||||
|
"cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
|
||||||
|
"blocked": "Blockiert",
|
||||||
|
"titlePlaceholder": "Titel...",
|
||||||
|
"postNewBulletinLabel": "Neue Meldung veröffentlichen",
|
||||||
|
"newBulletinLabel": "Neue Meldung",
|
||||||
|
"joinGroup": "Gruppe beitreten",
|
||||||
|
"createGroup": "Gruppe erstellen",
|
||||||
|
"addPeer": "Peer hinzufügen",
|
||||||
|
"groupAddr": "Adresse",
|
||||||
|
"invitation": "Einladung",
|
||||||
|
"server": "Server",
|
||||||
|
"groupName": "Gruppenname",
|
||||||
|
"peerName": "Namen",
|
||||||
|
"peerAddress": "Adresse",
|
||||||
|
"joinGroupTab": "Einer Gruppe beitreten",
|
||||||
|
"createGroupTab": "Eine Gruppe erstellen",
|
||||||
|
"addPeerTab": "Einen Peer hinzufügen",
|
||||||
|
"createGroupBtn": "Anlegen",
|
||||||
|
"defaultGroupName": "Tolle Gruppe",
|
||||||
|
"createGroupTitle": "Gruppe Anlegen"
|
||||||
}
|
}
|
|
@ -1,160 +1,193 @@
|
||||||
{
|
{
|
||||||
"@@locale": "en",
|
"@@locale": "en",
|
||||||
"acceptGroupBtn": "Accept",
|
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||||
"acceptGroupInviteLabel": "Do you want to accept the invitation to",
|
"tooltipHidePassword": "Hide Password",
|
||||||
"acknowledgedLabel": "Acknowledged",
|
"tooltipShowPassword": "Show Password",
|
||||||
"addListItem": "Add a New List Item",
|
"serverNotSynced": "Syncing New Messages (This can take some time)...",
|
||||||
"addListItemBtn": "Add Item",
|
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||||
"addNewItem": "Add a new item to the list",
|
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||||
"addNewProfileBtn": "Add new profile",
|
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||||
"addPeer": "Add Peer",
|
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||||
"addPeerTab": "Add a peer",
|
"shutdownCwtchTooltip": "Shutdown Cwtch",
|
||||||
"addProfileTitle": "Add new profile",
|
"malformedMessage": "Malformed message",
|
||||||
"addressLabel": "Address",
|
"profileDeleteSuccess": "Successfully deleted profile",
|
||||||
"blockBtn": "Block Peer",
|
"debugLog": "Turn on console debug logging",
|
||||||
"blocked": "Blocked",
|
"torNetworkStatus": "Tor network status",
|
||||||
"blockUnknownLabel": "Block Unknown Peers",
|
"addContactFirst": "Add or pick a contact to begin chatting.",
|
||||||
"builddate": "Built on: %2",
|
"createProfileToBegin": "Please create or unlock a profile to begin",
|
||||||
"bulletinsBtn": "Bulletins",
|
"nickChangeSuccess": "Profile nickname changed successfully",
|
||||||
"chatBtn": "Chat",
|
"addServerFirst": "You need to add a server before you can create a group",
|
||||||
"contactAlreadyExists": "Contact Already Exists",
|
"deleteProfileSuccess": "Successfully deleted profile",
|
||||||
"conversationSettings": "Conversation Settings",
|
"sendInvite": "Send a contact or group invite",
|
||||||
"copiedClipboardNotification": "Copied to clipboard",
|
"sendMessage": "Send Message",
|
||||||
"copiedToClipboardNotification": "Copied to Clipboard",
|
"cancel": "Cancel",
|
||||||
"copyBtn": "Copy",
|
"resetTor": "Reset",
|
||||||
"couldNotSendMsgError": "Could not send this message",
|
"torStatus": "Tor Status",
|
||||||
"createGroup": "Create group",
|
"torVersion": "Tor Version",
|
||||||
"createGroupBtn": "Create",
|
"sendAnInvitation": "You sent an invitation for: ",
|
||||||
"createGroupTab": "Create a group",
|
"contactSuggestion": "This is a contact suggestion for: ",
|
||||||
"createGroupTitle": "Create Group",
|
"rejected": "Rejected!",
|
||||||
"createProfileBtn": "Create Profile",
|
"accepted": "Accepted!",
|
||||||
"currentPasswordLabel": "Current Password",
|
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
|
||||||
"cwtchSettingsTitle": "Cwtch Settings",
|
"newPassword": "New Password",
|
||||||
"cycleCatsAndroid": "Click to cycle category.\\nLong-press to reset.",
|
"yesLeave": "Yes, Leave This Conversation",
|
||||||
"cycleCatsDesktop": "Click to cycle category.\\nRight-click to reset.",
|
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||||
"cycleColoursAndroid": "Click to cycle colours.\\nLong-press to reset.",
|
"leaveGroup": "Leave This Conversation",
|
||||||
"cycleColoursDesktop": "Click to cycle colours.\\nRight-click to reset.",
|
"inviteToGroup": "You have been invited to join a group:",
|
||||||
"cycleMorphsAndroid": "Click to cycle morphs.\\nLong-press to reset.",
|
"pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation",
|
||||||
"cycleMorphsDesktop": "Click to cycle morphs.\\nRight-click to reset.",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"dateDaysAgo": "Days Ago",
|
"titleManageContacts": "Conversations",
|
||||||
"dateHoursAgo": "Hours Ago",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateLastMonth": "Last Month",
|
"dateMonthsAgo": "Months Ago",
|
||||||
"dateLastYear": "Last Year",
|
"dateNever": "Never",
|
||||||
"dateMinutesAgo": "Minutes Ago",
|
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
||||||
"dateMonthsAgo": "Months Ago",
|
"dateLastYear": "Last Year",
|
||||||
"dateNever": "Never",
|
"dateYesterday": "Yesterday",
|
||||||
"dateRightNow": "Right Now",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "Weeks Ago",
|
"dateWeeksAgo": "Weeks Ago",
|
||||||
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
"dateDaysAgo": "Days Ago",
|
||||||
"dateYesterday": "Yesterday",
|
"dateHoursAgo": "Hours Ago",
|
||||||
"defaultGroupName": "Awesome Group",
|
"dateMinutesAgo": "Minutes Ago",
|
||||||
"defaultProfileName": "Alice",
|
"dateRightNow": "Right Now",
|
||||||
"defaultScalingText": "Default size text (scale factor:",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"deleteBtn": "Delete",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
"deleteConfirmLabel": "Type DELETE to confirm",
|
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
|
||||||
"deleteConfirmText": "DELETE",
|
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
|
||||||
"deleteProfileBtn": "Delete Profile",
|
"titleManageProfiles": "Manage Cwtch Profiles",
|
||||||
"deleteProfileConfirmBtn": "Really Delete Profile",
|
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
||||||
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
"tooltipOpenSettings": "Open the settings pane",
|
||||||
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
|
"invalidImportString": "Invalid import string",
|
||||||
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
|
"contactAlreadyExists": "Contact Already Exists",
|
||||||
"displayNameLabel": "Display Name",
|
"conversationSettings": "Conversation Settings",
|
||||||
"dmTooltip": "Click to DM",
|
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
|
||||||
"dontSavePeerHistory": "Delete Peer History",
|
"enableGroups": "Enable Group Chat",
|
||||||
"editProfile": "Edit Profille",
|
"experimentsEnabled": "Enable Experiments",
|
||||||
"editProfileTitle": "Edit Profile",
|
"localeIt": "Italiana",
|
||||||
"enableGroups": "Enable Group Chat",
|
"localeEs": "Espanol",
|
||||||
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
|
"addListItem": "Add a New List Item",
|
||||||
"enterProfilePassword": "Enter a password to view your profiles",
|
"addNewItem": "Add a new item to the list",
|
||||||
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
|
"todoPlaceholder": "Todo...",
|
||||||
"experimentsEnabled": "Enable Experiments",
|
"newConnectionPaneTitle": "New Connection",
|
||||||
"groupAddr": "Address",
|
"networkStatusOnline": "Online",
|
||||||
"groupName": "Group name",
|
"networkStatusConnecting": "Connecting to network and peers...",
|
||||||
"groupNameLabel": "Group Name",
|
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
|
||||||
"invalidImportString": "Invalid import string",
|
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
|
||||||
"invitation": "Invitation",
|
"viewGroupMembershipTooltip": "View Group Membership",
|
||||||
"invitationLabel": "Invitation",
|
"loadingTor": "Loading tor...",
|
||||||
"inviteBtn": "Invite",
|
"smallTextLabel": "Small",
|
||||||
"inviteToGroupLabel": "Invite to group",
|
"defaultScalingText": "Default size text (scale factor:",
|
||||||
"joinGroup": "Join group",
|
"builddate": "Built on: %2",
|
||||||
"joinGroupTab": "Join a group",
|
"version": "Version %1",
|
||||||
"largeTextLabel": "Large",
|
"versionTor": "Version %1 with tor %2",
|
||||||
"listsBtn": "Lists",
|
"themeDark": "Dark",
|
||||||
"loadingTor": "Loading tor...",
|
"themeLight": "Light",
|
||||||
"localeDe": "Deutsche",
|
"settingTheme": "Theme",
|
||||||
"localeEn": "English",
|
"largeTextLabel": "Large",
|
||||||
"localeEs": "Espanol",
|
"settingInterfaceZoom": "Zoom level",
|
||||||
"localeFr": "Frances",
|
"localeDe": "Deutsche",
|
||||||
"localeIt": "Italiana",
|
"localePt": "Portuguesa",
|
||||||
"localePt": "Portuguesa",
|
"localeFr": "Frances",
|
||||||
"membershipDescription": "Below is a list of users who have sent messages to the group. This list may not reflect all users who have access to the group.",
|
"localeEn": "English",
|
||||||
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
|
"settingLanguage": "Language",
|
||||||
"networkStatusConnecting": "Connecting to network and peers...",
|
"blockUnknownLabel": "Block Unknown Peers",
|
||||||
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
|
"zoomLabel": "Interface zoom (mostly affects text and button sizes)",
|
||||||
"networkStatusOnline": "Online",
|
"versionBuilddate": "Version: %1 Built on: %2",
|
||||||
"newBulletinLabel": "New Bulletin",
|
"cwtchSettingsTitle": "Cwtch Settings",
|
||||||
"newConnectionPaneTitle": "New Connection",
|
"unlock": "Unlock",
|
||||||
"newGroupBtn": "Create new group",
|
"yourServers": "Your Servers",
|
||||||
"newProfile": "New Profile",
|
"yourProfiles": "Your Profiles",
|
||||||
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
|
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"password1Label": "Password",
|
"enterProfilePassword": "Enter a password to view your profiles",
|
||||||
"password2Label": "Reenter password",
|
"addNewProfileBtn": "Add new profile",
|
||||||
"passwordChangeError": "Error changing password: Supplied password rejected",
|
"deleteConfirmText": "DELETE",
|
||||||
"passwordErrorEmpty": "Password cannot be empty",
|
"deleteProfileConfirmBtn": "Really Delete Profile",
|
||||||
"passwordErrorMatch": "Passwords do not match",
|
"deleteConfirmLabel": "Type DELETE to confirm",
|
||||||
"pasteAddressToAddContact": "Paste a cwtch address here to add a new contact.",
|
"deleteProfileBtn": "Delete Profile",
|
||||||
"peerAddress": "Address",
|
"passwordChangeError": "Error changing password: Supplied password rejected",
|
||||||
"peerBlockedMessage": "Peer is blocked",
|
"passwordErrorMatch": "Passwords do not match",
|
||||||
"peerName": "Name",
|
"saveProfileBtn": "Save Profile",
|
||||||
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
|
"createProfileBtn": "Create Profile",
|
||||||
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
|
"passwordErrorEmpty": "Password cannot be empty",
|
||||||
"pendingLabel": "Pending",
|
"password2Label": "Reenter password",
|
||||||
"postNewBulletinLabel": "Post new bulletin",
|
"password1Label": "Password",
|
||||||
"profileName": "Display name",
|
"currentPasswordLabel": "Current Password",
|
||||||
"profileOnionLabel": "Send this address to peers you want to connect with",
|
"yourDisplayName": "Your Display Name",
|
||||||
"puzzleGameBtn": "Puzzle Game",
|
"profileOnionLabel": "Send this address to peers you want to connect with",
|
||||||
"radioNoPassword": "Unencrypted (No password)",
|
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
|
||||||
"radioUsePassword": "Password",
|
"radioNoPassword": "Unencrypted (No password)",
|
||||||
"rejectGroupBtn": "Reject",
|
"radioUsePassword": "Password",
|
||||||
"saveBtn": "Save",
|
"copiedToClipboardNotification": "Copied to Clipboard",
|
||||||
"savePeerHistory": "Save Peer History",
|
"copyBtn": "Copy",
|
||||||
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
|
"editProfile": "Edit Profille",
|
||||||
"saveProfileBtn": "Save Profile",
|
"newProfile": "New Profile",
|
||||||
"search": "Search...",
|
"defaultProfileName": "Alice",
|
||||||
"searchList": "Search List",
|
"profileName": "Display name",
|
||||||
"server": "Server",
|
"editProfileTitle": "Edit Profile",
|
||||||
"serverConnectivityConnected": "Server Connected",
|
"addProfileTitle": "Add new profile",
|
||||||
"serverConnectivityDisconnected": "Server Disconnected",
|
"deleteBtn": "Delete",
|
||||||
"serverInfo": "Server Information",
|
"unblockBtn": "Unblock Peer",
|
||||||
"serverLabel": "Server",
|
"dontSavePeerHistory": "Delete Peer History",
|
||||||
"serverNotSynced": "Out of Sync",
|
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
|
||||||
"serverSynced": "Synced",
|
"savePeerHistory": "Save Peer History",
|
||||||
"settingInterfaceZoom": "Zoom level",
|
"blockBtn": "Block Peer",
|
||||||
"settingLanguage": "Language",
|
"saveBtn": "Save",
|
||||||
"settingTheme": "Theme",
|
"displayNameLabel": "Display Name",
|
||||||
"smallTextLabel": "Small",
|
"addressLabel": "Address",
|
||||||
"successfullAddedContact": "Successfully added ",
|
"puzzleGameBtn": "Puzzle Game",
|
||||||
"themeDark": "Dark",
|
"bulletinsBtn": "Bulletins",
|
||||||
"themeLight": "Light",
|
"listsBtn": "Lists",
|
||||||
"titleManageContacts": "Manage Contacts",
|
"chatBtn": "Chat",
|
||||||
"titleManageProfiles": "Manage Cwtch Profiles",
|
"rejectGroupBtn": "Reject",
|
||||||
"titleManageServers": "Manage Servers",
|
"acceptGroupBtn": "Accept",
|
||||||
"titlePlaceholder": "title...",
|
"acceptGroupInviteLabel": "Do you want to accept the invitation to",
|
||||||
"todoPlaceholder": "Todo...",
|
"newGroupBtn": "Create new group",
|
||||||
"tooltipAddContact": "Add a new contact",
|
"copiedClipboardNotification": "Copied to clipboard",
|
||||||
"tooltipOpenSettings": "Open the settings pane",
|
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
|
||||||
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
"peerBlockedMessage": "Peer is blocked",
|
||||||
"unblockBtn": "Unblock Peer",
|
"pendingLabel": "Pending",
|
||||||
"unlock": "Unlock",
|
"acknowledgedLabel": "Acknowledged",
|
||||||
"update": "Update",
|
"couldNotSendMsgError": "Could not send this message",
|
||||||
"version": "Version %1",
|
"dmTooltip": "Click to DM",
|
||||||
"versionBuilddate": "Version: %1 Built on: %2",
|
"membershipDescription": "Below is a list of users who have sent messages to the group. This list may not reflect all users who have access to the group.",
|
||||||
"versionTor": "Version %1 with tor %2",
|
"addListItemBtn": "Add Item",
|
||||||
"viewGroupMembershipTooltip": "View Group Membership",
|
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
|
||||||
"viewServerInfo": "Server Info",
|
"searchList": "Search List",
|
||||||
"yourDisplayName": "Your Display Name",
|
"update": "Update",
|
||||||
"yourProfiles": "Your Profiles",
|
"inviteBtn": "Invite",
|
||||||
"yourServers": "Your Servers",
|
"inviteToGroupLabel": "Invite to group",
|
||||||
"zoomLabel": "Interface zoom (mostly affects text and button sizes)"
|
"groupNameLabel": "Group Name",
|
||||||
|
"viewServerInfo": "Server Info",
|
||||||
|
"serverSynced": "Synced",
|
||||||
|
"serverConnectivityDisconnected": "Server Disconnected",
|
||||||
|
"serverConnectivityConnected": "Server Connected",
|
||||||
|
"serverInfo": "Server Information",
|
||||||
|
"invitationLabel": "Invitation",
|
||||||
|
"serverLabel": "Server",
|
||||||
|
"search": "Search...",
|
||||||
|
"cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
|
||||||
|
"cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
|
||||||
|
"cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
|
||||||
|
"cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
|
||||||
|
"cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
|
||||||
|
"cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
|
||||||
|
"blocked": "Blocked",
|
||||||
|
"titlePlaceholder": "title...",
|
||||||
|
"postNewBulletinLabel": "Post new bulletin",
|
||||||
|
"newBulletinLabel": "New Bulletin",
|
||||||
|
"joinGroup": "Join group",
|
||||||
|
"createGroup": "Create group",
|
||||||
|
"addPeer": "Add Peer",
|
||||||
|
"groupAddr": "Address",
|
||||||
|
"invitation": "Invitation",
|
||||||
|
"server": "Server",
|
||||||
|
"groupName": "Group name",
|
||||||
|
"peerName": "Name",
|
||||||
|
"peerAddress": "Address",
|
||||||
|
"joinGroupTab": "Join a group",
|
||||||
|
"createGroupTab": "Create a group",
|
||||||
|
"addPeerTab": "Add a peer",
|
||||||
|
"createGroupBtn": "Create",
|
||||||
|
"defaultGroupName": "Awesome Group",
|
||||||
|
"createGroupTitle": "Create Group"
|
||||||
}
|
}
|
|
@ -1,160 +1,193 @@
|
||||||
{
|
{
|
||||||
"@@locale": "es",
|
"@@locale": "es",
|
||||||
"acceptGroupBtn": "Aceptar",
|
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||||
"acceptGroupInviteLabel": "¿Quieres aceptar la invitación a ",
|
"tooltipHidePassword": "Hide Password",
|
||||||
"acknowledgedLabel": "Reconocido",
|
"tooltipShowPassword": "Show Password",
|
||||||
"addListItem": "Añadir un nuevo elemento a la lista",
|
"serverNotSynced": "Fuera de sincronización con el servidor",
|
||||||
"addListItemBtn": "Agregar artículo",
|
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||||
"addNewItem": "Añadir un nuevo elemento a la lista",
|
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||||
"addNewProfileBtn": "Agregar nuevo perfil",
|
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||||
"addPeer": "Agregar Contacto",
|
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||||
"addPeerTab": "Agregar Contacto",
|
"shutdownCwtchTooltip": "Shutdown Cwtch",
|
||||||
"addProfileTitle": "Agregar nuevo perfil",
|
"malformedMessage": "Malformed message",
|
||||||
"addressLabel": "Dirección",
|
"profileDeleteSuccess": "Successfully deleted profile",
|
||||||
"blockBtn": "Bloquear contacto",
|
"debugLog": "Turn on console debug logging",
|
||||||
"blocked": "Bloqueado",
|
"torNetworkStatus": "Tor network status",
|
||||||
"blockUnknownLabel": "Bloquear conexiones desconocidas",
|
"addContactFirst": "Add or pick a contact to begin chatting.",
|
||||||
"builddate": "Basado en: %2",
|
"createProfileToBegin": "Please create or unlock a profile to begin",
|
||||||
"bulletinsBtn": "Boletines",
|
"nickChangeSuccess": "Profile nickname changed successfully",
|
||||||
"chatBtn": "Chat",
|
"addServerFirst": "You need to add a server before you can create a group",
|
||||||
"contactAlreadyExists": "",
|
"deleteProfileSuccess": "Successfully deleted profile",
|
||||||
"conversationSettings": "",
|
"sendInvite": "Send a contact or group invite",
|
||||||
"copiedClipboardNotification": "Copiado al portapapeles",
|
"sendMessage": "Send Message",
|
||||||
"copiedToClipboardNotification": "Copiado al portapapeles",
|
"cancel": "Cancel",
|
||||||
"copyBtn": "Copiar",
|
"resetTor": "Reset",
|
||||||
"couldNotSendMsgError": "No se pudo enviar este mensaje",
|
"torStatus": "Tor Status",
|
||||||
"createGroup": "Crear perfil",
|
"torVersion": "Tor Version",
|
||||||
"createGroupBtn": "Crear",
|
"sendAnInvitation": "You sent an invitation for: ",
|
||||||
"createGroupTab": "Crear un grupo",
|
"contactSuggestion": "This is a contact suggestion for: ",
|
||||||
"createGroupTitle": "Crear un grupo",
|
"rejected": "Rejected!",
|
||||||
"createProfileBtn": "Crear perfil",
|
"accepted": "Accepted!",
|
||||||
"currentPasswordLabel": "Contraseña actual",
|
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
|
||||||
"cwtchSettingsTitle": "Configuración de Cwtch",
|
"newPassword": "New Password",
|
||||||
"cycleCatsAndroid": "Click para cambiar categoría. Mantenga pulsado para reiniciar.",
|
"yesLeave": "Yes, Leave This Conversation",
|
||||||
"cycleCatsDesktop": "Click para cambiar categoría. Click derecho para reiniciar.",
|
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||||
"cycleColoursAndroid": "Click para cambiar colores. Mantenga pulsado para reiniciar.",
|
"leaveGroup": "Leave This Conversation",
|
||||||
"cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.",
|
"inviteToGroup": "You have been invited to join a group:",
|
||||||
"cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.",
|
"pasteAddressToAddContact": "...pegar una dirección aquí para añadir contacto...",
|
||||||
"cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"dateDaysAgo": "",
|
"titleManageContacts": "Conversations",
|
||||||
"dateHoursAgo": "",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateLastMonth": "",
|
"dateMonthsAgo": "Months Ago",
|
||||||
"dateLastYear": "",
|
"dateNever": "Never",
|
||||||
"dateMinutesAgo": "",
|
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
||||||
"dateMonthsAgo": "",
|
"dateLastYear": "Last Year",
|
||||||
"dateNever": "",
|
"dateYesterday": "Yesterday",
|
||||||
"dateRightNow": "",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "",
|
"dateWeeksAgo": "Weeks Ago",
|
||||||
"dateYearsAgo": "",
|
"dateDaysAgo": "Days Ago",
|
||||||
"dateYesterday": "",
|
"dateHoursAgo": "Hours Ago",
|
||||||
"defaultGroupName": "El Grupo Asombroso",
|
"dateMinutesAgo": "Minutes Ago",
|
||||||
"defaultProfileName": "Alicia",
|
"dateRightNow": "Right Now",
|
||||||
"defaultScalingText": "Tamaño predeterminado de texto (factor de escala:",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"deleteBtn": "Eliminar",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
"deleteConfirmLabel": "Escribe ELIMINAR para confirmar",
|
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
|
||||||
"deleteConfirmText": "ELIMINAR",
|
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
|
||||||
"deleteProfileBtn": "Eliminar Perfil",
|
"titleManageProfiles": "Manage Cwtch Profiles",
|
||||||
"deleteProfileConfirmBtn": "Confirmar eliminar perfil",
|
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
||||||
"descriptionBlockUnknownConnections": "",
|
"tooltipOpenSettings": "Open the settings pane",
|
||||||
"descriptionExperiments": "",
|
"invalidImportString": "Invalid import string",
|
||||||
"descriptionExperimentsGroups": "",
|
"contactAlreadyExists": "Contact Already Exists",
|
||||||
"displayNameLabel": "Nombre de Usuario",
|
"conversationSettings": "Conversation Settings",
|
||||||
"dmTooltip": "Haz clic para enviar mensaje directo",
|
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
|
||||||
"dontSavePeerHistory": "Eliminar historial de contacto",
|
"enableGroups": "Enable Group Chat",
|
||||||
"editProfile": "Editar perfil",
|
"experimentsEnabled": "Experimentos habilitados",
|
||||||
"editProfileTitle": "Editar perfil",
|
"localeIt": "Italiano",
|
||||||
"enableGroups": "",
|
"localeEs": "Español",
|
||||||
"enterCurrentPasswordForDelete": "",
|
"addListItem": "Añadir un nuevo elemento a la lista",
|
||||||
"enterProfilePassword": "Ingresa tu contraseña para ver tus perfiles",
|
"addNewItem": "Añadir un nuevo elemento a la lista",
|
||||||
"error0ProfilesLoadedForPassword": "0 perfiles cargados con esa contraseña",
|
"todoPlaceholder": "Por hacer...",
|
||||||
"experimentsEnabled": "Experimentos habilitados",
|
"newConnectionPaneTitle": "Nueva conexión",
|
||||||
"groupAddr": "Dirección",
|
"networkStatusOnline": "En línea",
|
||||||
"groupName": "Nombre del grupo",
|
"networkStatusConnecting": "Conectando a la red y a los contactos...",
|
||||||
"groupNameLabel": "Nombre del grupo",
|
"networkStatusAttemptingTor": "Intentando conectarse a la red Tor",
|
||||||
"invalidImportString": "",
|
"networkStatusDisconnected": "Sin conexión, comprueba tu conexión",
|
||||||
"invitation": "Invitación",
|
"viewGroupMembershipTooltip": "Ver membresía del grupo",
|
||||||
"invitationLabel": "Invitación",
|
"loadingTor": "Cargando tor...",
|
||||||
"inviteBtn": "Invitar",
|
"smallTextLabel": "Pequeño",
|
||||||
"inviteToGroupLabel": "Invitar al grupo",
|
"defaultScalingText": "Tamaño predeterminado de texto (factor de escala:",
|
||||||
"joinGroup": "Únete al grupo",
|
"builddate": "Basado en: %2",
|
||||||
"joinGroupTab": "Únete a un grupo",
|
"version": "Versión %1",
|
||||||
"largeTextLabel": "Grande",
|
"versionTor": "Versión %1 con tor %2",
|
||||||
"listsBtn": "Listas",
|
"themeDark": "Oscuro",
|
||||||
"loadingTor": "Cargando tor...",
|
"themeLight": "Claro",
|
||||||
"localeDe": "Alemán",
|
"settingTheme": "Tema",
|
||||||
"localeEn": "Inglés",
|
"largeTextLabel": "Grande",
|
||||||
"localeEs": "Español",
|
"settingInterfaceZoom": "Nivel de zoom",
|
||||||
"localeFr": "Francés",
|
"localeDe": "Alemán",
|
||||||
"localeIt": "Italiano",
|
"localePt": "Portugués",
|
||||||
"localePt": "Portugués",
|
"localeFr": "Francés",
|
||||||
"membershipDescription": "La lista a continuación solo muestra los miembros que han enviado mensajes al grupo, no incluye a todos los usuarios dentro del grupo",
|
"localeEn": "Inglés",
|
||||||
"networkStatusAttemptingTor": "Intentando conectarse a la red Tor",
|
"settingLanguage": "Idioma",
|
||||||
"networkStatusConnecting": "Conectando a la red y a los contactos...",
|
"blockUnknownLabel": "Bloquear conexiones desconocidas",
|
||||||
"networkStatusDisconnected": "Sin conexión, comprueba tu conexión",
|
"zoomLabel": "Zoom de la interfaz (afecta principalmente el tamaño del texto y de los botones)",
|
||||||
"networkStatusOnline": "En línea",
|
"versionBuilddate": "Versión: %1 Basado en %2",
|
||||||
"newBulletinLabel": "Nuevo Boletín",
|
"cwtchSettingsTitle": "Configuración de Cwtch",
|
||||||
"newConnectionPaneTitle": "Nueva conexión",
|
"unlock": "Desbloquear",
|
||||||
"newGroupBtn": "Crear un nuevo grupo de chat",
|
"yourServers": "Tus servidores",
|
||||||
"newProfile": "Nuevo perfil",
|
"yourProfiles": "Tus perfiles",
|
||||||
"noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados",
|
"error0ProfilesLoadedForPassword": "0 perfiles cargados con esa contraseña",
|
||||||
"password": "Contraseña",
|
"password": "Contraseña",
|
||||||
"password1Label": "Contraseña",
|
"enterProfilePassword": "Ingresa tu contraseña para ver tus perfiles",
|
||||||
"password2Label": "Vuelve a ingresar tu contraseña",
|
"addNewProfileBtn": "Agregar nuevo perfil",
|
||||||
"passwordChangeError": "Hubo un error cambiando tu contraseña: la contraseña ingresada fue rechazada",
|
"deleteConfirmText": "ELIMINAR",
|
||||||
"passwordErrorEmpty": "El campo de contraseña no puede estar vacío",
|
"deleteProfileConfirmBtn": "Confirmar eliminar perfil",
|
||||||
"passwordErrorMatch": "Las contraseñas no coinciden",
|
"deleteConfirmLabel": "Escribe ELIMINAR para confirmar",
|
||||||
"pasteAddressToAddContact": "...pegar una dirección aquí para añadir contacto...",
|
"deleteProfileBtn": "Eliminar Perfil",
|
||||||
"peerAddress": "Dirección",
|
"passwordChangeError": "Hubo un error cambiando tu contraseña: la contraseña ingresada fue rechazada",
|
||||||
"peerBlockedMessage": "Contacto bloqueado",
|
"passwordErrorMatch": "Las contraseñas no coinciden",
|
||||||
"peerName": "Nombre",
|
"saveProfileBtn": "Guardar perfil",
|
||||||
"peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento",
|
"createProfileBtn": "Crear perfil",
|
||||||
"peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento",
|
"passwordErrorEmpty": "El campo de contraseña no puede estar vacío",
|
||||||
"pendingLabel": "Pendiente",
|
"password2Label": "Vuelve a ingresar tu contraseña",
|
||||||
"postNewBulletinLabel": "Publicar nuevo boletín",
|
"password1Label": "Contraseña",
|
||||||
"profileName": "Nombre de Usuario",
|
"currentPasswordLabel": "Contraseña actual",
|
||||||
"profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte",
|
"yourDisplayName": "Tu nombre de usuario",
|
||||||
"puzzleGameBtn": "Juego de rompecabezas",
|
"profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte",
|
||||||
"radioNoPassword": "Sin cifrado (sin contraseña)",
|
"noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados",
|
||||||
"radioUsePassword": "Contraseña",
|
"radioNoPassword": "Sin cifrado (sin contraseña)",
|
||||||
"rejectGroupBtn": "Rechazar",
|
"radioUsePassword": "Contraseña",
|
||||||
"saveBtn": "Guardar",
|
"copiedToClipboardNotification": "Copiado al portapapeles",
|
||||||
"savePeerHistory": "Guardar el historial con contacto",
|
"copyBtn": "Copiar",
|
||||||
"savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.",
|
"editProfile": "Editar perfil",
|
||||||
"saveProfileBtn": "Guardar perfil",
|
"newProfile": "Nuevo perfil",
|
||||||
"search": "Búsqueda...",
|
"defaultProfileName": "Alicia",
|
||||||
"searchList": "Buscar en la lista",
|
"profileName": "Nombre de Usuario",
|
||||||
"server": "Servidor",
|
"editProfileTitle": "Editar perfil",
|
||||||
"serverConnectivityConnected": "Servidor conectado",
|
"addProfileTitle": "Agregar nuevo perfil",
|
||||||
"serverConnectivityDisconnected": "Servidor desconectado",
|
"deleteBtn": "Eliminar",
|
||||||
"serverInfo": "Información del servidor",
|
"unblockBtn": "Desbloquear contacto",
|
||||||
"serverLabel": "Servidor",
|
"dontSavePeerHistory": "Eliminar historial de contacto",
|
||||||
"serverNotSynced": "Fuera de sincronización con el servidor",
|
"savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.",
|
||||||
"serverSynced": "Sincronizado",
|
"savePeerHistory": "Guardar el historial con contacto",
|
||||||
"settingInterfaceZoom": "Nivel de zoom",
|
"blockBtn": "Bloquear contacto",
|
||||||
"settingLanguage": "Idioma",
|
"saveBtn": "Guardar",
|
||||||
"settingTheme": "Tema",
|
"displayNameLabel": "Nombre de Usuario",
|
||||||
"smallTextLabel": "Pequeño",
|
"addressLabel": "Dirección",
|
||||||
"successfullAddedContact": "",
|
"puzzleGameBtn": "Juego de rompecabezas",
|
||||||
"themeDark": "Oscuro",
|
"bulletinsBtn": "Boletines",
|
||||||
"themeLight": "Claro",
|
"listsBtn": "Listas",
|
||||||
"titleManageContacts": "",
|
"chatBtn": "Chat",
|
||||||
"titleManageProfiles": "",
|
"rejectGroupBtn": "Rechazar",
|
||||||
"titleManageServers": "",
|
"acceptGroupBtn": "Aceptar",
|
||||||
"titlePlaceholder": "título...",
|
"acceptGroupInviteLabel": "¿Quieres aceptar la invitación a ",
|
||||||
"todoPlaceholder": "Por hacer...",
|
"newGroupBtn": "Crear un nuevo grupo de chat",
|
||||||
"tooltipAddContact": "",
|
"copiedClipboardNotification": "Copiado al portapapeles",
|
||||||
"tooltipOpenSettings": "",
|
"peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento",
|
||||||
"tooltipUnlockProfiles": "",
|
"peerBlockedMessage": "Contacto bloqueado",
|
||||||
"unblockBtn": "Desbloquear contacto",
|
"pendingLabel": "Pendiente",
|
||||||
"unlock": "Desbloquear",
|
"acknowledgedLabel": "Reconocido",
|
||||||
"update": "Actualizar",
|
"couldNotSendMsgError": "No se pudo enviar este mensaje",
|
||||||
"version": "Versión %1",
|
"dmTooltip": "Haz clic para enviar mensaje directo",
|
||||||
"versionBuilddate": "Versión: %1 Basado en %2",
|
"membershipDescription": "La lista a continuación solo muestra los miembros que han enviado mensajes al grupo, no incluye a todos los usuarios dentro del grupo",
|
||||||
"versionTor": "Versión %1 con tor %2",
|
"addListItemBtn": "Agregar artículo",
|
||||||
"viewGroupMembershipTooltip": "Ver membresía del grupo",
|
"peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento",
|
||||||
"viewServerInfo": "Información del servidor",
|
"searchList": "Buscar en la lista",
|
||||||
"yourDisplayName": "Tu nombre de usuario",
|
"update": "Actualizar",
|
||||||
"yourProfiles": "Tus perfiles",
|
"inviteBtn": "Invitar",
|
||||||
"yourServers": "Tus servidores",
|
"inviteToGroupLabel": "Invitar al grupo",
|
||||||
"zoomLabel": "Zoom de la interfaz (afecta principalmente el tamaño del texto y de los botones)"
|
"groupNameLabel": "Nombre del grupo",
|
||||||
|
"viewServerInfo": "Información del servidor",
|
||||||
|
"serverSynced": "Sincronizado",
|
||||||
|
"serverConnectivityDisconnected": "Servidor desconectado",
|
||||||
|
"serverConnectivityConnected": "Servidor conectado",
|
||||||
|
"serverInfo": "Información del servidor",
|
||||||
|
"invitationLabel": "Invitación",
|
||||||
|
"serverLabel": "Servidor",
|
||||||
|
"search": "Búsqueda...",
|
||||||
|
"cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.",
|
||||||
|
"cycleColoursAndroid": "Click para cambiar colores. Mantenga pulsado para reiniciar.",
|
||||||
|
"cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.",
|
||||||
|
"cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.",
|
||||||
|
"cycleCatsDesktop": "Click para cambiar categoría. Click derecho para reiniciar.",
|
||||||
|
"cycleCatsAndroid": "Click para cambiar categoría. Mantenga pulsado para reiniciar.",
|
||||||
|
"blocked": "Bloqueado",
|
||||||
|
"titlePlaceholder": "título...",
|
||||||
|
"postNewBulletinLabel": "Publicar nuevo boletín",
|
||||||
|
"newBulletinLabel": "Nuevo Boletín",
|
||||||
|
"joinGroup": "Únete al grupo",
|
||||||
|
"createGroup": "Crear perfil",
|
||||||
|
"addPeer": "Agregar Contacto",
|
||||||
|
"groupAddr": "Dirección",
|
||||||
|
"invitation": "Invitación",
|
||||||
|
"server": "Servidor",
|
||||||
|
"groupName": "Nombre del grupo",
|
||||||
|
"peerName": "Nombre",
|
||||||
|
"peerAddress": "Dirección",
|
||||||
|
"joinGroupTab": "Únete a un grupo",
|
||||||
|
"createGroupTab": "Crear un grupo",
|
||||||
|
"addPeerTab": "Agregar Contacto",
|
||||||
|
"createGroupBtn": "Crear",
|
||||||
|
"defaultGroupName": "El Grupo Asombroso",
|
||||||
|
"createGroupTitle": "Crear un grupo"
|
||||||
}
|
}
|
|
@ -1,160 +1,193 @@
|
||||||
{
|
{
|
||||||
"@@locale": "fr",
|
"@@locale": "fr",
|
||||||
"acceptGroupBtn": "Accepter",
|
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||||
"acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe",
|
"tooltipHidePassword": "Hide Password",
|
||||||
"acknowledgedLabel": "Confirmé",
|
"tooltipShowPassword": "Show Password",
|
||||||
"addListItem": "Ajouter un nouvel élément",
|
"serverNotSynced": "Syncing New Messages (This can take some time)...",
|
||||||
"addListItemBtn": "",
|
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||||
"addNewItem": "Ajouter un nouvel élément à la liste",
|
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||||
"addNewProfileBtn": "",
|
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||||
"addPeer": "",
|
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||||
"addPeerTab": "",
|
"shutdownCwtchTooltip": "Shutdown Cwtch",
|
||||||
"addProfileTitle": "",
|
"malformedMessage": "Malformed message",
|
||||||
"addressLabel": "Adresse",
|
"profileDeleteSuccess": "Successfully deleted profile",
|
||||||
"blockBtn": "",
|
"debugLog": "Turn on console debug logging",
|
||||||
"blocked": "",
|
"torNetworkStatus": "Tor network status",
|
||||||
"blockUnknownLabel": "",
|
"addContactFirst": "Add or pick a contact to begin chatting.",
|
||||||
"builddate": "",
|
"createProfileToBegin": "Please create or unlock a profile to begin",
|
||||||
"bulletinsBtn": "Bulletins",
|
"nickChangeSuccess": "Profile nickname changed successfully",
|
||||||
"chatBtn": "Discuter",
|
"addServerFirst": "You need to add a server before you can create a group",
|
||||||
"contactAlreadyExists": "",
|
"deleteProfileSuccess": "Successfully deleted profile",
|
||||||
"conversationSettings": "",
|
"sendInvite": "Send a contact or group invite",
|
||||||
"copiedClipboardNotification": "Copié dans le presse-papier",
|
"sendMessage": "Send Message",
|
||||||
"copiedToClipboardNotification": "Copié dans le presse-papier",
|
"cancel": "Cancel",
|
||||||
"copyBtn": "Copier",
|
"resetTor": "Reset",
|
||||||
"couldNotSendMsgError": "Impossible d'envoyer ce message",
|
"torStatus": "Tor Status",
|
||||||
"createGroup": "",
|
"torVersion": "Tor Version",
|
||||||
"createGroupBtn": "Créer",
|
"sendAnInvitation": "You sent an invitation for: ",
|
||||||
"createGroupTab": "",
|
"contactSuggestion": "This is a contact suggestion for: ",
|
||||||
"createGroupTitle": "Créer un groupe",
|
"rejected": "Rejected!",
|
||||||
"createProfileBtn": "",
|
"accepted": "Accepted!",
|
||||||
"currentPasswordLabel": "",
|
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
|
||||||
"cwtchSettingsTitle": "Préférences Cwtch",
|
"newPassword": "New Password",
|
||||||
"cycleCatsAndroid": "",
|
"yesLeave": "Yes, Leave This Conversation",
|
||||||
"cycleCatsDesktop": "",
|
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||||
"cycleColoursAndroid": "",
|
"leaveGroup": "Leave This Conversation",
|
||||||
"cycleColoursDesktop": "",
|
"inviteToGroup": "You have been invited to join a group:",
|
||||||
"cycleMorphsAndroid": "",
|
"pasteAddressToAddContact": "... coller une adresse ici pour ajouter un contact...",
|
||||||
"cycleMorphsDesktop": "",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"dateDaysAgo": "",
|
"titleManageContacts": "Conversations",
|
||||||
"dateHoursAgo": "",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateLastMonth": "",
|
"dateMonthsAgo": "Months Ago",
|
||||||
"dateLastYear": "",
|
"dateNever": "Never",
|
||||||
"dateMinutesAgo": "",
|
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
||||||
"dateMonthsAgo": "",
|
"dateLastYear": "Last Year",
|
||||||
"dateNever": "",
|
"dateYesterday": "Yesterday",
|
||||||
"dateRightNow": "",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "",
|
"dateWeeksAgo": "Weeks Ago",
|
||||||
"dateYearsAgo": "",
|
"dateDaysAgo": "Days Ago",
|
||||||
"dateYesterday": "",
|
"dateHoursAgo": "Hours Ago",
|
||||||
"defaultGroupName": "Un super groupe",
|
"dateMinutesAgo": "Minutes Ago",
|
||||||
"defaultProfileName": "",
|
"dateRightNow": "Right Now",
|
||||||
"defaultScalingText": "Taille par défaut du texte (échelle:",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"deleteBtn": "Effacer",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
"deleteConfirmLabel": "",
|
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
|
||||||
"deleteConfirmText": "",
|
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
|
||||||
"deleteProfileBtn": "",
|
"titleManageProfiles": "Manage Cwtch Profiles",
|
||||||
"deleteProfileConfirmBtn": "",
|
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
||||||
"descriptionBlockUnknownConnections": "",
|
"tooltipOpenSettings": "Open the settings pane",
|
||||||
"descriptionExperiments": "",
|
"invalidImportString": "Invalid import string",
|
||||||
"descriptionExperimentsGroups": "",
|
"contactAlreadyExists": "Contact Already Exists",
|
||||||
"displayNameLabel": "Pseudo",
|
"conversationSettings": "Conversation Settings",
|
||||||
"dmTooltip": "Envoyer un message privé",
|
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
|
||||||
"dontSavePeerHistory": "",
|
"enableGroups": "Enable Group Chat",
|
||||||
"editProfile": "",
|
"experimentsEnabled": "Enable Experiments",
|
||||||
"editProfileTitle": "",
|
"localeIt": "Italiana",
|
||||||
"enableGroups": "",
|
"localeEs": "Espanol",
|
||||||
"enterCurrentPasswordForDelete": "",
|
"addListItem": "Ajouter un nouvel élément",
|
||||||
"enterProfilePassword": "",
|
"addNewItem": "Ajouter un nouvel élément à la liste",
|
||||||
"error0ProfilesLoadedForPassword": "",
|
"todoPlaceholder": "A faire...",
|
||||||
"experimentsEnabled": "",
|
"newConnectionPaneTitle": "New Connection",
|
||||||
"groupAddr": "",
|
"networkStatusOnline": "Online",
|
||||||
"groupName": "",
|
"networkStatusConnecting": "Connecting to network and peers...",
|
||||||
"groupNameLabel": "Nom du groupe",
|
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
|
||||||
"invalidImportString": "",
|
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
|
||||||
"invitation": "",
|
"viewGroupMembershipTooltip": "View Group Membership",
|
||||||
"invitationLabel": "Invitation",
|
"loadingTor": "Loading tor...",
|
||||||
"inviteBtn": "Invitation",
|
"smallTextLabel": "Petit",
|
||||||
"inviteToGroupLabel": "Inviter quelqu'un",
|
"defaultScalingText": "Taille par défaut du texte (échelle:",
|
||||||
"joinGroup": "",
|
"builddate": "Built on: %2",
|
||||||
"joinGroupTab": "",
|
"version": "Version %1",
|
||||||
"largeTextLabel": "Large",
|
"versionTor": "Version %1 with tor %2",
|
||||||
"listsBtn": "Listes",
|
"themeDark": "Dark",
|
||||||
"loadingTor": "",
|
"themeLight": "Light",
|
||||||
"localeDe": "",
|
"settingTheme": "Theme",
|
||||||
"localeEn": "",
|
"largeTextLabel": "Large",
|
||||||
"localeEs": "",
|
"settingInterfaceZoom": "Zoom level",
|
||||||
"localeFr": "",
|
"localeDe": "Deutsche",
|
||||||
"localeIt": "",
|
"localePt": "Portuguesa",
|
||||||
"localePt": "",
|
"localeFr": "Frances",
|
||||||
"membershipDescription": "Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être representatives de l'ensemble des membres du groupe.",
|
"localeEn": "English",
|
||||||
"networkStatusAttemptingTor": "",
|
"settingLanguage": "Language",
|
||||||
"networkStatusConnecting": "",
|
"blockUnknownLabel": "Block Unknown Peers",
|
||||||
"networkStatusDisconnected": "",
|
"zoomLabel": "Interface zoom (essentiellement la taille du texte et des composants de l'interface)",
|
||||||
"networkStatusOnline": "",
|
"versionBuilddate": "Version: %1 Built on: %2",
|
||||||
"newBulletinLabel": "Nouveau bulletin",
|
"cwtchSettingsTitle": "Préférences Cwtch",
|
||||||
"newConnectionPaneTitle": "",
|
"unlock": "Unlock",
|
||||||
"newGroupBtn": "Créer un nouveau groupe",
|
"yourServers": "Your Servers",
|
||||||
"newProfile": "",
|
"yourProfiles": "Your Profiles",
|
||||||
"noPasswordWarning": "",
|
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
|
||||||
"password": "",
|
"password": "Password",
|
||||||
"password1Label": "",
|
"enterProfilePassword": "Enter a password to view your profiles",
|
||||||
"password2Label": "",
|
"addNewProfileBtn": "Add new profile",
|
||||||
"passwordChangeError": "",
|
"deleteConfirmText": "DELETE",
|
||||||
"passwordErrorEmpty": "",
|
"deleteProfileConfirmBtn": "Really Delete Profile",
|
||||||
"passwordErrorMatch": "",
|
"deleteConfirmLabel": "Type DELETE to confirm",
|
||||||
"pasteAddressToAddContact": "... coller une adresse ici pour ajouter un contact...",
|
"deleteProfileBtn": "Delete Profile",
|
||||||
"peerAddress": "",
|
"passwordChangeError": "Error changing password: Supplied password rejected",
|
||||||
"peerBlockedMessage": "",
|
"passwordErrorMatch": "Passwords do not match",
|
||||||
"peerName": "",
|
"saveProfileBtn": "Save Profile",
|
||||||
"peerNotOnline": "",
|
"createProfileBtn": "Create Profile",
|
||||||
"peerOfflineMessage": "",
|
"passwordErrorEmpty": "Password cannot be empty",
|
||||||
"pendingLabel": "En attente",
|
"password2Label": "Reenter password",
|
||||||
"postNewBulletinLabel": "Envoyer un nouveau bulletin",
|
"password1Label": "Password",
|
||||||
"profileName": "",
|
"currentPasswordLabel": "Current Password",
|
||||||
"profileOnionLabel": "",
|
"yourDisplayName": "Your Display Name",
|
||||||
"puzzleGameBtn": "Puzzle",
|
"profileOnionLabel": "Send this address to peers you want to connect with",
|
||||||
"radioNoPassword": "",
|
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
|
||||||
"radioUsePassword": "",
|
"radioNoPassword": "Unencrypted (No password)",
|
||||||
"rejectGroupBtn": "Refuser",
|
"radioUsePassword": "Password",
|
||||||
"saveBtn": "Sauvegarder",
|
"copiedToClipboardNotification": "Copié dans le presse-papier",
|
||||||
"savePeerHistory": "",
|
"copyBtn": "Copier",
|
||||||
"savePeerHistoryDescription": "",
|
"editProfile": "Edit Profille",
|
||||||
"saveProfileBtn": "",
|
"newProfile": "New Profile",
|
||||||
"search": "",
|
"defaultProfileName": "Alice",
|
||||||
"searchList": "",
|
"profileName": "Display name",
|
||||||
"server": "",
|
"editProfileTitle": "Edit Profile",
|
||||||
"serverConnectivityConnected": "",
|
"addProfileTitle": "Add new profile",
|
||||||
"serverConnectivityDisconnected": "",
|
"deleteBtn": "Effacer",
|
||||||
"serverInfo": "",
|
"unblockBtn": "Unblock Peer",
|
||||||
"serverLabel": "Serveur",
|
"dontSavePeerHistory": "Delete Peer History",
|
||||||
"serverNotSynced": "",
|
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
|
||||||
"serverSynced": "",
|
"savePeerHistory": "Save Peer History",
|
||||||
"settingInterfaceZoom": "",
|
"blockBtn": "Block Peer",
|
||||||
"settingLanguage": "",
|
"saveBtn": "Sauvegarder",
|
||||||
"settingTheme": "",
|
"displayNameLabel": "Pseudo",
|
||||||
"smallTextLabel": "Petit",
|
"addressLabel": "Adresse",
|
||||||
"successfullAddedContact": "",
|
"puzzleGameBtn": "Puzzle",
|
||||||
"themeDark": "",
|
"bulletinsBtn": "Bulletins",
|
||||||
"themeLight": "",
|
"listsBtn": "Listes",
|
||||||
"titleManageContacts": "",
|
"chatBtn": "Discuter",
|
||||||
"titleManageProfiles": "",
|
"rejectGroupBtn": "Refuser",
|
||||||
"titleManageServers": "",
|
"acceptGroupBtn": "Accepter",
|
||||||
"titlePlaceholder": "titre...",
|
"acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe",
|
||||||
"todoPlaceholder": "A faire...",
|
"newGroupBtn": "Créer un nouveau groupe",
|
||||||
"tooltipAddContact": "",
|
"copiedClipboardNotification": "Copié dans le presse-papier",
|
||||||
"tooltipOpenSettings": "",
|
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
|
||||||
"tooltipUnlockProfiles": "",
|
"peerBlockedMessage": "Peer is blocked",
|
||||||
"unblockBtn": "",
|
"pendingLabel": "En attente",
|
||||||
"unlock": "",
|
"acknowledgedLabel": "Confirmé",
|
||||||
"update": "",
|
"couldNotSendMsgError": "Impossible d'envoyer ce message",
|
||||||
"version": "",
|
"dmTooltip": "Envoyer un message privé",
|
||||||
"versionBuilddate": "",
|
"membershipDescription": "Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être representatives de l'ensemble des membres du groupe.",
|
||||||
"versionTor": "",
|
"addListItemBtn": "Add Item",
|
||||||
"viewGroupMembershipTooltip": "",
|
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
|
||||||
"viewServerInfo": "",
|
"searchList": "Search List",
|
||||||
"yourDisplayName": "",
|
"update": "Update",
|
||||||
"yourProfiles": "",
|
"inviteBtn": "Invitation",
|
||||||
"yourServers": "",
|
"inviteToGroupLabel": "Inviter quelqu'un",
|
||||||
"zoomLabel": "Interface zoom (essentiellement la taille du texte et des composants de l'interface)"
|
"groupNameLabel": "Nom du groupe",
|
||||||
|
"viewServerInfo": "Server Info",
|
||||||
|
"serverSynced": "Synced",
|
||||||
|
"serverConnectivityDisconnected": "Server Disconnected",
|
||||||
|
"serverConnectivityConnected": "Server Connected",
|
||||||
|
"serverInfo": "Server Information",
|
||||||
|
"invitationLabel": "Invitation",
|
||||||
|
"serverLabel": "Serveur",
|
||||||
|
"search": "Search...",
|
||||||
|
"cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
|
||||||
|
"cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
|
||||||
|
"cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
|
||||||
|
"cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
|
||||||
|
"cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
|
||||||
|
"cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
|
||||||
|
"blocked": "Blocked",
|
||||||
|
"titlePlaceholder": "titre...",
|
||||||
|
"postNewBulletinLabel": "Envoyer un nouveau bulletin",
|
||||||
|
"newBulletinLabel": "Nouveau bulletin",
|
||||||
|
"joinGroup": "Join group",
|
||||||
|
"createGroup": "Create group",
|
||||||
|
"addPeer": "Add Peer",
|
||||||
|
"groupAddr": "Address",
|
||||||
|
"invitation": "Invitation",
|
||||||
|
"server": "Server",
|
||||||
|
"groupName": "Group name",
|
||||||
|
"peerName": "Name",
|
||||||
|
"peerAddress": "Address",
|
||||||
|
"joinGroupTab": "Join a group",
|
||||||
|
"createGroupTab": "Create a group",
|
||||||
|
"addPeerTab": "Add a peer",
|
||||||
|
"createGroupBtn": "Créer",
|
||||||
|
"defaultGroupName": "Un super groupe",
|
||||||
|
"createGroupTitle": "Créer un groupe"
|
||||||
}
|
}
|
|
@ -1,160 +1,193 @@
|
||||||
{
|
{
|
||||||
"@@locale": "it",
|
"@@locale": "it",
|
||||||
"acceptGroupBtn": "Accetta",
|
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||||
"acceptGroupInviteLabel": "Vuoi accettare l'invito a",
|
"tooltipHidePassword": "Hide Password",
|
||||||
"acknowledgedLabel": "Riconosciuto",
|
"tooltipShowPassword": "Show Password",
|
||||||
"addListItem": "Aggiungi un nuovo elemento alla lista",
|
"serverNotSynced": "Non sincronizzato",
|
||||||
"addListItemBtn": "Aggiungi elemento",
|
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||||
"addNewItem": "Aggiungi un nuovo elemento alla lista",
|
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||||
"addNewProfileBtn": "Aggiungi nuovo profilo",
|
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||||
"addPeer": "Aggiungi peer",
|
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||||
"addPeerTab": "Aggiungi un peer",
|
"shutdownCwtchTooltip": "Shutdown Cwtch",
|
||||||
"addProfileTitle": "Aggiungi nuovo profilo",
|
"malformedMessage": "Malformed message",
|
||||||
"addressLabel": "Indirizzo",
|
"profileDeleteSuccess": "Successfully deleted profile",
|
||||||
"blockBtn": "Blocca il peer",
|
"debugLog": "Turn on console debug logging",
|
||||||
"blocked": "Bloccato",
|
"torNetworkStatus": "Tor network status",
|
||||||
"blockUnknownLabel": "Blocca peer sconosciuti",
|
"addContactFirst": "Add or pick a contact to begin chatting.",
|
||||||
"builddate": "Costruito il: %2",
|
"createProfileToBegin": "Please create or unlock a profile to begin",
|
||||||
"bulletinsBtn": "Bollettini",
|
"nickChangeSuccess": "Profile nickname changed successfully",
|
||||||
"chatBtn": "Chat",
|
"addServerFirst": "You need to add a server before you can create a group",
|
||||||
"contactAlreadyExists": "",
|
"deleteProfileSuccess": "Successfully deleted profile",
|
||||||
"conversationSettings": "",
|
"sendInvite": "Send a contact or group invite",
|
||||||
"copiedClipboardNotification": "Copiato negli Appunti",
|
"sendMessage": "Send Message",
|
||||||
"copiedToClipboardNotification": "Copiato negli Appunti",
|
"cancel": "Cancel",
|
||||||
"copyBtn": "Copia",
|
"resetTor": "Reset",
|
||||||
"couldNotSendMsgError": "Impossibile inviare questo messaggio",
|
"torStatus": "Tor Status",
|
||||||
"createGroup": "Crea un gruppo",
|
"torVersion": "Tor Version",
|
||||||
"createGroupBtn": "Crea",
|
"sendAnInvitation": "You sent an invitation for: ",
|
||||||
"createGroupTab": "Crea un gruppo",
|
"contactSuggestion": "This is a contact suggestion for: ",
|
||||||
"createGroupTitle": "Crea un gruppo",
|
"rejected": "Rejected!",
|
||||||
"createProfileBtn": "Crea un profilo",
|
"accepted": "Accepted!",
|
||||||
"currentPasswordLabel": "Password corrente",
|
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
|
||||||
"cwtchSettingsTitle": "Impostazioni di Cwtch",
|
"newPassword": "New Password",
|
||||||
"cycleCatsAndroid": "Fare clic per scorrere le categorie.\\nPressione lunga per resettare.",
|
"yesLeave": "Yes, Leave This Conversation",
|
||||||
"cycleCatsDesktop": "Fare clic per scorrere le categorie.\\nCliccare con il tasto destro per resettare.",
|
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||||
"cycleColoursAndroid": "Fare clic per scorrere i colori.\\nPressione lunga per resettare.",
|
"leaveGroup": "Leave This Conversation",
|
||||||
"cycleColoursDesktop": "Fare clic per scorrere i colori.\\nCliccare con il tasto destro per resettare.",
|
"inviteToGroup": "You have been invited to join a group:",
|
||||||
"cycleMorphsAndroid": "Fare clic per scorrere i morph.\\nPressione lunga per resettare.",
|
"pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto...",
|
||||||
"cycleMorphsDesktop": "Fare clic per scorrere i morph.\\nCliccare con il tasto destro per resettare.",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"dateDaysAgo": "",
|
"titleManageContacts": "Conversations",
|
||||||
"dateHoursAgo": "",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateLastMonth": "",
|
"dateMonthsAgo": "Months Ago",
|
||||||
"dateLastYear": "",
|
"dateNever": "Never",
|
||||||
"dateMinutesAgo": "",
|
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
||||||
"dateMonthsAgo": "",
|
"dateLastYear": "Last Year",
|
||||||
"dateNever": "",
|
"dateYesterday": "Yesterday",
|
||||||
"dateRightNow": "",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "",
|
"dateWeeksAgo": "Weeks Ago",
|
||||||
"dateYearsAgo": "",
|
"dateDaysAgo": "Days Ago",
|
||||||
"dateYesterday": "",
|
"dateHoursAgo": "Hours Ago",
|
||||||
"defaultGroupName": "Gruppo fantastico",
|
"dateMinutesAgo": "Minutes Ago",
|
||||||
"defaultProfileName": "Alice",
|
"dateRightNow": "Right Now",
|
||||||
"defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"deleteBtn": "Elimina",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
"deleteConfirmLabel": "Digita ELIMINA per confermare",
|
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
|
||||||
"deleteConfirmText": "ELIMINA",
|
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
|
||||||
"deleteProfileBtn": "Elimina profilo",
|
"titleManageProfiles": "Manage Cwtch Profiles",
|
||||||
"deleteProfileConfirmBtn": "Elimina realmente il profilo",
|
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
||||||
"descriptionBlockUnknownConnections": "",
|
"tooltipOpenSettings": "Open the settings pane",
|
||||||
"descriptionExperiments": "",
|
"invalidImportString": "Invalid import string",
|
||||||
"descriptionExperimentsGroups": "",
|
"contactAlreadyExists": "Contact Already Exists",
|
||||||
"displayNameLabel": "Nome visualizzato",
|
"conversationSettings": "Conversation Settings",
|
||||||
"dmTooltip": "Clicca per inviare un Messagio Diretto",
|
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
|
||||||
"dontSavePeerHistory": "Elimina cronologia dei peer",
|
"enableGroups": "Enable Group Chat",
|
||||||
"editProfile": "Modifica profilo",
|
"experimentsEnabled": "Esperimenti abilitati",
|
||||||
"editProfileTitle": "Modifica profilo",
|
"localeIt": "Italiano",
|
||||||
"enableGroups": "",
|
"localeEs": "Spagnolo",
|
||||||
"enterCurrentPasswordForDelete": "",
|
"addListItem": "Aggiungi un nuovo elemento alla lista",
|
||||||
"enterProfilePassword": "Inserisci una password per visualizzare i tuoi profili",
|
"addNewItem": "Aggiungi un nuovo elemento alla lista",
|
||||||
"error0ProfilesLoadedForPassword": "0 profili caricati con quella password",
|
"todoPlaceholder": "Da fare...",
|
||||||
"experimentsEnabled": "Esperimenti abilitati",
|
"newConnectionPaneTitle": "Nuova connessione",
|
||||||
"groupAddr": "Indirizzo",
|
"networkStatusOnline": "Online",
|
||||||
"groupName": "Nome del gruppo",
|
"networkStatusConnecting": "Connessione alla rete e ai peer ...",
|
||||||
"groupNameLabel": "Nome del gruppo",
|
"networkStatusAttemptingTor": "Tentativo di connessione alla rete Tor",
|
||||||
"invalidImportString": "",
|
"networkStatusDisconnected": "Disconnesso da Internet, controlla la tua connessione",
|
||||||
"invitation": "Invito",
|
"viewGroupMembershipTooltip": "Visualizza i membri del gruppo",
|
||||||
"invitationLabel": "Invito",
|
"loadingTor": "Caricamento di tor...",
|
||||||
"inviteBtn": "Invitare",
|
"smallTextLabel": "Piccolo",
|
||||||
"inviteToGroupLabel": "Invitare nel gruppo",
|
"defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:",
|
||||||
"joinGroup": "Unisciti al gruppo",
|
"builddate": "Costruito il: %2",
|
||||||
"joinGroupTab": "Unisciti a un gruppo",
|
"version": "Versione %1",
|
||||||
"largeTextLabel": "Grande",
|
"versionTor": "Versione %1 con tor %2",
|
||||||
"listsBtn": "Liste",
|
"themeDark": "Scuro",
|
||||||
"loadingTor": "Caricamento di tor...",
|
"themeLight": "Chiaro",
|
||||||
"localeDe": "Tedesco",
|
"settingTheme": "Tema",
|
||||||
"localeEn": "Inglese",
|
"largeTextLabel": "Grande",
|
||||||
"localeEs": "Spagnolo",
|
"settingInterfaceZoom": "Livello di zoom",
|
||||||
"localeFr": "Francese",
|
"localeDe": "Tedesco",
|
||||||
"localeIt": "Italiano",
|
"localePt": "Portoghese",
|
||||||
"localePt": "Portoghese",
|
"localeFr": "Francese",
|
||||||
"membershipDescription": "Di seguito è riportato un elenco di utenti che hanno inviato messaggi al gruppo. Questo elenco potrebbe non corrispondere a tutti gli utenti che hanno accesso al gruppo.",
|
"localeEn": "Inglese",
|
||||||
"networkStatusAttemptingTor": "Tentativo di connessione alla rete Tor",
|
"settingLanguage": "Lingua",
|
||||||
"networkStatusConnecting": "Connessione alla rete e ai peer ...",
|
"blockUnknownLabel": "Blocca peer sconosciuti",
|
||||||
"networkStatusDisconnected": "Disconnesso da Internet, controlla la tua connessione",
|
"zoomLabel": "Zoom dell'interfaccia (influisce principalmente sulle dimensioni del testo e dei pulsanti)",
|
||||||
"networkStatusOnline": "Online",
|
"versionBuilddate": "Versione: %1 Costruito il: %2",
|
||||||
"newBulletinLabel": "Nuovo bollettino",
|
"cwtchSettingsTitle": "Impostazioni di Cwtch",
|
||||||
"newConnectionPaneTitle": "Nuova connessione",
|
"unlock": "Sblocca",
|
||||||
"newGroupBtn": "Crea un nuovo gruppo",
|
"yourServers": "I tuoi server",
|
||||||
"newProfile": "Nuovo profilo",
|
"yourProfiles": "I tuoi profili",
|
||||||
"noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati",
|
"error0ProfilesLoadedForPassword": "0 profili caricati con quella password",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"password1Label": "Password",
|
"enterProfilePassword": "Inserisci una password per visualizzare i tuoi profili",
|
||||||
"password2Label": "Reinserire la password",
|
"addNewProfileBtn": "Aggiungi nuovo profilo",
|
||||||
"passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata",
|
"deleteConfirmText": "ELIMINA",
|
||||||
"passwordErrorEmpty": "La password non può essere vuota",
|
"deleteProfileConfirmBtn": "Elimina realmente il profilo",
|
||||||
"passwordErrorMatch": "Le password non corrispondono",
|
"deleteConfirmLabel": "Digita ELIMINA per confermare",
|
||||||
"pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto...",
|
"deleteProfileBtn": "Elimina profilo",
|
||||||
"peerAddress": "Indirizzo",
|
"passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata",
|
||||||
"peerBlockedMessage": "Il peer è bloccato",
|
"passwordErrorMatch": "Le password non corrispondono",
|
||||||
"peerName": "Nome",
|
"saveProfileBtn": "Salva il profilo",
|
||||||
"peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.",
|
"createProfileBtn": "Crea un profilo",
|
||||||
"peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento",
|
"passwordErrorEmpty": "La password non può essere vuota",
|
||||||
"pendingLabel": "In corso",
|
"password2Label": "Reinserire la password",
|
||||||
"postNewBulletinLabel": "Pubblica un nuovo bollettino",
|
"password1Label": "Password",
|
||||||
"profileName": "Nome visualizzato",
|
"currentPasswordLabel": "Password corrente",
|
||||||
"profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi",
|
"yourDisplayName": "Il tuo nome visualizzato",
|
||||||
"puzzleGameBtn": "Gioco di puzzle",
|
"profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi",
|
||||||
"radioNoPassword": "Non criptato (senza password)",
|
"noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati",
|
||||||
"radioUsePassword": "Password",
|
"radioNoPassword": "Non criptato (senza password)",
|
||||||
"rejectGroupBtn": "Rifiuta",
|
"radioUsePassword": "Password",
|
||||||
"saveBtn": "Salva",
|
"copiedToClipboardNotification": "Copiato negli appunti",
|
||||||
"savePeerHistory": "Salva cronologia peer",
|
"copyBtn": "Copia",
|
||||||
"savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.",
|
"editProfile": "Modifica profilo",
|
||||||
"saveProfileBtn": "Salva il profilo",
|
"newProfile": "Nuovo profilo",
|
||||||
"search": "Ricerca...",
|
"defaultProfileName": "Alice",
|
||||||
"searchList": "Cerca nella lista",
|
"profileName": "Nome visualizzato",
|
||||||
"server": "Server",
|
"editProfileTitle": "Modifica profilo",
|
||||||
"serverConnectivityConnected": "Server connesso",
|
"addProfileTitle": "Aggiungi nuovo profilo",
|
||||||
"serverConnectivityDisconnected": "Server disconnesso",
|
"deleteBtn": "Elimina",
|
||||||
"serverInfo": "Informazioni sul server",
|
"unblockBtn": "Sblocca il peer",
|
||||||
"serverLabel": "Server",
|
"dontSavePeerHistory": "Elimina cronologia dei peer",
|
||||||
"serverNotSynced": "Non sincronizzato",
|
"savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.",
|
||||||
"serverSynced": "Sincronizzato",
|
"savePeerHistory": "Salva cronologia peer",
|
||||||
"settingInterfaceZoom": "Livello di zoom",
|
"blockBtn": "Blocca il peer",
|
||||||
"settingLanguage": "Lingua",
|
"saveBtn": "Salva",
|
||||||
"settingTheme": "Tema",
|
"displayNameLabel": "Nome visualizzato",
|
||||||
"smallTextLabel": "Piccolo",
|
"addressLabel": "Indirizzo",
|
||||||
"successfullAddedContact": "",
|
"puzzleGameBtn": "Gioco di puzzle",
|
||||||
"themeDark": "Scuro",
|
"bulletinsBtn": "Bollettini",
|
||||||
"themeLight": "Chiaro",
|
"listsBtn": "Liste",
|
||||||
"titleManageContacts": "",
|
"chatBtn": "Chat",
|
||||||
"titleManageProfiles": "",
|
"rejectGroupBtn": "Rifiuta",
|
||||||
"titleManageServers": "",
|
"acceptGroupBtn": "Accetta",
|
||||||
"titlePlaceholder": "titolo...",
|
"acceptGroupInviteLabel": "Vuoi accettare l'invito a",
|
||||||
"todoPlaceholder": "Da fare...",
|
"newGroupBtn": "Crea un nuovo gruppo",
|
||||||
"tooltipAddContact": "",
|
"copiedClipboardNotification": "Copiato negli Appunti",
|
||||||
"tooltipOpenSettings": "",
|
"peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento",
|
||||||
"tooltipUnlockProfiles": "",
|
"peerBlockedMessage": "Il peer è bloccato",
|
||||||
"unblockBtn": "Sblocca il peer",
|
"pendingLabel": "In corso",
|
||||||
"unlock": "Sblocca",
|
"acknowledgedLabel": "Riconosciuto",
|
||||||
"update": "Aggiornamento",
|
"couldNotSendMsgError": "Impossibile inviare questo messaggio",
|
||||||
"version": "Versione %1",
|
"dmTooltip": "Clicca per inviare un Messagio Diretto",
|
||||||
"versionBuilddate": "Versione: %1 Costruito il: %2",
|
"membershipDescription": "Di seguito è riportato un elenco di utenti che hanno inviato messaggi al gruppo. Questo elenco potrebbe non corrispondere a tutti gli utenti che hanno accesso al gruppo.",
|
||||||
"versionTor": "Versione %1 con tor %2",
|
"addListItemBtn": "Aggiungi elemento",
|
||||||
"viewGroupMembershipTooltip": "Visualizza i membri del gruppo",
|
"peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.",
|
||||||
"viewServerInfo": "Informazioni sul server",
|
"searchList": "Cerca nella lista",
|
||||||
"yourDisplayName": "Il tuo nome visualizzato",
|
"update": "Aggiornamento",
|
||||||
"yourProfiles": "I tuoi profili",
|
"inviteBtn": "Invitare",
|
||||||
"yourServers": "I tuoi server",
|
"inviteToGroupLabel": "Invitare nel gruppo",
|
||||||
"zoomLabel": "Zoom dell'interfaccia (influisce principalmente sulle dimensioni del testo e dei pulsanti)"
|
"groupNameLabel": "Nome del gruppo",
|
||||||
|
"viewServerInfo": "Informazioni sul server",
|
||||||
|
"serverSynced": "Sincronizzato",
|
||||||
|
"serverConnectivityDisconnected": "Server disconnesso",
|
||||||
|
"serverConnectivityConnected": "Server connesso",
|
||||||
|
"serverInfo": "Informazioni sul server",
|
||||||
|
"invitationLabel": "Invito",
|
||||||
|
"serverLabel": "Server",
|
||||||
|
"search": "Ricerca...",
|
||||||
|
"cycleColoursDesktop": "Fare clic per scorrere i colori.\nCliccare con il tasto destro per resettare.",
|
||||||
|
"cycleColoursAndroid": "Fare clic per scorrere i colori.\nPressione lunga per resettare.",
|
||||||
|
"cycleMorphsDesktop": "Fare clic per scorrere i morph.\nCliccare con il tasto destro per resettare.",
|
||||||
|
"cycleMorphsAndroid": "Fare clic per scorrere i morph.\nPressione lunga per resettare.",
|
||||||
|
"cycleCatsDesktop": "Fare clic per scorrere le categorie.\nCliccare con il tasto destro per resettare.",
|
||||||
|
"cycleCatsAndroid": "Fare clic per scorrere le categorie.\nPressione lunga per resettare.",
|
||||||
|
"blocked": "Bloccato",
|
||||||
|
"titlePlaceholder": "titolo...",
|
||||||
|
"postNewBulletinLabel": "Pubblica un nuovo bollettino",
|
||||||
|
"newBulletinLabel": "Nuovo bollettino",
|
||||||
|
"joinGroup": "Unisciti al gruppo",
|
||||||
|
"createGroup": "Crea un gruppo",
|
||||||
|
"addPeer": "Aggiungi peer",
|
||||||
|
"groupAddr": "Indirizzo",
|
||||||
|
"invitation": "Invito",
|
||||||
|
"server": "Server",
|
||||||
|
"groupName": "Nome del gruppo",
|
||||||
|
"peerName": "Nome",
|
||||||
|
"peerAddress": "Indirizzo",
|
||||||
|
"joinGroupTab": "Unisciti a un gruppo",
|
||||||
|
"createGroupTab": "Crea un gruppo",
|
||||||
|
"addPeerTab": "Aggiungi un peer",
|
||||||
|
"createGroupBtn": "Crea",
|
||||||
|
"defaultGroupName": "Gruppo fantastico",
|
||||||
|
"createGroupTitle": "Crea un gruppo"
|
||||||
}
|
}
|
|
@ -1,160 +1,193 @@
|
||||||
{
|
{
|
||||||
"@@locale": "pt",
|
"@@locale": "pt",
|
||||||
"acceptGroupBtn": "Aceitar",
|
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||||
"acceptGroupInviteLabel": "Você quer aceitar o convite para",
|
"tooltipHidePassword": "Hide Password",
|
||||||
"acknowledgedLabel": "Confirmada",
|
"tooltipShowPassword": "Show Password",
|
||||||
"addListItem": "Adicionar Item à Lista",
|
"serverNotSynced": "Syncing New Messages (This can take some time)...",
|
||||||
"addListItemBtn": "",
|
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||||
"addNewItem": "Adicionar novo item à lista",
|
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||||
"addNewProfileBtn": "",
|
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||||
"addPeer": "",
|
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||||
"addPeerTab": "",
|
"shutdownCwtchTooltip": "Shutdown Cwtch",
|
||||||
"addProfileTitle": "",
|
"malformedMessage": "Malformed message",
|
||||||
"addressLabel": "Endereço",
|
"profileDeleteSuccess": "Successfully deleted profile",
|
||||||
"blockBtn": "",
|
"debugLog": "Turn on console debug logging",
|
||||||
"blocked": "",
|
"torNetworkStatus": "Tor network status",
|
||||||
"blockUnknownLabel": "",
|
"addContactFirst": "Add or pick a contact to begin chatting.",
|
||||||
"builddate": "",
|
"createProfileToBegin": "Please create or unlock a profile to begin",
|
||||||
"bulletinsBtn": "Boletins",
|
"nickChangeSuccess": "Profile nickname changed successfully",
|
||||||
"chatBtn": "Chat",
|
"addServerFirst": "You need to add a server before you can create a group",
|
||||||
"contactAlreadyExists": "",
|
"deleteProfileSuccess": "Successfully deleted profile",
|
||||||
"conversationSettings": "",
|
"sendInvite": "Send a contact or group invite",
|
||||||
"copiedClipboardNotification": "Copiado",
|
"sendMessage": "Send Message",
|
||||||
"copiedToClipboardNotification": "Copiado",
|
"cancel": "Cancel",
|
||||||
"copyBtn": "Copiar",
|
"resetTor": "Reset",
|
||||||
"couldNotSendMsgError": "Não deu para enviar esta mensagem",
|
"torStatus": "Tor Status",
|
||||||
"createGroup": "",
|
"torVersion": "Tor Version",
|
||||||
"createGroupBtn": "Criar",
|
"sendAnInvitation": "You sent an invitation for: ",
|
||||||
"createGroupTab": "",
|
"contactSuggestion": "This is a contact suggestion for: ",
|
||||||
"createGroupTitle": "Criar Grupo",
|
"rejected": "Rejected!",
|
||||||
"createProfileBtn": "",
|
"accepted": "Accepted!",
|
||||||
"currentPasswordLabel": "",
|
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
|
||||||
"cwtchSettingsTitle": "Configurações do Cwtch",
|
"newPassword": "New Password",
|
||||||
"cycleCatsAndroid": "",
|
"yesLeave": "Yes, Leave This Conversation",
|
||||||
"cycleCatsDesktop": "",
|
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||||
"cycleColoursAndroid": "",
|
"leaveGroup": "Leave This Conversation",
|
||||||
"cycleColoursDesktop": "",
|
"inviteToGroup": "You have been invited to join a group:",
|
||||||
"cycleMorphsAndroid": "",
|
"pasteAddressToAddContact": "… cole um endereço aqui para adicionar um contato…",
|
||||||
"cycleMorphsDesktop": "",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"dateDaysAgo": "",
|
"titleManageContacts": "Conversations",
|
||||||
"dateHoursAgo": "",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateLastMonth": "",
|
"dateMonthsAgo": "Months Ago",
|
||||||
"dateLastYear": "",
|
"dateNever": "Never",
|
||||||
"dateMinutesAgo": "",
|
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
||||||
"dateMonthsAgo": "",
|
"dateLastYear": "Last Year",
|
||||||
"dateNever": "",
|
"dateYesterday": "Yesterday",
|
||||||
"dateRightNow": "",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "",
|
"dateWeeksAgo": "Weeks Ago",
|
||||||
"dateYearsAgo": "",
|
"dateDaysAgo": "Days Ago",
|
||||||
"dateYesterday": "",
|
"dateHoursAgo": "Hours Ago",
|
||||||
"defaultGroupName": "Grupo incrível",
|
"dateMinutesAgo": "Minutes Ago",
|
||||||
"defaultProfileName": "",
|
"dateRightNow": "Right Now",
|
||||||
"defaultScalingText": "Texto tamanho padrão (fator de escala: ",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"deleteBtn": "Deletar",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
"deleteConfirmLabel": "",
|
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
|
||||||
"deleteConfirmText": "",
|
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
|
||||||
"deleteProfileBtn": "",
|
"titleManageProfiles": "Manage Cwtch Profiles",
|
||||||
"deleteProfileConfirmBtn": "",
|
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
||||||
"descriptionBlockUnknownConnections": "",
|
"tooltipOpenSettings": "Open the settings pane",
|
||||||
"descriptionExperiments": "",
|
"invalidImportString": "Invalid import string",
|
||||||
"descriptionExperimentsGroups": "",
|
"contactAlreadyExists": "Contact Already Exists",
|
||||||
"displayNameLabel": "Nome de Exibição",
|
"conversationSettings": "Conversation Settings",
|
||||||
"dmTooltip": "Clique para DM",
|
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
|
||||||
"dontSavePeerHistory": "",
|
"enableGroups": "Enable Group Chat",
|
||||||
"editProfile": "",
|
"experimentsEnabled": "Enable Experiments",
|
||||||
"editProfileTitle": "",
|
"localeIt": "Italiana",
|
||||||
"enableGroups": "",
|
"localeEs": "Espanol",
|
||||||
"enterCurrentPasswordForDelete": "",
|
"addListItem": "Adicionar Item à Lista",
|
||||||
"enterProfilePassword": "",
|
"addNewItem": "Adicionar novo item à lista",
|
||||||
"error0ProfilesLoadedForPassword": "",
|
"todoPlaceholder": "Afazer…",
|
||||||
"experimentsEnabled": "",
|
"newConnectionPaneTitle": "New Connection",
|
||||||
"groupAddr": "",
|
"networkStatusOnline": "Online",
|
||||||
"groupName": "",
|
"networkStatusConnecting": "Connecting to network and peers...",
|
||||||
"groupNameLabel": "Nome do Grupo",
|
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
|
||||||
"invalidImportString": "",
|
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
|
||||||
"invitation": "",
|
"viewGroupMembershipTooltip": "View Group Membership",
|
||||||
"invitationLabel": "Convite",
|
"loadingTor": "Loading tor...",
|
||||||
"inviteBtn": "Convidar",
|
"smallTextLabel": "Pequeno",
|
||||||
"inviteToGroupLabel": "Convidar ao grupo",
|
"defaultScalingText": "Texto tamanho padrão (fator de escala: ",
|
||||||
"joinGroup": "",
|
"builddate": "Built on: %2",
|
||||||
"joinGroupTab": "",
|
"version": "Version %1",
|
||||||
"largeTextLabel": "Grande",
|
"versionTor": "Version %1 with tor %2",
|
||||||
"listsBtn": "Listas",
|
"themeDark": "Dark",
|
||||||
"loadingTor": "",
|
"themeLight": "Light",
|
||||||
"localeDe": "",
|
"settingTheme": "Theme",
|
||||||
"localeEn": "",
|
"largeTextLabel": "Grande",
|
||||||
"localeEs": "",
|
"settingInterfaceZoom": "Zoom level",
|
||||||
"localeFr": "",
|
"localeDe": "Deutsche",
|
||||||
"localeIt": "",
|
"localePt": "Portuguesa",
|
||||||
"localePt": "",
|
"localeFr": "Frances",
|
||||||
"membershipDescription": "A lista abaixo é de usuários que enviaram mensagens ao grupo. Essa lista pode não refletir todos os usuários que têm acesso ao grupo.",
|
"localeEn": "English",
|
||||||
"networkStatusAttemptingTor": "",
|
"settingLanguage": "Language",
|
||||||
"networkStatusConnecting": "",
|
"blockUnknownLabel": "Block Unknown Peers",
|
||||||
"networkStatusDisconnected": "",
|
"zoomLabel": "Zoom da interface (afeta principalmente tamanho de texto e botões)",
|
||||||
"networkStatusOnline": "",
|
"versionBuilddate": "Version: %1 Built on: %2",
|
||||||
"newBulletinLabel": "Novo Boletim",
|
"cwtchSettingsTitle": "Configurações do Cwtch",
|
||||||
"newConnectionPaneTitle": "",
|
"unlock": "Unlock",
|
||||||
"newGroupBtn": "Criar novo grupo",
|
"yourServers": "Your Servers",
|
||||||
"newProfile": "",
|
"yourProfiles": "Your Profiles",
|
||||||
"noPasswordWarning": "",
|
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
|
||||||
"password": "",
|
"password": "Password",
|
||||||
"password1Label": "",
|
"enterProfilePassword": "Enter a password to view your profiles",
|
||||||
"password2Label": "",
|
"addNewProfileBtn": "Add new profile",
|
||||||
"passwordChangeError": "",
|
"deleteConfirmText": "DELETE",
|
||||||
"passwordErrorEmpty": "",
|
"deleteProfileConfirmBtn": "Really Delete Profile",
|
||||||
"passwordErrorMatch": "",
|
"deleteConfirmLabel": "Type DELETE to confirm",
|
||||||
"pasteAddressToAddContact": "… cole um endereço aqui para adicionar um contato…",
|
"deleteProfileBtn": "Delete Profile",
|
||||||
"peerAddress": "",
|
"passwordChangeError": "Error changing password: Supplied password rejected",
|
||||||
"peerBlockedMessage": "",
|
"passwordErrorMatch": "Passwords do not match",
|
||||||
"peerName": "",
|
"saveProfileBtn": "Save Profile",
|
||||||
"peerNotOnline": "",
|
"createProfileBtn": "Create Profile",
|
||||||
"peerOfflineMessage": "",
|
"passwordErrorEmpty": "Password cannot be empty",
|
||||||
"pendingLabel": "Pendente",
|
"password2Label": "Reenter password",
|
||||||
"postNewBulletinLabel": "Postar novo boletim",
|
"password1Label": "Password",
|
||||||
"profileName": "",
|
"currentPasswordLabel": "Current Password",
|
||||||
"profileOnionLabel": "",
|
"yourDisplayName": "Your Display Name",
|
||||||
"puzzleGameBtn": "Jogo de Adivinhação",
|
"profileOnionLabel": "Send this address to peers you want to connect with",
|
||||||
"radioNoPassword": "",
|
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
|
||||||
"radioUsePassword": "",
|
"radioNoPassword": "Unencrypted (No password)",
|
||||||
"rejectGroupBtn": "Recusar",
|
"radioUsePassword": "Password",
|
||||||
"saveBtn": "Salvar",
|
"copiedToClipboardNotification": "Copiado",
|
||||||
"savePeerHistory": "",
|
"copyBtn": "Copiar",
|
||||||
"savePeerHistoryDescription": "",
|
"editProfile": "Edit Profille",
|
||||||
"saveProfileBtn": "",
|
"newProfile": "New Profile",
|
||||||
"search": "",
|
"defaultProfileName": "Alice",
|
||||||
"searchList": "",
|
"profileName": "Display name",
|
||||||
"server": "",
|
"editProfileTitle": "Edit Profile",
|
||||||
"serverConnectivityConnected": "",
|
"addProfileTitle": "Add new profile",
|
||||||
"serverConnectivityDisconnected": "",
|
"deleteBtn": "Deletar",
|
||||||
"serverInfo": "",
|
"unblockBtn": "Unblock Peer",
|
||||||
"serverLabel": "Servidor",
|
"dontSavePeerHistory": "Delete Peer History",
|
||||||
"serverNotSynced": "",
|
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
|
||||||
"serverSynced": "",
|
"savePeerHistory": "Save Peer History",
|
||||||
"settingInterfaceZoom": "",
|
"blockBtn": "Block Peer",
|
||||||
"settingLanguage": "",
|
"saveBtn": "Salvar",
|
||||||
"settingTheme": "",
|
"displayNameLabel": "Nome de Exibição",
|
||||||
"smallTextLabel": "Pequeno",
|
"addressLabel": "Endereço",
|
||||||
"successfullAddedContact": "",
|
"puzzleGameBtn": "Jogo de Adivinhação",
|
||||||
"themeDark": "",
|
"bulletinsBtn": "Boletins",
|
||||||
"themeLight": "",
|
"listsBtn": "Listas",
|
||||||
"titleManageContacts": "",
|
"chatBtn": "Chat",
|
||||||
"titleManageProfiles": "",
|
"rejectGroupBtn": "Recusar",
|
||||||
"titleManageServers": "",
|
"acceptGroupBtn": "Aceitar",
|
||||||
"titlePlaceholder": "título…",
|
"acceptGroupInviteLabel": "Você quer aceitar o convite para",
|
||||||
"todoPlaceholder": "Afazer…",
|
"newGroupBtn": "Criar novo grupo",
|
||||||
"tooltipAddContact": "",
|
"copiedClipboardNotification": "Copiado",
|
||||||
"tooltipOpenSettings": "",
|
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
|
||||||
"tooltipUnlockProfiles": "",
|
"peerBlockedMessage": "Peer is blocked",
|
||||||
"unblockBtn": "",
|
"pendingLabel": "Pendente",
|
||||||
"unlock": "",
|
"acknowledgedLabel": "Confirmada",
|
||||||
"update": "",
|
"couldNotSendMsgError": "Não deu para enviar esta mensagem",
|
||||||
"version": "",
|
"dmTooltip": "Clique para DM",
|
||||||
"versionBuilddate": "",
|
"membershipDescription": "A lista abaixo é de usuários que enviaram mensagens ao grupo. Essa lista pode não refletir todos os usuários que têm acesso ao grupo.",
|
||||||
"versionTor": "",
|
"addListItemBtn": "Add Item",
|
||||||
"viewGroupMembershipTooltip": "",
|
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
|
||||||
"viewServerInfo": "",
|
"searchList": "Search List",
|
||||||
"yourDisplayName": "",
|
"update": "Update",
|
||||||
"yourProfiles": "",
|
"inviteBtn": "Convidar",
|
||||||
"yourServers": "",
|
"inviteToGroupLabel": "Convidar ao grupo",
|
||||||
"zoomLabel": "Zoom da interface (afeta principalmente tamanho de texto e botões)"
|
"groupNameLabel": "Nome do Grupo",
|
||||||
|
"viewServerInfo": "Server Info",
|
||||||
|
"serverSynced": "Synced",
|
||||||
|
"serverConnectivityDisconnected": "Server Disconnected",
|
||||||
|
"serverConnectivityConnected": "Server Connected",
|
||||||
|
"serverInfo": "Server Information",
|
||||||
|
"invitationLabel": "Convite",
|
||||||
|
"serverLabel": "Servidor",
|
||||||
|
"search": "Search...",
|
||||||
|
"cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
|
||||||
|
"cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
|
||||||
|
"cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
|
||||||
|
"cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
|
||||||
|
"cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
|
||||||
|
"cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
|
||||||
|
"blocked": "Blocked",
|
||||||
|
"titlePlaceholder": "título…",
|
||||||
|
"postNewBulletinLabel": "Postar novo boletim",
|
||||||
|
"newBulletinLabel": "Novo Boletim",
|
||||||
|
"joinGroup": "Join group",
|
||||||
|
"createGroup": "Create group",
|
||||||
|
"addPeer": "Add Peer",
|
||||||
|
"groupAddr": "Address",
|
||||||
|
"invitation": "Invitation",
|
||||||
|
"server": "Server",
|
||||||
|
"groupName": "Group name",
|
||||||
|
"peerName": "Name",
|
||||||
|
"peerAddress": "Address",
|
||||||
|
"joinGroupTab": "Join a group",
|
||||||
|
"createGroupTab": "Create a group",
|
||||||
|
"addPeerTab": "Add a peer",
|
||||||
|
"createGroupBtn": "Criar",
|
||||||
|
"defaultGroupName": "Grupo incrível",
|
||||||
|
"createGroupTitle": "Criar Grupo"
|
||||||
}
|
}
|
|
@ -114,4 +114,7 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''');
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''');
|
||||||
|
|
||||||
|
|
||||||
|
yield LicenseEntryWithLineBreaks(["flaticons"], "Icons made by Freepik (https://www.freepik.com) from Flaticon (www.flaticon.com)");
|
||||||
}
|
}
|
||||||
|
|
100
lib/main.dart
|
@ -1,4 +1,6 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:cwtch/notification_manager.dart';
|
import 'package:cwtch/notification_manager.dart';
|
||||||
|
import 'package:cwtch/views/messageview.dart';
|
||||||
import 'package:cwtch/widgets/rightshiftfixer.dart';
|
import 'package:cwtch/widgets/rightshiftfixer.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cwtch/cwtch/ffi.dart';
|
import 'package:cwtch/cwtch/ffi.dart';
|
||||||
|
@ -8,6 +10,7 @@ import 'package:cwtch/errorHandler.dart';
|
||||||
import 'package:cwtch/settings.dart';
|
import 'package:cwtch/settings.dart';
|
||||||
import 'package:cwtch/torstatus.dart';
|
import 'package:cwtch/torstatus.dart';
|
||||||
import 'package:cwtch/views/triplecolview.dart';
|
import 'package:cwtch/views/triplecolview.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'cwtch/cwtch.dart';
|
import 'cwtch/cwtch.dart';
|
||||||
import 'cwtch/cwtchNotifier.dart';
|
import 'cwtch/cwtchNotifier.dart';
|
||||||
|
@ -15,16 +18,20 @@ import 'licenses.dart';
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
import 'views/profilemgrview.dart';
|
import 'views/profilemgrview.dart';
|
||||||
import 'views/splashView.dart';
|
import 'views/splashView.dart';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform, exit;
|
||||||
import 'opaque.dart';
|
import 'opaque.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
var globalSettings = Settings(Locale("en", ''), Opaque.dark);
|
var globalSettings = Settings(Locale("en", ''), OpaqueDark());
|
||||||
var globalErrorHandler = ErrorHandler();
|
var globalErrorHandler = ErrorHandler();
|
||||||
var globalTorStatus = TorStatus();
|
var globalTorStatus = TorStatus();
|
||||||
|
var globalAppState = AppState();
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
print("main()");
|
||||||
LicenseRegistry.addLicense(() => licenses());
|
LicenseRegistry.addLicense(() => licenses());
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
print("runApp()");
|
||||||
runApp(Flwtch());
|
runApp(Flwtch());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,47 +45,45 @@ class Flwtch extends StatefulWidget {
|
||||||
class FlwtchState extends State<Flwtch> {
|
class FlwtchState extends State<Flwtch> {
|
||||||
final TextStyle biggerFont = const TextStyle(fontSize: 18);
|
final TextStyle biggerFont = const TextStyle(fontSize: 18);
|
||||||
late Cwtch cwtch;
|
late Cwtch cwtch;
|
||||||
bool cwtchInit = false;
|
|
||||||
late ProfileInfoState selectedProfile;
|
|
||||||
String selectedConversation = "";
|
|
||||||
var columns = [1]; // default or 'single column' mode
|
|
||||||
//var columns = [1, 1, 2];
|
|
||||||
late ProfileListState profs;
|
late ProfileListState profs;
|
||||||
|
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
|
||||||
|
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdown');
|
||||||
|
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
|
print("initState: running...");
|
||||||
super.initState();
|
super.initState();
|
||||||
cwtchInit = false;
|
|
||||||
|
|
||||||
|
print("initState: registering notification, shutdown handlers...");
|
||||||
profs = ProfileListState();
|
profs = ProfileListState();
|
||||||
|
notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
|
||||||
|
shutdownMethodChannel.setMethodCallHandler(shutdown);
|
||||||
|
print("initState: creating cwtchnotifier, ffi");
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
|
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
|
||||||
cwtch = CwtchGomobile(cwtchNotifier);
|
cwtch = CwtchGomobile(cwtchNotifier);
|
||||||
} else if (Platform.isLinux) {
|
} else if (Platform.isLinux) {
|
||||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, LinuxNotificationsManager());
|
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, LinuxNotificationsManager(), globalAppState);
|
||||||
cwtch = CwtchFfi(cwtchNotifier);
|
cwtch = CwtchFfi(cwtchNotifier);
|
||||||
} else {
|
} else {
|
||||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
|
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
|
||||||
cwtch = CwtchFfi(cwtchNotifier);
|
cwtch = CwtchFfi(cwtchNotifier);
|
||||||
}
|
}
|
||||||
|
print("initState: invoking cwtch.Start()");
|
||||||
cwtch.Start().then((val) {
|
cwtch.Start();
|
||||||
setState(() {
|
print("initState: done!");
|
||||||
cwtchInit = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeNotifierProvider<TorStatus> getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);
|
ChangeNotifierProvider<TorStatus> getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);
|
||||||
ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler);
|
ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler);
|
||||||
ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings);
|
ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings);
|
||||||
|
ChangeNotifierProvider<AppState> getAppStateProvider() => ChangeNotifierProvider.value(value: globalAppState);
|
||||||
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
|
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
|
||||||
ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
|
ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
//appStatus = AppModel(cwtch: cwtch);
|
|
||||||
globalSettings.initPackageInfo();
|
globalSettings.initPackageInfo();
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -87,25 +92,72 @@ class FlwtchState extends State<Flwtch> {
|
||||||
getSettingsProvider(),
|
getSettingsProvider(),
|
||||||
getErrorHandlerProvider(),
|
getErrorHandlerProvider(),
|
||||||
getTorStatusProvider(),
|
getTorStatusProvider(),
|
||||||
|
getAppStateProvider(),
|
||||||
],
|
],
|
||||||
builder: (context, widget) {
|
builder: (context, widget) {
|
||||||
return Consumer<Settings>(
|
return Consumer2<Settings, AppState>(
|
||||||
builder: (context, settings, child) => MaterialApp(
|
builder: (context, settings, appState, child) => MaterialApp(
|
||||||
key: Key('app'),
|
key: Key('app'),
|
||||||
|
navigatorKey: navKey,
|
||||||
locale: settings.locale,
|
locale: settings.locale,
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
title: 'Cwtch',
|
title: 'Cwtch',
|
||||||
theme: mkThemeData(settings),
|
theme: mkThemeData(settings),
|
||||||
// from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(),
|
home: appState.cwtchInit == true ? ShiftRightFixer(child: ProfileMgrView()) : SplashView(),
|
||||||
// from erinn: home: columns.length == 3 ? TripleColumnView() : ProfileMgrView(),
|
|
||||||
home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> shutdown(MethodCall call) async {
|
||||||
|
cwtch.Shutdown();
|
||||||
|
// Wait a few seconds as shutting down things takes a little time..
|
||||||
|
Future.delayed(Duration(seconds: 2)).then((value) {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
SystemNavigator.pop();
|
||||||
|
} else if (Platform.isLinux || Platform.isWindows) {
|
||||||
|
print("Exiting...");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked via notificationClickChannel by MyBroadcastReceiver in MainActivity.kt
|
||||||
|
// coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
|
||||||
|
Future<void> _externalNotificationClicked(MethodCall call) async {
|
||||||
|
var args = jsonDecode(call.arguments);
|
||||||
|
var profile = profs.getProfile(args["ProfileOnion"])!;
|
||||||
|
var contact = profile.contactList.getContact(args["RemotePeer"])!;
|
||||||
|
contact.unreadMessages = 0;
|
||||||
|
|
||||||
|
// single pane mode pushes; double pane mode reads AppState.selectedProfile/Conversation
|
||||||
|
var isLandscape = Provider.of<AppState>(navKey.currentContext!, listen: false).isLandscape(navKey.currentContext!);
|
||||||
|
if (Provider.of<Settings>(navKey.currentContext!, listen: false).uiColumns(isLandscape).length == 1) {
|
||||||
|
if (navKey.currentContext?.findAncestorWidgetOfExactType<MessageView>() != null) {
|
||||||
|
print("messageview already open; popping before pushing replacement");
|
||||||
|
navKey.currentState?.pop();
|
||||||
|
}
|
||||||
|
navKey.currentState?.push(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
builder: (BuildContext builderContext) {
|
||||||
|
return MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider.value(value: profile),
|
||||||
|
ChangeNotifierProvider.value(value: contact),
|
||||||
|
],
|
||||||
|
builder: (context, child) => MessageView(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else { //dual pane
|
||||||
|
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedProfile = args["ProfileOnion"];
|
||||||
|
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = args["RemotePeer"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
cwtch.dispose();
|
cwtch.dispose();
|
||||||
|
|
|
@ -13,9 +13,8 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:glob/glob.dart';
|
import 'package:glob/glob.dart';
|
||||||
import 'package:glob/list_local_fs.dart';
|
|
||||||
|
|
||||||
var globalSettings = Settings(Locale("en", ''), Opaque.dark);
|
var globalSettings = Settings(Locale("en", ''), OpaqueDark());
|
||||||
var globalErrorHandler = ErrorHandler();
|
var globalErrorHandler = ErrorHandler();
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
137
lib/model.dart
|
@ -38,13 +38,13 @@ class ProfileListState extends ChangeNotifier {
|
||||||
List<ProfileInfoState> _profiles = [];
|
List<ProfileInfoState> _profiles = [];
|
||||||
int get num => _profiles.length;
|
int get num => _profiles.length;
|
||||||
|
|
||||||
void addAll(Iterable<ProfileInfoState> newProfiles) {
|
void add(String onion, String name, String picture, String contactsJson, String serverJson, bool online, bool encrypted) {
|
||||||
_profiles.addAll(newProfiles);
|
var idx = _profiles.indexWhere((element) => element.onion == onion);
|
||||||
notifyListeners();
|
if (idx == -1) {
|
||||||
}
|
_profiles.add(ProfileInfoState(onion: onion, nickname: name, imagePath: picture, contactsJson: contactsJson, serversJson: serverJson, online: online, encrypted: encrypted));
|
||||||
|
} else {
|
||||||
void add(ProfileInfoState newProfile) {
|
_profiles[idx].updateFrom(onion, name, picture, contactsJson, serverJson, online);
|
||||||
_profiles.add(newProfile);
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,42 @@ class ProfileListState extends ChangeNotifier {
|
||||||
int idx = _profiles.indexWhere((element) => element.onion == onion);
|
int idx = _profiles.indexWhere((element) => element.onion == onion);
|
||||||
return idx >= 0 ? _profiles[idx] : null;
|
return idx >= 0 ? _profiles[idx] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void delete(String onion) {
|
||||||
|
_profiles.removeWhere((element) => element.onion == onion);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppState extends ChangeNotifier {
|
||||||
|
bool cwtchInit = false;
|
||||||
|
String appError = "";
|
||||||
|
String? _selectedProfile;
|
||||||
|
String? _selectedConversation;
|
||||||
|
|
||||||
|
void SetCwtchInit() {
|
||||||
|
cwtchInit = true;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAppError(String error) {
|
||||||
|
appError = error;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get selectedProfile => _selectedProfile;
|
||||||
|
set selectedProfile(String? newVal) {
|
||||||
|
this._selectedProfile = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get selectedConversation => _selectedConversation;
|
||||||
|
set selectedConversation(String? newVal) {
|
||||||
|
this._selectedConversation = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactListState extends ChangeNotifier {
|
class ContactListState extends ChangeNotifier {
|
||||||
|
@ -64,13 +100,13 @@ class ContactListState extends ChangeNotifier {
|
||||||
bool get isFiltered => _filter != "";
|
bool get isFiltered => _filter != "";
|
||||||
String get filter => _filter;
|
String get filter => _filter;
|
||||||
set filter(String newVal) {
|
set filter(String newVal) {
|
||||||
_filter = newVal;
|
_filter = newVal.toLowerCase();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ContactInfoState> filteredList() {
|
List<ContactInfoState> filteredList() {
|
||||||
if (!isFiltered) return contacts;
|
if (!isFiltered) return contacts;
|
||||||
return _contacts.where((ContactInfoState c) => c.onion.contains(_filter) || (c.nickname.contains(_filter))).toList();
|
return _contacts.where((ContactInfoState c) => c.onion.toLowerCase().startsWith(_filter) || (c.nickname.toLowerCase().contains(_filter))).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAll(Iterable<ContactInfoState> newContacts) {
|
void addAll(Iterable<ContactInfoState> newContacts) {
|
||||||
|
@ -141,6 +177,10 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
int _unreadMessages = 0;
|
int _unreadMessages = 0;
|
||||||
bool _online = false;
|
bool _online = false;
|
||||||
|
|
||||||
|
// assume profiles are encrypted...this will be set to false
|
||||||
|
// in the constructor if the profile is encrypted with the defacto password.
|
||||||
|
bool _encrypted = true;
|
||||||
|
|
||||||
ProfileInfoState({
|
ProfileInfoState({
|
||||||
required this.onion,
|
required this.onion,
|
||||||
nickname = "",
|
nickname = "",
|
||||||
|
@ -149,11 +189,13 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
contactsJson = "",
|
contactsJson = "",
|
||||||
serversJson = "",
|
serversJson = "",
|
||||||
online = false,
|
online = false,
|
||||||
|
encrypted = true,
|
||||||
}) {
|
}) {
|
||||||
this._nickname = nickname;
|
this._nickname = nickname;
|
||||||
this._imagePath = imagePath;
|
this._imagePath = imagePath;
|
||||||
this._unreadMessages = unreadMessages;
|
this._unreadMessages = unreadMessages;
|
||||||
this._online = online;
|
this._online = online;
|
||||||
|
this._encrypted = encrypted;
|
||||||
|
|
||||||
if (contactsJson != null && contactsJson != "" && contactsJson != "null") {
|
if (contactsJson != null && contactsJson != "" && contactsJson != "null") {
|
||||||
List<dynamic> contacts = jsonDecode(contactsJson);
|
List<dynamic> contacts = jsonDecode(contactsJson);
|
||||||
|
@ -183,7 +225,7 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
|
|
||||||
// Parse out the server list json into our server info state struct...
|
// Parse out the server list json into our server info state struct...
|
||||||
void replaceServers(String serversJson) {
|
void replaceServers(String serversJson) {
|
||||||
if (serversJson != null && serversJson != "" && serversJson != "null") {
|
if (serversJson != "" && serversJson != "null") {
|
||||||
print("got servers $serversJson");
|
print("got servers $serversJson");
|
||||||
List<dynamic> servers = jsonDecode(serversJson);
|
List<dynamic> servers = jsonDecode(serversJson);
|
||||||
this._servers.replace(servers.map((server) {
|
this._servers.replace(servers.map((server) {
|
||||||
|
@ -207,6 +249,9 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check encrypted status for profile info screen
|
||||||
|
bool get isEncrypted => this._encrypted;
|
||||||
|
|
||||||
String get nickname => this._nickname;
|
String get nickname => this._nickname;
|
||||||
set nickname(String newValue) {
|
set nickname(String newValue) {
|
||||||
this._nickname = newValue;
|
this._nickname = newValue;
|
||||||
|
@ -241,6 +286,41 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
print("profileinfostate.dispose()");
|
print("profileinfostate.dispose()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateFrom(String onion, String name, String picture, String contactsJson, String serverJson, bool online) {
|
||||||
|
this._nickname = name;
|
||||||
|
this._imagePath = picture;
|
||||||
|
this._online = online;
|
||||||
|
this.replaceServers(serverJson);
|
||||||
|
|
||||||
|
if (contactsJson != null && contactsJson != "" && contactsJson != "null") {
|
||||||
|
List<dynamic> contacts = jsonDecode(contactsJson);
|
||||||
|
contacts.forEach((contact) {
|
||||||
|
var profileContact = this._contacts.getContact(contact["onion"]);
|
||||||
|
if (profileContact != null) {
|
||||||
|
profileContact.status = contact["status"];
|
||||||
|
profileContact.totalMessages = contact["numMessages"];
|
||||||
|
profileContact.lastMessageTime = DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"]));
|
||||||
|
} else {
|
||||||
|
this._contacts.add(ContactInfoState(
|
||||||
|
this.onion,
|
||||||
|
contact["onion"],
|
||||||
|
nickname: contact["name"],
|
||||||
|
status: contact["status"],
|
||||||
|
imagePath: contact["picture"],
|
||||||
|
isBlocked: contact["authorization"] == "blocked",
|
||||||
|
isInvitation: contact["authorization"] == "unknown",
|
||||||
|
savePeerHistory: contact["saveConversationHistory"],
|
||||||
|
numMessages: contact["numMessages"],
|
||||||
|
numUnread: contact["numUnread"],
|
||||||
|
isGroup: contact["isGroup"],
|
||||||
|
server: contact["groupServer"],
|
||||||
|
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactInfoState extends ChangeNotifier {
|
class ContactInfoState extends ChangeNotifier {
|
||||||
|
@ -357,7 +437,8 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
|
|
||||||
bool isOnline() {
|
bool isOnline() {
|
||||||
if (this.isGroup == true) {
|
if (this.isGroup == true) {
|
||||||
return this.status == "Synced";
|
// We now have an out of sync warning so we will mark these as online...
|
||||||
|
return this.status == "Authenticated" || this.status == "Synced";
|
||||||
} else {
|
} else {
|
||||||
return this.status == "Authenticated";
|
return this.status == "Authenticated";
|
||||||
}
|
}
|
||||||
|
@ -382,7 +463,8 @@ class MessageState extends ChangeNotifier {
|
||||||
late String _inviteNick;
|
late String _inviteNick;
|
||||||
late DateTime _timestamp;
|
late DateTime _timestamp;
|
||||||
late String _senderOnion;
|
late String _senderOnion;
|
||||||
late String _senderImage;
|
late int _flags;
|
||||||
|
String? _senderImage;
|
||||||
late String _signature = "";
|
late String _signature = "";
|
||||||
late bool _ackd = false;
|
late bool _ackd = false;
|
||||||
late bool _error = false;
|
late bool _error = false;
|
||||||
|
@ -402,12 +484,18 @@ class MessageState extends ChangeNotifier {
|
||||||
get message => this._message;
|
get message => this._message;
|
||||||
get overlay => this._overlay;
|
get overlay => this._overlay;
|
||||||
get timestamp => this._timestamp;
|
get timestamp => this._timestamp;
|
||||||
|
int get flags => this._flags;
|
||||||
|
set flags(int newVal) {
|
||||||
|
this._flags = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
bool get ackd => this._ackd;
|
bool get ackd => this._ackd;
|
||||||
bool get error => this._error;
|
bool get error => this._error;
|
||||||
get malformed => this._malformed;
|
bool get malformed => this._malformed;
|
||||||
|
bool get loaded => this._loaded;
|
||||||
get senderOnion => this._senderOnion;
|
get senderOnion => this._senderOnion;
|
||||||
get senderImage => this._senderImage;
|
get senderImage => this._senderImage;
|
||||||
get loaded => this._loaded;
|
|
||||||
get signature => this._signature;
|
get signature => this._signature;
|
||||||
get isInvite => this.overlay == 100 || this.overlay == 101;
|
get isInvite => this.overlay == 100 || this.overlay == 101;
|
||||||
get inviteTarget => this._inviteTarget;
|
get inviteTarget => this._inviteTarget;
|
||||||
|
@ -423,13 +511,24 @@ class MessageState extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set malformed(bool newVal) {
|
||||||
|
this._malformed = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
set loaded(bool newVal) {
|
||||||
|
// quickly-arriving messages get discarded before loading sometimes
|
||||||
|
if (!hasListeners) return;
|
||||||
|
this._loaded = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void tryLoad(BuildContext context) {
|
void tryLoad(BuildContext context) {
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) {
|
Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) {
|
||||||
try {
|
try {
|
||||||
dynamic messageWrapper = jsonDecode(jsonMessage);
|
dynamic messageWrapper = jsonDecode(jsonMessage);
|
||||||
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
||||||
this._senderOnion = profileOnion;
|
this._senderOnion = profileOnion;
|
||||||
//todo: remove once sent group messages are prestored
|
|
||||||
Future.delayed(const Duration(milliseconds: 2), () {
|
Future.delayed(const Duration(milliseconds: 2), () {
|
||||||
tryLoad(context);
|
tryLoad(context);
|
||||||
});
|
});
|
||||||
|
@ -441,6 +540,7 @@ class MessageState extends ChangeNotifier {
|
||||||
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
||||||
this._senderOnion = messageWrapper['PeerID'];
|
this._senderOnion = messageWrapper['PeerID'];
|
||||||
this._senderImage = messageWrapper['ContactImage'];
|
this._senderImage = messageWrapper['ContactImage'];
|
||||||
|
this._flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
|
||||||
|
|
||||||
// If this is a group, store the signature
|
// If this is a group, store the signature
|
||||||
if (contactHandle.length == 32) {
|
if (contactHandle.length == 32) {
|
||||||
|
@ -456,7 +556,6 @@ class MessageState extends ChangeNotifier {
|
||||||
} else {
|
} else {
|
||||||
var parts = message['d'].toString().split("||");
|
var parts = message['d'].toString().split("||");
|
||||||
if (parts.length == 2) {
|
if (parts.length == 2) {
|
||||||
print("jsondecoding: " + utf8.fuse(base64).decode(parts[1].substring(5)));
|
|
||||||
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
||||||
this._inviteTarget = jsonObj['GroupID'];
|
this._inviteTarget = jsonObj['GroupID'];
|
||||||
this._inviteNick = jsonObj['GroupName'];
|
this._inviteNick = jsonObj['GroupName'];
|
||||||
|
@ -464,7 +563,7 @@ class MessageState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._loaded = true;
|
this.loaded = true;
|
||||||
|
|
||||||
//update ackd and error last as they are changenotified
|
//update ackd and error last as they are changenotified
|
||||||
this.ackd = messageWrapper['Acknowledged'];
|
this.ackd = messageWrapper['Acknowledged'];
|
||||||
|
@ -472,7 +571,9 @@ class MessageState extends ChangeNotifier {
|
||||||
this.error = true;
|
this.error = true;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._malformed = true;
|
this._overlay = -1;
|
||||||
|
this.loaded = true;
|
||||||
|
this.malformed = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,10 @@ class NullNotificationsManager implements NotificationsManager {
|
||||||
// the standard dbus-powered linux desktop notifications.
|
// the standard dbus-powered linux desktop notifications.
|
||||||
class LinuxNotificationsManager implements NotificationsManager {
|
class LinuxNotificationsManager implements NotificationsManager {
|
||||||
int previous_id = 0;
|
int previous_id = 0;
|
||||||
|
final NotificationsClient client = NotificationsClient();
|
||||||
LinuxNotificationsManager() {}
|
LinuxNotificationsManager() {}
|
||||||
Future<void> notify(String message) async {
|
Future<void> notify(String message) async {
|
||||||
var client = NotificationsClient();
|
|
||||||
var icon_path = Uri.file(path.join(path.current, "cwtch.png"));
|
var icon_path = Uri.file(path.join(path.current, "cwtch.png"));
|
||||||
client.notify('New Message from Peer!', appName: "cwtch", appIcon: icon_path.toString(), replacesId: this.previous_id).then((Notification value) => previous_id = value.id);
|
client.notify('New Message from Peer!', appName: "cwtch", appIcon: icon_path.toString(), replacesId: this.previous_id).then((Notification value) => previous_id = value.id);
|
||||||
client.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
205
lib/opaque.dart
|
@ -10,6 +10,11 @@ import 'package:cwtch/settings.dart';
|
||||||
|
|
||||||
abstract class OpaqueThemeType {
|
abstract class OpaqueThemeType {
|
||||||
static final Color red = Color(0xFFFF0000);
|
static final Color red = Color(0xFFFF0000);
|
||||||
|
|
||||||
|
String identifier() {
|
||||||
|
return "dummy";
|
||||||
|
}
|
||||||
|
|
||||||
Color backgroundMainColor() {
|
Color backgroundMainColor() {
|
||||||
return red;
|
return red;
|
||||||
}
|
}
|
||||||
|
@ -304,9 +309,15 @@ abstract class OpaqueThemeType {
|
||||||
|
|
||||||
// ... more to come
|
// ... more to come
|
||||||
|
|
||||||
|
// Sizes
|
||||||
|
|
||||||
|
double contactOnionTextSize() {
|
||||||
|
return 18;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CwtchDark extends OpaqueThemeType {
|
class OpaqueDark extends OpaqueThemeType {
|
||||||
static final Color darkGreyPurple = Color(0xFF281831);
|
static final Color darkGreyPurple = Color(0xFF281831);
|
||||||
static final Color deepPurple = Color(0xFF422850);
|
static final Color deepPurple = Color(0xFF422850);
|
||||||
static final Color mauvePurple = Color(0xFF8E64A5);
|
static final Color mauvePurple = Color(0xFF8E64A5);
|
||||||
|
@ -319,6 +330,10 @@ class CwtchDark extends OpaqueThemeType {
|
||||||
static final Color softGreen = Color(0xFFA0FFB0);
|
static final Color softGreen = Color(0xFFA0FFB0);
|
||||||
static final Color softRed = Color(0xFFFFA0B0);
|
static final Color softRed = Color(0xFFFFA0B0);
|
||||||
|
|
||||||
|
String identifier() {
|
||||||
|
return "dark";
|
||||||
|
}
|
||||||
|
|
||||||
Color backgroundMainColor() {
|
Color backgroundMainColor() {
|
||||||
return darkGreyPurple;
|
return darkGreyPurple;
|
||||||
}
|
}
|
||||||
|
@ -612,7 +627,7 @@ class CwtchDark extends OpaqueThemeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CwtchLight extends OpaqueThemeType {
|
class OpaqueLight extends OpaqueThemeType {
|
||||||
static final Color whitePurple = Color(0xFFFFFDFF);
|
static final Color whitePurple = Color(0xFFFFFDFF);
|
||||||
static final Color softPurple = Color(0xFFFDF3FC);
|
static final Color softPurple = Color(0xFFFDF3FC);
|
||||||
static final Color purple = Color(0xFFDFB9DE);
|
static final Color purple = Color(0xFFDFB9DE);
|
||||||
|
@ -625,6 +640,10 @@ class CwtchLight extends OpaqueThemeType {
|
||||||
static final Color softGreen = Color(0xFFA0FFB0);
|
static final Color softGreen = Color(0xFFA0FFB0);
|
||||||
static final Color softRed = Color(0xFFFFA0B0);
|
static final Color softRed = Color(0xFFFFA0B0);
|
||||||
|
|
||||||
|
String identifier() {
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
|
|
||||||
Color backgroundMainColor() {
|
Color backgroundMainColor() {
|
||||||
return whitePurple;
|
return whitePurple;
|
||||||
}
|
}
|
||||||
|
@ -918,6 +937,99 @@ class CwtchLight extends OpaqueThemeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThemeData mkThemeData(Settings opaque) {
|
||||||
|
return ThemeData(
|
||||||
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||||
|
primarySwatch: Colors.red,
|
||||||
|
primaryIconTheme: IconThemeData(
|
||||||
|
color: opaque.current().mainTextColor(),
|
||||||
|
),
|
||||||
|
primaryColor: opaque.current().backgroundMainColor(),
|
||||||
|
canvasColor: opaque.current().backgroundPaneColor(),
|
||||||
|
backgroundColor: opaque.current().backgroundMainColor(),
|
||||||
|
highlightColor: opaque.current().hilightElementTextColor(),
|
||||||
|
iconTheme: IconThemeData(
|
||||||
|
color: opaque.current().mainTextColor(),
|
||||||
|
),
|
||||||
|
cardColor: opaque.current().backgroundMainColor(),
|
||||||
|
appBarTheme: AppBarTheme(
|
||||||
|
backgroundColor: opaque.current().backgroundPaneColor(),
|
||||||
|
titleTextStyle: TextStyle(
|
||||||
|
color: opaque.current().mainTextColor(),
|
||||||
|
),
|
||||||
|
actionsIconTheme: IconThemeData(
|
||||||
|
color: opaque.current().mainTextColor(),
|
||||||
|
)),
|
||||||
|
bottomNavigationBarTheme: BottomNavigationBarThemeData(type: BottomNavigationBarType.fixed, backgroundColor: opaque.current().backgroundHilightElementColor()),
|
||||||
|
textButtonTheme: TextButtonThemeData(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
|
||||||
|
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()),
|
||||||
|
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
|
||||||
|
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()),
|
||||||
|
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.all(20)),
|
||||||
|
shape: MaterialStateProperty.all(RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(18.0),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
|
isAlwaysShown: false, thumbColor: MaterialStateProperty.all(opaque.current().scrollbarActiveColor()), trackColor: MaterialStateProperty.all(opaque.current().scrollbarDefaultColor())),
|
||||||
|
tabBarTheme: TabBarTheme(indicator: UnderlineTabIndicator(borderSide: BorderSide(color: opaque.current().defaultButtonActiveColor()))),
|
||||||
|
dialogTheme: DialogTheme(
|
||||||
|
backgroundColor: opaque.current().backgroundPaneColor(),
|
||||||
|
titleTextStyle: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
contentTextStyle: TextStyle(color: opaque.current().mainTextColor())),
|
||||||
|
textTheme: TextTheme(
|
||||||
|
headline1: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
headline2: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
headline3: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
headline4: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
headline5: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
headline6: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
bodyText1: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
bodyText2: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
subtitle1: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
subtitle2: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
caption: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
button: TextStyle(color: opaque.current().mainTextColor()),
|
||||||
|
overline: TextStyle(color: opaque.current().mainTextColor())),
|
||||||
|
switchTheme: SwitchThemeData(
|
||||||
|
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
||||||
|
thumbColor: MaterialStateProperty.all(opaque.current().mainTextColor()),
|
||||||
|
trackColor: MaterialStateProperty.all(opaque.current().dropShadowColor()),
|
||||||
|
),
|
||||||
|
floatingActionButtonTheme: FloatingActionButtonThemeData(backgroundColor: opaque.current().defaultButtonColor(), hoverColor: opaque.current().defaultButtonActiveColor()),
|
||||||
|
textSelectionTheme: TextSelectionThemeData(
|
||||||
|
cursorColor: opaque.current().defaultButtonActiveColor(), selectionColor: opaque.current().defaultButtonActiveColor(), selectionHandleColor: opaque.current().defaultButtonActiveColor()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
OpaqueThemeType _current = CwtchDark();
|
||||||
|
|
||||||
|
void setDark() {
|
||||||
|
_current = CwtchDark();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLight() {
|
||||||
|
_current = CwtchLight();
|
||||||
|
}
|
||||||
|
|
||||||
|
OpaqueThemeType current() {
|
||||||
|
if (_current == null) {
|
||||||
|
setDark();
|
||||||
|
}
|
||||||
|
return _current;
|
||||||
|
}
|
||||||
|
|
||||||
class Opaque extends OpaqueThemeType {
|
class Opaque extends OpaqueThemeType {
|
||||||
Color backgroundMainColor() {
|
Color backgroundMainColor() {
|
||||||
return current().backgroundMainColor();
|
return current().backgroundMainColor();
|
||||||
|
@ -1226,22 +1338,9 @@ class Opaque extends OpaqueThemeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
static late OpaqueThemeType _current;
|
static late OpaqueThemeType _current;
|
||||||
static final OpaqueThemeType dark = CwtchDark();
|
//static final OpaqueThemeType dark = CwtchDark();
|
||||||
static final OpaqueThemeType light = CwtchLight();
|
//static final OpaqueThemeType light = CwtchLight();
|
||||||
static void setDark() {
|
|
||||||
_current = dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setLight() {
|
|
||||||
_current = light;
|
|
||||||
}
|
|
||||||
|
|
||||||
static OpaqueThemeType current() {
|
|
||||||
if (_current == null) {
|
|
||||||
setDark();
|
|
||||||
}
|
|
||||||
return _current;
|
|
||||||
}
|
|
||||||
|
|
||||||
int scale = 2;
|
int scale = 2;
|
||||||
static final String gcdOS = "linux";
|
static final String gcdOS = "linux";
|
||||||
|
@ -1341,74 +1440,4 @@ class Opaque extends OpaqueThemeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeData mkThemeData(Settings opaque) {
|
*/
|
||||||
return ThemeData(
|
|
||||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
|
||||||
primarySwatch: Colors.red,
|
|
||||||
primaryIconTheme: IconThemeData(
|
|
||||||
color: opaque.current().mainTextColor(),
|
|
||||||
),
|
|
||||||
primaryColor: opaque.current().backgroundMainColor(),
|
|
||||||
canvasColor: opaque.current().backgroundPaneColor(),
|
|
||||||
backgroundColor: opaque.current().backgroundMainColor(),
|
|
||||||
highlightColor: opaque.current().hilightElementTextColor(),
|
|
||||||
iconTheme: IconThemeData(
|
|
||||||
color: opaque.current().mainTextColor(),
|
|
||||||
),
|
|
||||||
cardColor: opaque.current().backgroundMainColor(),
|
|
||||||
appBarTheme: AppBarTheme(
|
|
||||||
backgroundColor: opaque.current().backgroundPaneColor(),
|
|
||||||
titleTextStyle: TextStyle(
|
|
||||||
color: opaque.current().mainTextColor(),
|
|
||||||
),
|
|
||||||
actionsIconTheme: IconThemeData(
|
|
||||||
color: opaque.current().mainTextColor(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
textButtonTheme: TextButtonThemeData(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
|
|
||||||
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()),
|
|
||||||
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
|
||||||
),
|
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
|
|
||||||
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()),
|
|
||||||
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.all(20)),
|
|
||||||
shape: MaterialStateProperty.all(RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(18.0),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
tabBarTheme: TabBarTheme(indicator: UnderlineTabIndicator(borderSide: BorderSide(color: opaque.current().defaultButtonActiveColor()))),
|
|
||||||
dialogTheme: DialogTheme(
|
|
||||||
backgroundColor: opaque.current().backgroundPaneColor(),
|
|
||||||
titleTextStyle: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
contentTextStyle: TextStyle(color: opaque.current().mainTextColor())),
|
|
||||||
textTheme: TextTheme(
|
|
||||||
headline1: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
headline2: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
headline3: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
headline4: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
headline5: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
headline6: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
bodyText1: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
bodyText2: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
subtitle1: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
subtitle2: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
caption: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
button: TextStyle(color: opaque.current().mainTextColor()),
|
|
||||||
overline: TextStyle(color: opaque.current().mainTextColor())),
|
|
||||||
switchTheme: SwitchThemeData(
|
|
||||||
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
|
||||||
thumbColor: MaterialStateProperty.all(opaque.current().mainTextColor()),
|
|
||||||
trackColor: MaterialStateProperty.all(opaque.current().dropShadowColor()),
|
|
||||||
),
|
|
||||||
floatingActionButtonTheme: FloatingActionButtonThemeData(backgroundColor: opaque.current().defaultButtonColor(), hoverColor: opaque.current().defaultButtonActiveColor()),
|
|
||||||
textSelectionTheme: TextSelectionThemeData(
|
|
||||||
cursorColor: opaque.current().defaultButtonActiveColor(), selectionColor: opaque.current().defaultButtonActiveColor(), selectionHandleColor: opaque.current().defaultButtonActiveColor()),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -9,6 +9,13 @@ import 'opaque.dart';
|
||||||
|
|
||||||
const TapirGroupsExperiment = "tapir-groups-experiment";
|
const TapirGroupsExperiment = "tapir-groups-experiment";
|
||||||
|
|
||||||
|
enum DualpaneMode {
|
||||||
|
Single,
|
||||||
|
Dual1to2,
|
||||||
|
Dual1to4,
|
||||||
|
CopyPortrait,
|
||||||
|
}
|
||||||
|
|
||||||
/// Settings govern the *Globally* relevant settings like Locale, Theme and Experiments.
|
/// Settings govern the *Globally* relevant settings like Locale, Theme and Experiments.
|
||||||
/// We also provide access to the version information here as it is also accessed from the
|
/// We also provide access to the version information here as it is also accessed from the
|
||||||
/// Settings Pane.
|
/// Settings Pane.
|
||||||
|
@ -16,20 +23,23 @@ class Settings extends ChangeNotifier {
|
||||||
Locale locale;
|
Locale locale;
|
||||||
late PackageInfo packageInfo;
|
late PackageInfo packageInfo;
|
||||||
OpaqueThemeType theme;
|
OpaqueThemeType theme;
|
||||||
late bool experimentsEnabled;
|
// explicitly set experiments to false until told otherwise...
|
||||||
|
bool experimentsEnabled = false;
|
||||||
HashMap<String, bool> experiments = HashMap.identity();
|
HashMap<String, bool> experiments = HashMap.identity();
|
||||||
|
DualpaneMode _uiColumnModePortrait = DualpaneMode.Single;
|
||||||
|
DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait;
|
||||||
|
|
||||||
late bool blockUnknownConnections;
|
bool blockUnknownConnections = false;
|
||||||
|
|
||||||
/// Set the dark theme.
|
/// Set the dark theme.
|
||||||
void setDark() {
|
void setDark() {
|
||||||
theme = Opaque.dark;
|
theme = OpaqueDark();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the Light theme.
|
/// Set the Light theme.
|
||||||
void setLight() {
|
void setLight() {
|
||||||
theme = Opaque.light;
|
theme = OpaqueLight();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +48,18 @@ class Settings extends ChangeNotifier {
|
||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// isExperimentEnabled can be used to safely check whether a particular
|
||||||
|
/// experiment is enabled
|
||||||
|
bool isExperimentEnabled(String experiment) {
|
||||||
|
if (this.experimentsEnabled) {
|
||||||
|
if (this.experiments.containsKey(experiment)) {
|
||||||
|
// We now know it cannot be null...
|
||||||
|
return this.experiments[experiment]! == true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Called by the event bus. When new settings are loaded from a file the JSON will
|
/// Called by the event bus. When new settings are loaded from a file the JSON will
|
||||||
/// be sent to the function and new settings will be instantiated based on the contents.
|
/// be sent to the function and new settings will be instantiated based on the contents.
|
||||||
handleUpdate(dynamic settings) {
|
handleUpdate(dynamic settings) {
|
||||||
|
@ -60,6 +82,10 @@ class Settings extends ChangeNotifier {
|
||||||
// Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON
|
// Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON
|
||||||
experiments = new HashMap<String, bool>.from(settings["Experiments"]);
|
experiments = new HashMap<String, bool>.from(settings["Experiments"]);
|
||||||
|
|
||||||
|
// single pane vs dual pane preferences
|
||||||
|
_uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]);
|
||||||
|
_uiColumnModeLandscape = uiColumnModeFromString(settings["UIColumnModeLandscape"]);
|
||||||
|
|
||||||
// Push the experimental settings to Consumers of Settings
|
// Push the experimental settings to Consumers of Settings
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -121,16 +147,62 @@ class Settings extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DualpaneMode get uiColumnModePortrait => _uiColumnModePortrait;
|
||||||
|
set uiColumnModePortrait(DualpaneMode newval) {
|
||||||
|
this._uiColumnModePortrait = newval;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
DualpaneMode get uiColumnModeLandscape => _uiColumnModeLandscape;
|
||||||
|
set uiColumnModeLandscape(DualpaneMode newval) {
|
||||||
|
this._uiColumnModeLandscape = newval;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> uiColumns(bool isLandscape) {
|
||||||
|
var m = (!isLandscape || uiColumnModeLandscape == DualpaneMode.CopyPortrait) ? uiColumnModePortrait : uiColumnModeLandscape;
|
||||||
|
switch(m) {
|
||||||
|
case DualpaneMode.Single: return [1];
|
||||||
|
case DualpaneMode.Dual1to2: return [1, 2];
|
||||||
|
case DualpaneMode.Dual1to4: return [1, 4];
|
||||||
|
}
|
||||||
|
print("impossible column configuration: portrait/$uiColumnModePortrait landscape/$uiColumnModeLandscape");
|
||||||
|
return [1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<DualpaneMode> uiColumnModeOptions(bool isLandscape) {
|
||||||
|
if (isLandscape) return [DualpaneMode.CopyPortrait, DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4,];
|
||||||
|
else return [DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4];
|
||||||
|
}
|
||||||
|
|
||||||
|
static DualpaneMode uiColumnModeFromString(String m) {
|
||||||
|
switch(m) {
|
||||||
|
case "DualpaneMode.Single": return DualpaneMode.Single;
|
||||||
|
case "DualpaneMode.Dual1to2": return DualpaneMode.Dual1to2;
|
||||||
|
case "DualpaneMode.Dual1to4": return DualpaneMode.Dual1to4;
|
||||||
|
case "DualpaneMode.CopyPortrait": return DualpaneMode.CopyPortrait;
|
||||||
|
}
|
||||||
|
print("Error: ui requested translation of column mode [$m] which doesn't exist");
|
||||||
|
return DualpaneMode.Single;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String uiColumnModeToString(DualpaneMode m) {
|
||||||
|
// todo: translate
|
||||||
|
switch(m) {
|
||||||
|
case DualpaneMode.Single: return "Single";
|
||||||
|
case DualpaneMode.Dual1to2: return "Double (1:2)";
|
||||||
|
case DualpaneMode.Dual1to4: return "Double (1:4)";
|
||||||
|
case DualpaneMode.CopyPortrait: return "Same as portrait mode setting";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a default settings object.
|
/// Construct a default settings object.
|
||||||
Settings(this.locale, this.theme);
|
Settings(this.locale, this.theme);
|
||||||
|
|
||||||
/// Convert this Settings object to a JSON representation for serialization on the
|
/// Convert this Settings object to a JSON representation for serialization on the
|
||||||
/// event bus.
|
/// event bus.
|
||||||
dynamic asJson() {
|
dynamic asJson() {
|
||||||
var themeString = "light";
|
var themeString = theme.identifier();
|
||||||
if (theme == Opaque.dark) {
|
|
||||||
themeString = "dark";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"Locale": this.locale.languageCode,
|
"Locale": this.locale.languageCode,
|
||||||
|
@ -140,7 +212,9 @@ class Settings extends ChangeNotifier {
|
||||||
"ExperimentsEnabled": this.experimentsEnabled,
|
"ExperimentsEnabled": this.experimentsEnabled,
|
||||||
"Experiments": experiments,
|
"Experiments": experiments,
|
||||||
"StateRootPane": 0,
|
"StateRootPane": 0,
|
||||||
"FirstTime": false
|
"FirstTime": false,
|
||||||
|
"UIColumnModePortrait": uiColumnModePortrait.toString(),
|
||||||
|
"UIColumnModeLandscape": uiColumnModeLandscape.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ class TorStatus extends ChangeNotifier {
|
||||||
int progress;
|
int progress;
|
||||||
String status;
|
String status;
|
||||||
bool connected;
|
bool connected;
|
||||||
|
String version;
|
||||||
|
|
||||||
TorStatus({this.connected = false, this.progress = 0, this.status = ""});
|
TorStatus({this.connected = false, this.progress = 0, this.status = "", this.version = ""});
|
||||||
|
|
||||||
/// Called by the event bus.
|
/// Called by the event bus.
|
||||||
handleUpdate(int new_progress, String new_status) {
|
handleUpdate(int new_progress, String new_status) {
|
||||||
|
@ -20,4 +21,9 @@ class TorStatus extends ChangeNotifier {
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateVersion(String new_version) {
|
||||||
|
version = new_version;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:cwtch/errorHandler.dart';
|
import 'package:cwtch/errorHandler.dart';
|
||||||
|
@ -49,8 +50,8 @@ class _AddContactViewState extends State<AddContactView> {
|
||||||
Widget _buildForm() {
|
Widget _buildForm() {
|
||||||
ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
|
ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
|
||||||
|
|
||||||
/// We display a different number of tabs dependening on the experiment setup
|
/// We display a different number of tabs depending on the experiment setup
|
||||||
bool groupsEnabled = Provider.of<Settings>(context).experimentsEnabled && Provider.of<Settings>(context).experiments[TapirGroupsExperiment]!;
|
bool groupsEnabled = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
|
||||||
return Consumer<ErrorHandler>(builder: (context, globalErrorHandler, child) {
|
return Consumer<ErrorHandler>(builder: (context, globalErrorHandler, child) {
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: groupsEnabled ? 2 : 1,
|
length: groupsEnabled ? 2 : 1,
|
||||||
|
@ -80,7 +81,7 @@ class _AddContactViewState extends State<AddContactView> {
|
||||||
return TabBar(
|
return TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(
|
Tab(
|
||||||
icon: Icon(Icons.person_add_rounded),
|
icon: Icon(CwtchIcons.add_peer),
|
||||||
text: AppLocalizations.of(context)!.addPeer,
|
text: AppLocalizations.of(context)!.addPeer,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -92,11 +93,11 @@ class _AddContactViewState extends State<AddContactView> {
|
||||||
return TabBar(
|
return TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(
|
Tab(
|
||||||
icon: Icon(Icons.person_add_rounded),
|
icon: Icon(CwtchIcons.add_peer),
|
||||||
text: AppLocalizations.of(context)!.tooltipAddContact,
|
text: AppLocalizations.of(context)!.tooltipAddContact,
|
||||||
),
|
),
|
||||||
//Tab(icon: Icon(Icons.backup), text: AppLocalizations.of(context)!.titleManageServers),
|
//Tab(icon: Icon(Icons.backup), text: AppLocalizations.of(context)!.titleManageServers),
|
||||||
Tab(icon: Icon(Icons.group), text: AppLocalizations.of(context)!.createGroup),
|
Tab(icon: Icon(CwtchIcons.add_group), text: AppLocalizations.of(context)!.createGroup),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -118,7 +119,11 @@ class _AddContactViewState extends State<AddContactView> {
|
||||||
CwtchButtonTextField(
|
CwtchButtonTextField(
|
||||||
controller: ctrlrOnion,
|
controller: ctrlrOnion,
|
||||||
onPressed: _copyOnion,
|
onPressed: _copyOnion,
|
||||||
icon: Icon(Icons.copy),
|
readonly: true,
|
||||||
|
icon: Icon(
|
||||||
|
CwtchIcons.address_copy_2,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
tooltip: AppLocalizations.of(context)!.copyBtn,
|
tooltip: AppLocalizations.of(context)!.copyBtn,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
@ -162,7 +167,7 @@ class _AddContactViewState extends State<AddContactView> {
|
||||||
Widget addGroupTab() {
|
Widget addGroupTab() {
|
||||||
// TODO We should replace with with a "Paste in Server Key Bundle"
|
// TODO We should replace with with a "Paste in Server Key Bundle"
|
||||||
if (Provider.of<ProfileInfoState>(context).serverList.servers.isEmpty) {
|
if (Provider.of<ProfileInfoState>(context).serverList.servers.isEmpty) {
|
||||||
return Text("You need to add a server before you can create a group.");
|
return Text(AppLocalizations.of(context)!.addServerFirst);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -11,6 +12,8 @@ import 'package:cwtch/widgets/textfield.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import '../cwtch_icons_icons.dart';
|
||||||
|
import '../errorHandler.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../opaque.dart';
|
import '../opaque.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
@ -99,6 +102,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
),
|
),
|
||||||
CwtchTextField(
|
CwtchTextField(
|
||||||
controller: ctrlrNick,
|
controller: ctrlrNick,
|
||||||
|
autofocus: false,
|
||||||
labelText: AppLocalizations.of(context)!.yourDisplayName,
|
labelText: AppLocalizations.of(context)!.yourDisplayName,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
|
@ -121,7 +125,11 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
CwtchButtonTextField(
|
CwtchButtonTextField(
|
||||||
controller: ctrlrOnion,
|
controller: ctrlrOnion,
|
||||||
onPressed: _copyOnion,
|
onPressed: _copyOnion,
|
||||||
icon: Icon(Icons.copy),
|
readonly: true,
|
||||||
|
icon: Icon(
|
||||||
|
CwtchIcons.address_copy_2,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
tooltip: AppLocalizations.of(context)!.copyBtn,
|
tooltip: AppLocalizations.of(context)!.copyBtn,
|
||||||
)
|
)
|
||||||
])),
|
])),
|
||||||
|
@ -147,7 +155,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
visible: usePassword,
|
visible: usePassword,
|
||||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
|
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty && Provider.of<ProfileInfoState>(context).isEncrypted,
|
||||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
|
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
@ -157,9 +165,12 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
controller: ctrlrOldPass,
|
controller: ctrlrOldPass,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
// Password field can be empty when just updating the profile, not on creation
|
// Password field can be empty when just updating the profile, not on creation
|
||||||
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) {
|
if (Provider.of<ProfileInfoState>(context).isEncrypted && Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) {
|
||||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||||
}
|
}
|
||||||
|
if (Provider.of<ErrorHandler>(context).deleteProfileError == true) {
|
||||||
|
return AppLocalizations.of(context)!.enterCurrentPasswordForDelete;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -167,7 +178,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
])),
|
])),
|
||||||
CwtchLabel(label: AppLocalizations.of(context)!.password1Label),
|
CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
|
@ -231,11 +242,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: AppLocalizations.of(context)!.enterCurrentPasswordForDelete,
|
message: AppLocalizations.of(context)!.enterCurrentPasswordForDelete,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: checkCurrentPassword()
|
onPressed: () {
|
||||||
? null
|
showAlertDialog(context);
|
||||||
: () {
|
},
|
||||||
showAlertDialog(context);
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
|
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
|
||||||
icon: Icon(Icons.delete_forever),
|
icon: Icon(Icons.delete_forever),
|
||||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||||
|
@ -301,52 +310,49 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Stub - wire this into a libCwtch call.
|
showAlertDialog(BuildContext context) {
|
||||||
bool checkCurrentPassword() {
|
// set up the buttons
|
||||||
return ctrlrOldPass.value.text.isEmpty;
|
Widget cancelButton = TextButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // dismiss dialog
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Widget continueButton = TextButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
||||||
|
onPressed: () {
|
||||||
|
var onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteProfile(onion, ctrlrOldPass.value.text);
|
||||||
|
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(milliseconds: 500),
|
||||||
|
() {
|
||||||
|
if (globalErrorHandler.deleteProfileSuccess) {
|
||||||
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteProfileSuccess + ":" + onion));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
Navigator.of(context).popUntil((route) => route.isFirst); // dismiss dialog
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// set up the AlertDialog
|
||||||
|
AlertDialog alert = AlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
||||||
|
actions: [
|
||||||
|
cancelButton,
|
||||||
|
continueButton,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// show the dialog
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return alert;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showAlertDialog(BuildContext context) {
|
|
||||||
// set up the buttons
|
|
||||||
Widget cancelButton = TextButton(
|
|
||||||
child: Text("Cancel"),
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
|
|
||||||
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()),
|
|
||||||
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()),
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(); // dismiss dialog
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Widget continueButton = TextButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
|
|
||||||
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()),
|
|
||||||
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()),
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
|
||||||
child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
|
||||||
onPressed: () {
|
|
||||||
// TODO Actually Delete the Peer
|
|
||||||
Navigator.of(context).pop(); // dismiss dialog
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// set up the AlertDialog
|
|
||||||
AlertDialog alert = AlertDialog(
|
|
||||||
title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
|
||||||
actions: [
|
|
||||||
cancelButton,
|
|
||||||
continueButton,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// show the dialog
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return alert;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cwtch/views/torstatusview.dart';
|
import 'package:cwtch/views/torstatusview.dart';
|
||||||
import 'package:cwtch/widgets/contactrow.dart';
|
import 'package:cwtch/widgets/contactrow.dart';
|
||||||
|
@ -47,7 +48,7 @@ class _ContactsViewState extends State<ContactsView> {
|
||||||
width: 10,
|
width: 10,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text("%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", "Contacts"),
|
child: Text("%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts),
|
||||||
overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor()))), //todo
|
overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor()))), //todo
|
||||||
])),
|
])),
|
||||||
actions: [
|
actions: [
|
||||||
|
@ -66,7 +67,7 @@ class _ContactsViewState extends State<ContactsView> {
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: _pushAddContact,
|
onPressed: _pushAddContact,
|
||||||
tooltip: AppLocalizations.of(context)!.tooltipAddContact,
|
tooltip: AppLocalizations.of(context)!.tooltipAddContact,
|
||||||
child: const Icon(Icons.person_add_sharp),
|
child: const Icon(CwtchIcons.person_add_alt_1_24px),
|
||||||
),
|
),
|
||||||
body: showSearchBar || Provider.of<ContactListState>(context).isFiltered ? _buildFilterable() : _buildContactList());
|
body: showSearchBar || Provider.of<ContactListState>(context).isFiltered ? _buildFilterable() : _buildContactList());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
|
import '../settings.dart';
|
||||||
import 'contactsview.dart';
|
import 'contactsview.dart';
|
||||||
import 'messageview.dart';
|
import 'messageview.dart';
|
||||||
|
|
||||||
|
@ -14,24 +15,25 @@ class DoubleColumnView extends StatefulWidget {
|
||||||
class _DoubleColumnViewState extends State<DoubleColumnView> {
|
class _DoubleColumnViewState extends State<DoubleColumnView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var flwtch = Provider.of<FlwtchState>(context);
|
var flwtch = Provider.of<AppState>(context);
|
||||||
|
var cols = Provider.of<Settings>(context).uiColumns(true);
|
||||||
return Flex(
|
return Flex(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[0],
|
flex: cols[0],
|
||||||
child: ContactsView(
|
child: ContactsView(
|
||||||
key: widget.key,
|
key: widget.key,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[1],
|
flex: cols[1],
|
||||||
child: flwtch.selectedConversation == ""
|
child: flwtch.selectedConversation == null
|
||||||
? Center(child: Text("pick a contact"))
|
? Card(child:Center(child: Text(AppLocalizations.of(context)!.addContactFirst)))
|
||||||
: //dev
|
: //dev
|
||||||
MultiProvider(providers: [
|
MultiProvider(providers: [
|
||||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
|
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
|
||||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation)!),
|
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation!)!),
|
||||||
], child: Container(child: MessageView())),
|
], child: Container(child: MessageView())),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cwtch/opaque.dart';
|
import 'package:cwtch/opaque.dart';
|
||||||
|
@ -45,7 +47,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.settingLanguage, style: TextStyle(color: settings.current().mainTextColor())),
|
title: Text(AppLocalizations.of(context)!.settingLanguage, style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
leading: Icon(Icons.language, color: settings.current().mainTextColor()),
|
leading: Icon(CwtchIcons.change_language, color: settings.current().mainTextColor()),
|
||||||
trailing: DropdownButton(
|
trailing: DropdownButton(
|
||||||
value: Provider.of<Settings>(context).locale.languageCode,
|
value: Provider.of<Settings>(context).locale.languageCode,
|
||||||
onChanged: (String? newValue) {
|
onChanged: (String? newValue) {
|
||||||
|
@ -62,7 +64,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
}).toList())),
|
}).toList())),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.settingTheme, style: TextStyle(color: settings.current().mainTextColor())),
|
title: Text(AppLocalizations.of(context)!.settingTheme, style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
value: settings.current() == Opaque.light,
|
value: settings.current().identifier() == "light",
|
||||||
onChanged: (bool value) {
|
onChanged: (bool value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
settings.setLight();
|
settings.setLight();
|
||||||
|
@ -73,26 +75,38 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
// Save Settings...
|
// Save Settings...
|
||||||
saveSettings(context);
|
saveSettings(context);
|
||||||
},
|
},
|
||||||
secondary: Icon(Icons.lightbulb_outline, color: settings.current().mainTextColor()),
|
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||||
|
secondary: Icon(CwtchIcons.change_theme, color: settings.current().mainTextColor()),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(/*AppLocalizations.of(context)!.settingLanguage*/ "UI Columns", style: TextStyle(color: settings.current().mainTextColor())),
|
title: Text(/*AppLocalizations.of(context)!.settingLanguage*/ "UI Columns in Portrait Mode", style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
||||||
trailing: DropdownButton(
|
trailing: DropdownButton(
|
||||||
value: "Single",
|
value: settings.uiColumnModePortrait.toString(),
|
||||||
onChanged: (String? newValue) {
|
onChanged: (String? newValue) {
|
||||||
if (newValue == "Double (1:2)") {
|
settings.uiColumnModePortrait = Settings.uiColumnModeFromString(newValue!);
|
||||||
Provider.of<FlwtchState>(context).columns = [1, 2];
|
saveSettings(context);
|
||||||
} else if (newValue == "Double (1:4)") {
|
|
||||||
Provider.of<FlwtchState>(context).columns = [1, 4];
|
|
||||||
} else {
|
|
||||||
Provider.of<FlwtchState>(context).columns = [1];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
items: ["Single", "Double (1:2)", "Double (1:4)"].map<DropdownMenuItem<String>>((String value) {
|
items: Settings.uiColumnModeOptions(false).map<DropdownMenuItem<String>>((DualpaneMode value) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: value,
|
value: value.toString(),
|
||||||
child: Text(value),
|
child: Text(Settings.uiColumnModeToString(value)),
|
||||||
|
);
|
||||||
|
}).toList())),
|
||||||
|
ListTile(
|
||||||
|
title: Text(/*AppLocalizations.of(context)!.settingLanguage*/ "UI Columns in Landscape Mode", style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
|
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
||||||
|
trailing: DropdownButton(
|
||||||
|
value: settings.uiColumnModeLandscape.toString(),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!);
|
||||||
|
saveSettings(context);
|
||||||
|
},
|
||||||
|
items: Settings.uiColumnModeOptions(true).map<DropdownMenuItem<String>>((DualpaneMode value) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: value.toString(),
|
||||||
|
child: Text(Settings.uiColumnModeToString(value)),
|
||||||
);
|
);
|
||||||
}).toList())),
|
}).toList())),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
|
@ -109,7 +123,9 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
// Save Settings...
|
// Save Settings...
|
||||||
saveSettings(context);
|
saveSettings(context);
|
||||||
},
|
},
|
||||||
secondary: Icon(Icons.app_blocking, color: settings.current().mainTextColor()),
|
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||||
|
secondary: Icon(CwtchIcons.block_unknown, color: settings.current().mainTextColor()),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
title: Text(AppLocalizations.of(context)!.experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
|
@ -124,7 +140,9 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
// Save Settings...
|
// Save Settings...
|
||||||
saveSettings(context);
|
saveSettings(context);
|
||||||
},
|
},
|
||||||
secondary: Icon(Icons.science, color: settings.current().mainTextColor()),
|
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||||
|
secondary: Icon(CwtchIcons.enable_experiments, color: settings.current().mainTextColor()),
|
||||||
),
|
),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: settings.experimentsEnabled,
|
visible: settings.experimentsEnabled,
|
||||||
|
@ -133,7 +151,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.enableGroups, style: TextStyle(color: settings.current().mainTextColor())),
|
title: Text(AppLocalizations.of(context)!.enableGroups, style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
subtitle: Text(AppLocalizations.of(context)!.descriptionExperimentsGroups),
|
subtitle: Text(AppLocalizations.of(context)!.descriptionExperimentsGroups),
|
||||||
value: settings.experiments.containsKey(TapirGroupsExperiment) && settings.experiments[TapirGroupsExperiment]!,
|
value: settings.isExperimentEnabled(TapirGroupsExperiment),
|
||||||
onChanged: (bool value) {
|
onChanged: (bool value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
settings.enableExperiment(TapirGroupsExperiment);
|
settings.enableExperiment(TapirGroupsExperiment);
|
||||||
|
@ -143,19 +161,15 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
// Save Settings...
|
// Save Settings...
|
||||||
saveSettings(context);
|
saveSettings(context);
|
||||||
},
|
},
|
||||||
secondary: Icon(Icons.group_sharp, color: settings.current().mainTextColor()),
|
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||||
|
secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
AboutListTile(
|
AboutListTile(
|
||||||
icon: Icon(Icons.info, color: settings.current().mainTextColor()),
|
icon: Icon(Icons.info, color: settings.current().mainTextColor()),
|
||||||
applicationIcon: Padding(
|
applicationIcon: Padding(padding:EdgeInsets.all(5), child: Icon(CwtchIcons.cwtch_knott)),
|
||||||
padding: EdgeInsets.all(20),
|
|
||||||
child: Image(
|
|
||||||
image: AssetImage("assets/knott.png"),
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
)),
|
|
||||||
applicationName: "Cwtch (Flutter UI)",
|
applicationName: "Cwtch (Flutter UI)",
|
||||||
applicationVersion: AppLocalizations.of(context)!.versionBuilddate.replaceAll("%1", EnvironmentConfig.BUILD_VER).replaceAll("%2", EnvironmentConfig.BUILD_DATE),
|
applicationVersion: AppLocalizations.of(context)!.versionBuilddate.replaceAll("%1", EnvironmentConfig.BUILD_VER).replaceAll("%2", EnvironmentConfig.BUILD_DATE),
|
||||||
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
|
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:cwtch/model.dart';
|
import 'package:cwtch/model.dart';
|
||||||
import 'package:cwtch/widgets/buttontextfield.dart';
|
import 'package:cwtch/widgets/buttontextfield.dart';
|
||||||
|
@ -135,13 +136,13 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: AppLocalizations.of(context)!.rejectGroupBtn,
|
message: AppLocalizations.of(context)!.leaveGroup,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showAlertDialog(context);
|
showAlertDialog(context);
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.delete),
|
icon: Icon(CwtchIcons.leave_group),
|
||||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
label: Text(AppLocalizations.of(context)!.leaveGroup),
|
||||||
))
|
))
|
||||||
])
|
])
|
||||||
])))));
|
])))));
|
||||||
|
@ -158,7 +159,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
||||||
showAlertDialog(BuildContext context) {
|
showAlertDialog(BuildContext context) {
|
||||||
// set up the buttons
|
// set up the buttons
|
||||||
Widget cancelButton = TextButton(
|
Widget cancelButton = TextButton(
|
||||||
child: Text("Cancel"),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(); // dismiss dialog
|
Navigator.of(context).pop(); // dismiss dialog
|
||||||
|
@ -166,7 +167,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
||||||
);
|
);
|
||||||
Widget continueButton = TextButton(
|
Widget continueButton = TextButton(
|
||||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||||
child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
child: Text(AppLocalizations.of(context)!.yesLeave),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||||
|
@ -179,7 +180,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
||||||
|
|
||||||
// set up the AlertDialog
|
// set up the AlertDialog
|
||||||
AlertDialog alert = AlertDialog(
|
AlertDialog alert = AlertDialog(
|
||||||
title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
title: Text(AppLocalizations.of(context)!.reallyLeaveThisGroupPrompt),
|
||||||
actions: [
|
actions: [
|
||||||
cancelButton,
|
cancelButton,
|
||||||
continueButton,
|
continueButton,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
|
import 'package:cwtch/widgets/profileimage.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cwtch/views/peersettingsview.dart';
|
import 'package:cwtch/views/peersettingsview.dart';
|
||||||
|
@ -40,17 +43,32 @@ class _MessageViewState extends State<MessageView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var appState = Provider.of<AppState>(context);
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: _onWillPop,
|
onWillPop: _onWillPop,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(Provider.of<ContactInfoState>(context).nickname),
|
// setting leading to null makes it do the default behaviour; container() hides it
|
||||||
|
leading: Provider.of<Settings>(context).uiColumns(appState.isLandscape(context)).length > 1 ? Container() : null,
|
||||||
|
title: Row(children: [
|
||||||
|
ProfileImage(
|
||||||
|
imagePath: Provider.of<ContactInfoState>(context).imagePath,
|
||||||
|
diameter: 42,
|
||||||
|
border: Provider.of<Settings>(context).current().portraitOnlineBorderColor(),
|
||||||
|
badgeTextColor: Colors.red,
|
||||||
|
badgeColor: Colors.red,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),Text(Provider.of<ContactInfoState>(context).nickname)]),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(icon: Icon(Icons.chat), onPressed: _pushContactSettings),
|
//IconButton(icon: Icon(Icons.chat), onPressed: _pushContactSettings),
|
||||||
IconButton(icon: Icon(Icons.list), onPressed: _pushContactSettings),
|
//IconButton(icon: Icon(Icons.list), onPressed: _pushContactSettings),
|
||||||
IconButton(icon: Icon(Icons.push_pin), onPressed: _pushContactSettings),
|
//IconButton(icon: Icon(Icons.push_pin), onPressed: _pushContactSettings),
|
||||||
IconButton(icon: Icon(Icons.settings), onPressed: _pushContactSettings),
|
IconButton(
|
||||||
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _debugResetContact),
|
icon: Provider.of<ContactInfoState>(context, listen: false).isGroup == true ? Icon(CwtchIcons.group_settings_24px) : Icon(CwtchIcons.peer_settings_24px),
|
||||||
|
tooltip: AppLocalizations.of(context)!.conversationSettings,
|
||||||
|
onPressed: _pushContactSettings),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Padding(padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0), child: MessageList()),
|
body: Padding(padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0), child: MessageList()),
|
||||||
|
@ -63,12 +81,6 @@ class _MessageViewState extends State<MessageView> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _debugResetContact() {
|
|
||||||
Provider.of<FlwtchState>(context, listen: false)
|
|
||||||
.cwtch
|
|
||||||
.DebugResetContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _pushContactSettings() {
|
void _pushContactSettings() {
|
||||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||||
builder: (BuildContext bcontext) {
|
builder: (BuildContext bcontext) {
|
||||||
|
@ -126,7 +138,7 @@ class _MessageViewState extends State<MessageView> {
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
key: Key('txtCompose'),
|
key: Key('txtCompose'),
|
||||||
controller: ctrlrCompose,
|
controller: ctrlrCompose,
|
||||||
autofocus: true,
|
autofocus: !Platform.isAndroid,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
textInputAction: TextInputAction.send,
|
textInputAction: TextInputAction.send,
|
||||||
onFieldSubmitted: _sendMessage,
|
onFieldSubmitted: _sendMessage,
|
||||||
|
@ -135,12 +147,15 @@ class _MessageViewState extends State<MessageView> {
|
||||||
focusedBorder: InputBorder.none,
|
focusedBorder: InputBorder.none,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
prefixIcon: IconButton(
|
prefixIcon: IconButton(
|
||||||
icon: Icon(Icons.insert_invitation, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
icon: Icon(CwtchIcons.send_invite, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||||
tooltip: "Send a contact or group invite",
|
tooltip: AppLocalizations.of(context)!.sendInvite,
|
||||||
|
enableFeedback: true,
|
||||||
|
splashColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||||
|
hoverColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||||
onPressed: () => _modalSendInvitation(context)),
|
onPressed: () => _modalSendInvitation(context)),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(Icons.send, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
icon: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||||
tooltip: "Send Message",
|
tooltip: AppLocalizations.of(context)!.sendMessage,
|
||||||
onPressed: _sendMessage,
|
onPressed: _sendMessage,
|
||||||
),
|
),
|
||||||
))),
|
))),
|
||||||
|
@ -174,7 +189,9 @@ class _MessageViewState extends State<MessageView> {
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider.value(
|
ChangeNotifierProvider.value(
|
||||||
value: Provider.of<ProfileInfoState>(ctx, listen: false),
|
value: Provider.of<ProfileInfoState>(ctx, listen: false),
|
||||||
child: DropdownContacts(onChanged: (newVal) {
|
child: DropdownContacts(filter: (contact) {
|
||||||
|
return contact.onion != Provider.of<ContactInfoState>(context).onion;
|
||||||
|
}, onChanged: (newVal) {
|
||||||
setState(() {
|
setState(() {
|
||||||
this.selectedContact = newVal;
|
this.selectedContact = newVal;
|
||||||
});
|
});
|
||||||
|
@ -185,7 +202,9 @@ class _MessageViewState extends State<MessageView> {
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn),
|
child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
this._sendInvitation();
|
if (this.selectedContact != "") {
|
||||||
|
this._sendInvitation();
|
||||||
|
}
|
||||||
Navigator.pop(bcontext);
|
Navigator.pop(bcontext);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:cwtch/model.dart';
|
import 'package:cwtch/model.dart';
|
||||||
import 'package:cwtch/widgets/buttontextfield.dart';
|
import 'package:cwtch/widgets/buttontextfield.dart';
|
||||||
|
@ -76,8 +77,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
||||||
};
|
};
|
||||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||||
// todo translations
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
|
||||||
final snackBar = SnackBar(content: Text("Nickname changed successfully"));
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.save),
|
icon: Icon(Icons.save),
|
||||||
|
@ -138,12 +138,14 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
secondary: Icon(Icons.block, color: settings.current().mainTextColor()),
|
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||||
|
secondary: Icon(CwtchIcons.block_peer, color: settings.current().mainTextColor()),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.savePeerHistory, style: TextStyle(color: settings.current().mainTextColor())),
|
title: Text(AppLocalizations.of(context)!.savePeerHistory, style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
subtitle: Text(AppLocalizations.of(context)!.savePeerHistoryDescription),
|
subtitle: Text(AppLocalizations.of(context)!.savePeerHistoryDescription),
|
||||||
leading: Icon(Icons.history_sharp, color: settings.current().mainTextColor()),
|
leading: Icon(CwtchIcons.peer_history, color: settings.current().mainTextColor()),
|
||||||
trailing: DropdownButton(
|
trailing: DropdownButton(
|
||||||
value: Provider.of<ContactInfoState>(context).savePeerHistory == "DefaultDeleteHistory"
|
value: Provider.of<ContactInfoState>(context).savePeerHistory == "DefaultDeleteHistory"
|
||||||
? AppLocalizations.of(context)!.dontSavePeerHistory
|
? AppLocalizations.of(context)!.dontSavePeerHistory
|
||||||
|
@ -184,6 +186,22 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
||||||
);
|
);
|
||||||
}).toList())),
|
}).toList())),
|
||||||
]),
|
]),
|
||||||
|
Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||||
|
Tooltip(
|
||||||
|
message: AppLocalizations.of(context)!.leaveGroup,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
showAlertDialog(context);
|
||||||
|
},
|
||||||
|
icon: Icon(CwtchIcons.leave_chat),
|
||||||
|
label: Text(AppLocalizations.of(context)!.leaveGroup),
|
||||||
|
))
|
||||||
|
])
|
||||||
|
]),
|
||||||
])))));
|
])))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -194,4 +212,44 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
||||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedClipboardNotification));
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedClipboardNotification));
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showAlertDialog(BuildContext context) {
|
||||||
|
// set up the buttons
|
||||||
|
Widget cancelButton = TextButton(
|
||||||
|
child: Text("Cancel"),
|
||||||
|
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // dismiss dialog
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Widget continueButton = TextButton(
|
||||||
|
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||||
|
child: Text(AppLocalizations.of(context)!.yesLeave),
|
||||||
|
onPressed: () {
|
||||||
|
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||||
|
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.LeaveConversation(profileOnion, handle);
|
||||||
|
Future.delayed(Duration(milliseconds: 500), () {
|
||||||
|
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// set up the AlertDialog
|
||||||
|
AlertDialog alert = AlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(context)!.reallyLeaveThisGroupPrompt),
|
||||||
|
actions: [
|
||||||
|
cancelButton,
|
||||||
|
continueButton,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// show the dialog
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return alert;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cwtch/settings.dart';
|
import 'package:cwtch/settings.dart';
|
||||||
import 'package:cwtch/views/torstatusview.dart';
|
import 'package:cwtch/views/torstatusview.dart';
|
||||||
import 'package:cwtch/widgets/passwordfield.dart';
|
import 'package:cwtch/widgets/passwordfield.dart';
|
||||||
import 'package:cwtch/widgets/tor_icon.dart';
|
import 'package:cwtch/widgets/tor_icon.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:cwtch/widgets/profilerow.dart';
|
import 'package:cwtch/widgets/profilerow.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import '../config.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import '../opaque.dart';
|
import '../torstatus.dart';
|
||||||
import 'addeditprofileview.dart';
|
import 'addeditprofileview.dart';
|
||||||
import 'globalsettingsview.dart';
|
import 'globalsettingsview.dart';
|
||||||
|
|
||||||
|
@ -24,6 +28,8 @@ class ProfileMgrView extends StatefulWidget {
|
||||||
class _ProfileMgrViewState extends State<ProfileMgrView> {
|
class _ProfileMgrViewState extends State<ProfileMgrView> {
|
||||||
final ctrlrPassword = TextEditingController();
|
final ctrlrPassword = TextEditingController();
|
||||||
|
|
||||||
|
bool closeApp = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
ctrlrPassword.dispose();
|
ctrlrPassword.dispose();
|
||||||
|
@ -36,57 +42,111 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
||||||
// Prevents Android back button from closing the app on the profile manager screen
|
// Prevents Android back button from closing the app on the profile manager screen
|
||||||
// (which would shutdown connections and all kinds of other expensive to generate things)
|
// (which would shutdown connections and all kinds of other expensive to generate things)
|
||||||
// TODO pop up a dialogue regarding closing the app?
|
// TODO pop up a dialogue regarding closing the app?
|
||||||
builder: (context, settings, child) => WillPopScope(
|
builder: (context, settings, child) =>
|
||||||
onWillPop: () async => false,
|
WillPopScope(
|
||||||
child: Scaffold(
|
onWillPop: () async {
|
||||||
backgroundColor: settings.theme.backgroundMainColor(),
|
_showShutdown();
|
||||||
appBar: AppBar(
|
return closeApp;
|
||||||
title: Row(children: [
|
},
|
||||||
Image(
|
child: Scaffold(
|
||||||
image: AssetImage("assets/core/knott-white.png"),
|
backgroundColor: settings.theme.backgroundMainColor(),
|
||||||
filterQuality: FilterQuality.medium,
|
appBar: AppBar(
|
||||||
isAntiAlias: true,
|
title: Row(children: [
|
||||||
width: 32,
|
Image(
|
||||||
height: 32,
|
image: AssetImage("assets/core/knott-white.png"),
|
||||||
colorBlendMode: BlendMode.dstIn,
|
filterQuality: FilterQuality.medium,
|
||||||
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
|
isAntiAlias: true,
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
colorBlendMode: BlendMode.dstIn,
|
||||||
|
color: Provider
|
||||||
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.backgroundHilightElementColor(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor())))
|
||||||
|
]),
|
||||||
|
actions: getActions(),
|
||||||
),
|
),
|
||||||
SizedBox(
|
floatingActionButton: FloatingActionButton(
|
||||||
width: 10,
|
onPressed: _pushAddEditProfile,
|
||||||
|
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor())))
|
body: _buildProfileManager(),
|
||||||
]),
|
)),
|
||||||
actions: [
|
|
||||||
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
|
|
||||||
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.lock_open),
|
|
||||||
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
|
|
||||||
onPressed: _modalUnlockProfiles,
|
|
||||||
),
|
|
||||||
IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: _pushAddEditProfile,
|
|
||||||
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
|
||||||
child: Icon(
|
|
||||||
Icons.add,
|
|
||||||
semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: _buildProfileManager(),
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setLoggingLevelDebug() {
|
List<Widget> getActions() {
|
||||||
final setLoggingLevel = {
|
List<Widget> actions = new List<Widget>.empty(growable: true);
|
||||||
"EventType": "SetLoggingLevel",
|
|
||||||
"Data": {"Debug": "true"},
|
// Tor Status
|
||||||
};
|
actions.add(IconButton(
|
||||||
final setLoggingLevelJson = jsonEncode(setLoggingLevel);
|
icon: TorIcon(),
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendAppEvent(setLoggingLevelJson);
|
onPressed: _pushTorStatus,
|
||||||
|
tooltip: Provider.of<TorStatus>(context).progress == 100
|
||||||
|
? AppLocalizations.of(context)!.networkStatusOnline
|
||||||
|
: (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Only show debug button on development builds
|
||||||
|
|
||||||
|
// Unlock Profiles
|
||||||
|
actions.add(IconButton(
|
||||||
|
icon: Icon(CwtchIcons.lock_open_24px),
|
||||||
|
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
|
||||||
|
onPressed: _modalUnlockProfiles,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Global Settings
|
||||||
|
actions.add(IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings));
|
||||||
|
|
||||||
|
actions.add(IconButton(icon: Icon(Icons.close), tooltip: AppLocalizations.of(context)!.shutdownCwtchTooltip, onPressed: _showShutdown));
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
_showShutdown() {
|
||||||
|
// set up the buttons
|
||||||
|
Widget cancelButton = TextButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // dismiss dialog
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Widget continueButton = TextButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.shutdownCwtchAction),
|
||||||
|
onPressed: () {
|
||||||
|
// Directly call the shutdown command, Android will do this for us...
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).shutdown(MethodCall(""));
|
||||||
|
closeApp = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// set up the AlertDialog
|
||||||
|
AlertDialog alert = AlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(context)!.shutdownCwtchDialogTitle),
|
||||||
|
content: Text(AppLocalizations.of(context)!.shutdownCwtchDialog),
|
||||||
|
actions: [
|
||||||
|
cancelButton,
|
||||||
|
continueButton,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// show the dialog
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return alert;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushGlobalSettings() {
|
void _pushGlobalSettings() {
|
||||||
|
|
|
@ -1,11 +1,37 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../model.dart';
|
||||||
|
import '../settings.dart';
|
||||||
|
|
||||||
class SplashView extends StatelessWidget {
|
class SplashView extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Scaffold(
|
return Consumer<AppState>(
|
||||||
body: const Center(child: const Text("Loading Cwtch...")),
|
builder: (context, appState, child) => Scaffold(
|
||||||
);
|
body: Center(
|
||||||
|
child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||||
|
Image(
|
||||||
|
image: AssetImage("assets/core/knott-white.png"),
|
||||||
|
filterQuality: FilterQuality.medium,
|
||||||
|
isAntiAlias: true,
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
),
|
||||||
|
Image(
|
||||||
|
image: AssetImage("assets/cwtch_title.png"),
|
||||||
|
filterQuality: FilterQuality.medium,
|
||||||
|
isAntiAlias: true,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Text(appState.appError == "" ? "Loading Cwtch..." : appState.appError,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0, color: appState.appError == "" ? Provider.of<Settings>(context).theme.mainTextColor() : Provider.of<Settings>(context).theme.textfieldErrorColor())),
|
||||||
|
),
|
||||||
|
Image(image: AssetImage("assets/Open_Privacy_Logo_lightoutline.png")),
|
||||||
|
])),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ class _TorStatusView extends State<TorStatusView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("Tor Network Status"),
|
title: Text(AppLocalizations.of(context)!.torNetworkStatus),
|
||||||
),
|
),
|
||||||
body: _buildSettingsList(),
|
body: _buildSettingsList(),
|
||||||
);
|
);
|
||||||
|
@ -43,15 +43,19 @@ class _TorStatusView extends State<TorStatusView> {
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: TorIcon(),
|
leading: TorIcon(),
|
||||||
title: Text("Tor Status"),
|
title: Text(AppLocalizations.of(context)!.torStatus),
|
||||||
subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context)!.networkStatusOnline : torStatus.status),
|
subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context)!.networkStatusOnline : torStatus.status),
|
||||||
trailing: ElevatedButton(
|
trailing: ElevatedButton(
|
||||||
child: Text("Reset"),
|
child: Text(AppLocalizations.of(context)!.resetTor),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ResetTor();
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ResetTor();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(AppLocalizations.of(context)!.torVersion),
|
||||||
|
subtitle: Text(torStatus.version),
|
||||||
|
),
|
||||||
]))));
|
]))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cwtch/views/profilemgrview.dart';
|
import 'package:cwtch/views/profilemgrview.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
|
import '../model.dart';
|
||||||
|
import '../settings.dart';
|
||||||
import 'contactsview.dart';
|
import 'contactsview.dart';
|
||||||
import 'messageview.dart';
|
import 'messageview.dart';
|
||||||
|
|
||||||
|
// currently unused but maybe one day?
|
||||||
class TripleColumnView extends StatefulWidget {
|
class TripleColumnView extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_TripleColumnViewState createState() => _TripleColumnViewState();
|
_TripleColumnViewState createState() => _TripleColumnViewState();
|
||||||
|
@ -14,20 +17,23 @@ class TripleColumnView extends StatefulWidget {
|
||||||
class _TripleColumnViewState extends State<TripleColumnView> {
|
class _TripleColumnViewState extends State<TripleColumnView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var flwtch = Provider.of<FlwtchState>(context);
|
var appState = Provider.of<AppState>(context);
|
||||||
|
var settings = Provider.of<Settings>(context);
|
||||||
|
var columns = settings.uiColumns(appState.isLandscape(context));
|
||||||
|
|
||||||
return Flex(direction: Axis.horizontal, children: <Widget>[
|
return Flex(direction: Axis.horizontal, children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[0],
|
flex: columns[0],
|
||||||
child: ProfileMgrView(),
|
child: ProfileMgrView(),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[1],
|
flex: columns[1],
|
||||||
child: flwtch.selectedProfile == null ? Center(child: Text("pick a profile")) : ContactsView(), //dev
|
child: appState.selectedProfile == null ? Center(child: Text(AppLocalizations.of(context)!.createProfileToBegin)) : ContactsView(), //dev
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[2],
|
flex: columns[2],
|
||||||
child: flwtch.selectedConversation == ""
|
child: appState.selectedConversation == null
|
||||||
? Center(child: Text("pick a contact"))
|
? Center(child: Text(AppLocalizations.of(context)!.addContactFirst))
|
||||||
: //dev
|
: //dev
|
||||||
Container(child: MessageView()),
|
Container(child: MessageView()),
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,10 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
|
|
||||||
|
bool noFilter(ContactInfoState peer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Dropdown menu populated from Provider.of<ProfileInfoState>'s contact list
|
// Dropdown menu populated from Provider.of<ProfileInfoState>'s contact list
|
||||||
// Includes both peers and groups; begins empty/nothing selected
|
// Includes both peers and groups; begins empty/nothing selected
|
||||||
// Displays nicknames to UI but uses handles as values
|
// Displays nicknames to UI but uses handles as values
|
||||||
|
@ -10,8 +14,10 @@ import '../model.dart';
|
||||||
class DropdownContacts extends StatefulWidget {
|
class DropdownContacts extends StatefulWidget {
|
||||||
DropdownContacts({
|
DropdownContacts({
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
|
this.filter = noFilter,
|
||||||
});
|
});
|
||||||
final Function(dynamic) onChanged;
|
final Function(dynamic) onChanged;
|
||||||
|
final bool Function(ContactInfoState) filter;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_DropdownContactsState createState() => _DropdownContactsState();
|
_DropdownContactsState createState() => _DropdownContactsState();
|
||||||
|
@ -24,7 +30,7 @@ class _DropdownContactsState extends State<DropdownContacts> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DropdownButton(
|
return DropdownButton(
|
||||||
value: this.selected,
|
value: this.selected,
|
||||||
items: Provider.of<ProfileInfoState>(context, listen: false).contactList.contacts.map<DropdownMenuItem<String>>((ContactInfoState contact) {
|
items: Provider.of<ProfileInfoState>(context, listen: false).contactList.contacts.where(widget.filter).map<DropdownMenuItem<String>>((ContactInfoState contact) {
|
||||||
return DropdownMenuItem<String>(value: contact.onion, child: Text(contact.nickname));
|
return DropdownMenuItem<String>(value: contact.onion, child: Text(contact.nickname));
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (String? newVal) {
|
onChanged: (String? newVal) {
|
||||||
|
|
|
@ -17,6 +17,18 @@ class CwtchButtonTextField extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
|
class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
|
||||||
|
late final FocusNode _focusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_focusNode = FocusNode();
|
||||||
|
_focusNode.addListener(() {
|
||||||
|
// Select all...
|
||||||
|
if (_focusNode.hasFocus) widget.controller.selection = TextSelection(baseOffset: 0, extentOffset: widget.controller.text.length);
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<Settings>(builder: (context, theme, child) {
|
return Consumer<Settings>(builder: (context, theme, child) {
|
||||||
|
@ -24,10 +36,12 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
readOnly: widget.readonly,
|
readOnly: widget.readonly,
|
||||||
showCursor: !widget.readonly,
|
showCursor: !widget.readonly,
|
||||||
|
focusNode: _focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
onPressed: widget.onPressed,
|
onPressed: widget.onPressed,
|
||||||
icon: widget.icon,
|
icon: widget.icon,
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 4.0, 2.0, 2.0),
|
||||||
tooltip: widget.tooltip,
|
tooltip: widget.tooltip,
|
||||||
enableFeedback: true,
|
enableFeedback: true,
|
||||||
color: theme.current().mainTextColor(),
|
color: theme.current().mainTextColor(),
|
||||||
|
|
|
@ -20,6 +20,8 @@ class _ContactRowState extends State<ContactRow> {
|
||||||
var contact = Provider.of<ContactInfoState>(context);
|
var contact = Provider.of<ContactInfoState>(context);
|
||||||
return Card(
|
return Card(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
|
color: Provider.of<AppState>(context).selectedConversation == contact.onion ? Provider.of<Settings>(context).theme.backgroundHilightElementColor() : null,
|
||||||
|
borderOnForeground: false,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -31,7 +33,7 @@ class _ContactRowState extends State<ContactRow> {
|
||||||
diameter: 64.0,
|
diameter: 64.0,
|
||||||
imagePath: contact.imagePath,
|
imagePath: contact.imagePath,
|
||||||
maskOut: !contact.isOnline(),
|
maskOut: !contact.isOnline(),
|
||||||
border: contact.isOnline() ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
border: contact.isOnline() ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -41,16 +43,19 @@ class _ContactRowState extends State<ContactRow> {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
contact.nickname, //(contact.isInvitation ? "invite " : "non-invite ") + (contact.isBlocked ? "blokt" : "nonblokt"),//
|
contact.nickname, //(contact.isInvitation ? "invite " : "non-invite ") + (contact.isBlocked ? "blokt" : "nonblokt"),//
|
||||||
style: Provider.of<FlwtchState>(context).biggerFont,
|
|
||||||
|
style: TextStyle(fontSize: Provider.of<Settings>(context).theme.contactOnionTextSize(),
|
||||||
|
color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor() : Provider.of<Settings>(context).theme.mainTextColor()), //Provider.of<FlwtchState>(context).biggerFont,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
overflow: TextOverflow.visible,
|
overflow: TextOverflow.visible,
|
||||||
),
|
),
|
||||||
Text(contact.onion),
|
Text(contact.onion,
|
||||||
|
style: TextStyle(color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor() : Provider.of<Settings>(context).theme.mainTextColor())),
|
||||||
],
|
],
|
||||||
))),
|
))),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(5.0),
|
padding: const EdgeInsets.all(5.0),
|
||||||
child: contact.isInvitation != null && contact.isInvitation
|
child: contact.isInvitation == true
|
||||||
? Wrap(direction: Axis.vertical, children: <Widget>[
|
? Wrap(direction: Axis.vertical, children: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
|
@ -77,17 +82,19 @@ class _ContactRowState extends State<ContactRow> {
|
||||||
]),
|
]),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
var flwtch = Provider.of<FlwtchState>(context, listen: false);
|
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||||
flwtch.setState(() => flwtch.selectedConversation = contact.onion);
|
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0;
|
||||||
// case 2/3 handled by Double/TripleColumnView respectively
|
// triggers update in Double/TripleColumnView
|
||||||
if (flwtch.columns.length == 1) _pushMessageView(contact.onion);
|
Provider.of<AppState>(context, listen: false).selectedConversation = contact.onion;
|
||||||
|
// if in singlepane mode, push to the stack
|
||||||
|
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||||
|
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushMessageView(String handle) {
|
void _pushMessageView(String handle) {
|
||||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0;
|
|
||||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
|
@ -132,6 +139,6 @@ class _ContactRowState extends State<ContactRow> {
|
||||||
return DateFormat.yMd().format(date.toLocal());
|
return DateFormat.yMd().format(date.toLocal());
|
||||||
}
|
}
|
||||||
// Otherwise just state the time.
|
// Otherwise just state the time.
|
||||||
return DateFormat.Hms().format(date.toLocal());
|
return DateFormat.Hm().format(date.toLocal());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import '../settings.dart';
|
||||||
|
|
||||||
// Provides a styled Label
|
// Provides a styled Label
|
||||||
// Callers must provide a label text
|
// Callers must provide a label text
|
||||||
// TODO: Integrate this with a settings "zoom" / accessibility setting
|
|
||||||
class CwtchLabel extends StatefulWidget {
|
class CwtchLabel extends StatefulWidget {
|
||||||
CwtchLabel({required this.label});
|
CwtchLabel({required this.label});
|
||||||
final String label;
|
final String label;
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
import 'messagebubbledecorations.dart';
|
||||||
|
|
||||||
// Like MessageBubble but for displaying chat overlay 100/101 invitations
|
// Like MessageBubble but for displaying chat overlay 100/101 invitations
|
||||||
// Offers the user an accept/reject button if they don't have a matching contact already
|
// Offers the user an accept/reject button if they don't have a matching contact already
|
||||||
// todo: Reject buttons currently aren't tracked and will reset when the message is reloaded
|
|
||||||
class InvitationBubble extends StatefulWidget {
|
class InvitationBubble extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
InvitationBubbleState createState() => InvitationBubbleState();
|
InvitationBubbleState createState() => InvitationBubbleState();
|
||||||
|
@ -18,15 +21,21 @@ class InvitationBubble extends StatefulWidget {
|
||||||
|
|
||||||
class InvitationBubbleState extends State<InvitationBubble> {
|
class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
bool rejected = false;
|
bool rejected = false;
|
||||||
FocusNode _focus = FocusNode();
|
bool isAccepted = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (Provider.of<MessageState>(context).malformed) {
|
||||||
|
return MalformedBubble();
|
||||||
|
}
|
||||||
|
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
||||||
var isGroup = Provider.of<MessageState>(context).overlay == 101;
|
var isGroup = Provider.of<MessageState>(context).overlay == 101;
|
||||||
var isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null;
|
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null;
|
||||||
var prettyDate = "";
|
var prettyDate = "";
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
|
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
|
||||||
|
rejected = Provider.of<MessageState>(context).flags & 0x01 == 0x01;
|
||||||
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
||||||
|
|
||||||
if (Provider.of<MessageState>(context).timestamp != null) {
|
if (Provider.of<MessageState>(context).timestamp != null) {
|
||||||
|
@ -50,56 +59,36 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
child: SelectableText(senderDisplayStr + '\u202F',
|
child: SelectableText(senderDisplayStr + '\u202F',
|
||||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor())));
|
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor())));
|
||||||
|
|
||||||
// todo: translations
|
// If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from
|
||||||
var messageStr = "";
|
// some kind of malfeasance.
|
||||||
if (fromMe) {
|
var selfInvite = Provider.of<MessageState>(context).inviteNick == Provider.of<ProfileInfoState>(context).onion;
|
||||||
//todo: get group name?
|
if (selfInvite) {
|
||||||
messageStr = "You sent an invitation for " + (isGroup ? "a group" : Provider.of<MessageState>(context).message ?? "");
|
return MalformedBubble();
|
||||||
} else {
|
|
||||||
messageStr = (isGroup ? "You have been invited to join " + (Provider.of<MessageState>(context).inviteNick ?? "") : "This is a contact suggestion for:") +
|
|
||||||
"\n" +
|
|
||||||
(Provider.of<MessageState>(context).inviteTarget ?? "");
|
|
||||||
}
|
}
|
||||||
var wdgMessage = Center(
|
|
||||||
widthFactor: 1,
|
var wdgMessage = isGroup && !showGroupInvite ?
|
||||||
child: SelectableText(
|
Text(AppLocalizations.of(context)!.groupInviteSettingsWarning) :
|
||||||
messageStr + '\u202F',
|
fromMe
|
||||||
key: Key(myKey),
|
? senderInviteChrome(AppLocalizations.of(context)!.sendAnInvitation,
|
||||||
focusNode: _focus,
|
isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget)!.nickname : Provider.of<MessageState>(context).message, myKey)
|
||||||
style: TextStyle(
|
: (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, Provider.of<MessageState>(context).inviteNick,
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
Provider.of<MessageState>(context).inviteTarget, myKey));
|
||||||
),
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
textWidthBasis: TextWidthBasis.longestLine,
|
|
||||||
));
|
|
||||||
|
|
||||||
Widget wdgDecorations;
|
Widget wdgDecorations;
|
||||||
if (fromMe) {
|
if (isGroup && !showGroupInvite) {
|
||||||
wdgDecorations = Center(
|
wdgDecorations = Text('\u202F');
|
||||||
widthFactor: 1.0,
|
} else if (fromMe) {
|
||||||
child: Row(
|
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(prettyDate,
|
|
||||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
|
|
||||||
textAlign: fromMe ? TextAlign.right : TextAlign.left),
|
|
||||||
!fromMe
|
|
||||||
? SizedBox(width: 1, height: 1)
|
|
||||||
: Provider.of<MessageState>(context).ackd
|
|
||||||
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
|
||||||
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
|
||||||
],
|
|
||||||
));
|
|
||||||
} else if (isAccepted) {
|
} else if (isAccepted) {
|
||||||
wdgDecorations = Text("Accepted!");
|
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F');
|
||||||
} else if (this.rejected) {
|
} else if (this.rejected) {
|
||||||
wdgDecorations = Text("Rejected.");
|
wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F');
|
||||||
} else {
|
} else {
|
||||||
wdgDecorations = Center(
|
wdgDecorations = Center(
|
||||||
widthFactor: 1,
|
widthFactor: 1,
|
||||||
child: Row(children: [
|
child: Wrap(children: [
|
||||||
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text("Reject"), onPressed: _btnReject)),
|
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F'), onPressed: _btnReject)),
|
||||||
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text("Accept"), onPressed: _btnAccept)),
|
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F'), onPressed: _btnAccept)),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,35 +112,83 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
widthFactor: 1.0,
|
widthFactor: 1.0,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(9.0),
|
padding: EdgeInsets.all(9.0),
|
||||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||||
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(4), child: Icon(Icons.group_add, size: 32))),
|
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(isGroup && !showGroupInvite ? CwtchIcons.enable_experiments : CwtchIcons.send_invite, size: 32))),
|
||||||
Center(
|
Center(
|
||||||
widthFactor: 1.0,
|
widthFactor: 1.0,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])),
|
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]),
|
||||||
|
)
|
||||||
])))));
|
])))));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _btnReject() {
|
void _btnReject() {
|
||||||
//todo: how should we track inline invite rejections?
|
setState(() {
|
||||||
setState(() => this.rejected = true);
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
|
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||||
|
var idx = Provider.of<MessageState>(context, listen: false).messageIndex;
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageState>(context, listen: false).flags | 0x01);
|
||||||
|
Provider.of<MessageState>(context).flags |= 0x01;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _btnAccept() {
|
void _btnAccept() {
|
||||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
setState(() {
|
||||||
if (Provider.of<MessageState>(context, listen: false).overlay == 100) {
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
final setPeerAttribute = {
|
|
||||||
"EventType": "AddContact",
|
|
||||||
"Data": {"ImportString": Provider.of<MessageState>(context, listen: false).message},
|
|
||||||
};
|
|
||||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
|
||||||
} else {
|
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, Provider.of<MessageState>(context, listen: false).message);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, Provider.of<MessageState>(context, listen: false).message);
|
||||||
}
|
isAccepted = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct an invite chrome for the sender
|
||||||
|
Widget senderInviteChrome(String chrome, String targetName, String myKey) {
|
||||||
|
return Wrap(children: [
|
||||||
|
SelectableText(
|
||||||
|
chrome + '\u202F',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
maxLines: 2,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
),
|
||||||
|
SelectableText(
|
||||||
|
targetName + '\u202F',
|
||||||
|
key: Key(myKey),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
maxLines: 2,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct an invite chrome
|
||||||
|
Widget inviteChrome(String chrome, String targetName, String targetId, String myKey) {
|
||||||
|
return Wrap(children: [
|
||||||
|
SelectableText(
|
||||||
|
chrome + '\u202F',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
SelectableText(
|
||||||
|
targetName + '\u202F',
|
||||||
|
key: Key(myKey),
|
||||||
|
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
maxLines: 2,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'dart:ffi';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../settings.dart';
|
|
||||||
|
|
||||||
final Color malformedColor = Color(0xFFE85DA1);
|
final Color malformedColor = Color(0xFFE85DA1);
|
||||||
|
|
||||||
|
@ -40,22 +36,17 @@ class MalformedBubbleState extends State<MalformedBubble> {
|
||||||
widthFactor: 1,
|
widthFactor: 1,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(4),
|
padding: EdgeInsets.all(4),
|
||||||
child: Image(
|
child: Icon(
|
||||||
image: AssetImage("assets/core/broken_heart_24.png"),
|
CwtchIcons.favorite_black_24dp_broken,
|
||||||
filterQuality: FilterQuality.medium,
|
size: 24,
|
||||||
// We need some theme specific blending here...we might want to consider making this a theme level attribute
|
))),
|
||||||
colorBlendMode: BlendMode.srcIn,
|
|
||||||
color: Provider.of<Settings>(context).theme.mainTextColor(),
|
|
||||||
isAntiAlias: false,
|
|
||||||
width: 32,
|
|
||||||
height: 32))),
|
|
||||||
Center(
|
Center(
|
||||||
widthFactor: 1.0,
|
widthFactor: 1.0,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [Text("Malformed Message")],
|
children: [Text(AppLocalizations.of(context)!.malformedMessage)],
|
||||||
))
|
))
|
||||||
])))));
|
])))));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
import 'messagebubbledecorations.dart';
|
||||||
|
|
||||||
class MessageBubble extends StatefulWidget {
|
class MessageBubble extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
|
@ -50,26 +52,9 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
textWidthBasis: TextWidthBasis.longestLine,
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
);
|
);
|
||||||
|
|
||||||
var wdgDecorations = Center(
|
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
widthFactor: 1.0,
|
|
||||||
child: Row(
|
var error = Provider.of<MessageState>(context).error;
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(prettyDate,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 9.0,
|
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
|
||||||
),
|
|
||||||
textAlign: fromMe ? TextAlign.right : TextAlign.left),
|
|
||||||
!fromMe
|
|
||||||
? SizedBox(width: 1, height: 1)
|
|
||||||
: Provider.of<MessageState>(context).ackd == true
|
|
||||||
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
|
||||||
: (Provider.of<MessageState>(context).error == true
|
|
||||||
? Icon(Icons.error_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
|
||||||
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12))
|
|
||||||
],
|
|
||||||
));
|
|
||||||
|
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
||||||
|
@ -77,9 +62,14 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
child: Container(
|
child: Container(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
|
color: error
|
||||||
|
? malformedColor
|
||||||
|
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(), width: 1),
|
color: error
|
||||||
|
? malformedColor
|
||||||
|
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()),
|
||||||
|
width: 1),
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(borderRadiousEh),
|
topLeft: Radius.circular(borderRadiousEh),
|
||||||
topRight: Radius.circular(borderRadiousEh),
|
topRight: Radius.circular(borderRadiousEh),
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../settings.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
// Provides message decorations (acks/errors/dates etc.) for generic message bubble overlays (chats, invites etc.)
|
||||||
|
class MessageBubbleDecoration extends StatefulWidget {
|
||||||
|
MessageBubbleDecoration({required this.ackd, required this.errored, required this.prettyDate, required this.fromMe});
|
||||||
|
final String prettyDate;
|
||||||
|
final bool fromMe;
|
||||||
|
final bool ackd;
|
||||||
|
final bool errored;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MessageBubbleDecoration createState() => _MessageBubbleDecoration();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageBubbleDecoration extends State<MessageBubbleDecoration> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
widthFactor: 1.0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(widget.prettyDate,
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 9.0, color: widget.fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
|
||||||
|
textAlign: widget.fromMe ? TextAlign.right : TextAlign.left),
|
||||||
|
!widget.fromMe
|
||||||
|
? SizedBox(width: 1, height: 1)
|
||||||
|
: Padding(
|
||||||
|
padding: EdgeInsets.all(1.0),
|
||||||
|
child: widget.ackd == true
|
||||||
|
? Tooltip(
|
||||||
|
message: AppLocalizations.of(context)!.acknowledgedLabel,
|
||||||
|
child: Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16))
|
||||||
|
: (widget.errored == true
|
||||||
|
? Tooltip(
|
||||||
|
message: AppLocalizations.of(context)!.couldNotSendMsgError,
|
||||||
|
child: Icon(Icons.error_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16))
|
||||||
|
: Tooltip(
|
||||||
|
message: AppLocalizations.of(context)!.pendingLabel,
|
||||||
|
child: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16))))
|
||||||
|
],
|
||||||
|
));
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
import 'messagerow.dart';
|
import 'messagerow.dart';
|
||||||
|
@ -15,24 +16,50 @@ class _MessageListState extends State<MessageList> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext outerContext) {
|
Widget build(BuildContext outerContext) {
|
||||||
|
bool showEphemeralWarning = (Provider.of<ContactInfoState>(context).isGroup == false && Provider.of<ContactInfoState>(context).savePeerHistory != "SaveHistory");
|
||||||
|
bool showOfflineWarning = Provider.of<ContactInfoState>(context).isOnline() == false;
|
||||||
|
bool showMessageWarning = showEphemeralWarning || showOfflineWarning;
|
||||||
|
bool showSyncing = Provider.of<ContactInfoState>(context).isGroup == true && Provider.of<ContactInfoState>(context).status != "Synced";
|
||||||
|
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
child: Container(
|
child: Container(
|
||||||
child: Scrollbar(
|
child: Column(children: [
|
||||||
isAlwaysShown: true,
|
Visibility(
|
||||||
controller: ctrlr1,
|
visible: showMessageWarning,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(5.0),
|
||||||
|
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||||
|
child: showSyncing ?
|
||||||
|
Text(AppLocalizations.of(context)!.serverNotSynced,
|
||||||
|
textAlign: TextAlign.center)
|
||||||
|
: showOfflineWarning
|
||||||
|
? Text(Provider.of<ContactInfoState>(context).isGroup ? AppLocalizations.of(context)!.serverConnectivityDisconnected : AppLocalizations.of(context)!.peerOfflineMessage,
|
||||||
|
textAlign: TextAlign.center)
|
||||||
|
// Only show the ephemeral status for peer conversations, not for groups...
|
||||||
|
: (showEphemeralWarning
|
||||||
|
? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center)
|
||||||
|
:
|
||||||
|
// We are not allowed to put null here, so put an empty text widge
|
||||||
|
Text("")),
|
||||||
|
)),
|
||||||
|
Expanded(
|
||||||
|
child: Scrollbar(
|
||||||
|
controller: ctrlr1,
|
||||||
|
child: Container(
|
||||||
// Only show broken heart is the contact is offline...
|
// Only show broken heart is the contact is offline...
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: Provider.of<ContactInfoState>(outerContext).isOnline()
|
image: Provider.of<ContactInfoState>(outerContext).isOnline()
|
||||||
? null
|
? null
|
||||||
: DecorationImage(
|
: DecorationImage(
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: Alignment.center,
|
||||||
image: AssetImage("assets/core/negative_heart_512px.png"),
|
image: AssetImage("assets/core/negative_heart_512px.png"),
|
||||||
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.mainTextColor(), BlendMode.srcIn))),
|
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementTextColor(), BlendMode.srcIn))),
|
||||||
|
// Don't load messages for syncing server...
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
controller: ctrlr1,
|
controller: ctrlr1,
|
||||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||||
reverse: true,
|
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
||||||
itemBuilder: (itemBuilderContext, index) {
|
itemBuilder: (itemBuilderContext, index) {
|
||||||
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
||||||
return ChangeNotifierProvider(
|
return ChangeNotifierProvider(
|
||||||
|
@ -51,7 +78,7 @@ class _MessageListState extends State<MessageList> {
|
||||||
return RepaintBoundary(child: MessageRow(key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx)));
|
return RepaintBoundary(child: MessageRow(key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx)));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
))))
|
||||||
))));
|
])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,8 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
fromMe = false;
|
fromMe = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget wdgBubble = Flexible(
|
Widget wdgBubble =
|
||||||
flex: 3,
|
Flexible(flex: 3, fit: FlexFit.loose, child: Provider.of<MessageState>(context).loaded == true ? widgetForOverlay(Provider.of<MessageState>(context).overlay) : MessageLoadingBubble());
|
||||||
fit: FlexFit.loose,
|
|
||||||
child: malformed ? MalformedBubble() : (Provider.of<MessageState>(context).loaded == true ? widgetForOverlay(Provider.of<MessageState>(context).overlay) : MessageLoadingBubble()));
|
|
||||||
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Provider.of<Settings>(context).theme.dropShadowColor());
|
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Provider.of<Settings>(context).theme.dropShadowColor());
|
||||||
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
||||||
var widgetRow = <Widget>[];
|
var widgetRow = <Widget>[];
|
||||||
|
@ -96,7 +94,10 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||||
|
|
||||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact));
|
final snackBar = SnackBar(
|
||||||
|
content: Text(AppLocalizations.of(context)!.successfullAddedContact),
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
// Provides a styled Password Input Field for use in Form Widgets.
|
// Provides a styled Password Input Field for use in Form Widgets.
|
||||||
// Callers must provide a text controller, label helper text and a validator.
|
// Callers must provide a text controller, label helper text and a validator.
|
||||||
class CwtchPasswordField extends StatefulWidget {
|
class CwtchPasswordField extends StatefulWidget {
|
||||||
CwtchPasswordField({required this.controller, required this.validator, this.action, this.autofocus = true});
|
CwtchPasswordField({required this.controller, required this.validator, this.action, this.autofocus = false});
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final FormFieldValidator validator;
|
final FormFieldValidator validator;
|
||||||
final Function(String)? action;
|
final Function(String)? action;
|
||||||
|
@ -21,9 +23,9 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// todo: translations
|
// todo: translations
|
||||||
var label = "View Password";
|
var label = AppLocalizations.of(context)!.tooltipShowPassword;
|
||||||
if (!obscureText) {
|
if (!obscureText) {
|
||||||
label = "Hide Password";
|
label = AppLocalizations.of(context)!.tooltipHidePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Consumer<Settings>(builder: (context, theme, child) {
|
return Consumer<Settings>(builder: (context, theme, child) {
|
||||||
|
@ -32,6 +34,7 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
validator: widget.validator,
|
validator: widget.validator,
|
||||||
obscureText: obscureText,
|
obscureText: obscureText,
|
||||||
|
autovalidateMode: AutovalidateMode.always,
|
||||||
onFieldSubmitted: widget.action,
|
onFieldSubmitted: widget.action,
|
||||||
textInputAction: TextInputAction.unspecified,
|
textInputAction: TextInputAction.unspecified,
|
||||||
enableSuggestions: false,
|
enableSuggestions: false,
|
||||||
|
@ -43,7 +46,7 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
||||||
obscureText = !obscureText;
|
obscureText = !obscureText;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: Icon((obscureText ? Icons.remove_red_eye : Icons.remove_red_eye_outlined), semanticLabel: label),
|
icon: Icon((obscureText ? CwtchIcons.eye_closed : CwtchIcons.eye_open), semanticLabel: label),
|
||||||
tooltip: label,
|
tooltip: label,
|
||||||
color: theme.current().mainTextColor(),
|
color: theme.current().mainTextColor(),
|
||||||
highlightColor: theme.current().defaultButtonColor(),
|
highlightColor: theme.current().defaultButtonColor(),
|
||||||
|
|
|
@ -38,7 +38,7 @@ class _ProfileImageState extends State<ProfileImage> {
|
||||||
filterQuality: FilterQuality.medium,
|
filterQuality: FilterQuality.medium,
|
||||||
// We need some theme specific blending here...we might want to consider making this a theme level attribute
|
// We need some theme specific blending here...we might want to consider making this a theme level attribute
|
||||||
colorBlendMode: !widget.maskOut
|
colorBlendMode: !widget.maskOut
|
||||||
? Provider.of<Settings>(context).theme == Opaque.dark
|
? Provider.of<Settings>(context).theme.identifier() == "dark"
|
||||||
? BlendMode.softLight
|
? BlendMode.softLight
|
||||||
: BlendMode.darken
|
: BlendMode.darken
|
||||||
: BlendMode.srcOut,
|
: BlendMode.srcOut,
|
||||||
|
|
|
@ -30,85 +30,96 @@ class _ProfileRowState extends State<ProfileRow> {
|
||||||
padding: const EdgeInsets.all(2.0), //border size
|
padding: const EdgeInsets.all(2.0), //border size
|
||||||
child: ProfileImage(
|
child: ProfileImage(
|
||||||
badgeCount: 0,
|
badgeCount: 0,
|
||||||
badgeColor: Provider.of<Settings>(context).theme.portraitProfileBadgeColor(),
|
badgeColor: Provider
|
||||||
badgeTextColor: Provider.of<Settings>(context).theme.portraitProfileBadgeTextColor(),
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.portraitProfileBadgeColor(),
|
||||||
|
badgeTextColor: Provider
|
||||||
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.portraitProfileBadgeTextColor(),
|
||||||
diameter: 64.0,
|
diameter: 64.0,
|
||||||
imagePath: profile.imagePath,
|
imagePath: profile.imagePath,
|
||||||
border: profile.isOnline ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor())),
|
border: profile.isOnline ? Provider
|
||||||
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.portraitOnlineBorderColor() : Provider
|
||||||
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.portraitOfflineBorderColor())),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
profile.nickname,
|
profile.nickname,
|
||||||
semanticsLabel: profile.nickname,
|
semanticsLabel: profile.nickname,
|
||||||
style: Provider.of<FlwtchState>(context).biggerFont,
|
style: Provider
|
||||||
softWrap: true,
|
.of<FlwtchState>(context)
|
||||||
overflow: TextOverflow.ellipsis,
|
.biggerFont,
|
||||||
),
|
softWrap: true,
|
||||||
ExcludeSemantics(
|
overflow: TextOverflow.ellipsis,
|
||||||
child: Text(
|
),
|
||||||
profile.onion,
|
ExcludeSemantics(
|
||||||
softWrap: true,
|
child: Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
profile.onion,
|
||||||
))
|
softWrap: true,
|
||||||
],
|
overflow: TextOverflow.ellipsis,
|
||||||
)),
|
))
|
||||||
|
],
|
||||||
|
)),
|
||||||
IconButton(
|
IconButton(
|
||||||
enableFeedback: true,
|
enableFeedback: true,
|
||||||
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname,
|
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname,
|
||||||
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
|
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath);
|
_pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath, encrypted: profile.isEncrypted);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
var flwtch = Provider.of<FlwtchState>(context, listen: false);
|
var appState = Provider.of<AppState>(context, listen: false);
|
||||||
flwtch.cwtch.SelectProfile(profile.onion);
|
appState.selectedProfile = profile.onion;
|
||||||
flwtch.setState(() {
|
appState.selectedConversation = null;
|
||||||
flwtch.selectedProfile = profile;
|
|
||||||
flwtch.selectedConversation = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (flwtch.columns.length) {
|
_pushContactList(profile, appState.isLandscape(context));//orientation == Orientation.landscape);
|
||||||
case 1:
|
|
||||||
_pushContactList(profile, false);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
_pushContactList(profile, true);
|
|
||||||
break;
|
|
||||||
} // case 3: handled by TripleColumnView
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushContactList(ProfileInfoState profile, bool includeDoublePane) {
|
void _pushContactList(ProfileInfoState profile, bool isLandscape) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
settings: RouteSettings(name: "conversations"),
|
settings: RouteSettings(name: "conversations"),
|
||||||
builder: (BuildContext buildcontext) {
|
builder: (BuildContext buildcontext) {
|
||||||
return MultiProvider(
|
return OrientationBuilder(
|
||||||
providers: [
|
builder: (orientationBuilderContext, orientation) {
|
||||||
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
|
return MultiProvider(
|
||||||
ChangeNotifierProvider<ContactListState>.value(value: profile.contactList),
|
providers: [
|
||||||
],
|
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
|
||||||
builder: (context, widget) => includeDoublePane ? DoubleColumnView() : ContactsView(),
|
ChangeNotifierProvider<ContactListState>.value(value: profile.contactList),
|
||||||
);
|
],
|
||||||
|
builder: (innercontext, widget) {
|
||||||
|
var appState = Provider.of<AppState>(context);
|
||||||
|
var settings = Provider.of<Settings>(context);
|
||||||
|
return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : ContactsView();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushAddEditProfile({onion: "", displayName: "", profileImage: ""}) {
|
void _pushAddEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
|
||||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider<ProfileInfoState>(
|
ChangeNotifierProvider<ProfileInfoState>(
|
||||||
create: (_) => ProfileInfoState(onion: onion, nickname: displayName, imagePath: profileImage),
|
create: (_) => ProfileInfoState(onion: onion, nickname: displayName, imagePath: profileImage, encrypted: encrypted),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
builder: (context, widget) => AddEditProfileView(),
|
builder: (context, widget) => AddEditProfileView(),
|
||||||
|
|
|
@ -7,17 +7,30 @@ doNothing(String x) {}
|
||||||
// Provides a styled Text Field for use in Form Widgets.
|
// Provides a styled Text Field for use in Form Widgets.
|
||||||
// Callers must provide a text controller, label helper text and a validator.
|
// Callers must provide a text controller, label helper text and a validator.
|
||||||
class CwtchTextField extends StatefulWidget {
|
class CwtchTextField extends StatefulWidget {
|
||||||
CwtchTextField({required this.controller, required this.labelText, this.validator = null, this.onChanged = doNothing});
|
CwtchTextField({required this.controller, required this.labelText, this.validator, this.autofocus = false, this.onChanged = doNothing});
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final String labelText;
|
final String labelText;
|
||||||
final FormFieldValidator? validator;
|
final FormFieldValidator? validator;
|
||||||
final Function(String) onChanged;
|
final Function(String) onChanged;
|
||||||
|
final bool autofocus;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CwtchTextFieldState createState() => _CwtchTextFieldState();
|
_CwtchTextFieldState createState() => _CwtchTextFieldState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CwtchTextFieldState extends State<CwtchTextField> {
|
class _CwtchTextFieldState extends State<CwtchTextField> {
|
||||||
|
late final FocusNode _focusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_focusNode = FocusNode();
|
||||||
|
_focusNode.addListener(() {
|
||||||
|
// Select all...
|
||||||
|
if (_focusNode.hasFocus) widget.controller.selection = TextSelection(baseOffset: 0, extentOffset: widget.controller.text.length);
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<Settings>(builder: (context, theme, child) {
|
return Consumer<Settings>(builder: (context, theme, child) {
|
||||||
|
@ -25,6 +38,8 @@ class _CwtchTextFieldState extends State<CwtchTextField> {
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
validator: widget.validator,
|
validator: widget.validator,
|
||||||
onChanged: widget.onChanged,
|
onChanged: widget.onChanged,
|
||||||
|
autofocus: widget.autofocus,
|
||||||
|
focusNode: _focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: widget.labelText,
|
labelText: widget.labelText,
|
||||||
labelStyle: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
labelStyle: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -16,13 +17,9 @@ class _TorIconState extends State<TorIcon> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
child: Image(
|
child: Icon(
|
||||||
image: AssetImage(Provider.of<TorStatus>(context).progress == 0
|
Provider.of<TorStatus>(context).progress == 0 ? CwtchIcons.onion_off : (Provider.of<TorStatus>(context).progress == 100 ? CwtchIcons.onion_on : CwtchIcons.onion_waiting),
|
||||||
? "assets/core/Tor_OFF.png"
|
|
||||||
: (Provider.of<TorStatus>(context).progress == 100 ? "assets/core/Tor_icon.png" : "assets/core/Tor_Booting_up.png")),
|
|
||||||
// Color the onion per the text color...
|
|
||||||
color: Provider.of<Settings>(context).theme.mainTextColor(),
|
color: Provider.of<Settings>(context).theme.mainTextColor(),
|
||||||
colorBlendMode: BlendMode.srcIn,
|
|
||||||
semanticLabel: Provider.of<TorStatus>(context).progress == 100
|
semanticLabel: Provider.of<TorStatus>(context).progress == 100
|
||||||
? AppLocalizations.of(context)!.networkStatusOnline
|
? AppLocalizations.of(context)!.networkStatusOnline
|
||||||
: (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
|
: (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name=Cwtch
|
||||||
|
Comment=Metadata Resistant Chat
|
||||||
|
Exec=env LD_LIBRARY_PATH=~/.local/lib/cwtch/ ~/.local/bin/cwtch
|
||||||
|
Icon=cwtch
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;InstantMessaging;
|
||||||
|
Keywords=Internet;IM;Instant Messaging;Messaging;Chat;
|
|
@ -1,9 +1,10 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=cwtch
|
Name=Cwtch
|
||||||
Comment=Metadata Resistant Chat
|
Comment=Metadata Resistant Chat
|
||||||
Exec=env LD_LIBRARY_PATH=./lib/ ./cwtch
|
Exec=env LD_LIBRARY_PATH=./lib/ ./cwtch
|
||||||
Icon=cwtch
|
Icon=cwtch
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Internet;Chat;
|
Categories=Network;InstantMessaging;
|
||||||
|
Keywords=Internet;IM;Instant Messaging;Messaging;Chat
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name=Cwtch
|
||||||
|
Comment=Metadata Resistant Chat
|
||||||
|
Exec=env LD_LIBRARY_PATH=/usr/lib/cwtch /usr/bin/cwtch
|
||||||
|
Icon=cwtch
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;InstantMessaging;
|
||||||
|
Keywords=Internet;IM;Instant Messaging;Messaging;Chat
|
|
@ -2,12 +2,10 @@
|
||||||
// Generated file. Do not edit.
|
// Generated file. Do not edit.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <window_size/window_size_plugin.h>
|
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) window_size_registrar =
|
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
|
|
||||||
window_size_plugin_register_with_registrar(window_size_registrar);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
// Generated file. Do not edit.
|
// Generated file. Do not edit.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||||
#define GENERATED_PLUGIN_REGISTRANT_
|
#define GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
window_size
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
cp cwtch ~/.local/bin/
|
||||||
|
|
||||||
|
mkdir -p ~/.local/share/icons
|
||||||
|
cp cwtch.png ~/.local/share/icons
|
||||||
|
|
||||||
|
mkdir -p ~/.local/share/cwtch
|
||||||
|
cp -r data ~/.local/share/cwtch
|
||||||
|
|
||||||
|
mkdir -p ~/.local/lib/cwtch
|
||||||
|
cp -r lib/* ~/.local/lib/cwtch
|
||||||
|
|
||||||
|
mkdir -p ~/.local/share/applications
|
||||||
|
sed "s|~|$HOME|g" cwtch.home.desktop > $HOME/.local/share/applications/cwtch.desktop
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
cp cwtch /usr/bin/
|
||||||
|
|
||||||
|
cp cwtch.png /usr/share/icons
|
||||||
|
|
||||||
|
mkdir -p /usr/share/cwtch
|
||||||
|
cp -r data /usr/share/cwtch
|
||||||
|
|
||||||
|
mkdir -p /usr/lib/cwtch
|
||||||
|
cp -r lib/* /usr/lib/cwtch
|
||||||
|
|
||||||
|
cp cwtch.sys.desktop /usr/share/applications/cwtch.desktop
|
|
@ -1,5 +1,14 @@
|
||||||
#include "my_application.h"
|
#include "my_application.h"
|
||||||
|
|
||||||
|
// Added to check for location of assets folder
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
// To get the home dir of the user
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
#include <flutter_linux/flutter_linux.h>
|
#include <flutter_linux/flutter_linux.h>
|
||||||
#ifdef GDK_WINDOWING_X11
|
#ifdef GDK_WINDOWING_X11
|
||||||
#include <gdk/gdkx.h>
|
#include <gdk/gdkx.h>
|
||||||
|
@ -12,6 +21,18 @@ struct _MyApplication {
|
||||||
char** dart_entrypoint_arguments;
|
char** dart_entrypoint_arguments;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Redefining from flutter/engine::shell/platform/linux/fl_dart_project.cc
|
||||||
|
// struct def required here to enable compiler to allow access to variables
|
||||||
|
struct _FlDartProject {
|
||||||
|
GObject parent_instance;
|
||||||
|
|
||||||
|
gboolean enable_mirrors;
|
||||||
|
gchar* aot_library_path;
|
||||||
|
gchar* assets_path;
|
||||||
|
gchar* icu_data_path;
|
||||||
|
gchar** dart_entrypoint_args;
|
||||||
|
};
|
||||||
|
|
||||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||||
|
|
||||||
// Implements GApplication::activate.
|
// Implements GApplication::activate.
|
||||||
|
@ -48,11 +69,36 @@ static void my_application_activate(GApplication* application) {
|
||||||
gtk_window_set_title(window, "cwtch");
|
gtk_window_set_title(window, "cwtch");
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_window_set_icon_from_file(window, "./cwtch.png", NULL);
|
|
||||||
gtk_window_set_default_size(window, 1280, 720);
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
gtk_widget_show(GTK_WIDGET(window));
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
|
|
||||||
|
// Check if assets folder is relative to the executable or if we can use a system copy
|
||||||
|
struct stat info;
|
||||||
|
if (stat(fl_dart_project_get_assets_path(project), &info ) != 0 ) {
|
||||||
|
if( stat("/usr/share/cwtch/data/flutter_assets", &info ) != 0 ) {
|
||||||
|
struct passwd *pw = getpwuid(getuid());
|
||||||
|
const char *homedir = pw->pw_dir;
|
||||||
|
// /home/$USER/.local/share/cwtch/data/flutter_assets
|
||||||
|
project->assets_path = g_build_filename(homedir, ".local", "share", "cwtch", "data", "flutter_assets", nullptr);
|
||||||
|
// /home/$USER/.local/lib/cwtch/
|
||||||
|
project->aot_library_path = g_build_filename(homedir, ".local", "lib", "cwtch", "libapp.so", nullptr);
|
||||||
|
// /home/$USER/.local/share/cwtch/data
|
||||||
|
project->icu_data_path = g_build_filename(homedir, ".local", "share", "cwtch", "data", "icudtl.dat", nullptr);
|
||||||
|
gtk_window_set_icon_from_file(window, g_build_filename(homedir, ".local", "share", "icons", "cwtch.png", nullptr), NULL);
|
||||||
|
} else {
|
||||||
|
// /usr/share/cwtch/data/flutter_assets
|
||||||
|
project->assets_path = g_build_filename("/", "usr", "share", "cwtch", "data", "flutter_assets", nullptr);
|
||||||
|
// /usr/lib/cwtch
|
||||||
|
project->aot_library_path = g_build_filename("/", "usr", "lib", "cwtch", "libapp.so", nullptr);
|
||||||
|
// /usr/share/cwtch/data
|
||||||
|
project->icu_data_path = g_build_filename("/", "usr", "share", "cwtch", "data", "icudtl.dat", nullptr);
|
||||||
|
gtk_window_set_icon_from_file(window, "/usr/share/icons/cwtch.png", NULL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gtk_window_set_icon_from_file(window, "./cwtch.png", NULL);
|
||||||
|
}
|
||||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||||
|
|
||||||
FlView* view = fl_view_new(project);
|
FlView* view = fl_view_new(project);
|
||||||
|
|
112
pubspec.lock
|
@ -1,27 +1,27 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
archive:
|
ansicolor:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: ansicolor
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "2.0.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.1"
|
version: "2.7.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -42,7 +42,7 @@ packages:
|
||||||
name: charcode
|
name: charcode
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -57,13 +57,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
crypto:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: crypto
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.1"
|
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -77,7 +70,7 @@ packages:
|
||||||
name: dbus
|
name: dbus
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.5.1"
|
||||||
desktop_notifications:
|
desktop_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -111,11 +104,6 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_driver:
|
|
||||||
dependency: "direct main"
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -131,18 +119,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
fuchsia_remote_debug_protocol:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
glob:
|
glob:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: glob
|
name: glob
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "1.2.0"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -157,13 +140,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
integration_test:
|
injector:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: integration_test
|
name: injector
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2+3"
|
version: "2.0.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -191,7 +174,14 @@ packages:
|
||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.0"
|
||||||
|
msix:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: msix
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -199,6 +189,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
node_interop:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_interop
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
node_io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -358,13 +369,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
sync_http:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sync_http
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.0"
|
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -378,7 +382,7 @@ packages:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.4.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -393,20 +397,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
vm_service:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vm_service
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "6.2.0"
|
|
||||||
webdriver:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: webdriver
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.0"
|
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -414,15 +404,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
window_size:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "plugins/window_size"
|
|
||||||
ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
|
|
||||||
resolved-ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
|
|
||||||
url: "git://github.com/google/flutter-desktop-embedding.git"
|
|
||||||
source: git
|
|
||||||
version: "0.1.0"
|
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -436,7 +417,14 @@ packages:
|
||||||
name: xml
|
name: xml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.2"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.13.0 <3.0.0"
|
dart: ">=2.13.0 <3.0.0"
|
||||||
flutter: ">=1.20.0"
|
flutter: ">=1.20.0"
|
||||||
|
|
47
pubspec.yaml
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.0.0+1
|
version: 1.0.0+15
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
@ -37,30 +37,18 @@ dependencies:
|
||||||
desktop_notifications: 0.5.0
|
desktop_notifications: 0.5.0
|
||||||
|
|
||||||
glob: any
|
glob: any
|
||||||
# todo: flutter_driver causes version conflict. eg https://github.com/flutter/flutter/issues/44829
|
|
||||||
# testing-related deps
|
|
||||||
integration_test: ^1.0.0
|
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_driver:
|
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
window_size:
|
|
||||||
git:
|
|
||||||
url: git://github.com/google/flutter-desktop-embedding.git
|
|
||||||
path: plugins/window_size
|
|
||||||
ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
|
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
msix: ^2.1.3
|
||||||
|
# Uncomment to update lokalise translations (see README for list of deps to comment out bc incompatibilities)
|
||||||
#dev_dependencies:
|
#dev_dependencies:
|
||||||
# flutter_lokalise: any
|
# flutter_lokalise: any
|
||||||
|
|
||||||
# alternatively: flutter pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/intl/app_localizations.dart lib/l10n/intl_*.arb --api-token X --project-id Y
|
|
||||||
#flutter_lokalise:
|
#flutter_lokalise:
|
||||||
# project_id: ""
|
# project_id: "737094205fceda35c50aa2.60364948"
|
||||||
# api_token: ""
|
# api_token: "0407300fe4aa1edf1c1818e56234589e74c83c59" # Read only api Token from Dan
|
||||||
# include_tags:
|
|
||||||
# - tag1
|
|
||||||
# - tag2
|
|
||||||
|
|
||||||
flutter_intl:
|
flutter_intl:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
@ -95,6 +83,11 @@ flutter:
|
||||||
- assets/profiles/
|
- assets/profiles/
|
||||||
- assets/servers/
|
- assets/servers/
|
||||||
|
|
||||||
|
fonts:
|
||||||
|
- family: CwtchIcons
|
||||||
|
fonts:
|
||||||
|
- asset: assets/fonts/CwtchIcons.ttf
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
# To add custom fonts to your application, add a fonts section here,
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
@ -114,3 +107,19 @@ flutter:
|
||||||
#
|
#
|
||||||
# For details regarding fonts from package dependencies,
|
# For details regarding fonts from package dependencies,
|
||||||
# see https://flutter.dev/custom-fonts/#from-packages
|
# see https://flutter.dev/custom-fonts/#from-packages
|
||||||
|
|
||||||
|
|
||||||
|
msix_config:
|
||||||
|
display_name: Cwtch
|
||||||
|
publisher_display_name: Open Privacy Research Society
|
||||||
|
identity_name: im.cwtch.flwtch
|
||||||
|
msix_version: 1.0.0.0
|
||||||
|
certificate_path: codesign.pfx
|
||||||
|
certificate_password: pfx_pass
|
||||||
|
publisher: CN=Open Privacy Research Society, O=Open Privacy Research Society, L=Vancouver, S=British Columbia, C=CA
|
||||||
|
logo_path: cwtch.png
|
||||||
|
start_menu_icon_path: cwtch.png
|
||||||
|
tile_icon_path: assets\cwtch_title.png
|
||||||
|
icons_background_color: transparent
|
||||||
|
architecture: x64
|
||||||
|
capabilities: 'internetClient'
|
||||||
|
|
|
@ -14,8 +14,8 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
var settingsEnglishDark = Settings(Locale("en", ''), Opaque.dark);
|
var settingsEnglishDark = Settings(Locale("en", ''), OpaqueDark());
|
||||||
var settingsEnglishLight = Settings(Locale("en", ''), Opaque.light);
|
var settingsEnglishLight = Settings(Locale("en", ''), OpaqueLight());
|
||||||
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
|
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
@ -14,8 +14,8 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
var settingsEnglishDark = Settings(Locale("en", ''), Opaque.dark);
|
var settingsEnglishDark = Settings(Locale("en", ''), OpaqueDark());
|
||||||
var settingsEnglishLight = Settings(Locale("en", ''), Opaque.light);
|
var settingsEnglishLight = Settings(Locale("en", ''), OpaqueLight());
|
||||||
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
|
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
@ -14,8 +14,8 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
var settingsEnglishDark = Settings(Locale("en", ''), Opaque.dark);
|
var settingsEnglishDark = Settings(Locale("en", ''), OpaqueDark());
|
||||||
var settingsEnglishLight = Settings(Locale("en", ''), Opaque.light);
|
var settingsEnglishLight = Settings(Locale("en", ''), OpaqueLight());
|
||||||
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
|
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
|
||||||
|
|
||||||
String file(String slug) {
|
String file(String slug) {
|
||||||
|
|
|
@ -14,8 +14,8 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
var settingsEnglishDark = Settings(Locale("en", ''), Opaque.dark);
|
var settingsEnglishDark = Settings(Locale("en", ''), OpaqueDark());
|
||||||
var settingsEnglishLight = Settings(Locale("en", ''), Opaque.light);
|
var settingsEnglishLight = Settings(Locale("en", ''), OpaqueLight());
|
||||||
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
|
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
|
||||||
|
|
||||||
String file(String slug) {
|
String file(String slug) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
cmake_minimum_required(VERSION 3.15)
|
cmake_minimum_required(VERSION 3.15)
|
||||||
project(flutter_app LANGUAGES CXX)
|
project(cwtch LANGUAGES CXX)
|
||||||
|
|
||||||
set(BINARY_NAME "flutter_app")
|
set(BINARY_NAME "cwtch")
|
||||||
|
|
||||||
cmake_policy(SET CMP0063 NEW)
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 51 KiB |
|
@ -0,0 +1,6 @@
|
||||||
|
- cp nsis/cwtch-installer.nsi deploy/
|
||||||
|
- cd deploy
|
||||||
|
- makensis -V3 cwtch-installer.nsi
|
||||||
|
- export BUILDDATE=`date +%G-%m-%d-%H-%M`
|
||||||
|
- export FILENAME=cwtch-installer-$BUILDDATE.exe
|
||||||
|
- mv cwtch-installer.exe $FILENAME
|
|
@ -0,0 +1,92 @@
|
||||||
|
; USAGE: Run in ui/deploy, requires the output be in 'windows' directory
|
||||||
|
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
|
||||||
|
; General settings ----------------------------
|
||||||
|
Name "Cwtch"
|
||||||
|
; !define MUI_BRANDINGTEXT "SIG Beta Ver. 1.0"
|
||||||
|
|
||||||
|
Unicode True
|
||||||
|
|
||||||
|
# define the name of the installer
|
||||||
|
Outfile "cwtch-installer.exe"
|
||||||
|
|
||||||
|
# For removing Start Menu shortcut in Windows 7
|
||||||
|
#RequestExecutionLevel user
|
||||||
|
RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on)
|
||||||
|
|
||||||
|
# define the directory to install to, the desktop in this case as specified
|
||||||
|
# by the predefined $DESKTOP variable
|
||||||
|
InstallDir "$PROGRAMFILES\Cwtch"
|
||||||
|
|
||||||
|
;Get installation folder from registry if available
|
||||||
|
InstallDirRegKey HKCU "Software\Cwtch" "installLocation"
|
||||||
|
|
||||||
|
; MUI Interface -----------------------------
|
||||||
|
|
||||||
|
!define MUI_INSTALLCOLORS "DFB9DE 281831"
|
||||||
|
|
||||||
|
; 128x128, 32bit
|
||||||
|
!define MUI_ICON "../runner/resources/knot_128.ico"
|
||||||
|
|
||||||
|
!define MUI_HEADERIMAGE
|
||||||
|
!define MUI_HEADERIMAGE_BITMAP "cwtch_title.bmp"
|
||||||
|
|
||||||
|
!define MUI_TEXTCOLOR "350052"
|
||||||
|
|
||||||
|
!define MUI_WELCOMEFINISHPAGE_BITMAP "brand_side.bmp"
|
||||||
|
!define MUI_WELCOMEFINISHPAGE_BITMAP_STRETCH NoStretchNoCrop
|
||||||
|
|
||||||
|
!define MUI_INSTFILESPAGE_COLORS "DFB9DE 281831"
|
||||||
|
!define MUI_INSTFILESPAGE_PROGRESSBAR "colored"
|
||||||
|
|
||||||
|
!define MUI_FINISHPAGE_NOAUTOCLOSE
|
||||||
|
|
||||||
|
|
||||||
|
ShowInstDetails show
|
||||||
|
|
||||||
|
; Pages --------
|
||||||
|
|
||||||
|
|
||||||
|
!define MUI_WELCOMEPAGE_TITLE "Welcome to the Cwtch installer"
|
||||||
|
!define MUI_WELCOMEPAGE_TEXT "Cwtch (pronounced: kutch) is a Welsh word roughly meaning 'a hug that creates a safe space'$\n$\n\
|
||||||
|
Cwtch is a platform for building consentful, decentralized, untrusted infrastructure using metadata resistant group communication applications. Currently there is a selfnamed instant messaging prototype app that is driving development and testing. Many Further apps are planned as the platform matures."
|
||||||
|
|
||||||
|
!define MUI_FINISHPAGE_TITLE "Enjoy Cwtch"
|
||||||
|
!define MUI_FINISHPAGE_RUN $INSTDIR/ui.exe
|
||||||
|
!define MUI_FINISHPAGE_TEXT "You can keep up-to-date on Cwtch and report any issues you have at https://cwtch.im"
|
||||||
|
!define MUI_FINISHPAGE_LINK "https://cwtch.im"
|
||||||
|
!define MUI_FINISHPAGE_LINK_LOCATION "https://cwtch.im"
|
||||||
|
!define MUI_FINISHPAGE_LINK_COLOR "D01972"
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "../../LICENSE"
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
; Languages --------------------------------
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
|
|
||||||
|
# default section
|
||||||
|
Section
|
||||||
|
|
||||||
|
# define the output path for this file
|
||||||
|
SetOutPath $INSTDIR
|
||||||
|
|
||||||
|
# define what to install and place it in the output path
|
||||||
|
# Filler for .sh to populate with contents of deploy/windows
|
||||||
|
#FILESLISTSTART
|
||||||
|
FILE /r "..\..\build\windows\runner\Release\"
|
||||||
|
#FILESLISTEND
|
||||||
|
|
||||||
|
|
||||||
|
# create a shortcut in the start menu programs directory
|
||||||
|
CreateDirectory "$SMPROGRAMS\Cwtch"
|
||||||
|
CreateShortcut "$SMPROGRAMS\Cwtch\Cwtch.lnk" "$INSTDIR\cwtch.exe" "" "$INSTDIR\cwtch.ico"
|
||||||
|
|
||||||
|
;Store installation folder
|
||||||
|
WriteRegStr HKCU "Software\Cwtch" "installLocation" $INSTDIR
|
||||||
|
|
||||||
|
SectionEnd
|
After Width: | Height: | Size: 9.5 KiB |
|
@ -52,7 +52,16 @@ END
|
||||||
|
|
||||||
// Icon with lowest ID value placed first to ensure application icon
|
// Icon with lowest ID value placed first to ensure application icon
|
||||||
// remains consistent on all systems.
|
// remains consistent on all systems.
|
||||||
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
IDI_APP_ICON_LG ICON "resources\\knot_256.ico"
|
||||||
|
IDI_APP_ICON_SM ICON "resources\\knot_64.ico"
|
||||||
|
|
||||||
|
IDI_APP_ICON_256 ICON "resources\\knot_256.ico"
|
||||||
|
IDI_APP_ICON_128 ICON "resources\\knot_128.ico"
|
||||||
|
IDI_APP_ICON_64 ICON "resources\\knot_64.ico"
|
||||||
|
IDI_APP_ICON_48 ICON "resources\\knot_48.ico"
|
||||||
|
IDI_APP_ICON_32 ICON "resources\\knot_32.ico"
|
||||||
|
IDI_APP_ICON_16 ICON "resources\\knot_16.ico"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -89,13 +98,13 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "040904e4"
|
BLOCK "040904e4"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "com.example" "\0"
|
VALUE "CompanyName", "Open Privacy Research Society" "\0"
|
||||||
VALUE "FileDescription", "A new Flutter project." "\0"
|
VALUE "FileDescription", "Cwtch Instant Messenger" "\0"
|
||||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||||
VALUE "InternalName", "flutter_app" "\0"
|
VALUE "InternalName", "cwtch" "\0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0"
|
VALUE "LegalCopyright", "Copyright (C) 2021 Open Privacy Research Society. All rights reserved." "\0"
|
||||||
VALUE "OriginalFilename", "flutter_app.exe" "\0"
|
VALUE "OriginalFilename", "cwtch.exe" "\0"
|
||||||
VALUE "ProductName", "flutter_app" "\0"
|
VALUE "ProductName", "Cwtch" "\0"
|
||||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
|
|
@ -30,7 +30,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||||
FlutterWindow window(&run_loop, project);
|
FlutterWindow window(&run_loop, project);
|
||||||
Win32Window::Point origin(10, 10);
|
Win32Window::Point origin(10, 10);
|
||||||
Win32Window::Size size(1280, 720);
|
Win32Window::Size size(1280, 720);
|
||||||
if (!window.CreateAndShow(L"flutter_app", origin, size)) {
|
if (!window.CreateAndShow(L"cwtch", origin, size)) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
window.SetQuitOnClose(true);
|
window.SetQuitOnClose(true);
|
||||||
|
|
|
@ -2,7 +2,16 @@
|
||||||
// Microsoft Visual C++ generated include file.
|
// Microsoft Visual C++ generated include file.
|
||||||
// Used by Runner.rc
|
// Used by Runner.rc
|
||||||
//
|
//
|
||||||
#define IDI_APP_ICON 101
|
#define IDI_APP_ICON_LG 101
|
||||||
|
#define IDI_APP_ICON_SM 102
|
||||||
|
|
||||||
|
#define IDI_APP_ICON_256 103
|
||||||
|
#define IDI_APP_ICON_128 104
|
||||||
|
#define IDI_APP_ICON_64 105
|
||||||
|
#define IDI_APP_ICON_48 106
|
||||||
|
#define IDI_APP_ICON_32 107
|
||||||
|
#define IDI_APP_ICON_16 108
|
||||||
|
|
||||||
|
|
||||||
// Next default values for new objects
|
// Next default values for new objects
|
||||||
//
|
//
|
||||||
|
|
Before Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 17 KiB |