Compare commits
33 Commits
opaqueBloc
...
trunk
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 |
237
.drone.yml
237
.drone.yml
|
@ -1,184 +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:
|
||||
- ./fetch-tor.sh
|
||||
- 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/install-*.sh deploy/linux
|
||||
- cp linux/cwtch.png deploy/linux
|
||||
- cp linux/libCwtch.so deploy/linux/lib/
|
||||
# should not be needed, should be in data/flutter_assets and work from there
|
||||
#- cp /sdks/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat deploy/linux
|
||||
- cp linux/tor deploy/linux
|
||||
- cd deploy
|
||||
- mv linux cwtch
|
||||
- tar -czf cwtch-`cat ../VERSION`.tar.gz cwtch
|
||||
- rm -r cwtch
|
||||
|
||||
- name: test-build-android
|
||||
image: cirrusci/flutter:dev
|
||||
when:
|
||||
event: pull_request
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /root/.pub-cache
|
||||
commands:
|
||||
- flutter build apk --debug
|
||||
|
||||
- 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
|
||||
type: docker
|
||||
name: windows
|
||||
|
@ -221,27 +40,11 @@ steps:
|
|||
|
||||
- name: build-windows
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
environment:
|
||||
pfx:
|
||||
from_secret: pfx
|
||||
pfx_pass:
|
||||
from_secret: pfx_pass
|
||||
commands:
|
||||
- move pubspec.yaml pubspec.yaml.orig
|
||||
- (Get-Content -path pubspec.yaml.orig -Raw) -Replace 'pfx_pass',"$Env:pfx_pass" | Set-Content -path pubspec.yaml
|
||||
- flutter pub get
|
||||
- $Env:version += type .\VERSION
|
||||
- $Env:builddate += type .\BUILDDATE
|
||||
- $Env:buildname = 'flwtch-win-' + $Env:version + '-' + $Env:builddate
|
||||
- $Env:builddir = $Env:buildname
|
||||
- $Env:zip = 'deploy\\' + $Env:builddir +'\\cwtch-' + $Env:version + '.zip'
|
||||
- $Env:zipsha = $Env:zip + '.sha512'
|
||||
- $Env:msix = 'cwtch-install-' + $Env:version + '.msix'
|
||||
- $Env:msixsha = $Env:msix + '.sha512'
|
||||
- $Env:releasedir = "build\\windows\\runner\\Release\\"
|
||||
- echo $Env:releasedir
|
||||
- echo $Env:builddir
|
||||
- echo $Env:zip
|
||||
- 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
|
||||
|
@ -251,24 +54,46 @@ steps:
|
|||
- 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"
|
||||
- dir $Env:releasedir
|
||||
|
||||
- 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
|
||||
- flutter pub run msix:create
|
||||
- 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\$Env:builddir
|
||||
- dir deploy
|
||||
- powershell -command "move $Env:releasedir\cwtch.msix deploy\$Env:builddir\$Env:msix"
|
||||
- move $Env:releasedir $Env:builddir
|
||||
- powershell -command "Compress-Archive -Path $Env:builddir -DestinationPath $Env:zip"
|
||||
#- powershell -command "move $Env:zip deploy\$Env:builddir\$Env:zip"
|
||||
#- powershell -command "(Get-FileHash $Env:zip -Algorithm sha512).Hash" > ${Env:zipsha}
|
||||
- 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
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
when:
|
||||
event: push
|
||||
status: [ success ]
|
||||
event: push
|
||||
status: [ success ]
|
||||
environment:
|
||||
BUILDFILES_KEY:
|
||||
from_secret: buildfiles_key
|
||||
|
|
|
@ -1 +1 @@
|
|||
v0.0.2-104-gc1b7e4c-2021-06-22-23-59
|
||||
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.
|
||||
|
|
@ -136,7 +136,7 @@ class MainActivity: FlutterActivity() {
|
|||
super.onResume()
|
||||
Log.i("MainActivity.kt", "onResume")
|
||||
if (myReceiver == null) {
|
||||
Log.i("MainActivity.kt", "onResume registering localbroadcastreceiver")
|
||||
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)
|
||||
|
@ -163,7 +163,7 @@ class MainActivity: FlutterActivity() {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.i("MainActivity.kt", "onDestroy")
|
||||
Log.i("MainActivity.kt", "onDestroy - cancelling all WORKER_TAG and pruning old work")
|
||||
WorkManager.getInstance(this).cancelAllWorkByTag(WORKER_TAG)
|
||||
WorkManager.getInstance(this).pruneWork()
|
||||
}
|
||||
|
@ -188,6 +188,8 @@ class MainActivity: FlutterActivity() {
|
|||
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
|
||||
|
||||
|
@ -198,5 +200,4 @@ class MainActivity: FlutterActivity() {
|
|||
eventBus.invokeMethod(evtType, evtData)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -37,7 +37,8 @@ class CwtchNotifier {
|
|||
appState.SetAppError(data["Error"]);
|
||||
break;
|
||||
case "NewPeer":
|
||||
profileCN.add(data["Identity"], data["name"], data["picture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true");
|
||||
// if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta...
|
||||
profileCN.add(data["Identity"], data["name"], data["picture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["tag"] != "v1-defaultPassword");
|
||||
break;
|
||||
case "PeerCreated":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(
|
||||
|
@ -97,7 +98,9 @@ class CwtchNotifier {
|
|||
break;
|
||||
case "NewMessageFromPeer":
|
||||
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.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
||||
break;
|
||||
|
@ -122,7 +125,9 @@ class CwtchNotifier {
|
|||
case "NewMessageFromGroup":
|
||||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||
//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.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2021-06-16T23:15:48+02:00",
|
||||
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||
"tooltipHidePassword": "Hide Password",
|
||||
"tooltipShowPassword": "Show Password",
|
||||
"serverNotSynced": "Syncing New Messages (This can take some time)...",
|
||||
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||
|
@ -154,7 +158,6 @@
|
|||
"inviteToGroupLabel": "In die Gruppe einladen",
|
||||
"groupNameLabel": "Gruppenname",
|
||||
"viewServerInfo": "Server Info",
|
||||
"serverNotSynced": "Out of Sync",
|
||||
"serverSynced": "Synced",
|
||||
"serverConnectivityDisconnected": "Server getrennt",
|
||||
"serverConnectivityConnected": "Server verbunden",
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"@@locale": "en",
|
||||
"@@last_modified": "2021-06-16T23:15:48+02:00",
|
||||
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||
"tooltipHidePassword": "Hide Password",
|
||||
"tooltipShowPassword": "Show Password",
|
||||
"serverNotSynced": "Syncing New Messages (This can take some time)...",
|
||||
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||
|
@ -154,7 +158,6 @@
|
|||
"inviteToGroupLabel": "Invite to group",
|
||||
"groupNameLabel": "Group Name",
|
||||
"viewServerInfo": "Server Info",
|
||||
"serverNotSynced": "Out of Sync",
|
||||
"serverSynced": "Synced",
|
||||
"serverConnectivityDisconnected": "Server Disconnected",
|
||||
"serverConnectivityConnected": "Server Connected",
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2021-06-16T23:15:48+02:00",
|
||||
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||
"tooltipHidePassword": "Hide Password",
|
||||
"tooltipShowPassword": "Show Password",
|
||||
"serverNotSynced": "Fuera de sincronización con el servidor",
|
||||
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||
|
@ -154,7 +158,6 @@
|
|||
"inviteToGroupLabel": "Invitar al grupo",
|
||||
"groupNameLabel": "Nombre del grupo",
|
||||
"viewServerInfo": "Información del servidor",
|
||||
"serverNotSynced": "Fuera de sincronización con el servidor",
|
||||
"serverSynced": "Sincronizado",
|
||||
"serverConnectivityDisconnected": "Servidor desconectado",
|
||||
"serverConnectivityConnected": "Servidor conectado",
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2021-06-16T23:15:48+02:00",
|
||||
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||
"tooltipHidePassword": "Hide Password",
|
||||
"tooltipShowPassword": "Show Password",
|
||||
"serverNotSynced": "Syncing New Messages (This can take some time)...",
|
||||
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||
|
@ -154,7 +158,6 @@
|
|||
"inviteToGroupLabel": "Inviter quelqu'un",
|
||||
"groupNameLabel": "Nom du groupe",
|
||||
"viewServerInfo": "Server Info",
|
||||
"serverNotSynced": "Out of Sync",
|
||||
"serverSynced": "Synced",
|
||||
"serverConnectivityDisconnected": "Server Disconnected",
|
||||
"serverConnectivityConnected": "Server Connected",
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"@@locale": "it",
|
||||
"@@last_modified": "2021-06-16T23:15:48+02:00",
|
||||
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||
"tooltipHidePassword": "Hide Password",
|
||||
"tooltipShowPassword": "Show Password",
|
||||
"serverNotSynced": "Non sincronizzato",
|
||||
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||
|
@ -154,7 +158,6 @@
|
|||
"inviteToGroupLabel": "Invitare nel gruppo",
|
||||
"groupNameLabel": "Nome del gruppo",
|
||||
"viewServerInfo": "Informazioni sul server",
|
||||
"serverNotSynced": "Non sincronizzato",
|
||||
"serverSynced": "Sincronizzato",
|
||||
"serverConnectivityDisconnected": "Server disconnesso",
|
||||
"serverConnectivityConnected": "Server connesso",
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"@@locale": "pt",
|
||||
"@@last_modified": "2021-06-16T23:15:48+02:00",
|
||||
"@@last_modified": "2021-06-24T23:32:06+02:00",
|
||||
"tooltipHidePassword": "Hide Password",
|
||||
"tooltipShowPassword": "Show Password",
|
||||
"serverNotSynced": "Syncing New Messages (This can take some time)...",
|
||||
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
||||
"shutdownCwtchAction": "Shutdown Cwtch",
|
||||
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
||||
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
||||
|
@ -154,7 +158,6 @@
|
|||
"inviteToGroupLabel": "Convidar ao grupo",
|
||||
"groupNameLabel": "Nome do Grupo",
|
||||
"viewServerInfo": "Server Info",
|
||||
"serverNotSynced": "Out of Sync",
|
||||
"serverSynced": "Synced",
|
||||
"serverConnectivityDisconnected": "Server Disconnected",
|
||||
"serverConnectivityConnected": "Server Connected",
|
||||
|
|
|
@ -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
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
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)");
|
||||
}
|
||||
|
|
|
@ -45,10 +45,6 @@ class Flwtch extends StatefulWidget {
|
|||
class FlwtchState extends State<Flwtch> {
|
||||
final TextStyle biggerFont = const TextStyle(fontSize: 18);
|
||||
late Cwtch cwtch;
|
||||
late ProfileInfoState selectedProfile;
|
||||
String selectedConversation = "";
|
||||
var columns = [1]; // default or 'single column' mode
|
||||
//var columns = [1, 1, 2];
|
||||
late ProfileListState profs;
|
||||
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
|
||||
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdown');
|
||||
|
@ -108,7 +104,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
title: 'Cwtch',
|
||||
theme: mkThemeData(settings),
|
||||
home: appState.cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(),
|
||||
home: appState.cwtchInit == true ? ShiftRightFixer(child: ProfileMgrView()) : SplashView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -128,24 +124,38 @@ class FlwtchState extends State<Flwtch> {
|
|||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
navKey.currentState?.push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext builderContext) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: profile),
|
||||
ChangeNotifierProvider.value(value: contact),
|
||||
],
|
||||
builder: (context, child) => MessageView(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// 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
|
||||
|
|
|
@ -13,7 +13,6 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:glob/glob.dart';
|
||||
import 'package:glob/list_local_fs.dart';
|
||||
|
||||
var globalSettings = Settings(Locale("en", ''), OpaqueDark());
|
||||
var globalErrorHandler = ErrorHandler();
|
||||
|
|
|
@ -38,10 +38,10 @@ class ProfileListState extends ChangeNotifier {
|
|||
List<ProfileInfoState> _profiles = [];
|
||||
int get num => _profiles.length;
|
||||
|
||||
void add(String onion, String name, String picture, String contactsJson, String serverJson, bool online) {
|
||||
void add(String onion, String name, String picture, String contactsJson, String serverJson, bool online, bool encrypted) {
|
||||
var idx = _profiles.indexWhere((element) => element.onion == onion);
|
||||
if (idx == -1) {
|
||||
_profiles.add(ProfileInfoState(onion: onion, nickname: name, imagePath: picture, contactsJson: contactsJson, serversJson: serverJson, online: online));
|
||||
_profiles.add(ProfileInfoState(onion: onion, nickname: name, imagePath: picture, contactsJson: contactsJson, serversJson: serverJson, online: online, encrypted: encrypted));
|
||||
} else {
|
||||
_profiles[idx].updateFrom(onion, name, picture, contactsJson, serverJson, online);
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ class ProfileListState extends ChangeNotifier {
|
|||
class AppState extends ChangeNotifier {
|
||||
bool cwtchInit = false;
|
||||
String appError = "";
|
||||
String? _selectedProfile;
|
||||
String? _selectedConversation;
|
||||
|
||||
void SetCwtchInit() {
|
||||
cwtchInit = true;
|
||||
|
@ -74,6 +76,20 @@ class AppState extends ChangeNotifier {
|
|||
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 {
|
||||
|
@ -161,6 +177,10 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
int _unreadMessages = 0;
|
||||
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({
|
||||
required this.onion,
|
||||
nickname = "",
|
||||
|
@ -169,11 +189,13 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
contactsJson = "",
|
||||
serversJson = "",
|
||||
online = false,
|
||||
encrypted = true,
|
||||
}) {
|
||||
this._nickname = nickname;
|
||||
this._imagePath = imagePath;
|
||||
this._unreadMessages = unreadMessages;
|
||||
this._online = online;
|
||||
this._encrypted = encrypted;
|
||||
|
||||
if (contactsJson != null && contactsJson != "" && contactsJson != "null") {
|
||||
List<dynamic> contacts = jsonDecode(contactsJson);
|
||||
|
@ -227,6 +249,9 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
// Check encrypted status for profile info screen
|
||||
bool get isEncrypted => this._encrypted;
|
||||
|
||||
String get nickname => this._nickname;
|
||||
set nickname(String newValue) {
|
||||
this._nickname = newValue;
|
||||
|
@ -412,7 +437,8 @@ class ContactInfoState extends ChangeNotifier {
|
|||
|
||||
bool isOnline() {
|
||||
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 {
|
||||
return this.status == "Authenticated";
|
||||
}
|
||||
|
@ -491,6 +517,8 @@ class MessageState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
set loaded(bool newVal) {
|
||||
// quickly-arriving messages get discarded before loading sometimes
|
||||
if (!hasListeners) return;
|
||||
this._loaded = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,13 @@ import 'opaque.dart';
|
|||
|
||||
const TapirGroupsExperiment = "tapir-groups-experiment";
|
||||
|
||||
enum DualpaneMode {
|
||||
Single,
|
||||
Dual1to2,
|
||||
Dual1to4,
|
||||
CopyPortrait,
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// Settings Pane.
|
||||
|
@ -19,6 +26,8 @@ class Settings extends ChangeNotifier {
|
|||
// explicitly set experiments to false until told otherwise...
|
||||
bool experimentsEnabled = false;
|
||||
HashMap<String, bool> experiments = HashMap.identity();
|
||||
DualpaneMode _uiColumnModePortrait = DualpaneMode.Single;
|
||||
DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait;
|
||||
|
||||
bool blockUnknownConnections = false;
|
||||
|
||||
|
@ -73,6 +82,10 @@ class Settings extends ChangeNotifier {
|
|||
// Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON
|
||||
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
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -134,6 +147,55 @@ class Settings extends ChangeNotifier {
|
|||
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.
|
||||
Settings(this.locale, this.theme);
|
||||
|
||||
|
@ -150,7 +212,9 @@ class Settings extends ChangeNotifier {
|
|||
"ExperimentsEnabled": this.experimentsEnabled,
|
||||
"Experiments": experiments,
|
||||
"StateRootPane": 0,
|
||||
"FirstTime": false
|
||||
"FirstTime": false,
|
||||
"UIColumnModePortrait": uiColumnModePortrait.toString(),
|
||||
"UIColumnModeLandscape": uiColumnModeLandscape.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
visible: usePassword,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
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: [
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
|
||||
SizedBox(
|
||||
|
@ -165,7 +165,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
controller: ctrlrOldPass,
|
||||
validator: (value) {
|
||||
// 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;
|
||||
}
|
||||
if (Provider.of<ErrorHandler>(context).deleteProfileError == true) {
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../settings.dart';
|
||||
import 'contactsview.dart';
|
||||
import 'messageview.dart';
|
||||
|
||||
|
@ -14,24 +15,25 @@ class DoubleColumnView extends StatefulWidget {
|
|||
class _DoubleColumnViewState extends State<DoubleColumnView> {
|
||||
@override
|
||||
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(
|
||||
direction: Axis.horizontal,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
flex: flwtch.columns[0],
|
||||
flex: cols[0],
|
||||
child: ContactsView(
|
||||
key: widget.key,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
flex: flwtch.columns[1],
|
||||
child: flwtch.selectedConversation == ""
|
||||
? Center(child: Text(AppLocalizations.of(context)!.addContactFirst))
|
||||
flex: cols[1],
|
||||
child: flwtch.selectedConversation == null
|
||||
? Card(child:Center(child: Text(AppLocalizations.of(context)!.addContactFirst)))
|
||||
: //dev
|
||||
MultiProvider(providers: [
|
||||
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())),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -64,7 +64,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
}).toList())),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.settingTheme, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
value: settings.current() == "light",
|
||||
value: settings.current().identifier() == "light",
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
settings.setLight();
|
||||
|
@ -80,24 +80,33 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
secondary: Icon(CwtchIcons.change_theme, color: settings.current().mainTextColor()),
|
||||
),
|
||||
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()),
|
||||
trailing: DropdownButton(
|
||||
value: "Single",
|
||||
value: settings.uiColumnModePortrait.toString(),
|
||||
onChanged: (String? newValue) {
|
||||
if (newValue == "Double (1:2)") {
|
||||
Provider.of<FlwtchState>(context).columns = [1, 2];
|
||||
} else if (newValue == "Double (1:4)") {
|
||||
Provider.of<FlwtchState>(context).columns = [1, 4];
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context).columns = [1];
|
||||
}
|
||||
settings.uiColumnModePortrait = Settings.uiColumnModeFromString(newValue!);
|
||||
saveSettings(context);
|
||||
},
|
||||
// TODO: Only allow in landscape?
|
||||
items: (Platform.isAndroid ? ["Single"] : ["Single", "Double (1:2)", "Double (1:4)"]).map<DropdownMenuItem<String>>((String value) {
|
||||
items: Settings.uiColumnModeOptions(false).map<DropdownMenuItem<String>>((DualpaneMode value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
value: value.toString(),
|
||||
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())),
|
||||
SwitchListTile(
|
||||
|
@ -160,13 +169,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
)),
|
||||
AboutListTile(
|
||||
icon: Icon(Icons.info, color: settings.current().mainTextColor()),
|
||||
applicationIcon: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Image(
|
||||
image: AssetImage("assets/knott.png"),
|
||||
width: 128,
|
||||
height: 128,
|
||||
)),
|
||||
applicationIcon: Padding(padding:EdgeInsets.all(5), child: Icon(CwtchIcons.cwtch_knott)),
|
||||
applicationName: "Cwtch (Flutter UI)",
|
||||
applicationVersion: AppLocalizations.of(context)!.versionBuilddate.replaceAll("%1", EnvironmentConfig.BUILD_VER).replaceAll("%2", EnvironmentConfig.BUILD_DATE),
|
||||
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
|
||||
|
|
|
@ -2,6 +2,7 @@ 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/material.dart';
|
||||
import 'package:cwtch/views/peersettingsview.dart';
|
||||
|
@ -42,11 +43,24 @@ class _MessageViewState extends State<MessageView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var appState = Provider.of<AppState>(context);
|
||||
return WillPopScope(
|
||||
onWillPop: _onWillPop,
|
||||
child: Scaffold(
|
||||
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: [
|
||||
//IconButton(icon: Icon(Icons.chat), onPressed: _pushContactSettings),
|
||||
//IconButton(icon: Icon(Icons.list), onPressed: _pushContactSettings),
|
||||
|
|
|
@ -77,7 +77,6 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
};
|
||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||
// todo translations
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
},
|
||||
|
|
|
@ -42,41 +42,45 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
// 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)
|
||||
// TODO pop up a dialogue regarding closing the app?
|
||||
builder: (context, settings, child) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
_showShutdown();
|
||||
return closeApp;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: settings.theme.backgroundMainColor(),
|
||||
appBar: AppBar(
|
||||
title: Row(children: [
|
||||
Image(
|
||||
image: AssetImage("assets/core/knott-white.png"),
|
||||
filterQuality: FilterQuality.medium,
|
||||
isAntiAlias: true,
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorBlendMode: BlendMode.dstIn,
|
||||
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
|
||||
builder: (context, settings, child) =>
|
||||
WillPopScope(
|
||||
onWillPop: () async {
|
||||
_showShutdown();
|
||||
return closeApp;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: settings.theme.backgroundMainColor(),
|
||||
appBar: AppBar(
|
||||
title: Row(children: [
|
||||
Image(
|
||||
image: AssetImage("assets/core/knott-white.png"),
|
||||
filterQuality: FilterQuality.medium,
|
||||
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(
|
||||
width: 10,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
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())))
|
||||
]),
|
||||
actions: getActions(),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddEditProfile,
|
||||
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||
),
|
||||
),
|
||||
body: _buildProfileManager(),
|
||||
)),
|
||||
body: _buildProfileManager(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class SplashView extends StatelessWidget {
|
|||
body: Center(
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Image(
|
||||
image: AssetImage("assets/knott.png"),
|
||||
image: AssetImage("assets/core/knott-white.png"),
|
||||
filterQuality: FilterQuality.medium,
|
||||
isAntiAlias: true,
|
||||
width: 200,
|
||||
|
|
|
@ -3,9 +3,12 @@ import 'package:cwtch/views/profilemgrview.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../settings.dart';
|
||||
import 'contactsview.dart';
|
||||
import 'messageview.dart';
|
||||
|
||||
// currently unused but maybe one day?
|
||||
class TripleColumnView extends StatefulWidget {
|
||||
@override
|
||||
_TripleColumnViewState createState() => _TripleColumnViewState();
|
||||
|
@ -14,19 +17,22 @@ class TripleColumnView extends StatefulWidget {
|
|||
class _TripleColumnViewState extends State<TripleColumnView> {
|
||||
@override
|
||||
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>[
|
||||
Flexible(
|
||||
flex: flwtch.columns[0],
|
||||
flex: columns[0],
|
||||
child: ProfileMgrView(),
|
||||
),
|
||||
Flexible(
|
||||
flex: flwtch.columns[1],
|
||||
child: flwtch.selectedProfile == null ? Center(child: Text(AppLocalizations.of(context)!.createProfileToBegin)) : ContactsView(), //dev
|
||||
flex: columns[1],
|
||||
child: appState.selectedProfile == null ? Center(child: Text(AppLocalizations.of(context)!.createProfileToBegin)) : ContactsView(), //dev
|
||||
),
|
||||
Flexible(
|
||||
flex: flwtch.columns[2],
|
||||
child: flwtch.selectedConversation == ""
|
||||
flex: columns[2],
|
||||
child: appState.selectedConversation == null
|
||||
? Center(child: Text(AppLocalizations.of(context)!.addContactFirst))
|
||||
: //dev
|
||||
Container(child: MessageView()),
|
||||
|
|
|
@ -20,6 +20,8 @@ class _ContactRowState extends State<ContactRow> {
|
|||
var contact = Provider.of<ContactInfoState>(context);
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
color: Provider.of<AppState>(context).selectedConversation == contact.onion ? Provider.of<Settings>(context).theme.backgroundHilightElementColor() : null,
|
||||
borderOnForeground: false,
|
||||
child: InkWell(
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Padding(
|
||||
|
@ -80,17 +82,19 @@ class _ContactRowState extends State<ContactRow> {
|
|||
]),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
var flwtch = Provider.of<FlwtchState>(context, listen: false);
|
||||
flwtch.setState(() => flwtch.selectedConversation = contact.onion);
|
||||
// case 2/3 handled by Double/TripleColumnView respectively
|
||||
if (flwtch.columns.length == 1) _pushMessageView(contact.onion);
|
||||
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0;
|
||||
// triggers update in Double/TripleColumnView
|
||||
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) {
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0;
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
|
|
|
@ -14,7 +14,6 @@ import 'messagebubbledecorations.dart';
|
|||
|
||||
// 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
|
||||
// todo: Reject buttons currently aren't tracked and will reset when the message is reloaded
|
||||
class InvitationBubble extends StatefulWidget {
|
||||
@override
|
||||
InvitationBubbleState createState() => InvitationBubbleState();
|
||||
|
@ -35,6 +34,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null;
|
||||
var prettyDate = "";
|
||||
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();
|
||||
|
||||
|
@ -66,14 +66,18 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
return MalformedBubble();
|
||||
}
|
||||
|
||||
var wdgMessage = fromMe
|
||||
var wdgMessage = isGroup && !showGroupInvite ?
|
||||
Text(AppLocalizations.of(context)!.groupInviteSettingsWarning) :
|
||||
fromMe
|
||||
? senderInviteChrome(AppLocalizations.of(context)!.sendAnInvitation,
|
||||
isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget)!.nickname : Provider.of<MessageState>(context).message, myKey)
|
||||
: (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, Provider.of<MessageState>(context).inviteNick,
|
||||
Provider.of<MessageState>(context).inviteTarget, myKey));
|
||||
|
||||
Widget wdgDecorations;
|
||||
if (fromMe) {
|
||||
if (isGroup && !showGroupInvite) {
|
||||
wdgDecorations = Text('\u202F');
|
||||
} else if (fromMe) {
|
||||
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||
} else if (isAccepted) {
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F');
|
||||
|
@ -109,7 +113,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
child: Padding(
|
||||
padding: EdgeInsets.all(9.0),
|
||||
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(CwtchIcons.send_invite, 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(
|
||||
widthFactor: 1.0,
|
||||
child: Column(
|
||||
|
|
|
@ -19,6 +19,8 @@ class _MessageListState extends State<MessageList> {
|
|||
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(
|
||||
child: Container(
|
||||
child: Column(children: [
|
||||
|
@ -27,7 +29,10 @@ class _MessageListState extends State<MessageList> {
|
|||
child: Container(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||
child: showOfflineWarning
|
||||
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...
|
||||
|
@ -50,6 +55,7 @@ class _MessageListState extends State<MessageList> {
|
|||
alignment: Alignment.center,
|
||||
image: AssetImage("assets/core/negative_heart_512px.png"),
|
||||
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementTextColor(), BlendMode.srcIn))),
|
||||
// Don't load messages for syncing server...
|
||||
child: ListView.builder(
|
||||
controller: ctrlr1,
|
||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cwtch/cwtch_icons_icons.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../settings.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
// Provides a styled Password Input Field for use in Form Widgets.
|
||||
// Callers must provide a text controller, label helper text and a validator.
|
||||
|
@ -22,9 +23,9 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// todo: translations
|
||||
var label = "View Password";
|
||||
var label = AppLocalizations.of(context)!.tooltipShowPassword;
|
||||
if (!obscureText) {
|
||||
label = "Hide Password";
|
||||
label = AppLocalizations.of(context)!.tooltipHidePassword;
|
||||
}
|
||||
|
||||
return Consumer<Settings>(builder: (context, theme, child) {
|
||||
|
|
|
@ -30,84 +30,96 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
padding: const EdgeInsets.all(2.0), //border size
|
||||
child: ProfileImage(
|
||||
badgeCount: 0,
|
||||
badgeColor: Provider.of<Settings>(context).theme.portraitProfileBadgeColor(),
|
||||
badgeTextColor: Provider.of<Settings>(context).theme.portraitProfileBadgeTextColor(),
|
||||
badgeColor: Provider
|
||||
.of<Settings>(context)
|
||||
.theme
|
||||
.portraitProfileBadgeColor(),
|
||||
badgeTextColor: Provider
|
||||
.of<Settings>(context)
|
||||
.theme
|
||||
.portraitProfileBadgeTextColor(),
|
||||
diameter: 64.0,
|
||||
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(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
profile.nickname,
|
||||
semanticsLabel: profile.nickname,
|
||||
style: Provider.of<FlwtchState>(context).biggerFont,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
ExcludeSemantics(
|
||||
child: Text(
|
||||
profile.onion,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
],
|
||||
)),
|
||||
children: [
|
||||
Text(
|
||||
profile.nickname,
|
||||
semanticsLabel: profile.nickname,
|
||||
style: Provider
|
||||
.of<FlwtchState>(context)
|
||||
.biggerFont,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
ExcludeSemantics(
|
||||
child: Text(
|
||||
profile.onion,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
],
|
||||
)),
|
||||
IconButton(
|
||||
enableFeedback: true,
|
||||
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname,
|
||||
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||
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: () {
|
||||
setState(() {
|
||||
var flwtch = Provider.of<FlwtchState>(context, listen: false);
|
||||
flwtch.setState(() {
|
||||
flwtch.selectedProfile = profile;
|
||||
flwtch.selectedConversation = "";
|
||||
});
|
||||
var appState = Provider.of<AppState>(context, listen: false);
|
||||
appState.selectedProfile = profile.onion;
|
||||
appState.selectedConversation = null;
|
||||
|
||||
switch (flwtch.columns.length) {
|
||||
case 1:
|
||||
_pushContactList(profile, false);
|
||||
break;
|
||||
case 2:
|
||||
_pushContactList(profile, true);
|
||||
break;
|
||||
} // case 3: handled by TripleColumnView
|
||||
_pushContactList(profile, appState.isLandscape(context));//orientation == Orientation.landscape);
|
||||
});
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
void _pushContactList(ProfileInfoState profile, bool includeDoublePane) {
|
||||
void _pushContactList(ProfileInfoState profile, bool isLandscape) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
settings: RouteSettings(name: "conversations"),
|
||||
builder: (BuildContext buildcontext) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
|
||||
ChangeNotifierProvider<ContactListState>.value(value: profile.contactList),
|
||||
],
|
||||
builder: (context, widget) => includeDoublePane ? DoubleColumnView() : ContactsView(),
|
||||
);
|
||||
return OrientationBuilder(
|
||||
builder: (orientationBuilderContext, orientation) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
|
||||
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>(
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<ProfileInfoState>(
|
||||
create: (_) => ProfileInfoState(onion: onion, nickname: displayName, imagePath: profileImage),
|
||||
create: (_) => ProfileInfoState(onion: onion, nickname: displayName, imagePath: profileImage, encrypted: encrypted),
|
||||
),
|
||||
],
|
||||
builder: (context, widget) => AddEditProfileView(),
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <window_size/window_size_plugin.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
window_size
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
77
pubspec.lock
77
pubspec.lock
|
@ -1,13 +1,13 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
archive:
|
||||
ansicolor:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
name: ansicolor
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "2.0.1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -42,7 +42,7 @@ packages:
|
|||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -57,13 +57,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -111,11 +104,6 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_driver:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -131,11 +119,6 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
glob:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -157,13 +140,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
integration_test:
|
||||
dependency: "direct main"
|
||||
injector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: integration_test
|
||||
name: injector
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2+3"
|
||||
version: "2.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -192,6 +175,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
msix:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: msix
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -213,6 +203,13 @@ packages:
|
|||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -372,13 +369,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -392,7 +382,7 @@ packages:
|
|||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
version: "0.4.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -407,20 +397,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -442,6 +418,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.1.2"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
sdks:
|
||||
dart: ">=2.13.0 <3.0.0"
|
||||
flutter: ">=1.20.0"
|
||||
|
|
|
@ -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.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.0.0+14
|
||||
version: 1.0.0+15
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
@ -37,13 +37,8 @@ dependencies:
|
|||
desktop_notifications: 0.5.0
|
||||
|
||||
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:
|
||||
sdk: flutter
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
msix: ^2.1.3
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <window_size/window_size_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
WindowSizePluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("WindowSizePlugin"));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
window_size
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
Binary file not shown.
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
|
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
Loading…
Reference in New Issue