merge to master
the build was successful Details

This commit is contained in:
erinn 2020-01-07 17:30:20 -08:00
commit 78f4d96a9b
48 changed files with 2831 additions and 1243 deletions

View File

@ -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

View File

@ -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

View File

@ -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

9
go.mod
View File

@ -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

36
go.sum
View File

@ -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=

View File

@ -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{}))
}
}
}
}

View File

@ -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 :/")
}
}
}
}
}

View File

@ -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"

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

123
go/handlers/appHandler.go Normal file
View File

@ -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{}))
}
}
}
}

124
go/handlers/peerHandler.go Normal file
View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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)
}

281
go/ui/manager.go Normal file
View File

@ -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)
}

View File

@ -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"

View File

@ -4,239 +4,469 @@
<context>
<name>AddGroupPane</name>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="19"/>
<source>create-group-title</source>
<translation type="vanished">Gruppe Anlegen</translation>
<translation>Gruppe Anlegen</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="42"/>
<source>server-label</source>
<extracomment>Server label</extracomment>
<translation type="vanished">Server</translation>
<translation>Server</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="53"/>
<source>group-name-label</source>
<extracomment>Group name label</extracomment>
<translation type="vanished">Gruppenname</translation>
<translation>Gruppenname</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="60"/>
<source>default-group-name</source>
<extracomment>default suggested group name</extracomment>
<translation type="vanished">Tolle Gruppe</translation>
<translation>Tolle Gruppe</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="65"/>
<source>create-group-btn</source>
<extracomment>create group button</extracomment>
<translation type="vanished">Anlegen</translation>
<translation>Anlegen</translation>
</message>
</context>
<context>
<name>BulletinOverlay</name>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="203"/>
<source>new-bulletin-label</source>
<translation type="vanished">Neue Meldung</translation>
<translation>Neue Meldung</translation>
</message>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="215"/>
<source>post-new-bulletin-label</source>
<extracomment>Post a new Bulletin Post</extracomment>
<translation type="vanished">Neue Meldung veröffentlichen</translation>
<translation>Neue Meldung veröffentlichen</translation>
</message>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="221"/>
<source>title-placeholder</source>
<extracomment>title place holder text</extracomment>
<translation type="vanished">Titel...</translation>
<translation>Titel...</translation>
</message>
</context>
<context>
<name>GroupSettingsPane</name>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="42"/>
<source>server-label</source>
<translation type="vanished">Server</translation>
<translation>Server</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="53"/>
<location filename="../qml/panes/GroupSettingsPane.qml" line="74"/>
<source>copy-btn</source>
<translation type="vanished">Kopieren</translation>
<translation>Kopieren</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="63"/>
<source>invitation-label</source>
<translation type="vanished">Einladung</translation>
<translation>Einladung</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="84"/>
<source>group-name-label</source>
<translation type="vanished">Gruppenname</translation>
<translation>Gruppenname</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="93"/>
<source>save-btn</source>
<translation type="vanished">Speichern</translation>
<translation>Speichern</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="103"/>
<source>invite-to-group-label</source>
<extracomment>Invite someone to the group</extracomment>
<translation type="vanished">In die Gruppe einladen</translation>
<translation>In die Gruppe einladen</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="114"/>
<source>invite-btn</source>
<translation type="vanished">Einladen</translation>
<translation>Einladen</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="123"/>
<source>delete-btn</source>
<translation type="vanished">Löschen</translation>
<translation>Löschen</translation>
</message>
</context>
<context>
<name>InplaceEditText</name>
<message>
<location filename="../qml/widgets/InplaceEditText.qml" line="85"/>
<source>Update</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ListOverlay</name>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="184"/>
<source>add-list-item</source>
<extracomment>Add a New List Item</extracomment>
<translation type="vanished">Liste hinzufügen</translation>
<translation>Liste hinzufügen</translation>
</message>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="196"/>
<source>add-new-item</source>
<extracomment>Add a new item to the list</extracomment>
<translation type="vanished">Neues Listenelement hinzüfgen</translation>
<translation>Neues Listenelement hinzüfgen</translation>
</message>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="202"/>
<source>todo-placeholder</source>
<extracomment>Todo... placeholder text</extracomment>
<translation type="vanished">noch zu erledigen</translation>
<translation>noch zu erledigen</translation>
</message>
</context>
<context>
<name>MembershipOverlay</name>
<message>
<location filename="../qml/overlays/MembershipOverlay.qml" line="21"/>
<source>membership-description</source>
<extracomment>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.</extracomment>
<translation type="vanished">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.</translation>
<translation>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.</translation>
</message>
</context>
<context>
<name>Message</name>
<message>
<location filename="../qml/widgets/Message.qml" line="56"/>
<source>dm-tooltip</source>
<extracomment>Click to DM</extracomment>
<translation type="vanished">Klicken, um DM zu senden</translation>
<translation>Klicken, um DM zu senden</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>could-not-send-msg-error</source>
<extracomment>Could not send this message</extracomment>
<translation type="vanished">Nachricht konnte nicht gesendet werden</translation>
<translation>Nachricht konnte nicht gesendet werden</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>acknowledged-label</source>
<translation type="vanished">bestätigt</translation>
<translation>bestätigt</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>pending-label</source>
<translation type="vanished">Bestätigung ausstehend</translation>
<translation>Bestätigung ausstehend</translation>
</message>
</context>
<context>
<name>MyProfile</name>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="185"/>
<source>copy-btn</source>
<extracomment>Button for copying profile onion address to clipboard</extracomment>
<translation type="vanished">Kopieren</translation>
<translation>Kopieren</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="189"/>
<source>copied-clipboard-notification</source>
<extracomment>Copied to clipboard</extracomment>
<translation type="vanished">in die Zwischenablage kopiert</translation>
<translation>in die Zwischenablage kopiert</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="219"/>
<source>new-group-btn</source>
<extracomment>create new group button</extracomment>
<translation type="vanished">Neue Gruppe anlegen</translation>
<translation>Neue Gruppe anlegen</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="229"/>
<source>paste-address-to-add-contact</source>
<extracomment>ex: &quot;... paste an address here to add a contact ...&quot;</extracomment>
<translation type="vanished">Adresse hier hinzufügen, um einen Kontakt aufzunehmen</translation>
<translation>Adresse hier hinzufügen, um einen Kontakt aufzunehmen</translation>
</message>
</context>
<context>
<name>OverlayPane</name>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="44"/>
<source>accept-group-invite-label</source>
<extracomment>Do you want to accept the invitation to $GROUP</extracomment>
<translation type="vanished">Möchtest Du die Einladung annehmen</translation>
<translation>Möchtest Du die Einladung annehmen</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="49"/>
<source>accept-group-btn</source>
<extracomment>Accept group invite button</extracomment>
<translation type="vanished">Annehmen</translation>
<translation>Annehmen</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="59"/>
<source>reject-group-btn</source>
<extracomment>Reject Group invite button</extracomment>
<translation type="vanished">Ablehnen</translation>
<translation>Ablehnen</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="73"/>
<source>chat-btn</source>
<translation type="vanished">Chat</translation>
<translation>Chat</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="80"/>
<source>lists-btn</source>
<translation type="vanished">Listen</translation>
<translation>Listen</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="87"/>
<source>bulletins-btn</source>
<translation type="vanished">Meldungen</translation>
<translation>Meldungen</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="94"/>
<source>puzzle-game-btn</source>
<translation type="vanished">Puzzlespiel</translation>
<translation>Puzzlespiel</translation>
</message>
</context>
<context>
<name>PeerSettingsPane</name>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="42"/>
<source>address-label</source>
<translation type="vanished">Adresse</translation>
<translation>Adresse</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="53"/>
<source>copy-btn</source>
<translation type="vanished">Kopieren</translation>
<translation>Kopieren</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="57"/>
<source>copied-to-clipboard-notification</source>
<extracomment>notification: copied to clipboard</extracomment>
<translation type="vanished">in die Zwischenablage kopiert</translation>
<translation>in die Zwischenablage kopiert</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="64"/>
<source>display-name-label</source>
<translation type="vanished">Angezeigter Name</translation>
<translation>Angezeigter Name</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="73"/>
<source>save-btn</source>
<translation type="vanished">speichern</translation>
<translation>speichern</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="85"/>
<source>block-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="85"/>
<source>unblock-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="99"/>
<source>delete-btn</source>
<translation type="vanished">löschen</translation>
<translation>löschen</translation>
</message>
</context>
<context>
<name>ProfileAddEditPane</name>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="24"/>
<source>add-profile-title</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="24"/>
<source>edit-profile-title</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="92"/>
<source>profile-name</source>
<extracomment>Display name</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="32"/>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="100"/>
<source>default-profile-name</source>
<extracomment>default suggested profile name</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="81"/>
<source>profile-onion-label</source>
<extracomment>Onion</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="112"/>
<source>radio-use-password</source>
<extracomment>Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="122"/>
<source>radio-no-password</source>
<extracomment>Unencrypted (No password)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="133"/>
<source>no-password-warning</source>
<extracomment>Not using a password on this account means that all data stored locally will not be encrypted</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="140"/>
<source>current-password-label</source>
<extracomment>Current Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="154"/>
<source>password1-label</source>
<extracomment>Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="174"/>
<source>password2-label</source>
<extracomment>Reenter password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="188"/>
<source>create-profile-btn</source>
<extracomment>Create Profile || Save Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="188"/>
<source>save-profile-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="217"/>
<source>password-error-match</source>
<extracomment>Passwords do not match</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="225"/>
<source>password-change-error</source>
<extracomment>Error changing password: Supplied password rejected</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="234"/>
<source>delete-profile-btn</source>
<extracomment>Delete Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="247"/>
<source>delete-confirm-label</source>
<extracomment>Type DELETE to confirm</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="263"/>
<source>delete-profile-confirm-btn</source>
<extracomment>Really Delete Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="269"/>
<source>delete-confirm-text</source>
<extracomment>DELETE</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProfileList</name>
<message>
<location filename="../qml/widgets/ProfileList.qml" line="88"/>
<location filename="../qml/widgets/ProfileList.qml" line="101"/>
<source>add-new-profile-btn</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProfileManagerPane</name>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="26"/>
<source>enter-profile-password</source>
<extracomment>Please enter password:</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="42"/>
<source>error-0-profiles-loaded-for-password</source>
<extracomment>0 profiles loaded with that password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="52"/>
<source>unlock</source>
<extracomment>Unlock</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPane</name>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="21"/>
<source>cwtch-settings-title</source>
<extracomment>Cwtch Settings title</extracomment>
<translation type="vanished">Cwtch Einstellungen</translation>
<translation>Cwtch Einstellungen</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="47"/>
<source>version %1 builddate %2</source>
<extracomment>Version: %1 Built on: %2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="54"/>
<source>zoom-label</source>
<extracomment>Interface zoom (mostly affects text and button sizes)</extracomment>
<translation type="vanished">Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)</translation>
<translation>Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="83"/>
<source>block-unknown-label</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="90"/>
<source>large-text-label</source>
<translation type="vanished">Groß</translation>
<translation>Groß</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="98"/>
<source>default-scaling-text</source>
<extracomment>&quot;Default size text (scale factor: &quot;</extracomment>
<translation type="vanished">defaultmäßige Textgröße (Skalierungsfaktor:</translation>
<translation>defaultmäßige Textgröße (Skalierungsfaktor:</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="102"/>
<source>small-text-label</source>
<translation type="vanished">Klein</translation>
<translation>Klein</translation>
</message>
</context>
<context>
<name>StackToolbar</name>
<message>
<location filename="../qml/widgets/StackToolbar.qml" line="58"/>
<source>view-group-membership-tooltip</source>
<extracomment>View Group Membership</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

Binary file not shown.

View File

@ -4,264 +4,469 @@
<context>
<name>AddGroupPane</name>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="19"/>
<source>create-group-title</source>
<translation type="vanished">Create Group</translation>
<translation>Create Group</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="42"/>
<source>server-label</source>
<extracomment>Server label</extracomment>
<translation type="vanished">Server</translation>
<translation>Server</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="53"/>
<source>group-name-label</source>
<extracomment>Group name label</extracomment>
<translation type="vanished">Group name</translation>
<translation>Group name</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="60"/>
<source>default-group-name</source>
<extracomment>default suggested group name</extracomment>
<translation type="vanished">Awesome Group</translation>
<translation>Awesome Group</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="65"/>
<source>create-group-btn</source>
<extracomment>create group button</extracomment>
<translation type="vanished">Create</translation>
<translation>Create</translation>
</message>
</context>
<context>
<name>BulletinOverlay</name>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="203"/>
<source>new-bulletin-label</source>
<translation type="vanished">New Bulletin</translation>
<translation>New Bulletin</translation>
</message>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="215"/>
<source>post-new-bulletin-label</source>
<extracomment>Post a new Bulletin Post</extracomment>
<translation type="vanished">Post new bulletin</translation>
<translation>Post new bulletin</translation>
</message>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="221"/>
<source>title-placeholder</source>
<extracomment>title place holder text</extracomment>
<translation type="vanished">title...</translation>
<translation>title...</translation>
</message>
</context>
<context>
<name>GroupSettingsPane</name>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="42"/>
<source>server-label</source>
<translation type="vanished">Server</translation>
<translation>Server</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="53"/>
<location filename="../qml/panes/GroupSettingsPane.qml" line="74"/>
<source>copy-btn</source>
<translation type="vanished">Copy</translation>
<translation>Copy</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="63"/>
<source>invitation-label</source>
<translation type="vanished">Invitation</translation>
<translation>Invitation</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="84"/>
<source>group-name-label</source>
<translation type="vanished">Group Name</translation>
<translation>Group Name</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="93"/>
<source>save-btn</source>
<translation type="vanished">Save</translation>
<translation>Save</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="103"/>
<source>invite-to-group-label</source>
<extracomment>Invite someone to the group</extracomment>
<translation type="vanished">Invite to group</translation>
<translation>Invite to group</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="114"/>
<source>invite-btn</source>
<translation type="vanished">Invite</translation>
<translation>Invite</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="123"/>
<source>delete-btn</source>
<translation type="vanished">Delete</translation>
<translation>Delete</translation>
</message>
</context>
<context>
<name>InplaceEditText</name>
<message>
<location filename="../qml/widgets/InplaceEditText.qml" line="85"/>
<source>Update</source>
<translation>Update</translation>
</message>
</context>
<context>
<name>ListOverlay</name>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="184"/>
<source>add-list-item</source>
<extracomment>Add a New List Item</extracomment>
<translation type="vanished">Add a New List Item</translation>
<translation>Add a New List Item</translation>
</message>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="196"/>
<source>add-new-item</source>
<extracomment>Add a new item to the list</extracomment>
<translation type="vanished">Add a new item to the list</translation>
<translation>Add a new item to the list</translation>
</message>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="202"/>
<source>todo-placeholder</source>
<extracomment>Todo... placeholder text</extracomment>
<translation type="vanished">Todo...</translation>
<translation>Todo...</translation>
</message>
</context>
<context>
<name>MembershipOverlay</name>
<message>
<location filename="../qml/overlays/MembershipOverlay.qml" line="21"/>
<source>membership-description</source>
<extracomment>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.</extracomment>
<translation type="vanished">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.</translation>
<translation>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.</translation>
</message>
</context>
<context>
<name>Message</name>
<message>
<location filename="../qml/widgets/Message.qml" line="56"/>
<source>dm-tooltip</source>
<extracomment>Click to DM</extracomment>
<translation type="vanished">Click to DM</translation>
<translation>Click to DM</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>could-not-send-msg-error</source>
<extracomment>Could not send this message</extracomment>
<translation type="vanished">Could not send this message</translation>
<translation>Could not send this message</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>acknowledged-label</source>
<translation type="vanished">Acknowledged</translation>
<translation>Acknowledged</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>pending-label</source>
<translation type="vanished">Pending</translation>
<translation>Pending</translation>
</message>
</context>
<context>
<name>MyProfile</name>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="185"/>
<source>copy-btn</source>
<extracomment>Button for copying profile onion address to clipboard</extracomment>
<translation type="vanished">Copy</translation>
<translation>Copy</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="189"/>
<source>copied-clipboard-notification</source>
<extracomment>Copied to clipboard</extracomment>
<translation type="vanished">Copied to clipboard</translation>
<translation>Copied to clipboard</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="219"/>
<source>new-group-btn</source>
<extracomment>create new group button</extracomment>
<translation type="vanished">Create new group</translation>
<translation>Create new group</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="229"/>
<source>paste-address-to-add-contact</source>
<extracomment>ex: &quot;... paste an address here to add a contact ...&quot;</extracomment>
<translation type="vanished">... paste an address here to add a contact...</translation>
<translation>... paste an address here to add a contact...</translation>
</message>
</context>
<context>
<name>OverlayPane</name>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="44"/>
<source>accept-group-invite-label</source>
<extracomment>Do you want to accept the invitation to $GROUP</extracomment>
<translation type="vanished">Do you want to accept the invitation to</translation>
<translation>Do you want to accept the invitation to</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="49"/>
<source>accept-group-btn</source>
<extracomment>Accept group invite button</extracomment>
<translation type="vanished">Accept</translation>
<translation>Accept</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="59"/>
<source>reject-group-btn</source>
<extracomment>Reject Group invite button</extracomment>
<translation type="vanished">Reject</translation>
<translation>Reject</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="73"/>
<source>chat-btn</source>
<translation type="vanished">Chat</translation>
<translation>Chat</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="80"/>
<source>lists-btn</source>
<translation type="vanished">Lists</translation>
<translation>Lists</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="87"/>
<source>bulletins-btn</source>
<translation type="vanished">Bulletins</translation>
<translation>Bulletins</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="94"/>
<source>puzzle-game-btn</source>
<translation type="vanished">Puzzle Game</translation>
<translation>Puzzle Game</translation>
</message>
</context>
<context>
<name>PeerSettingsPane</name>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="42"/>
<source>address-label</source>
<translation type="vanished">Address</translation>
<translation>Address</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="53"/>
<source>copy-btn</source>
<translation type="vanished">Copy</translation>
<translation>Copy</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="57"/>
<source>copied-to-clipboard-notification</source>
<extracomment>notification: copied to clipboard</extracomment>
<translation type="vanished">Copied to Clipboard</translation>
<translation>Copied to Clipboard</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="64"/>
<source>display-name-label</source>
<translation type="vanished">Display Name</translation>
<translation>Display Name</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="73"/>
<source>save-btn</source>
<translation type="vanished">Save</translation>
<translation>Save</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="85"/>
<source>block-btn</source>
<translation type="vanished">Block Peer</translation>
<translation>Block Peer</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="85"/>
<source>unblock-btn</source>
<translation type="vanished">Unblock Peer</translation>
<translation>Unblock Peer</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="99"/>
<source>delete-btn</source>
<translation type="vanished">Delete</translation>
<translation>Delete</translation>
</message>
</context>
<context>
<name>ProfileAddEditPane</name>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="24"/>
<source>add-profile-title</source>
<translation>Add new profile</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="24"/>
<source>edit-profile-title</source>
<translation>Edit Profile</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="92"/>
<source>profile-name</source>
<extracomment>Display name</extracomment>
<translation>Display name</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="32"/>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="100"/>
<source>default-profile-name</source>
<extracomment>default suggested profile name</extracomment>
<translation>Alice</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="81"/>
<source>profile-onion-label</source>
<extracomment>Onion</extracomment>
<translation>Onion</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="112"/>
<source>radio-use-password</source>
<extracomment>Password</extracomment>
<translation>Password</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="122"/>
<source>radio-no-password</source>
<extracomment>Unencrypted (No password)</extracomment>
<translation>Unencrypted (No password)</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="133"/>
<source>no-password-warning</source>
<extracomment>Not using a password on this account means that all data stored locally will not be encrypted</extracomment>
<translation>Not using a password on this account means that all data stored locally will not be encrypted</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="140"/>
<source>current-password-label</source>
<extracomment>Current Password</extracomment>
<translation>Current Password</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="154"/>
<source>password1-label</source>
<extracomment>Password</extracomment>
<translation>Password</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="174"/>
<source>password2-label</source>
<extracomment>Reenter password</extracomment>
<translation>Reenter password</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="188"/>
<source>create-profile-btn</source>
<extracomment>Create Profile || Save Profile</extracomment>
<translation>Create Profile</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="188"/>
<source>save-profile-btn</source>
<translation>Save Profile</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="217"/>
<source>password-error-match</source>
<extracomment>Passwords do not match</extracomment>
<translation>Passwords do not match</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="225"/>
<source>password-change-error</source>
<extracomment>Error changing password: Supplied password rejected</extracomment>
<translation>Error changing password: Supplied password rejected</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="234"/>
<source>delete-profile-btn</source>
<extracomment>Delete Profile</extracomment>
<translation>Delete Profile</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="247"/>
<source>delete-confirm-label</source>
<extracomment>Type DELETE to confirm</extracomment>
<translation>Type DELETE to confirm</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="263"/>
<source>delete-profile-confirm-btn</source>
<extracomment>Really Delete Profile</extracomment>
<translation>Really Delete Profile</translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="269"/>
<source>delete-confirm-text</source>
<extracomment>DELETE</extracomment>
<translation>DELETE</translation>
</message>
</context>
<context>
<name>ProfileList</name>
<message>
<location filename="../qml/widgets/ProfileList.qml" line="88"/>
<location filename="../qml/widgets/ProfileList.qml" line="101"/>
<source>add-new-profile-btn</source>
<translation>Add new profile</translation>
</message>
</context>
<context>
<name>ProfileManagerPane</name>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="26"/>
<source>enter-profile-password</source>
<extracomment>Please enter password:</extracomment>
<translation>Please enter password</translation>
</message>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="42"/>
<source>error-0-profiles-loaded-for-password</source>
<extracomment>0 profiles loaded with that password</extracomment>
<translation>0 profiles loaded with that password</translation>
</message>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="52"/>
<source>unlock</source>
<extracomment>Unlock</extracomment>
<translation>Unlock</translation>
</message>
</context>
<context>
<name>SettingsPane</name>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="21"/>
<source>cwtch-settings-title</source>
<extracomment>Cwtch Settings title</extracomment>
<translation type="vanished">Cwtch Settings</translation>
<translation>Cwtch Settings</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="47"/>
<source>version %1 builddate %2</source>
<extracomment>Version: %1 Built on: %2</extracomment>
<translation type="vanished">Version: %1 Built on: %2</translation>
<translation>Version: %1 Built on: %2</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="54"/>
<source>zoom-label</source>
<extracomment>Interface zoom (mostly affects text and button sizes)</extracomment>
<translation type="vanished">Interface zoom (mostly affects text and button sizes)</translation>
<translation>Interface zoom (mostly affects text and button sizes)</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="83"/>
<source>block-unknown-label</source>
<translation type="vanished">Block Unknown Peers</translation>
<translation>Block Unknown Peers</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="90"/>
<source>large-text-label</source>
<translation type="vanished">Large</translation>
<translation>Large</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="98"/>
<source>default-scaling-text</source>
<extracomment>&quot;Default size text (scale factor: &quot;</extracomment>
<translation type="vanished">Default size text (scale factor:</translation>
<translation>Default size text (scale factor:</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="102"/>
<source>small-text-label</source>
<translation type="vanished">Small</translation>
<translation>Small</translation>
</message>
</context>
<context>
<name>StackToolbar</name>
<message>
<location filename="../qml/widgets/StackToolbar.qml" line="58"/>
<source>view-group-membership-tooltip</source>
<extracomment>View Group Membership</extracomment>
<translation type="vanished">View Group Membership</translation>
<translation>View Group Membership</translation>
</message>
</context>
</TS>

View File

@ -4,239 +4,469 @@
<context>
<name>AddGroupPane</name>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="19"/>
<source>create-group-title</source>
<translation type="vanished">Créer un groupe</translation>
<translation>Créer un groupe</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="42"/>
<source>server-label</source>
<extracomment>Server label</extracomment>
<translation type="vanished">Serveur</translation>
<translation>Serveur</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="53"/>
<source>group-name-label</source>
<extracomment>Group name label</extracomment>
<translation type="vanished">Groupe</translation>
<translation>Groupe</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="60"/>
<source>default-group-name</source>
<extracomment>default suggested group name</extracomment>
<translation type="vanished">Un super groupe</translation>
<translation>Un super groupe</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="65"/>
<source>create-group-btn</source>
<extracomment>create group button</extracomment>
<translation type="vanished">Créer</translation>
<translation>Créer</translation>
</message>
</context>
<context>
<name>BulletinOverlay</name>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="203"/>
<source>new-bulletin-label</source>
<translation type="vanished">Nouveau bulletin</translation>
<translation>Nouveau bulletin</translation>
</message>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="215"/>
<source>post-new-bulletin-label</source>
<extracomment>Post a new Bulletin Post</extracomment>
<translation type="vanished">Envoyer un nouveau bulletin</translation>
<translation>Envoyer un nouveau bulletin</translation>
</message>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="221"/>
<source>title-placeholder</source>
<extracomment>title place holder text</extracomment>
<translation type="vanished">titre...</translation>
<translation>titre...</translation>
</message>
</context>
<context>
<name>GroupSettingsPane</name>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="42"/>
<source>server-label</source>
<translation type="vanished">Serveur</translation>
<translation>Serveur</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="53"/>
<location filename="../qml/panes/GroupSettingsPane.qml" line="74"/>
<source>copy-btn</source>
<translation type="vanished">Copier</translation>
<translation>Copier</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="63"/>
<source>invitation-label</source>
<translation type="vanished">Invitation</translation>
<translation>Invitation</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="84"/>
<source>group-name-label</source>
<translation type="vanished">Nom du groupe</translation>
<translation>Nom du groupe</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="93"/>
<source>save-btn</source>
<translation type="vanished">Sauvegarder</translation>
<translation>Sauvegarder</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="103"/>
<source>invite-to-group-label</source>
<extracomment>Invite someone to the group</extracomment>
<translation type="vanished">Inviter quelqu&apos;un</translation>
<translation>Inviter quelqu&apos;un</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="114"/>
<source>invite-btn</source>
<translation type="vanished">Invitation</translation>
<translation>Invitation</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="123"/>
<source>delete-btn</source>
<translation type="vanished">Effacer</translation>
<translation>Effacer</translation>
</message>
</context>
<context>
<name>InplaceEditText</name>
<message>
<location filename="../qml/widgets/InplaceEditText.qml" line="85"/>
<source>Update</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ListOverlay</name>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="184"/>
<source>add-list-item</source>
<extracomment>Add a New List Item</extracomment>
<translation type="vanished">Ajouter un nouvel élément</translation>
<translation>Ajouter un nouvel élément</translation>
</message>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="196"/>
<source>add-new-item</source>
<extracomment>Add a new item to the list</extracomment>
<translation type="vanished">Ajouter un nouvel élément à la liste</translation>
<translation>Ajouter un nouvel élément à la liste</translation>
</message>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="202"/>
<source>todo-placeholder</source>
<extracomment>Todo... placeholder text</extracomment>
<translation type="vanished">A faire...</translation>
<translation>A faire...</translation>
</message>
</context>
<context>
<name>MembershipOverlay</name>
<message>
<location filename="../qml/overlays/MembershipOverlay.qml" line="21"/>
<source>membership-description</source>
<extracomment>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.</extracomment>
<translation type="vanished">Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être representatives de l&apos;ensemble des membres du groupe.</translation>
<translation>Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être representatives de l&apos;ensemble des membres du groupe.</translation>
</message>
</context>
<context>
<name>Message</name>
<message>
<location filename="../qml/widgets/Message.qml" line="56"/>
<source>dm-tooltip</source>
<extracomment>Click to DM</extracomment>
<translation type="vanished">Envoyer un message privé</translation>
<translation>Envoyer un message privé</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>could-not-send-msg-error</source>
<extracomment>Could not send this message</extracomment>
<translation type="vanished">Impossible d&apos;envoyer ce message</translation>
<translation>Impossible d&apos;envoyer ce message</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>acknowledged-label</source>
<translation type="vanished">Confirmé</translation>
<translation>Confirmé</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>pending-label</source>
<translation type="vanished">En attente</translation>
<translation>En attente</translation>
</message>
</context>
<context>
<name>MyProfile</name>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="185"/>
<source>copy-btn</source>
<extracomment>Button for copying profile onion address to clipboard</extracomment>
<translation type="vanished">Copier</translation>
<translation>Copier</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="189"/>
<source>copied-clipboard-notification</source>
<extracomment>Copied to clipboard</extracomment>
<translation type="vanished">Copié dans le presse-papier</translation>
<translation>Copié dans le presse-papier</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="219"/>
<source>new-group-btn</source>
<extracomment>create new group button</extracomment>
<translation type="vanished">Créer un nouveau groupe</translation>
<translation>Créer un nouveau groupe</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="229"/>
<source>paste-address-to-add-contact</source>
<extracomment>ex: &quot;... paste an address here to add a contact ...&quot;</extracomment>
<translation type="vanished">... coller une adresse ici pour ajouter un contact...</translation>
<translation>... coller une adresse ici pour ajouter un contact...</translation>
</message>
</context>
<context>
<name>OverlayPane</name>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="44"/>
<source>accept-group-invite-label</source>
<extracomment>Do you want to accept the invitation to $GROUP</extracomment>
<translation type="vanished">Voulez-vous accepter l&apos;invitation au groupe</translation>
<translation>Voulez-vous accepter l&apos;invitation au groupe</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="49"/>
<source>accept-group-btn</source>
<extracomment>Accept group invite button</extracomment>
<translation type="vanished">Accepter</translation>
<translation>Accepter</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="59"/>
<source>reject-group-btn</source>
<extracomment>Reject Group invite button</extracomment>
<translation type="vanished">Refuser</translation>
<translation>Refuser</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="73"/>
<source>chat-btn</source>
<translation type="vanished">Discuter</translation>
<translation>Discuter</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="80"/>
<source>lists-btn</source>
<translation type="vanished">Listes</translation>
<translation>Listes</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="87"/>
<source>bulletins-btn</source>
<translation type="vanished">Bulletins</translation>
<translation>Bulletins</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="94"/>
<source>puzzle-game-btn</source>
<translation type="vanished">Puzzle</translation>
<translation>Puzzle</translation>
</message>
</context>
<context>
<name>PeerSettingsPane</name>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="42"/>
<source>address-label</source>
<translation type="vanished">Adresse</translation>
<translation>Adresse</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="53"/>
<source>copy-btn</source>
<translation type="vanished">Copier</translation>
<translation>Copier</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="57"/>
<source>copied-to-clipboard-notification</source>
<extracomment>notification: copied to clipboard</extracomment>
<translation type="vanished">Copié dans le presse-papier</translation>
<translation>Copié dans le presse-papier</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="64"/>
<source>display-name-label</source>
<translation type="vanished">Pseudo</translation>
<translation>Pseudo</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="73"/>
<source>save-btn</source>
<translation type="vanished">Sauvegarder</translation>
<translation>Sauvegarder</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="85"/>
<source>block-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="85"/>
<source>unblock-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="99"/>
<source>delete-btn</source>
<translation type="vanished">Effacer</translation>
<translation>Effacer</translation>
</message>
</context>
<context>
<name>ProfileAddEditPane</name>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="24"/>
<source>add-profile-title</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="24"/>
<source>edit-profile-title</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="92"/>
<source>profile-name</source>
<extracomment>Display name</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="32"/>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="100"/>
<source>default-profile-name</source>
<extracomment>default suggested profile name</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="81"/>
<source>profile-onion-label</source>
<extracomment>Onion</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="112"/>
<source>radio-use-password</source>
<extracomment>Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="122"/>
<source>radio-no-password</source>
<extracomment>Unencrypted (No password)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="133"/>
<source>no-password-warning</source>
<extracomment>Not using a password on this account means that all data stored locally will not be encrypted</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="140"/>
<source>current-password-label</source>
<extracomment>Current Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="154"/>
<source>password1-label</source>
<extracomment>Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="174"/>
<source>password2-label</source>
<extracomment>Reenter password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="188"/>
<source>create-profile-btn</source>
<extracomment>Create Profile || Save Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="188"/>
<source>save-profile-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="217"/>
<source>password-error-match</source>
<extracomment>Passwords do not match</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="225"/>
<source>password-change-error</source>
<extracomment>Error changing password: Supplied password rejected</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="234"/>
<source>delete-profile-btn</source>
<extracomment>Delete Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="247"/>
<source>delete-confirm-label</source>
<extracomment>Type DELETE to confirm</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="263"/>
<source>delete-profile-confirm-btn</source>
<extracomment>Really Delete Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="269"/>
<source>delete-confirm-text</source>
<extracomment>DELETE</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProfileList</name>
<message>
<location filename="../qml/widgets/ProfileList.qml" line="88"/>
<location filename="../qml/widgets/ProfileList.qml" line="101"/>
<source>add-new-profile-btn</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProfileManagerPane</name>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="26"/>
<source>enter-profile-password</source>
<extracomment>Please enter password:</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="42"/>
<source>error-0-profiles-loaded-for-password</source>
<extracomment>0 profiles loaded with that password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="52"/>
<source>unlock</source>
<extracomment>Unlock</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPane</name>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="21"/>
<source>cwtch-settings-title</source>
<extracomment>Cwtch Settings title</extracomment>
<translation type="vanished">Préférences Cwtch</translation>
<translation>Préférences Cwtch</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="47"/>
<source>version %1 builddate %2</source>
<extracomment>Version: %1 Built on: %2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="54"/>
<source>zoom-label</source>
<extracomment>Interface zoom (mostly affects text and button sizes)</extracomment>
<translation type="vanished">Interface zoom (essentiellement la taille du texte et des composants de l&apos;interface)</translation>
<translation>Interface zoom (essentiellement la taille du texte et des composants de l&apos;interface)</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="83"/>
<source>block-unknown-label</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="90"/>
<source>large-text-label</source>
<translation type="obsolete">Large</translation>
<translation type="unfinished">Large</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="98"/>
<source>default-scaling-text</source>
<extracomment>&quot;Default size text (scale factor: &quot;</extracomment>
<translation type="vanished">Taille par défaut du texte (échelle:</translation>
<translation>Taille par défaut du texte (échelle:</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="102"/>
<source>small-text-label</source>
<translation type="vanished">Petit</translation>
<translation>Petit</translation>
</message>
</context>
<context>
<name>StackToolbar</name>
<message>
<location filename="../qml/widgets/StackToolbar.qml" line="58"/>
<source>view-group-membership-tooltip</source>
<extracomment>View Group Membership</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -4,239 +4,469 @@
<context>
<name>AddGroupPane</name>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="19"/>
<source>create-group-title</source>
<translation type="vanished">Criar Grupo</translation>
<translation>Criar Grupo</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="42"/>
<source>server-label</source>
<extracomment>Server label</extracomment>
<translation type="vanished">Servidor</translation>
<translation>Servidor</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="53"/>
<source>group-name-label</source>
<extracomment>Group name label</extracomment>
<translation type="vanished">Nome do grupo</translation>
<translation>Nome do grupo</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="60"/>
<source>default-group-name</source>
<extracomment>default suggested group name</extracomment>
<translation type="vanished">Grupo incrível</translation>
<translation>Grupo incrível</translation>
</message>
<message>
<location filename="../qml/panes/AddGroupPane.qml" line="65"/>
<source>create-group-btn</source>
<extracomment>create group button</extracomment>
<translation type="vanished">Criar</translation>
<translation>Criar</translation>
</message>
</context>
<context>
<name>BulletinOverlay</name>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="203"/>
<source>new-bulletin-label</source>
<translation type="vanished">Novo Boletim</translation>
<translation>Novo Boletim</translation>
</message>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="215"/>
<source>post-new-bulletin-label</source>
<extracomment>Post a new Bulletin Post</extracomment>
<translation type="vanished">Postar novo boletim</translation>
<translation>Postar novo boletim</translation>
</message>
<message>
<location filename="../qml/overlays/BulletinOverlay.qml" line="221"/>
<source>title-placeholder</source>
<extracomment>title place holder text</extracomment>
<translation type="vanished">título</translation>
<translation>título</translation>
</message>
</context>
<context>
<name>GroupSettingsPane</name>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="42"/>
<source>server-label</source>
<translation type="vanished">Servidor</translation>
<translation>Servidor</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="53"/>
<location filename="../qml/panes/GroupSettingsPane.qml" line="74"/>
<source>copy-btn</source>
<translation type="vanished">Copiar</translation>
<translation>Copiar</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="63"/>
<source>invitation-label</source>
<translation type="vanished">Convite</translation>
<translation>Convite</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="84"/>
<source>group-name-label</source>
<translation type="vanished">Nome do Grupo</translation>
<translation>Nome do Grupo</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="93"/>
<source>save-btn</source>
<translation type="vanished">Salvar</translation>
<translation>Salvar</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="103"/>
<source>invite-to-group-label</source>
<extracomment>Invite someone to the group</extracomment>
<translation type="vanished">Convidar ao grupo</translation>
<translation>Convidar ao grupo</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="114"/>
<source>invite-btn</source>
<translation type="vanished">Convidar</translation>
<translation>Convidar</translation>
</message>
<message>
<location filename="../qml/panes/GroupSettingsPane.qml" line="123"/>
<source>delete-btn</source>
<translation type="vanished">Deletar</translation>
<translation>Deletar</translation>
</message>
</context>
<context>
<name>InplaceEditText</name>
<message>
<location filename="../qml/widgets/InplaceEditText.qml" line="85"/>
<source>Update</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ListOverlay</name>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="184"/>
<source>add-list-item</source>
<extracomment>Add a New List Item</extracomment>
<translation type="vanished">Adicionar Item à Lista</translation>
<translation>Adicionar Item à Lista</translation>
</message>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="196"/>
<source>add-new-item</source>
<extracomment>Add a new item to the list</extracomment>
<translation type="vanished">Adicionar novo item à lista</translation>
<translation>Adicionar novo item à lista</translation>
</message>
<message>
<location filename="../qml/overlays/ListOverlay.qml" line="202"/>
<source>todo-placeholder</source>
<extracomment>Todo... placeholder text</extracomment>
<translation type="vanished">Afazer</translation>
<translation>Afazer</translation>
</message>
</context>
<context>
<name>MembershipOverlay</name>
<message>
<location filename="../qml/overlays/MembershipOverlay.qml" line="21"/>
<source>membership-description</source>
<extracomment>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.</extracomment>
<translation type="vanished">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.</translation>
<translation>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.</translation>
</message>
</context>
<context>
<name>Message</name>
<message>
<location filename="../qml/widgets/Message.qml" line="56"/>
<source>dm-tooltip</source>
<extracomment>Click to DM</extracomment>
<translation type="vanished">Clique para DM</translation>
<translation>Clique para DM</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>could-not-send-msg-error</source>
<extracomment>Could not send this message</extracomment>
<translation type="vanished">Não deu para enviar esta mensagem</translation>
<translation>Não deu para enviar esta mensagem</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>acknowledged-label</source>
<translation type="vanished">Confirmada</translation>
<translation>Confirmada</translation>
</message>
<message>
<location filename="../qml/widgets/Message.qml" line="162"/>
<source>pending-label</source>
<translation type="vanished">Pendente</translation>
<translation>Pendente</translation>
</message>
</context>
<context>
<name>MyProfile</name>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="185"/>
<source>copy-btn</source>
<extracomment>Button for copying profile onion address to clipboard</extracomment>
<translation type="vanished">Copiar</translation>
<translation>Copiar</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="189"/>
<source>copied-clipboard-notification</source>
<extracomment>Copied to clipboard</extracomment>
<translation type="vanished">Copiado</translation>
<translation>Copiado</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="219"/>
<source>new-group-btn</source>
<extracomment>create new group button</extracomment>
<translation type="vanished">Criar novo grupo</translation>
<translation>Criar novo grupo</translation>
</message>
<message>
<location filename="../qml/widgets/MyProfile.qml" line="229"/>
<source>paste-address-to-add-contact</source>
<extracomment>ex: &quot;... paste an address here to add a contact ...&quot;</extracomment>
<translation type="vanished"> cole um endereço aqui para adicionar um contato</translation>
<translation> cole um endereço aqui para adicionar um contato</translation>
</message>
</context>
<context>
<name>OverlayPane</name>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="44"/>
<source>accept-group-invite-label</source>
<extracomment>Do you want to accept the invitation to $GROUP</extracomment>
<translation type="vanished">Você quer aceitar o convite para</translation>
<translation>Você quer aceitar o convite para</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="49"/>
<source>accept-group-btn</source>
<extracomment>Accept group invite button</extracomment>
<translation type="vanished">Aceitar</translation>
<translation>Aceitar</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="59"/>
<source>reject-group-btn</source>
<extracomment>Reject Group invite button</extracomment>
<translation type="vanished">Recusar</translation>
<translation>Recusar</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="73"/>
<source>chat-btn</source>
<translation type="vanished">Chat</translation>
<translation>Chat</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="80"/>
<source>lists-btn</source>
<translation type="vanished">Listas</translation>
<translation>Listas</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="87"/>
<source>bulletins-btn</source>
<translation type="vanished">Boletins</translation>
<translation>Boletins</translation>
</message>
<message>
<location filename="../qml/panes/OverlayPane.qml" line="94"/>
<source>puzzle-game-btn</source>
<translation type="vanished">Jogo de Adivinhação</translation>
<translation>Jogo de Adivinhação</translation>
</message>
</context>
<context>
<name>PeerSettingsPane</name>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="42"/>
<source>address-label</source>
<translation type="vanished">Endereço</translation>
<translation>Endereço</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="53"/>
<source>copy-btn</source>
<translation type="vanished">Copiar</translation>
<translation>Copiar</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="57"/>
<source>copied-to-clipboard-notification</source>
<extracomment>notification: copied to clipboard</extracomment>
<translation type="vanished">Copiado</translation>
<translation>Copiado</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="64"/>
<source>display-name-label</source>
<translation type="vanished">Nome de Exibição</translation>
<translation>Nome de Exibição</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="73"/>
<source>save-btn</source>
<translation type="vanished">Salvar</translation>
<translation>Salvar</translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="85"/>
<source>block-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="85"/>
<source>unblock-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/PeerSettingsPane.qml" line="99"/>
<source>delete-btn</source>
<translation type="vanished">Deletar</translation>
<translation>Deletar</translation>
</message>
</context>
<context>
<name>ProfileAddEditPane</name>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="24"/>
<source>add-profile-title</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="24"/>
<source>edit-profile-title</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="92"/>
<source>profile-name</source>
<extracomment>Display name</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="32"/>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="100"/>
<source>default-profile-name</source>
<extracomment>default suggested profile name</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="81"/>
<source>profile-onion-label</source>
<extracomment>Onion</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="112"/>
<source>radio-use-password</source>
<extracomment>Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="122"/>
<source>radio-no-password</source>
<extracomment>Unencrypted (No password)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="133"/>
<source>no-password-warning</source>
<extracomment>Not using a password on this account means that all data stored locally will not be encrypted</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="140"/>
<source>current-password-label</source>
<extracomment>Current Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="154"/>
<source>password1-label</source>
<extracomment>Password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="174"/>
<source>password2-label</source>
<extracomment>Reenter password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="188"/>
<source>create-profile-btn</source>
<extracomment>Create Profile || Save Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="188"/>
<source>save-profile-btn</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="217"/>
<source>password-error-match</source>
<extracomment>Passwords do not match</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="225"/>
<source>password-change-error</source>
<extracomment>Error changing password: Supplied password rejected</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="234"/>
<source>delete-profile-btn</source>
<extracomment>Delete Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="247"/>
<source>delete-confirm-label</source>
<extracomment>Type DELETE to confirm</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="263"/>
<source>delete-profile-confirm-btn</source>
<extracomment>Really Delete Profile</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileAddEditPane.qml" line="269"/>
<source>delete-confirm-text</source>
<extracomment>DELETE</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProfileList</name>
<message>
<location filename="../qml/widgets/ProfileList.qml" line="88"/>
<location filename="../qml/widgets/ProfileList.qml" line="101"/>
<source>add-new-profile-btn</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProfileManagerPane</name>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="26"/>
<source>enter-profile-password</source>
<extracomment>Please enter password:</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="42"/>
<source>error-0-profiles-loaded-for-password</source>
<extracomment>0 profiles loaded with that password</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/ProfileManagerPane.qml" line="52"/>
<source>unlock</source>
<extracomment>Unlock</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPane</name>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="21"/>
<source>cwtch-settings-title</source>
<extracomment>Cwtch Settings title</extracomment>
<translation type="vanished">Configurações do Cwtch</translation>
<translation>Configurações do Cwtch</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="47"/>
<source>version %1 builddate %2</source>
<extracomment>Version: %1 Built on: %2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="54"/>
<source>zoom-label</source>
<extracomment>Interface zoom (mostly affects text and button sizes)</extracomment>
<translation type="vanished">Zoom da interface (afeta principalmente tamanho de texto e botões)</translation>
<translation>Zoom da interface (afeta principalmente tamanho de texto e botões)</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="83"/>
<source>block-unknown-label</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="90"/>
<source>large-text-label</source>
<translation type="vanished">Grande</translation>
<translation>Grande</translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="98"/>
<source>default-scaling-text</source>
<extracomment>&quot;Default size text (scale factor: &quot;</extracomment>
<translation type="vanished">Texto tamanho padrão (fator de escala: </translation>
<translation>Texto tamanho padrão (fator de escala: </translation>
</message>
<message>
<location filename="../qml/panes/SettingsPane.qml" line="102"/>
<source>small-text-label</source>
<translation type="vanished">Pequeno</translation>
<translation>Pequeno</translation>
</message>
</context>
<context>
<name>StackToolbar</name>
<message>
<location filename="../qml/widgets/StackToolbar.qml" line="58"/>
<source>view-group-membership-tooltip</source>
<extracomment>View Group Membership</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

28
main.go
View File

@ -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
}

View File

@ -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()
}
}
}
}

View File

@ -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"

View File

@ -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 {

View File

@ -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"

View File

@ -105,7 +105,6 @@ ColumnLayout {
handle: _handle
displayName: _displayName
image: _image
trusted: true
blocked: false
background: false
}

View File

@ -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")

View File

@ -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")

View File

@ -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
}
}

View File

@ -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")

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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"
}
}
}
}
}

View File

@ -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

View File

@ -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++
}
}
}
}

View File

@ -62,6 +62,7 @@ Item {
onClicked: {
gcd.createContact(from)
gcd.broadcast("ResetMessagePane")
theStack.pane = theStack.messagePane
gcd.loadMessagesPane(from)

View File

@ -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) {

125
qml/widgets/ProfileList.qml Normal file
View File

@ -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
}
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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 {

View File

@ -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

16
qml/widgets/TextField.qml Normal file
View File

@ -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
}
}

View File

@ -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
}
}
}
}
}

View File

@ -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
}
}
}