diff --git a/.drone.yml b/.drone.yml
index 9a72ab71..f48637a2 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -14,15 +14,16 @@ pipeline:
- QT_DIR=/opt/Qt
- QT_DOCKER='true'
- QT_API=5.13.0
+ - GO111MODULE=on
commands:
- - export GOPATH=$GOPATH:/media/sf_GOPATH1/
- export PATH=$PATH:/home/user/work/bin:/media/sf_GOPATH1/bin
- apt-get -qq update && apt-get --no-install-recommends -qq -y install ca-certificates curl git openssh-client
- - go get -d
+ - go mod download
- $QT_DIR/$QT_API/gcc_64/bin/lrelease ui.pro
- git fetch --tags
- export VERSION=`git describe --tags`
- export BUILDDATE=`date +%G-%m-%d-%H-%M`
+ - go mod vendor
- qtdeploy -ldflags "-X main.buildVer=$VERSION -X main.buildDate=$BUILDDATE" build linux
- cp README.md deploy/linux
- export FILENAME=cwtch-linux-$BUILDDATE.tar.gz
@@ -44,16 +45,18 @@ pipeline:
- QT_API=5.13.0
- ANDROID_NDK_DIR=/home/user/android-ndk-r18b
- ANDROID_SDK_DIR=/home/user/android-sdk-linux
+ - GO111MODULE=on
commands:
- - export GOPATH=$GOPATH:/media/sf_GOPATH1/
- export PATH=$PATH:/home/user/work/bin:/media/sf_GOPATH1/bin
- apt-get -qq update && apt-get --no-install-recommends -qq -y install ca-certificates curl git
- - find -iname 'moc*' | xargs rm
- - find -iname 'rcc*' | xargs rm
- - go get -d
+ - rm -r vendor/
+ - make clean
+ - go mod download
- export VERSION=`git describe --tags`
- export BUILDDATE=`date +%G-%m-%d-%H-%M`
- - qtdeploy -ldflags "-X main.buildVer=$VERSION -X main.buildDate=$BUILDDATE" build android
+ - go mod vendor
+ - qtsetup generate android
+ - qtdeploy -ldflags "-X main.buildVer=$VERSION -X main.buildDate=$BUILDDATE" build android
- cd deploy
- export FILENAME=cwtch-android-$BUILDDATE.apk
- cp android/build-debug.apk $FILENAME
@@ -69,15 +72,16 @@ pipeline:
- QT_DIR=/opt/Qt
- QT_DOCKER='true'
- QT_API=5.13.0
+ - GO111MODULE=on
commands:
- - export GOPATH=$GOPATH:/media/sf_GOPATH1/
- export PATH=$PATH:/home/user/work/bin:/media/sf_GOPATH1/bin
- apt-get -qq update && apt-get --no-install-recommends -qq -y install ca-certificates curl git zip
- - find -iname 'moc*' | xargs rm
- - find -iname 'rcc*' | xargs rm
- - go get -d
+ - rm -r vendor
+ - make clean
+ - go mod download
- export VERSION=`git describe --tags`
- export BUILDDATE=`date +%G-%m-%d-%H-%M`
+ - go mod vendor
- qtdeploy -ldflags "-X main.buildVer=$VERSION -X main.buildDate=$BUILDDATE" build windows
- cp README.md deploy/windows
- cp -r windows/* deploy/windows
diff --git a/Makefile b/Makefile
index 5292ed7a..342345c0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,9 @@
.PHONY: all clean linux android
-all:
-
-default: all
+all: clean linux android
+default: linux
clean:
+ rm -r vendor || true
find -type f -iname "moc*" | xargs rm
find -iname "rcc*" | xargs rm
diff --git a/README.md b/README.md
index 96870753..0979608e 100644
--- a/README.md
+++ b/README.md
@@ -15,11 +15,11 @@ The UI is built using QT so you will need the development libraries and tools fo
This code relies on [therecipe/qt](https://github.com/therecipe/qt) before getting started consult the [Installation](https://github.com/therecipe/qt/wiki/Installation) and [Getting Started](https://github.com/therecipe/qt/wiki/Getting-Started) documentation to get that up and running. It will make building this much easier.
-We are aiming to use Go module support for versioning but it has some issues working with therecipe/qt so we aren't there yet. For now build with GO111MODULE=off using just the GOPATH for dependancies.
+Cwtch UI uses the Go module system for dependancies
## Linux
- go get -d
+ go mod vendor
qtdeploy build linux
./deploy/linux/ui -local -debug 2>&1 | grep -v 'Detected anchors on an item that is managed by a layout.'
@@ -33,7 +33,7 @@ The grep statement filters out some QML noise.
We supply an arm-pie version of tor in `android/libs/armeabi-v7a` with the name `libtor.so`
- go get -d
+ go mod vendor
qtdeploy -docker build android
adb install deploy/android/build-debug.apk
@@ -52,7 +52,7 @@ We supply an arm-pie version of tor in `android/libs/armeabi-v7a` with the name
If all that is done, then check out cwtch.im/ui
- go get -d
+ go mod vendor
qtdeploy
deploy/windows/ui
diff --git a/go.mod b/go.mod
index aff5778e..7630ee7a 100644
--- a/go.mod
+++ b/go.mod
@@ -3,14 +3,13 @@ module cwtch.im/ui
go 1.12
require (
- cwtch.im/cwtch v0.3.0
- git.openprivacy.ca/openprivacy/libricochet-go v1.0.6
+ cwtch.im/cwtch v0.3.8
+ git.openprivacy.ca/openprivacy/libricochet-go v1.0.8
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.4.0 // indirect
- github.com/therecipe/qt v0.0.0-20191221221430-5e239f03fa53
- github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200103041036-2b818d970888 // indirect
- github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20191221221430-5e239f03fa53 // indirect
+ github.com/therecipe/qt v0.0.0-20191101232336-18864661ae4f
+ github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20191002095216-73192f6811d0 // indirect; Required - do not delete or `go tidy` away
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
diff --git a/go.sum b/go.sum
index a4280120..c623810b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,10 @@
-cwtch.im/cwtch v0.3.0 h1:RFZyc2m9BowFNdngBs7GcQE41w75jMp3Ku5zEE92v9I=
-cwtch.im/cwtch v0.3.0/go.mod h1:8tmtp3c7fccWw9H7s9u6E8GD2PKI3ar21i0fjN8pzd0=
-cwtch.im/tapir v0.1.10 h1:V+TkmwXNd6gySZqlVw468wMYEkmDwMSyvhkkpOfUw7w=
-cwtch.im/tapir v0.1.10/go.mod h1:EuRYdVrwijeaGBQ4OijDDRHf7R2MDSypqHkSl5DxI34=
+cwtch.im/cwtch v0.3.8 h1:QxuDu+sH5VIcLQZGGfah3zuseq02Iyqhm7O2+ATtA9M=
+cwtch.im/cwtch v0.3.8/go.mod h1:/CAGNdgidvJ0sOfsWeU2hxlYCXv8usf6kspsfhG8gtQ=
+cwtch.im/tapir v0.1.14 h1:lg+reZNT998l++4Q4RQBLXYv3ukqWffhI0Wed9RSjuA=
+cwtch.im/tapir v0.1.14/go.mod h1:QwERb982YIes9UOxDqIthm1HZ1xy0YQetD2+XxDbg9Y=
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4/go.mod h1:yMSG1gBaP4f1U+RMZXN85d29D39OK5s8aTpyVRoH5FY=
-git.openprivacy.ca/openprivacy/libricochet-go v1.0.6 h1:5o4K2qn3otEE1InC5v5CzU0yL7Wl7DhVp4s8H3K6mXY=
-git.openprivacy.ca/openprivacy/libricochet-go v1.0.6/go.mod h1:yMSG1gBaP4f1U+RMZXN85d29D39OK5s8aTpyVRoH5FY=
+git.openprivacy.ca/openprivacy/libricochet-go v1.0.8 h1:HVoyxfivFaEtkfO5K3piD6oq6MQB1qGF5IB2EYXeCW8=
+git.openprivacy.ca/openprivacy/libricochet-go v1.0.8/go.mod h1:6I+vO9Aagv3/yUWv+e7A57H8tgXgR67FCjfSj9Pp970=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/c-bata/go-prompt v0.2.3/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
@@ -19,6 +19,10 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f h1:KMlcu9X58lhTA/KrfX8Bi1LQSO4pzoVjTiL3h4Jk+Zk=
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
+github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
+github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
+github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -30,6 +34,8 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
+github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
+github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -43,17 +49,11 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/struCoder/pidusage v0.1.2/go.mod h1:pWBlW3YuSwRl6h7R5KbvA4N8oOqe9LjaKW5CwT1SPjI=
-github.com/therecipe/qt v0.0.0-20190824160953-615e084bab56 h1:CAFR/rHptsl8gEP6igtp6VbuQpPALEJ/B+gl9QkyFXU=
-github.com/therecipe/qt v0.0.0-20190824160953-615e084bab56/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
-github.com/therecipe/qt v0.0.0-20191221221430-5e239f03fa53 h1:vmHLq7TFJ0OQLZzJF0mnCQW6o7NKV319X4F9ImMcLv4=
-github.com/therecipe/qt v0.0.0-20191221221430-5e239f03fa53/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
-github.com/therecipe/qt v0.0.0-20200103041036-2b818d970888 h1:kwDtZGIbjPGYzvs4Dk/4O4E2nnJugQkccLyfFUHpHk0=
-github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200103041036-2b818d970888 h1:GnH3hKsPT8vfSw6LQ6+gHoYdJw7Zd5bifdXzP/Z1tus=
-github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200103041036-2b818d970888/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
-github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20191221221430-5e239f03fa53 h1:nnH71oC0mqkEFEZT8hxykAV+7/yQXial8gbJuxNQNdY=
-github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20191221221430-5e239f03fa53/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
-github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200103041036-2b818d970888 h1:4tZ5WKqMm1U6iOM3kaRvD+mndR5flF61YMXr7gxQ9TU=
-github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200103041036-2b818d970888/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
+github.com/therecipe/qt v0.0.0-20191101232336-18864661ae4f h1:06ICDSmDOBUC9jwgv44ngvyHzwudJNLa5H+rbCyDFRY=
+github.com/therecipe/qt v0.0.0-20191101232336-18864661ae4f/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
+github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20191002095216-73192f6811d0/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
+go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
@@ -69,6 +69,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -78,7 +79,6 @@ golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190420181800-aa740d480789 h1:FF0rjo15h51+N6642mf5S3QuplmKo2aCrJUYkHTx85s=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
diff --git a/go/characters/appEventListener.go b/go/characters/appEventListener.go
deleted file mode 100644
index 0c328d90..00000000
--- a/go/characters/appEventListener.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package characters
-
-import (
- "cwtch.im/cwtch/app/plugins"
- "cwtch.im/cwtch/event"
- "cwtch.im/ui/go/constants"
- "cwtch.im/ui/go/cwutil"
- "cwtch.im/ui/go/gobjects"
- "cwtch.im/ui/go/gothings"
- "cwtch.im/ui/go/the"
- "git.openprivacy.ca/openprivacy/libricochet-go/log"
- "os"
- "strconv"
-)
-
-func AppEventListener(gcd *gothings.GrandCentralDispatcher, subscribed chan bool) {
- q := event.NewQueue()
- the.AppBus.Subscribe(event.NewPeer, q)
- the.AppBus.Subscribe(event.PeerError, q)
- the.AppBus.Subscribe(event.AppError, q)
- the.AppBus.Subscribe(event.ACNStatus, q)
- the.AppBus.Subscribe(event.ReloadDone, q)
- subscribed <- true
-
- for {
- e := q.Next()
-
- switch e.EventType {
- case event.ACNStatus:
- progStr := e.Data[event.Progreess]
- percent, _ := strconv.Atoi(progStr)
- message := e.Data[event.Status]
- var statuscode int
- if percent >= 0 && percent <= 25 {
- statuscode = 1
- message = "Connecting to network"
- } else if percent < 100 {
- statuscode = 2
- message = "Establishng Tor circut"
- } else if percent == 100 {
- statuscode = 3
- message = "tor appears to be running just fine!"
- } else {
- statuscode = 0
- message = "can't find tor. is it running? is the controlport configured?"
- }
-
- gcd.TorStatus(statuscode, message)
- case event.PeerError:
- // current only case
- log.Errorf("couldn't load profiles: %v", e.Data[event.Error])
- os.Exit(1)
-
- case event.AppError:
-
- if e.Data[event.Error] == event.AppErrLoaded0 {
- // TODO: only an error if other profiles are not loaded
- log.Infoln("couldn't load your config file. attempting to create one now")
-
- the.CwtchApp.CreatePeer("alice", the.AppPassword)
- }
-
- case event.ReloadDone:
- if the.Peer == nil {
- the.CwtchApp.LoadProfiles(the.AppPassword)
- }
- case event.NewPeer:
- if the.Peer != nil {
- continue
- }
- onion := e.Data[event.Identity]
-
- the.CwtchApp.AddPeerPlugin(onion, plugins.CONTACTRETRY)
-
- the.Peer = the.CwtchApp.GetPeer(onion)
- the.EventBus = the.CwtchApp.GetEventBus(onion)
-
- incSubscribed := make(chan bool)
- go IncomingListener(&gcd.UIState, incSubscribed)
- <-incSubscribed
-
- gcd.UpdateMyProfile(the.Peer.GetProfile().Name, the.Peer.GetProfile().Onion, cwutil.RandomProfileImage(the.Peer.GetProfile().Onion))
-
- contacts := the.Peer.GetContacts()
- for i := range contacts {
- contact, _ := the.Peer.GetProfile().GetContact(contacts[i])
- displayName, _ := contact.GetAttribute("nick")
-
- gcd.UIState.AddContact(&gobjects.Contact{
- Handle: contacts[i],
- DisplayName: displayName,
- Image: cwutil.RandomProfileImage(contacts[i]),
- Trusted: contact.Trusted,
- Blocked: contact.Blocked,
- Loading: false,
- })
- }
-
- groups := the.Peer.GetGroups()
- for i := range groups {
- group := the.Peer.GetGroup(groups[i])
- nick, exists := group.GetAttribute("nick")
- if !exists {
- nick = group.GroupID[:12]
- }
- // Only join servers for active and explicitly accepted groups.
- gcd.UIState.AddContact(&gobjects.Contact{
- Handle: group.GroupID,
- DisplayName: nick,
- Image: cwutil.RandomGroupImage(group.GroupID),
- Server: group.GroupServer,
- Trusted: group.Accepted,
- Loading: false,
- })
- }
-
- if e.Data[event.Status] != "running" {
- the.CwtchApp.LaunchPeers()
- }
-
- // load ui preferences
- gcd.RequestSettings()
- locale, exists := the.Peer.GetProfile().GetAttribute(constants.LocaleSetting)
- if exists {
- gcd.SetLocale_helper(locale)
- }
-
- blockUnkownPeers, exists := the.Peer.GetProfile().GetAttribute(constants.BlockUnknownPeersSetting)
- if exists && blockUnkownPeers == "true" {
- the.EventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{}))
- }
- }
- }
-
-}
diff --git a/go/characters/incominglistener.go b/go/characters/incominglistener.go
deleted file mode 100644
index c2f6697d..00000000
--- a/go/characters/incominglistener.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package characters
-
-import (
- "cwtch.im/cwtch/event"
- "cwtch.im/cwtch/protocol/connections"
- "cwtch.im/ui/go/cwutil"
- "cwtch.im/ui/go/gobjects"
- "cwtch.im/ui/go/gothings"
- "cwtch.im/ui/go/the"
- "git.openprivacy.ca/openprivacy/libricochet-go/log"
- "time"
-)
-
-func IncomingListener(uiState *gothings.InterfaceState, subscribed chan bool) {
- q := event.NewQueue()
- the.EventBus.Subscribe(event.NewMessageFromPeer, q)
- the.EventBus.Subscribe(event.PeerAcknowledgement, q)
- the.EventBus.Subscribe(event.NewMessageFromGroup, q)
- the.EventBus.Subscribe(event.NewGroupInvite, q)
- the.EventBus.Subscribe(event.SendMessageToGroupError, q)
- the.EventBus.Subscribe(event.SendMessageToPeerError, q)
- the.EventBus.Subscribe(event.ServerStateChange, q)
- the.EventBus.Subscribe(event.PeerStateChange, q)
- subscribed <- true
-
- for {
- e := q.Next()
-
- switch e.EventType {
- case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
- ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampReceived])
- uiState.AddMessage(&gobjects.Message{
- Handle: e.Data[event.RemotePeer],
- From: e.Data[event.RemotePeer],
- Message: e.Data[event.Data],
- Image: cwutil.RandomProfileImage(e.Data[event.RemotePeer]),
- Timestamp: ts,
- })
- if the.Peer.GetContact(e.Data[event.RemotePeer]) == nil {
- the.Peer.AddContact(e.Data[event.RemotePeer], e.Data[event.RemotePeer], false)
- }
-
- case event.PeerAcknowledgement:
- ackI, ok := the.AcknowledgementIDs.Load(e.Data[event.EventID])
- if ok {
- ack := ackI.(*the.AckId)
- if ack.Peer == e.Data[event.RemotePeer] {
- ack.Ack = true
- uiState.Acknowledge(e.Data[event.EventID])
- continue
- }
- }
- log.Debugf("Received Ack ID for unknown message or peer: %v", e)
- case event.NewMessageFromGroup: //event.TimestampReceived, event.TimestampSent, event.Data, event.GroupID, event.RemotePeer
- var name string
- var exists bool
- ctc := the.Peer.GetContact(e.Data[event.RemotePeer])
- if ctc != nil {
- name, exists = ctc.GetAttribute("nick")
- if !exists || name == "" {
- name = e.Data[event.RemotePeer]
- }
- } else {
- name = e.Data[event.RemotePeer]
- }
-
- ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampSent])
- uiState.AddMessage(&gobjects.Message{
- MessageID: e.Data[event.Signature],
- Handle: e.Data[event.GroupID],
- From: e.Data[event.RemotePeer],
- Message: e.Data[event.Data],
- Image: cwutil.RandomProfileImage(e.Data[event.RemotePeer]),
- FromMe: e.Data[event.RemotePeer] == the.Peer.GetProfile().Onion,
- Timestamp: ts,
- Acknowledged: true,
- DisplayName: name,
- })
- case event.NewGroupInvite:
- gid, err := the.Peer.GetProfile().ProcessInvite(e.Data[event.GroupInvite], e.Data[event.RemotePeer])
- group := the.Peer.GetGroup(gid)
- if err == nil && group != nil {
- uiState.AddContact(&gobjects.Contact{
- Handle: gid,
- DisplayName: gid,
- Image: cwutil.RandomGroupImage(gid),
- Server: group.GroupServer,
- Trusted: false,
- Loading: false,
- })
- }
- case event.SendMessageToGroupError:
- uiState.AddSendMessageError(e.Data[event.GroupServer], e.Data[event.Signature], e.Data[event.Error])
- case event.SendMessageToPeerError:
- uiState.AddSendMessageError(e.Data[event.RemotePeer], e.Data[event.Signature], e.Data[event.Error])
- case event.PeerStateChange:
- cxnState := connections.ConnectionStateToType[e.Data[event.ConnectionState]]
-
- // if it's not in the.Peer it's new. Only add once Authed
- _, exists := the.Peer.GetProfile().Contacts[e.Data[event.RemotePeer]]
- if !exists {
- // Contact does not exist, we will add them but we won't know who they are until they are authenticated
- // So if we get any other state from an unknown contact we do nothing
- // (the next exists check will fail)
- if cxnState == connections.AUTHENTICATED {
- the.Peer.AddContact(e.Data[event.RemotePeer], e.Data[event.RemotePeer], false)
- }
- }
-
- // if it's in the.Peer its either existing and needs an update or not in the UI and needs to be added
- if contact, exists := the.Peer.GetProfile().Contacts[e.Data[event.RemotePeer]]; exists {
- contact.State = e.Data[event.ConnectionState]
- uiContact := uiState.GetContact(contact.Onion)
- if uiContact != nil {
- if uiContact.Status != int(cxnState) {
- uiContact.Status = int(cxnState)
- uiState.UpdateContact(contact.Onion)
- }
- } else {
- uiState.AddContact(&gobjects.Contact{
- e.Data[event.RemotePeer],
- e.Data[event.RemotePeer],
- cwutil.RandomProfileImage(e.Data[event.RemotePeer]),
- "",
- 0,
- int(cxnState),
- false,
- false,
- false,
- })
- }
- }
- case event.ServerStateChange:
- serverOnion := e.Data[event.GroupServer]
- state := connections.ConnectionStateToType[e.Data[event.ConnectionState]]
- groups := the.Peer.GetGroups()
- for _, groupID := range groups {
- group := the.Peer.GetGroup(groupID)
- group.State = e.Data[event.ConnectionState]
- if group != nil && group.GroupServer == serverOnion {
- uiState.GetContact(group.GroupID).Status = int(state)
- if state == connections.AUTHENTICATED {
- uiState.GetContact(group.GroupID).Loading = true
- } else {
- uiState.GetContact(group.GroupID).Loading = false
- }
- uiState.UpdateContact(group.GroupID)
- } else {
- log.Errorf("found group that is nil :/")
- }
- }
- }
- }
-}
diff --git a/go/constants/attributes.go b/go/constants/attributes.go
new file mode 100644
index 00000000..7471c265
--- /dev/null
+++ b/go/constants/attributes.go
@@ -0,0 +1,9 @@
+package constants
+
+const Nick = "nick"
+const LastRead = "last-read"
+const Picture = "picture"
+const DefaultPassword = "default-password"
+
+const ProfileTypeV1DefaultPassword = "v1-defaultPassword"
+const ProfileTypeV1Password = "v1-userPassword"
diff --git a/go/gobjects/contact.go b/go/gobjects/contact.go
deleted file mode 100644
index c038c1de..00000000
--- a/go/gobjects/contact.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package gobjects
-
-type Contact struct {
- Handle string
- DisplayName string
- Image string
- Server string
- Badge int
- Status int
- Trusted bool
- Blocked bool
- Loading bool
-}
diff --git a/go/gobjects/letter.go b/go/gobjects/letter.go
deleted file mode 100644
index fc35c26f..00000000
--- a/go/gobjects/letter.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package gobjects
-
-// a Letter is a very simple message object passed to us from the UI
-type Letter struct {
- To, Message string
- MID string
-}
diff --git a/go/gobjects/message.go b/go/gobjects/message.go
deleted file mode 100644
index 01284ffb..00000000
--- a/go/gobjects/message.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package gobjects
-
-import "time"
-
-type Message struct {
- Handle string
- From string
- DisplayName string
- Message string
- Image string
- FromMe bool
- MessageID string
- Timestamp time.Time
- Acknowledged bool
- Error bool
-}
diff --git a/go/gothings/uistate.go b/go/gothings/uistate.go
deleted file mode 100644
index 8f55c800..00000000
--- a/go/gothings/uistate.go
+++ /dev/null
@@ -1,195 +0,0 @@
-package gothings
-
-import (
- "cwtch.im/ui/go/constants"
- "cwtch.im/ui/go/cwutil"
- "cwtch.im/ui/go/gobjects"
- "cwtch.im/ui/go/the"
- "git.openprivacy.ca/openprivacy/libricochet-go/log"
- "runtime/debug"
- "sync"
- "time"
-)
-
-type InterfaceState struct {
- parentGcd *GrandCentralDispatcher
- contacts sync.Map // string : *gobjects.Contact
- messages sync.Map
-}
-
-func NewUIState(gcd *GrandCentralDispatcher) (uis InterfaceState) {
- uis = InterfaceState{gcd, sync.Map{}, sync.Map{}}
- return
-}
-
-func (this *InterfaceState) Acknowledge(mID string) {
- this.parentGcd.Acknowledged(mID)
-}
-
-func (this *InterfaceState) AddContact(c *gobjects.Contact) {
- if len(c.Handle) == 32 { // ADD GROUP
- if _, found := this.contacts.Load(c.Handle); !found {
- this.parentGcd.AddContact(c.Handle, c.DisplayName, c.Image, c.Server, c.Badge, c.Status, c.Trusted, c.Blocked, c.Loading)
- this.contacts.Store(c.Handle, c)
- }
- return
- } else if len(c.Handle) != 56 {
- log.Errorf("sorry, unable to handle AddContact(%v)", c.Handle)
- debug.PrintStack()
- return
- }
-
- if _, found := this.contacts.Load(c.Handle); !found {
- this.contacts.Store(c.Handle, c)
- this.parentGcd.AddContact(c.Handle, c.DisplayName, c.Image, c.Server, c.Badge, c.Status, c.Trusted, c.Blocked, false)
- if the.Peer.GetContact(c.Handle) == nil {
- the.Peer.AddContact(c.DisplayName, c.Handle, c.Trusted)
- go the.Peer.PeerWithOnion(c.Handle)
- }
- }
-}
-
-func (this *InterfaceState) DeleteContact(id string) {
- this.contacts.Delete(id)
-}
-
-func (this *InterfaceState) GetContact(handle string) *gobjects.Contact {
- if _, found := this.contacts.Load(handle); !found {
- if len(handle) == 32 {
- group := the.Peer.GetGroup(handle)
- if group != nil {
- nick, exists := group.GetAttribute("nick")
- if !exists {
- nick = group.GroupID[:12]
- }
- this.AddContact(&gobjects.Contact{
- handle,
- nick,
- cwutil.RandomGroupImage(handle),
- group.GroupServer,
- 0,
- 0,
- group.Accepted,
- false,
- false,
- })
- } else {
- log.Errorf("Attempting to add non existent group to ui %v", handle)
- }
- } else {
- contact := the.Peer.GetContact(handle)
- if contact != nil && handle != contact.Onion {
- nick, exists := contact.GetAttribute("name")
- if !exists {
- nick = contact.Onion
- }
- this.AddContact(&gobjects.Contact{
- handle,
- nick,
- cwutil.RandomProfileImage(handle),
- "",
- 0,
- 0,
- false,
- contact.Blocked,
- false,
- })
- } else if contact == nil {
- //log.Errorf("Attempting to add non existent contact to ui %v", handle)
- }
- }
- }
-
- contactIntf, _ := this.contacts.Load(handle)
- if contactIntf == nil {
- return nil
- }
- contact := contactIntf.(*gobjects.Contact)
- return contact
-}
-
-func (this *InterfaceState) AddSendMessageError(peer string, signature string, err string) {
- ackI, ok := the.AcknowledgementIDs.Load(signature)
- if ok {
- ack := ackI.(*the.AckId)
- ack.Error = true
- }
-
- messages, _ := this.messages.Load(peer)
- messageList, _ := messages.([]*gobjects.Message)
-
- for _, message := range messageList {
- if message.MessageID == signature {
- message.Error = true
- }
- }
- log.Debugf("Received Error Sending Message: %v", err)
- // FIXME: Sometimes, for the first Peer message we send our error beats our message to the UI
- time.Sleep(time.Second * 1)
- this.parentGcd.GroupSendError(signature, err)
-}
-
-func (this *InterfaceState) AddMessage(m *gobjects.Message) {
- this.GetContact(m.From)
-
- _, found := this.messages.Load(m.Handle)
- if !found {
- this.messages.Store(m.Handle, make([]*gobjects.Message, 0))
- }
-
- // Ack message sent to group
- this.parentGcd.Acknowledged(m.MessageID)
-
- messages, _ := this.messages.Load(m.Handle)
- messageList, _ := messages.([]*gobjects.Message)
- this.messages.Store(m.Handle, append(messageList, m))
-
- // If we have this group loaded already
- if this.parentGcd.CurrentOpenConversation() == m.Handle {
- // If the message is not from the user then add it, otherwise, just acknowledge.
- if !m.FromMe || !m.Acknowledged {
- this.parentGcd.AppendMessage(m.Handle, m.From, m.DisplayName, m.Message, m.Image, m.MessageID, m.FromMe, m.Timestamp.Format(constants.TIME_FORMAT), m.Acknowledged, m.Error)
- } else {
- this.parentGcd.Acknowledged(m.MessageID)
- }
- } else {
- c := this.GetContact(m.Handle)
- if c != nil {
- c.Badge++
- this.UpdateContact(c.Handle)
- }
- }
-
-}
-
-func (this *InterfaceState) GetMessages(handle string) []*gobjects.Message {
- _, found := this.messages.Load(handle)
- if !found {
- this.messages.Store(handle, make([]*gobjects.Message, 0))
- }
- messages, found := this.messages.Load(handle)
- messageList, _ := messages.([]*gobjects.Message)
- return messageList
-}
-
-func (this *InterfaceState) UpdateContact(handle string) {
- contact := the.Peer.GetContact(handle)
- if contact != nil {
- cif, found := this.contacts.Load(handle)
- if found {
- c := cif.(*gobjects.Contact)
- c.Blocked = contact.Blocked
- this.parentGcd.UpdateContact(c.Handle, c.DisplayName, c.Image, c.Server, c.Badge, c.Status, c.Trusted, c.Blocked, c.Loading)
- }
- } else {
- cif, found := this.contacts.Load(handle)
- if found {
- c := cif.(*gobjects.Contact)
- this.parentGcd.UpdateContact(c.Handle, c.DisplayName, c.Image, c.Server, c.Badge, c.Status, c.Trusted, c.Blocked, c.Loading)
- }
- }
-}
-
-func (this *InterfaceState) UpdateContactAttribute(handle, key, value string) {
- this.parentGcd.UpdateContactAttribute(handle, key, value)
-}
diff --git a/go/handlers/appHandler.go b/go/handlers/appHandler.go
new file mode 100644
index 00000000..a2085a21
--- /dev/null
+++ b/go/handlers/appHandler.go
@@ -0,0 +1,123 @@
+package handlers
+
+import (
+ "cwtch.im/cwtch/app"
+ "cwtch.im/cwtch/app/plugins"
+ "cwtch.im/cwtch/event"
+ "cwtch.im/ui/go/constants"
+ "cwtch.im/ui/go/the"
+ "cwtch.im/ui/go/ui"
+ "git.openprivacy.ca/openprivacy/libricochet-go/log"
+ "os"
+ "strconv"
+ "time"
+)
+
+func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool, reloadingAccounts bool) {
+ q := event.NewQueue()
+ the.AppBus.Subscribe(event.NewPeer, q)
+ the.AppBus.Subscribe(event.PeerError, q)
+ the.AppBus.Subscribe(event.AppError, q)
+ the.AppBus.Subscribe(event.ACNStatus, q)
+ the.AppBus.Subscribe(event.NetworkStatus, q)
+ the.AppBus.Subscribe(event.ReloadDone, q)
+ subscribed <- true
+
+ networkOffline := false
+ timeSinceLastSuccess := time.Unix(0, 0)
+
+ gcd.Loaded()
+
+ for {
+ e := q.Next()
+
+ switch e.EventType {
+ case event.NetworkStatus:
+ status := e.Data[event.Status]
+ if status == "Error" && !networkOffline {
+ networkOffline = true
+ // if it has been more that 5 minutes since we received any kind of success, then we should kill tor
+ // anything less that this i.e. transient networking failures, should allow us to reconnect without issue
+ if time.Now().Sub(timeSinceLastSuccess) > (time.Minute * 5) {
+ the.ACN.Restart()
+ }
+ }
+
+ if status == "Success" && networkOffline {
+ timeSinceLastSuccess = time.Now()
+ networkOffline = false
+ }
+
+ case event.ACNStatus:
+ progStr := e.Data[event.Progreess]
+ percent, _ := strconv.Atoi(progStr)
+ message := e.Data[event.Status]
+ var statuscode int
+ if percent >= 0 && percent <= 25 {
+ statuscode = 1
+ message = "Connecting to network"
+ } else if percent < 100 {
+ statuscode = 2
+ message = "Establishing Tor circuit"
+ } else if percent == 100 {
+ statuscode = 3
+ message = "tor appears to be running just fine!"
+ } else {
+ statuscode = 0
+ message = "can't find tor. is it running? is the controlport configured?"
+ }
+
+ gcd.TorStatus(statuscode, message)
+
+ case event.PeerError:
+ // current only case
+ log.Errorf("couldn't load profiles: %v", e.Data[event.Error])
+ os.Exit(1)
+
+ case event.AppError:
+
+ if e.Data[event.Error] == event.AppErrLoaded0 {
+ if reloadingAccounts {
+ reloadingAccounts = false
+ } else {
+ gcd.ErrorLoaded0()
+ }
+ }
+
+ case event.ReloadDone:
+ reloadingAccounts = false
+ if len(the.CwtchApp.ListPeers()) == 0 {
+ the.CwtchApp.LoadProfiles(the.AppPassword)
+ }
+ case event.NewPeer:
+ onion := e.Data[event.Identity]
+ peer := the.CwtchApp.GetPeer(onion)
+
+ if tag, exists := peer.GetAttribute(app.AttributeTag); !exists || tag == "" {
+ peer.SetAttribute(app.AttributeTag, constants.ProfileTypeV1DefaultPassword)
+ }
+
+ log.Infof("NewPeer for %v\n", onion)
+ ui.AddProfile(gcd, onion)
+
+ the.CwtchApp.AddPeerPlugin(onion, plugins.CONNECTIONRETRY)
+ the.CwtchApp.AddPeerPlugin(onion, plugins.NETWORKCHECK)
+
+ incSubscribed := make(chan bool)
+ go PeerHandler(onion, gcd.GetUiManager(peer.GetProfile().Onion), incSubscribed)
+ <-incSubscribed
+
+ if e.Data[event.Status] != "running" {
+ peer.Listen()
+ peer.StartPeersConnections()
+ peer.StartGroupConnections()
+ }
+
+ blockUnkownPeers, exists := peer.GetProfile().GetAttribute(constants.BlockUnknownPeersSetting)
+ if exists && blockUnkownPeers == "true" {
+ the.EventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{}))
+ }
+ }
+ }
+
+}
diff --git a/go/handlers/peerHandler.go b/go/handlers/peerHandler.go
new file mode 100644
index 00000000..e64e6005
--- /dev/null
+++ b/go/handlers/peerHandler.go
@@ -0,0 +1,124 @@
+package handlers
+
+import (
+ "cwtch.im/cwtch/app"
+ "cwtch.im/cwtch/event"
+ "cwtch.im/cwtch/protocol/connections"
+ "cwtch.im/ui/go/constants"
+ "cwtch.im/ui/go/the"
+ "cwtch.im/ui/go/ui"
+ "git.openprivacy.ca/openprivacy/libricochet-go/log"
+ "time"
+)
+
+func PeerHandler(onion string, uiManager ui.Manager, subscribed chan bool) {
+ peer := the.CwtchApp.GetPeer(onion)
+ eventBus := the.CwtchApp.GetEventBus(onion)
+ q := event.NewQueue()
+ eventBus.Subscribe(event.NewMessageFromPeer, q)
+ eventBus.Subscribe(event.PeerAcknowledgement, q)
+ eventBus.Subscribe(event.NewMessageFromGroup, q)
+ eventBus.Subscribe(event.NewGroupInvite, q)
+ eventBus.Subscribe(event.SendMessageToGroupError, q)
+ eventBus.Subscribe(event.SendMessageToPeerError, q)
+ eventBus.Subscribe(event.ServerStateChange, q)
+ eventBus.Subscribe(event.PeerStateChange, q)
+ eventBus.Subscribe(event.PeerCreated, q)
+ eventBus.Subscribe(event.NetworkStatus, q)
+ eventBus.Subscribe(event.ChangePasswordSuccess, q)
+ eventBus.Subscribe(event.ChangePasswordError, q)
+
+ subscribed <- true
+
+ networkOffline := false
+
+ for {
+ e := q.Next()
+
+ switch e.EventType {
+
+ case event.NetworkStatus:
+ the.AppBus.Publish(*e)
+ if e.Data["Status"] == "Success" && networkOffline {
+ networkOffline = false
+ // TODO we may have to reinitialize the peer
+ } else {
+ networkOffline = true
+ }
+
+ case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
+ ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampReceived])
+ uiManager.AddMessage(e.Data[event.RemotePeer], e.Data[event.RemotePeer], e.Data[event.Data], false, e.EventID, ts, true)
+ if peer.GetContact(e.Data[event.RemotePeer]) == nil {
+ peer.AddContact(e.Data[event.RemotePeer], e.Data[event.RemotePeer], false)
+ }
+
+ case event.PeerAcknowledgement:
+ uiManager.Acknowledge(e.Data[event.EventID])
+
+ case event.NewMessageFromGroup: //event.TimestampReceived, event.TimestampSent, event.Data, event.GroupID, event.RemotePeer
+ ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampSent])
+ uiManager.AddMessage(e.Data[event.GroupID], e.Data[event.RemotePeer], e.Data[event.Data], e.Data[event.RemotePeer] == peer.GetProfile().Onion, e.Data[event.Signature], ts, true)
+ case event.NewGroupInvite:
+ gid, err := peer.GetProfile().ProcessInvite(e.Data[event.GroupInvite], e.Data[event.RemotePeer])
+ group := peer.GetGroup(gid)
+ if err == nil && group != nil {
+ uiManager.AddContact(gid)
+ }
+ case event.PeerCreated:
+ onion := e.Data[event.RemotePeer]
+ uiManager.AddContact(onion)
+ case event.SendMessageToGroupError:
+ uiManager.AddSendMessageError(e.Data[event.GroupServer], e.Data[event.Signature], e.Data[event.Error])
+ case event.SendMessageToPeerError:
+ uiManager.AddSendMessageError(e.Data[event.RemotePeer], e.Data[event.EventID], e.Data[event.Error])
+ case event.PeerStateChange:
+ cxnState := connections.ConnectionStateToType[e.Data[event.ConnectionState]]
+
+ // if it's not in the.PeerHandler it's new. Only add once Authed
+ if _, exists := peer.GetProfile().Contacts[e.Data[event.RemotePeer]]; !exists {
+ // Contact does not exist, we will add them but we won't know who they are until they are authenticated
+ // So if we get any other state from an unknown contact we do nothing
+ // (the next exists check will fail)
+ if cxnState == connections.AUTHENTICATED {
+ peer.AddContact(e.Data[event.RemotePeer], e.Data[event.RemotePeer], false)
+ uiManager.AddContact(e.Data[event.RemotePeer])
+ }
+ }
+
+ // if it's in the.PeerHandler its either existing and needs an update or not in the UI and needs to be added
+ if contact, exists := peer.GetProfile().Contacts[e.Data[event.RemotePeer]]; exists {
+ contact.State = e.Data[event.ConnectionState]
+ uiManager.UpdateContactStatus(contact.Onion, int(cxnState), false)
+
+ }
+ case event.ServerStateChange:
+ serverOnion := e.Data[event.GroupServer]
+ state := connections.ConnectionStateToType[e.Data[event.ConnectionState]]
+ groups := peer.GetGroups()
+ for _, groupID := range groups {
+ group := peer.GetGroup(groupID)
+ if group != nil && group.GroupServer == serverOnion {
+ group.State = e.Data[event.ConnectionState]
+ loading := false
+ if state == connections.AUTHENTICATED {
+ loading = true
+ }
+ uiManager.UpdateContactStatus(group.GroupID, int(state), loading)
+ } else {
+ log.Errorf("found group that is nil :/")
+ }
+ }
+ case event.DeletePeer:
+ log.Infof("PeerHandler got DeletePeer, SHUTTING down!\n")
+ uiManager.ReloadProfiles()
+ return
+ case event.ChangePasswordSuccess:
+ peer.SetAttribute(app.AttributeTag, constants.ProfileTypeV1Password)
+ uiManager.ChangePasswordResponse(false)
+ case event.ChangePasswordError:
+ uiManager.ChangePasswordResponse(true)
+ }
+
+ }
+}
diff --git a/go/the/globals.go b/go/the/globals.go
index 013a76cb..b4813824 100644
--- a/go/the/globals.go
+++ b/go/the/globals.go
@@ -5,7 +5,6 @@ import (
"cwtch.im/cwtch/event"
libPeer "cwtch.im/cwtch/peer"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
- "sync"
)
// Terrible, to be replaced when proper profile/password management comes in ~ 0.2
@@ -19,12 +18,3 @@ var ACN connectivity.ACN
var Peer libPeer.CwtchPeer
var CwtchDir string
var IPCBridge event.IPCBridge
-
-type AckId struct {
- ID string
- Peer string
- Ack bool
- Error bool
-}
-
-var AcknowledgementIDs sync.Map
diff --git a/go/gothings/android/CwtchActivity.go b/go/ui/android/CwtchActivity.go
similarity index 100%
rename from go/gothings/android/CwtchActivity.go
rename to go/ui/android/CwtchActivity.go
diff --git a/go/gothings/gcd.go b/go/ui/gcd.go
similarity index 51%
rename from go/gothings/gcd.go
rename to go/ui/gcd.go
index 9842ac05..40d54d1a 100644
--- a/go/gothings/gcd.go
+++ b/go/ui/gcd.go
@@ -1,12 +1,13 @@
-package gothings
+package ui
import (
+ "cwtch.im/cwtch/app"
"cwtch.im/cwtch/event"
+ "cwtch.im/cwtch/protocol/connections"
"cwtch.im/ui/go/constants"
- "cwtch.im/ui/go/cwutil"
"github.com/therecipe/qt/qml"
+ "sync"
- "cwtch.im/ui/go/gobjects"
"cwtch.im/ui/go/the"
"encoding/base32"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
@@ -18,23 +19,41 @@ import (
type GrandCentralDispatcher struct {
core.QObject
- OutgoingMessages chan gobjects.Letter
- UIState InterfaceState
- QMLEngine *qml.QQmlApplicationEngine
- Translator *core.QTranslator
+ QMLEngine *qml.QQmlApplicationEngine
+ Translator *core.QTranslator
+
+ uIManagers map[string]Manager // profile-onion : Manager
+
+ profileLock sync.Mutex
+ conversationLock sync.Mutex
+
+ m_selectedProfile string
+ m_selectedConversation string
_ string `property:"os"`
- _ string `property:"currentOpenConversation"`
_ float32 `property:"themeScale"`
_ string `property:"version"`
_ string `property:"buildDate"`
_ string `property:"assetPath"`
+ _ string `property:"selectedProfile,auto"`
+ _ string `property:"selectedConversation,auto"`
+
+ // profile management stuff
+ _ func() `signal:"Loaded"`
+ _ func(handle, displayname, image, tag string) `signal:"AddProfile"`
+ _ func() `signal:"ErrorLoaded0"`
+ _ func() `signal:"ResetProfile"`
+ _ func() `signal:"ResetProfileList"`
+ _ func(failed bool) `signal:"ChangePasswordResponse"`
// contact list stuff
- _ func(handle, displayName, image, server string, badge, status int, trusted bool, blocked bool, loading bool) `signal:"AddContact"`
- _ func(handle, displayName, image, server string, badge, status int, trusted bool, blocked bool, loading bool) `signal:"UpdateContact"`
- _ func(handle string) `signal:"RemoveContact"`
- _ func(handle, key, value string) `signal:"UpdateContactAttribute"`
+ _ func(handle, displayName, image, server string, badge, status int, blocked bool, loading bool, lastMsgTime int) `signal:"AddContact"`
+ _ func(handle, displayName string) `signal:"UpdateContactDisplayName"`
+ _ func(handle string, status int, loading bool) `signal:"UpdateContactStatus"`
+ _ func(handle string, blocked bool) `signal:"UpdateContactBlocked"`
+ _ func(handle string) `signal:"IncContactUnreadCount"`
+ _ func(handle string) `signal:"RemoveContact"`
+ _ func(handle, key, value string) `signal:"UpdateContactAttribute"`
// messages pane stuff
_ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts string, ackd bool, error bool) `signal:"AppendMessage"`
@@ -57,14 +76,26 @@ type GrandCentralDispatcher struct {
_ func(onion, nick string, blocked bool) `signal:"SupplyPeerSettings"`
// signals emitted from the ui (written in go, below)
+ // ui
+ _ func() `signal:"onActivate,auto"`
+ _ func(locale string) `signal:"setLocale,auto"`
+ // profile managemenet
+ _ func(onion, nick string) `signal:"updateNick,auto"`
+ _ func(handle string) `signal:"loadProfile,auto"`
+ _ func(nick string, defaultPass bool, password string) `signal:"createProfile,auto"`
+ _ func(password string) `signal:"unlockProfiles,auto"`
+ _ func() `signal:"reloadProfileList,auto"`
+ _ func(onion string) `signal:"deleteProfile,auto"`
+ _ func(onion, currentPassword, newPassword string, defaultPass bool) `signal:"changePassword,auto""`
+ // operating a profile
_ func(message string, mid string) `signal:"sendMessage,auto"`
_ func(onion string) `signal:"blockPeer,auto"`
_ func(onion string) `signal:"unblockPeer,auto"`
_ func(onion string) `signal:"loadMessagesPane,auto"`
_ func(signal string) `signal:"broadcast,auto"` // convenience relay signal
_ func(str string) `signal:"importString,auto"`
+ _ func(str string) `signal:"createContact,auto"`
_ func(str string) `signal:"popup,auto"`
- _ func(nick string) `signal:"updateNick,auto"`
_ func(server, groupName string) `signal:"createGroup,auto"`
_ func(groupID string) `signal:"leaveGroup,auto"`
_ func(groupID string) `signal:"acceptGroup,auto"`
@@ -77,9 +108,85 @@ type GrandCentralDispatcher struct {
_ func(onion, groupID string) `signal:"inviteToGroup,auto"`
_ func(onion, key, nick string) `signal:"setAttribute,auto"`
_ func(onion string) `signal:"deleteContact,auto"`
- _ func(locale string) `signal:"setLocale,auto"`
_ func() `signal:"allowUnknownPeers,auto"`
_ func() `signal:"blockUnknownPeers,auto"`
+
+ _ func() `constructor:"init"`
+}
+
+func (this *GrandCentralDispatcher) init() {
+ this.uIManagers = make(map[string]Manager)
+}
+
+// GetUiManager gets (and creates if required) a ui Manager for the supplied profile id
+func (this *GrandCentralDispatcher) GetUiManager(profile string) Manager {
+ this.profileLock.Lock()
+ defer this.profileLock.Unlock()
+
+ if manager, exists := this.uIManagers[profile]; exists {
+ return manager
+ } else {
+ this.uIManagers[profile] = NewManager(profile, this)
+ return this.uIManagers[profile]
+ }
+}
+
+func (this *GrandCentralDispatcher) selectedProfile() string {
+ this.profileLock.Lock()
+ defer this.profileLock.Unlock()
+
+ return this.m_selectedProfile
+}
+
+func (this *GrandCentralDispatcher) setSelectedProfile(onion string) {
+ this.profileLock.Lock()
+ defer this.profileLock.Unlock()
+
+ this.m_selectedProfile = onion
+}
+
+func (this *GrandCentralDispatcher) selectedProfileChanged(onion string) {
+ this.SelectedProfileChanged(onion)
+}
+
+// DoIfProfile performs a gcd action for a profile IF it is the currently selected profile in the UI
+// otherwise it does nothing. it also locks profile switching for the duration of the action
+func (this *GrandCentralDispatcher) DoIfProfile(profile string, fn func()) {
+ this.profileLock.Lock()
+ defer this.profileLock.Unlock()
+
+ if this.m_selectedProfile == profile {
+ fn()
+ }
+}
+
+func (this *GrandCentralDispatcher) selectedConversation() string {
+ this.conversationLock.Lock()
+ defer this.conversationLock.Unlock()
+
+ return this.m_selectedConversation
+}
+
+func (this *GrandCentralDispatcher) setSelectedConversation(handle string) {
+ this.conversationLock.Lock()
+ defer this.conversationLock.Unlock()
+
+ this.m_selectedConversation = handle
+}
+
+func (this *GrandCentralDispatcher) selectedConversationChanged(handle string) {
+ this.SelectedConversationChanged(handle)
+}
+
+// DoIfConversation performs a gcd action for a conversation IF it is the currently selected conversation in the UI
+// otherwise it does nothing. it also locks conversation switching for the duration of the action
+func (this *GrandCentralDispatcher) DoIfConversation(conversation string, fn func()) {
+ this.conversationLock.Lock()
+ defer this.conversationLock.Unlock()
+
+ if this.m_selectedConversation == conversation {
+ fn()
+ }
}
func (this *GrandCentralDispatcher) sendMessage(message string, mID string) {
@@ -88,72 +195,34 @@ func (this *GrandCentralDispatcher) sendMessage(message string, mID string) {
return
}
- if this.CurrentOpenConversation() == "" {
+ if this.SelectedConversation() == "" {
this.InvokePopup("ui error")
return
}
- if len(this.CurrentOpenConversation()) == 32 { // SEND TO GROUP
- if !the.Peer.GetGroup(this.CurrentOpenConversation()).Accepted {
- err := the.Peer.AcceptInvite(this.CurrentOpenConversation())
+ if isGroup(this.SelectedConversation()) {
+ if !the.Peer.GetGroup(this.SelectedConversation()).Accepted {
+ err := the.Peer.AcceptInvite(this.SelectedConversation())
if err != nil {
log.Errorf("tried to mark a nonexistent group as existed. bad!")
return
}
- c := this.UIState.GetContact(this.CurrentOpenConversation())
- c.Trusted = true
- this.UIState.UpdateContact(c.Handle)
}
var err error
- mID, err = the.Peer.SendMessageToGroupTracked(this.CurrentOpenConversation(), message)
+ mID, err = the.Peer.SendMessageToGroupTracked(this.SelectedConversation(), message)
- this.UIState.AddMessage(&gobjects.Message{
- this.CurrentOpenConversation(),
- "me",
- "",
- message,
- "",
- true,
- mID,
- time.Now(),
- false,
- false,
- })
+ this.GetUiManager(this.selectedProfile()).AddMessage(this.SelectedConversation(), "me", message, true, mID, time.Now(), false)
if err != nil {
this.InvokePopup("failed to send message " + err.Error())
return
}
} else {
-
- // TODO: require explicit invite accept/reject instead of implicitly trusting on send
- if !this.UIState.GetContact(this.CurrentOpenConversation()).Trusted {
- this.UIState.GetContact(this.CurrentOpenConversation()).Trusted = true
- this.UIState.UpdateContact(this.CurrentOpenConversation())
- }
-
- to := this.CurrentOpenConversation()
+ to := this.SelectedConversation()
mID = the.Peer.SendMessageToPeer(to, message)
- this.UIState.AddMessage(&gobjects.Message{
- to,
- "me",
- "",
- message,
- "",
- true,
- mID,
- time.Now(),
- false,
- false,
- })
-
- ackID := new(the.AckId)
- ackID.ID = mID
- ackID.Ack = false
- ackID.Peer = to
- the.AcknowledgementIDs.Store(mID, ackID)
+ this.GetUiManager(this.selectedProfile()).AddMessage(to, "me", message, true, mID, time.Now(), false)
}
}
@@ -167,30 +236,21 @@ func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
return
}
this.ClearMessages()
- this.SetCurrentOpenConversation(handle)
- c := this.UIState.GetContact(handle)
+ this.SetSelectedConversation(handle)
- if c == nil {
- this.UIState.AddContact(&gobjects.Contact{
- handle,
- handle,
- cwutil.RandomProfileImage(handle),
- "",
- 0,
- 0,
- false,
- false,
- false,
- })
- } else {
- c.Badge = 0
- this.UIState.UpdateContact(handle)
- }
-
- if len(handle) == 32 { // LOAD GROUP
+ if isGroup(handle) { // LOAD GROUP
group := the.Peer.GetGroup(handle)
+
+ loading := false
+ state := connections.ConnectionStateToType[group.State]
+ if state == connections.AUTHENTICATED {
+ loading = true
+ }
+ this.UpdateContactStatus(group.GroupID, int(state), loading)
+
tl := group.GetTimeline()
- nick, _ := group.GetAttribute("nick")
+ nick, _ := group.GetAttribute(constants.Nick)
+ updateLastReadTime(group.GroupID)
if nick == "" {
this.SetToolbarTitle(handle)
} else {
@@ -203,24 +263,16 @@ func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
} else {
handle = tl[i].PeerID
}
- var name string
- var exists bool
- ctc := the.Peer.GetContact(tl[i].PeerID)
- if ctc != nil {
- name, exists = ctc.GetAttribute("nick")
- if !exists || name == "" {
- name = tl[i].PeerID
- }
- } else {
- name = tl[i].PeerID
- }
+
+ name := getOrDefault(tl[i].PeerID, constants.Nick, tl[i].PeerID)
+ image := getProfilePic(tl[i].PeerID)
this.PrependMessage(
handle,
tl[i].PeerID,
name,
tl[i].Message,
- cwutil.RandomProfileImage(tl[i].PeerID),
+ image,
string(tl[i].Signature),
tl[i].PeerID == the.Peer.GetProfile().Onion,
tl[i].Timestamp.Format(constants.TIME_FORMAT),
@@ -232,42 +284,43 @@ func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
} // ELSE LOAD CONTACT
contact, _ := the.Peer.GetProfile().GetContact(handle)
+
+ this.UpdateContactStatus(handle, int(connections.ConnectionStateToType[contact.State]), false)
+
+ var nick string
if contact != nil {
- nick, _ := contact.GetAttribute("nick")
+ nick, _ = contact.GetAttribute(constants.Nick)
if nick == "" {
- this.SetToolbarTitle(handle)
- } else {
- this.SetToolbarTitle(nick)
+ nick = handle
}
}
+ updateLastReadTime(contact.Onion)
+ this.SetToolbarTitle(nick)
- messages := this.UIState.GetMessages(handle)
+ peer := the.Peer.GetContact(handle)
+ messages := peer.Timeline.GetMessages()
for i := range messages {
- from := messages[i].From
- if messages[i].FromMe {
+ from := messages[i].PeerID
+ fromMe := messages[i].PeerID == the.Peer.GetProfile().Onion
+ if fromMe {
from = "me"
}
- ackI, ok := the.AcknowledgementIDs.Load(messages[i].MessageID)
- acked := false
- if ok {
- ack := ackI.(*the.AckId)
- acked = ack.Ack
- }
+ displayname := getOrDefault(messages[i].PeerID, constants.Nick, messages[i].PeerID)
+ image := getProfilePic(messages[i].PeerID)
this.AppendMessage(
- messages[i].Handle,
from,
- messages[i].DisplayName,
+ messages[i].PeerID,
+ displayname,
messages[i].Message,
- cwutil.RandomProfileImage(handle),
- messages[i].MessageID,
- messages[i].FromMe,
+ image,
+ string(messages[i].Signature),
+ fromMe,
messages[i].Timestamp.Format(constants.TIME_FORMAT),
- acked,
- messages[i].Error,
+ messages[i].Acknowledged,
+ messages[i].Error != "",
)
-
}
}
@@ -285,25 +338,22 @@ func (this *GrandCentralDispatcher) saveSettings(zoom, locale string) {
return
}
- the.EventBus.Publish(event.NewEvent(event.SetAttribute, map[event.Field]string{
- event.Key: constants.ZoomSetting,
- event.Data: zoom,
- }))
- the.Peer.GetProfile().SetAttribute(constants.ZoomSetting, zoom)
+ the.Peer.SetAttribute(constants.ZoomSetting, zoom)
}
func (this *GrandCentralDispatcher) requestPeerSettings() {
- contact := the.Peer.GetContact(this.CurrentOpenConversation())
+ contact := the.Peer.GetContact(this.SelectedConversation())
if contact == nil {
- log.Errorf("error: requested settings for unknown contact %v?", this.CurrentOpenConversation())
- this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation(), false)
+ log.Errorf("error: requested settings for unknown contact %v?", this.SelectedConversation())
+ this.SupplyPeerSettings(this.SelectedConversation(), this.SelectedConversation(), false)
return
}
- name, exists := contact.GetAttribute("nick")
+ name, exists := contact.GetAttribute(constants.Nick)
if !exists {
- log.Errorf("error: couldn't find contact %v", this.CurrentOpenConversation())
- this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation(), contact.Blocked)
+ log.Errorf("error: couldn't find contact %v", this.SelectedConversation())
+ this.SupplyPeerSettings(this.SelectedConversation(), this.SelectedConversation(), contact.Blocked)
+ this.SupplyPeerSettings(this.SelectedConversation(), this.SelectedConversation(), contact.Blocked)
return
}
@@ -311,23 +361,8 @@ func (this *GrandCentralDispatcher) requestPeerSettings() {
}
func (this *GrandCentralDispatcher) savePeerSettings(onion, nick string) {
- contact := the.Peer.GetContact(onion)
- if contact == nil {
- log.Errorf("error: tried to save settings for unknown peer %v", onion)
- return
- }
-
- contact.SetAttribute("nick", nick)
- the.EventBus.Publish(event.NewEvent(event.SetPeerAttribute, map[event.Field]string{
- event.RemotePeer: onion,
- event.Key: "nick",
- event.Data: nick,
- }))
-
- cif, _ := this.UIState.contacts.Load(onion)
- c := cif.(*gobjects.Contact)
- c.DisplayName = nick
- this.UIState.UpdateContact(onion)
+ the.Peer.SetContactAttribute(onion, constants.Nick, nick)
+ this.UpdateContactDisplayName(onion, nick)
}
func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) {
@@ -338,13 +373,13 @@ func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) {
return
}
- nick, _ := group.GetAttribute("nick")
+ nick, _ := group.GetAttribute(constants.Nick)
invite, _ := the.Peer.ExportGroup(groupID)
contactaddrs := the.Peer.GetContacts()
contactnames := make([]string, len(contactaddrs))
for i, contact := range contactaddrs {
- name, hasname := the.Peer.GetContact(contact).GetAttribute("nick")
+ name, hasname := the.Peer.GetContact(contact).GetAttribute(constants.Nick)
if hasname {
contactnames[i] = name
} else {
@@ -356,24 +391,8 @@ func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) {
}
func (this *GrandCentralDispatcher) saveGroupSettings(groupID, nick string) {
- group := the.Peer.GetGroup(groupID)
-
- if group == nil {
- log.Errorf("couldn't find group %v", groupID)
- return
- }
-
- group.SetAttribute("nick", nick)
- the.EventBus.Publish(event.NewEvent(event.SetGroupAttribute, map[event.Field]string{
- event.GroupID: groupID,
- event.Key: "nick",
- event.Data: nick,
- }))
-
- cif, _ := this.UIState.contacts.Load(groupID)
- c := cif.(*gobjects.Contact)
- c.DisplayName = nick
- this.UIState.UpdateContact(groupID)
+ the.Peer.SetGroupAttribute(groupID, constants.Nick, nick)
+ this.UpdateContactDisplayName(groupID, nick)
}
func (this *GrandCentralDispatcher) broadcast(signal string) {
@@ -382,9 +401,19 @@ func (this *GrandCentralDispatcher) broadcast(signal string) {
log.Debugf("unhandled broadcast signal: %v", signal)
case "ResetMessagePane":
this.ResetMessagePane()
+ case "ResetProfile":
+ this.ResetProfile()
}
}
+func (this *GrandCentralDispatcher) createContact(onion string) {
+ if contact := the.Peer.GetContact(onion); contact != nil {
+ return
+ }
+ the.Peer.AddContact(onion, onion, false)
+ the.Peer.PeerWithOnion(onion)
+}
+
func (this *GrandCentralDispatcher) importString(str string) {
if len(str) < 5 {
log.Debugf("ignoring short string")
@@ -435,7 +464,7 @@ func (this *GrandCentralDispatcher) importString(str string) {
_, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion[:56]))
if err != nil {
log.Debugln(err)
- this.InvokePopup("bad format. missing characters?")
+ this.InvokePopup("bad format. missing handlers?")
return
}
@@ -443,25 +472,26 @@ func (this *GrandCentralDispatcher) importString(str string) {
if checkc != nil {
this.InvokePopup("already have this contact")
return //TODO: bring them to the duplicate
+ } else {
+ the.Peer.AddContact(name, onion, false)
+ the.Peer.PeerWithOnion(onion)
}
- this.UIState.AddContact(&gobjects.Contact{
- Handle: onion,
- DisplayName: name,
- Image: cwutil.RandomProfileImage(onion),
- Trusted: true,
- })
+ this.GetUiManager(this.selectedProfile()).AddContact(onion)
}
func (this *GrandCentralDispatcher) popup(str string) {
this.InvokePopup(str)
}
-func (this *GrandCentralDispatcher) updateNick(nick string) {
- the.Peer.GetProfile().Name = nick
- the.EventBus.Publish(event.NewEvent(event.SetProfileName, map[event.Field]string{
- event.ProfileName: nick,
- }))
+func (this *GrandCentralDispatcher) updateNick(onion, nick string) {
+ peer := the.CwtchApp.GetPeer(onion)
+ if peer != nil {
+ peer.GetProfile().Name = nick
+ the.CwtchApp.GetEventBus(onion).Publish(event.NewEvent(event.SetProfileName, map[event.Field]string{
+ event.ProfileName: nick,
+ }))
+ }
}
func (this *GrandCentralDispatcher) createGroup(server, groupName string) {
@@ -471,21 +501,9 @@ func (this *GrandCentralDispatcher) createGroup(server, groupName string) {
return
}
- this.UIState.AddContact(&gobjects.Contact{
- Handle: groupID,
- DisplayName: groupName,
- Image: cwutil.RandomGroupImage(groupID),
- Server: server,
- Trusted: true,
- })
+ this.GetUiManager(this.selectedProfile()).AddContact(groupID)
- group := the.Peer.GetGroup(groupID)
- group.SetAttribute("nick", groupName)
- the.EventBus.Publish(event.NewEvent(event.SetGroupAttribute, map[event.Field]string{
- event.GroupID: groupID,
- event.Key: "nick",
- event.Data: groupName,
- }))
+ the.Peer.SetGroupAttribute(groupID, constants.Nick, groupName)
the.Peer.JoinServer(server)
}
@@ -495,7 +513,7 @@ func (this *GrandCentralDispatcher) blockPeer(onion string) {
if err != nil {
this.InvokePopup("Error Blocking Peer: " + err.Error())
}
- this.UIState.UpdateContact(onion)
+ this.UpdateContactBlocked(onion, true)
}
func (this *GrandCentralDispatcher) unblockPeer(onion string) {
@@ -504,7 +522,7 @@ func (this *GrandCentralDispatcher) unblockPeer(onion string) {
this.InvokePopup("Error Unblocking Peer: " + err.Error())
}
the.Peer.PeerWithOnion(onion)
- this.UIState.UpdateContact(onion)
+ this.UpdateContactBlocked(onion, false)
}
func (this *GrandCentralDispatcher) inviteToGroup(onion, groupID string) {
@@ -527,54 +545,28 @@ func (this *GrandCentralDispatcher) deleteContact(onion string) {
func (this *GrandCentralDispatcher) acceptGroup(groupID string) {
if the.Peer.GetGroup(groupID) != nil {
the.Peer.AcceptInvite(groupID)
- this.UIState.UpdateContact(groupID)
}
}
func (this *GrandCentralDispatcher) setAttribute(onion, key, value string) {
- pp, _ := the.Peer.GetProfile().GetContact(onion)
- if pp != nil {
- pp.SetAttribute(key, value)
- the.EventBus.Publish(event.NewEvent(event.SetPeerAttribute, map[event.Field]string{
- event.RemotePeer: onion,
- event.Key: key,
- event.Data: value,
- }))
- this.UIState.UpdateContactAttribute(onion, key, value)
- }
+ the.Peer.SetContactAttribute(onion, key, value)
+ this.GetUiManager(this.selectedProfile()).UpdateContactAttribute(onion, key, value)
}
func (this *GrandCentralDispatcher) blockUnknownPeers() {
-
- // Save this setting
- the.EventBus.Publish(event.NewEvent(event.SetAttribute, map[event.Field]string{
- event.Key: constants.BlockUnknownPeersSetting,
- event.Data: "true",
- }))
-
- the.Peer.GetProfile().SetAttribute(constants.BlockUnknownPeersSetting, "true")
+ the.Peer.SetAttribute(constants.BlockUnknownPeersSetting, "true")
the.EventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{}))
}
func (this *GrandCentralDispatcher) allowUnknownPeers() {
-
- // Save this setting
- the.EventBus.Publish(event.NewEvent(event.SetAttribute, map[event.Field]string{
- event.Key: constants.BlockUnknownPeersSetting,
- event.Data: "false",
- }))
-
- the.Peer.GetProfile().SetAttribute(constants.BlockUnknownPeersSetting, "false")
+ the.Peer.SetAttribute(constants.BlockUnknownPeersSetting, "false")
the.EventBus.Publish(event.NewEvent(event.AllowUnknownPeers, map[event.Field]string{}))
}
func (this *GrandCentralDispatcher) setLocale(locale string) {
this.SetLocale_helper(locale)
- the.EventBus.Publish(event.NewEvent(event.SetAttribute, map[event.Field]string{
- event.Key: constants.LocaleSetting,
- event.Data: locale,
- }))
+ the.Peer.SetAttribute(constants.LocaleSetting, locale)
zoom, _ := the.Peer.GetProfile().GetAttribute(constants.ZoomSetting)
blockunkownpeers, _ := the.Peer.GetProfile().GetAttribute(constants.BlockUnknownPeersSetting)
@@ -582,6 +574,13 @@ func (this *GrandCentralDispatcher) setLocale(locale string) {
}
+func (this *GrandCentralDispatcher) onActivate() {
+ log.Debugln("onActivate")
+ if the.CwtchApp != nil {
+ the.CwtchApp.QueryACNStatus()
+ }
+}
+
func (this *GrandCentralDispatcher) SetLocale_helper(locale string) {
core.QCoreApplication_RemoveTranslator(this.Translator)
this.Translator = core.NewQTranslator(nil)
@@ -589,3 +588,72 @@ func (this *GrandCentralDispatcher) SetLocale_helper(locale string) {
core.QCoreApplication_InstallTranslator(this.Translator)
this.QMLEngine.Retranslate()
}
+
+func (this *GrandCentralDispatcher) unlockProfiles(password string) {
+ the.CwtchApp.LoadProfiles(password)
+}
+
+func (this *GrandCentralDispatcher) loadProfile(onion string) {
+ the.Peer = the.CwtchApp.GetPeer(onion)
+ the.EventBus = the.CwtchApp.GetEventBus(onion)
+
+ pic, exists := the.Peer.GetAttribute(constants.Picture)
+ if !exists {
+ pic = RandomProfileImage(the.Peer.GetProfile().Onion)
+ the.Peer.SetAttribute(constants.Picture, pic)
+ }
+ this.UpdateMyProfile(the.Peer.GetProfile().Name, the.Peer.GetProfile().Onion, pic)
+
+ contacts := the.Peer.GetContacts()
+ for i := range contacts {
+ this.GetUiManager(this.selectedProfile()).AddContact(contacts[i])
+ }
+
+ groups := the.Peer.GetGroups()
+ for i := range groups {
+ // Only join servers for active and explicitly accepted groups.
+ this.GetUiManager(this.selectedProfile()).AddContact(groups[i])
+ }
+
+ // load ui preferences
+ this.RequestSettings()
+ locale, exists := the.Peer.GetProfile().GetAttribute(constants.LocaleSetting)
+ if exists {
+ this.SetLocale_helper(locale)
+ }
+}
+
+func (this *GrandCentralDispatcher) createProfile(nick string, defaultPass bool, password string) {
+ if defaultPass {
+ the.CwtchApp.CreateTaggedPeer(nick, the.AppPassword, constants.ProfileTypeV1DefaultPassword)
+ } else {
+ the.CwtchApp.CreateTaggedPeer(nick, password, constants.ProfileTypeV1Password)
+ }
+}
+
+func (this *GrandCentralDispatcher) changePassword(onion, currentPassword, newPassword string, defaultPass bool) {
+ tag, _ := the.CwtchApp.GetPeer(onion).GetAttribute(app.AttributeTag)
+
+ if tag == constants.ProfileTypeV1DefaultPassword {
+ currentPassword = the.AppPassword
+ }
+
+ if defaultPass {
+ newPassword = the.AppPassword
+ }
+
+ the.CwtchApp.ChangePeerPassword(onion, currentPassword, newPassword)
+}
+
+func (this *GrandCentralDispatcher) reloadProfileList() {
+ this.ResetProfileList()
+
+ for onion, _ := range the.CwtchApp.ListPeers() {
+ AddProfile(this, onion)
+ }
+}
+
+func (this *GrandCentralDispatcher) deleteProfile(onion string) {
+ log.Infof("deleteProfile %v\n", onion)
+ the.CwtchApp.DeletePeer(onion)
+}
diff --git a/go/ui/manager.go b/go/ui/manager.go
new file mode 100644
index 00000000..89ebc9ba
--- /dev/null
+++ b/go/ui/manager.go
@@ -0,0 +1,281 @@
+package ui
+
+import (
+ "cwtch.im/cwtch/app"
+ "cwtch.im/cwtch/model"
+ "cwtch.im/cwtch/protocol/connections"
+ "cwtch.im/ui/go/constants"
+ "cwtch.im/ui/go/the"
+ "git.openprivacy.ca/openprivacy/libricochet-go/log"
+ "runtime/debug"
+ "time"
+)
+
+func isGroup(id string) bool {
+ return len(id) == 32
+}
+
+func isPeer(id string) bool {
+ return len(id) == 56
+}
+
+func getOrDefault(id, key, defaultVal string) string {
+ var val string
+ var ok bool
+ if isGroup(id) {
+ val, ok = the.Peer.GetGroupAttribute(id, key)
+ } else {
+ val, ok = the.Peer.GetContactAttribute(id, key)
+ }
+ if ok {
+ return val
+ } else {
+ return defaultVal
+ }
+}
+
+func getWithSetDefault(id string, key, defaultVal string) string {
+ var val string
+ var ok bool
+ if isGroup(id) {
+ val, ok = the.Peer.GetGroupAttribute(id, key)
+ } else {
+ val, ok = the.Peer.GetContactAttribute(id, key)
+ }
+ if !ok {
+ val = defaultVal
+ if isGroup(id) {
+ the.Peer.SetGroupAttribute(id, key, defaultVal)
+ } else {
+ the.Peer.SetContactAttribute(id, key, defaultVal)
+ }
+ }
+ return val
+}
+
+// initLastReadTime checks and gets the Attributable's LastRead time or sets it to now
+func initLastReadTime(id string) time.Time {
+ nowStr, _ := time.Now().MarshalText()
+ lastReadStr := getWithSetDefault(id, constants.LastRead, string(nowStr))
+ var lastRead time.Time
+ lastRead.UnmarshalText([]byte(lastReadStr))
+ return lastRead
+}
+
+func initProfilePicture(id string) string {
+ if isGroup(id) {
+ return getWithSetDefault(id, constants.Picture, RandomGroupImage(id))
+ } else {
+ return getWithSetDefault(id, constants.Picture, RandomProfileImage(id))
+ }
+}
+
+// getProfilePic supplies a profile pic to use. In groups we may not have a contact so it will generate one
+func getProfilePic(id string) string {
+ if isGroup(id) {
+ if pic, exists := the.Peer.GetGroupAttribute(id, constants.Picture); !exists {
+ return RandomGroupImage(id)
+ } else {
+ return pic
+ }
+ } else {
+ if pic, exists := the.Peer.GetContactAttribute(id, constants.Picture); !exists {
+ return RandomProfileImage(id)
+ } else {
+ return pic
+ }
+ }
+}
+
+func updateLastReadTime(id string) {
+ lastRead, _ := time.Now().MarshalText()
+ if isGroup(id) {
+ the.Peer.SetGroupAttribute(id, constants.LastRead, string(lastRead))
+ } else {
+ the.Peer.SetContactAttribute(id, constants.LastRead, string(lastRead))
+ }
+}
+
+func countUnread(messages []model.Message, lastRead time.Time) int {
+ count := 0
+ for i := len(messages) - 1; i >= 0; i-- {
+ if messages[i].Timestamp.After(lastRead) || messages[i].Timestamp.Equal(lastRead) {
+ count++
+ } else {
+ break
+ }
+ }
+ return count
+}
+
+// AddProfile adds a new profile to the UI
+func AddProfile(gcd *GrandCentralDispatcher, handle string) {
+ peer := the.CwtchApp.GetPeer(handle)
+ if peer != nil {
+ nick := peer.GetProfile().Name
+ if nick == "" {
+ nick = handle
+ peer.SetAttribute(constants.Nick, nick)
+ }
+
+ pic, ok := peer.GetAttribute(constants.Picture)
+ if !ok {
+ pic = RandomProfileImage(handle)
+ peer.SetAttribute(constants.Picture, pic)
+ }
+
+ tag, _ := peer.GetAttribute(app.AttributeTag)
+ log.Infof("AddProfile %v %v %v %v\n", handle, nick, pic, tag)
+ gcd.AddProfile(handle, nick, pic, tag)
+ }
+}
+
+type manager struct {
+ gcd *GrandCentralDispatcher
+ profile string
+}
+
+// Manager is a middleware helper for entities like peer event listeners wishing to trigger ui changes (via the gcd)
+// each manager is for one profile/peer
+// manager takes minimal arguments and builds the full struct of data (usually pulled from a cwtch peer) required to call the GCD to perform the ui action
+// manager also performs call filtering based on UI state: users of manager can safely always call it on events and not have to worry about weather the relevant ui is active
+// ie: you can always safely call AddMessage even if in the ui a different profile is selected. manager will check with gcd, and if the correct conditions are not met, it will not call on gcd to update the ui incorrectly
+type Manager interface {
+ Acknowledge(mID string)
+ AddContact(Handle string)
+ AddSendMessageError(peer string, signature string, err string)
+ AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool)
+
+ ReloadProfiles()
+
+ UpdateContactDisplayName(handle string, name string)
+ UpdateContactStatus(handle string, status int, loading bool)
+ UpdateContactAttribute(handle, key, value string)
+
+ ChangePasswordResponse(error bool)
+}
+
+// NewManager returns a new Manager interface for a profile to the gcd
+func NewManager(profile string, gcd *GrandCentralDispatcher) Manager {
+ return &manager{gcd: gcd, profile: profile}
+}
+
+// Acknowledge acknowledges the given message id in the UI
+func (this *manager) Acknowledge(mID string) {
+ this.gcd.DoIfProfile(this.profile, func() {
+ this.gcd.Acknowledged(mID)
+ })
+}
+
+func getLastMessageTime(tl *model.Timeline) int {
+ if len(tl.Messages) == 0 {
+ return 0
+ }
+
+ return int(tl.Messages[len(tl.Messages)-1].Timestamp.Unix())
+}
+
+// AddContact adds a new contact to the ui for this manager's profile
+func (this *manager) AddContact(Handle string) {
+ this.gcd.DoIfProfile(this.profile, func() {
+
+ if isGroup(Handle) {
+ group := the.Peer.GetGroup(Handle)
+ if group != nil {
+ lastRead := initLastReadTime(group.GroupID)
+ unread := countUnread(group.Timeline.GetMessages(), lastRead)
+ picture := initProfilePicture(Handle)
+ nick, exists := group.GetAttribute(constants.Nick)
+ if !exists {
+ nick = Handle
+ }
+
+ this.gcd.AddContact(Handle, nick, picture, group.GroupServer, unread, int(connections.ConnectionStateToType[group.State]), false, false, getLastMessageTime(&group.Timeline))
+ }
+ return
+ } else if !isPeer(Handle) {
+ log.Errorf("sorry, unable to handle AddContact(%v)", Handle)
+ debug.PrintStack()
+ return
+ }
+
+ contact := the.Peer.GetContact(Handle)
+ if contact != nil {
+ lastRead := initLastReadTime(contact.Onion)
+ unread := countUnread(contact.Timeline.GetMessages(), lastRead)
+ picture := initProfilePicture(Handle)
+ nick, exists := contact.GetAttribute(constants.Nick)
+ if !exists {
+ nick = Handle
+ }
+
+ this.gcd.AddContact(Handle, nick, picture, "", unread, int(connections.ConnectionStateToType[contact.State]), contact.Blocked, false, getLastMessageTime(&contact.Timeline))
+ }
+ })
+}
+
+// AddSendMessageError adds an error not and icon to a message in a conversation in the ui for the message identified by the peer/sig combo
+func (this *manager) AddSendMessageError(peer string, signature string, err string) {
+ this.gcd.DoIfProfile(this.profile, func() {
+ this.gcd.DoIfConversation(peer, func() {
+ log.Debugf("Received Error Sending Message: %v", err)
+ // FIXME: Sometimes, for the first Peer message we send our error beats our message to the UI
+ time.Sleep(time.Second * 1)
+ this.gcd.GroupSendError(signature, err)
+ })
+ })
+}
+
+// AddMessage adds a message to the message pane for the supplied conversation if it is active
+func (this *manager) AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool) {
+ this.gcd.DoIfProfile(this.profile, func() {
+
+ nick := getOrDefault(handle, constants.Nick, handle)
+ image := getProfilePic(handle)
+
+ // If we have this group loaded already
+ this.gcd.DoIfConversation(handle, func() {
+ updateLastReadTime(handle)
+ // If the message is not from the user then add it, otherwise, just acknowledge.
+ if !fromMe {
+ this.gcd.AppendMessage(handle, from, nick, message, image, messageID, fromMe, timestamp.Format(constants.TIME_FORMAT), false, false)
+ } else {
+ if !Acknowledged {
+ this.gcd.AppendMessage(handle, from, nick, message, image, messageID, fromMe, timestamp.Format(constants.TIME_FORMAT), false, false)
+ } else {
+ this.gcd.Acknowledged(messageID)
+ }
+ }
+ })
+ this.gcd.IncContactUnreadCount(handle)
+ })
+}
+
+func (this *manager) ReloadProfiles() {
+ this.gcd.reloadProfileList()
+}
+
+// UpdateContactDisplayName updates a contact's display name in the contact list and conversations
+func (this *manager) UpdateContactDisplayName(handle string, name string) {
+ this.gcd.DoIfProfile(this.profile, func() {
+ this.gcd.UpdateContactDisplayName(handle, name)
+ })
+}
+
+// UpdateContactStatus updates a contact's status in the ui
+func (this *manager) UpdateContactStatus(handle string, status int, loading bool) {
+ this.gcd.DoIfProfile(this.profile, func() {
+ this.gcd.UpdateContactStatus(handle, status, loading)
+ })
+}
+
+// UpdateContactAttribute update's a contacts attribute in the ui
+func (this *manager) UpdateContactAttribute(handle, key, value string) {
+ this.gcd.DoIfProfile(this.profile, func() {
+ this.gcd.UpdateContactAttribute(handle, key, value)
+ })
+}
+
+func (this *manager) ChangePasswordResponse(error bool) {
+ this.gcd.ChangePasswordResponse(error)
+}
diff --git a/go/cwutil/utils.go b/go/ui/utils.go
similarity index 97%
rename from go/cwutil/utils.go
rename to go/ui/utils.go
index 157042b8..b0f39e8e 100644
--- a/go/cwutil/utils.go
+++ b/go/ui/utils.go
@@ -1,9 +1,8 @@
-package cwutil
+package ui
import (
"encoding/base32"
"encoding/hex"
- "fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"strings"
)
@@ -23,7 +22,7 @@ func RandomGroupImage(handle string) string {
choices := []string{"001-borobudur", "002-opera-house", "003-burj-al-arab", "004-chrysler", "005-acropolis", "006-empire-state-building", "007-temple", "008-indonesia-1", "009-new-zealand", "010-notre-dame", "011-space-needle", "012-seoul", "013-mosque", "014-milan", "015-statue", "016-pyramid", "017-cologne", "018-brandenburg-gate", "019-berlin-cathedral", "020-hungarian-parliament", "021-buckingham", "022-thailand", "023-independence", "024-angkor-wat", "025-vaticano", "026-christ-the-redeemer", "027-colosseum", "028-golden-gate-bridge", "029-sphinx", "030-statue-of-liberty", "031-cradle-of-humankind", "032-istanbul", "033-london-eye", "034-sagrada-familia", "035-tower-bridge", "036-burj-khalifa", "037-washington", "038-big-ben", "039-stonehenge", "040-white-house", "041-ahu-tongariki", "042-capitol", "043-eiffel-tower", "044-church-of-the-savior-on-spilled-blood", "045-arc-de-triomphe", "046-windmill", "047-louvre", "048-torii-gate", "049-petronas", "050-matsumoto-castle", "051-fuji", "052-temple-of-heaven", "053-pagoda", "054-chichen-itza", "055-forbidden-city", "056-merlion", "057-great-wall-of-china", "058-taj-mahal", "059-pisa", "060-indonesia"}
barr, err := hex.DecodeString(handle)
if err != nil || len(barr) == 0 {
- fmt.Printf("error: %v %v %v\n", handle, err, barr)
+ log.Errorf("error: %v %v %v\n", handle, err, barr)
return "qrc:/qml/images/extra/openprivacy.png"
}
return "servers/" + choices[int(barr[0])%len(choices)] + ".png"
diff --git a/i18n/translation_de.ts b/i18n/translation_de.ts
index 1a8a443f..cd9416c4 100644
--- a/i18n/translation_de.ts
+++ b/i18n/translation_de.ts
@@ -4,239 +4,469 @@
AddGroupPane
+
- Gruppe Anlegen
+ Gruppe Anlegen
+
Server label
- Server
+ Server
+
Group name label
- Gruppenname
+ Gruppenname
+
default suggested group name
- Tolle Gruppe
+ Tolle Gruppe
+
create group button
- Anlegen
+ Anlegen
BulletinOverlay
+
- Neue Meldung
+ Neue Meldung
+
Post a new Bulletin Post
- Neue Meldung veröffentlichen
+ Neue Meldung veröffentlichen
+
title place holder text
- Titel...
+ Titel...
GroupSettingsPane
+
- Server
+ Server
+
+
- Kopieren
+ Kopieren
+
- Einladung
+ Einladung
+
- Gruppenname
+ Gruppenname
+
- Speichern
+ Speichern
+
Invite someone to the group
- In die Gruppe einladen
+ In die Gruppe einladen
+
- Einladen
+ Einladen
+
- Löschen
+ Löschen
+
+
+
+ InplaceEditText
+
+
+
+
ListOverlay
+
Add a New List Item
- Liste hinzufügen
+ Liste hinzufügen
+
Add a new item to the list
- Neues Listenelement hinzüfgen
+ Neues Listenelement hinzüfgen
+
Todo... placeholder text
- noch zu erledigen
+ noch zu erledigen
MembershipOverlay
+
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.
- 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.
+ 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.
Message
+
Click to DM
- Klicken, um DM zu senden
+ Klicken, um DM zu senden
+
Could not send this message
- Nachricht konnte nicht gesendet werden
+ Nachricht konnte nicht gesendet werden
+
- bestätigt
+ bestätigt
+
- Bestätigung ausstehend
+ Bestätigung ausstehend
MyProfile
+
Button for copying profile onion address to clipboard
- Kopieren
+ Kopieren
+
Copied to clipboard
- in die Zwischenablage kopiert
+ in die Zwischenablage kopiert
+
create new group button
- Neue Gruppe anlegen
+ Neue Gruppe anlegen
+
ex: "... paste an address here to add a contact ..."
- Adresse hier hinzufügen, um einen Kontakt aufzunehmen
+ Adresse hier hinzufügen, um einen Kontakt aufzunehmen
OverlayPane
+
Do you want to accept the invitation to $GROUP
- Möchtest Du die Einladung annehmen
+ Möchtest Du die Einladung annehmen
+
Accept group invite button
- Annehmen
+ Annehmen
+
Reject Group invite button
- Ablehnen
+ Ablehnen
+
- Chat
+ Chat
+
- Listen
+ Listen
+
- Meldungen
+ Meldungen
+
- Puzzlespiel
+ Puzzlespiel
PeerSettingsPane
+
- Adresse
+ Adresse
+
- Kopieren
+ Kopieren
+
notification: copied to clipboard
- in die Zwischenablage kopiert
+ in die Zwischenablage kopiert
+
- Angezeigter Name
+ Angezeigter Name
+
- speichern
+ speichern
+
+
+
+
+
+
+
+
+
+
+
- löschen
+ löschen
+
+
+
+ ProfileAddEditPane
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Display name
+
+
+
+
+
+
+ default suggested profile name
+
+
+
+
+
+ Onion
+
+
+
+
+
+ Password
+
+
+
+
+
+ Unencrypted (No password)
+
+
+
+
+
+ Not using a password on this account means that all data stored locally will not be encrypted
+
+
+
+
+
+ Current Password
+
+
+
+
+
+ Password
+
+
+
+
+
+ Reenter password
+
+
+
+
+
+ Create Profile || Save Profile
+
+
+
+
+
+
+
+
+
+
+ Passwords do not match
+
+
+
+
+
+ Error changing password: Supplied password rejected
+
+
+
+
+
+ Delete Profile
+
+
+
+
+
+ Type DELETE to confirm
+
+
+
+
+
+ Really Delete Profile
+
+
+
+
+
+ DELETE
+
+
+
+
+ ProfileList
+
+
+
+
+
+
+
+
+ ProfileManagerPane
+
+
+
+ Please enter password:
+
+
+
+
+
+ 0 profiles loaded with that password
+
+
+
+
+
+ Unlock
+
SettingsPane
+
Cwtch Settings title
- Cwtch Einstellungen
+ Cwtch Einstellungen
+
+
+ Version: %1 Built on: %2
+
+
+
+
Interface zoom (mostly affects text and button sizes)
- Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)
+ Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)
+
+
+
+
+
+
- Groß
+ Groß
+
"Default size text (scale factor: "
- defaultmäßige Textgröße (Skalierungsfaktor:
+ defaultmäßige Textgröße (Skalierungsfaktor:
+
- Klein
+ Klein
+
+
+
+ StackToolbar
+
+
+
+ View Group Membership
+
diff --git a/i18n/translation_en.qm b/i18n/translation_en.qm
index 9dad8dff..41cfb9e3 100644
Binary files a/i18n/translation_en.qm and b/i18n/translation_en.qm differ
diff --git a/i18n/translation_en.ts b/i18n/translation_en.ts
index 51319b63..9ec91004 100644
--- a/i18n/translation_en.ts
+++ b/i18n/translation_en.ts
@@ -4,264 +4,469 @@
AddGroupPane
+
- Create Group
+ Create Group
+
Server label
- Server
+ Server
+
Group name label
- Group name
+ Group name
+
default suggested group name
- Awesome Group
+ Awesome Group
+
create group button
- Create
+ Create
BulletinOverlay
+
- New Bulletin
+ New Bulletin
+
Post a new Bulletin Post
- Post new bulletin
+ Post new bulletin
+
title place holder text
- title...
+ title...
GroupSettingsPane
+
- Server
+ Server
+
+
- Copy
+ Copy
+
- Invitation
+ Invitation
+
- Group Name
+ Group Name
+
- Save
+ Save
+
Invite someone to the group
- Invite to group
+ Invite to group
+
- Invite
+ Invite
+
- Delete
+ Delete
+
+
+
+ InplaceEditText
+
+
+
+ Update
ListOverlay
+
Add a New List Item
- Add a New List Item
+ Add a New List Item
+
Add a new item to the list
- Add a new item to the list
+ Add a new item to the list
+
Todo... placeholder text
- Todo...
+ Todo...
MembershipOverlay
+
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.
- 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.
+ 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.
Message
+
Click to DM
- Click to DM
+ Click to DM
+
Could not send this message
- Could not send this message
+ Could not send this message
+
- Acknowledged
+ Acknowledged
+
- Pending
+ Pending
MyProfile
+
Button for copying profile onion address to clipboard
- Copy
+ Copy
+
Copied to clipboard
- Copied to clipboard
+ Copied to clipboard
+
create new group button
- Create new group
+ Create new group
+
ex: "... paste an address here to add a contact ..."
- ... paste an address here to add a contact...
+ ... paste an address here to add a contact...
OverlayPane
+
Do you want to accept the invitation to $GROUP
- Do you want to accept the invitation to
+ Do you want to accept the invitation to
+
Accept group invite button
- Accept
+ Accept
+
Reject Group invite button
- Reject
+ Reject
+
- Chat
+ Chat
+
- Lists
+ Lists
+
- Bulletins
+ Bulletins
+
- Puzzle Game
+ Puzzle Game
PeerSettingsPane
+
- Address
+ Address
+
- Copy
+ Copy
+
notification: copied to clipboard
- Copied to Clipboard
+ Copied to Clipboard
+
- Display Name
+ Display Name
+
- Save
+ Save
+
- Block Peer
+ Block Peer
+
- Unblock Peer
+ Unblock Peer
+
- Delete
+ Delete
+
+
+
+ ProfileAddEditPane
+
+
+
+ Add new profile
+
+
+
+
+ Edit Profile
+
+
+
+
+ Display name
+ Display name
+
+
+
+
+
+ default suggested profile name
+ Alice
+
+
+
+
+ Onion
+ Onion
+
+
+
+
+ Password
+ Password
+
+
+
+
+ Unencrypted (No password)
+ Unencrypted (No password)
+
+
+
+
+ Not using a password on this account means that all data stored locally will not be encrypted
+ Not using a password on this account means that all data stored locally will not be encrypted
+
+
+
+
+ Current Password
+ Current Password
+
+
+
+
+ Password
+ Password
+
+
+
+
+ Reenter password
+ Reenter password
+
+
+
+
+ Create Profile || Save Profile
+ Create Profile
+
+
+
+
+ Save Profile
+
+
+
+
+ Passwords do not match
+ Passwords do not match
+
+
+
+
+ Error changing password: Supplied password rejected
+ Error changing password: Supplied password rejected
+
+
+
+
+ Delete Profile
+ Delete Profile
+
+
+
+
+ Type DELETE to confirm
+ Type DELETE to confirm
+
+
+
+
+ Really Delete Profile
+ Really Delete Profile
+
+
+
+
+ DELETE
+ DELETE
+
+
+
+ ProfileList
+
+
+
+
+ Add new profile
+
+
+
+ ProfileManagerPane
+
+
+
+ Please enter password:
+ Please enter password
+
+
+
+
+ 0 profiles loaded with that password
+ 0 profiles loaded with that password
+
+
+
+
+ Unlock
+ Unlock
SettingsPane
+
Cwtch Settings title
- Cwtch Settings
+ Cwtch Settings
+
Version: %1 Built on: %2
- Version: %1 Built on: %2
+ Version: %1 Built on: %2
+
Interface zoom (mostly affects text and button sizes)
- Interface zoom (mostly affects text and button sizes)
+ Interface zoom (mostly affects text and button sizes)
+
- Block Unknown Peers
+ Block Unknown Peers
+
- Large
+ Large
+
"Default size text (scale factor: "
- Default size text (scale factor:
+ Default size text (scale factor:
+
- Small
+ Small
StackToolbar
+
View Group Membership
- View Group Membership
+ View Group Membership
diff --git a/i18n/translation_fr.ts b/i18n/translation_fr.ts
index 39db9d6e..dd42812f 100644
--- a/i18n/translation_fr.ts
+++ b/i18n/translation_fr.ts
@@ -4,239 +4,469 @@
AddGroupPane
+
- Créer un groupe
+ Créer un groupe
+
Server label
- Serveur
+ Serveur
+
Group name label
- Groupe
+ Groupe
+
default suggested group name
- Un super groupe
+ Un super groupe
+
create group button
- Créer
+ Créer
BulletinOverlay
+
- Nouveau bulletin
+ Nouveau bulletin
+
Post a new Bulletin Post
- Envoyer un nouveau bulletin
+ Envoyer un nouveau bulletin
+
title place holder text
- titre...
+ titre...
GroupSettingsPane
+
- Serveur
+ Serveur
+
+
- Copier
+ Copier
+
- Invitation
+ Invitation
+
- Nom du groupe
+ Nom du groupe
+
- Sauvegarder
+ Sauvegarder
+
Invite someone to the group
- Inviter quelqu'un
+ Inviter quelqu'un
+
- Invitation
+ Invitation
+
- Effacer
+ Effacer
+
+
+
+ InplaceEditText
+
+
+
+
ListOverlay
+
Add a New List Item
- Ajouter un nouvel élément
+ Ajouter un nouvel élément
+
Add a new item to the list
- Ajouter un nouvel élément à la liste
+ Ajouter un nouvel élément à la liste
+
Todo... placeholder text
- A faire...
+ A faire...
MembershipOverlay
+
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.
- 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.
+ 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.
Message
+
Click to DM
- Envoyer un message privé
+ Envoyer un message privé
+
Could not send this message
- Impossible d'envoyer ce message
+ Impossible d'envoyer ce message
+
- Confirmé
+ Confirmé
+
- En attente
+ En attente
MyProfile
+
Button for copying profile onion address to clipboard
- Copier
+ Copier
+
Copied to clipboard
- Copié dans le presse-papier
+ Copié dans le presse-papier
+
create new group button
- Créer un nouveau groupe
+ Créer un nouveau groupe
+
ex: "... paste an address here to add a contact ..."
- ... coller une adresse ici pour ajouter un contact...
+ ... coller une adresse ici pour ajouter un contact...
OverlayPane
+
Do you want to accept the invitation to $GROUP
- Voulez-vous accepter l'invitation au groupe
+ Voulez-vous accepter l'invitation au groupe
+
Accept group invite button
- Accepter
+ Accepter
+
Reject Group invite button
- Refuser
+ Refuser
+
- Discuter
+ Discuter
+
- Listes
+ Listes
+
- Bulletins
+ Bulletins
+
- Puzzle
+ Puzzle
PeerSettingsPane
+
- Adresse
+ Adresse
+
- Copier
+ Copier
+
notification: copied to clipboard
- Copié dans le presse-papier
+ Copié dans le presse-papier
+
- Pseudo
+ Pseudo
+
- Sauvegarder
+ Sauvegarder
+
+
+
+
+
+
+
+
+
+
+
- Effacer
+ Effacer
+
+
+
+ ProfileAddEditPane
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Display name
+
+
+
+
+
+
+ default suggested profile name
+
+
+
+
+
+ Onion
+
+
+
+
+
+ Password
+
+
+
+
+
+ Unencrypted (No password)
+
+
+
+
+
+ Not using a password on this account means that all data stored locally will not be encrypted
+
+
+
+
+
+ Current Password
+
+
+
+
+
+ Password
+
+
+
+
+
+ Reenter password
+
+
+
+
+
+ Create Profile || Save Profile
+
+
+
+
+
+
+
+
+
+
+ Passwords do not match
+
+
+
+
+
+ Error changing password: Supplied password rejected
+
+
+
+
+
+ Delete Profile
+
+
+
+
+
+ Type DELETE to confirm
+
+
+
+
+
+ Really Delete Profile
+
+
+
+
+
+ DELETE
+
+
+
+
+ ProfileList
+
+
+
+
+
+
+
+
+ ProfileManagerPane
+
+
+
+ Please enter password:
+
+
+
+
+
+ 0 profiles loaded with that password
+
+
+
+
+
+ Unlock
+
SettingsPane
+
Cwtch Settings title
- Préférences Cwtch
+ Préférences Cwtch
+
+
+ Version: %1 Built on: %2
+
+
+
+
Interface zoom (mostly affects text and button sizes)
- Interface zoom (essentiellement la taille du texte et des composants de l'interface)
+ Interface zoom (essentiellement la taille du texte et des composants de l'interface)
+
+
+
+
+
+
- Large
+ Large
+
"Default size text (scale factor: "
- Taille par défaut du texte (échelle:
+ Taille par défaut du texte (échelle:
+
- Petit
+ Petit
+
+
+
+ StackToolbar
+
+
+
+ View Group Membership
+
diff --git a/i18n/translation_pt.ts b/i18n/translation_pt.ts
index b31eebdd..aa042b14 100644
--- a/i18n/translation_pt.ts
+++ b/i18n/translation_pt.ts
@@ -4,239 +4,469 @@
AddGroupPane
+
- Criar Grupo
+ Criar Grupo
+
Server label
- Servidor
+ Servidor
+
Group name label
- Nome do grupo
+ Nome do grupo
+
default suggested group name
- Grupo incrível
+ Grupo incrível
+
create group button
- Criar
+ Criar
BulletinOverlay
+
- Novo Boletim
+ Novo Boletim
+
Post a new Bulletin Post
- Postar novo boletim
+ Postar novo boletim
+
title place holder text
- título…
+ título…
GroupSettingsPane
+
- Servidor
+ Servidor
+
+
- Copiar
+ Copiar
+
- Convite
+ Convite
+
- Nome do Grupo
+ Nome do Grupo
+
- Salvar
+ Salvar
+
Invite someone to the group
- Convidar ao grupo
+ Convidar ao grupo
+
- Convidar
+ Convidar
+
- Deletar
+ Deletar
+
+
+
+ InplaceEditText
+
+
+
+
ListOverlay
+
Add a New List Item
- Adicionar Item à Lista
+ Adicionar Item à Lista
+
Add a new item to the list
- Adicionar novo item à lista
+ Adicionar novo item à lista
+
Todo... placeholder text
- Afazer…
+ Afazer…
MembershipOverlay
+
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.
- 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.
+ 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.
Message
+
Click to DM
- Clique para DM
+ Clique para DM
+
Could not send this message
- Não deu para enviar esta mensagem
+ Não deu para enviar esta mensagem
+
- Confirmada
+ Confirmada
+
- Pendente
+ Pendente
MyProfile
+
Button for copying profile onion address to clipboard
- Copiar
+ Copiar
+
Copied to clipboard
- Copiado
+ Copiado
+
create new group button
- Criar novo grupo
+ Criar novo grupo
+
ex: "... paste an address here to add a contact ..."
- … cole um endereço aqui para adicionar um contato…
+ … cole um endereço aqui para adicionar um contato…
OverlayPane
+
Do you want to accept the invitation to $GROUP
- Você quer aceitar o convite para
+ Você quer aceitar o convite para
+
Accept group invite button
- Aceitar
+ Aceitar
+
Reject Group invite button
- Recusar
+ Recusar
+
- Chat
+ Chat
+
- Listas
+ Listas
+
- Boletins
+ Boletins
+
- Jogo de Adivinhação
+ Jogo de Adivinhação
PeerSettingsPane
+
- Endereço
+ Endereço
+
- Copiar
+ Copiar
+
notification: copied to clipboard
- Copiado
+ Copiado
+
- Nome de Exibição
+ Nome de Exibição
+
- Salvar
+ Salvar
+
+
+
+
+
+
+
+
+
+
+
- Deletar
+ Deletar
+
+
+
+ ProfileAddEditPane
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Display name
+
+
+
+
+
+
+ default suggested profile name
+
+
+
+
+
+ Onion
+
+
+
+
+
+ Password
+
+
+
+
+
+ Unencrypted (No password)
+
+
+
+
+
+ Not using a password on this account means that all data stored locally will not be encrypted
+
+
+
+
+
+ Current Password
+
+
+
+
+
+ Password
+
+
+
+
+
+ Reenter password
+
+
+
+
+
+ Create Profile || Save Profile
+
+
+
+
+
+
+
+
+
+
+ Passwords do not match
+
+
+
+
+
+ Error changing password: Supplied password rejected
+
+
+
+
+
+ Delete Profile
+
+
+
+
+
+ Type DELETE to confirm
+
+
+
+
+
+ Really Delete Profile
+
+
+
+
+
+ DELETE
+
+
+
+
+ ProfileList
+
+
+
+
+
+
+
+
+ ProfileManagerPane
+
+
+
+ Please enter password:
+
+
+
+
+
+ 0 profiles loaded with that password
+
+
+
+
+
+ Unlock
+
SettingsPane
+
Cwtch Settings title
- Configurações do Cwtch
+ Configurações do Cwtch
+
+
+ Version: %1 Built on: %2
+
+
+
+
Interface zoom (mostly affects text and button sizes)
- Zoom da interface (afeta principalmente tamanho de texto e botões)
+ Zoom da interface (afeta principalmente tamanho de texto e botões)
+
+
+
+
+
+
- Grande
+ Grande
+
"Default size text (scale factor: "
- Texto tamanho padrão (fator de escala:
+ Texto tamanho padrão (fator de escala:
+
- Pequeno
+ Pequeno
+
+
+
+ StackToolbar
+
+
+
+ View Group Membership
+
diff --git a/main.go b/main.go
index 4f2e85fe..a7c5b87d 100644
--- a/main.go
+++ b/main.go
@@ -3,11 +3,10 @@ package main
import (
libapp "cwtch.im/cwtch/app"
"cwtch.im/cwtch/event/bridge"
- "cwtch.im/ui/go/characters"
- "cwtch.im/ui/go/gobjects"
- "cwtch.im/ui/go/gothings"
- "cwtch.im/ui/go/gothings/android"
+ "cwtch.im/ui/go/handlers"
"cwtch.im/ui/go/the"
+ "cwtch.im/ui/go/ui"
+ "cwtch.im/ui/go/ui/android"
"flag"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
@@ -34,7 +33,7 @@ var (
func init() {
// make go-defined types available in qml
- gothings.GrandCentralDispatcher_QmlRegisterType2("CustomQmlTypes", 1, 0, "GrandCentralDispatcher")
+ ui.GrandCentralDispatcher_QmlRegisterType2("CustomQmlTypes", 1, 0, "GrandCentralDispatcher")
}
func main() {
@@ -58,7 +57,9 @@ func main() {
}
// TESTING
- //log.SetLevel(log.LevelDebug)
+ if buildVer == "" {
+ log.SetLevel(log.LevelDebug)
+ }
//log.ExcludeFromPattern("connection/connection")
//log.ExcludeFromPattern("outbound/3dhauthchannel")
//log.AddNothingExceptFilter("event/eventmanager")
@@ -75,6 +76,12 @@ func main() {
}
the.CwtchDir = path.Join(usr.HomeDir, ".cwtch")
}
+
+ if buildVer == "" && os.Getenv("CWTCH_FOLDER") == "" {
+ log.Infoln("Development build: using dev directory for dev profiles")
+ the.CwtchDir = path.Join(the.CwtchDir, "dev")
+ }
+
the.ACN = nil
the.Peer = nil
the.IPCBridge = nil
@@ -111,6 +118,7 @@ func mainService() {
log.Infoln("Making QGuiApplication...")
app = gui.NewQGuiApplication(len(os.Args), os.Args)
}
+
log.Infoln("Cwtch Service starting app.Exec")
app.Exec()
}
@@ -121,7 +129,7 @@ func mainUi(flagLocal bool, flagClientUI bool) {
//app := gui.NewQGuiApplication(len(os.Args), os.Args)
app := widgets.NewQApplication(len(os.Args), os.Args)
// our globals
- gcd := gothings.NewGrandCentralDispatcher(nil)
+ gcd := ui.NewGrandCentralDispatcher(nil)
gcd.SetOs(runtime.GOOS)
ex, err := os.Executable()
if err != nil { log.Infof("error getting path: %v", err) }
@@ -139,8 +147,6 @@ func mainUi(flagLocal bool, flagClientUI bool) {
gcd.SetVersion("development")
gcd.SetBuildDate("now")
}
- gcd.UIState = gothings.NewUIState(gcd)
- gcd.OutgoingMessages = make(chan gobjects.Letter, 1000)
//TODO: put theme stuff somewhere better
gcd.SetThemeScale(1.0)
@@ -232,7 +238,7 @@ func loadACN() {
}
}
-func loadNetworkingAndFiles(gcd *gothings.GrandCentralDispatcher, service bool, clientUI bool) {
+func loadNetworkingAndFiles(gcd *ui.GrandCentralDispatcher, service bool, clientUI bool) {
if service || clientUI || runtime.GOOS == "android" {
clientIn := path.Join(the.CwtchDir, "clientIn")
serviceIn := path.Join(the.CwtchDir, "serviceIn")
@@ -255,7 +261,7 @@ func loadNetworkingAndFiles(gcd *gothings.GrandCentralDispatcher, service bool,
if !service {
the.AppBus = the.CwtchApp.GetPrimaryBus()
subscribed := make(chan bool)
- go characters.AppEventListener(gcd, subscribed)
+ go handlers.App(gcd, subscribed, clientUI)
<-subscribed
}
diff --git a/qml/main.qml b/qml/main.qml
index cd9cad18..756786b5 100644
--- a/qml/main.qml
+++ b/qml/main.qml
@@ -127,6 +127,13 @@ ApplicationWindow {
currentIndex: 1
anchors.fill: parent
+ currentIndex: 0
+ readonly property int splashPane: 0
+ readonly property int managementPane: 1
+ readonly property int addEditProfilePane: 2
+ readonly property int profilePane: 3
+ property alias pane: parentStack.currentIndex
+
Rectangle { // Splash pane
color: "#FFFFFF"
//Layout.fillHeight: true
@@ -143,6 +150,28 @@ ApplicationWindow {
}
}
+ Rectangle { // Profile login/management pane
+ anchors.fill: parent
+ visible: false
+ color: "#D2C0DD"
+
+ ProfileManagerPane {
+ id: profilesPane
+ anchors.fill: parent
+ }
+ }
+
+ Rectangle { // Profile login/management pane
+ anchors.fill: parent
+ color: "#EEEEFF"
+
+
+ ProfileAddEditPane{
+ id: profileAddEditPane
+ anchors.fill: parent
+ }
+ }
+
RowLayout { // CONTAINS EVERYTHING EXCEPT THE TOOLBAR
/* anchors.left: ratio >= 0.92 ? parent.left : toolbar.right
@@ -260,7 +289,23 @@ ApplicationWindow {
onSetToolbarTitle: function(str) {
theStack.title = str
}
+
+ onLoaded: function() {
+ parentStack.pane = parentStack.managementPane
+ splashPane.running = false
+ }
}
Component.onCompleted: Mutant.standard.imagePath = gcd.assetPath;
+
+ Connections {
+ target: Qt.application
+ onStateChanged: function() {
+ // https://doc.qt.io/qt-5/qt.html#ApplicationState-enum
+ if (Qt.application.state == 4) {
+ // Active
+ gcd.onActivate()
+ }
+ }
+ }
}
diff --git a/qml/overlays/BulletinOverlay.qml b/qml/overlays/BulletinOverlay.qml
index 1e1f3cd8..47a4a64a 100644
--- a/qml/overlays/BulletinOverlay.qml
+++ b/qml/overlays/BulletinOverlay.qml
@@ -6,8 +6,7 @@ import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
-import "../widgets"
-import "../widgets/controls" as Awesome
+import "../widgets" as Widgets
import "../fonts/Twemoji.js" as T
import "../utils.js" as Utils
import "../styles"
@@ -87,13 +86,13 @@ ColumnLayout {
})
}
- if (sv.contentY + sv.height >= sv.contentHeight - colMessages.height && sv.contentHeight > sv.height) {
+ /*if (sv.contentY + sv.height >= sv.contentHeight - colMessages.height && sv.contentHeight > sv.height) {
sv.contentY = sv.contentHeight - sv.height
- }
+ }*/
}
- onUpdateContact: function(_handle, _displayName, _image, _server, _badge, _status, _trusted, _blocked, _loading) {
- if (gcd.currentOpenConversation == _handle) {
+ onUpdateContactStatus: function(_handle, _status, _loading) {
+ if (gcd.selectedConversation == _handle) {
if (_loading == true) {
newposttitle.enabled = false
newpostbody.enabled = false
@@ -176,7 +175,7 @@ ColumnLayout {
width: parent.width - 50
}
- SimpleButton {
+ Widgets.SimpleButton {
id: replybtn
visible: selected
text: "reply"
@@ -231,7 +230,7 @@ ColumnLayout {
}
- SimpleButton { // SEND MESSAGE BUTTON
+ Widgets.SimpleButton { // SEND MESSAGE BUTTON
id: btnSend
icon: "regular/paper-plane"
text: "post"
diff --git a/qml/overlays/ChatOverlay.qml b/qml/overlays/ChatOverlay.qml
index 3b3ccb2e..ecc7c97b 100644
--- a/qml/overlays/ChatOverlay.qml
+++ b/qml/overlays/ChatOverlay.qml
@@ -112,10 +112,10 @@ Item {
messagesListView.positionViewAtEnd()
}
- onUpdateContact: function(_handle, _displayName, _image, _server, _badge, _status, _trusted, _blocked, _loading) {
- if (gcd.currentOpenConversation == _handle) {
+ onUpdateContactStatus: function(_handle, _status, _loading) {
+ if (gcd.selectedConversation == _handle) {
// Group is Synced OR p2p is Authenticated
- if ( (_handle.length == 32 && _status == 4) || _status == 3) {
+ if ( (_handle.length == 32 && _status == 4) || (_handle.length == 56 && _status == 3) ) {
txtMessage.enabled = true
btnSend.enabled = true
} else {
diff --git a/qml/overlays/ListOverlay.qml b/qml/overlays/ListOverlay.qml
index 5304ac28..3bfeebbd 100644
--- a/qml/overlays/ListOverlay.qml
+++ b/qml/overlays/ListOverlay.qml
@@ -5,7 +5,7 @@ import QtQuick.Controls.Material 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
-import "../widgets"
+import "../widgets" as Widgets
import "../widgets/controls" as Awesome
import "../fonts/Twemoji.js" as T
import "../utils.js" as Utils
@@ -87,17 +87,17 @@ ColumnLayout {
})
}
- if(msg.c != undefined) {
+ /*if(msg.c != undefined) {
jsonModel4.get(msg.c).complete = true
}
if (sv.contentY + sv.height >= sv.contentHeight - colMessages.height && sv.contentHeight > sv.height) {
sv.contentY = sv.contentHeight - sv.height
- }
+ }*/
}
- onUpdateContact: function(_handle, _displayName, _image, _server, _badge, _status, _trusted, _blocked, _loading) {
- if (gcd.currentOpenConversation == _handle) {
+ onUpdateContactStatus: function(_handle, _status, _loading) {
+ if (gcd.selectedConversation == _handle) {
if (_loading == true) {
newposttitle.enabled = false
btnSend.enabled = false
@@ -204,7 +204,7 @@ ColumnLayout {
style: CwtchTextFieldStyle{}
}
- SimpleButton { // SEND MESSAGE BUTTON
+ Widgets.SimpleButton { // SEND MESSAGE BUTTON
id: btnSend
icon: "regular/paper-plane"
text: "add"
diff --git a/qml/overlays/MembershipOverlay.qml b/qml/overlays/MembershipOverlay.qml
index d35caef8..b9297717 100644
--- a/qml/overlays/MembershipOverlay.qml
+++ b/qml/overlays/MembershipOverlay.qml
@@ -105,7 +105,6 @@ ColumnLayout {
handle: _handle
displayName: _displayName
image: _image
- trusted: true
blocked: false
background: false
}
diff --git a/qml/panes/AddGroupPane.qml b/qml/panes/AddGroupPane.qml
index 048e8aaa..4a0e45de 100644
--- a/qml/panes/AddGroupPane.qml
+++ b/qml/panes/AddGroupPane.qml
@@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3
import QtQuick.Window 2.11
import QtQuick.Controls 1.4
-import "../widgets"
+import "../widgets" as Widgets
import "../styles"
ColumnLayout { // settingsPane
@@ -14,7 +14,7 @@ ColumnLayout { // settingsPane
anchors.fill: parent
- StackToolbar {
+ Widgets.StackToolbar {
id: stb
text: qsTr("create-group-title")
aux.visible: false
@@ -37,7 +37,7 @@ ColumnLayout { // settingsPane
spacing: 5
width: root.width
- ScalingLabel {
+ Widgets.ScalingLabel {
//: Server label
text: qsTr("server-label") + ":"
}
@@ -48,7 +48,7 @@ ColumnLayout { // settingsPane
text: "2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd"
}
- ScalingLabel{
+ Widgets.ScalingLabel{
//: Group name label
text: qsTr("group-name-label") + ":"
}
@@ -60,7 +60,7 @@ ColumnLayout { // settingsPane
text: qsTr("default-group-name")
}
- SimpleButton {
+ Widgets.SimpleButton {
//: create group button
text: qsTr("create-group-btn")
diff --git a/qml/panes/GroupSettingsPane.qml b/qml/panes/GroupSettingsPane.qml
index 7616158d..bb0499af 100644
--- a/qml/panes/GroupSettingsPane.qml
+++ b/qml/panes/GroupSettingsPane.qml
@@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3
import QtQuick.Window 2.11
import QtQuick.Controls 1.4
-import "../widgets"
+import "../widgets" as Widgets
import "../styles"
import "../utils.js" as Utils
@@ -16,7 +16,7 @@ ColumnLayout { // groupSettingsPane
property string groupID
property variant addrbook
- StackToolbar {
+ Widgets.StackToolbar {
id: toolbar
aux.visible: false
back.onClicked: theStack.pane = theStack.messagePane
@@ -38,7 +38,7 @@ ColumnLayout { // groupSettingsPane
leftPadding: 10
spacing: 5
- ScalingLabel {
+ Widgets.ScalingLabel {
text: qsTr("server-label") + ":"
}
@@ -48,7 +48,7 @@ ColumnLayout { // groupSettingsPane
readOnly: true
}
- SimpleButton {
+ Widgets.SimpleButton {
icon: "regular/clipboard"
text: qsTr("copy-btn")
@@ -59,7 +59,7 @@ ColumnLayout { // groupSettingsPane
}
}
- ScalingLabel {
+ Widgets.ScalingLabel {
text: qsTr("invitation-label") + ":"
}
@@ -69,7 +69,7 @@ ColumnLayout { // groupSettingsPane
readOnly: true
}
- SimpleButton {
+ Widgets.SimpleButton {
icon: "regular/clipboard"
text: qsTr("copy-btn")
@@ -80,7 +80,7 @@ ColumnLayout { // groupSettingsPane
}
}
- ScalingLabel{
+ Widgets.ScalingLabel{
text: qsTr("group-name-label") + ":"
}
@@ -89,7 +89,7 @@ ColumnLayout { // groupSettingsPane
style: CwtchTextFieldStyle{ width: tehcol.width * 0.8 }
}
- SimpleButton {
+ Widgets.SimpleButton {
text: qsTr("save-btn")
onClicked: {
@@ -100,7 +100,7 @@ ColumnLayout { // groupSettingsPane
}
//: Invite someone to the group
- ScalingLabel { text: qsTr("invite-to-group-label") }
+ Widgets.ScalingLabel { text: qsTr("invite-to-group-label") }
ComboBox {
id: cbInvite
@@ -110,7 +110,7 @@ ColumnLayout { // groupSettingsPane
style: CwtchComboBoxStyle{}
}
- SimpleButton {
+ Widgets.SimpleButton {
text: qsTr("invite-btn")
onClicked: {
@@ -118,7 +118,7 @@ ColumnLayout { // groupSettingsPane
}
}
- SimpleButton {
+ Widgets.SimpleButton {
icon: "regular/trash-alt"
text: qsTr("delete-btn")
diff --git a/qml/panes/OverlayPane.qml b/qml/panes/OverlayPane.qml
index 6ac6f185..d56d3462 100644
--- a/qml/panes/OverlayPane.qml
+++ b/qml/panes/OverlayPane.qml
@@ -19,14 +19,14 @@ ColumnLayout {
StackToolbar {
id: toolbar
- membership.visible: gcd.currentOpenConversation.length == 32
+ membership.visible: gcd.selectedConversation.length == 32
membership.onClicked: overlayStack.overlay = overlayStack.membershipOverlay
aux.onClicked: {
- if (gcd.currentOpenConversation.length == 32) {
+ if (gcd.selectedConversation.length == 32) {
theStack.pane = theStack.groupProfilePane
- gcd.requestGroupSettings(gcd.currentOpenConversation)
+ gcd.requestGroupSettings(gcd.selectedConversation)
} else {
theStack.pane = theStack.userProfilePane
gcd.requestPeerSettings()
@@ -36,7 +36,7 @@ ColumnLayout {
}
RowLayout {
- visible:!overlay.accepted && (gcd.currentOpenConversation.length == 32)
+ visible:!overlay.accepted && (gcd.selectedConversation.length == 32)
Text {
@@ -49,8 +49,8 @@ ColumnLayout {
text: qsTr("accept-group-btn")
icon: "regular/heart"
onClicked: {
- gcd.acceptGroup(gcd.currentOpenConversation)
- gcd.requestGroupSettings(gcd.currentOpenConversation)
+ gcd.acceptGroup(gcd.selectedConversation)
+ gcd.requestGroupSettings(gcd.selectedConversation)
}
}
@@ -59,7 +59,7 @@ ColumnLayout {
text: qsTr("reject-group-btn")
icon: "regular/trash-alt"
onClicked: {
- gcd.leaveGroup(gcd.currentOpenConversation)
+ gcd.leaveGroup(gcd.selectedConversation)
theStack.pane = theStack.emptyPane
}
}
diff --git a/qml/panes/PeerSettingsPane.qml b/qml/panes/PeerSettingsPane.qml
index a48b8f02..3b7927e5 100644
--- a/qml/panes/PeerSettingsPane.qml
+++ b/qml/panes/PeerSettingsPane.qml
@@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3
import QtQuick.Window 2.11
import QtQuick.Controls 1.4
-import "../widgets"
+import "../widgets" as Widgets
import "../styles"
ColumnLayout { // peerSettingsPane
@@ -14,7 +14,7 @@ ColumnLayout { // peerSettingsPane
anchors.fill: parent
property bool blocked
- StackToolbar {
+ Widgets.StackToolbar {
id: toolbar
aux.visible: false
@@ -38,7 +38,7 @@ ColumnLayout { // peerSettingsPane
leftPadding: 10
spacing: 5
- ScalingLabel {
+ Widgets.ScalingLabel {
text: qsTr("address-label")
}
@@ -48,7 +48,7 @@ ColumnLayout { // peerSettingsPane
readOnly: true
}
- SimpleButton {
+ Widgets.SimpleButton {
icon: "regular/clipboard"
text: qsTr("copy-btn")
@@ -60,7 +60,7 @@ ColumnLayout { // peerSettingsPane
}
}
- ScalingLabel{
+ Widgets.ScalingLabel{
text: qsTr("display-name-label")
}
@@ -69,7 +69,7 @@ ColumnLayout { // peerSettingsPane
style: CwtchTextFieldStyle{ width: tehcol.width * 0.8 }
}
- SimpleButton {
+ Widgets.SimpleButton {
text: qsTr("save-btn")
onClicked: {
@@ -80,7 +80,7 @@ ColumnLayout { // peerSettingsPane
}
- SimpleButton {
+ Widgets.SimpleButton {
icon: "solid/hand-paper"
text: root.blocked ? qsTr("unblock-btn") : qsTr("block-btn")
@@ -94,7 +94,7 @@ ColumnLayout { // peerSettingsPane
}
}
- SimpleButton {
+ Widgets.SimpleButton {
icon: "regular/trash-alt"
text: qsTr("delete-btn")
diff --git a/qml/panes/ProfileAddEditPane.qml b/qml/panes/ProfileAddEditPane.qml
new file mode 100644
index 00000000..8939877d
--- /dev/null
+++ b/qml/panes/ProfileAddEditPane.qml
@@ -0,0 +1,303 @@
+import QtGraphicalEffects 1.0
+import QtQuick 2.7
+import QtQuick.Controls 2.13
+import QtQuick.Controls.Material 2.0
+import QtQuick.Layouts 1.3
+import QtQuick.Window 2.11
+
+
+import "../widgets" as Widgets
+// import "../styles"
+
+ColumnLayout { // Add Profile Pane
+ id: profileAddEditPane
+ anchors.fill: parent
+
+ property string mode // edit or add
+ property string onion
+ property string tag
+ property bool deleting
+ property bool changingPassword
+
+ Widgets.StackToolbar {
+ id: stb
+ text: mode == "add" ? qsTr("add-profile-title") : qsTr("edit-profile-title")
+ aux.visible: false
+ membership.visible: false
+ stack: "management"
+ }
+
+ function reset() {
+ mode = "add"
+ txtProfileName.text = qsTr("default-profile-name")
+ changingPassword = false
+ txtPassword1.text = ""
+ txtPassword2.text = ""
+ deleting = false
+ deleteConfirmLabel.color = "black"
+ passwordErrorLabel.visible = false
+ txtCurrentPassword.text = ""
+
+ tag = ""
+ confirmDeleteTxt.text = ""
+ radioUsePassword.checked = true
+ passwordChangeErrorLabel.visible = false
+ }
+
+ function load(userOnion, name, userTag) {
+ reset()
+
+ mode = "edit"
+ tag = userTag
+ onion = userOnion
+ txtPassword1.text = ""
+ txtPassword2.text = ""
+ onionLabel.text = onion
+ txtProfileName.text = name
+
+ if (tag == "v1-defaultPassword" || tag == "v1-default-password") {
+ radioNoPassword.checked = true
+ } else {
+ radioUsePassword.checked = true
+ }
+ }
+
+
+ Flickable {
+ anchors.top: stb.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ boundsBehavior: Flickable.StopAtBounds
+ clip:true
+ contentWidth: tehcol.width
+ contentHeight: tehcol.height
+
+ Column {
+ id: tehcol
+ leftPadding: 10
+ spacing: 5
+ width: profileAddEditPane.width
+
+ Widgets.ScalingLabel {
+ //: Onion
+ text: qsTr("profile-onion-label") + ":"
+ visible: mode == "edit"
+ }
+
+ Widgets.ScalingLabel {
+ id: onionLabel
+ visible: mode == "edit"
+ }
+
+ Widgets.ScalingLabel {
+ //: Display name
+ text: qsTr("profile-name") + ":"
+ }
+
+ Widgets.TextField {
+ id: txtProfileName
+ Layout.fillWidth: true
+ //style: CwtchTextFieldStyle{ width: tehcol.width * 0.8 }
+ //: default suggested profile name
+ text: qsTr("default-profile-name")
+
+
+ }
+
+ RowLayout {
+ //id: radioButtons
+
+ Widgets.RadioButton {
+ id: radioUsePassword
+ checked: true
+ //: Password
+ text: qsTr("radio-use-password")
+ visible: mode == "add" || tag == "v1-defaultPassword"
+ onClicked: {
+ changingPassword = true
+ }
+ }
+
+ Widgets.RadioButton {
+ id: radioNoPassword
+ //: Unencrypted (No password)
+ text: qsTr("radio-no-password")
+ visible: mode == "add" || tag == "v1-defaultPassword"
+ onClicked: {
+ changingPassword = true
+ }
+ }
+ }
+
+ Widgets.ScalingLabel {
+ id: noPasswordLabel
+ //: Not using a password on this account means that all data stored locally will not be encrypted
+ text: qsTr("no-password-warning")
+ visible: radioNoPassword.checked
+ }
+
+ Widgets.ScalingLabel {
+ id: currentPasswordLabel
+ //: Current Password
+ text: qsTr("current-password-label") + ":"
+ visible: radioUsePassword.checked && mode == "edit" && tag != "v1-defaultPassword"
+ }
+
+ Widgets.TextField {
+ id: txtCurrentPassword
+ Layout.fillWidth: true
+ echoMode: TextInput.Password
+ visible: radioUsePassword.checked && mode == "edit" && tag != "v1-defaultPassword"
+ }
+
+ Widgets.ScalingLabel {
+ id: passwordLabel
+ //: Password
+ text: qsTr("password1-label") + ":"
+ visible: radioUsePassword.checked
+ }
+
+ Widgets.TextField {
+ id: txtPassword1
+ Layout.fillWidth: true
+ //style: CwtchTextFieldStyle{ width: tehcol.width * 0.8 }
+ echoMode: TextInput.Password
+ visible: radioUsePassword.checked
+
+ onTextEdited: {
+ changingPassword = true
+ }
+ }
+
+
+ Widgets.ScalingLabel {
+ id: passwordReLabel
+ //: Reenter password
+ text: qsTr("password2-label") + ":"
+ visible: radioUsePassword.checked
+ }
+
+ Widgets.TextField {
+ id: txtPassword2
+ Layout.fillWidth: true
+ //style: CwtchTextFieldStyle{ width: tehcol.width * 0.8 }
+ echoMode: TextInput.Password
+ visible: radioUsePassword.checked
+ }
+
+ Widgets.SimpleButton { // ADD or SAVE button
+ //: Create Profile || Save Profile
+ text: mode == "add" ? qsTr("create-profile-btn") : qsTr("save-profile-btn")
+
+ onClicked: {
+ if (mode == "add") {
+ if (txtPassword1.text != txtPassword2.text) {
+ passwordErrorLabel.visible = true
+ } else {
+ gcd.createProfile(txtProfileName.text, radioNoPassword.checked, txtPassword1.text)
+ gcd.reloadProfileList()
+ parentStack.pane = parentStack.managementPane
+ }
+ } else {
+ gcd.updateNick(onion, txtProfileName.text)
+
+ if (changingPassword) {
+ if (txtPassword1.text != txtPassword2.text) {
+ passwordErrorLabel.visible = true
+ } else {
+ gcd.changePassword(onion, txtCurrentPassword.text, txtPassword1.text, radioNoPassword.checked)
+ }
+ } else {
+ gcd.reloadProfileList()
+ parentStack.pane = parentStack.managementPane
+ }
+ }
+
+ }
+ }
+
+ Widgets.ScalingLabel {
+ id: passwordErrorLabel
+ //: Passwords do not match
+ text: qsTr("password-error-match")
+ visible: false
+ color: "red"
+ }
+
+ Widgets.ScalingLabel {
+ id: passwordChangeErrorLabel
+ //: Error changing password: Supplied password rejected
+ text: qsTr("password-change-error")
+ visible: false
+ color: "red"
+ }
+
+ // ***** Delete button and confirm flow *****
+
+ Widgets.SimpleButton {
+ //: Delete Profile
+ text: qsTr("delete-profile-btn")
+ icon: "regular/trash-alt"
+ visible: mode == "edit"
+
+
+ onClicked: {
+ deleting = true
+ }
+ }
+
+ Widgets.ScalingLabel {
+ id: deleteConfirmLabel
+ //: Type DELETE to confirm
+ text: qsTr("delete-confirm-label")+ ":"
+ visible: deleting
+ }
+
+ Widgets.TextField {
+ id: confirmDeleteTxt
+ Layout.fillWidth: true
+ //style: CwtchTextFieldStyle{ width: tehcol.width * 0.8 }
+ visible: deleting
+ }
+
+ Widgets.SimpleButton {
+ id: confirmDeleteBtn
+ icon: "regular/trash-alt"
+
+ //: Really Delete Profile
+ text: qsTr("delete-profile-confirm-btn")
+ color: "red"
+ visible: deleting
+
+ onClicked: {
+ //: DELETE
+ if (confirmDeleteTxt.text == qsTr("delete-confirm-text")) {
+ deleteConfirmLabel.color = "black"
+ gcd.deleteProfile(onion)
+ gcd.reloadProfileList()
+ parentStack.pane = parentStack.managementPane
+ } else {
+ deleteConfirmLabel.color = "red"
+ }
+
+ }
+ }
+
+
+ }//end of column with padding
+ }//end of flickable
+
+ Connections { // UPDATE UNREAD MESSAGES COUNTER
+ target: gcd
+
+ onChangePasswordResponse: function(error) {
+ if (!error) {
+ gcd.reloadProfileList()
+ parentStack.pane = parentStack.managementPane
+ } else {
+ passwordChangeErrorLabel.visible = true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/panes/ProfileManagerPane.qml b/qml/panes/ProfileManagerPane.qml
new file mode 100644
index 00000000..c288b4a4
--- /dev/null
+++ b/qml/panes/ProfileManagerPane.qml
@@ -0,0 +1,85 @@
+import QtQuick 2.0
+import QtGraphicalEffects 1.0
+import QtQuick 2.7
+import QtQuick.Controls 2.4
+import QtQuick.Controls.Material 2.0
+import QtQuick.Layouts 1.3
+import QtQuick.Window 2.11
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import "../widgets" as Widgets
+import "../widgets/controls"
+import "../styles"
+
+
+ColumnLayout {
+ id: thecol
+ anchors.fill: parent
+ //leftPadding: 10
+ //spacing: 5
+
+ Widgets.ScalingLabel {
+ anchors.horizontalCenter: parent.horizontalCenter
+ wrapMode: TextEdit.Wrap
+ //: Please enter password:
+ text: qsTr("enter-profile-password")+":"
+ }
+
+ TextField {
+ id: txtPassword
+ anchors.horizontalCenter: parent.horizontalCenter
+ style: CwtchTextFieldStyle{ width: thecol.width * 0.8 }
+ echoMode: TextInput.Password
+ onAccepted: button.clicked()
+ }
+
+ Widgets.ScalingLabel {
+ id: error
+ anchors.horizontalCenter: parent.horizontalCenter
+ color: "red"
+ //: 0 profiles loaded with that password
+ text: qsTr("error-0-profiles-loaded-for-password")
+ visible: false
+ }
+
+ Widgets.SimpleButton {
+ id: "button"
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ icon: "solid/unlock-alt"
+ //: Unlock
+ text: qsTr("unlock")
+
+ onClicked: {
+ gcd.unlockProfiles(txtPassword.text)
+ txtPassword.text = ""
+ error.visible = false
+ }
+ }
+
+ Connections { // ADD/REMOVE CONTACT ENTRIES
+ target: gcd
+
+ onErrorLoaded0: function() {
+ error.visible = true
+ }
+ }
+
+
+ Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS
+ color: "#D2C0DD"
+ width: thecol.width
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.minimumWidth: Layout.maximumWidth
+ //Layout.maximumWidth: theStack.pane == theStack.emptyPane ? parent.width : 450
+
+ Widgets.ProfileList {
+ anchors.fill: parent
+ }
+ }
+
+
+}
diff --git a/qml/widgets/ContactList.qml b/qml/widgets/ContactList.qml
index d13fdd3d..1552475d 100644
--- a/qml/widgets/ContactList.qml
+++ b/qml/widgets/ContactList.qml
@@ -47,19 +47,36 @@ ColumnLayout {
Connections { // ADD/REMOVE CONTACT ENTRIES
target: gcd
- onAddContact: function(handle, displayName, image, server, badge, status, trusted, blocked, loading) {
- contactsModel.append({
- "_handle": handle,
- "_displayName": displayName + (blocked ? " (blocked)" : "" ),
- "_image": image,
- "_server": server,
- "_badge": badge,
- "_status": status,
- "_trusted": trusted,
- "_blocked": blocked,
- "_loading": loading,
- "_loading": loading
- })
+ onAddContact: function(handle, displayName, image, server, badge, status, blocked, loading, lastMsgTs) {
+
+ for (var i = 0; i < contactsModel.count; i++) {
+ if (contactsModel.get(i)["_handle"] == handle) {
+ return
+ }
+ }
+
+ var index = contactsModel.count
+ for (var i = 0; i < contactsModel.count; i++) {
+ if (contactsModel.get(i)["_lastMsgTs"] < lastMsgTs) {
+ index = i
+ break
+ }
+ }
+
+ var newContact = {
+ "_handle": handle,
+ "_displayName": displayName + (blocked ? " (blocked)" : "" ),
+ "_image": image,
+ "_server": server,
+ "_badge": badge,
+ "_status": status,
+ "_blocked": blocked,
+ "_loading": loading,
+ "_loading": loading,
+ "_lastMsgTs": lastMsgTs
+ }
+
+ contactsModel.insert(index, newContact)
}
onRemoveContact: function(handle) {
@@ -71,6 +88,22 @@ ColumnLayout {
}
}
}
+
+ onIncContactUnreadCount: function(handle) {
+ var ts = Math.round((new Date()).getTime() / 1000);
+ for(var i = 0; i < contactsModel.count; i++){
+ if(contactsModel.get(i)["_handle"] == handle) {
+ var contact = contactsModel.get(i)
+ contact["_lastMsgTs"] = ts
+ console.log("Found at " + i + " contact: " + contact)
+ contactsModel.move(i, 0, 1)
+ }
+ }
+ }
+
+ onResetProfile: function() {
+ contactsModel.clear()
+ }
}
ListModel { // CONTACT OBJECTS ARE STORED HERE ...
@@ -86,11 +119,18 @@ ColumnLayout {
server: _server
badge: _badge
status: _status
- trusted: _trusted
blocked: _blocked
loading: _loading
+ type: "contact"
}
}
+
+
}
}
+
+
}
+
+
+
diff --git a/qml/widgets/ContactPicture.qml b/qml/widgets/ContactPicture.qml
index 33ffa270..03ad82b6 100644
--- a/qml/widgets/ContactPicture.qml
+++ b/qml/widgets/ContactPicture.qml
@@ -17,6 +17,7 @@ Item {
property bool isGroup
property bool showStatus
property bool highlight
+ property bool button
property real logscale: 4 * Math.log10(gcd.themeScale + 1)
property int baseWidth: 48 * logscale
@@ -30,7 +31,7 @@ Item {
Rectangle {
width: highlight ? baseWidth - 4 : baseWidth
height: highlight ? baseWidth - 4 : baseWidth
- color: "#FFFFFF"
+ color: button ? windowItem.cwtch_dark_color: "#FFFFFF"
radius: width / 2
anchors.centerIn:parent
@@ -69,7 +70,7 @@ Item {
anchors.margins: 4 * logscale
- Rectangle { //-2:WtfCodeError,-1:Untrusted,0:Disconnected,1:Connecting,2:Connected,3:Authenticated,4:Synced,5:Failed,6:Killed
+ Rectangle { //-2:WtfCodeError,-1:Error,0:Disconnected,1:Connecting,2:Connected,3:Authenticated,4:Synced,5:Failed,6:Killed
color: status == 4 ? "green" : status == 3 ? "green" : status == -1 ? "blue" : status == 1 ? "orange" : status == 2 ? "orange" : "red"
width: 5 * logscale
height: 5 * logscale
diff --git a/qml/widgets/ContactRow.qml b/qml/widgets/ContactRow.qml
index 3b4f2661..f74d838d 100644
--- a/qml/widgets/ContactRow.qml
+++ b/qml/widgets/ContactRow.qml
@@ -10,6 +10,7 @@ import QtQuick.Controls.Styles 1.4
Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
+ id: crItem
anchors.left: parent.left
anchors.right: parent.right
height: 48 * logscale + 3
@@ -22,12 +23,18 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
property int badge
property bool isActive
property bool isHover
- property bool trusted
+ property bool background: true
+ property string type // profile or contact or button
+ property string tag // profile version/type
+
+ // Profile
+ property bool defaultPassword
+
+ // Contact
property bool blocked
property bool loading
- property alias status: imgProfile.status
- property string server
- property bool background: true
+ property alias status: imgProfile.status
+ property string server
Rectangle { // CONTACT ENTRY BACKGROUND COLOR
@@ -40,24 +47,43 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
ContactPicture {
id: imgProfile
- showStatus: true
+ showStatus: type == "contact"
+ button: type == "button"
}
- Label { // CONTACT NAME
- id: cn
- leftPadding: 10
- rightPadding: 10
- //wrapMode: Text.WordWrap
+ ColumnLayout {
+
anchors.left: imgProfile.right
anchors.right: loading ? loadingProgress.left : rectUnread.left
anchors.verticalCenter: parent.verticalCenter
- font.pixelSize: 16 * gcd.themeScale
- font.italic: !trusted
- font.strikeout: blocked
- textFormat: Text.PlainText
- //fontSizeMode: Text.HorizontalFit
- elide: Text.ElideRight
- color: "#000000"
+
+
+ Label { // CONTACT NAME
+ id: cn
+ leftPadding: 10
+ rightPadding: 10
+ //wrapMode: Text.WordWrap
+ font.pixelSize: 16 * gcd.themeScale
+ font.strikeout: blocked
+ textFormat: Text.PlainText
+ //fontSizeMode: Text.HorizontalFit
+ elide: Text.ElideRight
+ color: "#000000"
+ }
+
+ Label { // Onion
+ id: onion
+ text: handle
+ leftPadding: 10
+ rightPadding: 10
+ font.pixelSize: 10 * gcd.themeScale
+ font.strikeout: blocked
+ textFormat: Text.PlainText
+ //fontSizeMode: Text.HorizontalFit
+ elide: Text.ElideRight
+ color: "#000000"
+ }
+
}
Rectangle { // UNREAD MESSAGES?
@@ -87,6 +113,27 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
}
}
+ // Profile
+
+ Image {// Profle Type
+ id: profiletype
+
+ source: tag == "v1-userPassword" ? "qrc:/qml/images/fontawesome/solid/lock.svg" : "qrc:/qml/images/fontawesome/solid/lock-open.svg"
+
+ anchors.right: parent.right
+
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 1 * gcd.themeScale
+ anchors.rightMargin: (20 * gcd.themeScale) + parent.height
+ height: parent.height * 0.5
+ width: height
+
+ visible: type == "profile"
+
+ }
+
+
+ // Contact
ProgressBar { // LOADING ?
id: loadingProgress
property bool running
@@ -118,14 +165,26 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
hoverEnabled: true
onClicked: {
- if (displayName != "me") {
- gcd.broadcast("ResetMessagePane")
- isActive = true
- theStack.pane = theStack.messagePane
- gcd.loadMessagesPane(handle)
- if (handle.length == 32) {
- gcd.requestGroupSettings(handle)
- }
+ if (type == "contact") {
+ if (displayName != "me") {
+ gcd.broadcast("ResetMessagePane")
+ isActive = true
+ theStack.pane = theStack.messagePane
+ gcd.loadMessagesPane(handle)
+ badge = 0
+ if (handle.length == 32) {
+ gcd.requestGroupSettings(handle)
+ }
+ }
+ } else if (type == "profile") {
+ gcd.broadcast("ResetMessagePane")
+ gcd.broadcast("ResetProfile")
+ gcd.selectedProfile = handle
+ gcd.loadProfile(handle)
+ parentStack.pane = parentStack.profilePane
+ } else if (type == "button") { // Add profile button
+ profileAddEditPane.reset()
+ parentStack.pane = parentStack.addEditProfilePane
}
}
@@ -138,6 +197,27 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
}
}
+ SimpleButton {// Edit BUTTON
+ id: btnEdit
+ icon: "solid/user-edit"
+
+ anchors.right: parent.right
+
+ //rectUnread.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 1 * gcd.themeScale
+ anchors.rightMargin: 20 * gcd.themeScale
+ height: parent.height * 0.75
+
+ visible: type == "profile"
+
+
+ onClicked: {
+ profileAddEditPane.load(handle, displayName, tag)
+ parentStack.pane = parentStack.addEditProfilePane
+ }
+ }
+
Connections { // UPDATE UNREAD MESSAGES COUNTER
target: gcd
@@ -145,17 +225,29 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
isActive = false
}
- onUpdateContact: function(_handle, _displayName, _image, _server, _badge, _status, _trusted, _blocked, _loading) {
- if (handle == _handle) {
- displayName = _displayName + (_blocked == true ? " (blocked)" : "")
- image = _image
- server = _server
- badge = _badge
- status = _status
- trusted = _trusted
- blocked = _blocked
+ onUpdateContactStatus: function(_handle, _status, _loading) {
+ if (handle == _handle) {
+ status = _status
loadingProgress.visible = loadingProgress.running = loading = _loading
- }
+ }
+ }
+
+ onUpdateContactBlocked: function(_handle, _blocked) {
+ if (handle == _handle) {
+ blocked = _blocked
+ }
+ }
+
+ onUpdateContactDisplayName: function(_handle, _displayName) {
+ if (handle == _handle) {
+ displayName = _displayName + (_blocked == true ? " (blocked)" : "")
+ }
+ }
+
+ onIncContactUnreadCount: function(handle) {
+ if (handle == _handle && gcd.selectedConversation != handle) {
+ badge++
+ }
}
}
}
diff --git a/qml/widgets/Message.qml b/qml/widgets/Message.qml
index 104ee32e..7f437001 100644
--- a/qml/widgets/Message.qml
+++ b/qml/widgets/Message.qml
@@ -62,6 +62,7 @@ Item {
onClicked: {
+ gcd.createContact(from)
gcd.broadcast("ResetMessagePane")
theStack.pane = theStack.messagePane
gcd.loadMessagesPane(from)
diff --git a/qml/widgets/MyProfile.qml b/qml/widgets/MyProfile.qml
index 493b5a9b..8bb3ec0f 100644
--- a/qml/widgets/MyProfile.qml
+++ b/qml/widgets/MyProfile.qml
@@ -19,6 +19,22 @@ ColumnLayout {
property string onion
+ SimpleButton {// BACK BUTTON
+ id: btnBack
+ icon: "solid/arrow-circle-left"
+ anchors.left: parent.left
+ //anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 2
+ anchors.top: parent.top
+ anchors.topMargin: 2
+ onClicked: function() {
+ gcd.selectedProfile = "none"
+ gcd.reloadProfileList()
+ parentStack.pane = parentStack.managementPane
+ theStack.pane = theStack.emptyPane
+ }
+ }
+
Item{ height: 6 }
Item { // PROFILE IMAGE
@@ -106,7 +122,7 @@ ColumnLayout {
Layout.alignment: Qt.AlignHCenter
onUpdated: {
- gcd.updateNick(lblNick.text)
+ gcd.updateNick(onion, lblNick.text)
}
}
@@ -230,8 +246,6 @@ ColumnLayout {
lblNick.text = _nick
onion = _onion
image = _image
- parentStack.currentIndex = 1
- splashPane.running = false
}
onTorStatus: function(code, str) {
diff --git a/qml/widgets/ProfileList.qml b/qml/widgets/ProfileList.qml
new file mode 100644
index 00000000..b7c1cc57
--- /dev/null
+++ b/qml/widgets/ProfileList.qml
@@ -0,0 +1,125 @@
+import QtGraphicalEffects 1.0
+import QtQuick 2.7
+import QtQuick.Controls 2.4
+import QtQuick.Controls.Material 2.0
+import QtQuick.Layouts 1.3
+
+ColumnLayout {
+ id: root
+
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ forceActiveFocus()
+ }
+ }
+
+ Flickable { // Profile List
+ id: sv
+ clip: true
+ Layout.minimumHeight: 100
+ Layout.fillHeight: true
+ Layout.minimumWidth: parent.width
+ Layout.maximumWidth: parent.width
+ contentWidth: colContacts.width
+ contentHeight: colContacts.height
+ boundsBehavior: Flickable.StopAtBounds
+ maximumFlickVelocity: 400
+
+
+ ScrollBar.vertical: ScrollBar {
+ policy: ScrollBar.AlwaysOn
+ }
+
+ ColumnLayout {
+ id: colContacts
+ width: root.width
+ spacing: 0
+
+ Connections { // ADD/REMOVE CONTACT ENTRIES
+ target: gcd
+
+ onAddProfile: function(handle, displayName, image, tag) {
+
+ // don't add duplicates
+ console.log("ProfileList onAddProfile for: " + handle)
+ for (var i = 0; i < profilesModel.count; i++) {
+ if (profilesModel.get(i)["_handle"] == handle) {
+ return
+ }
+ }
+
+ // find index for insert (sort by onion)
+ var index = profilesModel.count-1
+ for (var i = 0; i < profilesModel.count-1; i++) {
+ if (profilesModel.get(i)["_handle"] > handle) {
+ index = i
+ break
+ }
+ }
+
+ profilesModel.insert(index,
+ {
+ "_handle": handle,
+ "_displayName": displayName,
+ "_image": image,
+ "_type": "profile",
+ "_tag": tag
+ })
+ }
+
+ /*
+ onRemoveProfile: function(handle) {
+ for(var i = 0; i < profilesModel.count; i++){
+ if(profilesModel.get(i)["_handle"] == handle) {
+ console.log("deleting contact " + profilesModel.get(i)["_handle"])
+ profilesModel.remove(i)
+ return
+ }
+ }
+ }*/
+
+ onResetProfileList: function() {
+ profilesModel.clear()
+ profilesModel.append({
+ _handle: "",
+ _displayName: qsTr("add-new-profile-btn"),
+ _image: "qrc:/qml/images/fontawesome/solid/user-plus.svg",
+ _type: "button",
+ _tag: ","
+ })
+ }
+ }
+
+ ListModel { // Profile OBJECTS ARE STORED HERE ...
+ id: profilesModel
+
+ ListElement {
+ _handle: ""
+ _displayName: qsTr("add-new-profile-btn")
+ _image: "qrc:/qml/images/fontawesome/solid/user-plus.svg"
+ _type: "button"
+ _tag: ""
+ }
+ }
+
+ Repeater {
+ model: profilesModel // ... AND DISPLAYED HERE
+ delegate: ContactRow {
+ handle: _handle
+ displayName: _displayName
+ image: _image
+ server: ""
+ badge: 0
+ status: 0
+ blocked: false
+ loading: false
+ type: _type
+ tag: _tag
+ }
+ }
+ }
+ }
+}
diff --git a/qml/widgets/RadioButton.qml b/qml/widgets/RadioButton.qml
new file mode 100644
index 00000000..187822e6
--- /dev/null
+++ b/qml/widgets/RadioButton.qml
@@ -0,0 +1,27 @@
+import QtQuick 2.7
+
+import QtQuick.Controls 2.13
+
+
+RadioButton {
+ id: control
+
+ property real size: 12
+ spacing: 0
+
+ indicator: Rectangle {
+ width: 16 * gcd.themeScale
+ height: 16 * gcd.themeScale
+ anchors.verticalCenter: parent.verticalCenter
+ radius: 9
+ border.width: 1
+
+ Rectangle {
+ anchors.fill: parent
+ visible: control.checked
+ color: "black"
+ radius: 9
+ anchors.margins: 4
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/widgets/SimpleButton.qml b/qml/widgets/SimpleButton.qml
index fe953ce0..f5bce956 100644
--- a/qml/widgets/SimpleButton.qml
+++ b/qml/widgets/SimpleButton.qml
@@ -4,7 +4,6 @@ import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.0
import QtQuick.Layouts 1.3
-import "controls" as Awesome
import "../fonts/Twemoji.js" as T
Rectangle {
diff --git a/qml/widgets/StackToolbar.qml b/qml/widgets/StackToolbar.qml
index c3b917c0..05ddc52e 100644
--- a/qml/widgets/StackToolbar.qml
+++ b/qml/widgets/StackToolbar.qml
@@ -4,7 +4,6 @@ import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.0
import QtQuick.Layouts 1.3
-import "controls" as Awesome
import "../fonts/Twemoji.js" as T
Rectangle { // OVERHEAD BAR ON STACK PANE
@@ -21,6 +20,7 @@ Rectangle { // OVERHEAD BAR ON STACK PANE
property alias aux: btnAux
property alias back: btnBack
property alias membership: btnMembership
+ property string stack: "profile" // profile(theStack) or management(parentStack)
SimpleButton {// BACK BUTTON
@@ -29,7 +29,13 @@ Rectangle { // OVERHEAD BAR ON STACK PANE
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 6
- onClicked: theStack.pane = theStack.emptyPane
+ onClicked: {
+ if (stack == "profile") {
+ theStack.pane = theStack.emptyPane
+ } else {
+ parentStack.pane = parentStack.managementPane
+ }
+ }
}
ScalingLabel { // TEXT
diff --git a/qml/widgets/TextField.qml b/qml/widgets/TextField.qml
new file mode 100644
index 00000000..6486f9d0
--- /dev/null
+++ b/qml/widgets/TextField.qml
@@ -0,0 +1,16 @@
+import QtQuick 2.7
+
+import QtQuick.Controls 2.13
+
+
+TextField {
+ color: "black"
+ font.pointSize: 10 * gcd.themeScale
+ width: 100
+
+ background: Rectangle {
+ radius: 2
+ color: windowItem.cwtch_background_color
+ border.color: windowItem.cwtch_color
+ }
+}
\ No newline at end of file
diff --git a/qml/widgets/controls/Button.qml b/qml/widgets/controls/Button.qml
deleted file mode 100644
index 1c120a50..00000000
--- a/qml/widgets/controls/Button.qml
+++ /dev/null
@@ -1,81 +0,0 @@
-/****************************************************************************
-**
-**
-** Copyright (c) 2014 Ricardo do Valle Flores de Oliveira
-**
-** $BEGIN_LICENSE:MIT$
-** Permission is hereby granted, free of charge, to any person obtaining a copy
-** of this software and associated documentation files (the "Software"), to deal
-** in the Software without restriction, including without limitation the rights
-** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-** copies of the Software, and to permit persons to whom the Software is
-** furnished to do so, subject to the following conditions:
-**
-** The above copyright notice and this permission notice shall be included in
-** all copies or substantial portions of the Software.
-**
-** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-** SOFTWARE.
-**
-**
-****************************************************************************/
-
-import QtQuick 2.0
-import QtQuick.Controls 1.0
-import QtQuick.Controls.Styles 1.0
-import QtQuick.Layouts 1.0
-
-Button {
- id: button
- property string icon
- property color color: "black"
- property font font
-
- style: ButtonStyle {
- id: buttonstyle
- property font font: button.font
- property color foregroundColor: button.color
-
- background: Item {
- Rectangle {
- id: baserect
- anchors.fill: parent
- color: "transparent"
- }
- }
-
- label: Item {
- implicitWidth: row.implicitWidth
- implicitHeight: row.implicitHeight
-
- RowLayout {
- id: row
- anchors.centerIn: parent
- spacing: 15
-
- Text {
- color: buttonstyle.foregroundColor
- font.pointSize: buttonstyle.font.pointSize * 2
- font.family: awesome.family
- renderType: Text.NativeRendering
- text: awesome.loaded ? icon : ""
- visible: !(icon === "")
- }
- Text {
- color: buttonstyle.foregroundColor
- font: buttonstyle.font
- renderType: Text.NativeRendering
- text: control.text
- visible: !(control.text === "")
-
- Layout.alignment: Qt.AlignBottom
- }
- }
- }
- }
-}
diff --git a/qml/widgets/controls/Text.qml b/qml/widgets/controls/Text.qml
deleted file mode 100644
index 8931eb17..00000000
--- a/qml/widgets/controls/Text.qml
+++ /dev/null
@@ -1,61 +0,0 @@
-/****************************************************************************
-**
-** The MIT License (MIT)
-**
-** Copyright (c) 2014 Ricardo do Valle Flores de Oliveira
-**
-** $BEGIN_LICENSE:MIT$
-** Permission is hereby granted, free of charge, to any person obtaining a copy
-** of this software and associated documentation files (the "Software"), to deal
-** in the Software without restriction, including without limitation the rights
-** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-** copies of the Software, and to permit persons to whom the Software is
-** furnished to do so, subject to the following conditions:
-**
-** The above copyright notice and this permission notice shall be included in
-** all copies or substantial portions of the Software.
-**
-** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-** SOFTWARE.
-**
-** $END_LICENSE$
-**
-****************************************************************************/
-
-import QtQuick 2.0
-import QtQuick.Controls 1.0
-import QtQuick.Layouts 1.0
-
-Text {
- id: root
-
- property alias spacing: row.spacing
- property alias text: content.text
- property color color: "black"
- property font font
- property string icon
-
- RowLayout {
- id: row
-
- Text {
- color: root.color
- font.pointSize: root.font.pointSize
- font.family: awesome.family
- renderType: Text.NativeRendering
- text: awesome.loaded ? icon : ""
- }
-
- Text {
- id: content
- color: root.color
- font.pointSize: root.font.pointSize
- renderType: Text.NativeRendering
- }
- }
-}