Compare commits
88 Commits
Author | SHA1 | Date |
---|---|---|
mvaneerde | 56ed1be5db | |
erinn | 397971a181 | |
Sarah Jamie Lewis | 6210a64315 | |
Sarah Jamie Lewis | 825fb23992 | |
Sarah Jamie Lewis | a06815fa08 | |
erinn | 62a9402357 | |
Sarah Jamie Lewis | 67ed282394 | |
erinn | e487d0bd65 | |
erinn | 76c6a5f069 | |
erinn | 004e211d0b | |
Sarah Jamie Lewis | afe2457062 | |
Dan Ballard | 42ed710fc9 | |
Sarah Jamie Lewis | 1a80b4b808 | |
erinn | 61d1a60b0e | |
Matthew van Eerde (^_^) | 7d15c41aed | |
Matthew van Eerde (^_^) | f09c40d7dd | |
erinn | b78f138cb2 | |
Sarah Jamie Lewis | 6d4ab5bab3 | |
Sarah Jamie Lewis | 3cf81e41d6 | |
Dan Ballard | ebb665355c | |
Dan Ballard | aee861df90 | |
Sarah Jamie Lewis | 132f1718ed | |
Dan Ballard | 5a8a39c77f | |
Sarah Jamie Lewis | e4d9829e7b | |
erinn | 06075583ed | |
erinn | 62bedcd612 | |
erinn | b3f9f10cdd | |
erinn | 65811231a7 | |
erinn | 0615a81042 | |
Sarah Jamie Lewis | 587bb783aa | |
Sarah Jamie Lewis | 94396d965b | |
Sarah Jamie Lewis | a19deb2b4d | |
Sarah Jamie Lewis | 505bceb887 | |
Dan Ballard | 83c9adac5d | |
Sarah Jamie Lewis | a1e168a5f7 | |
erinn | 08fcbc6a10 | |
Dan Ballard | 8668211e1d | |
Sarah Jamie Lewis | 16bdcce25c | |
Sarah Jamie Lewis | f9584d1301 | |
Dan Ballard | 13faa2c027 | |
Dan Ballard | a7536d30ea | |
Sarah Jamie Lewis | 0abc4caf95 | |
Sarah Jamie Lewis | 07ed216b0a | |
Sarah Jamie Lewis | f1594be932 | |
Dan Ballard | e7ecd28faf | |
Dan Ballard | 19269fce1c | |
Sarah Jamie Lewis | 91c556dd14 | |
Dan Ballard | 1726ea2f45 | |
Sarah Jamie Lewis | 6723949d17 | |
Sarah Jamie Lewis | 43d0e51890 | |
Dan Ballard | 51d2e89e9a | |
Sarah Jamie Lewis | cac05cd4e6 | |
Dan Ballard | 03fd76c598 | |
Dan Ballard | cddf204695 | |
Sarah Jamie Lewis | 464edf9aed | |
Sarah Jamie Lewis | b9ce0176f9 | |
Sarah Jamie Lewis | 79abd45a83 | |
erinn | 0126721979 | |
erinn | 248167c15a | |
Sarah Jamie Lewis | d126cc6ebb | |
Sarah Jamie Lewis | 7a7b3f22b0 | |
erinn | 502abb8eb0 | |
erinn | 782c838fc8 | |
erinn | e9d28e76ea | |
Sarah Jamie Lewis | c63a1ae489 | |
Dan Ballard | c73d96e9ba | |
Sarah Jamie Lewis | 681b5aa42b | |
Dan Ballard | 166c1ca726 | |
Sarah Jamie Lewis | 6dd2ecb199 | |
Dan Ballard | 86357be367 | |
Sarah Jamie Lewis | 4628b87c51 | |
Sarah Jamie Lewis | 0688b37b26 | |
Sarah Jamie Lewis | e7c05343d6 | |
Dan Ballard | 17e15e5ae6 | |
Dan Ballard | 2dd6886f22 | |
Dan Ballard | 04d8abe4e1 | |
Sarah Jamie Lewis | 70c5325351 | |
Sarah Jamie Lewis | 7b397ecfe2 | |
Sarah Jamie Lewis | 3b6638ce8a | |
Sarah Jamie Lewis | 6ea793875f | |
Sarah Jamie Lewis | 80f9ed6a6c | |
Dan Ballard | 3fef3153b4 | |
Dan Ballard | 3f020472fa | |
Dan Ballard | 9bb23de090 | |
Sarah Jamie Lewis | 992b26e1ab | |
Sarah Jamie Lewis | d4ab6585ea | |
Dan Ballard | 1f012b153c | |
Sarah Jamie Lewis | 9029386344 |
36
.drone.yml
|
@ -24,13 +24,13 @@ steps:
|
|||
- git checkout $DRONE_COMMIT
|
||||
|
||||
- name: fetch
|
||||
image: cirrusci/flutter:dev
|
||||
image: cirrusci/flutter:2.5.0-6.0.pre
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /root/.pub-cache
|
||||
commands:
|
||||
- ./fetch-tor.sh
|
||||
- echo `git describe --tags` > VERSION
|
||||
- echo `git describe --tags --abbrev=1` > 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-dev
|
||||
image: openpriv/flutter-desktop:linux-fdev2.5rc
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /root/.pub-cache
|
||||
|
@ -61,7 +61,7 @@ steps:
|
|||
- rm -r cwtch
|
||||
|
||||
- name: test-build-android
|
||||
image: cirrusci/flutter:dev
|
||||
image: cirrusci/flutter:2.5.0-6.0.pre
|
||||
when:
|
||||
event: pull_request
|
||||
volumes:
|
||||
|
@ -71,7 +71,7 @@ steps:
|
|||
- flutter build apk --debug
|
||||
|
||||
- name: build-android
|
||||
image: cirrusci/flutter:dev
|
||||
image: cirrusci/flutter:2.5.0-6.0.pre
|
||||
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:dev
|
||||
image: cirrusci/flutter:2.5.0-6.0.pre
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /root/.pub-cache
|
||||
|
@ -126,7 +126,7 @@ steps:
|
|||
- mv ./../sha256s.txt .
|
||||
- cd ..
|
||||
# TODO: do deployment once files actaully compile
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
- name: notify-email
|
||||
image: drillster/drone-email
|
||||
|
@ -175,7 +175,7 @@ clone:
|
|||
|
||||
steps:
|
||||
- name: clone
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.5rc
|
||||
environment:
|
||||
buildbot_key_b64:
|
||||
from_secret: buildbot_key_b64
|
||||
|
@ -193,16 +193,16 @@ steps:
|
|||
- git checkout $Env:DRONE_COMMIT
|
||||
|
||||
- name: fetch
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.5rc
|
||||
commands:
|
||||
- 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 "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 "Get-Date -Format 'yyyy-MM-dd-HH-mm'" > BUILDDATE
|
||||
- .\fetch-libcwtch-go.ps1
|
||||
|
||||
- name: build-windows
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.5rc
|
||||
commands:
|
||||
- flutter pub get
|
||||
- $Env:version += type .\VERSION
|
||||
|
@ -213,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.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 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 README.md $Env:releasedir\
|
||||
- copy windows\*.bat $Env:releasedir\
|
||||
- powershell -command "Expand-Archive -Path tor.zip -DestinationPath $Env:releasedir\Tor"
|
||||
|
@ -258,7 +258,7 @@ steps:
|
|||
- move *.sha512 deploy\$Env:builddir
|
||||
|
||||
- name: deploy-windows
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.5rc
|
||||
when:
|
||||
event: push
|
||||
status: [ success ]
|
||||
|
@ -268,7 +268,7 @@ steps:
|
|||
commands:
|
||||
- echo $Env:BUILDFILES_KEY > id_rsab64
|
||||
- certutil -decode id_rsab64 id_rsa
|
||||
- scp -r -o StrictHostKeyChecking=no -i id_rsa deploy\\* buildfiles@openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
- scp -r -o StrictHostKeyChecking=no -i id_rsa deploy\\* buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
trigger:
|
||||
repo: cwtch.im/cwtch-ui
|
||||
|
|
|
@ -1 +1 @@
|
|||
v1.0.0-27-g4d218df-2021-07-07-23-27
|
||||
v1.1.1-16-g7376218-2021-08-25-16-54
|
||||
|
|
|
@ -68,6 +68,7 @@ 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`).
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
.setContentTitle(data.getString("Nick"))
|
||||
.setContentText("New message")//todo: translate
|
||||
.setLargeIcon(BitmapFactory.decodeStream(fh))
|
||||
.setSmallIcon(R.mipmap.knott)
|
||||
.setSmallIcon(R.mipmap.knott_transparent)
|
||||
.setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
|
@ -240,7 +240,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
.setContentTitle(title)
|
||||
.setTicker(title)
|
||||
.setContentText(progress)
|
||||
.setSmallIcon(R.mipmap.knott)
|
||||
.setSmallIcon(R.mipmap.knott_transparent)
|
||||
.setOngoing(true)
|
||||
// Add the cancel action to the notification which can
|
||||
// be used to cancel the worker
|
||||
|
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 8.8 KiB |
|
@ -1,399 +0,0 @@
|
|||
<?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>
|
Before Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
@ -0,0 +1,99 @@
|
|||
<?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>
|
After Width: | Height: | Size: 9.7 KiB |
BIN
cwtch.png
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 51 KiB |
|
@ -1,6 +1,6 @@
|
|||
|
||||
Invoke-WebRequest -Uri https://dist.torproject.org/torbrowser/10.0.18/tor-win64-0.4.5.9.zip -OutFile tor.zip
|
||||
Invoke-WebRequest -Uri https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-win64-0.4.6.5.zip -OutFile tor.zip
|
||||
|
||||
if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '72764eb07ad8ab511603aba0734951ca003989f5f4686af91ba220217b4a8a4bcc5f571b59f52c847932f6efedf847b111621983050fcddbb8099d43ca66fb07' ) { Write-Error 'tor.zip sha512sum mismatch' }
|
||||
if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '7917561a7a063440a1ddfa9cb544ab9ffd09de84cea3dd66e3cc9cd349dd9f85b74a522ec390d7a974bc19b424c4d53af60e57bbc47e763d13cab6a203c4592f' ) { Write-Error 'tor.zip sha512sum mismatch' }
|
||||
|
||||
Expand-Archive -Path tor.zip -DestinationPath Tor
|
||||
|
|
|
@ -49,8 +49,7 @@ class CwtchNotifier {
|
|||
nickname: data["nick"],
|
||||
status: data["status"],
|
||||
imagePath: data["picture"],
|
||||
isBlocked: data["authorization"] == "blocked",
|
||||
isInvitation: data["authorization"] == "unknown",
|
||||
authorization: stringToContactAuthorization(data["authorization"]),
|
||||
savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"],
|
||||
numMessages: int.parse(data["numMessages"]),
|
||||
numUnread: int.parse(data["unread"]),
|
||||
|
@ -69,7 +68,13 @@ class CwtchNotifier {
|
|||
}
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"],
|
||||
isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
|
||||
authorization: ContactAuthorization.approved,
|
||||
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;
|
||||
|
@ -91,8 +96,7 @@ class CwtchNotifier {
|
|||
contact.status = data["ConnectionState"];
|
||||
}
|
||||
if (data["authorization"] != null) {
|
||||
contact.isInvitation = data["authorization"] == "unknown";
|
||||
contact.isBlocked = data["authorization"] == "blocked";
|
||||
contact.authorization = stringToContactAuthorization(data["authorization"]);
|
||||
}
|
||||
// contact.[status/isBlocked] might change the list's sort order
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
|
||||
|
@ -105,6 +109,14 @@ class CwtchNotifier {
|
|||
}
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
||||
|
||||
// We only ever see messages from authenticated peers.
|
||||
// If the contact is marked as offline then override this - can happen when the contact is removed from the front
|
||||
// end during syncing.
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.isOnline() == false) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.status = "Authenticated";
|
||||
}
|
||||
|
||||
break;
|
||||
case "PeerAcknowledgement":
|
||||
// We don't use these anymore, IndexedAcknowledgement is more suited to the UI front end...
|
||||
|
@ -118,6 +130,12 @@ class CwtchNotifier {
|
|||
try {
|
||||
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||
if (message == null) break;
|
||||
// We only ever see acks from authenticated peers.
|
||||
// If the contact is marked as offline then override this - can happen when the contact is removed from the front
|
||||
// end during syncing.
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.isOnline() == false) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.status = "Authenticated";
|
||||
}
|
||||
message.ackd = true;
|
||||
} catch (e) {
|
||||
// ignore, we received an ack for a message that hasn't loaded onto the screen yet...
|
||||
|
@ -131,12 +149,23 @@ class CwtchNotifier {
|
|||
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());
|
||||
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!");
|
||||
} else {
|
||||
// from me (already displayed - do not update counter)
|
||||
var idx = data["Signature"];
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])?.getMessageKey(idx);
|
||||
if (key == null) break;
|
||||
try {
|
||||
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||
|
@ -155,7 +184,7 @@ class CwtchNotifier {
|
|||
case "IndexedFailure":
|
||||
EnvironmentConfig.debugLog("IndexedFailure");
|
||||
var idx = data["Index"];
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])?.getMessageKey(idx);
|
||||
try {
|
||||
var message = Provider.of<MessageMetadata>(key!.currentContext!, listen: false);
|
||||
message.error = true;
|
||||
|
@ -227,22 +256,22 @@ class CwtchNotifier {
|
|||
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
||||
isInvitation: false,
|
||||
authorization: ContactAuthorization.approved,
|
||||
imagePath: data["PicturePath"],
|
||||
nickname: groupInvite["GroupName"],
|
||||
server: groupInvite["ServerHost"],
|
||||
status: status,
|
||||
isGroup: true,
|
||||
lastMessageTime: DateTime.now()));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.now());
|
||||
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(0)));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.fromMillisecondsSinceEpoch(0));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "AcceptGroupInvite":
|
||||
EnvironmentConfig.debugLog("accept group invite");
|
||||
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.isInvitation = false;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.authorization = ContactAuthorization.approved;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.fromMillisecondsSinceEpoch(0));
|
||||
break;
|
||||
case "ServerStateChange":
|
||||
// Update the Server Cache
|
||||
|
|
|
@ -4,7 +4,6 @@ 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';
|
||||
|
@ -22,6 +21,9 @@ 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);
|
||||
|
||||
|
@ -34,25 +36,15 @@ typedef VoidFromStringStringStringStringFn = void Function(Pointer<Utf8>, int, P
|
|||
typedef void_from_string_string_int_int_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int64, Int64);
|
||||
typedef VoidFromStringStringIntIntFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
|
||||
|
||||
typedef access_cwtch_eventbus_function = Void Function();
|
||||
typedef NextEventFn = void Function();
|
||||
|
||||
typedef string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length);
|
||||
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);
|
||||
|
@ -138,10 +130,8 @@ 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() {
|
||||
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
|
||||
static void _checkAppbusEvents(SendPort sendPort) async {
|
||||
|
@ -165,9 +155,21 @@ class CwtchFfi implements Cwtch {
|
|||
// ignore: non_constant_identifier_names
|
||||
final GetAppbusEvent = getAppbusEventC.asFunction<AppbusEventsFn>();
|
||||
|
||||
while (true) {
|
||||
// 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
|
||||
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");
|
||||
|
@ -184,6 +186,7 @@ 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
|
||||
|
@ -194,6 +197,8 @@ 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
|
||||
|
@ -203,6 +208,7 @@ 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
|
||||
|
@ -214,6 +220,9 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -226,6 +235,8 @@ 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
|
||||
|
@ -236,6 +247,7 @@ class CwtchFfi implements Cwtch {
|
|||
final SendAppBusEvent = sendAppBusEvent.asFunction<StringFn>();
|
||||
final utf8json = json.toNativeUtf8();
|
||||
SendAppBusEvent(utf8json, utf8json.length);
|
||||
malloc.free(utf8json);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -247,6 +259,8 @@ 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
|
||||
|
@ -258,6 +272,8 @@ 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
|
||||
|
@ -270,6 +286,9 @@ 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
|
||||
|
@ -282,6 +301,9 @@ 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
|
||||
|
@ -302,6 +324,8 @@ 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
|
||||
|
@ -315,6 +339,10 @@ 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
|
||||
|
@ -326,9 +354,12 @@ 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
|
||||
|
@ -337,6 +368,10 @@ 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
|
||||
|
@ -348,6 +383,8 @@ class CwtchFfi implements Cwtch {
|
|||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = handle.toNativeUtf8();
|
||||
LeaveConversation(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -359,9 +396,12 @@ class CwtchFfi implements Cwtch {
|
|||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = groupHandle.toNativeUtf8();
|
||||
LeaveGroup(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@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
|
||||
|
@ -369,6 +409,8 @@ 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
|
||||
|
@ -380,14 +422,18 @@ 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
|
||||
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();
|
||||
|
||||
|
@ -400,6 +446,7 @@ 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
|
||||
|
@ -409,6 +456,20 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,28 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"@@last_modified": "2021-08-26T23:44:51+02:00",
|
||||
"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",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -76,7 +98,6 @@
|
|||
"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",
|
||||
|
@ -96,7 +117,6 @@
|
|||
"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",
|
||||
|
@ -120,7 +140,6 @@
|
|||
"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",
|
||||
|
@ -132,13 +151,8 @@
|
|||
"profileName": "Anzeigename",
|
||||
"editProfileTitle": "Profil bearbeiten",
|
||||
"addProfileTitle": "Neues Profil hinzufügen",
|
||||
"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",
|
||||
"deleteBtn": "Löschen",
|
||||
"saveBtn": "Speichern",
|
||||
"displayNameLabel": "Angezeigename",
|
||||
"addressLabel": "Adresse",
|
||||
"puzzleGameBtn": "Puzzlespiel",
|
||||
|
@ -150,15 +164,12 @@
|
|||
"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",
|
||||
|
@ -184,7 +195,6 @@
|
|||
"newBulletinLabel": "Neue Meldung",
|
||||
"joinGroup": "Gruppe beitreten",
|
||||
"createGroup": "Gruppe erstellen",
|
||||
"addPeer": "Anderen Nutzer hinzufügen",
|
||||
"groupAddr": "Adresse",
|
||||
"invitation": "Einladung",
|
||||
"server": "Server",
|
||||
|
@ -193,7 +203,6 @@
|
|||
"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,6 +1,28 @@
|
|||
{
|
||||
"@@locale": "en",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"@@last_modified": "2021-08-26T23:44:51+02:00",
|
||||
"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",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -76,7 +98,6 @@
|
|||
"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",
|
||||
|
@ -96,7 +117,6 @@
|
|||
"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",
|
||||
|
@ -120,7 +140,6 @@
|
|||
"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",
|
||||
|
@ -133,11 +152,6 @@
|
|||
"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",
|
||||
|
@ -150,20 +164,17 @@
|
|||
"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",
|
||||
|
@ -184,7 +195,6 @@
|
|||
"newBulletinLabel": "New Bulletin",
|
||||
"joinGroup": "Join group",
|
||||
"createGroup": "Create group",
|
||||
"addPeer": "Add Peer",
|
||||
"groupAddr": "Address",
|
||||
"invitation": "Invitation",
|
||||
"server": "Server",
|
||||
|
@ -193,7 +203,6 @@
|
|||
"peerAddress": "Address",
|
||||
"joinGroupTab": "Join a group",
|
||||
"createGroupTab": "Create a group",
|
||||
"addPeerTab": "Add a peer",
|
||||
"createGroupBtn": "Create",
|
||||
"defaultGroupName": "Awesome Group",
|
||||
"createGroupTitle": "Create Group"
|
||||
|
|
|
@ -1,6 +1,28 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"@@last_modified": "2021-08-26T23:44:51+02:00",
|
||||
"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",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -76,7 +98,6 @@
|
|||
"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",
|
||||
|
@ -96,7 +117,6 @@
|
|||
"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",
|
||||
|
@ -120,7 +140,6 @@
|
|||
"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",
|
||||
|
@ -133,11 +152,6 @@
|
|||
"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",
|
||||
|
@ -150,15 +164,12 @@
|
|||
"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",
|
||||
|
@ -184,7 +195,6 @@
|
|||
"newBulletinLabel": "Nuevo Boletín",
|
||||
"joinGroup": "Únete al grupo",
|
||||
"createGroup": "Crear perfil",
|
||||
"addPeer": "Agregar Contacto",
|
||||
"groupAddr": "Dirección",
|
||||
"invitation": "Invitación",
|
||||
"server": "Servidor",
|
||||
|
@ -193,7 +203,6 @@
|
|||
"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,6 +1,28 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"@@last_modified": "2021-08-26T23:44:51+02:00",
|
||||
"profileOnionLabel": "Envoyez cette adresse aux personnes avec lesquelles vous souhaitez entrer en contact.",
|
||||
"addPeerTab": "Ajouter un pair",
|
||||
"addPeer": "Ajouter un pair",
|
||||
"peerNotOnline": "Le pair est hors ligne, les messages ne peuvent pas être remis pour le moment",
|
||||
"peerBlockedMessage": "Le pair est bloqué",
|
||||
"peerOfflineMessage": "Le pair est hors ligne, les messages ne peuvent pas être remis pour le moment",
|
||||
"blockBtn": "Bloquer le pair",
|
||||
"savePeerHistory": "Sauvegarder l'historique des pairs",
|
||||
"savePeerHistoryDescription": "Détermine s'il faut ou non supprimer tout historique associé au pair.",
|
||||
"dontSavePeerHistory": "Supprimer l'historique des pairs",
|
||||
"unblockBtn": "Débloquer le pair",
|
||||
"blockUnknownLabel": "Bloquer les pairs inconnus",
|
||||
"blockUnknownConnectionsEnabledDescription": "Connections from unknown contacts are blocked. You can change this in Settings",
|
||||
"networkStatusConnecting": "Se connecter au réseau et aux pairs...",
|
||||
"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",
|
||||
"settingUIColumnOptionSame": "Même réglage que pour le mode portrait",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -76,7 +98,6 @@
|
|||
"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",
|
||||
|
@ -96,7 +117,6 @@
|
|||
"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",
|
||||
|
@ -120,7 +140,6 @@
|
|||
"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",
|
||||
|
@ -132,12 +151,7 @@
|
|||
"profileName": "Pseudo",
|
||||
"editProfileTitle": "Modifier le profil",
|
||||
"addProfileTitle": "Ajouter un nouveau profil",
|
||||
"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",
|
||||
"deleteBtn": "Effacer",
|
||||
"saveBtn": "Sauvegarder",
|
||||
"displayNameLabel": "Pseudo",
|
||||
"addressLabel": "Adresse",
|
||||
|
@ -150,15 +164,12 @@
|
|||
"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",
|
||||
|
@ -184,7 +195,6 @@
|
|||
"newBulletinLabel": "Nouveau bulletin",
|
||||
"joinGroup": "Rejoindre le groupe",
|
||||
"createGroup": "Créer un groupe",
|
||||
"addPeer": "Ajouter un pair",
|
||||
"groupAddr": "Adresse",
|
||||
"invitation": "Invitation",
|
||||
"server": "Serveur",
|
||||
|
@ -193,7 +203,6 @@
|
|||
"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,6 +1,28 @@
|
|||
{
|
||||
"@@locale": "it",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"@@last_modified": "2021-08-26T23:44:51+02:00",
|
||||
"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",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -76,7 +98,6 @@
|
|||
"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",
|
||||
|
@ -96,7 +117,6 @@
|
|||
"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",
|
||||
|
@ -120,11 +140,10 @@
|
|||
"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",
|
||||
|
@ -133,11 +152,6 @@
|
|||
"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",
|
||||
|
@ -150,15 +164,12 @@
|
|||
"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",
|
||||
|
@ -184,7 +195,6 @@
|
|||
"newBulletinLabel": "Nuovo bollettino",
|
||||
"joinGroup": "Unisciti al gruppo",
|
||||
"createGroup": "Crea un gruppo",
|
||||
"addPeer": "Aggiungi peer",
|
||||
"groupAddr": "Indirizzo",
|
||||
"invitation": "Invito",
|
||||
"server": "Server",
|
||||
|
@ -193,7 +203,6 @@
|
|||
"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,6 +1,14 @@
|
|||
{
|
||||
"@@locale": "pl",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"@@last_modified": "2021-07-14T23:49:07+02:00",
|
||||
"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",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -163,7 +171,7 @@
|
|||
"update": "Update",
|
||||
"inviteBtn": "Invite",
|
||||
"inviteToGroupLabel": "Invite to group",
|
||||
"groupNameLabel": "Group Name",
|
||||
"groupNameLabel": "Group name",
|
||||
"viewServerInfo": "Server Info",
|
||||
"serverSynced": "Synced",
|
||||
"serverConnectivityDisconnected": "Server Disconnected",
|
||||
|
|
|
@ -1,6 +1,28 @@
|
|||
{
|
||||
"@@locale": "pt",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"@@last_modified": "2021-08-26T23:44:51+02:00",
|
||||
"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",
|
||||
"settingUIColumnOptionSame": "Same as portrait mode setting",
|
||||
"settingUIColumnDouble14Ratio": "Double (1:4)",
|
||||
"settingUIColumnDouble12Ratio": "Double (1:2)",
|
||||
|
@ -76,7 +98,6 @@
|
|||
"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",
|
||||
|
@ -96,7 +117,6 @@
|
|||
"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",
|
||||
|
@ -120,7 +140,6 @@
|
|||
"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",
|
||||
|
@ -133,11 +152,6 @@
|
|||
"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",
|
||||
|
@ -150,20 +164,17 @@
|
|||
"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",
|
||||
|
@ -184,7 +195,6 @@
|
|||
"newBulletinLabel": "Novo Boletim",
|
||||
"joinGroup": "Join group",
|
||||
"createGroup": "Create group",
|
||||
"addPeer": "Add Peer",
|
||||
"groupAddr": "Address",
|
||||
"invitation": "Invitation",
|
||||
"server": "Server",
|
||||
|
@ -193,7 +203,6 @@
|
|||
"peerAddress": "Address",
|
||||
"joinGroupTab": "Join a group",
|
||||
"createGroupTab": "Create a group",
|
||||
"addPeerTab": "Add a peer",
|
||||
"createGroupBtn": "Criar",
|
||||
"defaultGroupName": "Grupo incrível",
|
||||
"createGroupTitle": "Criar Grupo"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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';
|
||||
|
@ -28,7 +29,7 @@ var globalTorStatus = TorStatus();
|
|||
var globalAppState = AppState();
|
||||
|
||||
void main() {
|
||||
print("main()");
|
||||
print("Cwtch version: ${EnvironmentConfig.BUILD_VER} built on: ${EnvironmentConfig.BUILD_DATE}");
|
||||
LicenseRegistry.addLicense(() => licenses());
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
print("runApp()");
|
||||
|
@ -168,6 +169,7 @@ 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
|
||||
|
|
154
lib/model.dart
|
@ -3,13 +3,6 @@ import 'dart:convert';
|
|||
import 'package:cwtch/widgets/messagerow.dart';
|
||||
import 'package:flutter/cupertino.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 ///
|
||||
|
@ -31,6 +24,59 @@ class ChatMessage {
|
|||
};
|
||||
}
|
||||
|
||||
class AppState extends ChangeNotifier {
|
||||
bool cwtchInit = false;
|
||||
bool cwtchIsClosing = false;
|
||||
String appError = "";
|
||||
String? _selectedProfile;
|
||||
String? _selectedConversation;
|
||||
int _initialScrollIndex = 0;
|
||||
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();
|
||||
}
|
||||
|
||||
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 ///
|
||||
///////////////////
|
||||
|
@ -62,45 +108,6 @@ 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 = "";
|
||||
|
@ -153,13 +160,22 @@ class ContactListState extends ChangeNotifier {
|
|||
//} </todo>
|
||||
}
|
||||
|
||||
void updateLastMessageTime(String forOnion, DateTime newVal) {
|
||||
void updateLastMessageTime(String forOnion, DateTime newMessageTime) {
|
||||
var contact = getContact(forOnion);
|
||||
if (contact == null) return;
|
||||
|
||||
contact.lastMessageTime = newVal;
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
List<ContactInfoState> get contacts => _contacts.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
|
@ -213,8 +229,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
nickname: contact["name"],
|
||||
status: contact["status"],
|
||||
imagePath: contact["picture"],
|
||||
isBlocked: contact["authorization"] == "blocked",
|
||||
isInvitation: contact["authorization"] == "unknown",
|
||||
authorization: stringToContactAuthorization(contact["authorization"]),
|
||||
savePeerHistory: contact["saveConversationHistory"],
|
||||
numMessages: contact["numMessages"],
|
||||
numUnread: contact["numUnread"],
|
||||
|
@ -316,8 +331,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
nickname: contact["name"],
|
||||
status: contact["status"],
|
||||
imagePath: contact["picture"],
|
||||
isBlocked: contact["authorization"] == "blocked",
|
||||
isInvitation: contact["authorization"] == "unknown",
|
||||
authorization: stringToContactAuthorization(contact["authorization"]),
|
||||
savePeerHistory: contact["saveConversationHistory"],
|
||||
numMessages: contact["numMessages"],
|
||||
numUnread: contact["numUnread"],
|
||||
|
@ -331,13 +345,25 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
final String profileOnion;
|
||||
final String onion;
|
||||
late String _nickname;
|
||||
|
||||
late bool _isInvitation;
|
||||
late bool _isBlocked;
|
||||
late ContactAuthorization _authorization;
|
||||
late String _status;
|
||||
late String _imagePath;
|
||||
late String _savePeerHistory;
|
||||
|
@ -355,8 +381,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
this.onion, {
|
||||
nickname = "",
|
||||
isGroup = false,
|
||||
isInvitation = false,
|
||||
isBlocked = false,
|
||||
authorization = ContactAuthorization.unknown,
|
||||
status = "",
|
||||
imagePath = "",
|
||||
savePeerHistory = "DeleteHistoryConfirmed",
|
||||
|
@ -367,8 +392,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
}) {
|
||||
this._nickname = nickname;
|
||||
this._isGroup = isGroup;
|
||||
this._isInvitation = isInvitation;
|
||||
this._isBlocked = isBlocked;
|
||||
this._authorization = authorization;
|
||||
this._status = status;
|
||||
this._imagePath = imagePath;
|
||||
this._totalMessages = numMessages;
|
||||
|
@ -398,15 +422,13 @@ class ContactInfoState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get isBlocked => this._isBlocked;
|
||||
set isBlocked(bool newVal) {
|
||||
this._isBlocked = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
bool get isBlocked => this._authorization == ContactAuthorization.blocked;
|
||||
|
||||
bool get isInvitation => this._isInvitation;
|
||||
set isInvitation(bool newVal) {
|
||||
this._isInvitation = newVal;
|
||||
bool get isInvitation => this._authorization == ContactAuthorization.unknown;
|
||||
|
||||
ContactAuthorization get authorization => this._authorization;
|
||||
set authorization(ContactAuthorization newAuth) {
|
||||
this._authorization = newAuth;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import 'messages/invitemessage.dart';
|
||||
import 'messages/malformedmessage.dart';
|
||||
import 'messages/quotedmessage.dart';
|
||||
|
|
|
@ -25,6 +25,7 @@ class InviteMessage extends Message {
|
|||
|
||||
String inviteTarget;
|
||||
String inviteNick;
|
||||
String invite = this.content;
|
||||
|
||||
if (this.content.length == TorV3ContactHandleLength) {
|
||||
inviteTarget = this.content;
|
||||
|
@ -40,7 +41,7 @@ class InviteMessage extends Message {
|
|||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
}
|
||||
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -51,7 +52,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 +67,7 @@ class InviteMessage extends Message {
|
|||
return MalformedBubble();
|
||||
}
|
||||
}
|
||||
return InvitationBubble(overlay, inviteTarget, inviteNick);
|
||||
return InvitationBubble(overlay, inviteTarget, inviteNick, invite);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,17 @@ 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;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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';
|
||||
|
||||
|
|
|
@ -354,7 +354,7 @@ class OpaqueDark extends OpaqueThemeType {
|
|||
}
|
||||
|
||||
Color altTextColor() {
|
||||
return whitePurple;
|
||||
return mauvePurple;
|
||||
}
|
||||
|
||||
Color hilightElementTextColor() {
|
||||
|
|
|
@ -136,7 +136,12 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
// We only allow setting password types on profile creation
|
||||
Visibility(
|
||||
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
)),
|
||||
Visibility(
|
||||
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Checkbox(
|
||||
value: usePassword,
|
||||
fillColor: MaterialStateProperty.all(theme.current().defaultButtonColor()),
|
||||
|
@ -147,6 +152,15 @@ 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,
|
||||
|
|
|
@ -12,6 +12,8 @@ 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);
|
||||
|
||||
|
@ -19,6 +21,40 @@ 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;
|
||||
// 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;
|
||||
|
@ -49,20 +85,9 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
),
|
||||
Expanded(
|
||||
child: Text("%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts),
|
||||
overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor()))), //todo
|
||||
overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor()))),
|
||||
])),
|
||||
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;
|
||||
});
|
||||
})
|
||||
],
|
||||
actions: getActions(context),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddContact,
|
||||
|
@ -72,6 +97,26 @@ 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)));
|
||||
}
|
||||
actions.add(
|
||||
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
|
||||
);
|
||||
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,7 +33,8 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
|
|||
: //dev
|
||||
MultiProvider(providers: [
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation!)!),
|
||||
ChangeNotifierProvider.value(
|
||||
value: flwtch.selectedConversation != null ? Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation!)! : ContactInfoState("", "")),
|
||||
], child: Container(child: MessageView())),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -34,6 +34,7 @@ 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(
|
||||
|
@ -93,9 +94,17 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
);
|
||||
}).toList())),
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.settingUIColumnLandscape, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.settingUIColumnLandscape,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
softWrap: true,
|
||||
style: TextStyle(color: settings.current().mainTextColor()),
|
||||
),
|
||||
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
||||
trailing: DropdownButton(
|
||||
trailing: Container(
|
||||
width: 200.0,
|
||||
child: DropdownButton(
|
||||
isExpanded: true,
|
||||
value: settings.uiColumnModeLandscape.toString(),
|
||||
onChanged: (String? newValue) {
|
||||
settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!);
|
||||
|
@ -104,9 +113,12 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
items: Settings.uiColumnModeOptions(true).map<DropdownMenuItem<String>>((DualpaneMode value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value.toString(),
|
||||
child: Text(Settings.uiColumnModeToString(value, context)),
|
||||
child: Text(
|
||||
Settings.uiColumnModeToString(value, context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}).toList())),
|
||||
}).toList()))),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.blockUnknownLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionBlockUnknownConnections),
|
||||
|
@ -166,12 +178,17 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
],
|
||||
)),
|
||||
AboutListTile(
|
||||
icon: Icon(Icons.info, color: settings.current().mainTextColor()),
|
||||
icon: appIcon,
|
||||
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',
|
||||
),
|
||||
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)),
|
||||
)
|
||||
]),
|
||||
]))));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -173,6 +173,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
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,7 +3,7 @@ import 'dart:io';
|
|||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:cwtch/models/messages/quotedmessage.dart';
|
||||
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||
import 'package:cwtch/widgets/profileimage.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
@ -13,6 +13,7 @@ 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 '../main.dart';
|
||||
import '../model.dart';
|
||||
|
@ -29,14 +30,35 @@ class _MessageViewState extends State<MessageView> {
|
|||
final ctrlrCompose = TextEditingController();
|
||||
final focusNode = FocusNode();
|
||||
String selectedContact = "";
|
||||
ItemPositionsListener scrollListener = ItemPositionsListener.create();
|
||||
ItemScrollController scrollController = ItemScrollController();
|
||||
|
||||
// @override
|
||||
// void didChangeDependencies() {
|
||||
// super.didChangeDependencies();
|
||||
// if (Provider.of<ContactInfoState>(context, listen: false).unreadMessages > 0) {
|
||||
// Provider.of<ContactInfoState>(context, listen: false).unreadMessages = 0;
|
||||
// }
|
||||
// }
|
||||
@override
|
||||
void initState() {
|
||||
scrollListener.itemPositions.addListener(() {
|
||||
var first = scrollListener.itemPositions.value.first.index;
|
||||
var last = scrollListener.itemPositions.value.last.index;
|
||||
// sometimes these go hi->lo and sometimes they go lo->hi because [who tf knows]
|
||||
if ((first == 0 || last == 0) && Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true) {
|
||||
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 > 8 && appState.unreadMessagesBelow == false) {
|
||||
WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((timeStamp) {
|
||||
appState.unreadMessagesBelow = true;
|
||||
});
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
@ -47,10 +69,22 @@ 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 appState = Provider.of<AppState>(context);
|
||||
return WillPopScope(
|
||||
onWillPop: _onWillPop,
|
||||
child: Scaffold(
|
||||
floatingActionButton: appState.unreadMessagesBelow
|
||||
? FloatingActionButton(
|
||||
child: Icon(Icons.arrow_downward),
|
||||
onPressed: () {
|
||||
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,
|
||||
|
@ -65,25 +99,36 @@ class _MessageViewState extends State<MessageView> {
|
|||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(Provider.of<ContactInfoState>(context).nickname)
|
||||
Expanded(
|
||||
child: Text(
|
||||
Provider.of<ContactInfoState>(context).nickname,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
]),
|
||||
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: Icon(CwtchIcons.send_invite, size: 24),
|
||||
tooltip: AppLocalizations.of(context)!.sendInvite,
|
||||
onPressed: () {
|
||||
_modalSendInvitation(context);
|
||||
}),
|
||||
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()),
|
||||
body: Padding(padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0), child: MessageList(scrollController, scrollListener)),
|
||||
bottomSheet: _buildComposeBox(),
|
||||
));
|
||||
}
|
||||
|
||||
Future<bool> _onWillPop() async {
|
||||
Provider.of<ContactInfoState>(context, listen: false).unreadMessages = 0;
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -118,7 +163,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 = "{\"quotedHash\":\"" + contentHash + "\",\"body\":\"" + ctrlrCompose.value.text + "\"}";
|
||||
var quotedMessage = jsonEncode(QuotedMessageStructure(contentHash, ctrlrCompose.value.text));
|
||||
ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage);
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
|
@ -155,6 +200,8 @@ class _MessageViewState extends State<MessageView> {
|
|||
}
|
||||
|
||||
Widget _buildComposeBox() {
|
||||
bool isOffline = Provider.of<ContactInfoState>(context).isOnline() == false;
|
||||
|
||||
var composeBox = Container(
|
||||
color: Provider.of<Settings>(context).theme.backgroundMainColor(),
|
||||
padding: EdgeInsets.all(2),
|
||||
|
@ -168,6 +215,8 @@ class _MessageViewState extends State<MessageView> {
|
|||
child: RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey: handleKeyPress,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: TextFormField(
|
||||
key: Key('txtCompose'),
|
||||
controller: ctrlrCompose,
|
||||
|
@ -175,27 +224,23 @@ class _MessageViewState extends State<MessageView> {
|
|||
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,
|
||||
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,
|
||||
),
|
||||
suffixIcon: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(primary: Provider.of<Settings>(context).theme.defaultButtonColor()),
|
||||
child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
onPressed: isOffline ? null : _sendMessage,
|
||||
))),
|
||||
)))),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -114,7 +114,11 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
value: Provider.of<ContactInfoState>(context).isBlocked,
|
||||
onChanged: (bool blocked) {
|
||||
// Save local blocked status
|
||||
Provider.of<ContactInfoState>(context, listen: false).isBlocked = blocked;
|
||||
if (blocked) {
|
||||
Provider.of<ContactInfoState>(context, listen: false).authorization = ContactAuthorization.blocked;
|
||||
} else {
|
||||
Provider.of<ContactInfoState>(context, listen: false).authorization = ContactAuthorization.unknown;
|
||||
}
|
||||
|
||||
// Save New peer authorization
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
|
@ -216,7 +220,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
showAlertDialog(BuildContext context) {
|
||||
// set up the buttons
|
||||
Widget cancelButton = TextButton(
|
||||
child: Text("Cancel"),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
|
@ -230,6 +234,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
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
|
||||
});
|
||||
},
|
||||
|
|
|
@ -39,7 +39,6 @@ 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,14 +48,9 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
backgroundColor: settings.theme.backgroundMainColor(),
|
||||
appBar: AppBar(
|
||||
title: Row(children: [
|
||||
Image(
|
||||
image: AssetImage("assets/core/knott-white.png"),
|
||||
filterQuality: FilterQuality.medium,
|
||||
isAntiAlias: true,
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorBlendMode: BlendMode.dstIn,
|
||||
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
|
||||
Icon(
|
||||
CwtchIcons.cwtch_knott,
|
||||
size: 36,
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
|
@ -95,6 +89,7 @@ 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,
|
||||
));
|
||||
|
|
|
@ -54,7 +54,7 @@ class _TorStatusView extends State<TorStatusView> {
|
|||
),
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.torVersion),
|
||||
subtitle: Text(torStatus.version),
|
||||
subtitle: SelectableText(torStatus.version),
|
||||
),
|
||||
]))));
|
||||
});
|
||||
|
|
|
@ -37,6 +37,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
|
|||
readOnly: widget.readonly,
|
||||
showCursor: !widget.readonly,
|
||||
focusNode: _focusNode,
|
||||
enableIMEPersonalizedLearning: false,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: widget.onPressed,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
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';
|
||||
|
@ -22,10 +25,11 @@ 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(2.0), //border size
|
||||
padding: const EdgeInsets.all(6.0), //border size
|
||||
child: ProfileImage(
|
||||
badgeCount: contact.unreadMessages,
|
||||
badgeColor: Provider.of<Settings>(context).theme.portraitContactBadgeColor(),
|
||||
|
@ -56,6 +60,11 @@ 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(),
|
||||
)),
|
||||
Text(contact.onion,
|
||||
style: TextStyle(color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor() : Provider.of<Settings>(context).theme.mainTextColor())),
|
||||
],
|
||||
|
@ -93,40 +102,11 @@ class _ContactRowState extends State<ContactRow> {
|
|||
),
|
||||
]),
|
||||
onTap: () {
|
||||
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);
|
||||
});
|
||||
selectConversation(context, 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
|
||||
|
@ -149,9 +129,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().format(date.toLocal());
|
||||
return DateFormat.yMd(Platform.localeName).format(date.toLocal());
|
||||
}
|
||||
// Otherwise just state the time.
|
||||
return DateFormat.Hm().format(date.toLocal());
|
||||
return DateFormat.Hm(Platform.localeName).format(date.toLocal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
|
@ -19,8 +20,9 @@ class InvitationBubble extends StatefulWidget {
|
|||
final int overlay;
|
||||
final String inviteTarget;
|
||||
final String inviteNick;
|
||||
final String invite;
|
||||
|
||||
InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick);
|
||||
InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick, this.invite);
|
||||
|
||||
@override
|
||||
InvitationBubbleState createState() => InvitationBubbleState();
|
||||
|
@ -38,7 +40,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().add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
|
||||
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 = "";
|
||||
|
@ -136,7 +138,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
void _btnAccept() {
|
||||
setState(() {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, widget.inviteTarget);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, widget.invite);
|
||||
isAccepted = true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -28,7 +30,7 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
// var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
||||
|
||||
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
||||
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
||||
prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(messageDate.toLocal());
|
||||
|
||||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
|
|
|
@ -45,6 +45,5 @@ 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";
|
||||
|
@ -41,6 +41,8 @@ class _MessageListState extends State<MessageList> {
|
|||
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
|
||||
|
@ -50,12 +52,11 @@ class _MessageListState extends State<MessageList> {
|
|||
: (showEphemeralWarning
|
||||
? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center)
|
||||
:
|
||||
// We are not allowed to put null here, so put an empty text widge
|
||||
// We are not allowed to put null here, so put an empty text widget
|
||||
Text("")),
|
||||
)),
|
||||
))),
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: ctrlr1,
|
||||
child: Container(
|
||||
// Only show broken heart is the contact is offline...
|
||||
decoration: BoxDecoration(
|
||||
|
@ -68,8 +69,10 @@ class _MessageListState extends State<MessageList> {
|
|||
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementTextColor(), BlendMode.srcIn))),
|
||||
// Don't load messages for syncing server...
|
||||
child: loadMessages
|
||||
? ListView.builder(
|
||||
controller: ctrlr1,
|
||||
? ScrollablePositionedList.builder(
|
||||
itemPositionsListener: widget.scrollListener,
|
||||
itemScrollController: widget.scrollController,
|
||||
initialScrollIndex: Provider.of<AppState>(outerContext, listen: false).initialScrollIndex,
|
||||
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) {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
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';
|
||||
|
||||
|
@ -18,34 +21,127 @@ class MessageRow extends StatefulWidget {
|
|||
MessageRowState createState() => MessageRowState();
|
||||
}
|
||||
|
||||
class MessageRowState extends State<MessageRow> {
|
||||
class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMixin {
|
||||
bool showMenu = false;
|
||||
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();
|
||||
}
|
||||
|
||||
@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: this.showMenu,
|
||||
maintainSize: true,
|
||||
maintainAnimation: true,
|
||||
maintainState: true,
|
||||
maintainInteractivity: false,
|
||||
child: IconButton(
|
||||
tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
|
||||
onPressed: () {
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context).messageIndex;
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
},
|
||||
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor())));
|
||||
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
||||
Widget wdgSpacer = Flexible(child: SizedBox(width: 60, height: 10));
|
||||
var widgetRow = <Widget>[];
|
||||
|
||||
if (fromMe) {
|
||||
widgetRow = <Widget>[
|
||||
wdgSpacer,
|
||||
wdgIcons,
|
||||
Flexible(flex: 3, fit: FlexFit.loose, child: widget.child),
|
||||
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),
|
||||
overlayColor: MaterialStateProperty.all(blockedMessageBackground),
|
||||
),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.showMessageButton + '\u202F',
|
||||
style: TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
this.showBlockedMessage = true;
|
||||
});
|
||||
})),
|
||||
]))),
|
||||
wdgIcons,
|
||||
wdgSpacer,
|
||||
];
|
||||
} else {
|
||||
var contact = Provider.of<ContactInfoState>(context);
|
||||
Widget wdgPortrait = GestureDetector(
|
||||
onTap: _btnAdd,
|
||||
onTap: isContact ? _btnGoto : _btnAdd,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: ProfileImage(
|
||||
|
@ -54,19 +150,19 @@ class MessageRowState extends State<MessageRow> {
|
|||
//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,
|
||||
Flexible(flex: 3, fit: FlexFit.loose, child: widget.child),
|
||||
actualMessage,
|
||||
wdgIcons,
|
||||
wdgSpacer,
|
||||
];
|
||||
}
|
||||
|
||||
var size = MediaQuery.of(context).size;
|
||||
return MouseRegion(
|
||||
// For desktop...
|
||||
|
||||
onHover: (event) {
|
||||
setState(() {
|
||||
this.showMenu = true;
|
||||
|
@ -78,12 +174,59 @@ class MessageRowState extends State<MessageRow> {
|
|||
});
|
||||
},
|
||||
child: GestureDetector(
|
||||
|
||||
// Swipe to quote
|
||||
onHorizontalDragEnd: (details) {
|
||||
onPanUpdate: (details) {
|
||||
setState(() {
|
||||
_dragAlignment += Alignment(
|
||||
details.delta.dx / (size.width * 0.5),
|
||||
0,
|
||||
);
|
||||
});
|
||||
},
|
||||
onPanDown: (details) {
|
||||
_controller.stop();
|
||||
},
|
||||
onPanEnd: (details) {
|
||||
_runAnimation(details.velocity.pixelsPerSecond, size);
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
},
|
||||
child: Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow))));
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2),
|
||||
child: Align(
|
||||
widthFactor: 1,
|
||||
alignment: _dragAlignment,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: widgetRow,
|
||||
)))));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void _btnAdd() {
|
||||
|
@ -92,19 +235,49 @@ class MessageRowState extends State<MessageRow> {
|
|||
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 = TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = TextButton(
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
child: Text(AppLocalizations.of(context)!.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;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ 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,7 +5,8 @@ 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});
|
||||
ProfileImage(
|
||||
{required this.imagePath, required this.diameter, required this.border, this.badgeCount = 0, required this.badgeColor, required this.badgeTextColor, this.maskOut = false, this.tooltip = ""});
|
||||
final double diameter;
|
||||
final String imagePath;
|
||||
final Color border;
|
||||
|
@ -13,6 +14,7 @@ class ProfileImage extends StatefulWidget {
|
|||
final Color badgeColor;
|
||||
final Color badgeTextColor;
|
||||
final bool maskOut;
|
||||
final String tooltip;
|
||||
|
||||
@override
|
||||
_ProfileImageState createState() => _ProfileImageState();
|
||||
|
@ -21,19 +23,7 @@ class ProfileImage extends StatefulWidget {
|
|||
class _ProfileImageState extends State<ProfileImage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RepaintBoundary(
|
||||
child: Stack(children: [
|
||||
ClipOval(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Container(
|
||||
width: widget.diameter,
|
||||
height: widget.diameter,
|
||||
color: widget.border,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2.0), //border size
|
||||
child: ClipOval(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Image(
|
||||
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
|
||||
|
@ -46,7 +36,19 @@ class _ProfileImageState extends State<ProfileImage> {
|
|||
isAntiAlias: true,
|
||||
width: widget.diameter,
|
||||
height: widget.diameter,
|
||||
))))),
|
||||
);
|
||||
|
||||
return RepaintBoundary(
|
||||
child: Stack(children: [
|
||||
ClipOval(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Container(
|
||||
width: widget.diameter,
|
||||
height: widget.diameter,
|
||||
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))))),
|
||||
Visibility(
|
||||
visible: widget.badgeCount > 0,
|
||||
child: Positioned(
|
||||
|
|
|
@ -22,12 +22,13 @@ 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(2.0), //border size
|
||||
padding: const EdgeInsets.all(6.0), //border size
|
||||
child: ProfileImage(
|
||||
badgeCount: 0,
|
||||
badgeColor: Provider.of<Settings>(context).theme.portraitProfileBadgeColor(),
|
||||
|
|
|
@ -61,13 +61,14 @@ 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))),
|
||||
Center(widthFactor: 1.0, child: qMessage.getPreviewWidget(context))
|
||||
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)))
|
||||
]));
|
||||
} catch (e) {
|
||||
print(e);
|
||||
|
|
|
@ -39,6 +39,7 @@ class _CwtchTextFieldState extends State<CwtchTextField> {
|
|||
validator: widget.validator,
|
||||
onChanged: widget.onChanged,
|
||||
autofocus: widget.autofocus,
|
||||
enableIMEPersonalizedLearning: false,
|
||||
focusNode: _focusNode,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.labelText,
|
||||
|
|
BIN
linux/cwtch.png
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 51 KiB |
13
pubspec.lock
|
@ -21,7 +21,7 @@ packages:
|
|||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
version: "2.8.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -181,7 +181,7 @@ packages:
|
|||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.7.0"
|
||||
msix:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -329,6 +329,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
scrollable_positioned_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: scrollable_positioned_list
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0-nullsafety.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -375,7 +382,7 @@ packages:
|
|||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
version: "0.4.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.0.0+16
|
||||
version: 1.1.1+18
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
@ -40,6 +40,7 @@ dependencies:
|
|||
glob: any
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
||||
|
||||
dev_dependencies:
|
||||
msix: ^2.1.3
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
flutter format -l 200 lib
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/sh
|
||||
|
||||
orig=assets/core/knott-white.svg
|
||||
transparent=assets/core/knott-transparent.svg
|
||||
|
||||
# app icon used in profile manager bar
|
||||
inkscape -w 512 -h 512 -o assets/core/knott-white.png $orig
|
||||
inkscape -w 512 -h 512 -o assets/core/knott-transparent.png transparent
|
||||
|
||||
# linux deploy icon
|
||||
inkscape -w 512 -h 512 -o linux/cwtch.png $orig
|
||||
inkscape -w 512 -h 512 -o cwtch.png $orig
|
||||
|
||||
# windows icons
|
||||
inkscape -w 256 -h 256 -o windows/runner/resources/knot_256.png $orig
|
||||
convert windows/runner/resources/knot_256.png windows/runner/resources/knot_256.ico
|
||||
inkscape -w 128 -h 128 -o windows/runner/resources/knot_128.png $orig
|
||||
convert windows/runner/resources/knot_128.png windows/runner/resources/knot_128.ico
|
||||
inkscape -w 64 -h 64 -o windows/runner/resources/knot_64.png $orig
|
||||
convert windows/runner/resources/knot_64.png windows/runner/resources/knot_64.ico
|
||||
inkscape -w 48 -h 48 -o windows/runner/resources/knot_48.png $orig
|
||||
convert windows/runner/resources/knot_48.png windows/runner/resources/knot_48.ico
|
||||
inkscape -w 32 -h 32 -o windows/runner/resources/knot_32.png $orig
|
||||
convert windows/runner/resources/knot_32.png windows/runner/resources/knot_32.ico
|
||||
inkscape -w 16 -h 16 -o windows/runner/resources/knot_16.png $orig
|
||||
convert windows/runner/resources/knot_16.png windows/runner/resources/knot_16.ico
|
||||
|
||||
# android icons
|
||||
inkscape -w 48 -h 48 -o android/app/src/main/res/mipmap-mdpi/knott_transparent.png $transparent
|
||||
inkscape -w 48 -h 48 -o android/app/src/main/res/mipmap-mdpi/knott.png $orig
|
||||
inkscape -w 72 -h 72 -o android/app/src/main/res/mipmap-hdpi/knott_transparent.png $transparent
|
||||
inkscape -w 72 -h 72 -o android/app/src/main/res/mipmap-hdpi/knott.png $orig
|
||||
inkscape -w 96 -h 96 -o android/app/src/main/res/mipmap-xhdpi/knott_transparent.png $transparent
|
||||
inkscape -w 96 -h 96 -o android/app/src/main/res/mipmap-xhdpi/knott.png $orig
|
||||
inkscape -w 144 -h 144 -o android/app/src/main/res/mipmap-xxhdpi/knott_transparent.png $transparent
|
||||
inkscape -w 144 -h 144 -o android/app/src/main/res/mipmap-xxhdpi/knott.png $orig
|
||||
inkscape -w 192 -h 192 -o android/app/src/main/res/mipmap-xxxhdpi/knott_transparent.png $transparent
|
||||
inkscape -w 192 -h 192 -o android/app/src/main/res/mipmap-xxxhdpi/knott.png $orig
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 899 B |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 5.3 KiB |