Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Sarah Jamie Lewis | a784ac1937 |
134
.drone.yml
|
@ -8,7 +8,7 @@ clone:
|
|||
|
||||
steps:
|
||||
- name: clone
|
||||
image: cirrusci/flutter:2.5.3
|
||||
image: cirrusci/flutter:dev
|
||||
environment:
|
||||
buildbot_key_b64:
|
||||
from_secret: buildbot_key_b64
|
||||
|
@ -20,17 +20,17 @@ steps:
|
|||
# force by pass of ssh host key check, less secure
|
||||
- ssh-keyscan -H git.openprivacy.ca >> ~/.ssh/known_hosts
|
||||
# use Drone ssh var instead of hardcode to allow forks to build (gogs@git.openprivacy.ca:cwtch.im/cwtch-ui.git)
|
||||
- git clone gogs@git.openprivacy.ca:$DRONE_REPO.git .
|
||||
- git clone $DRONE_GIT_SSH_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
|
||||
- name: fetch
|
||||
image: cirrusci/flutter:2.5.3
|
||||
image: cirrusci/flutter:dev
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /root/.pub-cache
|
||||
commands:
|
||||
- ./fetch-tor.sh
|
||||
- echo `git describe --tags --abbrev=1` > VERSION
|
||||
- echo `git describe --tags` > VERSION
|
||||
- echo `date +%G-%m-%d-%H-%M` > BUILDDATE
|
||||
- flutter pub get
|
||||
- mkdir deploy
|
||||
|
@ -47,7 +47,7 @@ steps:
|
|||
# #Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting
|
||||
|
||||
- name: build-linux
|
||||
image: openpriv/flutter-desktop:linux-fstable-2.5.3
|
||||
image: openpriv/flutter-desktop:linux-dev
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /root/.pub-cache
|
||||
|
@ -61,7 +61,7 @@ steps:
|
|||
- rm -r cwtch
|
||||
|
||||
- name: test-build-android
|
||||
image: cirrusci/flutter:2.5.3
|
||||
image: cirrusci/flutter:dev
|
||||
when:
|
||||
event: pull_request
|
||||
volumes:
|
||||
|
@ -71,7 +71,7 @@ steps:
|
|||
- flutter build apk --debug
|
||||
|
||||
- name: build-android
|
||||
image: cirrusci/flutter:2.5.3
|
||||
image: cirrusci/flutter:dev
|
||||
when:
|
||||
event: push
|
||||
environment:
|
||||
|
@ -95,7 +95,7 @@ steps:
|
|||
#- cp build/app/outputs/flutter-apk/app-debug.apk deploy/android
|
||||
|
||||
- name: widget-tests
|
||||
image: cirrusci/flutter:2.5.3
|
||||
image: cirrusci/flutter:dev
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /root/.pub-cache
|
||||
|
@ -117,7 +117,7 @@ steps:
|
|||
- echo $BUILDFILES_KEY > ~/id_rsab64
|
||||
- base64 -d ~/id_rsab64 > ~/id_rsa
|
||||
- chmod 400 ~/id_rsa
|
||||
- export DIR=flwtch-`cat BUILDDATE`-`cat VERSION`
|
||||
- export DIR=flwtch-`cat VERSION`-`cat BUILDDATE`
|
||||
- mv deploy $DIR
|
||||
- cp -r coverage/html $DIR/coverage-tests
|
||||
- cp -r test/failures $DIR/test-failures || true
|
||||
|
@ -125,7 +125,8 @@ steps:
|
|||
- find . -type f -exec sha256sum {} \; > ./../sha256s.txt
|
||||
- mv ./../sha256s.txt .
|
||||
- cd ..
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
# TODO: do deployment once files actaully compile
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
- name: notify-email
|
||||
image: drillster/drone-email
|
||||
|
@ -153,7 +154,7 @@ volumes:
|
|||
temp: {}
|
||||
|
||||
trigger:
|
||||
#repo: cwtch.im/cwtch-ui # allow forks to build?
|
||||
repo: cwtch.im/cwtch-ui
|
||||
branch: trunk
|
||||
event:
|
||||
- push
|
||||
|
@ -174,7 +175,7 @@ clone:
|
|||
|
||||
steps:
|
||||
- name: clone
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
environment:
|
||||
buildbot_key_b64:
|
||||
from_secret: buildbot_key_b64
|
||||
|
@ -186,22 +187,22 @@ steps:
|
|||
- git init
|
||||
# -o UserKnownHostsFile=../known_hosts
|
||||
- git config core.sshCommand 'ssh -o StrictHostKeyChecking=no -i ../id_rsa'
|
||||
- git remote add origin gogs@git.openprivacy.ca:$Env:DRONE_REPO.git
|
||||
- git remote add origin $Env:DRONE_GIT_SSH_URL
|
||||
- git pull origin trunk
|
||||
- git fetch --tags
|
||||
- git checkout $Env:DRONE_COMMIT
|
||||
|
||||
- name: fetch
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
commands:
|
||||
- powershell -command "Invoke-WebRequest -Uri https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-win64-0.4.6.5.zip -OutFile tor.zip"
|
||||
- powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '7917561a7a063440a1ddfa9cb544ab9ffd09de84cea3dd66e3cc9cd349dd9f85b74a522ec390d7a974bc19b424c4d53af60e57bbc47e763d13cab6a203c4592f' ) { Write-Error 'tor.zip sha512sum mismatch' }"
|
||||
- git describe --tags --abbrev=1 > VERSION
|
||||
- powershell -command "Invoke-WebRequest -Uri https://dist.torproject.org/torbrowser/10.0.18/tor-win64-0.4.5.9.zip -OutFile tor.zip"
|
||||
- powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '72764eb07ad8ab511603aba0734951ca003989f5f4686af91ba220217b4a8a4bcc5f571b59f52c847932f6efedf847b111621983050fcddbb8099d43ca66fb07' ) { Write-Error 'tor.zip sha512sum mismatch' }"
|
||||
- git describe --tags > VERSION
|
||||
- powershell -command "Get-Date -Format 'yyyy-MM-dd-HH-mm'" > BUILDDATE
|
||||
- .\fetch-libcwtch-go.ps1
|
||||
|
||||
- name: build-windows
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
commands:
|
||||
- flutter pub get
|
||||
- $Env:version += type .\VERSION
|
||||
|
@ -212,9 +213,9 @@ steps:
|
|||
# flutter hasn't worked out it's packaging of required dll's so we have to resort to this manual nonsense
|
||||
# https://github.com/google/flutter-desktop-embedding/issues/587
|
||||
# https://github.com/flutter/flutter/issues/53167
|
||||
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\vcruntime140.dll $Env:releasedir
|
||||
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\vcruntime140_1.dll $Env:releasedir
|
||||
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\msvcp140.dll $Env:releasedir
|
||||
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\vcruntime140.dll $Env:releasedir
|
||||
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\vcruntime140_1.dll $Env:releasedir
|
||||
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\msvcp140.dll $Env:releasedir
|
||||
- copy README.md $Env:releasedir\
|
||||
- copy windows\*.bat $Env:releasedir\
|
||||
- powershell -command "Expand-Archive -Path tor.zip -DestinationPath $Env:releasedir\Tor"
|
||||
|
@ -237,7 +238,7 @@ steps:
|
|||
- $Env:zipsha = $Env:zip + '.sha512'
|
||||
- $Env:msix = 'cwtch-install-' + $Env:version + '.msix'
|
||||
- $Env:msixsha = $Env:msix + '.sha512'
|
||||
- $Env:buildname = 'flwtch-win-' + $Env:builddate + '-' + $Env:version
|
||||
- $Env:buildname = 'flwtch-win-' + $Env:version + '-' + $Env:builddate
|
||||
- $Env:builddir = $Env:buildname
|
||||
- echo $Env:pfx > codesign.pfx.b64
|
||||
- certutil -decode codesign.pfx.b64 codesign.pfx
|
||||
|
@ -257,7 +258,7 @@ steps:
|
|||
- move *.sha512 deploy\$Env:builddir
|
||||
|
||||
- name: deploy-windows
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
when:
|
||||
event: push
|
||||
status: [ success ]
|
||||
|
@ -267,94 +268,11 @@ steps:
|
|||
commands:
|
||||
- echo $Env:BUILDFILES_KEY > id_rsab64
|
||||
- certutil -decode id_rsab64 id_rsa
|
||||
- scp -r -o StrictHostKeyChecking=no -i id_rsa deploy\\* buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
- scp -r -o StrictHostKeyChecking=no -i id_rsa deploy\\* buildfiles@openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
trigger:
|
||||
# repo: cwtch.im/cwtch-ui # allow forks to build?
|
||||
repo: cwtch.im/cwtch-ui
|
||||
branch: trunk
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: exec
|
||||
name: macos
|
||||
|
||||
platform:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
environment:
|
||||
buildbot_key_b64:
|
||||
from_secret: buildbot_key_b64
|
||||
commands:
|
||||
- mkdir ~/.ssh
|
||||
- echo $buildbot_key_b64 > ~/.ssh/id_rsa.b64
|
||||
- base64 -d ~/.ssh/id_rsa.b64 > ~/.ssh/id_rsa
|
||||
- chmod 400 ~/.ssh/id_rsa
|
||||
# force by pass of ssh host key check, less secure
|
||||
- ssh-keyscan -H git.openprivacy.ca >> ~/.ssh/known_hosts
|
||||
- git init
|
||||
- git config core.sshCommand 'ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa'
|
||||
- git remote add origin gogs@git.openprivacy.ca:$DRONE_REPO.git
|
||||
- git pull origin trunk
|
||||
- git fetch --tags
|
||||
- git checkout $DRONE_COMMIT
|
||||
# use Drone ssh var instead of hardcode to allow forks to build (gogs@git.openprivacy.ca:cwtch.im/cwtch-ui.git)
|
||||
#- git clone gogs@git.openprivacy.ca:$DRONE_REPO.git .
|
||||
#- git checkout $DRONE_COMMIT
|
||||
|
||||
- name: fetch
|
||||
commands:
|
||||
- ./fetch-tor-macos.sh
|
||||
- echo `git describe --tags --abbrev=1` > VERSION
|
||||
- echo `date +%G-%m-%d-%H-%M` > BUILDDATE
|
||||
- export PATH=$PATH:/Users/Dan/development/flutter/bin
|
||||
- flutter pub get
|
||||
- mkdir deploy
|
||||
- ./fetch-libcwtch-go-macos.sh
|
||||
- gem install --user-install cocoapods
|
||||
|
||||
- name: build-macos
|
||||
commands:
|
||||
- export PATH=$PATH:/Users/Dan/development/flutter/bin
|
||||
- export GEM_HOME=$HOME/.gem
|
||||
- export PATH=$GEM_HOME/ruby/2.6.0/bin:$PATH
|
||||
- flutter config --enable-macos-desktop
|
||||
- flutter build macos --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE`
|
||||
- export PATH=$PATH:/usr/local/bin #create-dmg
|
||||
- macos/package-release.sh
|
||||
- mkdir -p deploy
|
||||
- mv Cwtch.dmg deploy
|
||||
|
||||
- name: deploy-buildfiles
|
||||
environment:
|
||||
BUILDFILES_KEY:
|
||||
from_secret: buildfiles_key
|
||||
when:
|
||||
event: push
|
||||
status: [ success ]
|
||||
commands:
|
||||
- echo $BUILDFILES_KEY > ~/id_rsab64
|
||||
- base64 -d ~/id_rsab64 > ~/id_rsa
|
||||
- chmod 400 ~/id_rsa
|
||||
- export DIR=flwtch-macos-`cat BUILDDATE`-`cat VERSION`
|
||||
- mv deploy $DIR
|
||||
- cd $DIR
|
||||
- find . -type f -exec shasum -a 512 {} \; > ./../sha512s.txt
|
||||
- mv ./../sha512s.txt .
|
||||
- cd ..
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
trigger:
|
||||
#repo: cwtch.im/cwtch-ui # allow forks to build?
|
||||
branch: trunk
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
|
@ -40,10 +40,8 @@ app.*.symbols
|
|||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
linux/tor
|
||||
linux/libCwtch.so
|
||||
libCwtch.so
|
||||
android/cwtch/cwtch.aar
|
||||
android/app/src/main/jniLibs/*/libtor.so
|
||||
coverage
|
||||
test/failures
|
||||
.gradle
|
||||
.gradle
|
|
@ -1 +0,0 @@
|
|||
2021-11-09-18-25-v1.4.2
|
|
@ -1 +1 @@
|
|||
2021-11-09-23-25-v1.4.2
|
||||
v1.0.0-29-g41ae09d-2021-07-08-20-22
|
||||
|
|
44
README.md
|
@ -12,65 +12,40 @@ This README covers build instructions, for information on Cwtch itself please go
|
|||
- `install.home.sh` installs the app into your home directory
|
||||
- `install.sys.sh` as root to install system wide
|
||||
- or run out of the unziped directory
|
||||
- MacOS: Available from [https://cwtch.im/download/](https://cwtch.im/download/) as a .dmg
|
||||
|
||||
## Running
|
||||
|
||||
Cwtch processes the following environment variables:
|
||||
- `CWTCH_HOME=` overrides the default storage path of `~/.cwtch` with what ever you choose
|
||||
- `LOG_FILE=` will reroute all of libcwtch-go's logging to the specified file instead of the console
|
||||
- `LOG_FILE=` will reroute all of libcwtch-go's logging to the specified file instead of the console
|
||||
- `LOG_LEVEL=debug` will set the log level to debug instead of info
|
||||
|
||||
## Building
|
||||
|
||||
### Getting Started
|
||||
|
||||
First you will need a valid [flutter sdk installation](https://flutter.dev/docs/get-started/install).
|
||||
First you will need a valid [flutter sdk installation](https://flutter.dev/docs/get-started/install)
|
||||
and run `flutter pub get` to fetch dependencies.
|
||||
|
||||
You will probably want to disable Analytics on the Flutter Tool: `flutter config --no-analytics`
|
||||
|
||||
This project uses the flutter `stable` channel
|
||||
|
||||
Once flutter is set up, run `flutter pub get` from this project folder to fetch dependencies.
|
||||
|
||||
By default a development version is built, which loads profiles from `$CWTCH_HOME/dev/`. This is so that you can build
|
||||
and test development builds with alternative profiles while running a release/stable version of Cwtch uninterrupted.
|
||||
To build a release version and load normal profiles, use `build-release.sh X` instead of `flutter build X`
|
||||
|
||||
### Building on Linux (for Linux)
|
||||
|
||||
- copy `libCwtch-go.so` to `linux/`, or run `fetch-libcwtch-go.sh` to download it
|
||||
- set `LD_LIBRARY_PATH="$PWD/linux"`
|
||||
- copy a `tor` binary to `linux/` or run `fetch-tor.sh` to download one
|
||||
- run `flutter config --enable-linux-desktop` if you've never done so before
|
||||
- optional: launch cwtch-ui debug build by running `flutter run -d linux`
|
||||
- to build cwtch-ui, run `flutter build linux`
|
||||
- optional: launch cwtch-ui release build with `env LD_LIBRARY_PATH=linux ./build/linux/x64/release/bundle/cwtch`
|
||||
- to package the build, run `linux/package-release.sh`
|
||||
- run `fetch-libcwtch-go.sh`libCwtch-go to fetch a prebuild version of `libCwtch-go.so` go to `./linux`. Include `./linux` in `LD_LIBRARY_PATH`
|
||||
- run `fetch-tor.sh` and/or ensure that `tor` is in `$PATH`
|
||||
- run `flutter run -d linux`
|
||||
|
||||
### Building on Windows (for Windows)
|
||||
|
||||
- copy `libCwtch.dll` to `windows/`, or run `fetch-libcwtch-go.ps1` to download it
|
||||
- run `fetch-libcwtch-go.ps1` to fetch a prebuild version of `libCwtch.dll`
|
||||
- run `fetch-tor-win.ps1` to fetch Tor for windows
|
||||
- optional: launch cwtch-ui debug build by running `flutter run -d windows`
|
||||
- to build cwtch-ui, run `flutter build windows`
|
||||
- optional: to run the release build:
|
||||
- `cp windows/libCwtch.dll .`
|
||||
- `./build/windows/runner/Release/cwtch.exe`
|
||||
- run `flutter run -d windows`
|
||||
|
||||
### Building on Linux/Windows (for Android)
|
||||
|
||||
- Follow the steps above to fetch `libCwtch-go` and `tor` (these will fetch Android versions of these binaries also)
|
||||
- run `flutter run` with an Android phone connect via USB (or some other valid debug mode)
|
||||
|
||||
### Building on MacOS
|
||||
|
||||
- Cocaopods is required, you may need to `gem install cocaopods -v 1.9.3`
|
||||
- copy `libCwtch.dylib` into the root folder, or run `fetch-libcwtch-go-macos.sh` to download it
|
||||
- run `fetch-tor-macos.sh` to fetch Tor or Download and install Tor Browser and `cp -r /Applications/Tor\ Browser.app/Contents/MacOS/Tor ./macos/`
|
||||
- `flutter build macos`
|
||||
- optional: launch cwtch-ui release build with `./build/macos/Build/Products/Release/Cwtch.app/Contents/MacOS/Cwtch`
|
||||
- To package the UI: `./macos/package-release.sh`, which results in a Cwtch.dmg that has libCwtch.dylib and tor in it as well and can be installed into Applications
|
||||
|
||||
### Known Platform Issues
|
||||
|
||||
- **Windows**: Flutter engine has a [known bug](https://github.com/flutter/flutter/issues/75675) around the Right Shift key being sticky.
|
||||
|
@ -93,7 +68,6 @@ In Lokalise, hit Download and make sure:
|
|||
* Format is set to "Flutter (.arb)
|
||||
* Output filename is set to `l10n/intl_%LANG_ISO%.%FORMAT%`
|
||||
* Empty translations is set to "Replace with base language"
|
||||
* Order "Last Update"
|
||||
|
||||
Build, download and unzip the output, overwriting `lib/l10n`. The next time Flwtch is built, Flutter will notice the changes and update `app_localizations.dart` accordingly (thanks to `generate:true` in `pubspec.yaml`).
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 29
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
@ -48,7 +48,7 @@ android {
|
|||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "im.cwtch.flwtch"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
targetSdkVersion 29
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
|
|
@ -48,11 +48,4 @@
|
|||
|
||||
<!--Meeded to check if activity is foregrounded or if messages from the service should be queued-->
|
||||
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
@ -15,10 +15,6 @@ import cwtch.Cwtch
|
|||
import io.flutter.FlutterInjector
|
||||
import org.json.JSONObject
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption
|
||||
import android.net.Uri
|
||||
|
||||
class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||
CoroutineWorker(context, parameters) {
|
||||
|
@ -60,7 +56,6 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
if (Cwtch.startCwtch(appDir, torPath) != 0.toLong()) return Result.failure()
|
||||
|
||||
Log.i("FlwtchWorker.kt", "startCwtch success, starting coroutine AppbusEvent loop...")
|
||||
val downloadIDs = mutableMapOf<String, Int>()
|
||||
while(true) {
|
||||
val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent())
|
||||
if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") {
|
||||
|
@ -92,71 +87,12 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
.setContentTitle(data.getString("Nick"))
|
||||
.setContentText("New message")//todo: translate
|
||||
.setLargeIcon(BitmapFactory.decodeStream(fh))
|
||||
.setSmallIcon(R.mipmap.knott_transparent)
|
||||
.setSmallIcon(R.mipmap.knott)
|
||||
.setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification)
|
||||
}
|
||||
} else if (evt.EventType == "FileDownloadProgressUpdate") {
|
||||
try {
|
||||
val data = JSONObject(evt.Data);
|
||||
val fileKey = data.getString("FileKey");
|
||||
val title = data.getString("NameSuggestion");
|
||||
val progress = data.getString("Progress").toInt();
|
||||
val progressMax = data.getString("FileSizeInChunks").toInt();
|
||||
if (!downloadIDs.containsKey(fileKey)) {
|
||||
downloadIDs.put(fileKey, downloadIDs.count());
|
||||
}
|
||||
var dlID = downloadIDs.get(fileKey);
|
||||
if (dlID == null) {
|
||||
dlID = 0;
|
||||
}
|
||||
if (progress >= 0) {
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createDownloadNotificationChannel(fileKey, fileKey)
|
||||
} else {
|
||||
// If earlier version channel ID is not used
|
||||
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||
""
|
||||
};
|
||||
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setOngoing(true)
|
||||
.setContentTitle("Downloading")//todo: translate
|
||||
.setContentText(title)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setProgress(progressMax, progress, false)
|
||||
.setSound(null)
|
||||
//.setSilent(true)
|
||||
.build();
|
||||
notificationManager.notify(dlID, newNotification);
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.i("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace());
|
||||
}
|
||||
} else if (evt.EventType == "FileDownloaded") {
|
||||
Log.i("FlwtchWorker", "file downloaded!");
|
||||
val data = JSONObject(evt.Data);
|
||||
val tempFile = data.getString("TempFile");
|
||||
val fileKey = data.getString("FileKey");
|
||||
if (tempFile != "") {
|
||||
val filePath = data.getString("FilePath");
|
||||
Log.i("FlwtchWorker", "moving "+tempFile+" to "+filePath);
|
||||
val sourcePath = Paths.get(tempFile);
|
||||
val targetUri = Uri.parse(filePath);
|
||||
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri);
|
||||
val bytesWritten = Files.copy(sourcePath, os);
|
||||
Log.i("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes");
|
||||
if (bytesWritten != 0L) {
|
||||
os?.flush();
|
||||
os?.close();
|
||||
Files.delete(sourcePath);
|
||||
}
|
||||
}
|
||||
if (downloadIDs.containsKey(fileKey)) {
|
||||
notificationManager.cancel(downloadIDs.get(fileKey)?:0);
|
||||
}
|
||||
}
|
||||
|
||||
Intent().also { intent ->
|
||||
|
@ -221,34 +157,6 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val target = (a.get("target") as? String) ?: ""
|
||||
Cwtch.sendInvitation(profile, handle, target)
|
||||
}
|
||||
"ShareFile" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val filepath = (a.get("filepath") as? String) ?: ""
|
||||
Cwtch.shareFile(profile, handle, filepath)
|
||||
}
|
||||
"DownloadFile" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val filepath = (a.get("filepath") as? String) ?: ""
|
||||
val manifestpath = (a.get("manifestpath") as? String) ?: ""
|
||||
val filekey = (a.get("filekey") as? String) ?: ""
|
||||
// FIXME: Prevent spurious calls by Intent
|
||||
if (profile != "") {
|
||||
Cwtch.downloadFile(profile, handle, filepath, manifestpath, filekey)
|
||||
}
|
||||
}
|
||||
"CheckDownloadStatus" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val fileKey = (a.get("fileKey") as? String) ?: ""
|
||||
Cwtch.checkDownloadStatus(profile, fileKey)
|
||||
}
|
||||
"VerifyOrResumeDownload" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val fileKey = (a.get("fileKey") as? String) ?: ""
|
||||
Cwtch.verifyOrResumeDownload(profile, handle, fileKey)
|
||||
}
|
||||
"SendProfileEvent" -> {
|
||||
val onion = (a.get("onion") as? String) ?: ""
|
||||
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
|
||||
|
@ -284,76 +192,25 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val pass = (a.get("pass") as? String) ?: ""
|
||||
Cwtch.deleteProfile(profile, pass)
|
||||
}
|
||||
"ArchiveConversation" -> {
|
||||
"LeaveConversation" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val contactHandle = (a.get("handle") as? String) ?: ""
|
||||
Cwtch.archiveConversation(profile, contactHandle)
|
||||
val contactHandle = (a.get("contactHandle") as? String) ?: ""
|
||||
Cwtch.leaveConversation(profile, contactHandle)
|
||||
}
|
||||
"DeleteContact" -> {
|
||||
"LeaveGroup" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
Cwtch.deleteContact(profile, handle)
|
||||
val groupHandle = (a.get("groupHandle") as? String) ?: ""
|
||||
Cwtch.leaveGroup(profile, groupHandle)
|
||||
}
|
||||
"RejectInvite" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val groupHandle = (a.get("groupHandle") as? String) ?: ""
|
||||
Cwtch.rejectInvite(profile, groupHandle)
|
||||
}
|
||||
"SetProfileAttribute" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val key = (a.get("Key") as? String) ?: ""
|
||||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setProfileAttribute(profile, key, v)
|
||||
}
|
||||
"SetContactAttribute" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val contact = (a.get("Contact") as? String) ?: ""
|
||||
val key = (a.get("Key") as? String) ?: ""
|
||||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setContactAttribute(profile, contact, key, v)
|
||||
}
|
||||
"Shutdown" -> {
|
||||
Cwtch.shutdownCwtch();
|
||||
return Result.success()
|
||||
}
|
||||
"LoadServers" -> {
|
||||
val password = (a.get("Password") as? String) ?: ""
|
||||
Cwtch.loadServers(password)
|
||||
}
|
||||
"CreateServer" -> {
|
||||
val password = (a.get("Password") as? String) ?: ""
|
||||
val desc = (a.get("Description") as? String) ?: ""
|
||||
val autostart = (a.get("Autostart") as? Boolean) ?: false
|
||||
Cwtch.createServer(password, desc, autostart)
|
||||
}
|
||||
"DeleteServer" -> {
|
||||
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
|
||||
val password = (a.get("Password") as? String) ?: ""
|
||||
Cwtch.deleteServer(serverOnion, password)
|
||||
}
|
||||
"LaunchServers" -> {
|
||||
Cwtch.launchServers()
|
||||
}
|
||||
"LaunchServer" -> {
|
||||
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
|
||||
Cwtch.launchServer(serverOnion)
|
||||
}
|
||||
"StopServer" -> {
|
||||
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
|
||||
Cwtch.stopServer(serverOnion)
|
||||
}
|
||||
"StopServers" -> {
|
||||
Cwtch.stopServers()
|
||||
}
|
||||
"DestroyServers" -> {
|
||||
Cwtch.destroyServers()
|
||||
}
|
||||
"SetServerAttribute" -> {
|
||||
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
|
||||
val key = (a.get("Key") as? String) ?: ""
|
||||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setServerAttribute(serverOnion, key, v)
|
||||
}
|
||||
else -> return Result.failure()
|
||||
}
|
||||
return Result.success()
|
||||
|
@ -383,7 +240,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
.setContentTitle(title)
|
||||
.setTicker(title)
|
||||
.setContentText(progress)
|
||||
.setSmallIcon(R.mipmap.knott_transparent)
|
||||
.setSmallIcon(R.mipmap.knott)
|
||||
.setOngoing(true)
|
||||
// Add the cancel action to the notification which can
|
||||
// be used to cancel the worker
|
||||
|
@ -411,15 +268,6 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
return channelId
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createDownloadNotificationChannel(channelId: String, channelName: String): String{
|
||||
val chan = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW)
|
||||
chan.lightColor = Color.MAGENTA
|
||||
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||
notificationManager.createNotificationChannel(chan)
|
||||
return channelId
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_METHOD = "KEY_METHOD"
|
||||
const val KEY_ARGS = "KEY_ARGS"
|
||||
|
|
|
@ -12,25 +12,17 @@ import android.view.Window
|
|||
import androidx.lifecycle.Observer
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.work.*
|
||||
|
||||
import io.flutter.embedding.android.SplashScreen
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import io.flutter.plugin.common.ErrorLogResult
|
||||
|
||||
import org.json.JSONObject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.content.ContentUris
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.database.Cursor
|
||||
import android.provider.MediaStore
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
override fun provideSplashScreen(): SplashScreen? = SplashView()
|
||||
|
||||
|
@ -55,13 +47,6 @@ class MainActivity: FlutterActivity() {
|
|||
private var notificationClickChannel: MethodChannel? = null
|
||||
private var shutdownClickChannel: MethodChannel? = null
|
||||
|
||||
// "Download to..." prompt extra arguments
|
||||
private val FILEPICKER_REQUEST_CODE = 234
|
||||
private var dlToProfile = ""
|
||||
private var dlToHandle = ""
|
||||
private var dlToFileKey = ""
|
||||
|
||||
// handles clicks received from outside the app (ie, notifications)
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
if (notificationClickChannel == null || intent.extras == null) return
|
||||
|
@ -83,28 +68,6 @@ class MainActivity: FlutterActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
// handles return values from the system file picker
|
||||
override fun onActivityResult(requestCode: Int, result: Int, intent: Intent?) {
|
||||
super.onActivityResult(requestCode, result, intent);
|
||||
|
||||
if (intent == null || intent!!.getData() == null) {
|
||||
Log.i("MainActivity:onActivityResult", "user canceled activity");
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestCode == FILEPICKER_REQUEST_CODE) {
|
||||
val filePath = intent!!.getData().toString();
|
||||
val manifestPath = StringBuilder().append(this.applicationContext.cacheDir).append("/").append(this.dlToFileKey).toString();
|
||||
handleCwtch(MethodCall("DownloadFile", mapOf(
|
||||
"ProfileOnion" to this.dlToProfile,
|
||||
"handle" to this.dlToHandle,
|
||||
"filepath" to filePath,
|
||||
"manifestpath" to manifestPath,
|
||||
"filekey" to this.dlToFileKey
|
||||
)), ErrorLogResult(""));//placeholder; this Result is never actually invoked
|
||||
}
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
// Note: this methods are invoked on the main thread.
|
||||
|
@ -162,18 +125,6 @@ class MainActivity: FlutterActivity() {
|
|||
val workRequest = PeriodicWorkRequestBuilder<FlwtchWorker>(15, TimeUnit.MINUTES).setInputData(data).addTag(WORKER_TAG).addTag(uniqueTag).build()
|
||||
WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.REPLACE, workRequest)
|
||||
return
|
||||
} else if (call.method == "CreateDownloadableFile") {
|
||||
this.dlToProfile = argmap["ProfileOnion"] ?: ""
|
||||
this.dlToHandle = argmap["handle"] ?: ""
|
||||
val suggestedName = argmap["filename"] ?: "filename.ext"
|
||||
this.dlToFileKey = argmap["filekey"] ?: ""
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/octet-stream"
|
||||
putExtra(Intent.EXTRA_TITLE, suggestedName)
|
||||
}
|
||||
startActivityForResult(intent, FILEPICKER_REQUEST_CODE)
|
||||
return
|
||||
}
|
||||
|
||||
// ...otherwise fallthru to a normal ffi method call (and return the result using the result callback)
|
||||
|
@ -227,6 +178,20 @@ class MainActivity: FlutterActivity() {
|
|||
WorkManager.getInstance(this).pruneWork()
|
||||
}
|
||||
|
||||
// source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095
|
||||
// for reference:
|
||||
//
|
||||
// class Response(json: String) : JSONObject(json) {
|
||||
// val type: String? = this.optString("type")
|
||||
// val data = this.optJSONArray("data")
|
||||
// ?.let { 0.until(it.length()).map { i -> it.optJSONObject(i) } } // returns an array of JSONObject
|
||||
// ?.map { Foo(it.toString()) } // transforms each JSONObject of the array into Foo
|
||||
// }
|
||||
//
|
||||
// class Foo(json: String) : JSONObject(json) {
|
||||
// val id = this.optInt("id")
|
||||
// val title: String? = this.optString("title")
|
||||
// }
|
||||
class AppbusEvent(json: String) : JSONObject(json) {
|
||||
val EventType = this.optString("EventType")
|
||||
val EventID = this.optString("EventID")
|
||||
|
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 8.8 KiB |
|
@ -6,7 +6,7 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.4'
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,399 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 500 500"
|
||||
style="enable-background:new 0 0 500 500;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="Cwtch_knott_white.svg"
|
||||
inkscape:export-filename="/home/sarah/PARA/projects/cwtch/assets/core/knott-white.png"
|
||||
inkscape:export-xdpi="98.300003"
|
||||
inkscape:export-ydpi="98.300003"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
|
||||
id="metadata35"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs33" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
id="namedview31"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3350176"
|
||||
inkscape:cx="-56.414859"
|
||||
inkscape:cy="254.41396"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="31"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#010101;}
|
||||
.st2{fill:#AF9CBA;}
|
||||
.st3{clip-path:url(#SVGID_2_);}
|
||||
.st4{clip-path:url(#SVGID_3_);}
|
||||
.st5{clip-path:url(#SVGID_4_);}
|
||||
.st6{clip-path:url(#SVGID_6_);}
|
||||
.st7{clip-path:url(#SVGID_7_);}
|
||||
.st8{clip-path:url(#SVGID_8_);}
|
||||
.st9{clip-path:url(#SVGID_10_);}
|
||||
.st10{clip-path:url(#SVGID_11_);}
|
||||
.st11{clip-path:url(#SVGID_12_);}
|
||||
.st12{clip-path:url(#SVGID_14_);}
|
||||
.st13{clip-path:url(#SVGID_15_);}
|
||||
.st14{clip-path:url(#SVGID_16_);}
|
||||
.st15{clip-path:url(#SVGID_18_);}
|
||||
.st16{clip-path:url(#SVGID_19_);}
|
||||
.st17{clip-path:url(#SVGID_20_);}
|
||||
.st18{clip-path:url(#SVGID_22_);}
|
||||
.st19{clip-path:url(#SVGID_23_);}
|
||||
.st20{clip-path:url(#SVGID_24_);}
|
||||
.st21{clip-path:url(#SVGID_26_);}
|
||||
.st22{clip-path:url(#SVGID_27_);}
|
||||
.st23{clip-path:url(#SVGID_28_);}
|
||||
.st24{clip-path:url(#SVGID_30_);}
|
||||
.st25{clip-path:url(#SVGID_31_);}
|
||||
.st26{clip-path:url(#SVGID_32_);}
|
||||
.st27{clip-path:url(#SVGID_34_);}
|
||||
.st28{clip-path:url(#SVGID_35_);}
|
||||
.st29{clip-path:url(#SVGID_36_);}
|
||||
.st30{clip-path:url(#SVGID_38_);}
|
||||
.st31{clip-path:url(#SVGID_39_);}
|
||||
.st32{clip-path:url(#SVGID_40_);}
|
||||
.st33{clip-path:url(#SVGID_42_);}
|
||||
.st34{clip-path:url(#SVGID_43_);}
|
||||
.st35{clip-path:url(#SVGID_44_);}
|
||||
.st36{clip-path:url(#SVGID_46_);}
|
||||
.st37{clip-path:url(#SVGID_47_);}
|
||||
.st38{clip-path:url(#SVGID_48_);}
|
||||
.st39{clip-path:url(#SVGID_50_);}
|
||||
.st40{clip-path:url(#SVGID_51_);}
|
||||
.st41{clip-path:url(#SVGID_52_);}
|
||||
.st42{clip-path:url(#SVGID_54_);}
|
||||
.st43{clip-path:url(#SVGID_55_);}
|
||||
.st44{clip-path:url(#SVGID_56_);}
|
||||
.st45{clip-path:url(#SVGID_58_);}
|
||||
.st46{clip-path:url(#SVGID_59_);}
|
||||
.st47{clip-path:url(#SVGID_60_);}
|
||||
.st48{clip-path:url(#SVGID_62_);}
|
||||
.st49{clip-path:url(#SVGID_63_);}
|
||||
.st50{clip-path:url(#SVGID_64_);}
|
||||
.st51{clip-path:url(#SVGID_66_);}
|
||||
.st52{clip-path:url(#SVGID_67_);}
|
||||
.st53{clip-path:url(#SVGID_68_);}
|
||||
.st54{clip-path:url(#SVGID_70_);}
|
||||
.st55{clip-path:url(#SVGID_71_);}
|
||||
.st56{clip-path:url(#SVGID_72_);}
|
||||
.st57{clip-path:url(#SVGID_74_);}
|
||||
.st58{clip-path:url(#SVGID_75_);}
|
||||
.st59{clip-path:url(#SVGID_76_);}
|
||||
.st60{clip-path:url(#SVGID_78_);}
|
||||
.st61{clip-path:url(#SVGID_79_);}
|
||||
.st62{clip-path:url(#SVGID_80_);}
|
||||
.st63{clip-path:url(#SVGID_82_);}
|
||||
.st64{clip-path:url(#SVGID_83_);}
|
||||
.st65{clip-path:url(#SVGID_84_);}
|
||||
.st66{clip-path:url(#SVGID_86_);}
|
||||
.st67{clip-path:url(#SVGID_87_);}
|
||||
.st68{clip-path:url(#SVGID_88_);}
|
||||
.st69{clip-path:url(#SVGID_90_);}
|
||||
.st70{clip-path:url(#SVGID_91_);}
|
||||
.st71{clip-path:url(#SVGID_92_);}
|
||||
.st72{clip-path:url(#SVGID_94_);}
|
||||
.st73{clip-path:url(#SVGID_95_);}
|
||||
.st74{clip-path:url(#SVGID_96_);}
|
||||
.st75{clip-path:url(#SVGID_98_);}
|
||||
.st76{clip-path:url(#SVGID_99_);}
|
||||
.st77{clip-path:url(#SVGID_100_);}
|
||||
.st78{clip-path:url(#SVGID_102_);}
|
||||
.st79{clip-path:url(#SVGID_103_);}
|
||||
.st80{clip-path:url(#SVGID_104_);}
|
||||
.st81{clip-path:url(#SVGID_106_);}
|
||||
.st82{clip-path:url(#SVGID_107_);}
|
||||
.st83{clip-path:url(#SVGID_108_);}
|
||||
.st84{clip-path:url(#SVGID_110_);}
|
||||
.st85{clip-path:url(#SVGID_111_);}
|
||||
.st86{clip-path:url(#SVGID_112_);}
|
||||
.st87{clip-path:url(#SVGID_114_);}
|
||||
.st88{clip-path:url(#SVGID_115_);}
|
||||
.st89{clip-path:url(#SVGID_116_);}
|
||||
.st90{clip-path:url(#SVGID_118_);}
|
||||
.st91{clip-path:url(#SVGID_119_);}
|
||||
.st92{clip-path:url(#SVGID_120_);}
|
||||
.st93{clip-path:url(#SVGID_122_);}
|
||||
.st94{clip-path:url(#SVGID_123_);}
|
||||
.st95{clip-path:url(#SVGID_124_);}
|
||||
.st96{clip-path:url(#SVGID_126_);}
|
||||
.st97{clip-path:url(#SVGID_127_);}
|
||||
.st98{clip-path:url(#SVGID_128_);}
|
||||
.st99{fill:#64317C;}
|
||||
.st100{clip-path:url(#SVGID_130_);}
|
||||
.st101{clip-path:url(#SVGID_131_);}
|
||||
.st102{clip-path:url(#SVGID_132_);}
|
||||
.st103{clip-path:url(#SVGID_134_);}
|
||||
.st104{clip-path:url(#SVGID_135_);}
|
||||
.st105{clip-path:url(#SVGID_136_);}
|
||||
.st106{clip-path:url(#SVGID_138_);}
|
||||
.st107{clip-path:url(#SVGID_139_);}
|
||||
.st108{clip-path:url(#SVGID_140_);}
|
||||
.st109{clip-path:url(#SVGID_142_);}
|
||||
.st110{clip-path:url(#SVGID_143_);}
|
||||
.st111{clip-path:url(#SVGID_144_);}
|
||||
.st112{clip-path:url(#SVGID_146_);}
|
||||
.st113{clip-path:url(#SVGID_147_);}
|
||||
.st114{clip-path:url(#SVGID_148_);}
|
||||
.st115{clip-path:url(#SVGID_150_);}
|
||||
.st116{clip-path:url(#SVGID_151_);}
|
||||
.st117{clip-path:url(#SVGID_152_);}
|
||||
.st118{clip-path:url(#SVGID_154_);}
|
||||
.st119{clip-path:url(#SVGID_155_);}
|
||||
.st120{clip-path:url(#SVGID_156_);}
|
||||
.st121{clip-path:url(#SVGID_158_);}
|
||||
.st122{clip-path:url(#SVGID_159_);}
|
||||
.st123{clip-path:url(#SVGID_160_);}
|
||||
.st124{clip-path:url(#SVGID_162_);}
|
||||
.st125{clip-path:url(#SVGID_163_);}
|
||||
.st126{clip-path:url(#SVGID_164_);}
|
||||
.st127{clip-path:url(#SVGID_166_);}
|
||||
.st128{clip-path:url(#SVGID_167_);}
|
||||
.st129{clip-path:url(#SVGID_168_);}
|
||||
.st130{clip-path:url(#SVGID_170_);}
|
||||
.st131{clip-path:url(#SVGID_171_);}
|
||||
.st132{clip-path:url(#SVGID_172_);}
|
||||
.st133{clip-path:url(#SVGID_174_);}
|
||||
.st134{clip-path:url(#SVGID_175_);}
|
||||
.st135{clip-path:url(#SVGID_176_);}
|
||||
.st136{clip-path:url(#SVGID_178_);}
|
||||
.st137{clip-path:url(#SVGID_179_);}
|
||||
.st138{clip-path:url(#SVGID_180_);}
|
||||
.st139{clip-path:url(#SVGID_182_);}
|
||||
.st140{clip-path:url(#SVGID_183_);}
|
||||
.st141{clip-path:url(#SVGID_184_);}
|
||||
.st142{clip-path:url(#SVGID_186_);}
|
||||
.st143{clip-path:url(#SVGID_187_);}
|
||||
.st144{clip-path:url(#SVGID_188_);}
|
||||
.st145{clip-path:url(#SVGID_190_);}
|
||||
.st146{clip-path:url(#SVGID_191_);}
|
||||
.st147{clip-path:url(#SVGID_192_);}
|
||||
.st148{clip-path:url(#SVGID_194_);}
|
||||
.st149{clip-path:url(#SVGID_195_);}
|
||||
.st150{clip-path:url(#SVGID_196_);}
|
||||
.st151{clip-path:url(#SVGID_198_);}
|
||||
.st152{clip-path:url(#SVGID_199_);}
|
||||
.st153{clip-path:url(#SVGID_200_);}
|
||||
.st154{clip-path:url(#SVGID_202_);}
|
||||
.st155{clip-path:url(#SVGID_203_);}
|
||||
.st156{clip-path:url(#SVGID_204_);}
|
||||
.st157{clip-path:url(#SVGID_206_);}
|
||||
.st158{clip-path:url(#SVGID_207_);}
|
||||
.st159{clip-path:url(#SVGID_208_);}
|
||||
.st160{clip-path:url(#SVGID_210_);}
|
||||
.st161{clip-path:url(#SVGID_211_);}
|
||||
.st162{clip-path:url(#SVGID_212_);}
|
||||
.st163{clip-path:url(#SVGID_214_);}
|
||||
.st164{clip-path:url(#SVGID_215_);}
|
||||
.st165{clip-path:url(#SVGID_216_);}
|
||||
.st166{clip-path:url(#SVGID_218_);}
|
||||
.st167{clip-path:url(#SVGID_219_);}
|
||||
.st168{clip-path:url(#SVGID_220_);}
|
||||
.st169{clip-path:url(#SVGID_222_);}
|
||||
.st170{clip-path:url(#SVGID_223_);}
|
||||
.st171{clip-path:url(#SVGID_224_);}
|
||||
.st172{clip-path:url(#SVGID_226_);}
|
||||
.st173{clip-path:url(#SVGID_227_);}
|
||||
.st174{clip-path:url(#SVGID_228_);}
|
||||
.st175{clip-path:url(#SVGID_230_);}
|
||||
.st176{clip-path:url(#SVGID_231_);}
|
||||
.st177{clip-path:url(#SVGID_232_);}
|
||||
.st178{clip-path:url(#SVGID_234_);}
|
||||
.st179{clip-path:url(#SVGID_235_);}
|
||||
.st180{clip-path:url(#SVGID_236_);}
|
||||
.st181{clip-path:url(#SVGID_238_);}
|
||||
.st182{clip-path:url(#SVGID_239_);}
|
||||
.st183{clip-path:url(#SVGID_240_);}
|
||||
.st184{clip-path:url(#SVGID_242_);}
|
||||
.st185{clip-path:url(#SVGID_243_);}
|
||||
.st186{clip-path:url(#SVGID_244_);}
|
||||
.st187{clip-path:url(#SVGID_246_);}
|
||||
.st188{clip-path:url(#SVGID_247_);}
|
||||
.st189{clip-path:url(#SVGID_248_);}
|
||||
.st190{clip-path:url(#SVGID_250_);}
|
||||
.st191{clip-path:url(#SVGID_251_);}
|
||||
.st192{clip-path:url(#SVGID_252_);}
|
||||
.st193{clip-path:url(#SVGID_254_);}
|
||||
.st194{clip-path:url(#SVGID_255_);}
|
||||
.st195{clip-path:url(#SVGID_256_);}
|
||||
</style>
|
||||
<g
|
||||
transform="translate(2.1186441)"
|
||||
id="g61"
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none">
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path37"
|
||||
d="m 465.94,209.46 c -0.72,3.49 -2.35,7.45 -4.95,12.03 l 21.16,21.16 c 2.83,-5.51 7.14,-15.16 9.47,-26.61 4.76,-23.39 -0.6,-43.02 -15.93,-58.35 -28.01,-28.01 -57.26,-30.61 -94.84,-8.43 -9.04,5.34 -18.64,12.24 -28.55,20.51 -22.58,18.86 -47.88,45.85 -75.22,80.23 17.91,22.52 35,41.95 50.83,57.79 7.26,7.26 14.4,13.91 21.27,19.81 l 18.76,-18.76 c -6.83,-5.78 -13.98,-12.41 -21.32,-19.75 -10.39,-10.39 -21.5,-22.57 -32.99,-36.19 l -2.43,-2.9 2.44,-2.89 c 21.03,-24.91 40.59,-44.8 58.14,-59.09 11.14,-9.08 21.56,-15.98 30.98,-20.53 8.14,-3.92 15.55,-6.11 22.03,-6.49 3.16,-0.19 8.05,-0.15 14,2.3 5.84,2.4 11.79,6.68 18.2,13.09 2.39,2.39 5.82,6.25 7.91,12.08 2.16,6.02 2.51,12.85 1.07,20.88 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path39"
|
||||
d="M 355.44,371.4 450,276.85 c -5.56,-8.23 -12.85,-16.22 -16.93,-20.49 l -93.37,93.37 c 6.02,7.42 11.29,14.68 15.74,21.67 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path41"
|
||||
d="m 475.69,342.31 c 15.32,-15.32 20.68,-34.95 15.93,-58.34 -3.3,-16.23 -10.57,-28.84 -11.98,-31.18 l -75.82,-75.82 c -6.95,3.64 -14.5,8.63 -22.52,14.89 l 58.13,58.14 -0.03,0.03 c 4.8,5.01 13.98,15.11 20.13,25.07 3.75,6.08 5.87,11.21 6.46,15.66 1.42,7.98 1.06,14.77 -1.09,20.77 -2.09,5.83 -5.51,9.68 -7.91,12.08 -6.83,6.83 -13.16,11.26 -19.37,13.55 -6.33,2.34 -11.55,2.09 -15.39,1.6 -5.05,-0.63 -10.62,-2.28 -16.58,-4.9 L 385.9,353.6 c 35.17,19.04 63.04,15.46 89.79,-11.29 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path43"
|
||||
d="m 311.53,35.11 c 5.83,2.09 9.68,5.51 12.08,7.91 6.83,6.83 11.26,13.16 13.55,19.37 2.34,6.33 2.09,11.55 1.6,15.39 -0.63,5.05 -2.28,10.62 -4.9,16.58 L 353.6,114.1 C 372.64,78.93 369.06,51.06 342.31,24.31 326.99,8.99 307.36,3.63 283.97,8.38 c -16.23,3.3 -28.84,10.57 -31.18,11.98 l -75.82,75.82 c 3.64,6.95 8.63,14.5 14.89,22.52 L 250,60.57 l 0.03,0.03 c 5.01,-4.8 15.11,-13.98 25.07,-20.13 6.08,-3.75 11.21,-5.87 15.66,-6.46 7.98,-1.41 14.77,-1.05 20.77,1.1 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path45"
|
||||
d="m 157.69,24.31 c -28.01,28.01 -30.61,57.26 -8.43,94.84 5.34,9.04 12.24,18.64 20.51,28.54 18.86,22.58 45.85,47.88 80.23,75.22 22.52,-17.91 41.95,-35 57.79,-50.83 7.26,-7.26 13.91,-14.4 19.81,-21.27 l -18.76,-18.76 c -5.78,6.83 -12.41,13.98 -19.75,21.32 -10.39,10.39 -22.57,21.49 -36.19,32.99 l -2.9,2.44 -2.89,-2.44 C 222.2,165.33 202.31,145.77 188.02,128.22 178.94,117.08 172.04,106.66 167.49,97.24 163.57,89.1 161.38,81.69 161,75.21 c -0.19,-3.16 -0.15,-8.05 2.3,-14 2.4,-5.84 6.68,-11.79 13.09,-18.2 2.39,-2.39 6.25,-5.82 12.08,-7.91 6.02,-2.16 12.85,-2.51 20.87,-1.07 l 0.11,0.02 c 3.49,0.72 7.45,2.35 12.03,4.95 L 242.64,17.84 C 237.13,15.01 227.48,10.7 216.02,8.37 192.64,3.63 173.01,8.99 157.69,24.31 Z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path47"
|
||||
d="m 276.85,50 c -8.23,5.56 -16.22,12.85 -20.49,16.92 l 93.37,93.37 c 7.42,-6.02 14.68,-11.29 21.67,-15.74 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path49"
|
||||
d="m 24.31,342.31 c 28.01,28.01 57.26,30.61 94.84,8.43 9.04,-5.34 18.64,-12.24 28.54,-20.51 22.58,-18.86 47.88,-45.85 75.22,-80.23 -17.91,-22.52 -35,-41.95 -50.83,-57.79 -7.26,-7.26 -14.4,-13.91 -21.27,-19.81 l -18.76,18.76 c 6.83,5.78 13.98,12.41 21.32,19.75 10.39,10.39 21.5,22.57 32.99,36.19 l 2.44,2.89 -2.44,2.89 c -21.03,24.91 -40.59,44.8 -58.14,59.09 -11.14,9.08 -21.56,15.98 -30.98,20.53 -8.14,3.92 -15.55,6.11 -22.03,6.49 -3.16,0.19 -8.05,0.15 -14,-2.3 -5.84,-2.4 -11.79,-6.68 -18.2,-13.09 -2.39,-2.39 -5.82,-6.25 -7.91,-12.08 -2.16,-6.02 -2.51,-12.85 -1.07,-20.88 l 0.02,-0.11 C 34.77,287.04 36.4,283.08 39,278.5 L 17.84,257.34 c -2.83,5.51 -7.14,15.17 -9.47,26.63 -4.74,23.39 0.62,43.02 15.94,58.34 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path51"
|
||||
d="m 60.57,250 0.03,-0.03 c -4.8,-5.02 -13.98,-15.11 -20.13,-25.07 -3.75,-6.08 -5.87,-11.21 -6.46,-15.66 -1.42,-7.98 -1.06,-14.77 1.09,-20.77 2.09,-5.83 5.51,-9.68 7.91,-12.08 6.83,-6.83 13.16,-11.26 19.37,-13.55 6.33,-2.34 11.55,-2.09 15.39,-1.6 5.05,0.63 10.62,2.28 16.58,4.9 L 114.09,146.4 C 78.92,127.36 51.05,130.94 24.3,157.69 8.99,173.01 3.63,192.64 8.38,216.03 c 3.3,16.23 10.57,28.84 11.98,31.18 l 75.82,75.82 c 6.95,-3.64 14.5,-8.63 22.52,-14.89 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path53"
|
||||
d="m 66.93,243.64 93.37,-93.37 c -6.02,-7.42 -11.29,-14.68 -15.74,-21.67 L 50,223.15 c 5.57,8.23 12.85,16.23 16.93,20.49 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path55"
|
||||
d="m 342.31,475.69 c 28.01,-28.01 30.61,-57.26 8.43,-94.84 -5.34,-9.04 -12.24,-18.64 -20.51,-28.54 -18.86,-22.58 -45.85,-47.88 -80.23,-75.22 -22.52,17.91 -41.95,35 -57.79,50.83 -7.26,7.26 -13.91,14.4 -19.81,21.27 l 18.76,18.76 c 5.78,-6.83 12.41,-13.98 19.75,-21.32 10.39,-10.39 22.57,-21.49 36.19,-32.99 l 2.89,-2.44 2.89,2.44 c 24.91,21.03 44.8,40.59 59.09,58.14 9.08,11.14 15.98,21.56 20.53,30.98 3.93,8.14 6.11,15.56 6.49,22.03 0.19,3.16 0.15,8.05 -2.3,14 -2.4,5.84 -6.68,11.79 -13.09,18.2 -2.39,2.39 -6.25,5.82 -12.08,7.91 -6.02,2.16 -12.85,2.51 -20.88,1.07 l -0.11,-0.02 c -3.49,-0.72 -7.45,-2.35 -12.03,-4.95 l -21.16,21.16 c 5.5,2.83 15.15,7.13 26.59,9.46 23.41,4.76 43.05,-0.6 58.38,-15.93 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path57"
|
||||
d="M 243.64,433.07 150.27,339.7 c -7.42,6.02 -14.68,11.29 -21.67,15.74 L 223.15,450 c 8.23,-5.57 16.23,-12.85 20.49,-16.93 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path59"
|
||||
d="m 157.69,475.69 c 15.32,15.32 34.95,20.68 58.33,15.93 16.24,-3.3 28.85,-10.58 31.19,-11.98 l 75.82,-75.82 c -3.64,-6.95 -8.63,-14.5 -14.89,-22.52 L 250,439.43 249.97,439.4 c -5.01,4.8 -15.11,13.98 -25.07,20.13 -6.08,3.75 -11.21,5.87 -15.66,6.46 -7.98,1.42 -14.77,1.06 -20.77,-1.09 -5.83,-2.09 -9.68,-5.51 -12.08,-7.91 -6.83,-6.83 -11.26,-13.16 -13.55,-19.37 -2.34,-6.33 -2.09,-11.55 -1.6,-15.39 0.63,-5.05 2.28,-10.62 4.9,-16.58 L 146.4,385.9 c -19.04,35.17 -15.46,63.04 11.29,89.79 z"
|
||||
class="st0" />
|
||||
</g><g
|
||||
id="g28"
|
||||
transform="translate(2.1186441)">
|
||||
<path
|
||||
class="st0"
|
||||
d="m 465.94,209.46 c -0.72,3.49 -2.35,7.45 -4.95,12.03 l 21.16,21.16 c 2.83,-5.51 7.14,-15.16 9.47,-26.61 4.76,-23.39 -0.6,-43.02 -15.93,-58.35 -28.01,-28.01 -57.26,-30.61 -94.84,-8.43 -9.04,5.34 -18.64,12.24 -28.55,20.51 -22.58,18.86 -47.88,45.85 -75.22,80.23 17.91,22.52 35,41.95 50.83,57.79 7.26,7.26 14.4,13.91 21.27,19.81 l 18.76,-18.76 c -6.83,-5.78 -13.98,-12.41 -21.32,-19.75 -10.39,-10.39 -21.5,-22.57 -32.99,-36.19 l -2.43,-2.9 2.44,-2.89 c 21.03,-24.91 40.59,-44.8 58.14,-59.09 11.14,-9.08 21.56,-15.98 30.98,-20.53 8.14,-3.92 15.55,-6.11 22.03,-6.49 3.16,-0.19 8.05,-0.15 14,2.3 5.84,2.4 11.79,6.68 18.2,13.09 2.39,2.39 5.82,6.25 7.91,12.08 2.16,6.02 2.51,12.85 1.07,20.88 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="M 355.44,371.4 450,276.85 c -5.56,-8.23 -12.85,-16.22 -16.93,-20.49 l -93.37,93.37 c 6.02,7.42 11.29,14.68 15.74,21.67 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 475.69,342.31 c 15.32,-15.32 20.68,-34.95 15.93,-58.34 -3.3,-16.23 -10.57,-28.84 -11.98,-31.18 l -75.82,-75.82 c -6.95,3.64 -14.5,8.63 -22.52,14.89 l 58.13,58.14 -0.03,0.03 c 4.8,5.01 13.98,15.11 20.13,25.07 3.75,6.08 5.87,11.21 6.46,15.66 1.42,7.98 1.06,14.77 -1.09,20.77 -2.09,5.83 -5.51,9.68 -7.91,12.08 -6.83,6.83 -13.16,11.26 -19.37,13.55 -6.33,2.34 -11.55,2.09 -15.39,1.6 -5.05,-0.63 -10.62,-2.28 -16.58,-4.9 L 385.9,353.6 c 35.17,19.04 63.04,15.46 89.79,-11.29 z"
|
||||
id="path8"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 311.53,35.11 c 5.83,2.09 9.68,5.51 12.08,7.91 6.83,6.83 11.26,13.16 13.55,19.37 2.34,6.33 2.09,11.55 1.6,15.39 -0.63,5.05 -2.28,10.62 -4.9,16.58 L 353.6,114.1 C 372.64,78.93 369.06,51.06 342.31,24.31 326.99,8.99 307.36,3.63 283.97,8.38 c -16.23,3.3 -28.84,10.57 -31.18,11.98 l -75.82,75.82 c 3.64,6.95 8.63,14.5 14.89,22.52 L 250,60.57 l 0.03,0.03 c 5.01,-4.8 15.11,-13.98 25.07,-20.13 6.08,-3.75 11.21,-5.87 15.66,-6.46 7.98,-1.41 14.77,-1.05 20.77,1.1 z"
|
||||
id="path10"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 157.69,24.31 c -28.01,28.01 -30.61,57.26 -8.43,94.84 5.34,9.04 12.24,18.64 20.51,28.54 18.86,22.58 45.85,47.88 80.23,75.22 22.52,-17.91 41.95,-35 57.79,-50.83 7.26,-7.26 13.91,-14.4 19.81,-21.27 l -18.76,-18.76 c -5.78,6.83 -12.41,13.98 -19.75,21.32 -10.39,10.39 -22.57,21.49 -36.19,32.99 l -2.9,2.44 -2.89,-2.44 C 222.2,165.33 202.31,145.77 188.02,128.22 178.94,117.08 172.04,106.66 167.49,97.24 163.57,89.1 161.38,81.69 161,75.21 c -0.19,-3.16 -0.15,-8.05 2.3,-14 2.4,-5.84 6.68,-11.79 13.09,-18.2 2.39,-2.39 6.25,-5.82 12.08,-7.91 6.02,-2.16 12.85,-2.51 20.87,-1.07 l 0.11,0.02 c 3.49,0.72 7.45,2.35 12.03,4.95 L 242.64,17.84 C 237.13,15.01 227.48,10.7 216.02,8.37 192.64,3.63 173.01,8.99 157.69,24.31 Z"
|
||||
id="path12"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 276.85,50 c -8.23,5.56 -16.22,12.85 -20.49,16.92 l 93.37,93.37 c 7.42,-6.02 14.68,-11.29 21.67,-15.74 z"
|
||||
id="path14"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 24.31,342.31 c 28.01,28.01 57.26,30.61 94.84,8.43 9.04,-5.34 18.64,-12.24 28.54,-20.51 22.58,-18.86 47.88,-45.85 75.22,-80.23 -17.91,-22.52 -35,-41.95 -50.83,-57.79 -7.26,-7.26 -14.4,-13.91 -21.27,-19.81 l -18.76,18.76 c 6.83,5.78 13.98,12.41 21.32,19.75 10.39,10.39 21.5,22.57 32.99,36.19 l 2.44,2.89 -2.44,2.89 c -21.03,24.91 -40.59,44.8 -58.14,59.09 -11.14,9.08 -21.56,15.98 -30.98,20.53 -8.14,3.92 -15.55,6.11 -22.03,6.49 -3.16,0.19 -8.05,0.15 -14,-2.3 -5.84,-2.4 -11.79,-6.68 -18.2,-13.09 -2.39,-2.39 -5.82,-6.25 -7.91,-12.08 -2.16,-6.02 -2.51,-12.85 -1.07,-20.88 l 0.02,-0.11 C 34.77,287.04 36.4,283.08 39,278.5 L 17.84,257.34 c -2.83,5.51 -7.14,15.17 -9.47,26.63 -4.74,23.39 0.62,43.02 15.94,58.34 z"
|
||||
id="path16"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 60.57,250 0.03,-0.03 c -4.8,-5.02 -13.98,-15.11 -20.13,-25.07 -3.75,-6.08 -5.87,-11.21 -6.46,-15.66 -1.42,-7.98 -1.06,-14.77 1.09,-20.77 2.09,-5.83 5.51,-9.68 7.91,-12.08 6.83,-6.83 13.16,-11.26 19.37,-13.55 6.33,-2.34 11.55,-2.09 15.39,-1.6 5.05,0.63 10.62,2.28 16.58,4.9 L 114.09,146.4 C 78.92,127.36 51.05,130.94 24.3,157.69 8.99,173.01 3.63,192.64 8.38,216.03 c 3.3,16.23 10.57,28.84 11.98,31.18 l 75.82,75.82 c 6.95,-3.64 14.5,-8.63 22.52,-14.89 z"
|
||||
id="path18"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 66.93,243.64 93.37,-93.37 c -6.02,-7.42 -11.29,-14.68 -15.74,-21.67 L 50,223.15 c 5.57,8.23 12.85,16.23 16.93,20.49 z"
|
||||
id="path20"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 342.31,475.69 c 28.01,-28.01 30.61,-57.26 8.43,-94.84 -5.34,-9.04 -12.24,-18.64 -20.51,-28.54 -18.86,-22.58 -45.85,-47.88 -80.23,-75.22 -22.52,17.91 -41.95,35 -57.79,50.83 -7.26,7.26 -13.91,14.4 -19.81,21.27 l 18.76,18.76 c 5.78,-6.83 12.41,-13.98 19.75,-21.32 10.39,-10.39 22.57,-21.49 36.19,-32.99 l 2.89,-2.44 2.89,2.44 c 24.91,21.03 44.8,40.59 59.09,58.14 9.08,11.14 15.98,21.56 20.53,30.98 3.93,8.14 6.11,15.56 6.49,22.03 0.19,3.16 0.15,8.05 -2.3,14 -2.4,5.84 -6.68,11.79 -13.09,18.2 -2.39,2.39 -6.25,5.82 -12.08,7.91 -6.02,2.16 -12.85,2.51 -20.88,1.07 l -0.11,-0.02 c -3.49,-0.72 -7.45,-2.35 -12.03,-4.95 l -21.16,21.16 c 5.5,2.83 15.15,7.13 26.59,9.46 23.41,4.76 43.05,-0.6 58.38,-15.93 z"
|
||||
id="path22"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="M 243.64,433.07 150.27,339.7 c -7.42,6.02 -14.68,11.29 -21.67,15.74 L 223.15,450 c 8.23,-5.57 16.23,-12.85 20.49,-16.93 z"
|
||||
id="path24"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 157.69,475.69 c 15.32,15.32 34.95,20.68 58.33,15.93 16.24,-3.3 28.85,-10.58 31.19,-11.98 l 75.82,-75.82 c -3.64,-6.95 -8.63,-14.5 -14.89,-22.52 L 250,439.43 249.97,439.4 c -5.01,4.8 -15.11,13.98 -25.07,20.13 -6.08,3.75 -11.21,5.87 -15.66,6.46 -7.98,1.42 -14.77,1.06 -20.77,-1.09 -5.83,-2.09 -9.68,-5.51 -12.08,-7.91 -6.83,-6.83 -11.26,-13.16 -13.55,-19.37 -2.34,-6.33 -2.09,-11.55 -1.6,-15.39 0.63,-5.05 2.28,-10.62 4.9,-16.58 L 146.4,385.9 c -19.04,35.17 -15.46,63.04 11.29,89.79 z"
|
||||
id="path26"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
@ -1,99 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#242425;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M436.2,215c-0.6,3-2,6.4-4.3,10.4l18.2,18.2c2.4-4.8,6.2-13.1,8.2-22.9c4.1-20.2-0.5-37.1-13.7-50.3
|
||||
c-24.2-24.2-49.4-26.4-81.8-7.3c-7.8,4.6-16.1,10.6-24.6,17.7c-19.5,16.3-41.3,39.5-64.9,69.2c15.4,19.4,30.2,36.2,43.8,49.8
|
||||
c6.3,6.3,12.4,12,18.3,17.1l16.2-16.2c-5.9-5-12.1-10.7-18.4-17c-9-9-18.5-19.5-28.4-31.2l-2.1-2.5l2.1-2.5
|
||||
c18.1-21.5,35-38.6,50.1-50.9c9.6-7.8,18.6-13.8,26.7-17.7c7-3.4,13.4-5.3,19-5.6c2.7-0.2,6.9-0.1,12.1,2c5,2.1,10.2,5.8,15.7,11.3
|
||||
c2.1,2.1,5,5.4,6.8,10.4C437.1,202.1,437.5,208,436.2,215L436.2,215z"/>
|
||||
<path class="st0" d="M340.9,354.7l81.5-81.5c-4.8-7.1-11.1-14-14.6-17.7L327.3,336C332.5,342.4,337.1,348.6,340.9,354.7z"/>
|
||||
<path class="st0" d="M444.6,329.6c13.2-13.2,17.8-30.1,13.7-50.3c-2.8-14-9.1-24.9-10.3-26.9L382.6,187c-6,3.1-12.5,7.4-19.4,12.8
|
||||
l50.1,50.1l0,0c4.1,4.3,12.1,13,17.4,21.6c3.2,5.2,5.1,9.7,5.6,13.5c1.2,6.9,0.9,12.7-0.9,17.9c-1.8,5-4.8,8.3-6.8,10.4
|
||||
c-5.9,5.9-11.3,9.7-16.7,11.7c-5.5,2-10,1.8-13.3,1.4c-4.4-0.5-9.2-2-14.3-4.2l-17,17C397.5,355.7,421.5,352.7,444.6,329.6z"/>
|
||||
<path class="st0" d="M303.1,64.7c5,1.8,8.3,4.8,10.4,6.8c5.9,5.9,9.7,11.3,11.7,16.7c2,5.5,1.8,10,1.4,13.3
|
||||
c-0.5,4.4-2,9.2-4.2,14.3l17,17c16.4-30.3,13.3-54.4-9.7-77.4c-13.2-13.2-30.1-17.8-50.3-13.7c-14,2.8-24.9,9.1-26.9,10.3
|
||||
L187,117.4c3.1,6,7.4,12.5,12.8,19.4L250,86.7l0,0c4.3-4.1,13-12.1,21.6-17.4c5.2-3.2,9.7-5.1,13.5-5.6
|
||||
C292,62.6,297.9,62.9,303.1,64.7z"/>
|
||||
<path class="st0" d="M170.4,55.4c-24.2,24.2-26.4,49.4-7.3,81.8c4.6,7.8,10.6,16.1,17.7,24.6c16.3,19.5,39.5,41.3,69.2,64.9
|
||||
c19.4-15.4,36.2-30.2,49.8-43.8c6.3-6.3,12-12.4,17.1-18.3l-16.2-16.2c-5,5.9-10.7,12.1-17,18.4c-9,9-19.5,18.5-31.2,28.4l-2.5,2.1
|
||||
l-2.5-2.1c-21.5-18.1-38.6-35-50.9-50.1c-7.8-9.6-13.8-18.6-17.7-26.7c-3.4-7-5.3-13.4-5.6-19c-0.2-2.7-0.1-6.9,2-12.1
|
||||
c2.1-5,5.8-10.2,11.3-15.7c2.1-2.1,5.4-5,10.4-6.8c5.2-1.9,11.1-2.2,18-0.9l0.1,0c3,0.6,6.4,2,10.4,4.3l18.2-18.2
|
||||
c-4.8-2.4-13.1-6.2-23-8.2C200.5,37.6,183.6,42.2,170.4,55.4z"/>
|
||||
<path class="st0" d="M273.2,77.6c-7.1,4.8-14,11.1-17.7,14.6l80.5,80.5c6.4-5.2,12.7-9.7,18.7-13.6L273.2,77.6z"/>
|
||||
<path class="st0" d="M55.4,329.6c24.2,24.2,49.4,26.4,81.8,7.3c7.8-4.6,16.1-10.6,24.6-17.7c19.5-16.3,41.3-39.5,64.9-69.2
|
||||
c-15.4-19.4-30.2-36.2-43.8-49.8c-6.3-6.3-12.4-12-18.3-17.1l-16.2,16.2c5.9,5,12.1,10.7,18.4,17c9,9,18.5,19.5,28.4,31.2l2.1,2.5
|
||||
l-2.1,2.5c-18.1,21.5-35,38.6-50.1,50.9c-9.6,7.8-18.6,13.8-26.7,17.7c-7,3.4-13.4,5.3-19,5.6c-2.7,0.2-6.9,0.1-12.1-2
|
||||
c-5-2.1-10.2-5.8-15.7-11.3c-2.1-2.1-5-5.4-6.8-10.4c-1.9-5.2-2.2-11.1-0.9-18l0-0.1c0.6-3,2-6.4,4.3-10.4l-18.2-18.2
|
||||
c-2.4,4.8-6.2,13.1-8.2,23C37.6,299.5,42.2,316.4,55.4,329.6z"/>
|
||||
<path class="st0" d="M86.7,250L86.7,250c-4.1-4.4-12-13.1-17.3-21.6c-3.2-5.2-5.1-9.7-5.6-13.5c-1.2-6.9-0.9-12.7,0.9-17.9
|
||||
c1.8-5,4.8-8.3,6.8-10.4c5.9-5.9,11.3-9.7,16.7-11.7c5.5-2,10-1.8,13.3-1.4c4.4,0.5,9.2,2,14.3,4.2l17-17
|
||||
c-30.3-16.4-54.4-13.3-77.4,9.7c-13.2,13.2-17.8,30.1-13.7,50.3c2.8,14,9.1,24.9,10.3,26.9l65.4,65.4c6-3.1,12.5-7.4,19.4-12.8
|
||||
L86.7,250z"/>
|
||||
<path class="st0" d="M92.2,244.5l80.5-80.5c-5.2-6.4-9.7-12.7-13.6-18.7l-81.5,81.5C82.4,233.9,88.6,240.8,92.2,244.5z"/>
|
||||
<path class="st0" d="M329.6,444.6c24.2-24.2,26.4-49.4,7.3-81.8c-4.6-7.8-10.6-16.1-17.7-24.6c-16.3-19.5-39.5-41.3-69.2-64.9
|
||||
c-19.4,15.4-36.2,30.2-49.8,43.8c-6.3,6.3-12,12.4-17.1,18.3l16.2,16.2c5-5.9,10.7-12.1,17-18.4c9-9,19.5-18.5,31.2-28.4l2.5-2.1
|
||||
l2.5,2.1c21.5,18.1,38.6,35,50.9,50.1c7.8,9.6,13.8,18.6,17.7,26.7c3.4,7,5.3,13.4,5.6,19c0.2,2.7,0.1,6.9-2,12.1
|
||||
c-2.1,5-5.8,10.2-11.3,15.7c-2.1,2.1-5.4,5-10.4,6.8c-5.2,1.9-11.1,2.2-18,0.9l-0.1,0c-3-0.6-6.4-2-10.4-4.3l-18.2,18.2
|
||||
c4.7,2.4,13.1,6.1,22.9,8.2C299.4,462.4,316.4,457.8,329.6,444.6z"/>
|
||||
<path class="st0" d="M244.5,407.8L164,327.3c-6.4,5.2-12.7,9.7-18.7,13.6l81.5,81.5C233.9,417.6,240.8,411.4,244.5,407.8z"/>
|
||||
<path class="st0" d="M483.4,250c2.6-6.3,5.3-14,7.1-22.7c6.3-30.8-1.8-59.2-22.7-80.1c-19.7-19.7-41.7-29.7-65.5-29.7
|
||||
c-7.5,0-15.1,1-22.8,3c2.9-11.3,3.7-22.3,2.3-33.2c-2.5-19.8-12.3-38.3-29-55.1C336.6,15.9,315.9,7.4,293,7.4
|
||||
c-6.6,0-13.4,0.7-20.2,2.1c-8.7,1.8-16.5,4.5-22.7,7.1c-6.3-2.6-14.1-5.3-22.8-7.1c-6.9-1.4-13.7-2.1-20.2-2.1
|
||||
c-22.9,0-43.6,8.6-59.9,24.8c-17.6,17.6-27.5,37.2-29.4,58.1c-0.9,9.9,0,19.9,2.7,30.2c-7.7-2-15.3-3-22.8-3
|
||||
c-23.8,0-45.8,10-65.5,29.7c-20.9,20.9-28.9,49.3-22.7,80.1c1.8,8.7,4.5,16.5,7.1,22.7c-2.6,6.3-5.3,14.1-7.1,22.8
|
||||
c-6.2,30.8,1.8,59.2,22.7,80.1c19.7,19.7,41.7,29.7,65.5,29.7c0,0,0,0,0,0c7.5,0,15.1-1,22.8-3c-2.9,11.3-3.7,22.3-2.3,33.2
|
||||
c2.5,19.8,12.3,38.3,29,55.1c16.2,16.2,36.9,24.8,59.8,24.8c0,0,0,0,0,0c6.6,0,13.4-0.7,20.2-2.1c8.7-1.8,16.5-4.4,22.7-7.1
|
||||
c6.3,2.6,14,5.3,22.7,7.1c6.9,1.4,13.7,2.1,20.3,2.1c0,0,0,0,0,0c22.9,0,43.6-8.6,59.9-24.8c17.6-17.6,27.5-37.2,29.4-58.1
|
||||
c0.9-9.9,0-19.9-2.7-30.2c7.7,2,15.3,3,22.8,3c0,0,0,0,0,0c23.8,0,45.8-10,65.5-29.7c20.9-20.9,29-49.3,22.7-80.1
|
||||
C488.8,264,486.1,256.3,483.4,250z"/>
|
||||
<path class="st0" d="M170.4,444.6c13.2,13.2,30.1,17.8,50.3,13.7c14-2.8,24.9-9.1,26.9-10.3l65.4-65.4c-3.1-6-7.4-12.5-12.8-19.4
|
||||
L250,413.3l0,0c-4.3,4.1-13,12.1-21.6,17.4c-5.2,3.2-9.7,5.1-13.5,5.6c-6.9,1.2-12.7,0.9-17.9-0.9c-5-1.8-8.3-4.8-10.4-6.8
|
||||
c-5.9-5.9-9.7-11.3-11.7-16.7c-2-5.5-1.8-10-1.4-13.3c0.5-4.4,2-9.2,4.2-14.3l-17-17C144.3,397.5,147.3,421.5,170.4,444.6z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M436.2,215c-0.6,3-2,6.4-4.3,10.4l18.2,18.2c2.4-4.8,6.2-13.1,8.2-22.9c4.1-20.2-0.5-37.1-13.7-50.3
|
||||
c-24.2-24.2-49.4-26.4-81.8-7.3c-7.8,4.6-16.1,10.6-24.6,17.7c-19.5,16.3-41.3,39.5-64.9,69.2c15.4,19.4,30.2,36.2,43.8,49.8
|
||||
c6.3,6.3,12.4,12,18.3,17.1l16.2-16.2c-5.9-5-12.1-10.7-18.4-17c-9-9-18.5-19.5-28.4-31.2l-2.1-2.5l2.1-2.5
|
||||
c18.1-21.5,35-38.6,50.1-50.9c9.6-7.8,18.6-13.8,26.7-17.7c7-3.4,13.4-5.3,19-5.6c2.7-0.2,6.9-0.1,12.1,2c5,2.1,10.2,5.8,15.7,11.3
|
||||
c2.1,2.1,5,5.4,6.8,10.4C437.1,202.1,437.5,208,436.2,215L436.2,215z"/>
|
||||
<path class="st1" d="M340.9,354.7l81.5-81.5c-4.8-7.1-11.1-14-14.6-17.7L327.3,336C332.5,342.4,337.1,348.6,340.9,354.7z"/>
|
||||
<path class="st1" d="M444.6,329.6c13.2-13.2,17.8-30.1,13.7-50.3c-2.8-14-9.1-24.9-10.3-26.9L382.6,187c-6,3.1-12.5,7.4-19.4,12.8
|
||||
l50.1,50.1l0,0c4.1,4.3,12.1,13,17.4,21.6c3.2,5.2,5.1,9.7,5.6,13.5c1.2,6.9,0.9,12.7-0.9,17.9c-1.8,5-4.8,8.3-6.8,10.4
|
||||
c-5.9,5.9-11.3,9.7-16.7,11.7c-5.5,2-10,1.8-13.3,1.4c-4.4-0.5-9.2-2-14.3-4.2l-17,17C397.5,355.7,421.5,352.7,444.6,329.6z"/>
|
||||
<path class="st1" d="M303.1,64.7c5,1.8,8.3,4.8,10.4,6.8c5.9,5.9,9.7,11.3,11.7,16.7c2,5.5,1.8,10,1.4,13.3
|
||||
c-0.5,4.4-2,9.2-4.2,14.3l17,17c16.4-30.3,13.3-54.4-9.7-77.4c-13.2-13.2-30.1-17.8-50.3-13.7c-14,2.8-24.9,9.1-26.9,10.3
|
||||
L187,117.4c3.1,6,7.4,12.5,12.8,19.4L250,86.7l0,0c4.3-4.1,13-12.1,21.6-17.4c5.2-3.2,9.7-5.1,13.5-5.6
|
||||
C292,62.6,297.9,62.9,303.1,64.7z"/>
|
||||
<path class="st1" d="M170.4,55.4c-24.2,24.2-26.4,49.4-7.3,81.8c4.6,7.8,10.6,16.1,17.7,24.6c16.3,19.5,39.5,41.3,69.2,64.9
|
||||
c19.4-15.4,36.2-30.2,49.8-43.8c6.3-6.3,12-12.4,17.1-18.3l-16.2-16.2c-5,5.9-10.7,12.1-17,18.4c-9,9-19.5,18.5-31.2,28.4l-2.5,2.1
|
||||
l-2.5-2.1c-21.5-18.1-38.6-35-50.9-50.1c-7.8-9.6-13.8-18.6-17.7-26.7c-3.4-7-5.3-13.4-5.6-19c-0.2-2.7-0.1-6.9,2-12.1
|
||||
c2.1-5,5.8-10.2,11.3-15.7c2.1-2.1,5.4-5,10.4-6.8c5.2-1.9,11.1-2.2,18-0.9l0.1,0c3,0.6,6.4,2,10.4,4.3l18.2-18.2
|
||||
c-4.8-2.4-13.1-6.2-23-8.2C200.5,37.6,183.6,42.2,170.4,55.4z"/>
|
||||
<path class="st1" d="M273.2,77.6c-7.1,4.8-14,11.1-17.7,14.6l80.5,80.5c6.4-5.2,12.7-9.7,18.7-13.6L273.2,77.6z"/>
|
||||
<path class="st1" d="M55.4,329.6c24.2,24.2,49.4,26.4,81.8,7.3c7.8-4.6,16.1-10.6,24.6-17.7c19.5-16.3,41.3-39.5,64.9-69.2
|
||||
c-15.4-19.4-30.2-36.2-43.8-49.8c-6.3-6.3-12.4-12-18.3-17.1l-16.2,16.2c5.9,5,12.1,10.7,18.4,17c9,9,18.5,19.5,28.4,31.2l2.1,2.5
|
||||
l-2.1,2.5c-18.1,21.5-35,38.6-50.1,50.9c-9.6,7.8-18.6,13.8-26.7,17.7c-7,3.4-13.4,5.3-19,5.6c-2.7,0.2-6.9,0.1-12.1-2
|
||||
c-5-2.1-10.2-5.8-15.7-11.3c-2.1-2.1-5-5.4-6.8-10.4c-1.9-5.2-2.2-11.1-0.9-18l0-0.1c0.6-3,2-6.4,4.3-10.4l-18.2-18.2
|
||||
c-2.4,4.8-6.2,13.1-8.2,23C37.6,299.5,42.2,316.4,55.4,329.6z"/>
|
||||
<path class="st1" d="M86.7,250L86.7,250c-4.1-4.4-12-13.1-17.3-21.6c-3.2-5.2-5.1-9.7-5.6-13.5c-1.2-6.9-0.9-12.7,0.9-17.9
|
||||
c1.8-5,4.8-8.3,6.8-10.4c5.9-5.9,11.3-9.7,16.7-11.7c5.5-2,10-1.8,13.3-1.4c4.4,0.5,9.2,2,14.3,4.2l17-17
|
||||
c-30.3-16.4-54.4-13.3-77.4,9.7c-13.2,13.2-17.8,30.1-13.7,50.3c2.8,14,9.1,24.9,10.3,26.9l65.4,65.4c6-3.1,12.5-7.4,19.4-12.8
|
||||
L86.7,250z"/>
|
||||
<path class="st1" d="M92.2,244.5l80.5-80.5c-5.2-6.4-9.7-12.7-13.6-18.7l-81.5,81.5C82.4,233.9,88.6,240.8,92.2,244.5z"/>
|
||||
<path class="st1" d="M329.6,444.6c24.2-24.2,26.4-49.4,7.3-81.8c-4.6-7.8-10.6-16.1-17.7-24.6c-16.3-19.5-39.5-41.3-69.2-64.9
|
||||
c-19.4,15.4-36.2,30.2-49.8,43.8c-6.3,6.3-12,12.4-17.1,18.3l16.2,16.2c5-5.9,10.7-12.1,17-18.4c9-9,19.5-18.5,31.2-28.4l2.5-2.1
|
||||
l2.5,2.1c21.5,18.1,38.6,35,50.9,50.1c7.8,9.6,13.8,18.6,17.7,26.7c3.4,7,5.3,13.4,5.6,19c0.2,2.7,0.1,6.9-2,12.1
|
||||
c-2.1,5-5.8,10.2-11.3,15.7c-2.1,2.1-5.4,5-10.4,6.8c-5.2,1.9-11.1,2.2-18,0.9l-0.1,0c-3-0.6-6.4-2-10.4-4.3l-18.2,18.2
|
||||
c4.7,2.4,13.1,6.1,22.9,8.2C299.4,462.4,316.4,457.8,329.6,444.6z"/>
|
||||
<path class="st1" d="M244.5,407.8L164,327.3c-6.4,5.2-12.7,9.7-18.7,13.6l81.5,81.5C233.9,417.6,240.8,411.4,244.5,407.8z"/>
|
||||
<path class="st1" d="M170.4,444.6c13.2,13.2,30.1,17.8,50.3,13.7c14-2.8,24.9-9.1,26.9-10.3l65.4-65.4c-3.1-6-7.4-12.5-12.8-19.4
|
||||
L250,413.3l0,0c-4.3,4.1-13,12.1-21.6,17.4c-5.2,3.2-9.7,5.1-13.5,5.6c-6.9,1.2-12.7,0.9-17.9-0.9c-5-1.8-8.3-4.8-10.4-6.8
|
||||
c-5.9-5.9-9.7-11.3-11.7-16.7c-2-5.5-1.8-10-1.4-13.3c0.5-4.4,2-9.2,4.2-14.3l-17-17C144.3,397.5,147.3,421.5,170.4,444.6z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 9.7 KiB |
|
@ -1,20 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "build-release.sh [android|linux|macos|windows]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "VERSION" ]; then
|
||||
VERSION=`cat VERSION`
|
||||
else
|
||||
VERSION=`git describe --tags --abbrev=1`
|
||||
fi
|
||||
|
||||
if [ -f "BUILDDATE" ]; then
|
||||
BUILDDATE=`cat BUILDDATE`
|
||||
else
|
||||
BUILDDATE=`date +%G-%m-%d-%H-%M`
|
||||
fi
|
||||
|
||||
flutter build $1 --dart-define BUILD_VER=$VERSION --dart-define BUILD_DATE=$BUILDDATE
|
BIN
cwtch.png
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 45 KiB |
|
@ -1,6 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
VERSION=`cat LIBCWTCH-GO-MACOS.version`
|
||||
echo $VERSION
|
||||
|
||||
curl https://build.openprivacy.ca/files/libCwtch-go-macos-$VERSION/libCwtch.dylib --output libCwtch.dylib
|
|
@ -5,3 +5,5 @@ echo $VERSION
|
|||
|
||||
wget https://build.openprivacy.ca/files/libCwtch-go-$VERSION/cwtch.aar -O android/cwtch/cwtch.aar
|
||||
wget https://build.openprivacy.ca/files/libCwtch-go-$VERSION/libCwtch.so -O linux/libCwtch.so
|
||||
|
||||
# wget https://build.openprivacy.ca/files/libCwtch-go-$VERSION/libCwtch.dll -O windows/libCwtch.dll
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd macos
|
||||
curl https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-macos-0.4.6.7.tar.gz --output tor.tar.gz
|
||||
tar -xzf tor.tar.gz
|
||||
chmod a+x Tor/tor.real
|
||||
cd ..
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
Invoke-WebRequest -Uri https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-win64-0.4.6.5.zip -OutFile tor.zip
|
||||
Invoke-WebRequest -Uri https://dist.torproject.org/torbrowser/10.0.18/tor-win64-0.4.5.9.zip -OutFile tor.zip
|
||||
|
||||
if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '7917561a7a063440a1ddfa9cb544ab9ffd09de84cea3dd66e3cc9cd349dd9f85b74a522ec390d7a974bc19b424c4d53af60e57bbc47e763d13cab6a203c4592f' ) { Write-Error 'tor.zip sha512sum mismatch' }
|
||||
if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '72764eb07ad8ab511603aba0734951ca003989f5f4686af91ba220217b4a8a4bcc5f571b59f52c847932f6efedf847b111621983050fcddbb8099d43ca66fb07' ) { Write-Error 'tor.zip sha512sum mismatch' }
|
||||
|
||||
Expand-Archive -Path tor.zip -DestinationPath Tor
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import 'package:flutter/src/services/text_input.dart';
|
||||
|
||||
// To handle profiles that are "unencrypted" (i.e don't require a password to open) we currently create a profile with a defacto, hardcoded password.
|
||||
// Details: https://docs.openprivacy.ca/cwtch-security-handbook/profile_encryption_and_storage.html
|
||||
const DefaultPassword = "be gay do crime";
|
||||
|
||||
abstract class Cwtch {
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<void> Start();
|
||||
|
@ -45,23 +41,12 @@ abstract class Cwtch {
|
|||
void SendInvitation(String profile, String handle, String target);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ShareFile(String profile, String handle, String filepath);
|
||||
// ignore: non_constant_identifier_names
|
||||
void DownloadFile(String profile, String handle, String filepath, String manifestpath, String filekey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateDownloadableFile(String profile, String handle, String filenameSuggestion, String filekey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CheckDownloadStatus(String profile, String fileKey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profile, String handle, String filekey);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ArchiveConversation(String profile, String handle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteContact(String profile, String handle);
|
||||
void LeaveConversation(String profile, String handle);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateGroup(String profile, String server, String groupName);
|
||||
// ignore: non_constant_identifier_names
|
||||
void LeaveGroup(String profile, String groupID);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ImportBundle(String profile, String bundle);
|
||||
|
@ -69,29 +54,6 @@ abstract class Cwtch {
|
|||
void SetGroupAttribute(String profile, String groupHandle, String key, String value);
|
||||
// ignore: non_constant_identifier_names
|
||||
void RejectInvite(String profileOnion, String groupHandle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetProfileAttribute(String profile, String key, String val);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String contact, String key, String val);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void LoadServers(String password);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateServer(String password, String description, bool autostart);
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteServer(String serverOnion, String password);
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServers();
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServer(String serverOnion);
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServer(String serverOnion);
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServers();
|
||||
// ignore: non_constant_identifier_names
|
||||
void DestroyServers();
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetServerAttribute(String serverOnion, String key, String val);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void Shutdown();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/profileservers.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/notification_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -21,16 +20,14 @@ class CwtchNotifier {
|
|||
late TorStatus torStatus;
|
||||
late NotificationsManager notificationManager;
|
||||
late AppState appState;
|
||||
late ServerListState serverListState;
|
||||
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) {
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN) {
|
||||
profileCN = pcn;
|
||||
settings = settingsCN;
|
||||
error = errorCN;
|
||||
torStatus = torStatusCN;
|
||||
notificationManager = notificationManagerP;
|
||||
appState = appStateCN;
|
||||
serverListState = serverListStateCN;
|
||||
}
|
||||
|
||||
void handleMessage(String type, dynamic data) {
|
||||
|
@ -52,50 +49,27 @@ class CwtchNotifier {
|
|||
nickname: data["nick"],
|
||||
status: data["status"],
|
||||
imagePath: data["picture"],
|
||||
authorization: stringToContactAuthorization(data["authorization"]),
|
||||
isBlocked: data["authorization"] == "blocked",
|
||||
isInvitation: data["authorization"] == "unknown",
|
||||
savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"],
|
||||
numMessages: int.parse(data["numMessages"]),
|
||||
numUnread: int.parse(data["unread"]),
|
||||
isGroup: data["isGroup"] == true,
|
||||
server: data["groupServer"],
|
||||
archived: data["isArchived"] == true,
|
||||
lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet
|
||||
));
|
||||
break;
|
||||
case "NewServer":
|
||||
EnvironmentConfig.debugLog("NewServer $data");
|
||||
serverListState.add(
|
||||
data["Onion"],
|
||||
data["ServerBundle"],
|
||||
data["Running"] == "true",
|
||||
data["Description"],
|
||||
data["Autostart"] == "true",
|
||||
data["StorageType"] == "storage-password");
|
||||
break;
|
||||
case "ServerIntentUpdate":
|
||||
EnvironmentConfig.debugLog("ServerIntentUpdate $data");
|
||||
var server = serverListState.getServer(data["Identity"]);
|
||||
if (server != null) {
|
||||
server.setRunning(data["Intent"] == "running");
|
||||
}
|
||||
break;
|
||||
case "GroupCreated":
|
||||
|
||||
// Retrieve Server Status from Cache...
|
||||
String status = "";
|
||||
RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
|
||||
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
|
||||
if (serverInfoState != null) {
|
||||
status = serverInfoState.status;
|
||||
}
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"],
|
||||
authorization: ContactAuthorization.approved,
|
||||
imagePath: data["PicturePath"],
|
||||
nickname: data["GroupName"],
|
||||
status: status,
|
||||
server: data["GroupServer"],
|
||||
isGroup: true,
|
||||
lastMessageTime: DateTime.now()));
|
||||
isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
}
|
||||
break;
|
||||
|
@ -104,12 +78,6 @@ class CwtchNotifier {
|
|||
// todo standarize
|
||||
error.handleUpdate("deleteprofile.success");
|
||||
break;
|
||||
case "ServerDeleted":
|
||||
error.handleUpdate("deletedserver." + data["Status"]);
|
||||
if (data["Status"] == "success") {
|
||||
serverListState.delete(data["Identity"]);
|
||||
}
|
||||
break;
|
||||
case "DeleteContact":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["RemotePeer"]);
|
||||
break;
|
||||
|
@ -123,7 +91,8 @@ class CwtchNotifier {
|
|||
contact.status = data["ConnectionState"];
|
||||
}
|
||||
if (data["authorization"] != null) {
|
||||
contact.authorization = stringToContactAuthorization(data["authorization"]);
|
||||
contact.isInvitation = data["authorization"] == "unknown";
|
||||
contact.isBlocked = data["authorization"] == "blocked";
|
||||
}
|
||||
// contact.[status/isBlocked] might change the list's sort order
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
|
||||
|
@ -133,8 +102,6 @@ class CwtchNotifier {
|
|||
notificationManager.notify("New Message From Peer!");
|
||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["RemotePeer"]) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++;
|
||||
} else {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.newMarker++;
|
||||
}
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
||||
|
@ -173,34 +140,13 @@ class CwtchNotifier {
|
|||
break;
|
||||
case "NewMessageFromGroup":
|
||||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||
var idx = int.parse(data["Index"]);
|
||||
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages;
|
||||
|
||||
// Only bother to do anything if we know about the group and the provided index is greater than our current total...
|
||||
if (currentTotal != null && idx >= currentTotal) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages = idx + 1;
|
||||
|
||||
//if not currently open
|
||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
||||
} else {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.newMarker++;
|
||||
}
|
||||
|
||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||
// TODO: There are 2 timestamps associated with a new group message - time sent and time received.
|
||||
// Sent refers to the time a profile alleges they sent a message
|
||||
// Received refers to the time we actually saw the message from the server
|
||||
// These can obviously be very different for legitimate reasons.
|
||||
// We also maintain a relative hash-link through PreviousMessageSignature which is the ground truth for
|
||||
// order.
|
||||
// In the future we will want to combine these 3 ordering mechanisms into a cohesive view of the timeline
|
||||
// For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts
|
||||
// and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time`
|
||||
// and `local now`.
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], timestampSent.toLocal());
|
||||
notificationManager.notify("New Message From Group!");
|
||||
//if not currently open
|
||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
||||
}
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
notificationManager.notify("New Message From Group!");
|
||||
} else {
|
||||
// from me (already displayed - do not update counter)
|
||||
var idx = data["Signature"];
|
||||
|
@ -218,15 +164,10 @@ class CwtchNotifier {
|
|||
case "MessageCounterResync":
|
||||
var contactHandle = data["RemotePeer"];
|
||||
if (contactHandle == null || contactHandle == "") contactHandle = data["GroupID"];
|
||||
var total = int.parse(data["Data"]);
|
||||
if (total != profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages) {
|
||||
profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages = total;
|
||||
}
|
||||
break;
|
||||
case "SendMessageToPeerError":
|
||||
// Ignore
|
||||
profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages = int.parse(data["Data"]);
|
||||
break;
|
||||
case "IndexedFailure":
|
||||
EnvironmentConfig.debugLog("IndexedFailure");
|
||||
var idx = data["Index"];
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])?.getMessageKey(idx);
|
||||
try {
|
||||
|
@ -236,6 +177,20 @@ class CwtchNotifier {
|
|||
// ignore, we likely have an old key that has been replaced with an actual signature
|
||||
}
|
||||
break;
|
||||
case "SendMessageToPeerError":
|
||||
// from me (already displayed - do not update counter)
|
||||
EnvironmentConfig.debugLog("SendMessageToPeerError");
|
||||
var idx = data["EventID"];
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||
if (key == null) break;
|
||||
try {
|
||||
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||
if (message == null) break;
|
||||
message.error = true;
|
||||
} catch (e) {
|
||||
// ignore, we likely have an old key that has been replaced with an actual signature
|
||||
}
|
||||
break;
|
||||
case "SendMessageToGroupError":
|
||||
// from me (already displayed - do not update counter)
|
||||
EnvironmentConfig.debugLog("SendMessageToGroupError");
|
||||
|
@ -293,29 +248,29 @@ class CwtchNotifier {
|
|||
|
||||
// Retrieve Server Status from Cache...
|
||||
String status = "";
|
||||
RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])!.serverList.getServer(groupInvite["ServerHost"]);
|
||||
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])!.serverList.getServer(groupInvite["ServerHost"]);
|
||||
if (serverInfoState != null) {
|
||||
status = serverInfoState.status;
|
||||
}
|
||||
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
||||
authorization: ContactAuthorization.approved,
|
||||
isInvitation: false,
|
||||
imagePath: data["PicturePath"],
|
||||
nickname: groupInvite["GroupName"],
|
||||
server: groupInvite["ServerHost"],
|
||||
status: status,
|
||||
isGroup: true,
|
||||
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(0)));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.fromMillisecondsSinceEpoch(0));
|
||||
lastMessageTime: DateTime.now()));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.now());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "AcceptGroupInvite":
|
||||
EnvironmentConfig.debugLog("accept group invite");
|
||||
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.authorization = ContactAuthorization.approved;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.fromMillisecondsSinceEpoch(0));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.isInvitation = false;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
break;
|
||||
case "ServerStateChange":
|
||||
// Update the Server Cache
|
||||
|
@ -333,31 +288,12 @@ class CwtchNotifier {
|
|||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.nickname = data["Data"];
|
||||
}
|
||||
} else if (data["Key"] == "local.archived") {
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.isArchived = data["Data"] == "true";
|
||||
}
|
||||
} else {
|
||||
EnvironmentConfig.debugLog("unhandled set group attribute event: ${data['Key']}");
|
||||
}
|
||||
break;
|
||||
case "SetPeerAttribute":
|
||||
if (data["Key"] == "local.name") {
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.nickname = data["Data"];
|
||||
}
|
||||
} else if (data["Key"] == "local.archived") {
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.isArchived = data["Data"] == "true";
|
||||
}
|
||||
} else if (data["Key"] == "LastKnowSignature") {
|
||||
// group syncing information that isn't relevant to the UI...
|
||||
} else {
|
||||
EnvironmentConfig.debugLog("unhandled set peer attribute event: ${data['Key']}");
|
||||
}
|
||||
break;
|
||||
case "NewRetValMessageFromPeer":
|
||||
if (data["Path"] == "name" && data["Data"].toString().trim().length > 0) {
|
||||
if (data["Path"] == "name") {
|
||||
// Update locally on the UI...
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.nickname = data["Data"];
|
||||
|
@ -366,20 +302,6 @@ class CwtchNotifier {
|
|||
EnvironmentConfig.debugLog("unhandled peer attribute event: ${data['Path']}");
|
||||
}
|
||||
break;
|
||||
case "ManifestSaved":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]);
|
||||
break;
|
||||
case "FileDownloadProgressUpdate":
|
||||
var progress = int.parse(data["Progress"]);
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], progress, int.parse(data["FileSizeInChunks"]));
|
||||
// progress == -1 is a "download was interrupted" message and should contain a path
|
||||
if (progress < 0) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadSetPath(data["FileKey"], data["FilePath"]);
|
||||
}
|
||||
break;
|
||||
case "FileDownloaded":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkFinished(data["FileKey"], data["FilePath"]);
|
||||
break;
|
||||
default:
|
||||
EnvironmentConfig.debugLog("unhandled event: $type");
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||
import 'dart:isolate';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:cwtch/cwtch/cwtchNotifier.dart';
|
||||
import 'package:flutter/src/services/text_input.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
@ -11,9 +12,6 @@ import 'package:cwtch/cwtch/cwtch.dart';
|
|||
|
||||
import '../config.dart';
|
||||
|
||||
import "package:path/path.dart" show dirname, join;
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
/////////////////////
|
||||
/// Cwtch API ///
|
||||
/////////////////////
|
||||
|
@ -24,9 +22,6 @@ typedef StartCwtchFn = int Function(Pointer<Utf8> dir, int len, Pointer<Utf8> to
|
|||
typedef void_from_void_funtion = Void Function();
|
||||
typedef VoidFromVoidFunction = void Function();
|
||||
|
||||
typedef free_function = Void Function(Pointer<Utf8>);
|
||||
typedef FreeFn = void Function(Pointer<Utf8>);
|
||||
|
||||
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
|
@ -36,14 +31,11 @@ typedef VoidFromStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer
|
|||
typedef void_from_string_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef void_from_string_string_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringStringStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef void_from_string_string_int_int_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int64, Int64);
|
||||
typedef VoidFromStringStringIntIntFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
|
||||
|
||||
typedef void_from_string_string_byte_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int8);
|
||||
typedef VoidFromStringStringByteFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
|
||||
typedef access_cwtch_eventbus_function = Void Function();
|
||||
typedef NextEventFn = void Function();
|
||||
|
||||
typedef string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length);
|
||||
typedef StringFn = void Function(Pointer<Utf8> dir, int);
|
||||
|
@ -51,9 +43,16 @@ typedef StringFn = void Function(Pointer<Utf8> dir, int);
|
|||
typedef string_string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
||||
typedef StringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef get_json_blob_void_function = Pointer<Utf8> Function();
|
||||
typedef GetJsonBlobVoidFn = Pointer<Utf8> Function();
|
||||
|
||||
typedef get_json_blob_string_function = Pointer<Utf8> Function(Pointer<Utf8> str, Int32 length);
|
||||
typedef GetJsonBlobStringFn = Pointer<Utf8> Function(Pointer<Utf8> str, int len);
|
||||
|
||||
//func NumMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int) (n C.int) {
|
||||
typedef get_int_from_str_str_function = Int32 Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef GetIntFromStrStrFn = int Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
//func GetMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, message_index C.int) *C.char {
|
||||
typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32);
|
||||
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
|
||||
|
@ -65,35 +64,22 @@ typedef GetJsonBlobFromStrStrStrFn = Pointer<Utf8> Function(Pointer<Utf8>, int,
|
|||
typedef appbus_events_function = Pointer<Utf8> Function();
|
||||
typedef AppbusEventsFn = Pointer<Utf8> Function();
|
||||
|
||||
const String UNSUPPORTED_OS = "unsupported-os";
|
||||
|
||||
class CwtchFfi implements Cwtch {
|
||||
late DynamicLibrary library;
|
||||
late CwtchNotifier cwtchNotifier;
|
||||
late Isolate cwtchIsolate;
|
||||
ReceivePort _receivePort = ReceivePort();
|
||||
|
||||
static String getLibraryPath() {
|
||||
if (Platform.isWindows) {
|
||||
return "libCwtch.dll";
|
||||
} else if (Platform.isLinux) {
|
||||
return "libCwtch.so";
|
||||
} else if (Platform.isMacOS) {
|
||||
print(dirname(Platform.script.path));
|
||||
return "libCwtch.dylib";
|
||||
} else {
|
||||
return UNSUPPORTED_OS;
|
||||
}
|
||||
}
|
||||
|
||||
CwtchFfi(CwtchNotifier _cwtchNotifier) {
|
||||
String library_path = getLibraryPath();
|
||||
if (library_path == UNSUPPORTED_OS) {
|
||||
if (Platform.isWindows) {
|
||||
library = DynamicLibrary.open("libCwtch.dll");
|
||||
} else if (Platform.isLinux) {
|
||||
library = DynamicLibrary.open("libCwtch.so");
|
||||
} else {
|
||||
print("OS ${Platform.operatingSystem} not supported by cwtch/ffi");
|
||||
// emergency, ideally the app stays on splash and just posts the error till user closes
|
||||
exit(0);
|
||||
}
|
||||
library = DynamicLibrary.open(library_path);
|
||||
cwtchNotifier = _cwtchNotifier;
|
||||
}
|
||||
|
||||
|
@ -102,9 +88,8 @@ class CwtchFfi implements Cwtch {
|
|||
String home = "";
|
||||
String bundledTor = "";
|
||||
Map<String, String> envVars = Platform.environment;
|
||||
String cwtchDir = "";
|
||||
if (Platform.isLinux) {
|
||||
cwtchDir = envVars['CWTCH_HOME'] ?? path.join(envVars['HOME']!, ".cwtch");
|
||||
home = envVars['HOME']!;
|
||||
if (await File("linux/tor").exists()) {
|
||||
bundledTor = "linux/tor";
|
||||
} else if (await File("lib/tor").exists()) {
|
||||
|
@ -117,50 +102,14 @@ class CwtchFfi implements Cwtch {
|
|||
bundledTor = "tor";
|
||||
}
|
||||
} else if (Platform.isWindows) {
|
||||
cwtchDir = envVars['CWTCH_DIR'] ?? path.join(envVars['UserProfile']!, ".cwtch");
|
||||
home = envVars['UserProfile']!;
|
||||
bundledTor = "Tor\\Tor\\tor.exe";
|
||||
} else if (Platform.isMacOS) {
|
||||
cwtchDir = envVars['CWTCH_HOME'] ?? path.join(envVars['HOME']!, "Library/Application Support/Cwtch");
|
||||
if (await File("Cwtch.app/Contents/MacOS/Tor/tor.real").exists()) {
|
||||
bundledTor = "Cwtch.app/Contents/MacOS/Tor/tor.real";
|
||||
} else if (await File("/Applications/Cwtch.app/Contents/MacOS/Tor/tor.real").exists()) {
|
||||
bundledTor = "/Applications/Cwtch.app/Contents/MacOS/Tor/tor.real";
|
||||
} else if (await File("/Volumes/Cwtch/Cwtch.app/Contents/MacOS/Tor/tor.real").exists()) {
|
||||
bundledTor = "/Volumes/Cwtch/Cwtch.app/Contents/MacOS/Tor/tor.real";
|
||||
} else if (await File("/Applications/Tor Browser.app/Contents/MacOS/Tor/tor.real").exists()) {
|
||||
bundledTor = "/Applications/Tor Browser.app/Contents/MacOS/Tor/tor.real";
|
||||
print("We couldn't find Tor in the Cwtch app directory, however we can fall back to the Tor Browser binary");
|
||||
} else {
|
||||
var splitPath = path.split(dirname(Platform.script.path));
|
||||
if (splitPath[0] == "/" && splitPath[1] == "Applications") {
|
||||
var appName = splitPath[2];
|
||||
print("We're running in /Applications in a non standard app name: $appName");
|
||||
if (await File("/Applications/$appName/Contents/MacOS/Tor/tor.real").exists()) {
|
||||
bundledTor = "/Applications/$appName/Contents/MacOS/Tor/tor.real";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the first Cwtch MacOS release (1.2) accidently was a dev build
|
||||
// we need to temporarily remedy this for a release or two then delete
|
||||
// if macOs and release build and no profile and is dev profile
|
||||
// copy dev profile to release profile
|
||||
if (Platform.isMacOS && EnvironmentConfig.BUILD_VER != dev_version) {
|
||||
var devProfileExists = await Directory(path.join(cwtchDir, "dev", "profiles")).exists();
|
||||
var releaseProfileExists = await Directory(path.join(cwtchDir, "profiles")).exists();
|
||||
if (devProfileExists && !releaseProfileExists) {
|
||||
print("MacOS one time dev -> release profile migration...");
|
||||
await Process.run("cp", ["-r", "-p", path.join(cwtchDir, "dev", "profiles"), cwtchDir]);
|
||||
await Process.run("cp", ["-r", "-p", path.join(cwtchDir, "dev", "SALT"), cwtchDir]);
|
||||
await Process.run("cp", ["-r", "-p", path.join(cwtchDir, "dev", "ui.globals"), cwtchDir]);
|
||||
}
|
||||
}
|
||||
|
||||
var cwtchDir = envVars['CWTCH_HOME'] ?? path.join(home, ".cwtch");
|
||||
if (EnvironmentConfig.BUILD_VER == dev_version) {
|
||||
cwtchDir = path.join(cwtchDir, "dev");
|
||||
}
|
||||
|
||||
print("StartCwtch( cwtchdir: $cwtchDir, torPath: $bundledTor )");
|
||||
|
||||
var startCwtchC = library.lookup<NativeFunction<start_cwtch_function>>("c_StartCwtch");
|
||||
|
@ -189,7 +138,9 @@ class CwtchFfi implements Cwtch {
|
|||
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
|
||||
@override
|
||||
void dispose() {
|
||||
cwtchIsolate.kill(priority: Isolate.immediate);
|
||||
if (cwtchIsolate != null) {
|
||||
cwtchIsolate.kill(priority: Isolate.immediate);
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point for an isolate to listen to a stream of events pulled from libcwtch-go and return them on the sendPort
|
||||
|
@ -203,27 +154,20 @@ class CwtchFfi implements Cwtch {
|
|||
|
||||
// Steam of appbus events. Call blocks in libcwtch-go GetAppbusEvent. Static so the isolate can use it
|
||||
static Stream<String> pollAppbusEvents() async* {
|
||||
late DynamicLibrary library = DynamicLibrary.open(getLibraryPath());
|
||||
late DynamicLibrary library;
|
||||
if (Platform.isWindows) {
|
||||
library = DynamicLibrary.open("libCwtch.dll");
|
||||
} else if (Platform.isLinux) {
|
||||
library = DynamicLibrary.open("libCwtch.so");
|
||||
}
|
||||
|
||||
var getAppbusEventC = library.lookup<NativeFunction<appbus_events_function>>("c_GetAppBusEvent");
|
||||
// ignore: non_constant_identifier_names
|
||||
final GetAppbusEvent = getAppbusEventC.asFunction<AppbusEventsFn>();
|
||||
|
||||
// Embedded Version of _UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved
|
||||
var free = library.lookup<NativeFunction<free_function>>("c_FreePointer");
|
||||
final Free = free.asFunction<FreeFn>();
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
final GetAppBusEvent = () {
|
||||
// ignore: non_constant_identifier_names
|
||||
while (true) {
|
||||
Pointer<Utf8> result = GetAppbusEvent();
|
||||
String event = result.toDartString();
|
||||
Free(result);
|
||||
return event;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
final event = GetAppBusEvent();
|
||||
|
||||
if (event.startsWith("{\"EventType\":\"Shutdown\"")) {
|
||||
print("Shutting down isolate thread: $event");
|
||||
|
@ -240,7 +184,6 @@ class CwtchFfi implements Cwtch {
|
|||
final SelectProfile = selectProfileC.asFunction<GetJsonBlobStringFn>();
|
||||
final ut8Onion = onion.toNativeUtf8();
|
||||
SelectProfile(ut8Onion, ut8Onion.length);
|
||||
malloc.free(ut8Onion);
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
|
@ -251,8 +194,6 @@ class CwtchFfi implements Cwtch {
|
|||
final utf8nick = nick.toNativeUtf8();
|
||||
final ut8pass = pass.toNativeUtf8();
|
||||
CreateProfile(utf8nick, utf8nick.length, ut8pass, ut8pass.length);
|
||||
malloc.free(utf8nick);
|
||||
malloc.free(ut8pass);
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
|
@ -262,7 +203,6 @@ class CwtchFfi implements Cwtch {
|
|||
final LoadProfiles = loadProfileC.asFunction<StringFn>();
|
||||
final ut8pass = pass.toNativeUtf8();
|
||||
LoadProfiles(ut8pass, ut8pass.length);
|
||||
malloc.free(ut8pass);
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
|
@ -274,9 +214,6 @@ class CwtchFfi implements Cwtch {
|
|||
final utf8handle = handle.toNativeUtf8();
|
||||
Pointer<Utf8> jsonMessageBytes = GetMessage(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index);
|
||||
String jsonMessage = jsonMessageBytes.toDartString();
|
||||
_UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(jsonMessageBytes);
|
||||
malloc.free(utf8profile);
|
||||
malloc.free(utf8handle);
|
||||
return jsonMessage;
|
||||
}
|
||||
|
||||
|
@ -289,8 +226,6 @@ class CwtchFfi implements Cwtch {
|
|||
final utf8onion = onion.toNativeUtf8();
|
||||
final utf8json = json.toNativeUtf8();
|
||||
SendAppBusEvent(utf8onion, utf8onion.length, utf8json, utf8json.length);
|
||||
malloc.free(utf8onion);
|
||||
malloc.free(utf8json);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -301,7 +236,6 @@ class CwtchFfi implements Cwtch {
|
|||
final SendAppBusEvent = sendAppBusEvent.asFunction<StringFn>();
|
||||
final utf8json = json.toNativeUtf8();
|
||||
SendAppBusEvent(utf8json, utf8json.length);
|
||||
malloc.free(utf8json);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -313,8 +247,6 @@ class CwtchFfi implements Cwtch {
|
|||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
AcceptContact(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -326,8 +258,6 @@ class CwtchFfi implements Cwtch {
|
|||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
BlockContact(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -340,9 +270,6 @@ class CwtchFfi implements Cwtch {
|
|||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = message.toNativeUtf8();
|
||||
SendMessage(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -355,80 +282,8 @@ class CwtchFfi implements Cwtch {
|
|||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = target.toNativeUtf8();
|
||||
SendInvitation(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ShareFile(String profileOnion, String contactHandle, String filepath) {
|
||||
var shareFile = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_ShareFile");
|
||||
// ignore: non_constant_identifier_names
|
||||
final ShareFile = shareFile.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = filepath.toNativeUtf8();
|
||||
ShareFile(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DownloadFile(String profileOnion, String contactHandle, String filepath, String manifestpath, String filekey) {
|
||||
var dlFile = library.lookup<NativeFunction<void_from_string_string_string_string_string_function>>("c_DownloadFile");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DownloadFile = dlFile.asFunction<VoidFromStringStringStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = filepath.toNativeUtf8();
|
||||
final u4 = manifestpath.toNativeUtf8();
|
||||
final u5 = filekey.toNativeUtf8();
|
||||
DownloadFile(u1, u1.length, u2, u2.length, u3, u3.length, u4, u4.length, u5, u5.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
malloc.free(u4);
|
||||
malloc.free(u5);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateDownloadableFile(String profileOnion, String contactHandle, String filenameSuggestion, String filekey) {
|
||||
// android only - do nothing
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CheckDownloadStatus(String profileOnion, String fileKey) {
|
||||
var checkDownloadStatus = library.lookup<NativeFunction<string_string_to_void_function>>("c_CheckDownloadStatus");
|
||||
// ignore: non_constant_identifier_names
|
||||
final CheckDownloadStatus = checkDownloadStatus.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = fileKey.toNativeUtf8();
|
||||
CheckDownloadStatus(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profileOnion, String contactHandle, String filekey) {
|
||||
var fn = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_VerifyOrResumeDownload");
|
||||
// ignore: non_constant_identifier_names
|
||||
final VerifyOrResumeDownload = fn.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = filekey.toNativeUtf8();
|
||||
VerifyOrResumeDownload(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor() {
|
||||
|
@ -447,8 +302,6 @@ class CwtchFfi implements Cwtch {
|
|||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = bundle.toNativeUtf8();
|
||||
ImportBundle(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -462,10 +315,6 @@ class CwtchFfi implements Cwtch {
|
|||
final u3 = key.toNativeUtf8();
|
||||
final u4 = value.toNativeUtf8();
|
||||
SetGroupAttribute(u1, u1.length, u2, u2.length, u3, u3.length, u4, u4.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
malloc.free(u4);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -477,12 +326,9 @@ class CwtchFfi implements Cwtch {
|
|||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = groupHandle.toNativeUtf8();
|
||||
RejectInvite(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateGroup(String profileOnion, String server, String groupName) {
|
||||
var createGroup = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_CreateGroup");
|
||||
// ignore: non_constant_identifier_names
|
||||
|
@ -491,40 +337,31 @@ class CwtchFfi implements Cwtch {
|
|||
final u2 = server.toNativeUtf8();
|
||||
final u3 = groupName.toNativeUtf8();
|
||||
CreateGroup(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ArchiveConversation(String profileOnion, String handle) {
|
||||
var archiveConversation = library.lookup<NativeFunction<string_string_to_void_function>>("c_ArchiveConversation");
|
||||
void LeaveConversation(String profileOnion, String handle) {
|
||||
var leaveConversation = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveConversation");
|
||||
// ignore: non_constant_identifier_names
|
||||
final ArchiveConversation = archiveConversation.asFunction<VoidFromStringStringFn>();
|
||||
final LeaveConversation = leaveConversation.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = handle.toNativeUtf8();
|
||||
ArchiveConversation(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
LeaveConversation(u1, u1.length, u2, u2.length);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteContact(String profileOnion, String handle) {
|
||||
var deleteContact = library.lookup<NativeFunction<string_string_to_void_function>>("c_DeleteContact");
|
||||
void LeaveGroup(String profileOnion, String groupHandle) {
|
||||
var leaveGroup = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveGroup");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DeleteContact = deleteContact.asFunction<VoidFromStringStringFn>();
|
||||
final LeaveGroup = leaveGroup.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = handle.toNativeUtf8();
|
||||
DeleteContact(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
final u2 = groupHandle.toNativeUtf8();
|
||||
LeaveGroup(u1, u1.length, u2, u2.length);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void UpdateMessageFlags(String profile, String handle, int index, int flags) {
|
||||
var updateMessageFlagsC = library.lookup<NativeFunction<void_from_string_string_int_int_function>>("c_UpdateMessageFlags");
|
||||
// ignore: non_constant_identifier_names
|
||||
|
@ -532,8 +369,6 @@ class CwtchFfi implements Cwtch {
|
|||
final utf8profile = profile.toNativeUtf8();
|
||||
final utf8handle = handle.toNativeUtf8();
|
||||
updateMessageFlags(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index, flags);
|
||||
malloc.free(utf8profile);
|
||||
malloc.free(utf8handle);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -545,151 +380,14 @@ class CwtchFfi implements Cwtch {
|
|||
final u1 = onion.toNativeUtf8();
|
||||
final u2 = currentPassword.toNativeUtf8();
|
||||
DeleteProfile(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetProfileAttribute(String profile, String key, String val) {
|
||||
var setProfileAttribute = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_SetProfileAttribute");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SetProfileAttribute = setProfileAttribute.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profile.toNativeUtf8();
|
||||
final u2 = key.toNativeUtf8();
|
||||
final u3 = key.toNativeUtf8();
|
||||
SetProfileAttribute(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String contact, String key, String val) {
|
||||
var setContactAttribute = library.lookup<NativeFunction<void_from_string_string_string_string_function>>("c_SetContactAttribute");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SetContactAttribute = setContactAttribute.asFunction<VoidFromStringStringStringStringFn>();
|
||||
final u1 = profile.toNativeUtf8();
|
||||
final u2 = contact.toNativeUtf8();
|
||||
final u3 = key.toNativeUtf8();
|
||||
final u4 = key.toNativeUtf8();
|
||||
SetContactAttribute(u1, u1.length, u2, u2.length, u3, u3.length, u4, u4.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
malloc.free(u4);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LoadServers(String password) {
|
||||
var loadServers = library.lookup<NativeFunction<string_to_void_function>>("c_LoadServers");
|
||||
// ignore: non_constant_identifier_names
|
||||
final LoadServers = loadServers.asFunction<StringFn>();
|
||||
final u1 = password.toNativeUtf8();
|
||||
LoadServers(u1, u1.length);
|
||||
malloc.free(u1);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateServer(String password, String description, bool autostart) {
|
||||
var createServer = library.lookup<NativeFunction<void_from_string_string_byte_function>>("c_CreateServer");
|
||||
// ignore: non_constant_identifier_names
|
||||
final CreateServer = createServer.asFunction<VoidFromStringStringByteFn>();
|
||||
final u1 = password.toNativeUtf8();
|
||||
final u2 = description.toNativeUtf8();
|
||||
CreateServer(u1, u1.length, u2, u2.length, autostart ? 1 : 0);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteServer(String serverOnion, String password) {
|
||||
var deleteServer = library.lookup<NativeFunction<string_string_to_void_function>>("c_DeleteServer");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DeleteServer = deleteServer.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = serverOnion.toNativeUtf8();
|
||||
final u2 = password.toNativeUtf8();
|
||||
DeleteServer(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServers() {
|
||||
var launchServers = library.lookup<NativeFunction<Void Function()>>("c_LaunchServers");
|
||||
// ignore: non_constant_identifier_names
|
||||
final LaunchServers = launchServers.asFunction<void Function()>();
|
||||
LaunchServers();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServer(String serverOnion) {
|
||||
var launchServer = library.lookup<NativeFunction<string_to_void_function>>("c_LaunchServer");
|
||||
// ignore: non_constant_identifier_names
|
||||
final LaunchServer = launchServer.asFunction<StringFn>();
|
||||
final u1 = serverOnion.toNativeUtf8();
|
||||
LaunchServer(u1, u1.length);
|
||||
malloc.free(u1);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServer(String serverOnion) {
|
||||
var shutdownServer = library.lookup<NativeFunction<string_to_void_function>>("c_StopServer");
|
||||
// ignore: non_constant_identifier_names
|
||||
final ShutdownServer = shutdownServer.asFunction<StringFn>();
|
||||
final u1 = serverOnion.toNativeUtf8();
|
||||
ShutdownServer(u1, u1.length);
|
||||
malloc.free(u1);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServers() {
|
||||
var shutdownServers = library.lookup<NativeFunction<Void Function()>>("c_StopServers");
|
||||
// ignore: non_constant_identifier_names
|
||||
final ShutdownServers = shutdownServers.asFunction<void Function()>();
|
||||
ShutdownServers();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DestroyServers() {
|
||||
var destroyServers = library.lookup<NativeFunction<Void Function()>>("c_DestroyServers");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DestroyServers = destroyServers.asFunction<void Function()>();
|
||||
DestroyServers();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetServerAttribute(String serverOnion, String key, String val) {
|
||||
var setServerAttribute = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_SetServerAttribute");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SetServerAttribute = setServerAttribute.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = serverOnion.toNativeUtf8();
|
||||
final u2 = key.toNativeUtf8();
|
||||
final u3 = val.toNativeUtf8();
|
||||
SetServerAttribute(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<void> Shutdown() async {
|
||||
var shutdown = library.lookup<NativeFunction<void_from_void_funtion>>("c_ShutdownCwtch");
|
||||
// ignore: non_constant_identifier_names
|
||||
|
||||
// Shutdown Cwtch + Tor...
|
||||
// ignore: non_constant_identifier_names
|
||||
final Shutdown = shutdown.asFunction<VoidFromVoidFunction>();
|
||||
Shutdown();
|
||||
|
||||
|
@ -702,7 +400,6 @@ class CwtchFfi implements Cwtch {
|
|||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
Future GetMessageByContentHash(String profile, String handle, String contentHash) async {
|
||||
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_str_str_function>>("c_GetMessagesByContentHash");
|
||||
// ignore: non_constant_identifier_names
|
||||
|
@ -712,20 +409,6 @@ class CwtchFfi implements Cwtch {
|
|||
final utf8contentHash = contentHash.toNativeUtf8();
|
||||
Pointer<Utf8> jsonMessageBytes = GetMessagesByContentHash(utf8profile, utf8profile.length, utf8handle, utf8handle.length, utf8contentHash, utf8contentHash.length);
|
||||
String jsonMessage = jsonMessageBytes.toDartString();
|
||||
|
||||
_UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(jsonMessageBytes);
|
||||
malloc.free(utf8profile);
|
||||
malloc.free(utf8handle);
|
||||
malloc.free(utf8contentHash);
|
||||
return jsonMessage;
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
// Incredibly dangerous function which invokes a free in libCwtch, should only be used
|
||||
// as documented in `MEMORY.md` in libCwtch repo.
|
||||
void _UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(Pointer<Utf8> ptr) {
|
||||
var free = library.lookup<NativeFunction<free_function>>("c_FreePointer");
|
||||
final Free = free.asFunction<FreeFn>();
|
||||
Free(ptr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,35 +131,6 @@ class CwtchGomobile implements Cwtch {
|
|||
cwtchPlatform.invokeMethod("SendInvitation", {"ProfileOnion": profileOnion, "handle": contactHandle, "target": target});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ShareFile(String profileOnion, String contactHandle, String filepath) {
|
||||
cwtchPlatform.invokeMethod("ShareFile", {"ProfileOnion": profileOnion, "handle": contactHandle, "filepath": filepath});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DownloadFile(String profileOnion, String contactHandle, String filepath, String manifestpath, String filekey) {
|
||||
cwtchPlatform.invokeMethod("DownloadFile", {"ProfileOnion": profileOnion, "handle": contactHandle, "filepath": filepath, "manifestpath": manifestpath, "filekey": filekey});
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateDownloadableFile(String profileOnion, String contactHandle, String filenameSuggestion, String filekey) {
|
||||
cwtchPlatform.invokeMethod("CreateDownloadableFile", {"ProfileOnion": profileOnion, "handle": contactHandle, "filename": filenameSuggestion, "filekey": filekey});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CheckDownloadStatus(String profileOnion, String fileKey) {
|
||||
cwtchPlatform.invokeMethod("CheckDownloadStatus", {"ProfileOnion": profileOnion, "fileKey": fileKey});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profileOnion, String contactHandle, String filekey) {
|
||||
cwtchPlatform.invokeMethod("VerifyOrResumeDownload", {"ProfileOnion": profileOnion, "handle": contactHandle, "filekey": filekey});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor() {
|
||||
|
@ -191,89 +162,23 @@ class CwtchGomobile implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteContact(String profileOnion, String handle) {
|
||||
cwtchPlatform.invokeMethod("DeleteContact", {"ProfileOnion": profileOnion, "handle": handle});
|
||||
void LeaveGroup(String profileOnion, String groupHandle) {
|
||||
cwtchPlatform.invokeMethod("LeaveGroup", {"ProfileOnion": profileOnion, "groupHandle": groupHandle});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ArchiveConversation(String profileOnion, String contactHandle) {
|
||||
cwtchPlatform.invokeMethod("ArchiveConversation", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
||||
void LeaveConversation(String profileOnion, String contactHandle) {
|
||||
cwtchPlatform.invokeMethod("LeaveConversation", {"ProfileOnion": profileOnion, "contactHandle": contactHandle});
|
||||
}
|
||||
|
||||
@override
|
||||
void UpdateMessageFlags(String profile, String handle, int index, int flags) {
|
||||
print("gomobile.dart UpdateMessageFlags " + index.toString());
|
||||
cwtchPlatform.invokeMethod("UpdateMessageFlags", {"profile": profile, "contact": handle, "midx": index, "flags": flags});
|
||||
cwtchPlatform.invokeMethod("UpdateMessageFlags", {"profile": profile, "contact": handle, "index": index, "flags": flags});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetProfileAttribute(String profile, String key, String val) {
|
||||
cwtchPlatform.invokeMethod("SetProfileAttribute", {"ProfileOnion": profile, "Key": key, "Val": val});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String contact, String key, String val) {
|
||||
cwtchPlatform.invokeMethod("SetContactAttribute", {"ProfileOnion": profile, "Contact": contact, "Key": key, "Val": val});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LoadServers(String password) {
|
||||
cwtchPlatform.invokeMethod("LoadServers", {"Password": password});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateServer(String password, String description, bool autostart) {
|
||||
cwtchPlatform.invokeMethod("CreateServer", {"Password": password, "Description": description, "Autostart": autostart});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteServer(String serverOnion, String password) {
|
||||
cwtchPlatform.invokeMethod("DeleteServer", {"ServerOnion": serverOnion, "Password": password});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServers() {
|
||||
cwtchPlatform.invokeMethod("LaunchServers", {});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServer(String serverOnion) {
|
||||
cwtchPlatform.invokeMethod("LaunchServer", {"ServerOnion": serverOnion});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServer(String serverOnion) {
|
||||
cwtchPlatform.invokeMethod("StopServer", {"ServerOnion": serverOnion});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServers() {
|
||||
cwtchPlatform.invokeMethod("StopServers", {});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DestroyServers() {
|
||||
cwtchPlatform.invokeMethod("DestroyServers", {});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetServerAttribute(String serverOnion, String key, String val) {
|
||||
cwtchPlatform.invokeMethod("SetServerAttribute", {"ServerOnion": serverOnion, "Key": key, "Val": val});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> Shutdown() async {
|
||||
print("gomobile.dart Shutdown");
|
||||
cwtchPlatform.invokeMethod("Shutdown", {});
|
||||
|
|
|
@ -104,16 +104,6 @@ class CwtchIcons {
|
|||
static const IconData add_24px = IconData(0xe850, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData address_copy_2 = IconData(0xe852, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData address = IconData(0xe856, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData streamer_bunnymask = IconData(0xe85b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData streamer_ghost = IconData(0xe85c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData cancel_schedule_send_black_24dp = IconData(0xe85d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData more_horiz_black_24dp = IconData(0xe85e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData dns_black_add_24dp = IconData(0xe85f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData dns_black_24dp = IconData(0xe860, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData info_black_24dp = IconData(0xe861, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData accept_unknown = IconData(0xe862, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData eye_closed_1 = IconData(0xe863, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData eye_open_1 = IconData(0xe864, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData send_invite = IconData(0xe888, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData leave_group = IconData(0xe88a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData leave_chat = IconData(0xe88b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
|
|
|
@ -21,27 +21,6 @@ class ErrorHandler extends ChangeNotifier {
|
|||
bool deleteProfileError = false;
|
||||
bool deleteProfileSuccess = false;
|
||||
|
||||
static const String deletedServerErrorPrefix = "deletedserver";
|
||||
bool deletedServerError = false;
|
||||
bool deletedServerSuccess = false;
|
||||
|
||||
reset() {
|
||||
invalidImportStringError = false;
|
||||
contactAlreadyExistsError = false;
|
||||
explicitAddContactSuccess = false;
|
||||
|
||||
importBundleError = false;
|
||||
importBundleSuccess = false;
|
||||
|
||||
deleteProfileError = false;
|
||||
deleteProfileSuccess = false;
|
||||
|
||||
deletedServerError = false;
|
||||
deletedServerSuccess = false;
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Called by the event bus.
|
||||
handleUpdate(String error) {
|
||||
var parts = error.split(".");
|
||||
|
@ -58,8 +37,6 @@ class ErrorHandler extends ChangeNotifier {
|
|||
case deleteProfileErrorPrefix:
|
||||
handleDeleteProfileError(errorType);
|
||||
break;
|
||||
case deletedServerErrorPrefix:
|
||||
handleDeletedServerError(errorType);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
@ -114,19 +91,4 @@ class ErrorHandler extends ChangeNotifier {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleDeletedServerError(String errorType) {
|
||||
// reset
|
||||
deletedServerError = false;
|
||||
deletedServerSuccess = false;
|
||||
|
||||
switch (errorType) {
|
||||
case successErrorType:
|
||||
deletedServerSuccess = true;
|
||||
break;
|
||||
default:
|
||||
deletedServerError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +1,6 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2021-11-11T01:02:08+01:00",
|
||||
"newMessagesLabel": "New Messages",
|
||||
"localeRU": "Russian",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
"messageFileOffered": "Contact is offering to send you a file",
|
||||
"messageFileSent": "You sent a file",
|
||||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
"labelFilename": "Filename",
|
||||
"downloadFileButton": "Herunterladen",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten",
|
||||
"addPeerTab": "Einen anderen Nutzer hinzufügen",
|
||||
"addPeer": "Anderen Nutzer hinzufügen",
|
||||
"peerNotOnline": "Der andere Nutzer ist offline. Die App kann momentan nicht verwendet werden.",
|
||||
"peerBlockedMessage": "Anderer Nutzer ist blockiert",
|
||||
"peerOfflineMessage": "Anderer Nutzer ist offline, Nachrichten können derzeit nicht zugestellt werden",
|
||||
"blockBtn": "Anderen Nutzer blockieren",
|
||||
"savePeerHistory": "Peer-Verlauf speichern",
|
||||
"savePeerHistoryDescription": "Legt fest, ob ein mit dem anderen Nutzer verknüpfter Verlauf gelöscht werden soll oder nicht.",
|
||||
"dontSavePeerHistory": "Verlauf mit anderem Nutzer löschen",
|
||||
"unblockBtn": "Anderen Nutzer entsperren",
|
||||
"blockUnknownLabel": "Unbekannte Peers blockieren",
|
||||
"blockUnknownConnectionsEnabledDescription": "Connections from unknown contacts are blocked. You can change this in Settings",
|
||||
"networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...",
|
||||
"showMessageButton": "Show Message",
|
||||
"blockedMessageMessage": "This message is from a profile you have blocked.",
|
||||
"placeholderEnterMessage": "Type a message...",
|
||||
"plainProfileDescription": "We recommend that you protect your Cwtch profiles with a password. If you do not set a password on this profile then anyone who has access to this device may be able to access information about this profile, including contacts, messages and sensitive cryptographic keys.",
|
||||
"encryptedProfileDescription": "Encrypting a profile with a password protects it from other people who may also use this device. Encrypted profiles cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"addContactConfirm": "Add contact %1",
|
||||
"addContact": "Add contact",
|
||||
"contactGoto": "Go to conversation with %1",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -144,6 +76,7 @@
|
|||
"todoPlaceholder": "noch zu erledigen",
|
||||
"newConnectionPaneTitle": "Neue Verbindung",
|
||||
"networkStatusOnline": "Online",
|
||||
"networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...",
|
||||
"networkStatusAttemptingTor": "Versuche, eine Verbindung mit dem Tor-Netzwerk herzustellen",
|
||||
"networkStatusDisconnected": "Vom Internet getrennt, überprüfe deine Verbindung",
|
||||
"viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen",
|
||||
|
@ -163,6 +96,7 @@
|
|||
"localeFr": "Frances",
|
||||
"localeEn": "English",
|
||||
"settingLanguage": "Sprache",
|
||||
"blockUnknownLabel": "Unbekannte Peers blockieren",
|
||||
"zoomLabel": "Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)",
|
||||
"versionBuilddate": "Version: %1 Aufgebaut auf: %2",
|
||||
"cwtchSettingsTitle": "Cwtch Einstellungen",
|
||||
|
@ -186,6 +120,7 @@
|
|||
"password1Label": "Passwort",
|
||||
"currentPasswordLabel": "aktuelles Passwort",
|
||||
"yourDisplayName": "Dein Anzeigename",
|
||||
"profileOnionLabel": "Sende diese Adresse an andere Nutzer, mit denen du dich verbinden möchtest",
|
||||
"noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.",
|
||||
"radioNoPassword": "Unverschlüsselt (kein Passwort)",
|
||||
"radioUsePassword": "Passwort",
|
||||
|
@ -197,8 +132,13 @@
|
|||
"profileName": "Anzeigename",
|
||||
"editProfileTitle": "Profil bearbeiten",
|
||||
"addProfileTitle": "Neues Profil hinzufügen",
|
||||
"deleteBtn": "Löschen",
|
||||
"saveBtn": "Speichern",
|
||||
"deleteBtn": "löschen",
|
||||
"unblockBtn": "Anderen Nutzer entsperren",
|
||||
"dontSavePeerHistory": "Verlauf mit anderem Nutzer löschen",
|
||||
"savePeerHistoryDescription": "Legt fest, ob ein mit dem anderen Nutzer verknüpfter Verlauf gelöscht werden soll oder nicht.",
|
||||
"savePeerHistory": "Peer-Verlauf speichern",
|
||||
"blockBtn": "Anderen Nutzer blockieren",
|
||||
"saveBtn": "speichern",
|
||||
"displayNameLabel": "Angezeigename",
|
||||
"addressLabel": "Adresse",
|
||||
"puzzleGameBtn": "Puzzlespiel",
|
||||
|
@ -210,12 +150,15 @@
|
|||
"acceptGroupInviteLabel": "Möchtest Du die Einladung annehmen",
|
||||
"newGroupBtn": "Neue Gruppe anlegen",
|
||||
"copiedClipboardNotification": "in die Zwischenablage kopiert",
|
||||
"peerOfflineMessage": "Anderer Nutzer ist offline, Nachrichten können derzeit nicht zugestellt werden",
|
||||
"peerBlockedMessage": "Anderer Nutzer ist blockiert",
|
||||
"pendingLabel": "Bestätigung ausstehend",
|
||||
"acknowledgedLabel": "bestätigt",
|
||||
"couldNotSendMsgError": "Nachricht konnte nicht gesendet werden",
|
||||
"dmTooltip": "Klicken, um Direktnachricht zu senden",
|
||||
"membershipDescription": "Unten steht eine Liste der Benutzer, die Nachrichten an die Gruppe gesendet haben. Möglicherweise enthält diese Benutzerzliste nicht alle, die Zugang zur Gruppe haben.",
|
||||
"addListItemBtn": "Element hinzufügen",
|
||||
"peerNotOnline": "Der andere Nutzer ist offline. Die App kann momentan nicht verwendet werden.",
|
||||
"searchList": "Liste durchsuchen",
|
||||
"update": "Update",
|
||||
"inviteBtn": "Einladen",
|
||||
|
@ -241,6 +184,7 @@
|
|||
"newBulletinLabel": "Neue Meldung",
|
||||
"joinGroup": "Gruppe beitreten",
|
||||
"createGroup": "Gruppe erstellen",
|
||||
"addPeer": "Anderen Nutzer hinzufügen",
|
||||
"groupAddr": "Adresse",
|
||||
"invitation": "Einladung",
|
||||
"server": "Server",
|
||||
|
@ -249,6 +193,7 @@
|
|||
"peerAddress": "Adresse",
|
||||
"joinGroupTab": "Einer Gruppe beitreten",
|
||||
"createGroupTab": "Eine Gruppe erstellen",
|
||||
"addPeerTab": "Einen anderen Nutzer hinzufügen",
|
||||
"createGroupBtn": "Anlegen",
|
||||
"defaultGroupName": "Tolle Gruppe",
|
||||
"createGroupTitle": "Gruppe Anlegen"
|
||||
|
|
|
@ -1,74 +1,6 @@
|
|||
{
|
||||
"@@locale": "en",
|
||||
"@@last_modified": "2021-11-11T01:02:08+01:00",
|
||||
"newMessagesLabel": "New Messages",
|
||||
"localeRU": "Russian",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
"messageFileOffered": "Contact is offering to send you a file",
|
||||
"messageFileSent": "You sent a file",
|
||||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
"labelFilename": "Filename",
|
||||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Send this address to people you want to connect with",
|
||||
"addPeerTab": "Add a contact",
|
||||
"addPeer": "Add Contact",
|
||||
"peerNotOnline": "Contact is offline. Applications cannot be used right now.",
|
||||
"peerBlockedMessage": "Contact is blocked",
|
||||
"peerOfflineMessage": "Contact is offline, messages can't be delivered right now",
|
||||
"blockBtn": "Block Contact",
|
||||
"savePeerHistory": "Save History",
|
||||
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the contact.",
|
||||
"dontSavePeerHistory": "Delete History",
|
||||
"unblockBtn": "Unblock Contact",
|
||||
"blockUnknownLabel": "Block Unknown Contacts",
|
||||
"blockUnknownConnectionsEnabledDescription": "Connections from unknown contacts are blocked. You can change this in Settings",
|
||||
"networkStatusConnecting": "Connecting to network and contacts...",
|
||||
"showMessageButton": "Show Message",
|
||||
"blockedMessageMessage": "This message is from a profile you have blocked.",
|
||||
"placeholderEnterMessage": "Type a message...",
|
||||
"plainProfileDescription": "We recommend that you protect your Cwtch profiles with a password. If you do not set a password on this profile then anyone who has access to this device may be able to access information about this profile, including contacts, messages and sensitive cryptographic keys.",
|
||||
"encryptedProfileDescription": "Encrypting a profile with a password protects it from other people who may also use this device. Encrypted profiles cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"addContactConfirm": "Add contact %1",
|
||||
"addContact": "Add contact",
|
||||
"contactGoto": "Go to conversation with %1",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -144,6 +76,7 @@
|
|||
"todoPlaceholder": "Todo...",
|
||||
"newConnectionPaneTitle": "New Connection",
|
||||
"networkStatusOnline": "Online",
|
||||
"networkStatusConnecting": "Connecting to network and peers...",
|
||||
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
|
||||
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
|
||||
"viewGroupMembershipTooltip": "View Group Membership",
|
||||
|
@ -163,6 +96,7 @@
|
|||
"localeFr": "Frances",
|
||||
"localeEn": "English",
|
||||
"settingLanguage": "Language",
|
||||
"blockUnknownLabel": "Block Unknown Peers",
|
||||
"zoomLabel": "Interface zoom (mostly affects text and button sizes)",
|
||||
"versionBuilddate": "Version: %1 Built on: %2",
|
||||
"cwtchSettingsTitle": "Cwtch Settings",
|
||||
|
@ -186,6 +120,7 @@
|
|||
"password1Label": "Password",
|
||||
"currentPasswordLabel": "Current Password",
|
||||
"yourDisplayName": "Your Display Name",
|
||||
"profileOnionLabel": "Send this address to peers you want to connect with",
|
||||
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
|
||||
"radioNoPassword": "Unencrypted (No password)",
|
||||
"radioUsePassword": "Password",
|
||||
|
@ -198,6 +133,11 @@
|
|||
"editProfileTitle": "Edit Profile",
|
||||
"addProfileTitle": "Add new profile",
|
||||
"deleteBtn": "Delete",
|
||||
"unblockBtn": "Unblock Peer",
|
||||
"dontSavePeerHistory": "Delete Peer History",
|
||||
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
|
||||
"savePeerHistory": "Save Peer History",
|
||||
"blockBtn": "Block Peer",
|
||||
"saveBtn": "Save",
|
||||
"displayNameLabel": "Display Name",
|
||||
"addressLabel": "Address",
|
||||
|
@ -210,17 +150,20 @@
|
|||
"acceptGroupInviteLabel": "Do you want to accept the invitation to",
|
||||
"newGroupBtn": "Create new group",
|
||||
"copiedClipboardNotification": "Copied to clipboard",
|
||||
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
|
||||
"peerBlockedMessage": "Peer is blocked",
|
||||
"pendingLabel": "Pending",
|
||||
"acknowledgedLabel": "Acknowledged",
|
||||
"couldNotSendMsgError": "Could not send this message",
|
||||
"dmTooltip": "Click to DM",
|
||||
"membershipDescription": "Below is a list of users who have sent messages to the group. This list may not reflect all users who have access to the group.",
|
||||
"addListItemBtn": "Add Item",
|
||||
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
|
||||
"searchList": "Search List",
|
||||
"update": "Update",
|
||||
"inviteBtn": "Invite",
|
||||
"inviteToGroupLabel": "Invite to group",
|
||||
"groupNameLabel": "Group name",
|
||||
"groupNameLabel": "Group Name",
|
||||
"viewServerInfo": "Server Info",
|
||||
"serverSynced": "Synced",
|
||||
"serverConnectivityDisconnected": "Server Disconnected",
|
||||
|
@ -241,6 +184,7 @@
|
|||
"newBulletinLabel": "New Bulletin",
|
||||
"joinGroup": "Join group",
|
||||
"createGroup": "Create group",
|
||||
"addPeer": "Add Peer",
|
||||
"groupAddr": "Address",
|
||||
"invitation": "Invitation",
|
||||
"server": "Server",
|
||||
|
@ -249,6 +193,7 @@
|
|||
"peerAddress": "Address",
|
||||
"joinGroupTab": "Join a group",
|
||||
"createGroupTab": "Create a group",
|
||||
"addPeerTab": "Add a peer",
|
||||
"createGroupBtn": "Create",
|
||||
"defaultGroupName": "Awesome Group",
|
||||
"createGroupTitle": "Create Group"
|
||||
|
|
|
@ -1,74 +1,6 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2021-11-11T01:02:08+01:00",
|
||||
"newMessagesLabel": "New Messages",
|
||||
"localeRU": "Russian",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
"messageFileOffered": "Contact is offering to send you a file",
|
||||
"messageFileSent": "You sent a file",
|
||||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
"labelFilename": "Filename",
|
||||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte",
|
||||
"addPeerTab": "Agregar Contacto",
|
||||
"addPeer": "Agregar Contacto",
|
||||
"peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento",
|
||||
"peerBlockedMessage": "Contacto bloqueado",
|
||||
"peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento",
|
||||
"blockBtn": "Bloquear contacto",
|
||||
"savePeerHistory": "Guardar el historial con contacto",
|
||||
"savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.",
|
||||
"dontSavePeerHistory": "Eliminar historial de contacto",
|
||||
"unblockBtn": "Desbloquear contacto",
|
||||
"blockUnknownLabel": "Bloquear conexiones desconocidas",
|
||||
"blockUnknownConnectionsEnabledDescription": "Connections from unknown contacts are blocked. You can change this in Settings",
|
||||
"networkStatusConnecting": "Conectando a la red y a los contactos...",
|
||||
"showMessageButton": "Show Message",
|
||||
"blockedMessageMessage": "This message is from a profile you have blocked.",
|
||||
"placeholderEnterMessage": "Type a message...",
|
||||
"plainProfileDescription": "We recommend that you protect your Cwtch profiles with a password. If you do not set a password on this profile then anyone who has access to this device may be able to access information about this profile, including contacts, messages and sensitive cryptographic keys.",
|
||||
"encryptedProfileDescription": "Encrypting a profile with a password protects it from other people who may also use this device. Encrypted profiles cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"addContactConfirm": "Add contact %1",
|
||||
"addContact": "Add contact",
|
||||
"contactGoto": "Go to conversation with %1",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -144,6 +76,7 @@
|
|||
"todoPlaceholder": "Por hacer...",
|
||||
"newConnectionPaneTitle": "Nueva conexión",
|
||||
"networkStatusOnline": "En línea",
|
||||
"networkStatusConnecting": "Conectando a la red y a los contactos...",
|
||||
"networkStatusAttemptingTor": "Intentando conectarse a la red Tor",
|
||||
"networkStatusDisconnected": "Sin conexión, comprueba tu conexión",
|
||||
"viewGroupMembershipTooltip": "Ver membresía del grupo",
|
||||
|
@ -163,6 +96,7 @@
|
|||
"localeFr": "Francés",
|
||||
"localeEn": "Inglés",
|
||||
"settingLanguage": "Idioma",
|
||||
"blockUnknownLabel": "Bloquear conexiones desconocidas",
|
||||
"zoomLabel": "Zoom de la interfaz (afecta principalmente el tamaño del texto y de los botones)",
|
||||
"versionBuilddate": "Versión: %1 Basado en %2",
|
||||
"cwtchSettingsTitle": "Configuración de Cwtch",
|
||||
|
@ -186,6 +120,7 @@
|
|||
"password1Label": "Contraseña",
|
||||
"currentPasswordLabel": "Contraseña actual",
|
||||
"yourDisplayName": "Tu nombre de usuario",
|
||||
"profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte",
|
||||
"noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados",
|
||||
"radioNoPassword": "Sin cifrado (sin contraseña)",
|
||||
"radioUsePassword": "Contraseña",
|
||||
|
@ -198,6 +133,11 @@
|
|||
"editProfileTitle": "Editar perfil",
|
||||
"addProfileTitle": "Agregar nuevo perfil",
|
||||
"deleteBtn": "Eliminar",
|
||||
"unblockBtn": "Desbloquear contacto",
|
||||
"dontSavePeerHistory": "Eliminar historial de contacto",
|
||||
"savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.",
|
||||
"savePeerHistory": "Guardar el historial con contacto",
|
||||
"blockBtn": "Bloquear contacto",
|
||||
"saveBtn": "Guardar",
|
||||
"displayNameLabel": "Nombre de Usuario",
|
||||
"addressLabel": "Dirección",
|
||||
|
@ -210,12 +150,15 @@
|
|||
"acceptGroupInviteLabel": "¿Quieres aceptar la invitación a ",
|
||||
"newGroupBtn": "Crear un nuevo grupo de chat",
|
||||
"copiedClipboardNotification": "Copiado al portapapeles",
|
||||
"peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento",
|
||||
"peerBlockedMessage": "Contacto bloqueado",
|
||||
"pendingLabel": "Pendiente",
|
||||
"acknowledgedLabel": "Reconocido",
|
||||
"couldNotSendMsgError": "No se pudo enviar este mensaje",
|
||||
"dmTooltip": "Haz clic para enviar mensaje directo",
|
||||
"membershipDescription": "La lista a continuación solo muestra los miembros que han enviado mensajes al grupo, no incluye a todos los usuarios dentro del grupo",
|
||||
"addListItemBtn": "Agregar artículo",
|
||||
"peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento",
|
||||
"searchList": "Buscar en la lista",
|
||||
"update": "Actualizar",
|
||||
"inviteBtn": "Invitar",
|
||||
|
@ -241,6 +184,7 @@
|
|||
"newBulletinLabel": "Nuevo Boletín",
|
||||
"joinGroup": "Únete al grupo",
|
||||
"createGroup": "Crear perfil",
|
||||
"addPeer": "Agregar Contacto",
|
||||
"groupAddr": "Dirección",
|
||||
"invitation": "Invitación",
|
||||
"server": "Servidor",
|
||||
|
@ -249,6 +193,7 @@
|
|||
"peerAddress": "Dirección",
|
||||
"joinGroupTab": "Únete a un grupo",
|
||||
"createGroupTab": "Crear un grupo",
|
||||
"addPeerTab": "Agregar Contacto",
|
||||
"createGroupBtn": "Crear",
|
||||
"defaultGroupName": "El Grupo Asombroso",
|
||||
"createGroupTitle": "Crear un grupo"
|
||||
|
|
|
@ -1,74 +1,6 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2021-11-11T01:02:08+01:00",
|
||||
"newMessagesLabel": "New Messages",
|
||||
"localeRU": "Russian",
|
||||
"copyServerKeys": "Copier les clés",
|
||||
"verfiyResumeButton": "Vérifier\/reprendre",
|
||||
"fileCheckingStatus": "Vérification de l'état du téléchargement",
|
||||
"fileInterrupted": "Interrompu",
|
||||
"fileSavedTo": "Enregistré dans",
|
||||
"plainServerDescription": "Nous vous recommandons de protéger vos serveurs Cwtch par un mot de passe. Si vous ne définissez pas de mot de passe sur ce serveur, toute personne ayant accès à cet appareil peut être en mesure d'accéder aux informations concernant ce serveur, y compris les clés cryptographiques sensibles.",
|
||||
"encryptedServerDescription": "Le chiffrement d’un serveur avec un mot de passe le protège des autres personnes qui peuvent également utiliser cet appareil. Les serveurs cryptés ne peuvent pas être déchiffrés, affichés ou accessibles tant que le mot de passe correct n’est pas entré pour les déverrouiller.",
|
||||
"deleteServerConfirmBtn": "Supprimer vraiment le serveur",
|
||||
"deleteServerSuccess": "Le serveur a été supprimé avec succès",
|
||||
"enterCurrentPasswordForDeleteServer": "Veuillez saisir le mot de passe actuel pour supprimer ce serveur",
|
||||
"copyAddress": "Copier l'adresse",
|
||||
"settingServersDescription": "L'expérience des serveurs d'hébergement permet d'héberger et de gérer les serveurs Cwtch.",
|
||||
"settingServers": "Serveurs d'hébergement",
|
||||
"enterServerPassword": "Entrez le mot de passe pour déverrouiller le serveur",
|
||||
"unlockProfileTip": "Veuillez créer ou déverrouiller un profil pour commencer !",
|
||||
"unlockServerTip": "Veuillez créer ou déverrouiller un serveur pour commencer !",
|
||||
"addServerTooltip": "Ajouter un nouveau serveur",
|
||||
"serversManagerTitleShort": "Serveurs",
|
||||
"serversManagerTitleLong": "Serveurs que vous hébergez",
|
||||
"saveServerButton": "Enregistrer le serveur",
|
||||
"serverAutostartDescription": "Contrôle si l'application lance automatiquement le serveur au démarrage.",
|
||||
"serverAutostartLabel": "Démarrage automatique",
|
||||
"serverEnabledDescription": "Démarrer ou arrêter le serveur",
|
||||
"serverEnabled": "Serveur activé",
|
||||
"serverDescriptionDescription": "Votre description du serveur est à des fins de gestion personnelle uniquement, elle ne sera jamais partagée.",
|
||||
"serverDescriptionLabel": "Description du serveur",
|
||||
"serverAddress": "Adresse du serveur",
|
||||
"editServerTitle": "Modifier le serveur",
|
||||
"addServerTitle": "Ajouter un serveur",
|
||||
"titleManageProfilesShort": "Profils",
|
||||
"descriptionStreamerMode": "Si elle est activée, cette option donne un rendu visuel plus privé à l'application pour la diffusion en direct ou la présentation, par exemple, en masquant profil et adresses de contacts.",
|
||||
"descriptionFileSharing": "L'expérience de partage de fichiers vous permet d'envoyer et de recevoir des fichiers à partir de contacts et de groupes Cwtch. Notez que si vous partagez un fichier avec un groupe, les membres de ce groupe se connecteront avec vous directement via Cwtch pour le télécharger.",
|
||||
"settingFileSharing": "Partage de fichiers",
|
||||
"tooltipSendFile": "Envoyer le fichier",
|
||||
"messageFileOffered": "Contact vous propose de vous envoyer un fichier",
|
||||
"messageFileSent": "Vous avez envoyé un fichier",
|
||||
"messageEnableFileSharing": "Activez l'expérience de partage de fichiers pour afficher ce message.",
|
||||
"labelFilesize": "Taille",
|
||||
"labelFilename": "Nom de fichier",
|
||||
"downloadFileButton": "Télécharger",
|
||||
"openFolderButton": "Ouvrir le dossier",
|
||||
"retrievingManifestMessage": "Récupération des informations sur le fichier...",
|
||||
"streamerModeLabel": "Mode Streamer\/Présentation",
|
||||
"archiveConversation": "Archiver cette conversation",
|
||||
"profileOnionLabel": "Envoyez cette adresse aux personnes avec lesquelles vous souhaitez entrer en contact.",
|
||||
"addPeerTab": "Ajouter un contact",
|
||||
"addPeer": "Ajouter le contact",
|
||||
"peerNotOnline": "Le contact est hors ligne. Les applications ne peuvent pas être utilisées pour le moment.",
|
||||
"peerBlockedMessage": "Le contact est bloqué",
|
||||
"peerOfflineMessage": "Le contact est hors ligne, les messages ne peuvent pas être transmis pour le moment.",
|
||||
"blockBtn": "Bloquer le contact",
|
||||
"savePeerHistory": "Enregistrer l'historique",
|
||||
"savePeerHistoryDescription": "Détermine s'il faut ou non supprimer tout historique associé au contact.",
|
||||
"dontSavePeerHistory": "Supprimer l'historique",
|
||||
"unblockBtn": "Débloquer le contact",
|
||||
"blockUnknownLabel": "Bloquer les pairs inconnus",
|
||||
"blockUnknownConnectionsEnabledDescription": "Les connexions provenant de contacts inconnus sont bloquées. Vous pouvez modifier cela dans les paramètres",
|
||||
"networkStatusConnecting": "Connexion au réseau et aux contacts...",
|
||||
"showMessageButton": "Afficher le message",
|
||||
"blockedMessageMessage": "Ce message provient d'un profil que vous avez bloqué.",
|
||||
"placeholderEnterMessage": "saisissez un message",
|
||||
"plainProfileDescription": "Nous vous recommandons de protéger vos profils Cwtch par un mot de passe. Si vous ne définissez pas de mot de passe sur ce profil, toute personne ayant accès à cet appareil peut être en mesure d'accéder aux informations relatives à ce profil, y compris les contacts, les messages et les clés de chiffrement sensibles.",
|
||||
"encryptedProfileDescription": "Le chiffrement d'un profil à l'aide d'un mot de passe le protège des autres personnes susceptibles d'utiliser également cet appareil. Les profils chiffrés ne peuvent pas être déchiffrés , affichés ou accessibles tant que le mot de passe correct n'a pas été saisi pour les déverrouiller.",
|
||||
"addContactConfirm": "Ajouter le contact %1",
|
||||
"addContact": "Ajouter le contact",
|
||||
"contactGoto": "Aller à la conversation avec %1",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"settingUIColumnOptionSame": "Même réglage que pour le mode portrait",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -144,6 +76,7 @@
|
|||
"todoPlaceholder": "À faire...",
|
||||
"newConnectionPaneTitle": "Nouvelle connexion",
|
||||
"networkStatusOnline": "En ligne",
|
||||
"networkStatusConnecting": "Se connecter au réseau et aux pairs...",
|
||||
"networkStatusAttemptingTor": "Tentative de connexion au réseau Tor",
|
||||
"networkStatusDisconnected": "Déconnecté d'Internet, vérifiez votre connexion",
|
||||
"viewGroupMembershipTooltip": "Afficher les membres du groupe",
|
||||
|
@ -163,6 +96,7 @@
|
|||
"localeFr": "Français",
|
||||
"localeEn": "Anglais",
|
||||
"settingLanguage": "Langue",
|
||||
"blockUnknownLabel": "Bloquer les pairs inconnus",
|
||||
"zoomLabel": "Zoom de l'interface (affecte principalement la taille du texte et des boutons)",
|
||||
"versionBuilddate": "Version : %1 Construite le : %2",
|
||||
"cwtchSettingsTitle": "Préférences Cwtch",
|
||||
|
@ -186,6 +120,7 @@
|
|||
"password1Label": "Mot de passe",
|
||||
"currentPasswordLabel": "Mot de passe actuel",
|
||||
"yourDisplayName": "Pseudo",
|
||||
"profileOnionLabel": "Envoyez cette adresse aux personnes avec lesquelles vous souhaitez entrer en contact.",
|
||||
"noPasswordWarning": "Ne pas utiliser de mot de passe sur ce compte signifie que toutes les données stockées localement ne seront pas chiffrées.",
|
||||
"radioNoPassword": "Non chiffré (pas de mot de passe)",
|
||||
"radioUsePassword": "Mot de passe",
|
||||
|
@ -197,7 +132,12 @@
|
|||
"profileName": "Pseudo",
|
||||
"editProfileTitle": "Modifier le profil",
|
||||
"addProfileTitle": "Ajouter un nouveau profil",
|
||||
"deleteBtn": "Effacer",
|
||||
"deleteBtn": "Supprimer",
|
||||
"unblockBtn": "Débloquer le pair",
|
||||
"dontSavePeerHistory": "Supprimer l'historique des pairs",
|
||||
"savePeerHistoryDescription": "Détermine s'il faut ou non supprimer tout historique associé au pair.",
|
||||
"savePeerHistory": "Sauvegarder l'historique des pairs",
|
||||
"blockBtn": "Bloquer le pair",
|
||||
"saveBtn": "Sauvegarder",
|
||||
"displayNameLabel": "Pseudo",
|
||||
"addressLabel": "Adresse",
|
||||
|
@ -210,12 +150,15 @@
|
|||
"acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe",
|
||||
"newGroupBtn": "Créer un nouveau groupe",
|
||||
"copiedClipboardNotification": "Copié dans le presse-papier",
|
||||
"peerOfflineMessage": "Le pair est hors ligne, les messages ne peuvent pas être remis pour le moment",
|
||||
"peerBlockedMessage": "Le pair est bloqué",
|
||||
"pendingLabel": "En attente",
|
||||
"acknowledgedLabel": "Accusé de réception",
|
||||
"couldNotSendMsgError": "Impossible d'envoyer ce message",
|
||||
"dmTooltip": "Envoyer un message privé",
|
||||
"membershipDescription": "Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être représentatives de l'ensemble des membres du groupe.",
|
||||
"addListItemBtn": "Ajouter un élément",
|
||||
"peerNotOnline": "Le pair est hors ligne, les messages ne peuvent pas être remis pour le moment",
|
||||
"searchList": "Liste de recherche",
|
||||
"update": "Mise à jour",
|
||||
"inviteBtn": "Invitation",
|
||||
|
@ -241,6 +184,7 @@
|
|||
"newBulletinLabel": "Nouveau bulletin",
|
||||
"joinGroup": "Rejoindre le groupe",
|
||||
"createGroup": "Créer un groupe",
|
||||
"addPeer": "Ajouter un pair",
|
||||
"groupAddr": "Adresse",
|
||||
"invitation": "Invitation",
|
||||
"server": "Serveur",
|
||||
|
@ -249,6 +193,7 @@
|
|||
"peerAddress": "Adresse",
|
||||
"joinGroupTab": "Rejoindre un groupe",
|
||||
"createGroupTab": "Créer un groupe",
|
||||
"addPeerTab": "Ajouter un pair",
|
||||
"createGroupBtn": "Créer",
|
||||
"defaultGroupName": "Un groupe génial",
|
||||
"createGroupTitle": "Créer un groupe"
|
||||
|
|
|
@ -1,74 +1,6 @@
|
|||
{
|
||||
"@@locale": "it",
|
||||
"@@last_modified": "2021-11-11T01:02:08+01:00",
|
||||
"newMessagesLabel": "New Messages",
|
||||
"localeRU": "Russian",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "L'esperimento di condivisione dei file ti consente di inviare e ricevere file dai contatti e dai gruppi di Cwtch. Tieni presente che la condivisione di un file con un gruppo farà sì che i membri di quel gruppo si colleghino con te direttamente su Cwtch per scaricarlo.",
|
||||
"settingFileSharing": "Condivisione file",
|
||||
"tooltipSendFile": "Invia file",
|
||||
"messageFileOffered": "Il contatto offre l'invio di un file",
|
||||
"messageFileSent": "You sent a file",
|
||||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
"labelFilename": "Filename",
|
||||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi",
|
||||
"addPeerTab": "Aggiungi un peer",
|
||||
"addPeer": "Aggiungi peer",
|
||||
"peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.",
|
||||
"peerBlockedMessage": "Il peer è bloccato",
|
||||
"peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento",
|
||||
"blockBtn": "Blocca il peer",
|
||||
"savePeerHistory": "Salva cronologia peer",
|
||||
"savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.",
|
||||
"dontSavePeerHistory": "Elimina cronologia dei peer",
|
||||
"unblockBtn": "Sblocca il peer",
|
||||
"blockUnknownLabel": "Blocca peer sconosciuti",
|
||||
"blockUnknownConnectionsEnabledDescription": "Connections from unknown contacts are blocked. You can change this in Settings",
|
||||
"networkStatusConnecting": "Connessione alla rete e ai peer ...",
|
||||
"showMessageButton": "Show Message",
|
||||
"blockedMessageMessage": "This message is from a profile you have blocked.",
|
||||
"placeholderEnterMessage": "Type a message...",
|
||||
"plainProfileDescription": "We recommend that you protect your Cwtch profiles with a password. If you do not set a password on this profile then anyone who has access to this device may be able to access information about this profile, including contacts, messages and sensitive cryptographic keys.",
|
||||
"encryptedProfileDescription": "Encrypting a profile with a password protects it from other people who may also use this device. Encrypted profiles cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"addContactConfirm": "Add contact %1",
|
||||
"addContact": "Add contact",
|
||||
"contactGoto": "Go to conversation with %1",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -144,6 +76,7 @@
|
|||
"todoPlaceholder": "Da fare...",
|
||||
"newConnectionPaneTitle": "Nuova connessione",
|
||||
"networkStatusOnline": "Online",
|
||||
"networkStatusConnecting": "Connessione alla rete e ai peer ...",
|
||||
"networkStatusAttemptingTor": "Tentativo di connessione alla rete Tor",
|
||||
"networkStatusDisconnected": "Disconnesso da Internet, controlla la tua connessione",
|
||||
"viewGroupMembershipTooltip": "Visualizza i membri del gruppo",
|
||||
|
@ -163,6 +96,7 @@
|
|||
"localeFr": "Francese",
|
||||
"localeEn": "Inglese",
|
||||
"settingLanguage": "Lingua",
|
||||
"blockUnknownLabel": "Blocca peer sconosciuti",
|
||||
"zoomLabel": "Zoom dell'interfaccia (influisce principalmente sulle dimensioni del testo e dei pulsanti)",
|
||||
"versionBuilddate": "Versione: %1 Costruito il: %2",
|
||||
"cwtchSettingsTitle": "Impostazioni di Cwtch",
|
||||
|
@ -186,10 +120,11 @@
|
|||
"password1Label": "Password",
|
||||
"currentPasswordLabel": "Password corrente",
|
||||
"yourDisplayName": "Il tuo nome visualizzato",
|
||||
"profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi",
|
||||
"noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati",
|
||||
"radioNoPassword": "Non criptato (senza password)",
|
||||
"radioUsePassword": "Password",
|
||||
"copiedToClipboardNotification": "Copiato negli Appunti",
|
||||
"copiedToClipboardNotification": "Copiato negli appunti",
|
||||
"copyBtn": "Copia",
|
||||
"editProfile": "Modifica profilo",
|
||||
"newProfile": "Nuovo profilo",
|
||||
|
@ -198,6 +133,11 @@
|
|||
"editProfileTitle": "Modifica profilo",
|
||||
"addProfileTitle": "Aggiungi nuovo profilo",
|
||||
"deleteBtn": "Elimina",
|
||||
"unblockBtn": "Sblocca il peer",
|
||||
"dontSavePeerHistory": "Elimina cronologia dei peer",
|
||||
"savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.",
|
||||
"savePeerHistory": "Salva cronologia peer",
|
||||
"blockBtn": "Blocca il peer",
|
||||
"saveBtn": "Salva",
|
||||
"displayNameLabel": "Nome visualizzato",
|
||||
"addressLabel": "Indirizzo",
|
||||
|
@ -210,12 +150,15 @@
|
|||
"acceptGroupInviteLabel": "Vuoi accettare l'invito a",
|
||||
"newGroupBtn": "Crea un nuovo gruppo",
|
||||
"copiedClipboardNotification": "Copiato negli Appunti",
|
||||
"peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento",
|
||||
"peerBlockedMessage": "Il peer è bloccato",
|
||||
"pendingLabel": "In corso",
|
||||
"acknowledgedLabel": "Riconosciuto",
|
||||
"couldNotSendMsgError": "Impossibile inviare questo messaggio",
|
||||
"dmTooltip": "Clicca per inviare un Messagio Diretto",
|
||||
"membershipDescription": "Di seguito è riportato un elenco di utenti che hanno inviato messaggi al gruppo. Questo elenco potrebbe non corrispondere a tutti gli utenti che hanno accesso al gruppo.",
|
||||
"addListItemBtn": "Aggiungi elemento",
|
||||
"peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.",
|
||||
"searchList": "Cerca nella lista",
|
||||
"update": "Aggiornamento",
|
||||
"inviteBtn": "Invitare",
|
||||
|
@ -241,6 +184,7 @@
|
|||
"newBulletinLabel": "Nuovo bollettino",
|
||||
"joinGroup": "Unisciti al gruppo",
|
||||
"createGroup": "Crea un gruppo",
|
||||
"addPeer": "Aggiungi peer",
|
||||
"groupAddr": "Indirizzo",
|
||||
"invitation": "Invito",
|
||||
"server": "Server",
|
||||
|
@ -249,6 +193,7 @@
|
|||
"peerAddress": "Indirizzo",
|
||||
"joinGroupTab": "Unisciti a un gruppo",
|
||||
"createGroupTab": "Crea un gruppo",
|
||||
"addPeerTab": "Aggiungi un peer",
|
||||
"createGroupBtn": "Crea",
|
||||
"defaultGroupName": "Gruppo fantastico",
|
||||
"createGroupTitle": "Crea un gruppo"
|
||||
|
|
|
@ -1,74 +1,6 @@
|
|||
{
|
||||
"@@locale": "pl",
|
||||
"@@last_modified": "2021-11-11T01:02:08+01:00",
|
||||
"newMessagesLabel": "New Messages",
|
||||
"localeRU": "Russian",
|
||||
"copyServerKeys": "Kopiuj klucze",
|
||||
"verfiyResumeButton": "Zweryfikuj\/wznów",
|
||||
"fileCheckingStatus": "Sprawdzanie stanu pobierania",
|
||||
"fileInterrupted": "Przerwane",
|
||||
"fileSavedTo": "Zapisano do",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Naprawdę usuń serwer",
|
||||
"deleteServerSuccess": "Pomyślnie usunięto serwer",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Skopiuj adres",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profile",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "Eksperyment udostępniania plików pozwala na wysyłanie i odbieranie plików od kontaktów i grup Cwtch. Zauważ, że udostępnienie pliku grupie spowoduje, że członkowie tej grupy połączą się z Tobą bezpośrednio przez Cwtch, aby go pobrać.",
|
||||
"settingFileSharing": "Udostępnianie plików",
|
||||
"tooltipSendFile": "Wyślij plik",
|
||||
"messageFileOffered": "Kontakt proponuje wysłanie Ci pliku",
|
||||
"messageFileSent": "Plik został wysłany",
|
||||
"messageEnableFileSharing": "Włącz eksperyment udostępniania plików, aby wyświetlić tę wiadomość.",
|
||||
"labelFilesize": "Rozmiar",
|
||||
"labelFilename": "Nazwa pliku",
|
||||
"downloadFileButton": "Pobierz",
|
||||
"openFolderButton": "Otwórz folder",
|
||||
"retrievingManifestMessage": "Pobieranie informacji o pliku...",
|
||||
"streamerModeLabel": "Tryb streamera\/prezentacji",
|
||||
"archiveConversation": "Zarchiwizuj tę rozmowę",
|
||||
"profileOnionLabel": "Send this address to contacts you want to connect with",
|
||||
"addPeerTab": "Add a contact",
|
||||
"addPeer": "Add Contact",
|
||||
"peerNotOnline": "Contact is offline. Applications cannot be used right now.",
|
||||
"peerBlockedMessage": "Contact is blocked",
|
||||
"peerOfflineMessage": "Contact is offline, messages can't be delivered right now",
|
||||
"blockBtn": "Block Contact",
|
||||
"savePeerHistory": "Save History",
|
||||
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the contact.",
|
||||
"dontSavePeerHistory": "Delete History",
|
||||
"unblockBtn": "Unblock Contact",
|
||||
"blockUnknownLabel": "Block Unknown Contacts",
|
||||
"blockUnknownConnectionsEnabledDescription": "Połączenia od nieznanych kontaktów są blokowane. Można to zmienić w Ustawieniach",
|
||||
"networkStatusConnecting": "Connecting to network and contacts...",
|
||||
"showMessageButton": "Show Message",
|
||||
"blockedMessageMessage": "This message is from a profile you have blocked.",
|
||||
"placeholderEnterMessage": "Type a message...",
|
||||
"plainProfileDescription": "We recommend that you protect your Cwtch profiles with a password. If you do not set a password on this profile then anyone who has access to this device may be able to access information about this profile, including contacts, messages and sensitive cryptographic keys.",
|
||||
"encryptedProfileDescription": "Encrypting a profile with a password protects it from other people who may also use this device. Encrypted profiles cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"addContactConfirm": "Add contact %1",
|
||||
"addContact": "Add contact",
|
||||
"contactGoto": "Go to conversation with %1",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -144,6 +76,7 @@
|
|||
"todoPlaceholder": "Todo...",
|
||||
"newConnectionPaneTitle": "New Connection",
|
||||
"networkStatusOnline": "Online",
|
||||
"networkStatusConnecting": "Connecting to network and peers...",
|
||||
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
|
||||
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
|
||||
"viewGroupMembershipTooltip": "View Group Membership",
|
||||
|
@ -163,6 +96,7 @@
|
|||
"localeFr": "Frances",
|
||||
"localeEn": "English",
|
||||
"settingLanguage": "Language",
|
||||
"blockUnknownLabel": "Block Unknown Peers",
|
||||
"zoomLabel": "Interface zoom (mostly affects text and button sizes)",
|
||||
"versionBuilddate": "Version: %1 Built on: %2",
|
||||
"cwtchSettingsTitle": "Cwtch Settings",
|
||||
|
@ -186,6 +120,7 @@
|
|||
"password1Label": "Password",
|
||||
"currentPasswordLabel": "Current Password",
|
||||
"yourDisplayName": "Your Display Name",
|
||||
"profileOnionLabel": "Send this address to peers you want to connect with",
|
||||
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
|
||||
"radioNoPassword": "Unencrypted (No password)",
|
||||
"radioUsePassword": "Password",
|
||||
|
@ -198,6 +133,11 @@
|
|||
"editProfileTitle": "Edit Profile",
|
||||
"addProfileTitle": "Add new profile",
|
||||
"deleteBtn": "Delete",
|
||||
"unblockBtn": "Unblock Peer",
|
||||
"dontSavePeerHistory": "Delete Peer History",
|
||||
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
|
||||
"savePeerHistory": "Save Peer History",
|
||||
"blockBtn": "Block Peer",
|
||||
"saveBtn": "Save",
|
||||
"displayNameLabel": "Display Name",
|
||||
"addressLabel": "Address",
|
||||
|
@ -210,17 +150,20 @@
|
|||
"acceptGroupInviteLabel": "Do you want to accept the invitation to",
|
||||
"newGroupBtn": "Create new group",
|
||||
"copiedClipboardNotification": "Copied to clipboard",
|
||||
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
|
||||
"peerBlockedMessage": "Peer is blocked",
|
||||
"pendingLabel": "Pending",
|
||||
"acknowledgedLabel": "Acknowledged",
|
||||
"couldNotSendMsgError": "Could not send this message",
|
||||
"dmTooltip": "Click to DM",
|
||||
"membershipDescription": "Below is a list of users who have sent messages to the group. This list may not reflect all users who have access to the group.",
|
||||
"addListItemBtn": "Add Item",
|
||||
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
|
||||
"searchList": "Search List",
|
||||
"update": "Update",
|
||||
"inviteBtn": "Invite",
|
||||
"inviteToGroupLabel": "Invite to group",
|
||||
"groupNameLabel": "Group name",
|
||||
"groupNameLabel": "Group Name",
|
||||
"viewServerInfo": "Server Info",
|
||||
"serverSynced": "Synced",
|
||||
"serverConnectivityDisconnected": "Server Disconnected",
|
||||
|
@ -241,6 +184,7 @@
|
|||
"newBulletinLabel": "New Bulletin",
|
||||
"joinGroup": "Join group",
|
||||
"createGroup": "Create group",
|
||||
"addPeer": "Add Peer",
|
||||
"groupAddr": "Address",
|
||||
"invitation": "Invitation",
|
||||
"server": "Server",
|
||||
|
@ -249,6 +193,7 @@
|
|||
"peerAddress": "Address",
|
||||
"joinGroupTab": "Join a group",
|
||||
"createGroupTab": "Create a group",
|
||||
"addPeerTab": "Add a peer",
|
||||
"createGroupBtn": "Create",
|
||||
"defaultGroupName": "Awesome Group",
|
||||
"createGroupTitle": "Create Group"
|
||||
|
|
|
@ -1,74 +1,6 @@
|
|||
{
|
||||
"@@locale": "pt",
|
||||
"@@last_modified": "2021-11-11T01:02:08+01:00",
|
||||
"newMessagesLabel": "New Messages",
|
||||
"localeRU": "Russian",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
"messageFileOffered": "Contact is offering to send you a file",
|
||||
"messageFileSent": "You sent a file",
|
||||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
"labelFilename": "Filename",
|
||||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Send this address to contacts you want to connect with",
|
||||
"addPeerTab": "Add a contact",
|
||||
"addPeer": "Add Contact",
|
||||
"peerNotOnline": "Contact is offline. Applications cannot be used right now.",
|
||||
"peerBlockedMessage": "Contact is blocked",
|
||||
"peerOfflineMessage": "Contact is offline, messages can't be delivered right now",
|
||||
"blockBtn": "Block Contact",
|
||||
"savePeerHistory": "Save History",
|
||||
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the contact.",
|
||||
"dontSavePeerHistory": "Delete History",
|
||||
"unblockBtn": "Unblock Contact",
|
||||
"blockUnknownLabel": "Block Unknown Contacts",
|
||||
"blockUnknownConnectionsEnabledDescription": "Connections from unknown contacts are blocked. You can change this in Settings",
|
||||
"networkStatusConnecting": "Connecting to network and contacts...",
|
||||
"showMessageButton": "Show Message",
|
||||
"blockedMessageMessage": "This message is from a profile you have blocked.",
|
||||
"placeholderEnterMessage": "Type a message...",
|
||||
"plainProfileDescription": "We recommend that you protect your Cwtch profiles with a password. If you do not set a password on this profile then anyone who has access to this device may be able to access information about this profile, including contacts, messages and sensitive cryptographic keys.",
|
||||
"encryptedProfileDescription": "Encrypting a profile with a password protects it from other people who may also use this device. Encrypted profiles cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"addContactConfirm": "Add contact %1",
|
||||
"addContact": "Add contact",
|
||||
"contactGoto": "Go to conversation with %1",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -144,6 +76,7 @@
|
|||
"todoPlaceholder": "Afazer…",
|
||||
"newConnectionPaneTitle": "New Connection",
|
||||
"networkStatusOnline": "Online",
|
||||
"networkStatusConnecting": "Connecting to network and peers...",
|
||||
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
|
||||
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
|
||||
"viewGroupMembershipTooltip": "View Group Membership",
|
||||
|
@ -163,6 +96,7 @@
|
|||
"localeFr": "Frances",
|
||||
"localeEn": "English",
|
||||
"settingLanguage": "Language",
|
||||
"blockUnknownLabel": "Block Unknown Peers",
|
||||
"zoomLabel": "Zoom da interface (afeta principalmente tamanho de texto e botões)",
|
||||
"versionBuilddate": "Version: %1 Built on: %2",
|
||||
"cwtchSettingsTitle": "Configurações do Cwtch",
|
||||
|
@ -186,6 +120,7 @@
|
|||
"password1Label": "Password",
|
||||
"currentPasswordLabel": "Current Password",
|
||||
"yourDisplayName": "Your Display Name",
|
||||
"profileOnionLabel": "Send this address to peers you want to connect with",
|
||||
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
|
||||
"radioNoPassword": "Unencrypted (No password)",
|
||||
"radioUsePassword": "Password",
|
||||
|
@ -198,6 +133,11 @@
|
|||
"editProfileTitle": "Edit Profile",
|
||||
"addProfileTitle": "Add new profile",
|
||||
"deleteBtn": "Deletar",
|
||||
"unblockBtn": "Unblock Peer",
|
||||
"dontSavePeerHistory": "Delete Peer History",
|
||||
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
|
||||
"savePeerHistory": "Save Peer History",
|
||||
"blockBtn": "Block Peer",
|
||||
"saveBtn": "Salvar",
|
||||
"displayNameLabel": "Nome de Exibição",
|
||||
"addressLabel": "Endereço",
|
||||
|
@ -210,17 +150,20 @@
|
|||
"acceptGroupInviteLabel": "Você quer aceitar o convite para",
|
||||
"newGroupBtn": "Criar novo grupo",
|
||||
"copiedClipboardNotification": "Copiado",
|
||||
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
|
||||
"peerBlockedMessage": "Peer is blocked",
|
||||
"pendingLabel": "Pendente",
|
||||
"acknowledgedLabel": "Confirmada",
|
||||
"couldNotSendMsgError": "Não deu para enviar esta mensagem",
|
||||
"dmTooltip": "Clique para DM",
|
||||
"membershipDescription": "A lista abaixo é de usuários que enviaram mensagens ao grupo. Essa lista pode não refletir todos os usuários que têm acesso ao grupo.",
|
||||
"addListItemBtn": "Add Item",
|
||||
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
|
||||
"searchList": "Search List",
|
||||
"update": "Update",
|
||||
"inviteBtn": "Convidar",
|
||||
"inviteToGroupLabel": "Convidar ao grupo",
|
||||
"groupNameLabel": "Nome do grupo",
|
||||
"groupNameLabel": "Nome do Grupo",
|
||||
"viewServerInfo": "Server Info",
|
||||
"serverSynced": "Synced",
|
||||
"serverConnectivityDisconnected": "Server Disconnected",
|
||||
|
@ -241,6 +184,7 @@
|
|||
"newBulletinLabel": "Novo Boletim",
|
||||
"joinGroup": "Join group",
|
||||
"createGroup": "Create group",
|
||||
"addPeer": "Add Peer",
|
||||
"groupAddr": "Address",
|
||||
"invitation": "Invitation",
|
||||
"server": "Server",
|
||||
|
@ -249,6 +193,7 @@
|
|||
"peerAddress": "Address",
|
||||
"joinGroupTab": "Join a group",
|
||||
"createGroupTab": "Create a group",
|
||||
"addPeerTab": "Add a peer",
|
||||
"createGroupBtn": "Criar",
|
||||
"defaultGroupName": "Grupo incrível",
|
||||
"createGroupTitle": "Criar Grupo"
|
||||
|
|
|
@ -1,254 +0,0 @@
|
|||
{
|
||||
"@@locale": "ru",
|
||||
"@@last_modified": "2021-11-10T18:47:30+01:00",
|
||||
"localeRU": "Russian",
|
||||
"copyServerKeys": "Копировать ключи",
|
||||
"verfiyResumeButton": "Проверить\/продолжить",
|
||||
"fileCheckingStatus": "Проверка статуса загрузки",
|
||||
"fileInterrupted": "Прервано",
|
||||
"fileSavedTo": "Сохранить в",
|
||||
"plainServerDescription": "Мы настоятельно рекомендуем защитить свой сервер Cwtch паролем. Если Вы этого не сделаете, то любой у кого окажется доступ к серверу, сможет получить доступ к информации на этом сервере включая конфиденциальные криптографические ключи.",
|
||||
"encryptedServerDescription": "Шифрование сервера паролем защитит его от других людей у которых может оказаться доступ к этому устройству, включая Onion адрес сервера. Зашифрованный сервер нельзя расшифровать, пока не будет введен правильный пароль разблокировки.",
|
||||
"deleteServerConfirmBtn": "Точно удалить сервер?",
|
||||
"deleteServerSuccess": "Сервер успешно удален",
|
||||
"enterCurrentPasswordForDeleteServer": "Пожалуйста, введите пароль сервера, чтобы удалить его",
|
||||
"copyAddress": "Копировать адрес",
|
||||
"settingServersDescription": "Экспериментальная функция которая позволяет добавлять сервер Cwtch. В меню появится дополнительная опция Серверы",
|
||||
"settingServers": "Использовать серверы",
|
||||
"enterServerPassword": "Введите пароль для разблокировки сервера",
|
||||
"unlockProfileTip": "Создайте или разблокируйте профиль, чтобы начать",
|
||||
"unlockServerTip": "Создайте или разблокируйте сервер, чтобы начать",
|
||||
"addServerTooltip": "Добавить сервер",
|
||||
"serversManagerTitleShort": "Серверы",
|
||||
"serversManagerTitleLong": "Личные серверы",
|
||||
"saveServerButton": "Сохранить сервер",
|
||||
"serverAutostartDescription": "Автозапуск сервера при старте программы",
|
||||
"serverAutostartLabel": "Автозапуск",
|
||||
"serverEnabledDescription": "Запустить или остановить сервер",
|
||||
"serverEnabled": "Сервер запущен",
|
||||
"serverDescriptionDescription": "Описание видите только Вы. Сделано для удобства",
|
||||
"serverDescriptionLabel": "Описание сервера",
|
||||
"serverAddress": "Адрес сервера",
|
||||
"editServerTitle": "Изменить сервер",
|
||||
"addServerTitle": "Добавить сервер",
|
||||
"titleManageProfilesShort": "Профили",
|
||||
"descriptionStreamerMode": "При включении этого параметра, внешний вид некоторых элементов становится более приватным, скрывая длинные Onion адреса и адреса контактов, оставляя только заданные имена",
|
||||
"descriptionFileSharing": "Данная функция позволяет обмениваться файлами напрямую с контактами и группами в Cwtch. Отправляемый файл будет напрямую скачиваться с вашего устройства через Cwtch.",
|
||||
"settingFileSharing": "Передача файлов",
|
||||
"tooltipSendFile": "Отправить файл",
|
||||
"messageFileOffered": "Контакт предлагает загрузить вам файл",
|
||||
"messageFileSent": "Вы отправили файл",
|
||||
"messageEnableFileSharing": "Включите экспериментальную функцию Обмен файлами чтобы просмотреть это сообщение.",
|
||||
"labelFilesize": "Размер",
|
||||
"labelFilename": "Имя-файла",
|
||||
"downloadFileButton": "Загрузить",
|
||||
"openFolderButton": "Открыть папку",
|
||||
"retrievingManifestMessage": "Получение информации о файле...",
|
||||
"streamerModeLabel": "Режим презентации",
|
||||
"archiveConversation": "Отправить чат в архив",
|
||||
"profileOnionLabel": "Send this address to contacts you want to connect with",
|
||||
"addPeerTab": "Добавить контакт",
|
||||
"addPeer": "Добавить контакт",
|
||||
"peerNotOnline": "Контакт не в сети. Вы не можете связаться с ним пока он не появиться в сети.",
|
||||
"peerBlockedMessage": "Контакт заблокирован",
|
||||
"peerOfflineMessage": "Контакт не в сети, сообщения не могут быть отправлены",
|
||||
"blockBtn": "Заблокировать контакт",
|
||||
"savePeerHistory": "Хранить исторую",
|
||||
"savePeerHistoryDescription": "Определяет политуку хранения или удаления переписки с данным контактом.",
|
||||
"dontSavePeerHistory": "Удалить историю",
|
||||
"unblockBtn": "Разблокировать контакт",
|
||||
"blockUnknownLabel": "Блокировать неизвестные контакты",
|
||||
"blockUnknownConnectionsEnabledDescription": "Соединения от неизвестных контактов блокируются. Данный параметр можно изменить в настройках",
|
||||
"networkStatusConnecting": "Подключение к сети и контактам...",
|
||||
"showMessageButton": "Показать сообщения",
|
||||
"blockedMessageMessage": "Это сообщение из заблокированного вами профиля.",
|
||||
"placeholderEnterMessage": "Написать сообщение...",
|
||||
"plainProfileDescription": "Мы рекомендуем защитить свой ПРОФИЛЬ Cwtch паролем. Если Вы этого не сделаете, то любой у кого окажется доступ к устройству, сможет получить доступ к информации об этом профиле, включая контакты, сообщения и конфиденциальные криптографические ключи.",
|
||||
"encryptedProfileDescription": "Шифрование ПРОФИЛЯ паролем защитит его от других людей у которых может оказаться доступ к этому устройству. Зашифрованный ПРОФИЛЬ нельзя расшифровать, пока не будет введен правильный пароль разблокировки.",
|
||||
"addContactConfirm": "Добавить контакт %1",
|
||||
"addContact": "Добавить контакт",
|
||||
"contactGoto": "Перейти к сообщению от %1",
|
||||
"settingUIColumnOptionSame": "Как в настройках портретного режима",
|
||||
"settingUIColumnDouble14Ratio": "Двойной (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Двойной (1:2)",
|
||||
"settingUIColumnSingle": "Одиночный",
|
||||
"settingUIColumnLandscape": "UI столбцы в Ландшафтном Режиме",
|
||||
"settingUIColumnPortrait": "UI столбцы в Портретном режиме",
|
||||
"localePl": "Польский",
|
||||
"tooltipRemoveThisQuotedMessage": "Удалить цитируемое сообщение.",
|
||||
"tooltipReplyToThisMessage": "Ответить на это сообщение",
|
||||
"tooltipRejectContactRequest": "Отклонить запрос в контакты.",
|
||||
"tooltipAcceptContactRequest": "Принять запрос в контакты.",
|
||||
"notificationNewMessageFromGroup": "Новое сообщение в группе!",
|
||||
"notificationNewMessageFromPeer": "Новое сообщение от контакта!",
|
||||
"tooltipHidePassword": "Скрыть пароль",
|
||||
"tooltipShowPassword": "Показать пароль",
|
||||
"serverNotSynced": "Синхронизация новых сообщений (это может занять некоторое время)...",
|
||||
"groupInviteSettingsWarning": "Вас пригласили присоединиться к группе! Пожалуйста, включите экспериментальную функцию групповые чаты в Настройках, чтобы просмотреть это приглашение.",
|
||||
"shutdownCwtchAction": "Выключить Cwtch",
|
||||
"shutdownCwtchDialog": "Вы уверены, что хотите выключить Cwtch? Это приведет к закрытию всех подключений и выходу из приложения.",
|
||||
"shutdownCwtchDialogTitle": "Выключить Cwtch?",
|
||||
"shutdownCwtchTooltip": "Выключить Cwtch",
|
||||
"malformedMessage": "Некорректное сообщение",
|
||||
"profileDeleteSuccess": "Профиль успешно удален",
|
||||
"debugLog": "Влючить отладку через консоль",
|
||||
"torNetworkStatus": "Статус сети Tor",
|
||||
"addContactFirst": "Добавьте или выберите контакт, чтобы начать чат.",
|
||||
"createProfileToBegin": "Пожалуйста, создайте или разблокируйте профиль, чтобы начать",
|
||||
"nickChangeSuccess": "Имя профиля успешно изменено",
|
||||
"addServerFirst": "Перед созданием группы, необходимо создать сервер",
|
||||
"deleteProfileSuccess": "Профиль успешно удален",
|
||||
"sendInvite": "Отправить контакт или приглашение в группу",
|
||||
"sendMessage": "Отправить сообщение",
|
||||
"cancel": "Отмена",
|
||||
"resetTor": "Сбросс",
|
||||
"torStatus": "Статус Tor",
|
||||
"torVersion": "Версия Tor",
|
||||
"sendAnInvitation": "Вы отправили приглашение для: ",
|
||||
"contactSuggestion": "Вам предложили этот контакт: ",
|
||||
"rejected": "Отклонить!",
|
||||
"accepted": "Принять!",
|
||||
"chatHistoryDefault": "Этот чат будет удален после закрытия Cwtch! Историю сообщений можно включить для каждого чата отдельно через меню настроек в правом верхнем углу..",
|
||||
"newPassword": "Новый пароль",
|
||||
"yesLeave": "Да, оставить этот чат",
|
||||
"reallyLeaveThisGroupPrompt": "Вы уверены, что хотите закончить этот разговор? Все сообщения будут удалены.",
|
||||
"leaveGroup": "Да, оставить этот чат",
|
||||
"inviteToGroup": "Вас пригласили присоединиться к группе:",
|
||||
"pasteAddressToAddContact": "Вставьте адрес cwtch, приглашение или пакет ключей здесь, чтобы добавить их в контакты",
|
||||
"tooltipAddContact": "Добавление нового контакта или разговора",
|
||||
"titleManageContacts": "Разговоры",
|
||||
"titleManageServers": "Управление серверами",
|
||||
"dateNever": "Никогда",
|
||||
"dateLastYear": "Прошлый год",
|
||||
"dateYesterday": "Вчера",
|
||||
"dateLastMonth": "Прошлый месяц",
|
||||
"dateRightNow": "Прямо сейчас",
|
||||
"successfullAddedContact": "Успешно добавлен",
|
||||
"descriptionBlockUnknownConnections": "Если включить этот параметр, все соединения от людей не состоящих в ваших контактах будут отклонены.",
|
||||
"descriptionExperimentsGroups": "Данная экспериментальная функция позволяет Cwtch подключаться к недоверенной серверной инфраструктуре, чтобы облегчить Вам общение с более чем одним контактом.",
|
||||
"descriptionExperiments": "Экспериментальные функции Cwtch это необязательные дополнительные функции, которые добавляют некоторые возможности, но не имеют такой же устойчивости к метаданным как если бы вы общались через традиционный част 1 на 1..",
|
||||
"titleManageProfiles": "Управление профилями Cwtch",
|
||||
"tooltipUnlockProfiles": "Разблокировать зашифрованные профили, введя их пароль.",
|
||||
"tooltipOpenSettings": "Откройте панель настроек",
|
||||
"invalidImportString": "Недействительная строка импорта",
|
||||
"contactAlreadyExists": "Контакт уже существует",
|
||||
"conversationSettings": "Настройки чата",
|
||||
"enterCurrentPasswordForDelete": "Пожалуйста, введите текущий пароль, чтобы удалить этот профиль.",
|
||||
"enableGroups": "Включить Групповые чаты",
|
||||
"experimentsEnabled": "Включить Экспериментальные функции",
|
||||
"localeIt": "Итальянский",
|
||||
"localeEs": "Испанский",
|
||||
"addListItem": "Добавить новый элемент",
|
||||
"addNewItem": "Добавить новый элемент в список",
|
||||
"todoPlaceholder": "Выполняю...",
|
||||
"newConnectionPaneTitle": "Новое соединение",
|
||||
"networkStatusOnline": "Online",
|
||||
"networkStatusAttemptingTor": "Попытка подключиться к сети Tor",
|
||||
"networkStatusDisconnected": "Нет сети. Проверьте подключение к интернету",
|
||||
"viewGroupMembershipTooltip": "Просмотр членства в группе",
|
||||
"loadingTor": "Загрузка Tor...",
|
||||
"smallTextLabel": "Маленький",
|
||||
"defaultScalingText": "Размер текста по умолчанию (коэффициент масштабирования:",
|
||||
"builddate": "Построен на: %2",
|
||||
"version": "Версия %1",
|
||||
"versionTor": "Версия %1 c tor %2",
|
||||
"themeDark": "Темная",
|
||||
"themeLight": "Светлая",
|
||||
"settingTheme": "Тема",
|
||||
"largeTextLabel": "Большой",
|
||||
"settingInterfaceZoom": "Уровень масштабирования",
|
||||
"localeDe": "Немецкий",
|
||||
"localePt": "Португальский",
|
||||
"localeFr": "Французский",
|
||||
"localeEn": "Английский",
|
||||
"settingLanguage": "Язык",
|
||||
"zoomLabel": "Масштаб интерфейса (в основном влияет на размеры текста и кнопок)",
|
||||
"versionBuilddate": "Версия: %1 Сборка от: %2",
|
||||
"cwtchSettingsTitle": "Настройки Cwtch",
|
||||
"unlock": "Разблокировать",
|
||||
"yourServers": "Ваши Серверы",
|
||||
"yourProfiles": "Ваши Профили",
|
||||
"error0ProfilesLoadedForPassword": "0 профилей, загруженных с этим паролем",
|
||||
"password": "Пароль",
|
||||
"enterProfilePassword": "Введите пароль для просмотра ваших профилей",
|
||||
"addNewProfileBtn": "Добавить новый профиль",
|
||||
"deleteConfirmText": "УДАЛИТЬ",
|
||||
"deleteProfileConfirmBtn": "Действительно удалить профиль?",
|
||||
"deleteConfirmLabel": "Введите DELETE чтобы продолжить",
|
||||
"deleteProfileBtn": "Удалить профиль",
|
||||
"passwordChangeError": "Ошибка при смене пароля: Введенный пароль отклонен",
|
||||
"passwordErrorMatch": "Пароли не совпадают",
|
||||
"saveProfileBtn": "Сохранить профиль",
|
||||
"createProfileBtn": "Создать профиль",
|
||||
"passwordErrorEmpty": "Пароль не может быть пустым",
|
||||
"password2Label": "Повторный ввод пароля",
|
||||
"password1Label": "Пароль",
|
||||
"currentPasswordLabel": "Текущий пароль",
|
||||
"yourDisplayName": "Отображаемое имя",
|
||||
"noPasswordWarning": "Отсутствие пароля в этой учетной записи означает, что все данные, хранящиеся локально, не будут зашифрованы",
|
||||
"radioNoPassword": "Незашифрованный (без пароля)",
|
||||
"radioUsePassword": "Пароль",
|
||||
"copiedToClipboardNotification": "Copied to Clipboard",
|
||||
"copyBtn": "Copy",
|
||||
"editProfile": "Изменить профиль",
|
||||
"newProfile": "Новый профиль",
|
||||
"defaultProfileName": "Alice",
|
||||
"profileName": "Отображаемое имя",
|
||||
"editProfileTitle": "Изменить профиль",
|
||||
"addProfileTitle": "Добавить новый профиль",
|
||||
"deleteBtn": "Delete",
|
||||
"saveBtn": "Save",
|
||||
"displayNameLabel": "Отображаемое имя",
|
||||
"addressLabel": "Адрес",
|
||||
"puzzleGameBtn": "Puzzle Game",
|
||||
"bulletinsBtn": "Bulletins",
|
||||
"listsBtn": "Списки",
|
||||
"chatBtn": "Чат",
|
||||
"rejectGroupBtn": "Отклонить",
|
||||
"acceptGroupBtn": "Принять",
|
||||
"acceptGroupInviteLabel": "Хотите принять приглашение в",
|
||||
"newGroupBtn": "Создать новую группу",
|
||||
"copiedClipboardNotification": "Скопировано в буфер обмена",
|
||||
"pendingLabel": "Ожидаемый",
|
||||
"acknowledgedLabel": "Отправлено",
|
||||
"couldNotSendMsgError": "Не удалось отправить это сообщение",
|
||||
"dmTooltip": "Нажмите, чтобы перейти в DM",
|
||||
"membershipDescription": "Ниже приведен список пользователей, отправивших сообщения группе. Этот список может не отражать всех пользователей, имеющих доступ к группе.",
|
||||
"addListItemBtn": "Добавить элемент",
|
||||
"searchList": "Список поиска",
|
||||
"update": "Обновить",
|
||||
"inviteBtn": "Пригласить",
|
||||
"inviteToGroupLabel": "Пригласить в группу",
|
||||
"groupNameLabel": "Group name",
|
||||
"viewServerInfo": "Информация о сервере",
|
||||
"serverSynced": "Синхронизировано",
|
||||
"serverConnectivityDisconnected": "Сервер отключен",
|
||||
"serverConnectivityConnected": "Сервер подключен",
|
||||
"serverInfo": "Информация о сервере",
|
||||
"invitationLabel": "Приглашение",
|
||||
"serverLabel": "Server",
|
||||
"search": "Поиск...",
|
||||
"cycleColoursDesktop": "Нажмите, чтобы переключать цвета.\nПравый клик чтобы сбросить.",
|
||||
"cycleColoursAndroid": "Нажмите, чтобы переключать цвета.\nНажмите и удерживайте, чтобы сбросить.",
|
||||
"cycleMorphsDesktop": "Нажмите, чтобы просмотреть формы.\nПравый клик чтобы сбросить.",
|
||||
"cycleMorphsAndroid": "Нажмите, чтобы просмотреть формы.\nНажмите и удерживайте, чтобы сбросить.",
|
||||
"cycleCatsDesktop": "Нажмите, чтобы просмотреть категории.\nПравый клик чтобы сбросить.",
|
||||
"cycleCatsAndroid": "Нажмите, чтобы просмотреть категории.\nНажмите и удерживайте, чтобы сбросить.",
|
||||
"blocked": "Заблокировано",
|
||||
"titlePlaceholder": "заговолок...",
|
||||
"postNewBulletinLabel": "Опубликовать новый бюллетень",
|
||||
"newBulletinLabel": "Новый бюллетень",
|
||||
"joinGroup": "Вступить в группу",
|
||||
"createGroup": "Создать группу",
|
||||
"groupAddr": "Адрес",
|
||||
"invitation": "Приглашение",
|
||||
"server": "Сервер",
|
||||
"groupName": "Имя группы",
|
||||
"peerName": "Имя",
|
||||
"peerAddress": "Адрес",
|
||||
"joinGroupTab": "Присоединиться к группе",
|
||||
"createGroupTab": "Создать группу",
|
||||
"createGroupBtn": "Создать",
|
||||
"defaultGroupName": "Замечательная группа",
|
||||
"createGroupTitle": "Создать группу"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cwtch/config.dart';
|
||||
import 'package:cwtch/notification_manager.dart';
|
||||
import 'package:cwtch/views/messageview.dart';
|
||||
import 'package:cwtch/widgets/rightshiftfixer.dart';
|
||||
|
@ -17,7 +16,6 @@ import 'cwtch/cwtch.dart';
|
|||
import 'cwtch/cwtchNotifier.dart';
|
||||
import 'licenses.dart';
|
||||
import 'model.dart';
|
||||
import 'models/servers.dart';
|
||||
import 'views/profilemgrview.dart';
|
||||
import 'views/splashView.dart';
|
||||
import 'dart:io' show Platform, exit;
|
||||
|
@ -28,10 +26,9 @@ var globalSettings = Settings(Locale("en", ''), OpaqueDark());
|
|||
var globalErrorHandler = ErrorHandler();
|
||||
var globalTorStatus = TorStatus();
|
||||
var globalAppState = AppState();
|
||||
var globalServersList = ServerListState();
|
||||
|
||||
void main() {
|
||||
print("Cwtch version: ${EnvironmentConfig.BUILD_VER} built on: ${EnvironmentConfig.BUILD_DATE}");
|
||||
print("main()");
|
||||
LicenseRegistry.addLicense(() => licenses());
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
print("runApp()");
|
||||
|
@ -64,13 +61,13 @@ class FlwtchState extends State<Flwtch> {
|
|||
shutdownMethodChannel.setMethodCallHandler(modalShutdown);
|
||||
print("initState: creating cwtchnotifier, ffi");
|
||||
if (Platform.isAndroid) {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList);
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
|
||||
cwtch = CwtchGomobile(cwtchNotifier);
|
||||
} else if (Platform.isLinux) {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(), globalAppState, globalServersList);
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(), globalAppState);
|
||||
cwtch = CwtchFfi(cwtchNotifier);
|
||||
} else {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList);
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
|
||||
cwtch = CwtchFfi(cwtchNotifier);
|
||||
}
|
||||
print("initState: invoking cwtch.Start()");
|
||||
|
@ -84,7 +81,6 @@ class FlwtchState extends State<Flwtch> {
|
|||
ChangeNotifierProvider<AppState> getAppStateProvider() => ChangeNotifierProvider.value(value: globalAppState);
|
||||
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
|
||||
ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
|
||||
ChangeNotifierProvider<ServerListState> getServerListStateProvider() => ChangeNotifierProvider.value(value: globalServersList);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -97,7 +93,6 @@ class FlwtchState extends State<Flwtch> {
|
|||
getErrorHandlerProvider(),
|
||||
getTorStatusProvider(),
|
||||
getAppStateProvider(),
|
||||
getServerListStateProvider(),
|
||||
],
|
||||
builder: (context, widget) {
|
||||
return Consumer2<Settings, AppState>(
|
||||
|
@ -120,13 +115,13 @@ class FlwtchState extends State<Flwtch> {
|
|||
// the MyBroadcastReceiver method channel
|
||||
Future<void> modalShutdown(MethodCall mc) async {
|
||||
// set up the buttons
|
||||
Widget cancelButton = ElevatedButton(
|
||||
Widget cancelButton = TextButton(
|
||||
child: Text(AppLocalizations.of(navKey.currentContext!)!.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(navKey.currentContext!).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = ElevatedButton(
|
||||
Widget continueButton = TextButton(
|
||||
child: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchAction),
|
||||
onPressed: () {
|
||||
// Directly call the shutdown command, Android will do this for us...
|
||||
|
@ -160,7 +155,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
Future.delayed(Duration(seconds: 2)).then((value) {
|
||||
if (Platform.isAndroid) {
|
||||
SystemNavigator.pop();
|
||||
} else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
|
||||
} else if (Platform.isLinux || Platform.isWindows) {
|
||||
print("Exiting...");
|
||||
exit(0);
|
||||
}
|
||||
|
@ -173,7 +168,6 @@ class FlwtchState extends State<Flwtch> {
|
|||
var args = jsonDecode(call.arguments);
|
||||
var profile = profs.getProfile(args["ProfileOnion"])!;
|
||||
var convo = profile.contactList.getContact(args["Handle"])!;
|
||||
Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
|
||||
convo.unreadMessages = 0;
|
||||
|
||||
// single pane mode pushes; double pane mode reads AppState.selectedProfile/Conversation
|
||||
|
|
365
lib/model.dart
|
@ -2,7 +2,14 @@ import 'dart:convert';
|
|||
|
||||
import 'package:cwtch/widgets/messagerow.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cwtch/models/profileservers.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/widgets/messagebubble.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'cwtch/cwtch.dart';
|
||||
import 'main.dart';
|
||||
|
||||
////////////////////
|
||||
/// UI State ///
|
||||
|
@ -24,68 +31,6 @@ class ChatMessage {
|
|||
};
|
||||
}
|
||||
|
||||
class AppState extends ChangeNotifier {
|
||||
bool cwtchInit = false;
|
||||
bool cwtchIsClosing = false;
|
||||
String appError = "";
|
||||
String? _selectedProfile;
|
||||
String? _selectedConversation;
|
||||
int _initialScrollIndex = 0;
|
||||
int _hoveredIndex = -1;
|
||||
int? _selectedIndex;
|
||||
bool _unreadMessagesBelow = false;
|
||||
|
||||
void SetCwtchInit() {
|
||||
cwtchInit = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void SetAppError(String error) {
|
||||
appError = error;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String? get selectedProfile => _selectedProfile;
|
||||
set selectedProfile(String? newVal) {
|
||||
this._selectedProfile = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String? get selectedConversation => _selectedConversation;
|
||||
set selectedConversation(String? newVal) {
|
||||
this._selectedConversation = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int? get selectedIndex => _selectedIndex;
|
||||
set selectedIndex(int? newVal) {
|
||||
this._selectedIndex = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Never use this for message lookup - can be a non-indexed value
|
||||
// e.g. -1
|
||||
int get hoveredIndex => _hoveredIndex;
|
||||
set hoveredIndex(int newVal) {
|
||||
this._hoveredIndex = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get unreadMessagesBelow => _unreadMessagesBelow;
|
||||
set unreadMessagesBelow(bool newVal) {
|
||||
this._unreadMessagesBelow = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int get initialScrollIndex => _initialScrollIndex;
|
||||
set initialScrollIndex(int newVal) {
|
||||
this._initialScrollIndex = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
/// Providers ///
|
||||
///////////////////
|
||||
|
@ -117,6 +62,45 @@ class ProfileListState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
class AppState extends ChangeNotifier {
|
||||
bool cwtchInit = false;
|
||||
bool cwtchIsClosing = false;
|
||||
String appError = "";
|
||||
String? _selectedProfile;
|
||||
String? _selectedConversation;
|
||||
int? _selectedIndex;
|
||||
|
||||
void SetCwtchInit() {
|
||||
cwtchInit = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void SetAppError(String error) {
|
||||
appError = error;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String? get selectedProfile => _selectedProfile;
|
||||
set selectedProfile(String? newVal) {
|
||||
this._selectedProfile = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String? get selectedConversation => _selectedConversation;
|
||||
set selectedConversation(String? newVal) {
|
||||
this._selectedConversation = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int? get selectedIndex => _selectedIndex;
|
||||
set selectedIndex(int? newVal) {
|
||||
this._selectedIndex = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height;
|
||||
}
|
||||
|
||||
class ContactListState extends ChangeNotifier {
|
||||
List<ContactInfoState> _contacts = [];
|
||||
String _filter = "";
|
||||
|
@ -151,9 +135,6 @@ class ContactListState extends ChangeNotifier {
|
|||
// blocked contacts last
|
||||
if (a.isBlocked == true && b.isBlocked != true) return 1;
|
||||
if (a.isBlocked != true && b.isBlocked == true) return -1;
|
||||
// archive is next...
|
||||
if (!a.isArchived && b.isArchived) return -1;
|
||||
if (a.isArchived && !b.isArchived) return 1;
|
||||
// special sorting for contacts with no messages in either history
|
||||
if (a.lastMessageTime.millisecondsSinceEpoch == 0 && b.lastMessageTime.millisecondsSinceEpoch == 0) {
|
||||
// online contacts first
|
||||
|
@ -172,21 +153,12 @@ class ContactListState extends ChangeNotifier {
|
|||
//} </todo>
|
||||
}
|
||||
|
||||
void updateLastMessageTime(String forOnion, DateTime newMessageTime) {
|
||||
void updateLastMessageTime(String forOnion, DateTime newVal) {
|
||||
var contact = getContact(forOnion);
|
||||
if (contact == null) return;
|
||||
|
||||
// Assert that the new time is after the current last message time AND that
|
||||
// new message time is before the current time.
|
||||
if (newMessageTime.isAfter(contact.lastMessageTime)) {
|
||||
if (newMessageTime.isBefore(DateTime.now().toLocal())) {
|
||||
contact.lastMessageTime = newMessageTime;
|
||||
} else {
|
||||
// Otherwise set the last message time to now...
|
||||
contact.lastMessageTime = DateTime.now().toLocal();
|
||||
}
|
||||
resort();
|
||||
}
|
||||
contact.lastMessageTime = newVal;
|
||||
resort();
|
||||
}
|
||||
|
||||
List<ContactInfoState> get contacts => _contacts.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
@ -207,13 +179,12 @@ class ContactListState extends ChangeNotifier {
|
|||
|
||||
class ProfileInfoState extends ChangeNotifier {
|
||||
ContactListState _contacts = ContactListState();
|
||||
ProfileServerListState _servers = ProfileServerListState();
|
||||
ServerListState _servers = ServerListState();
|
||||
final String onion;
|
||||
String _nickname = "";
|
||||
String _imagePath = "";
|
||||
int _unreadMessages = 0;
|
||||
bool _online = false;
|
||||
Map<String, FileDownloadProgress> _downloads = Map<String, FileDownloadProgress>();
|
||||
|
||||
// assume profiles are encrypted...this will be set to false
|
||||
// in the constructor if the profile is encrypted with the defacto password.
|
||||
|
@ -242,13 +213,13 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
nickname: contact["name"],
|
||||
status: contact["status"],
|
||||
imagePath: contact["picture"],
|
||||
authorization: stringToContactAuthorization(contact["authorization"]),
|
||||
isBlocked: contact["authorization"] == "blocked",
|
||||
isInvitation: contact["authorization"] == "unknown",
|
||||
savePeerHistory: contact["saveConversationHistory"],
|
||||
numMessages: contact["numMessages"],
|
||||
numUnread: contact["numUnread"],
|
||||
isGroup: contact["isGroup"],
|
||||
server: contact["groupServer"],
|
||||
archived: contact["isArchived"] == true,
|
||||
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])));
|
||||
}));
|
||||
|
||||
|
@ -267,7 +238,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
List<dynamic> servers = jsonDecode(serversJson);
|
||||
this._servers.replace(servers.map((server) {
|
||||
// TODO Keys...
|
||||
return RemoteServerInfoState(onion: server["onion"], status: server["status"]);
|
||||
return ServerInfoState(onion: server["onion"], status: server["status"]);
|
||||
}));
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -316,7 +287,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
ContactListState get contactList => this._contacts;
|
||||
ProfileServerListState get serverList => this._servers;
|
||||
ServerListState get serverList => this._servers;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
@ -345,7 +316,8 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
nickname: contact["name"],
|
||||
status: contact["status"],
|
||||
imagePath: contact["picture"],
|
||||
authorization: stringToContactAuthorization(contact["authorization"]),
|
||||
isBlocked: contact["authorization"] == "blocked",
|
||||
isInvitation: contact["authorization"] == "unknown",
|
||||
savePeerHistory: contact["saveConversationHistory"],
|
||||
numMessages: contact["numMessages"],
|
||||
numUnread: contact["numUnread"],
|
||||
|
@ -357,141 +329,6 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
void downloadInit(String fileKey, int numChunks) {
|
||||
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
||||
}
|
||||
|
||||
void downloadUpdate(String fileKey, int progress, int numChunks) {
|
||||
if (!downloadActive(fileKey)) {
|
||||
if (progress < 0) {
|
||||
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
||||
this._downloads[fileKey]!.interrupted = true;
|
||||
notifyListeners();
|
||||
} else {
|
||||
print("error: received progress for unknown download " + fileKey);
|
||||
}
|
||||
} else {
|
||||
if (this._downloads[fileKey]!.interrupted) {
|
||||
this._downloads[fileKey]!.interrupted = false;
|
||||
}
|
||||
this._downloads[fileKey]!.chunksDownloaded = progress;
|
||||
this._downloads[fileKey]!.chunksTotal = numChunks;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void downloadMarkManifest(String fileKey) {
|
||||
if (!downloadActive(fileKey)) {
|
||||
print("error: received download completion notice for unknown download " + fileKey);
|
||||
} else {
|
||||
this._downloads[fileKey]!.gotManifest = true;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void downloadMarkFinished(String fileKey, String finalPath) {
|
||||
if (!downloadActive(fileKey)) {
|
||||
// happens as a result of a CheckDownloadStatus call,
|
||||
// invoked from a historical (timeline) download message
|
||||
// so setting numChunks correctly shouldn't matter
|
||||
this.downloadInit(fileKey, 1);
|
||||
}
|
||||
this._downloads[fileKey]!.timeEnd = DateTime.now();
|
||||
this._downloads[fileKey]!.downloadedTo = finalPath;
|
||||
this._downloads[fileKey]!.complete = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool downloadActive(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) && !this._downloads[fileKey]!.interrupted;
|
||||
}
|
||||
|
||||
bool downloadGotManifest(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.gotManifest;
|
||||
}
|
||||
|
||||
bool downloadComplete(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.complete;
|
||||
}
|
||||
|
||||
bool downloadInterrupted(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.interrupted;
|
||||
}
|
||||
|
||||
void downloadMarkResumed(String fileKey) {
|
||||
if (this._downloads.containsKey(fileKey)) {
|
||||
this._downloads[fileKey]!.interrupted = false;
|
||||
}
|
||||
}
|
||||
|
||||
double downloadProgress(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.progress() : 0.0;
|
||||
}
|
||||
|
||||
// used for loading interrupted download info; use downloadMarkFinished for successful downloads
|
||||
void downloadSetPath(String fileKey, String path) {
|
||||
if (this._downloads.containsKey(fileKey)) {
|
||||
this._downloads[fileKey]!.downloadedTo = path;
|
||||
}
|
||||
}
|
||||
|
||||
String? downloadFinalPath(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.downloadedTo : null;
|
||||
}
|
||||
|
||||
String downloadSpeed(String fileKey) {
|
||||
if (!downloadActive(fileKey) || this._downloads[fileKey]!.chunksDownloaded == 0) {
|
||||
return "0 B/s";
|
||||
}
|
||||
var bytes = this._downloads[fileKey]!.chunksDownloaded * 4096;
|
||||
var seconds = (this._downloads[fileKey]!.timeEnd ?? DateTime.now()).difference(this._downloads[fileKey]!.timeStart!).inSeconds;
|
||||
if (seconds == 0) {
|
||||
return "0 B/s";
|
||||
}
|
||||
return prettyBytes((bytes / seconds).round()) + "/s";
|
||||
}
|
||||
}
|
||||
|
||||
class FileDownloadProgress {
|
||||
int chunksDownloaded = 0;
|
||||
int chunksTotal = 1;
|
||||
bool complete = false;
|
||||
bool gotManifest = false;
|
||||
bool interrupted = false;
|
||||
String? downloadedTo;
|
||||
DateTime? timeStart;
|
||||
DateTime? timeEnd;
|
||||
|
||||
FileDownloadProgress(this.chunksTotal, this.timeStart);
|
||||
double progress() {
|
||||
return 1.0 * chunksDownloaded / chunksTotal;
|
||||
}
|
||||
}
|
||||
|
||||
String prettyBytes(int bytes) {
|
||||
if (bytes > 1000000000) {
|
||||
return (1.0 * bytes / 1000000000).toStringAsFixed(1) + " GB";
|
||||
} else if (bytes > 1000000) {
|
||||
return (1.0 * bytes / 1000000).toStringAsFixed(1) + " MB";
|
||||
} else if (bytes > 1000) {
|
||||
return (1.0 * bytes / 1000).toStringAsFixed(1) + " kB";
|
||||
} else {
|
||||
return bytes.toString() + " B";
|
||||
}
|
||||
}
|
||||
|
||||
enum ContactAuthorization { unknown, approved, blocked }
|
||||
|
||||
ContactAuthorization stringToContactAuthorization(String authStr) {
|
||||
switch (authStr) {
|
||||
case "approved":
|
||||
return ContactAuthorization.approved;
|
||||
case "blocked":
|
||||
return ContactAuthorization.blocked;
|
||||
default:
|
||||
return ContactAuthorization.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
class ContactInfoState extends ChangeNotifier {
|
||||
|
@ -499,7 +336,8 @@ class ContactInfoState extends ChangeNotifier {
|
|||
final String onion;
|
||||
late String _nickname;
|
||||
|
||||
late ContactAuthorization _authorization;
|
||||
late bool _isInvitation;
|
||||
late bool _isBlocked;
|
||||
late String _status;
|
||||
late String _imagePath;
|
||||
late String _savePeerHistory;
|
||||
|
@ -507,29 +345,30 @@ class ContactInfoState extends ChangeNotifier {
|
|||
late int _totalMessages = 0;
|
||||
late DateTime _lastMessageTime;
|
||||
late Map<String, GlobalKey<MessageRowState>> keys;
|
||||
int _newMarker = 0;
|
||||
DateTime _newMarkerClearAt = DateTime.now();
|
||||
|
||||
// todo: a nicer way to model contacts, groups and other "entities"
|
||||
late bool _isGroup;
|
||||
String? _server;
|
||||
late bool _archived;
|
||||
|
||||
ContactInfoState(this.profileOnion, this.onion,
|
||||
{nickname = "",
|
||||
isGroup = false,
|
||||
authorization = ContactAuthorization.unknown,
|
||||
status = "",
|
||||
imagePath = "",
|
||||
savePeerHistory = "DeleteHistoryConfirmed",
|
||||
numMessages = 0,
|
||||
numUnread = 0,
|
||||
lastMessageTime,
|
||||
server,
|
||||
archived = false}) {
|
||||
ContactInfoState(
|
||||
this.profileOnion,
|
||||
this.onion, {
|
||||
nickname = "",
|
||||
isGroup = false,
|
||||
isInvitation = false,
|
||||
isBlocked = false,
|
||||
status = "",
|
||||
imagePath = "",
|
||||
savePeerHistory = "DeleteHistoryConfirmed",
|
||||
numMessages = 0,
|
||||
numUnread = 0,
|
||||
lastMessageTime,
|
||||
server,
|
||||
}) {
|
||||
this._nickname = nickname;
|
||||
this._isGroup = isGroup;
|
||||
this._authorization = authorization;
|
||||
this._isInvitation = isInvitation;
|
||||
this._isBlocked = isBlocked;
|
||||
this._status = status;
|
||||
this._imagePath = imagePath;
|
||||
this._totalMessages = numMessages;
|
||||
|
@ -537,24 +376,12 @@ class ContactInfoState extends ChangeNotifier {
|
|||
this._savePeerHistory = savePeerHistory;
|
||||
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
||||
this._server = server;
|
||||
this._archived = archived;
|
||||
keys = Map<String, GlobalKey<MessageRowState>>();
|
||||
}
|
||||
|
||||
String get nickname => this._nickname;
|
||||
|
||||
String get savePeerHistory => this._savePeerHistory;
|
||||
|
||||
// Indicated whether the conversation is archived, in which case it will
|
||||
// be moved to the very bottom of the active conversations list until
|
||||
// new messages appear
|
||||
set isArchived(bool archived) {
|
||||
this._archived = archived;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get isArchived => this._archived;
|
||||
|
||||
set savePeerHistory(String newVal) {
|
||||
this._savePeerHistory = newVal;
|
||||
notifyListeners();
|
||||
|
@ -571,13 +398,15 @@ class ContactInfoState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get isBlocked => this._authorization == ContactAuthorization.blocked;
|
||||
bool get isBlocked => this._isBlocked;
|
||||
set isBlocked(bool newVal) {
|
||||
this._isBlocked = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get isInvitation => this._authorization == ContactAuthorization.unknown;
|
||||
|
||||
ContactAuthorization get authorization => this._authorization;
|
||||
set authorization(ContactAuthorization newAuth) {
|
||||
this._authorization = newAuth;
|
||||
bool get isInvitation => this._isInvitation;
|
||||
set isInvitation(bool newVal) {
|
||||
this._isInvitation = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -589,36 +418,10 @@ class ContactInfoState extends ChangeNotifier {
|
|||
|
||||
int get unreadMessages => this._unreadMessages;
|
||||
set unreadMessages(int newVal) {
|
||||
// don't reset newMarker position when unreadMessages is being cleared
|
||||
if (newVal > 0) {
|
||||
this._newMarker = newVal;
|
||||
} else {
|
||||
this._newMarkerClearAt = DateTime.now().add(const Duration(minutes:2));
|
||||
}
|
||||
this._unreadMessages = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int get newMarker {
|
||||
if (DateTime.now().isAfter(this._newMarkerClearAt)) {
|
||||
// perform heresy
|
||||
this._newMarker = 0;
|
||||
// no need to notifyListeners() because presumably this getter is
|
||||
// being called from a renderer anyway
|
||||
}
|
||||
return this._newMarker;
|
||||
}
|
||||
// what's a getter that sometimes sets without a setter
|
||||
// that sometimes doesn't set
|
||||
set newMarker(int newVal) {
|
||||
// only unreadMessages++ can set newMarker = 1;
|
||||
// avoids drawing a marker when the convo is already open
|
||||
if (newVal > 1) {
|
||||
this._newMarker = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
int get totalMessages => this._totalMessages;
|
||||
set totalMessages(int newVal) {
|
||||
this._totalMessages = newVal;
|
||||
|
|
|
@ -3,8 +3,6 @@ import 'package:flutter/widgets.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import 'messages/filemessage.dart';
|
||||
import 'messages/invitemessage.dart';
|
||||
import 'messages/malformedmessage.dart';
|
||||
import 'messages/quotedmessage.dart';
|
||||
|
@ -15,7 +13,6 @@ const TextMessageOverlay = 1;
|
|||
const QuotedMessageOverlay = 10;
|
||||
const SuggestContactOverlay = 100;
|
||||
const InviteGroupOverlay = 101;
|
||||
const FileShareOverlay = 200;
|
||||
|
||||
// Defines the length of the tor v3 onion address. Code using this constant will
|
||||
// need to updated when we allow multiple different identifiers. At which time
|
||||
|
@ -35,39 +32,38 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, String
|
|||
try {
|
||||
var rawMessageEnvelopeFuture = Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, index);
|
||||
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
|
||||
var metadata = MessageMetadata(profileOnion, contactHandle, index, DateTime.now(), "", "", null, 0, false, true);
|
||||
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
||||
// There are 2 conditions in which this error condition can be met:
|
||||
// 1. The application == nil, in which case this instance of the UI is already
|
||||
// broken beyond repair, and will either be replaced by a new version, or requires a complete
|
||||
// restart.
|
||||
// 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion.
|
||||
// This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the
|
||||
// calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one
|
||||
// will find itself delayed.
|
||||
// The second case is recoverable by tail-recursing this future.
|
||||
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
||||
return Future.delayed(Duration(seconds: 2), () {
|
||||
print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug.");
|
||||
return messageHandler(context, profileOnion, contactHandle, index).then((value) => value);
|
||||
});
|
||||
}
|
||||
|
||||
// Construct the initial metadata
|
||||
var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
||||
var senderHandle = messageWrapper['PeerID'];
|
||||
var senderImage = messageWrapper['ContactImage'];
|
||||
var flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
|
||||
var ackd = messageWrapper['Acknowledged'];
|
||||
var error = messageWrapper['Error'] != null;
|
||||
String? signature;
|
||||
// If this is a group, store the signature
|
||||
if (contactHandle.length == GroupConversationHandleLength) {
|
||||
signature = messageWrapper['Signature'];
|
||||
}
|
||||
var metadata = MessageMetadata(profileOnion, contactHandle, index, timestamp, senderHandle, senderImage, signature, flags, ackd, error);
|
||||
|
||||
try {
|
||||
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
||||
// There are 2 conditions in which this error condition can be met:
|
||||
// 1. The application == nil, in which case this instance of the UI is already
|
||||
// broken beyond repair, and will either be replaced by a new version, or requires a complete
|
||||
// restart.
|
||||
// 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion.
|
||||
// This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the
|
||||
// calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one
|
||||
// will find itself delayed.
|
||||
// The second case is recoverable by tail-recursing this future.
|
||||
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
||||
return Future.delayed(Duration(seconds: 2), () {
|
||||
print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug.");
|
||||
return messageHandler(context, profileOnion, contactHandle, index).then((value) => value);
|
||||
});
|
||||
}
|
||||
|
||||
// Construct the initial metadata
|
||||
var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
||||
var senderHandle = messageWrapper['PeerID'];
|
||||
var senderImage = messageWrapper['ContactImage'];
|
||||
var flags = int.parse(messageWrapper['Flags'].toString());
|
||||
var ackd = messageWrapper['Acknowledged'];
|
||||
var error = messageWrapper['Error'] != null;
|
||||
String? signature;
|
||||
// If this is a group, store the signature
|
||||
if (contactHandle.length == GroupConversationHandleLength) {
|
||||
signature = messageWrapper['Signature'];
|
||||
}
|
||||
metadata = MessageMetadata(profileOnion, contactHandle, index, timestamp, senderHandle, senderImage, signature, flags, ackd, error);
|
||||
|
||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
||||
var content = message['d'] as dynamic;
|
||||
var overlay = int.parse(message['o'].toString());
|
||||
|
@ -80,14 +76,11 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, String
|
|||
return InviteMessage(overlay, metadata, content);
|
||||
case QuotedMessageOverlay:
|
||||
return QuotedMessage(metadata, content);
|
||||
case FileShareOverlay:
|
||||
return FileMessage(metadata, content);
|
||||
default:
|
||||
// Metadata is valid, content is not..
|
||||
return MalformedMessage(metadata);
|
||||
}
|
||||
} catch (e) {
|
||||
print("an error! " + e.toString());
|
||||
return MalformedMessage(metadata);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/widgets/filebubble.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:cwtch/widgets/messagerow.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../model.dart';
|
||||
|
||||
class FileMessage extends Message {
|
||||
final MessageMetadata metadata;
|
||||
final String content;
|
||||
final RegExp nonHex = RegExp(r'[^a-f0-9]');
|
||||
|
||||
FileMessage(this.metadata, this.content);
|
||||
|
||||
@override
|
||||
Widget getWidget(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
dynamic shareObj = jsonDecode(this.content);
|
||||
if (shareObj == null) {
|
||||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
String nameSuggestion = shareObj['f'] as String;
|
||||
String rootHash = shareObj['h'] as String;
|
||||
String nonce = shareObj['n'] as String;
|
||||
int fileSize = shareObj['s'] as int;
|
||||
|
||||
if (!validHash(rootHash, nonce)) {
|
||||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
|
||||
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget getPreviewWidget(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
dynamic shareObj = jsonDecode(this.content);
|
||||
if (shareObj == null) {
|
||||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
String nameSuggestion = shareObj['n'] as String;
|
||||
String rootHash = shareObj['h'] as String;
|
||||
String nonce = shareObj['n'] as String;
|
||||
int fileSize = shareObj['s'] as int;
|
||||
if (!validHash(rootHash, nonce)) {
|
||||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
return FileBubble(
|
||||
nameSuggestion,
|
||||
rootHash,
|
||||
nonce,
|
||||
fileSize,
|
||||
interactive: false,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
MessageMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
bool validHash(String hash, String nonce) {
|
||||
return hash.length == 128 && nonce.length == 48 && !hash.contains(nonHex) && !nonce.contains(nonHex);
|
||||
}
|
||||
}
|
|
@ -21,10 +21,10 @@ class InviteMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||
|
||||
String inviteTarget;
|
||||
String inviteNick;
|
||||
String invite = this.content;
|
||||
|
||||
if (this.content.length == TorV3ContactHandleLength) {
|
||||
inviteTarget = this.content;
|
||||
|
@ -40,7 +40,7 @@ class InviteMessage extends Message {
|
|||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
}
|
||||
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ class InviteMessage extends Message {
|
|||
builder: (bcontext, child) {
|
||||
String inviteTarget;
|
||||
String inviteNick;
|
||||
String invite = this.content;
|
||||
|
||||
if (this.content.length == TorV3ContactHandleLength) {
|
||||
inviteTarget = this.content;
|
||||
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
|
||||
|
@ -66,7 +66,7 @@ class InviteMessage extends Message {
|
|||
return MalformedBubble();
|
||||
}
|
||||
}
|
||||
return InvitationBubble(overlay, inviteTarget, inviteNick, invite);
|
||||
return InvitationBubble(overlay, inviteTarget, inviteNick);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,17 +10,6 @@ import 'package:provider/provider.dart';
|
|||
import '../../main.dart';
|
||||
import '../../model.dart';
|
||||
|
||||
class QuotedMessageStructure {
|
||||
final String quotedHash;
|
||||
final String body;
|
||||
QuotedMessageStructure(this.quotedHash, this.body);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'quotedHash': quotedHash,
|
||||
'body': body,
|
||||
};
|
||||
}
|
||||
|
||||
class LocallyIndexedMessage {
|
||||
final dynamic message;
|
||||
final int index;
|
||||
|
@ -94,7 +83,7 @@ class QuotedMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||
return MessageRow(
|
||||
QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
|
||||
if (localIndex != null) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/widgets/messagebubble.dart';
|
||||
import 'package:cwtch/widgets/messagerow.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -32,7 +31,7 @@ class TextMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||
return MessageRow(MessageBubble(this.content), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProfileServerListState extends ChangeNotifier {
|
||||
List<RemoteServerInfoState> _servers = [];
|
||||
|
||||
void replace(Iterable<RemoteServerInfoState> newServers) {
|
||||
_servers.clear();
|
||||
_servers.addAll(newServers);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
RemoteServerInfoState? getServer(String onion) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
return idx >= 0 ? _servers[idx] : null;
|
||||
}
|
||||
|
||||
void updateServerCache(String onion, String status) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_servers[idx] = RemoteServerInfoState(onion: onion, status: status);
|
||||
} else {
|
||||
print("Tried to update server cache without a starting state...this is probably an error");
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<RemoteServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
}
|
||||
|
||||
class RemoteServerInfoState extends ChangeNotifier {
|
||||
final String onion;
|
||||
final String status;
|
||||
|
||||
RemoteServerInfoState({required this.onion, required this.status});
|
||||
}
|
|
@ -9,72 +9,28 @@ class ServerListState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_servers.clear();
|
||||
}
|
||||
|
||||
ServerInfoState? getServer(String onion) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
return idx >= 0 ? _servers[idx] : null;
|
||||
}
|
||||
|
||||
void add(String onion, String serverBundle, bool running, String description, bool autoStart, bool isEncrypted) {
|
||||
var sis = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted);
|
||||
void updateServerCache(String onion, String status) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_servers[idx] = sis;
|
||||
_servers[idx] = ServerInfoState(onion: onion, status: status);
|
||||
} else {
|
||||
_servers.add(ServerInfoState(onion: onion,
|
||||
serverBundle: serverBundle,
|
||||
running: running,
|
||||
description: description,
|
||||
autoStart: autoStart,
|
||||
isEncrypted: isEncrypted));
|
||||
print("Tried to update server cache without a starting state...this is probably an error");
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void updateServer(String onion, String serverBundle, bool running, String description, bool autoStart, bool isEncrypted) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_servers[idx] = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted);
|
||||
} else {
|
||||
print("Tried to update server list without a starting state...this is probably an error");
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void delete(String onion) {
|
||||
_servers.removeWhere((element) => element.onion == onion);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<ServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
}
|
||||
|
||||
class ServerInfoState extends ChangeNotifier {
|
||||
String onion;
|
||||
String serverBundle;
|
||||
String description;
|
||||
bool running;
|
||||
bool autoStart;
|
||||
bool isEncrypted;
|
||||
final String onion;
|
||||
final String status;
|
||||
|
||||
ServerInfoState({required this.onion, required this.serverBundle, required this.running, required this.description, required this.autoStart, required this.isEncrypted});
|
||||
|
||||
void setAutostart(bool val) {
|
||||
autoStart = val;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setRunning(bool val) {
|
||||
running = val;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setDescription(String val) {
|
||||
description = val;
|
||||
notifyListeners();
|
||||
}
|
||||
ServerInfoState({required this.onion, required this.status});
|
||||
}
|
||||
|
|
|
@ -354,7 +354,7 @@ class OpaqueDark extends OpaqueThemeType {
|
|||
}
|
||||
|
||||
Color altTextColor() {
|
||||
return mauvePurple;
|
||||
return whitePurple;
|
||||
}
|
||||
|
||||
Color hilightElementTextColor() {
|
||||
|
@ -374,7 +374,7 @@ class OpaqueDark extends OpaqueThemeType {
|
|||
}
|
||||
|
||||
Color defaultButtonDisabledColor() {
|
||||
return lightGrey;
|
||||
return deepPurple;
|
||||
}
|
||||
|
||||
Color defaultButtonDisabledTextColor() {
|
||||
|
@ -630,7 +630,7 @@ class OpaqueLight extends OpaqueThemeType {
|
|||
static final Color whitePurple = Color(0xFFFFFDFF);
|
||||
static final Color softPurple = Color(0xFFFDF3FC);
|
||||
static final Color purple = Color(0xFFDFB9DE);
|
||||
static final Color brightPurple = Color(0xFFD1B0E0);
|
||||
static final Color brightPurple = Color(0xFF760388);
|
||||
static final Color darkPurple = Color(0xFF350052);
|
||||
static final Color greyPurple = Color(0xFF775F84);
|
||||
static final Color pink = Color(0xFFE85DA1);
|
||||
|
@ -684,7 +684,7 @@ class OpaqueLight extends OpaqueThemeType {
|
|||
}
|
||||
|
||||
Color defaultButtonDisabledColor() {
|
||||
return lightGrey;
|
||||
return purple;
|
||||
}
|
||||
|
||||
Color defaultButtonDisabledTextColor() {
|
||||
|
@ -900,11 +900,11 @@ class OpaqueLight extends OpaqueThemeType {
|
|||
}
|
||||
|
||||
Color messageFromMeBackgroundColor() {
|
||||
return brightPurple;
|
||||
return darkPurple;
|
||||
}
|
||||
|
||||
Color messageFromMeTextColor() {
|
||||
return mainTextColor();
|
||||
return whitePurple;
|
||||
}
|
||||
|
||||
Color messageFromOtherBackgroundColor() {
|
||||
|
@ -948,14 +948,11 @@ ThemeData mkThemeData(Settings opaque) {
|
|||
backgroundColor: opaque.current().backgroundMainColor(),
|
||||
highlightColor: opaque.current().hilightElementTextColor(),
|
||||
iconTheme: IconThemeData(
|
||||
color: opaque.current().toolbarIconColor(),
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
cardColor: opaque.current().backgroundMainColor(),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: opaque.current().backgroundPaneColor(),
|
||||
iconTheme: IconThemeData(
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
titleTextStyle: TextStyle(
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
|
@ -972,15 +969,9 @@ ThemeData mkThemeData(Settings opaque) {
|
|||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) => states.contains(MaterialState.disabled) ? opaque.current().defaultButtonDisabledColor() : opaque.current().defaultButtonColor()),
|
||||
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
|
||||
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()),
|
||||
overlayColor: MaterialStateProperty.resolveWith((states) => (states.contains(MaterialState.pressed) && states.contains(MaterialState.hovered))
|
||||
? opaque.current().defaultButtonActiveColor()
|
||||
: states.contains(MaterialState.disabled)
|
||||
? opaque.current().defaultButtonDisabledColor()
|
||||
: null),
|
||||
enableFeedback: true,
|
||||
splashFactory: InkRipple.splashFactory,
|
||||
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
||||
padding: MaterialStateProperty.all(EdgeInsets.all(20)),
|
||||
shape: MaterialStateProperty.all(RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18.0),
|
||||
|
@ -1013,11 +1004,7 @@ ThemeData mkThemeData(Settings opaque) {
|
|||
thumbColor: MaterialStateProperty.all(opaque.current().mainTextColor()),
|
||||
trackColor: MaterialStateProperty.all(opaque.current().dropShadowColor()),
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
backgroundColor: opaque.current().defaultButtonColor(),
|
||||
hoverColor: opaque.current().defaultButtonActiveColor(),
|
||||
enableFeedback: true,
|
||||
splashColor: opaque.current().defaultButtonActiveColor()),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(backgroundColor: opaque.current().defaultButtonColor(), hoverColor: opaque.current().defaultButtonActiveColor()),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: opaque.current().defaultButtonActiveColor(), selectionColor: opaque.current().defaultButtonActiveColor(), selectionHandleColor: opaque.current().defaultButtonActiveColor()),
|
||||
);
|
||||
|
|
|
@ -9,9 +9,6 @@ import 'opaque.dart';
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
const TapirGroupsExperiment = "tapir-groups-experiment";
|
||||
const ServerManagementExperiment = "servers-experiment";
|
||||
const FileSharingExperiment = "filesharing";
|
||||
const ClickableLinksExperiment = "clickable-links";
|
||||
|
||||
enum DualpaneMode {
|
||||
Single,
|
||||
|
@ -34,7 +31,6 @@ class Settings extends ChangeNotifier {
|
|||
DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait;
|
||||
|
||||
bool blockUnknownConnections = false;
|
||||
bool streamerMode = false;
|
||||
|
||||
/// Set the dark theme.
|
||||
void setDark() {
|
||||
|
@ -78,11 +74,11 @@ class Settings extends ChangeNotifier {
|
|||
// Set Locale and notify listeners
|
||||
switchLocale(Locale(settings["Locale"]));
|
||||
|
||||
blockUnknownConnections = settings["BlockUnknownConnections"] ?? false;
|
||||
streamerMode = settings["StreamerMode"] ?? false;
|
||||
// Decide whether to enable Experiments
|
||||
blockUnknownConnections = settings["BlockUnknownConnections"];
|
||||
|
||||
// Decide whether to enable Experiments
|
||||
experimentsEnabled = settings["ExperimentsEnabled"] ?? false;
|
||||
experimentsEnabled = settings["ExperimentsEnabled"];
|
||||
|
||||
// Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON
|
||||
experiments = new HashMap<String, bool>.from(settings["Experiments"]);
|
||||
|
@ -109,11 +105,6 @@ class Settings extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
setStreamerMode(bool newSteamerMode) {
|
||||
streamerMode = newSteamerMode;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Block Unknown Connections will autoblock connections if they authenticate with public key not in our contacts list.
|
||||
/// This is one of the best tools we have to combat abuse, while it isn't ideal it does allow a user to curate their contacts
|
||||
/// list without being bothered by spurious requests (either permanently, or as a short term measure).
|
||||
|
@ -236,7 +227,6 @@ class Settings extends ChangeNotifier {
|
|||
"Theme": themeString,
|
||||
"PreviousPid": -1,
|
||||
"BlockUnknownConnections": blockUnknownConnections,
|
||||
"StreamerMode": streamerMode,
|
||||
"ExperimentsEnabled": this.experimentsEnabled,
|
||||
"Experiments": experiments,
|
||||
"StateRootPane": 0,
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:cwtch/cwtch_icons_icons.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cwtch/errorHandler.dart';
|
||||
import 'package:cwtch/models/profileservers.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/settings.dart';
|
||||
import 'package:cwtch/widgets/buttontextfield.dart';
|
||||
import 'package:cwtch/widgets/cwtchlabel.dart';
|
||||
|
@ -197,7 +197,7 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
},
|
||||
isExpanded: true, // magic property
|
||||
value: server,
|
||||
items: Provider.of<ProfileInfoState>(context).serverList.servers.map<DropdownMenuItem<String>>((RemoteServerInfoState serverInfo) {
|
||||
items: Provider.of<ProfileInfoState>(context).serverList.servers.map<DropdownMenuItem<String>>((ServerInfoState serverInfo) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: serverInfo.onion,
|
||||
child: Text(
|
||||
|
@ -240,8 +240,8 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
|
||||
/// TODO Manage Servers Tab
|
||||
Widget manageServersTab() {
|
||||
final tiles = Provider.of<ProfileInfoState>(context).serverList.servers.map((RemoteServerInfoState server) {
|
||||
return ChangeNotifierProvider<RemoteServerInfoState>.value(
|
||||
final tiles = Provider.of<ProfileInfoState>(context).serverList.servers.map((ServerInfoState server) {
|
||||
return ChangeNotifierProvider<ServerInfoState>.value(
|
||||
value: server,
|
||||
child: ListTile(
|
||||
title: Text(server.onion),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cwtch/cwtch/cwtch.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cwtch/model.dart';
|
||||
|
@ -107,7 +106,6 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
labelText: AppLocalizations.of(context)!.yourDisplayName,
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
// TODO l10n ize
|
||||
return "Please enter a display name";
|
||||
}
|
||||
return null;
|
||||
|
@ -138,12 +136,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
// We only allow setting password types on profile creation
|
||||
Visibility(
|
||||
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
)),
|
||||
Visibility(
|
||||
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Checkbox(
|
||||
value: usePassword,
|
||||
fillColor: MaterialStateProperty.all(theme.current().defaultButtonColor()),
|
||||
|
@ -154,15 +147,6 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
AppLocalizations.of(context)!.radioUsePassword,
|
||||
style: TextStyle(color: theme.current().mainTextColor()),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
usePassword ? AppLocalizations.of(context)!.encryptedProfileDescription : AppLocalizations.of(context)!.plainProfileDescription,
|
||||
textAlign: TextAlign.center,
|
||||
))
|
||||
])),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
|
@ -265,6 +249,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
|
||||
icon: Icon(Icons.delete_forever),
|
||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||
))
|
||||
|
@ -289,19 +274,32 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, DefaultPassword);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, "be gay do crime");
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
// Profile Editing
|
||||
if (ctrlrPass.value.text.isEmpty) {
|
||||
// Don't update password, only update name
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
|
||||
final event = {
|
||||
"EventType": "SetAttribute",
|
||||
"Data": {"Key": "public.name", "Data": ctrlrNick.value.text}
|
||||
};
|
||||
final json = jsonEncode(event);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, json);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
// At this points passwords have been validated to be the same and not empty
|
||||
// Update both password and name, even if name hasn't been changed...
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
|
||||
final updateNameEvent = {
|
||||
"EventType": "SetAttribute",
|
||||
"Data": {"Key": "public.name", "Data": ctrlrNick.value.text}
|
||||
};
|
||||
final updateNameEventJson = jsonEncode(updateNameEvent);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, updateNameEventJson);
|
||||
|
||||
final updatePasswordEvent = {
|
||||
"EventType": "ChangePassword",
|
||||
"Data": {"Password": ctrlrOldPass.text, "NewPassword": ctrlrPass.text}
|
||||
|
@ -318,13 +316,13 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
|
||||
showAlertDialog(BuildContext context) {
|
||||
// set up the buttons
|
||||
Widget cancelButton = ElevatedButton(
|
||||
Widget cancelButton = TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = ElevatedButton(
|
||||
Widget continueButton = TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
||||
onPressed: () {
|
||||
var onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
|
|
|
@ -1,398 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cwtch/cwtch/cwtch.dart';
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/widgets/cwtchlabel.dart';
|
||||
import 'package:cwtch/widgets/passwordfield.dart';
|
||||
import 'package:cwtch/widgets/textfield.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../errorHandler.dart';
|
||||
import '../main.dart';
|
||||
import '../config.dart';
|
||||
|
||||
/// Pane to add or edit a server
|
||||
class AddEditServerView extends StatefulWidget {
|
||||
const AddEditServerView();
|
||||
|
||||
@override
|
||||
_AddEditServerViewState createState() => _AddEditServerViewState();
|
||||
}
|
||||
|
||||
class _AddEditServerViewState extends State<AddEditServerView> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final ctrlrDesc = TextEditingController(text: "");
|
||||
final ctrlrOldPass = TextEditingController(text: "");
|
||||
final ctrlrPass = TextEditingController(text: "");
|
||||
final ctrlrPass2 = TextEditingController(text: "");
|
||||
final ctrlrOnion = TextEditingController(text: "");
|
||||
|
||||
late bool usePassword;
|
||||
//late bool deleted;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var serverInfoState = Provider.of<ServerInfoState>(context, listen: false);
|
||||
ctrlrOnion.text = serverInfoState.onion;
|
||||
usePassword = serverInfoState.isEncrypted;
|
||||
if (serverInfoState.description.isNotEmpty) {
|
||||
ctrlrDesc.text = serverInfoState.description;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: ctrlrOnion.text.isEmpty ? Text(AppLocalizations.of(context)!.addServerTitle) : Text(AppLocalizations.of(context)!.editServerTitle),
|
||||
),
|
||||
body: _buildSettingsList(),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleSwitchPassword(bool? value) {
|
||||
setState(() {
|
||||
usePassword = value!;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildSettingsList() {
|
||||
return Consumer2<ServerInfoState, Settings>(builder: (context, serverInfoState, settings, child) {
|
||||
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
|
||||
return Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
child: SingleChildScrollView(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
margin: EdgeInsets.fromLTRB(30, 0, 30, 10),
|
||||
padding: EdgeInsets.fromLTRB(20, 0 , 20, 10),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
|
||||
// Onion
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.serverAddress),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
SelectableText(
|
||||
serverInfoState.onion
|
||||
)
|
||||
])),
|
||||
|
||||
// Description
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
|
||||
Text(AppLocalizations.of(context)!.serverDescriptionDescription),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrDesc,
|
||||
labelText: "Description",
|
||||
autofocus: false,
|
||||
)
|
||||
]),
|
||||
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
||||
// Enabled
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription),
|
||||
value: serverInfoState.running,
|
||||
onChanged: (bool value) {
|
||||
serverInfoState.setRunning(value);
|
||||
if (value) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.StopServer(serverInfoState.onion);
|
||||
}
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor()),
|
||||
)),
|
||||
|
||||
// Auto start
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription),
|
||||
value: serverInfoState.autoStart,
|
||||
onChanged: (bool value) {
|
||||
serverInfoState.setAutostart(value);
|
||||
|
||||
if (! serverInfoState.onion.isEmpty) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
|
||||
}
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor()),
|
||||
),
|
||||
|
||||
|
||||
// ***** Password *****
|
||||
|
||||
// use password toggle
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Checkbox(
|
||||
value: usePassword,
|
||||
fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor()),
|
||||
activeColor: settings.current().defaultButtonActiveColor(),
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.radioUsePassword,
|
||||
style: TextStyle(color: settings.current().mainTextColor()),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription,
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
])),
|
||||
|
||||
|
||||
// current password
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrOldPass,
|
||||
autoFillHints: [AutofillHints.newPassword],
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.isEncrypted &&
|
||||
serverInfoState.onion.isEmpty &&
|
||||
value.isEmpty &&
|
||||
usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (Provider.of<ErrorHandler>(context).deletedServerError == true) {
|
||||
return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
])),
|
||||
|
||||
// new passwords 1 & 2
|
||||
Visibility(
|
||||
// Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check
|
||||
visible: serverInfoState.onion.isEmpty && usePassword,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass,
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass2.value.text) {
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass2,
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass.value.text) {
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed,
|
||||
child: Text(
|
||||
serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
},
|
||||
icon: Icon(Icons.delete_forever),
|
||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||
))
|
||||
]))
|
||||
|
||||
// ***** END Password *****
|
||||
|
||||
]))))));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _createPressed() {
|
||||
// This will run all the validations in the form including
|
||||
// checking that display name is not empty, and an actual check that the passwords
|
||||
// match (and are provided if the user has requested an encrypted profile).
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (usePassword) {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
|
||||
} else {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
void _savePressed() {
|
||||
|
||||
var server = Provider.of<ServerInfoState>(context, listen: false);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
|
||||
server.setDescription(ctrlrDesc.text);
|
||||
|
||||
|
||||
if (_formKey.currentState!.validate()) {
|
||||
// TODO support change password
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
showAlertDialog(BuildContext context) {
|
||||
// set up the buttons
|
||||
Widget cancelButton = ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
|
||||
onPressed: () {
|
||||
var onion = Provider
|
||||
.of<ServerInfoState>(context, listen: false)
|
||||
.onion;
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.DeleteServer(onion, Provider.of<ServerInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
if (globalErrorHandler.deletedServerSuccess) {
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteServerSuccess + ":" + onion));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
Navigator.of(context).popUntil((route) => route.settings.name == "servers"); // dismiss dialog
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
|
||||
actions: [
|
||||
cancelButton,
|
||||
continueButton,
|
||||
],
|
||||
);
|
||||
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import 'package:cwtch/views/torstatusview.dart';
|
|||
import 'package:cwtch/widgets/contactrow.dart';
|
||||
import 'package:cwtch/widgets/profileimage.dart';
|
||||
import 'package:cwtch/widgets/textfield.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cwtch/widgets/tor_icon.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../main.dart';
|
||||
import '../settings.dart';
|
||||
|
@ -12,8 +12,6 @@ import 'addcontactview.dart';
|
|||
import '../model.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'messageview.dart';
|
||||
|
||||
class ContactsView extends StatefulWidget {
|
||||
const ContactsView({Key? key}) : super(key: key);
|
||||
|
||||
|
@ -21,41 +19,6 @@ class ContactsView extends StatefulWidget {
|
|||
_ContactsViewState createState() => _ContactsViewState();
|
||||
}
|
||||
|
||||
// selectConversation can be called from anywhere to set the active conversation
|
||||
void selectConversation(BuildContext context, String handle) {
|
||||
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||
var initialIndex = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages;
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0;
|
||||
// triggers update in Double/TripleColumnView
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = initialIndex;
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = handle;
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||
Provider.of<AppState>(context, listen: false).hoveredIndex = -1;
|
||||
// if in singlepane mode, push to the stack
|
||||
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle);
|
||||
}
|
||||
|
||||
void _pushMessageView(BuildContext context, String handle) {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext builderContext) {
|
||||
// assert we have an actual profile...
|
||||
// We need to listen for updates to the profile in order to update things like invitation message bubbles.
|
||||
var profile = Provider.of<FlwtchState>(builderContext).profs.getProfile(profileOnion)!;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: profile),
|
||||
ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!),
|
||||
],
|
||||
builder: (context, child) => MessageView(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _ContactsViewState extends State<ContactsView> {
|
||||
late TextEditingController ctrlrFilter;
|
||||
bool showSearchBar = false;
|
||||
|
@ -88,7 +51,18 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
child: Text("%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts),
|
||||
overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor()))),
|
||||
])),
|
||||
actions: getActions(context),
|
||||
actions: [
|
||||
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
|
||||
IconButton(
|
||||
// need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
|
||||
icon: Icon(showSearchBar || Provider.of<ContactListState>(context).isFiltered ? Icons.search_off : Icons.search),
|
||||
onPressed: () {
|
||||
Provider.of<ContactListState>(context, listen: false).filter = "";
|
||||
setState(() {
|
||||
showSearchBar = !showSearchBar;
|
||||
});
|
||||
})
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddContact,
|
||||
|
@ -98,36 +72,6 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
body: showSearchBar || Provider.of<ContactListState>(context).isFiltered ? _buildFilterable() : _buildContactList());
|
||||
}
|
||||
|
||||
List<Widget> getActions(context) {
|
||||
var actions = List<Widget>.empty(growable: true);
|
||||
if (Provider.of<Settings>(context).blockUnknownConnections) {
|
||||
actions.add(Tooltip(message: AppLocalizations.of(context)!.blockUnknownConnectionsEnabledDescription, child: Icon(CwtchIcons.block_unknown)));
|
||||
}
|
||||
|
||||
// Copy profile onion
|
||||
actions.add(IconButton(
|
||||
icon: Icon(CwtchIcons.address_copy_2),
|
||||
tooltip: AppLocalizations.of(context)!.copyAddress,
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
|
||||
}));
|
||||
|
||||
|
||||
// TODO servers
|
||||
|
||||
// Search contacts
|
||||
actions.add(IconButton(
|
||||
// need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
|
||||
icon: Icon(showSearchBar || Provider.of<ContactListState>(context).isFiltered ? Icons.search_off : Icons.search),
|
||||
onPressed: () {
|
||||
Provider.of<ContactListState>(context, listen: false).filter = "";
|
||||
setState(() {
|
||||
showSearchBar = !showSearchBar;
|
||||
});
|
||||
}));
|
||||
return actions;
|
||||
}
|
||||
|
||||
Widget _buildFilterable() {
|
||||
Widget txtfield = CwtchTextField(
|
||||
controller: ctrlrFilter,
|
||||
|
|
|
@ -33,9 +33,8 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
|
|||
: //dev
|
||||
MultiProvider(providers: [
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
|
||||
ChangeNotifierProvider.value(
|
||||
value: flwtch.selectedConversation != null ? Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation!)! : ContactInfoState("", "")),
|
||||
], child: Container(key: Key(flwtch.selectedConversation??"never_this"), child: MessageView())),
|
||||
ChangeNotifierProvider.value(value: flwtch.selectedConversation != null ? Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation!)! : ContactInfoState("","")),
|
||||
], child: Container(child: MessageView())),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/settings.dart';
|
||||
|
@ -36,7 +34,6 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
Widget _buildSettingsList() {
|
||||
return Consumer<Settings>(builder: (context, settings, child) {
|
||||
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
|
||||
var appIcon = Icon(Icons.info, color: settings.current().mainTextColor());
|
||||
return Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
child: SingleChildScrollView(
|
||||
|
@ -96,31 +93,20 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
);
|
||||
}).toList())),
|
||||
ListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.settingUIColumnLandscape,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
softWrap: true,
|
||||
style: TextStyle(color: settings.current().mainTextColor()),
|
||||
),
|
||||
title: Text(AppLocalizations.of(context)!.settingUIColumnLandscape, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
||||
trailing: Container(
|
||||
width: 200.0,
|
||||
child: DropdownButton(
|
||||
isExpanded: true,
|
||||
value: settings.uiColumnModeLandscape.toString(),
|
||||
onChanged: (String? newValue) {
|
||||
settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!);
|
||||
saveSettings(context);
|
||||
},
|
||||
items: Settings.uiColumnModeOptions(true).map<DropdownMenuItem<String>>((DualpaneMode value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value.toString(),
|
||||
child: Text(
|
||||
Settings.uiColumnModeToString(value, context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}).toList()))),
|
||||
trailing: DropdownButton(
|
||||
value: settings.uiColumnModeLandscape.toString(),
|
||||
onChanged: (String? newValue) {
|
||||
settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!);
|
||||
saveSettings(context);
|
||||
},
|
||||
items: Settings.uiColumnModeOptions(true).map<DropdownMenuItem<String>>((DualpaneMode value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value.toString(),
|
||||
child: Text(Settings.uiColumnModeToString(value, context)),
|
||||
);
|
||||
}).toList())),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.blockUnknownLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionBlockUnknownConnections),
|
||||
|
@ -139,19 +125,6 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.block_unknown, color: settings.current().mainTextColor()),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.streamerModeLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionStreamerMode),
|
||||
value: settings.streamerMode,
|
||||
onChanged: (bool value) {
|
||||
settings.setStreamerMode(value);
|
||||
// Save Settings...
|
||||
saveSettings(context);
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.streamer_bunnymask, color: settings.current().mainTextColor()),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionExperiments),
|
||||
|
@ -190,73 +163,15 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()),
|
||||
),
|
||||
Visibility(
|
||||
visible: !Platform.isAndroid && !Platform.isIOS,
|
||||
child:
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.settingServers, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.settingServersDescription),
|
||||
value: settings.isExperimentEnabled(ServerManagementExperiment),
|
||||
onChanged: (bool value) {
|
||||
Provider.of<ServerListState>(context, listen: false).clear();
|
||||
if (value) {
|
||||
settings.enableExperiment(ServerManagementExperiment);
|
||||
} else {
|
||||
settings.disableExperiment(ServerManagementExperiment);
|
||||
}
|
||||
// Save Settings...
|
||||
saveSettings(context);
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.dns_24px, color: settings.current().mainTextColor()),
|
||||
)),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.settingFileSharing, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionFileSharing),
|
||||
value: settings.isExperimentEnabled(FileSharingExperiment),
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
settings.enableExperiment(FileSharingExperiment);
|
||||
} else {
|
||||
settings.disableExperiment(FileSharingExperiment);
|
||||
}
|
||||
saveSettings(context);
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor()),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text("Enable Clickable Links", style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text("The clickable links experiment allows you to click on URLs shared in messages."),
|
||||
value: settings.isExperimentEnabled(ClickableLinksExperiment),
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
settings.enableExperiment(ClickableLinksExperiment);
|
||||
} else {
|
||||
settings.disableExperiment(ClickableLinksExperiment);
|
||||
}
|
||||
saveSettings(context);
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(Icons.link, color: settings.current().mainTextColor()),
|
||||
),
|
||||
],
|
||||
)),
|
||||
AboutListTile(
|
||||
icon: appIcon,
|
||||
applicationIcon: Padding(padding: EdgeInsets.all(5), child: Icon(CwtchIcons.cwtch_knott)),
|
||||
applicationName: "Cwtch (Flutter UI)",
|
||||
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
|
||||
aboutBoxChildren: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
24.0 + 10.0 + (appIcon.size ?? 24.0), 16.0, 0.0, 0.0), // About has 24 padding (ln 389) and there appears to be another 10 of padding in the widget
|
||||
child: SelectableText(AppLocalizations.of(context)!.versionBuilddate.replaceAll("%1", EnvironmentConfig.BUILD_VER).replaceAll("%2", EnvironmentConfig.BUILD_DATE)),
|
||||
)
|
||||
]),
|
||||
icon: Icon(Icons.info, color: settings.current().mainTextColor()),
|
||||
applicationIcon: Padding(padding: EdgeInsets.all(5), child: Icon(CwtchIcons.cwtch_knott)),
|
||||
applicationName: "Cwtch (Flutter UI)",
|
||||
applicationVersion: AppLocalizations.of(context)!.versionBuilddate.replaceAll("%1", EnvironmentConfig.BUILD_VER).replaceAll("%2", EnvironmentConfig.BUILD_DATE),
|
||||
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
|
||||
),
|
||||
]))));
|
||||
});
|
||||
});
|
||||
|
@ -295,9 +210,6 @@ String getLanguageFull(context, String languageCode) {
|
|||
if (languageCode == "pl") {
|
||||
return AppLocalizations.of(context)!.localePl;
|
||||
}
|
||||
if (languageCode == "ru") {
|
||||
return AppLocalizations.of(context)!.localeRU;
|
||||
}
|
||||
return languageCode;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,40 +136,14 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
height: 20,
|
||||
),
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.archiveConversation,
|
||||
message: AppLocalizations.of(context)!.leaveGroup,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
// locally update cache...
|
||||
Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle);
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = null;
|
||||
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
|
||||
});
|
||||
showAlertDialog(context);
|
||||
},
|
||||
icon: Icon(CwtchIcons.leave_chat),
|
||||
label: Text(AppLocalizations.of(context)!.archiveConversation),
|
||||
)),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.leaveGroup,
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
},
|
||||
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.transparent)),
|
||||
icon: Icon(CwtchIcons.leave_group),
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!.leaveGroup,
|
||||
style: TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
))
|
||||
])
|
||||
icon: Icon(CwtchIcons.leave_group),
|
||||
label: Text(AppLocalizations.of(context)!.leaveGroup),
|
||||
))
|
||||
])
|
||||
])))));
|
||||
});
|
||||
|
@ -184,23 +158,21 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
|
||||
showAlertDialog(BuildContext context) {
|
||||
// set up the buttons
|
||||
Widget cancelButton = ElevatedButton(
|
||||
Widget cancelButton = TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = ElevatedButton(
|
||||
Widget continueButton = TextButton(
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
child: Text(AppLocalizations.of(context)!.yesLeave),
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
// locally update cache...
|
||||
Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, handle);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LeaveGroup(profileOnion, handle);
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = null;
|
||||
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
|
||||
});
|
||||
},
|
||||
|
|
|
@ -3,12 +3,9 @@ import 'dart:io';
|
|||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/messages/quotedmessage.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||
import 'package:cwtch/widgets/profileimage.dart';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/views/peersettingsview.dart';
|
||||
|
@ -16,8 +13,6 @@ import 'package:cwtch/widgets/DropdownContacts.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:path/path.dart' show basename;
|
||||
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
|
@ -34,34 +29,14 @@ class _MessageViewState extends State<MessageView> {
|
|||
final ctrlrCompose = TextEditingController();
|
||||
final focusNode = FocusNode();
|
||||
String selectedContact = "";
|
||||
ItemPositionsListener scrollListener = ItemPositionsListener.create();
|
||||
ItemScrollController scrollController = ItemScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scrollListener.itemPositions.addListener(() {
|
||||
if (scrollListener.itemPositions.value.length != 0 &&
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true &&
|
||||
scrollListener.itemPositions.value.any((element) => element.index == 0)) {
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
var appState = Provider.of<AppState>(context, listen: false);
|
||||
|
||||
// using "8" because "# of messages that fit on one screen" isnt trivial to calculate at this point
|
||||
if (appState.initialScrollIndex > 4 && appState.unreadMessagesBelow == false) {
|
||||
WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((timeStamp) {
|
||||
appState.unreadMessagesBelow = true;
|
||||
});
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
// @override
|
||||
// void didChangeDependencies() {
|
||||
// super.didChangeDependencies();
|
||||
// if (Provider.of<ContactInfoState>(context, listen: false).unreadMessages > 0) {
|
||||
// Provider.of<ContactInfoState>(context, listen: false).unreadMessages = 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
@ -72,46 +47,16 @@ class _MessageViewState extends State<MessageView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
// After leaving a conversation the selected conversation is set to null...
|
||||
if (Provider.of<ContactInfoState>(context).profileOnion == "") {
|
||||
return Card(child: Center(child: Text(AppLocalizations.of(context)!.addContactFirst)));
|
||||
}
|
||||
|
||||
var showFileSharing = Provider.of<Settings>(context).isExperimentEnabled(FileSharingExperiment);
|
||||
var appBarButtons = <Widget>[];
|
||||
if (Provider.of<ContactInfoState>(context).isOnline()) {
|
||||
if (showFileSharing) {
|
||||
appBarButtons.add(IconButton(
|
||||
icon: Icon(Icons.attach_file, size: 24),
|
||||
tooltip: AppLocalizations.of(context)!.tooltipSendFile,
|
||||
onPressed: _showFilePicker,
|
||||
));
|
||||
}
|
||||
appBarButtons.add(IconButton(
|
||||
icon: Icon(CwtchIcons.send_invite, size: 24),
|
||||
tooltip: AppLocalizations.of(context)!.sendInvite,
|
||||
onPressed: () {
|
||||
_modalSendInvitation(context);
|
||||
}));
|
||||
}
|
||||
appBarButtons.add(IconButton(
|
||||
icon: Provider.of<ContactInfoState>(context, listen: false).isGroup == true ? Icon(CwtchIcons.group_settings_24px) : Icon(CwtchIcons.peer_settings_24px),
|
||||
tooltip: AppLocalizations.of(context)!.conversationSettings,
|
||||
onPressed: _pushContactSettings));
|
||||
|
||||
var appState = Provider.of<AppState>(context);
|
||||
return WillPopScope(
|
||||
onWillPop: _onWillPop,
|
||||
child: Scaffold(
|
||||
floatingActionButton: appState.unreadMessagesBelow
|
||||
? FloatingActionButton(
|
||||
child: Icon(Icons.arrow_downward),
|
||||
onPressed: () {
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
|
||||
scrollController.scrollTo(index: 0, duration: Duration(milliseconds: 600));
|
||||
})
|
||||
: null,
|
||||
appBar: AppBar(
|
||||
// setting leading to null makes it do the default behaviour; container() hides it
|
||||
leading: Provider.of<Settings>(context).uiColumns(appState.isLandscape(context)).length > 1 ? Container() : null,
|
||||
|
@ -126,22 +71,26 @@ class _MessageViewState extends State<MessageView> {
|
|||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
Provider.of<ContactInfoState>(context).nickname,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
Expanded(
|
||||
child:Text(Provider.of<ContactInfoState>(context).nickname, overflow: TextOverflow.ellipsis,))
|
||||
]),
|
||||
actions: appBarButtons,
|
||||
actions: [
|
||||
//IconButton(icon: Icon(Icons.chat), onPressed: _pushContactSettings),
|
||||
//IconButton(icon: Icon(Icons.list), onPressed: _pushContactSettings),
|
||||
//IconButton(icon: Icon(Icons.push_pin), onPressed: _pushContactSettings),
|
||||
IconButton(
|
||||
icon: Provider.of<ContactInfoState>(context, listen: false).isGroup == true ? Icon(CwtchIcons.group_settings_24px) : Icon(CwtchIcons.peer_settings_24px),
|
||||
tooltip: AppLocalizations.of(context)!.conversationSettings,
|
||||
onPressed: _pushContactSettings),
|
||||
],
|
||||
),
|
||||
body: Padding(padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0), child: MessageList(scrollController, scrollListener)),
|
||||
body: Padding(padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0), child: MessageList()),
|
||||
bottomSheet: _buildComposeBox(),
|
||||
));
|
||||
}
|
||||
|
||||
Future<bool> _onWillPop() async {
|
||||
Provider.of<ContactInfoState>(context, listen: false).unreadMessages = 0;
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -176,7 +125,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
var bytes1 = utf8.encode(messageWrapper["PeerID"] + messageWrapper['Message']);
|
||||
var digest1 = sha256.convert(bytes1);
|
||||
var contentHash = base64Encode(digest1.bytes);
|
||||
var quotedMessage = jsonEncode(QuotedMessageStructure(contentHash, ctrlrCompose.value.text));
|
||||
var quotedMessage = "{\"quotedHash\":\"" + contentHash + "\",\"body\":\"" + ctrlrCompose.value.text + "\"}";
|
||||
ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage);
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
|
@ -202,27 +151,17 @@ class _MessageViewState extends State<MessageView> {
|
|||
_sendMessageHelper();
|
||||
}
|
||||
|
||||
void _sendFile(String filePath) {
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.ShareFile(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, filePath);
|
||||
_sendMessageHelper();
|
||||
}
|
||||
|
||||
void _sendMessageHelper() {
|
||||
ctrlrCompose.clear();
|
||||
focusNode.requestFocus();
|
||||
Future.delayed(const Duration(milliseconds: 80), () {
|
||||
Provider.of<ContactInfoState>(context, listen: false).totalMessages++;
|
||||
Provider.of<ContactInfoState>(context, listen: false).newMarker++;
|
||||
// Resort the contact list...
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.updateLastMessageTime(Provider.of<ContactInfoState>(context, listen: false).onion, DateTime.now());
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildComposeBox() {
|
||||
bool isOffline = Provider.of<ContactInfoState>(context).isOnline() == false;
|
||||
|
||||
var composeBox = Container(
|
||||
color: Provider.of<Settings>(context).theme.backgroundMainColor(),
|
||||
padding: EdgeInsets.all(2),
|
||||
|
@ -231,36 +170,39 @@ class _MessageViewState extends State<MessageView> {
|
|||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor()))),
|
||||
child: RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey: handleKeyPress,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: TextFormField(
|
||||
key: Key('txtCompose'),
|
||||
controller: ctrlrCompose,
|
||||
focusNode: focusNode,
|
||||
autofocus: !Platform.isAndroid,
|
||||
textInputAction: TextInputAction.newline,
|
||||
keyboardType: TextInputType.multiline,
|
||||
enableIMEPersonalizedLearning: false,
|
||||
minLines: 1,
|
||||
maxLines: null,
|
||||
onFieldSubmitted: _sendMessage,
|
||||
enabled: !isOffline,
|
||||
decoration: InputDecoration(
|
||||
hintText: isOffline ? "" : AppLocalizations.of(context)!.placeholderEnterMessage,
|
||||
hintStyle: TextStyle(color: Provider.of<Settings>(context).theme.altTextColor()),
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabled: true,
|
||||
suffixIcon: ElevatedButton(
|
||||
child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.defaultButtonTextColor()),
|
||||
onPressed: isOffline ? null : _sendMessage,
|
||||
))),
|
||||
)))),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor()))),
|
||||
child: RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey: handleKeyPress,
|
||||
child: TextFormField(
|
||||
key: Key('txtCompose'),
|
||||
controller: ctrlrCompose,
|
||||
focusNode: focusNode,
|
||||
autofocus: !Platform.isAndroid,
|
||||
textInputAction: TextInputAction.newline,
|
||||
keyboardType: TextInputType.multiline,
|
||||
minLines: 1,
|
||||
maxLines: null,
|
||||
onFieldSubmitted: _sendMessage,
|
||||
decoration: InputDecoration(
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabled: true,
|
||||
prefixIcon: IconButton(
|
||||
icon: Icon(CwtchIcons.send_invite, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
tooltip: AppLocalizations.of(context)!.sendInvite,
|
||||
enableFeedback: true,
|
||||
splashColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||
hoverColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||
onPressed: () => _modalSendInvitation(context)),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
tooltip: AppLocalizations.of(context)!.sendMessage,
|
||||
onPressed: _sendMessage,
|
||||
),
|
||||
)))),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -278,27 +220,18 @@ class _MessageViewState extends State<MessageView> {
|
|||
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
|
||||
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()
|
||||
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Stack(children: [
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.highlight_remove),
|
||||
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
|
||||
onPressed: () {
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||
},
|
||||
)),
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(padding: EdgeInsets.all(2.0), child: Icon(Icons.reply)),
|
||||
)
|
||||
]),
|
||||
Wrap(
|
||||
runAlignment: WrapAlignment.spaceEvenly,
|
||||
alignment: WrapAlignment.center,
|
||||
runSpacing: 1.0,
|
||||
children: [Center(widthFactor: 1.0, child: Padding(padding: EdgeInsets.all(10.0), child: message.getPreviewWidget(context)))]),
|
||||
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32))),
|
||||
Center(widthFactor: 1.0, child: message.getPreviewWidget(context)),
|
||||
Center(
|
||||
widthFactor: 1.0,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.highlight_remove),
|
||||
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
|
||||
onPressed: () {
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||
},
|
||||
))
|
||||
]));
|
||||
} else {
|
||||
return MessageLoadingBubble();
|
||||
|
@ -372,20 +305,4 @@ class _MessageViewState extends State<MessageView> {
|
|||
));
|
||||
});
|
||||
}
|
||||
|
||||
void _showFilePicker() async {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles();
|
||||
if (result != null) {
|
||||
File file = File(result.files.first.path);
|
||||
// We have a maximum number of bytes we can represent in terms of
|
||||
// a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25)
|
||||
if (file.lengthSync() <= 10737418240) {
|
||||
print("Sending " + file.path);
|
||||
_sendFile(file.path);
|
||||
} else {
|
||||
print("file size cannot exceed 10 gigabytes");
|
||||
//todo: toast error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,11 +114,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
value: Provider.of<ContactInfoState>(context).isBlocked,
|
||||
onChanged: (bool blocked) {
|
||||
// Save local blocked status
|
||||
if (blocked) {
|
||||
Provider.of<ContactInfoState>(context, listen: false).authorization = ContactAuthorization.blocked;
|
||||
} else {
|
||||
Provider.of<ContactInfoState>(context, listen: false).authorization = ContactAuthorization.unknown;
|
||||
}
|
||||
Provider.of<ContactInfoState>(context, listen: false).isBlocked = blocked;
|
||||
|
||||
// Save New peer authorization
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
|
@ -196,21 +192,13 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
),
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.archiveConversation,
|
||||
message: AppLocalizations.of(context)!.leaveGroup,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
// locally update cache...
|
||||
Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle);
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = null;
|
||||
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
|
||||
});
|
||||
showAlertDialog(context);
|
||||
},
|
||||
icon: Icon(CwtchIcons.leave_chat),
|
||||
label: Text(AppLocalizations.of(context)!.archiveConversation),
|
||||
label: Text(AppLocalizations.of(context)!.leaveGroup),
|
||||
))
|
||||
])
|
||||
]),
|
||||
|
@ -228,23 +216,20 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
showAlertDialog(BuildContext context) {
|
||||
// set up the buttons
|
||||
Widget cancelButton = TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
child: Text("Cancel"),
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = ElevatedButton(
|
||||
Widget continueButton = TextButton(
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
child: Text(AppLocalizations.of(context)!.yesLeave),
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
// locally update cache...
|
||||
Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, handle);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LeaveConversation(profileOnion, handle);
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = null;
|
||||
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
|
||||
});
|
||||
},
|
||||
|
|
|
@ -17,7 +17,6 @@ import '../model.dart';
|
|||
import '../torstatus.dart';
|
||||
import 'addeditprofileview.dart';
|
||||
import 'globalsettingsview.dart';
|
||||
import 'serversview.dart';
|
||||
|
||||
class ProfileMgrView extends StatefulWidget {
|
||||
ProfileMgrView();
|
||||
|
@ -40,6 +39,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
return Consumer<Settings>(
|
||||
// Prevents Android back button from closing the app on the profile manager screen
|
||||
// (which would shutdown connections and all kinds of other expensive to generate things)
|
||||
// TODO pop up a dialogue regarding closing the app?
|
||||
builder: (context, settings, child) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
_modalShutdown();
|
||||
|
@ -49,22 +49,24 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
backgroundColor: settings.theme.backgroundMainColor(),
|
||||
appBar: AppBar(
|
||||
title: Row(children: [
|
||||
Icon(
|
||||
CwtchIcons.cwtch_knott,
|
||||
size: 36,
|
||||
color: settings.theme.mainTextColor(),
|
||||
Image(
|
||||
image: AssetImage("assets/core/knott-white.png"),
|
||||
filterQuality: FilterQuality.medium,
|
||||
isAntiAlias: true,
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorBlendMode: BlendMode.dstIn,
|
||||
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(child: Text(MediaQuery.of(context).size.width > 600 ?
|
||||
AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort,
|
||||
style: TextStyle(color: settings.current().mainTextColor())))
|
||||
Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor())))
|
||||
]),
|
||||
actions: getActions(),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddProfile,
|
||||
onPressed: _pushAddEditProfile,
|
||||
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
|
@ -93,16 +95,10 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
// Unlock Profiles
|
||||
actions.add(IconButton(
|
||||
icon: Icon(CwtchIcons.lock_open_24px),
|
||||
color: Provider.of<ProfileListState>(context).profiles.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor() : Provider.of<Settings>(context).theme.mainTextColor(),
|
||||
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
|
||||
onPressed: _modalUnlockProfiles,
|
||||
));
|
||||
|
||||
// Servers
|
||||
if (Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) && !Platform.isAndroid && !Platform.isIOS) {
|
||||
actions.add(IconButton(icon: Icon(CwtchIcons.dns_black_24dp), tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
|
||||
}
|
||||
|
||||
// Global Settings
|
||||
actions.add(IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings));
|
||||
|
||||
|
@ -127,18 +123,6 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
));
|
||||
}
|
||||
|
||||
void _pushServers() {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
settings: RouteSettings(name: "servers"),
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
|
||||
child: ServersView(),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
void _pushTorStatus() {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
|
@ -150,7 +134,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
));
|
||||
}
|
||||
|
||||
void _pushAddProfile({onion: ""}) {
|
||||
void _pushAddEditProfile({onion: ""}) {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
|
@ -236,9 +220,9 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
).toList();
|
||||
|
||||
if (tiles.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.unlockProfileTip,
|
||||
return const Center(
|
||||
child: const Text(
|
||||
"Please create or unlock a profile to begin!",
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/views/addeditservers.dart';
|
||||
import 'package:cwtch/widgets/passwordfield.dart';
|
||||
import 'package:cwtch/widgets/serverrow.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/torstatus.dart';
|
||||
import 'package:cwtch/widgets/tor_icon.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../cwtch_icons_icons.dart';
|
||||
import '../main.dart';
|
||||
import '../settings.dart';
|
||||
|
||||
///
|
||||
class ServersView extends StatefulWidget {
|
||||
@override
|
||||
_ServersView createState() => _ServersView();
|
||||
}
|
||||
|
||||
class _ServersView extends State<ServersView> {
|
||||
final ctrlrPassword = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ctrlrPassword.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text( MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.serversManagerTitleLong : AppLocalizations.of(context)!.serversManagerTitleShort),
|
||||
actions: getActions(),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddServer,
|
||||
tooltip: AppLocalizations.of(context)!.addServerTooltip,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
semanticLabel: AppLocalizations.of(context)!.addServerTooltip,
|
||||
),
|
||||
),
|
||||
body: Consumer<ServerListState>(
|
||||
builder: (context, svrs, child) {
|
||||
final tiles = svrs.servers.map((ServerInfoState server) {
|
||||
return ChangeNotifierProvider<ServerInfoState>.value(
|
||||
value: server,
|
||||
builder: (context, child) => RepaintBoundary(child: ServerRow()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final divided = ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: tiles,
|
||||
).toList();
|
||||
|
||||
if (tiles.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.unlockServerTip,
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
||||
return ListView(children: divided);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
List<Widget> getActions() {
|
||||
List<Widget> actions = new List<Widget>.empty(growable: true);
|
||||
|
||||
// Unlock Profiles
|
||||
actions.add(IconButton(
|
||||
icon: Icon(CwtchIcons.lock_open_24px),
|
||||
color: Provider.of<ServerListState>(context).servers.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor() : Provider.of<Settings>(context).theme.mainTextColor(),
|
||||
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
|
||||
onPressed: _modalUnlockServers,
|
||||
));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
void _modalUnlockServers() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext context) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
height: 200, // bespoke value courtesy of the [TextField] docs
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(context)!.enterServerPassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
autofocus: true,
|
||||
controller: ctrlrPassword,
|
||||
action: unlock,
|
||||
validator: (value) {},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
|
||||
onPressed: () {
|
||||
unlock(ctrlrPassword.value.text);
|
||||
},
|
||||
)),
|
||||
Spacer()
|
||||
]),
|
||||
],
|
||||
))),
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
void unlock(String password) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadServers(password);
|
||||
ctrlrPassword.text = "";
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
void _pushAddServer() {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [ChangeNotifierProvider<ServerInfoState>(
|
||||
create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true),
|
||||
)],
|
||||
child: AddEditServerView(),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ class _TorStatusView extends State<TorStatusView> {
|
|||
),
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.torVersion),
|
||||
subtitle: SelectableText(torStatus.version),
|
||||
subtitle: Text(torStatus.version),
|
||||
),
|
||||
]))));
|
||||
});
|
||||
|
|
|
@ -37,7 +37,6 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
|
|||
readOnly: widget.readonly,
|
||||
showCursor: !widget.readonly,
|
||||
focusNode: _focusNode,
|
||||
enableIMEPersonalizedLearning: false,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: widget.onPressed,
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/views/contactsview.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:cwtch/views/messageview.dart';
|
||||
|
@ -25,11 +22,10 @@ class _ContactRowState extends State<ContactRow> {
|
|||
clipBehavior: Clip.antiAlias,
|
||||
color: Provider.of<AppState>(context).selectedConversation == contact.onion ? Provider.of<Settings>(context).theme.backgroundHilightElementColor() : null,
|
||||
borderOnForeground: false,
|
||||
margin: EdgeInsets.all(0.0),
|
||||
child: InkWell(
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0), //border size
|
||||
padding: const EdgeInsets.all(2.0), //border size
|
||||
child: ProfileImage(
|
||||
badgeCount: contact.unreadMessages,
|
||||
badgeColor: Provider.of<Settings>(context).theme.portraitContactBadgeColor(),
|
||||
|
@ -60,16 +56,8 @@ class _ContactRowState extends State<ContactRow> {
|
|||
softWrap: true,
|
||||
overflow: TextOverflow.visible,
|
||||
),
|
||||
Visibility(
|
||||
visible: contact.isGroup && contact.status == "Authenticated",
|
||||
child: LinearProgressIndicator(
|
||||
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||
)),
|
||||
Visibility(
|
||||
visible: !Provider.of<Settings>(context).streamerMode,
|
||||
child: Text(contact.onion,
|
||||
style: TextStyle(color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor() : Provider.of<Settings>(context).theme.mainTextColor())),
|
||||
)
|
||||
Text(contact.onion,
|
||||
style: TextStyle(color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor() : Provider.of<Settings>(context).theme.mainTextColor())),
|
||||
],
|
||||
))),
|
||||
Padding(
|
||||
|
@ -105,11 +93,40 @@ class _ContactRowState extends State<ContactRow> {
|
|||
),
|
||||
]),
|
||||
onTap: () {
|
||||
selectConversation(context, contact.onion);
|
||||
setState(() {
|
||||
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0;
|
||||
// triggers update in Double/TripleColumnView
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = contact.onion;
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||
// if in singlepane mode, push to the stack
|
||||
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion);
|
||||
});
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
void _pushMessageView(String handle) {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext builderContext) {
|
||||
// assert we have an actual profile...
|
||||
// We need to listen for updates to the profile in order to update things like invitation message bubbles.
|
||||
var profile = Provider.of<FlwtchState>(builderContext).profs.getProfile(profileOnion)!;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: profile),
|
||||
ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!),
|
||||
],
|
||||
builder: (context, child) => MessageView(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _btnApprove() {
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
|
@ -132,9 +149,9 @@ class _ContactRowState extends State<ContactRow> {
|
|||
}
|
||||
// If the last message was over a day ago, just state the date
|
||||
if (DateTime.now().difference(date).inDays > 1) {
|
||||
return DateFormat.yMd(Platform.localeName).format(date.toLocal());
|
||||
return DateFormat.yMd().format(date.toLocal());
|
||||
}
|
||||
// Otherwise just state the time.
|
||||
return DateFormat.Hm(Platform.localeName).format(date.toLocal());
|
||||
return DateFormat.Hm().format(date.toLocal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,292 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:file_picker_desktop/file_picker_desktop.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
import 'messagebubbledecorations.dart';
|
||||
|
||||
// Like MessageBubble but for displaying chat overlay 100/101 invitations
|
||||
// Offers the user an accept/reject button if they don't have a matching contact already
|
||||
class FileBubble extends StatefulWidget {
|
||||
final String nameSuggestion;
|
||||
final String rootHash;
|
||||
final String nonce;
|
||||
final int fileSize;
|
||||
final bool interactive;
|
||||
|
||||
FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.interactive = true});
|
||||
|
||||
@override
|
||||
FileBubbleState createState() => FileBubbleState();
|
||||
|
||||
String fileKey() {
|
||||
return this.rootHash + "." + this.nonce;
|
||||
}
|
||||
}
|
||||
|
||||
class FileBubbleState extends State<FileBubble> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||
var flagStarted = Provider.of<MessageMetadata>(context).flags & 0x02 > 0;
|
||||
var borderRadiousEh = 15.0;
|
||||
var showFileSharing = Provider.of<Settings>(context).isExperimentEnabled(FileSharingExperiment);
|
||||
var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
|
||||
|
||||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
if (!fromMe) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||
}
|
||||
}
|
||||
|
||||
var wdgSender = Center(
|
||||
widthFactor: 1,
|
||||
child: SelectableText(senderDisplayStr + '\u202F',
|
||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor())));
|
||||
|
||||
var wdgMessage = !showFileSharing
|
||||
? Text(AppLocalizations.of(context)!.messageEnableFileSharing)
|
||||
: fromMe
|
||||
? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize)
|
||||
: (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize,
|
||||
Provider.of<ProfileInfoState>(context).downloadSpeed(widget.fileKey())));
|
||||
Widget wdgDecorations;
|
||||
if (!showFileSharing) {
|
||||
wdgDecorations = Text('\u202F');
|
||||
} else if (fromMe) {
|
||||
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||
} else if (Provider.of<ProfileInfoState>(context).downloadComplete(widget.fileKey())) {
|
||||
// in this case, whatever marked download.complete would have also set the path
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey())!;
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F');
|
||||
} else if (Provider.of<ProfileInfoState>(context).downloadActive(widget.fileKey())) {
|
||||
if (!Provider.of<ProfileInfoState>(context).downloadGotManifest(widget.fileKey())) {
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F');
|
||||
} else {
|
||||
wdgDecorations = LinearProgressIndicator(
|
||||
value: Provider.of<ProfileInfoState>(context).downloadProgress(widget.fileKey()),
|
||||
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||
);
|
||||
}
|
||||
} else if (flagStarted) {
|
||||
// in this case, the download was done in a previous application launch,
|
||||
// so we probably have to request an info lookup
|
||||
if (!Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey()) ) {
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F');
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.fileKey());
|
||||
} else {
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
|
||||
wdgDecorations = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:[Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
wdgDecorations = Center(
|
||||
widthFactor: 1,
|
||||
child: Wrap(children: [
|
||||
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)),
|
||||
]));
|
||||
}
|
||||
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
||||
return Center(
|
||||
widthFactor: 1.0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
|
||||
border:
|
||||
Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(), width: 1),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(borderRadiousEh),
|
||||
topRight: Radius.circular(borderRadiousEh),
|
||||
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
|
||||
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
widthFactor: 1.0,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(9.0),
|
||||
child: Wrap(alignment: WrapAlignment.start, children: [
|
||||
Center(
|
||||
widthFactor: 1.0,
|
||||
child: Column(
|
||||
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: fromMe
|
||||
? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]
|
||||
: [wdgSender, wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]),
|
||||
)
|
||||
])))));
|
||||
});
|
||||
}
|
||||
|
||||
void _btnAccept() async {
|
||||
String? selectedFileName;
|
||||
File? file;
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
var handle = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
var idx = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageMetadata>(context, listen: false).flags | 0x02);
|
||||
Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateDownloadableFile(profileOnion, handle, widget.nameSuggestion, widget.fileKey());
|
||||
} else {
|
||||
try {
|
||||
selectedFileName = await saveFile(
|
||||
defaultFileName: widget.nameSuggestion,
|
||||
);
|
||||
if (selectedFileName != null) {
|
||||
file = File(selectedFileName);
|
||||
print("saving to " + file.path);
|
||||
var manifestPath = file.path + ".manifest";
|
||||
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageMetadata>(context, listen: false).flags | 0x02);
|
||||
Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DownloadFile(profileOnion, handle, file.path, manifestPath, widget.fileKey());
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _btnResume() async {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
var handle = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||
Provider.of<ProfileInfoState>(context, listen: false).downloadMarkResumed(widget.fileKey());
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.VerifyOrResumeDownload(profileOnion, handle, widget.fileKey());
|
||||
}
|
||||
|
||||
// Construct an file chrome for the sender
|
||||
Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) {
|
||||
return ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [
|
||||
SelectableText(
|
||||
chrome + '\u202F',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 2,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
),
|
||||
SelectableText(
|
||||
fileName + '\u202F',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||
fontWeight: FontWeight.bold,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
textWidthBasis: TextWidthBasis.parent,
|
||||
maxLines: 2,
|
||||
),
|
||||
SelectableText(
|
||||
prettyBytes(fileSize) + '\u202F' + '\n',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 2,
|
||||
)
|
||||
]),
|
||||
subtitle: SelectableText(
|
||||
'sha512: ' + rootHash + '\u202F',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||
fontSize: 10,
|
||||
fontFamily: "monospace",
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 4,
|
||||
textWidthBasis: TextWidthBasis.parent,
|
||||
),
|
||||
leading: Icon(Icons.attach_file, size: 32, color: Provider.of<Settings>(context).theme.messageFromMeTextColor()));
|
||||
}
|
||||
|
||||
// Construct an file chrome
|
||||
Widget fileChrome(String chrome, String fileName, String rootHash, int fileSize, String speed) {
|
||||
return ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [
|
||||
SelectableText(
|
||||
chrome + '\u202F',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 2,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
),
|
||||
SelectableText(
|
||||
fileName + '\u202F',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||
fontWeight: FontWeight.bold,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
textWidthBasis: TextWidthBasis.parent,
|
||||
maxLines: 2,
|
||||
),
|
||||
SelectableText(
|
||||
AppLocalizations.of(context)!.labelFilesize + ': ' + prettyBytes(fileSize) + '\u202F' + '\n',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 2,
|
||||
)
|
||||
]),
|
||||
subtitle: SelectableText(
|
||||
'sha512: ' + rootHash + '\u202F',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||
fontSize: 10,
|
||||
fontFamily: "monospace",
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 4,
|
||||
textWidthBasis: TextWidthBasis.parent,
|
||||
),
|
||||
leading: Icon(Icons.attach_file, size: 32, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
|
||||
trailing: Visibility(
|
||||
visible: speed != "0 B/s",
|
||||
child: SelectableText(
|
||||
speed + '\u202F',
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 1,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
|
@ -20,9 +19,8 @@ class InvitationBubble extends StatefulWidget {
|
|||
final int overlay;
|
||||
final String inviteTarget;
|
||||
final String inviteNick;
|
||||
final String invite;
|
||||
|
||||
InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick, this.invite);
|
||||
InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick);
|
||||
|
||||
@override
|
||||
InvitationBubbleState createState() => InvitationBubbleState();
|
||||
|
@ -40,7 +38,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
var borderRadiousEh = 15.0;
|
||||
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
|
||||
rejected = Provider.of<MessageMetadata>(context).flags & 0x01 == 0x01;
|
||||
var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
|
||||
var prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
|
||||
|
||||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
|
@ -85,8 +83,8 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
wdgDecorations = Center(
|
||||
widthFactor: 1,
|
||||
child: Wrap(children: [
|
||||
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F'), onPressed: _btnReject)),
|
||||
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F'), onPressed: _btnAccept)),
|
||||
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F'), onPressed: _btnReject)),
|
||||
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F'), onPressed: _btnAccept)),
|
||||
]));
|
||||
}
|
||||
|
||||
|
@ -131,14 +129,14 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
var idx = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageMetadata>(context, listen: false).flags | 0x01);
|
||||
Provider.of<MessageMetadata>(context, listen: false).flags |= 0x01;
|
||||
Provider.of<MessageMetadata>(context).flags |= 0x01;
|
||||
});
|
||||
}
|
||||
|
||||
void _btnAccept() {
|
||||
setState(() {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, widget.invite);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, widget.inviteTarget);
|
||||
isAccepted = true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../model.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
import 'messagebubbledecorations.dart';
|
||||
|
@ -32,10 +26,9 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
var prettyDate = "";
|
||||
var borderRadiousEh = 15.0;
|
||||
// var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
||||
var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment);
|
||||
|
||||
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
||||
prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(messageDate.toLocal());
|
||||
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
||||
|
||||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
|
@ -50,40 +43,16 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
var wdgSender = SelectableText(senderDisplayStr,
|
||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
||||
|
||||
var wdgMessage;
|
||||
|
||||
if (!showClickableLinks) {
|
||||
wdgMessage = SelectableText(
|
||||
widget.content + '\u202F',
|
||||
//key: Key(myKey),
|
||||
focusNode: _focus,
|
||||
style: TextStyle(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
);
|
||||
} else {
|
||||
wdgMessage = SelectableLinkify(
|
||||
text: widget.content + '\u202F',
|
||||
// TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler?
|
||||
options: LinkifyOptions(humanize: false),
|
||||
linkifiers: [UrlLinkifier()],
|
||||
onOpen: (link) {
|
||||
_modalOpenLink(context, link);
|
||||
},
|
||||
//key: Key(myKey),
|
||||
focusNode: _focus,
|
||||
style: TextStyle(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: Provider.of<Settings>(context).current().mainTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
);
|
||||
}
|
||||
var wdgMessage = SelectableText(
|
||||
widget.content + '\u202F',
|
||||
//key: Key(myKey),
|
||||
focusNode: _focus,
|
||||
style: TextStyle(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
);
|
||||
|
||||
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||
|
||||
|
@ -119,57 +88,4 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))));
|
||||
});
|
||||
}
|
||||
|
||||
void _modalOpenLink(BuildContext ctx, LinkableElement link) {
|
||||
showModalBottomSheet<void>(
|
||||
context: ctx,
|
||||
builder: (BuildContext bcontext) {
|
||||
return Container(
|
||||
height: 200, // bespoke value courtesy of the [TextField] docs
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(30.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"Opening this link will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open links from people you trust. Are you sure you want to continue?"
|
||||
),
|
||||
Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||
child: ElevatedButton(
|
||||
child: Text("Copy link", semanticsLabel: "Copy link"),
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: link.url));
|
||||
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.copiedClipboardNotification),
|
||||
);
|
||||
|
||||
Navigator.pop(bcontext);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||
child: ElevatedButton(
|
||||
child: Text("Open link", semanticsLabel: "Open link"),
|
||||
onPressed: () async {
|
||||
if (await canLaunch(link.url)) {
|
||||
await launch(link.url);
|
||||
} else {
|
||||
throw 'Could not launch $link';
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
)),
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,5 +45,6 @@ class _MessageBubbleDecoration extends State<MessageBubbleDecoration> {
|
|||
child: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16))))
|
||||
],
|
||||
));
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/messages/malformedmessage.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../settings.dart';
|
||||
import 'messagerow.dart';
|
||||
|
||||
class MessageList extends StatefulWidget {
|
||||
ItemScrollController scrollController;
|
||||
ItemPositionsListener scrollListener;
|
||||
MessageList(this.scrollController, this.scrollListener);
|
||||
|
||||
@override
|
||||
_MessageListState createState() => _MessageListState();
|
||||
}
|
||||
|
||||
class _MessageListState extends State<MessageList> {
|
||||
ScrollController ctrlr1 = ScrollController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext outerContext) {
|
||||
var initi = Provider.of<AppState>(outerContext, listen: false).initialScrollIndex;
|
||||
bool isP2P = !Provider.of<ContactInfoState>(context).isGroup;
|
||||
bool isGroupAndSyncing = Provider.of<ContactInfoState>(context).isGroup == true && Provider.of<ContactInfoState>(context).status == "Authenticated";
|
||||
bool isGroupAndSynced = Provider.of<ContactInfoState>(context).isGroup && Provider.of<ContactInfoState>(context).status == "Synced";
|
||||
|
@ -39,61 +39,59 @@ class _MessageListState extends State<MessageList> {
|
|||
Visibility(
|
||||
visible: showMessageWarning,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(color: Provider.of<Settings>(context).theme.defaultButtonTextColor()),
|
||||
child: showSyncing
|
||||
? Text(AppLocalizations.of(context)!.serverNotSynced, textAlign: TextAlign.center)
|
||||
: showOfflineWarning
|
||||
? Text(Provider.of<ContactInfoState>(context).isGroup ? AppLocalizations.of(context)!.serverConnectivityDisconnected : AppLocalizations.of(context)!.peerOfflineMessage,
|
||||
textAlign: TextAlign.center)
|
||||
// Only show the ephemeral status for peer conversations, not for groups...
|
||||
: (showEphemeralWarning
|
||||
? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center)
|
||||
:
|
||||
// We are not allowed to put null here, so put an empty text widget
|
||||
Text("")),
|
||||
))),
|
||||
padding: EdgeInsets.all(5.0),
|
||||
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||
child: showSyncing
|
||||
? Text(AppLocalizations.of(context)!.serverNotSynced, textAlign: TextAlign.center)
|
||||
: showOfflineWarning
|
||||
? Text(Provider.of<ContactInfoState>(context).isGroup ? AppLocalizations.of(context)!.serverConnectivityDisconnected : AppLocalizations.of(context)!.peerOfflineMessage,
|
||||
textAlign: TextAlign.center)
|
||||
// Only show the ephemeral status for peer conversations, not for groups...
|
||||
: (showEphemeralWarning
|
||||
? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center)
|
||||
:
|
||||
// We are not allowed to put null here, so put an empty text widge
|
||||
Text("")),
|
||||
)),
|
||||
Expanded(
|
||||
child: Container(
|
||||
// Only show broken heart is the contact is offline...
|
||||
decoration: BoxDecoration(
|
||||
image: Provider.of<ContactInfoState>(outerContext).isOnline()
|
||||
? null
|
||||
: DecorationImage(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.center,
|
||||
image: AssetImage("assets/core/negative_heart_512px.png"),
|
||||
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementTextColor(), BlendMode.srcIn))),
|
||||
// Don't load messages for syncing server...
|
||||
child: loadMessages
|
||||
? ScrollablePositionedList.builder(
|
||||
itemPositionsListener: widget.scrollListener,
|
||||
itemScrollController: widget.scrollController,
|
||||
initialScrollIndex: initi > 4 ? initi - 4 : 0,
|
||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
||||
itemBuilder: (itemBuilderContext, index) {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(outerContext, listen: false).onion;
|
||||
var contactHandle = Provider.of<ContactInfoState>(outerContext, listen: false).onion;
|
||||
var messageIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
||||
child: Scrollbar(
|
||||
controller: ctrlr1,
|
||||
child: Container(
|
||||
// Only show broken heart is the contact is offline...
|
||||
decoration: BoxDecoration(
|
||||
image: Provider.of<ContactInfoState>(outerContext).isOnline()
|
||||
? null
|
||||
: DecorationImage(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.center,
|
||||
image: AssetImage("assets/core/negative_heart_512px.png"),
|
||||
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementTextColor(), BlendMode.srcIn))),
|
||||
// Don't load messages for syncing server...
|
||||
child: loadMessages
|
||||
? ListView.builder(
|
||||
controller: ctrlr1,
|
||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
||||
itemBuilder: (itemBuilderContext, index) {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(outerContext, listen: false).onion;
|
||||
var contactHandle = Provider.of<ContactInfoState>(outerContext, listen: false).onion;
|
||||
var messageIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
||||
|
||||
return FutureBuilder(
|
||||
future: messageHandler(outerContext, profileOnion, contactHandle, messageIndex),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var message = snapshot.data as Message;
|
||||
// Already includes MessageRow,,
|
||||
return message.getWidget(context);
|
||||
} else {
|
||||
return Text(''); //MessageLoadingBubble();
|
||||
}
|
||||
return FutureBuilder(
|
||||
future: messageHandler(outerContext, profileOnion, contactHandle, messageIndex),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var message = snapshot.data as Message;
|
||||
// Already includes MessageRow,,
|
||||
return message.getWidget(context);
|
||||
} else {
|
||||
return MessageLoadingBubble();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: null))
|
||||
)
|
||||
: null)))
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/views/contactsview.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/widgets/profileimage.dart';
|
||||
import 'package:flutter/physics.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
|
@ -21,125 +18,34 @@ class MessageRow extends StatefulWidget {
|
|||
MessageRowState createState() => MessageRowState();
|
||||
}
|
||||
|
||||
class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMixin {
|
||||
bool showBlockedMessage = false;
|
||||
late AnimationController _controller;
|
||||
late Animation<Alignment> _animation;
|
||||
late Alignment _dragAlignment = Alignment.center;
|
||||
Alignment _dragAffinity = Alignment.center;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(vsync: this);
|
||||
_controller.addListener(() {
|
||||
setState(() {
|
||||
_dragAlignment = _animation.value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
class MessageRowState extends State<MessageRow> {
|
||||
bool showMenu = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||
var isContact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle) != null;
|
||||
var isBlocked = isContact ? Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle)!.isBlocked : false;
|
||||
var actualMessage = Flexible(flex: 3, fit: FlexFit.loose, child: widget.child);
|
||||
|
||||
_dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft;
|
||||
|
||||
if (_dragAlignment == Alignment.center) {
|
||||
_dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft;
|
||||
}
|
||||
|
||||
var senderDisplayStr = "";
|
||||
if (!fromMe) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||
}
|
||||
}
|
||||
|
||||
Widget wdgIcons = Visibility(
|
||||
visible: Provider.of<AppState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageIndex,
|
||||
maintainSize: true,
|
||||
maintainAnimation: true,
|
||||
maintainState: true,
|
||||
maintainInteractivity: false,
|
||||
visible: this.showMenu,
|
||||
child: IconButton(
|
||||
tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
|
||||
onPressed: () {
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context).messageIndex;
|
||||
},
|
||||
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor())));
|
||||
Widget wdgSpacer = Flexible(child: SizedBox(width: 60, height: 10));
|
||||
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
||||
var widgetRow = <Widget>[];
|
||||
|
||||
if (fromMe) {
|
||||
widgetRow = <Widget>[
|
||||
wdgSpacer,
|
||||
wdgIcons,
|
||||
actualMessage,
|
||||
];
|
||||
} else if (isBlocked && !showBlockedMessage) {
|
||||
Color blockedMessageBackground = Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor();
|
||||
Widget wdgPortrait = Padding(padding: EdgeInsets.all(4.0), child: Icon(CwtchIcons.account_blocked));
|
||||
widgetRow = <Widget>[
|
||||
wdgPortrait,
|
||||
Container(
|
||||
padding: EdgeInsets.all(2.0),
|
||||
decoration: BoxDecoration(
|
||||
color: blockedMessageBackground,
|
||||
border: Border.all(color: blockedMessageBackground, width: 2),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(15.0),
|
||||
topRight: Radius.circular(15.0),
|
||||
bottomLeft: Radius.circular(15.0),
|
||||
bottomRight: Radius.circular(15.0),
|
||||
)),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(9.0),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
SelectableText(
|
||||
AppLocalizations.of(context)!.blockedMessageMessage,
|
||||
//key: Key(myKey),
|
||||
style: TextStyle(
|
||||
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(1.0),
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(blockedMessageBackground),
|
||||
),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.showMessageButton + '\u202F',
|
||||
style: TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
this.showBlockedMessage = true;
|
||||
});
|
||||
})),
|
||||
]))),
|
||||
wdgIcons,
|
||||
wdgSpacer,
|
||||
Flexible(flex: 3, fit: FlexFit.loose, child: widget.child),
|
||||
];
|
||||
} else {
|
||||
var contact = Provider.of<ContactInfoState>(context);
|
||||
Widget wdgPortrait = GestureDetector(
|
||||
onTap: isContact ? _btnGoto : _btnAdd,
|
||||
onTap: _btnAdd,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: ProfileImage(
|
||||
|
@ -148,108 +54,36 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
//maskOut: contact.status != "Authenticated",
|
||||
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
||||
badgeTextColor: Colors.red, badgeColor: Colors.red,
|
||||
tooltip: isContact ? AppLocalizations.of(context)!.contactGoto.replaceFirst("%1", senderDisplayStr) : AppLocalizations.of(context)!.addContact,
|
||||
)));
|
||||
|
||||
widgetRow = <Widget>[
|
||||
wdgPortrait,
|
||||
actualMessage,
|
||||
Flexible(flex: 3, fit: FlexFit.loose, child: widget.child),
|
||||
wdgIcons,
|
||||
wdgSpacer,
|
||||
];
|
||||
}
|
||||
var size = MediaQuery.of(context).size;
|
||||
var mr = MouseRegion(
|
||||
|
||||
return MouseRegion(
|
||||
// For desktop...
|
||||
|
||||
onHover: (event) {
|
||||
setState(() {
|
||||
Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
this.showMenu = true;
|
||||
});
|
||||
},
|
||||
onExit: (event) {
|
||||
setState(() {
|
||||
Provider.of<AppState>(context, listen: false).hoveredIndex = -1;
|
||||
this.showMenu = false;
|
||||
});
|
||||
},
|
||||
child: GestureDetector(
|
||||
onPanUpdate: (details) {
|
||||
setState(() {
|
||||
_dragAlignment += Alignment(
|
||||
details.delta.dx / (size.width * 0.5),
|
||||
0,
|
||||
);
|
||||
});
|
||||
},
|
||||
onPanDown: (details) {
|
||||
_controller.stop();
|
||||
},
|
||||
onPanEnd: (details) {
|
||||
_runAnimation(details.velocity.pixelsPerSecond, size);
|
||||
|
||||
// Swipe to quote
|
||||
onHorizontalDragEnd: (details) {
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2),
|
||||
child: Align(
|
||||
widthFactor: 1,
|
||||
alignment: _dragAlignment,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: widgetRow,
|
||||
)))));
|
||||
var mark = Provider.of<ContactInfoState>(context).newMarker;
|
||||
if (mark > 0 && mark == Provider.of<ContactInfoState>(context).totalMessages - Provider.of<MessageMetadata>(context).messageIndex) {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Align(alignment:Alignment.center ,child:_bubbleNew()), mr]);
|
||||
} else {
|
||||
return mr;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _bubbleNew() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||
border: Border.all(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||
width: 1),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(8),
|
||||
bottomRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(9.0),
|
||||
child: Text(AppLocalizations.of(context)!.newMessagesLabel)));
|
||||
}
|
||||
|
||||
void _runAnimation(Offset pixelsPerSecond, Size size) {
|
||||
_animation = _controller.drive(
|
||||
AlignmentTween(
|
||||
begin: _dragAlignment,
|
||||
end: _dragAffinity,
|
||||
),
|
||||
);
|
||||
// Calculate the velocity relative to the unit interval, [0,1],
|
||||
// used by the animation controller.
|
||||
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
|
||||
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
|
||||
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
|
||||
final unitVelocity = unitsPerSecond.distance;
|
||||
|
||||
const spring = SpringDescription(
|
||||
mass: 30,
|
||||
stiffness: 1,
|
||||
damping: 1,
|
||||
);
|
||||
|
||||
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
|
||||
_controller.animateWith(simulation);
|
||||
}
|
||||
|
||||
void _btnGoto() {
|
||||
selectConversation(context, Provider.of<MessageMetadata>(context, listen: false).senderHandle);
|
||||
child: Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow))));
|
||||
}
|
||||
|
||||
void _btnAdd() {
|
||||
|
@ -258,49 +92,19 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
print("sender not yet loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
final setPeerAttribute = {
|
||||
"EventType": "AddContact",
|
||||
"Data": {"ImportString": sender},
|
||||
};
|
||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||
|
||||
showAddContactConfirmAlertDialog(context, profileOnion, sender);
|
||||
}
|
||||
|
||||
showAddContactConfirmAlertDialog(BuildContext context, String profileOnion, String senderOnion) {
|
||||
// set up the buttons
|
||||
Widget cancelButton = ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = ElevatedButton(
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
child: Text(AppLocalizations.of(context)!.addContact),
|
||||
onPressed: () {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, senderOnion);
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.successfullAddedContact),
|
||||
duration: Duration(seconds: 2),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.addContactConfirm.replaceFirst("%1", senderOnion)),
|
||||
actions: [
|
||||
cancelButton,
|
||||
continueButton,
|
||||
],
|
||||
);
|
||||
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.successfullAddedContact),
|
||||
duration: Duration(seconds: 2),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
|||
controller: widget.controller,
|
||||
validator: widget.validator,
|
||||
obscureText: obscureText,
|
||||
enableIMEPersonalizedLearning: false,
|
||||
autofillHints: widget.autoFillHints,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
onFieldSubmitted: widget.action,
|
||||
|
|
|
@ -5,8 +5,7 @@ import 'package:provider/provider.dart';
|
|||
import '../settings.dart';
|
||||
|
||||
class ProfileImage extends StatefulWidget {
|
||||
ProfileImage(
|
||||
{required this.imagePath, required this.diameter, required this.border, this.badgeCount = 0, required this.badgeColor, required this.badgeTextColor, this.maskOut = false, this.tooltip = ""});
|
||||
ProfileImage({required this.imagePath, required this.diameter, required this.border, this.badgeCount = 0, required this.badgeColor, required this.badgeTextColor, this.maskOut = false});
|
||||
final double diameter;
|
||||
final String imagePath;
|
||||
final Color border;
|
||||
|
@ -14,7 +13,6 @@ class ProfileImage extends StatefulWidget {
|
|||
final Color badgeColor;
|
||||
final Color badgeTextColor;
|
||||
final bool maskOut;
|
||||
final String tooltip;
|
||||
|
||||
@override
|
||||
_ProfileImageState createState() => _ProfileImageState();
|
||||
|
@ -23,21 +21,6 @@ class ProfileImage extends StatefulWidget {
|
|||
class _ProfileImageState extends State<ProfileImage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var image = Image(
|
||||
image: AssetImage("assets/" + widget.imagePath),
|
||||
filterQuality: FilterQuality.medium,
|
||||
// We need some theme specific blending here...we might want to consider making this a theme level attribute
|
||||
colorBlendMode: !widget.maskOut
|
||||
? Provider.of<Settings>(context).theme.identifier() == "dark"
|
||||
? BlendMode.softLight
|
||||
: BlendMode.darken
|
||||
: BlendMode.srcOut,
|
||||
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
|
||||
isAntiAlias: true,
|
||||
width: widget.diameter,
|
||||
height: widget.diameter,
|
||||
);
|
||||
|
||||
return RepaintBoundary(
|
||||
child: Stack(children: [
|
||||
ClipOval(
|
||||
|
@ -48,7 +31,22 @@ class _ProfileImageState extends State<ProfileImage> {
|
|||
color: widget.border,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2.0), //border size
|
||||
child: ClipOval(clipBehavior: Clip.antiAlias, child: widget.tooltip == "" ? image : Tooltip(message: widget.tooltip, child: image))))),
|
||||
child: ClipOval(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Image(
|
||||
image: AssetImage("assets/" + widget.imagePath),
|
||||
filterQuality: FilterQuality.medium,
|
||||
// We need some theme specific blending here...we might want to consider making this a theme level attribute
|
||||
colorBlendMode: !widget.maskOut
|
||||
? Provider.of<Settings>(context).theme.identifier() == "dark"
|
||||
? BlendMode.softLight
|
||||
: BlendMode.darken
|
||||
: BlendMode.srcOut,
|
||||
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
|
||||
isAntiAlias: true,
|
||||
width: widget.diameter,
|
||||
height: widget.diameter,
|
||||
))))),
|
||||
Visibility(
|
||||
visible: widget.badgeCount > 0,
|
||||
child: Positioned(
|
||||
|
|
|
@ -7,7 +7,6 @@ import 'package:cwtch/widgets/profileimage.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../errorHandler.dart';
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../settings.dart';
|
||||
|
@ -23,13 +22,12 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
var profile = Provider.of<ProfileInfoState>(context);
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
margin: EdgeInsets.all(0.0),
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0), //border size
|
||||
padding: const EdgeInsets.all(2.0), //border size
|
||||
child: ProfileImage(
|
||||
badgeCount: 0,
|
||||
badgeColor: Provider.of<Settings>(context).theme.portraitProfileBadgeColor(),
|
||||
|
@ -47,14 +45,12 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Visibility(
|
||||
visible: !Provider.of<Settings>(context).streamerMode,
|
||||
child: ExcludeSemantics(
|
||||
child: Text(
|
||||
profile.onion,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)))
|
||||
ExcludeSemantics(
|
||||
child: Text(
|
||||
profile.onion,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
],
|
||||
)),
|
||||
IconButton(
|
||||
|
@ -62,7 +58,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname,
|
||||
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||
onPressed: () {
|
||||
_pushEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath, encrypted: profile.isEncrypted);
|
||||
_pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath, encrypted: profile.isEncrypted);
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -101,8 +97,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
);
|
||||
}
|
||||
|
||||
void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
|
||||
Provider.of<ErrorHandler>(context).reset();
|
||||
void _pushAddEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
|
|
|
@ -61,14 +61,13 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
|
|||
try {
|
||||
var qMessage = (snapshot.data! as Message);
|
||||
// Swap the background color for quoted tweets..
|
||||
var qTextColor = fromMe ? Provider.of<Settings>(context).theme.messageFromOtherTextColor() : Provider.of<Settings>(context).theme.messageFromMeTextColor();
|
||||
return Container(
|
||||
margin: EdgeInsets.all(5),
|
||||
padding: EdgeInsets.all(5),
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor() : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32, color: qTextColor))),
|
||||
Center(widthFactor: 1.0, child: DefaultTextStyle(child: qMessage.getPreviewWidget(context), style: TextStyle(color: qTextColor)))
|
||||
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32))),
|
||||
Center(widthFactor: 1.0, child: qMessage.getPreviewWidget(context))
|
||||
]));
|
||||
} catch (e) {
|
||||
print(e);
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
import 'package:cwtch/main.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/views/addeditservers.dart';
|
||||
import 'package:cwtch/widgets/profileimage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../cwtch_icons_icons.dart';
|
||||
import '../errorHandler.dart';
|
||||
import '../model.dart';
|
||||
import '../settings.dart';
|
||||
|
||||
class ServerRow extends StatefulWidget {
|
||||
@override
|
||||
_ServerRowState createState() => _ServerRowState();
|
||||
}
|
||||
|
||||
class _ServerRowState extends State<ServerRow> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var server = Provider.of<ServerInfoState>(context);
|
||||
return Card(clipBehavior: Clip.antiAlias,
|
||||
margin: EdgeInsets.all(0.0),
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0), //border size
|
||||
child: Icon(CwtchIcons.dns_24px,
|
||||
color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
||||
size: 64)
|
||||
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
server.description,
|
||||
semanticsLabel: server.description,
|
||||
style: Provider.of<FlwtchState>(context).biggerFont.apply(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Visibility(
|
||||
visible: !Provider.of<Settings>(context).streamerMode,
|
||||
child: ExcludeSemantics(
|
||||
child: Text(
|
||||
server.onion,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||
)))
|
||||
],
|
||||
)),
|
||||
|
||||
// Copy server button
|
||||
IconButton(
|
||||
enableFeedback: true,
|
||||
tooltip: AppLocalizations.of(context)!.copyServerKeys,
|
||||
icon: Icon(CwtchIcons.address_copy_2, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: server.serverBundle));
|
||||
},
|
||||
),
|
||||
|
||||
// Edit button
|
||||
IconButton(
|
||||
enableFeedback: true,
|
||||
tooltip: AppLocalizations.of(context)!.editServerTitle,
|
||||
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||
onPressed: () {
|
||||
_pushEditServer(server);
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
])));
|
||||
}
|
||||
|
||||
void _pushEditServer(ServerInfoState server ) {
|
||||
Provider.of<ErrorHandler>(context).reset();
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
settings: RouteSettings(name: "serveraddedit"),
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [ChangeNotifierProvider<ServerInfoState>(
|
||||
create: (_) => server,
|
||||
)],
|
||||
child: AddEditServerView(),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
|
@ -39,7 +39,6 @@ class _CwtchTextFieldState extends State<CwtchTextField> {
|
|||
validator: widget.validator,
|
||||
onChanged: widget.onChanged,
|
||||
autofocus: widget.autofocus,
|
||||
enableIMEPersonalizedLearning: false,
|
||||
focusNode: _focusNode,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.labelText,
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
exec env LD_LIBRARY_PATH=./lib/ ./lib/cwtch
|
||||
env LD_LIBRARY_PATH=./lib/ ./lib/cwtch
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
exec env LD_LIBRARY_PATH=~/.local/lib/cwtch/ ~/.local/lib/cwtch/cwtch
|
||||
env LD_LIBRARY_PATH=~/.local/lib/cwtch/ ~/.local/lib/cwtch/cwtch
|
BIN
linux/cwtch.png
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 45 KiB |
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
exec env LD_LIBRARY_PATH=/usr/lib/cwtch /usr/lib/cwtch/cwtch
|
||||
env LD_LIBRARY_PATH=/usr/lib/cwtch /usr/lib/cwtch/cwtch
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
mkdir -p ~/.local/bin
|
||||
sed "s|~|$HOME|g" cwtch.home.sh > ~/.local/bin/cwtch
|
||||
chmod a+x ~/.local/bin/cwtch
|
||||
|
||||
mkdir -p ~/.local/share/icons
|
||||
cp cwtch.png ~/.local/share/icons
|
||||
|
@ -14,5 +13,4 @@ mkdir -p ~/.local/lib/cwtch
|
|||
cp -r lib/* ~/.local/lib/cwtch
|
||||
|
||||
mkdir -p ~/.local/share/applications
|
||||
sed "s|~|$HOME|g" cwtch.home.desktop > $HOME/.local/share/applications/cwtch.desktop
|
||||
chmod a+x $HOME/.local/share/applications/cwtch.desktop
|
||||
sed "s|~|$HOME|g" cwtch.home.desktop > $HOME/.local/share/applications/cwtch.desktop
|
|
@ -1,7 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
cp cwtch.sys.sh /usr/bin/cwtch
|
||||
chmod a+x /usr/bin/cwtch
|
||||
|
||||
cp cwtch.png /usr/share/icons
|
||||
|
||||
|
@ -12,4 +11,3 @@ mkdir -p /usr/lib/cwtch
|
|||
cp -r lib/* /usr/lib/cwtch
|
||||
|
||||
cp cwtch.sys.desktop /usr/share/applications/cwtch.desktop
|
||||
chmod a+x /usr/share/applications/cwtch.desktop
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# Flutter-related
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
|
||||
# Xcode-related
|
||||
**/xcuserdata/
|
|
@ -1,2 +0,0 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
|
@ -1,2 +0,0 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import package_info_plus_macos
|
||||
import path_provider_macos
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
platform :osx, '10.11'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
end
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
PODS:
|
||||
- FlutterMacOS (1.0.0)
|
||||
- package_info_plus_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- path_provider_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
|
||||
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
package_info_plus_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
|
||||
path_provider_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
|
||||
path_provider_macos: a0a3fd666cb7cd0448e936fb4abad4052961002b
|
||||
url_launcher_macos: 45af3d61de06997666568a7149c1be98b41c95d4
|
||||
|
||||
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
|
||||
|
||||
COCOAPODS: 1.11.2
|
|
@ -1,634 +0,0 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
|
||||
buildPhases = (
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Flutter Assemble";
|
||||
productName = FLX;
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
211091843422DC99794C0E66 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C147BFC49BDAD7E14E179AF3 /* Pods_Runner.framework */; };
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
|
||||
remoteInfo = FLX;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Bundle Framework";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1AF219FB7E04D0D2DBC075A5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
2FFAA895D8F20891DA4D87C5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* Cwtch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cwtch.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
5EEB7EA2235BC5CDA2BCB6A9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
C147BFC49BDAD7E14E179AF3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
211091843422DC99794C0E66 /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
33BA886A226E78AF003329D5 /* Configs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
|
||||
);
|
||||
path = Configs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10E42044A3C60003C045 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33FAB671232836740065AC1E /* Runner */,
|
||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
35B90E5140F9C2DE6D3BD07E /* Pods */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10ED2044A3C60003C045 /* Cwtch.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC11242044D66E0003C045 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||
);
|
||||
name = Resources;
|
||||
path = ..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CEB47122A05771004F2AC0 /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
|
||||
);
|
||||
path = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33FAB671232836740065AC1E /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||
33E51914231749380026EE4D /* Release.entitlements */,
|
||||
33CC11242044D66E0003C045 /* Resources */,
|
||||
33BA886A226E78AF003329D5 /* Configs */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
35B90E5140F9C2DE6D3BD07E /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2FFAA895D8F20891DA4D87C5 /* Pods-Runner.debug.xcconfig */,
|
||||
1AF219FB7E04D0D2DBC075A5 /* Pods-Runner.release.xcconfig */,
|
||||
5EEB7EA2235BC5CDA2BCB6A9 /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C147BFC49BDAD7E14E179AF3 /* Pods_Runner.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
F13BA066A536BB902BFE0B8C /* [CP] Check Pods Manifest.lock */,
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
73F636226F48A847E9232926 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 33CC10ED2044A3C60003C045 /* Cwtch.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 0930;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1100;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
33CC111A2044C6BA0003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 33CC10E42044A3C60003C045;
|
||||
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
33CC10EC2044A3C60003C045 /* Runner */,
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
33CC10EB2044A3C60003C045 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n\n";
|
||||
};
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterInputs.xcfilelist,
|
||||
);
|
||||
inputPaths = (
|
||||
Flutter/ephemeral/tripwire,
|
||||
);
|
||||
outputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterOutputs.xcfilelist,
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
73F636226F48A847E9232926 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F13BA066A536BB902BFE0B8C /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
33CC10E92044A3C60003C045 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
|
||||
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
33CC10F52044A3C60003C045 /* Base */,
|
||||
);
|
||||
name = MainMenu.xib;
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
338D0CE9231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEA231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PRODUCT_NAME = Cwtch;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEB231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
33CC10F92044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FA2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC10FC2044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PRODUCT_NAME = Cwtch;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FD2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PRODUCT_NAME = Cwtch;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC111C2044C6BA0003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC111D2044C6BA0003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10F92044A3C60003C045 /* Debug */,
|
||||
33CC10FA2044A3C60003C045 /* Release */,
|
||||
338D0CE9231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10FC2044A3C60003C045 /* Debug */,
|
||||
33CC10FD2044A3C60003C045 /* Release */,
|
||||
338D0CEA231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC111C2044C6BA0003C045 /* Debug */,
|
||||
33CC111D2044C6BA0003C045 /* Release */,
|
||||
338D0CEB231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,87 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "Cwtch.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "Cwtch.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "Cwtch.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "Cwtch.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,9 +0,0 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|