Merge pull request 'UI Fix Bulk' (#96) from ui_fixes into trunk
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #96
|
@ -1 +1 @@
|
|||
v0.0.2-36-g84d85b7-2021-05-20-01-14
|
||||
v0.0.2-43-ga98b5de-2021-05-28-21-21
|
|
@ -46,7 +46,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||
and then use:
|
||||
|
||||
```
|
||||
Text(AppLocalizations.of(context).stringIdentifer),
|
||||
Text(AppLocalizations.of(context)!.stringIdentifer),
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
|
13
SPEC.md
|
@ -129,3 +129,16 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
|||
- [ ] Leave Group
|
||||
- [ ] Pressing Back should go back to the message pane for the group
|
||||
|
||||
|
||||
|
||||
## Android Requirements Notes
|
||||
|
||||
What are our expectations here?
|
||||
|
||||
- Can we periodically check groups in the background to power notifications?
|
||||
- Either way we need networking in the service not the main/UI thread.
|
||||
- We probably don't want to and very likely can't persist tor connections to peers indefinitely.
|
||||
- Neither google nor apple are very tolerant of apps that try to create their own push message infrastructure.
|
||||
|
||||
- "Aside": Retrieving a CallbackHandle for a method from PluginUtilities.getCallbackHandle has the side effect of populating a callback cache within the Flutter engine, as seen in the diagram above. This cache maps information required to retrieve callbacks to raw integer handles, which are simply hashes calculated based on the properties of the callback. This cache persists across launches, but be aware that callback lookups may fail if the callback is renamed or moved and PluginUtilities.getCallbackHandle is not called for the updated callback.
|
||||
- The above seems to imply that there is a persistent cache somewhere that can affect code between launches...the ramifications of this are ?!?!
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="Cwtch"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/knott">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
|
|
@ -178,6 +178,17 @@ class MainActivity: FlutterActivity() {
|
|||
val value = (call.argument("value") as? String) ?: "";
|
||||
Cwtch.setGroupAttribute(profile, groupHandle, key, value);
|
||||
}
|
||||
"CreateGroup" -> {
|
||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
||||
val server = (call.argument("server") as? String) ?: "";
|
||||
val groupName = (call.argument("groupname") as? String) ?: "";
|
||||
Cwtch.createGroup(profile, server, groupName);
|
||||
}
|
||||
"LeaveGroup" -> {
|
||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
||||
val groupHandle = (call.argument("groupHandle") as? String) ?: "";
|
||||
Cwtch.leaveGroup(profile, groupHandle);
|
||||
}
|
||||
"RejectInvite" -> {
|
||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
||||
val groupHandle = (call.argument("groupHandle") as? String) ?: "";
|
||||
|
|
|
@ -0,0 +1,399 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 500 500"
|
||||
style="enable-background:new 0 0 500 500;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="Cwtch_knott_white.svg"
|
||||
inkscape:export-filename="/home/sarah/PARA/projects/cwtch/assets/core/knott-white.png"
|
||||
inkscape:export-xdpi="98.300003"
|
||||
inkscape:export-ydpi="98.300003"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
|
||||
id="metadata35"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs33" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
id="namedview31"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3350176"
|
||||
inkscape:cx="-56.414859"
|
||||
inkscape:cy="254.41396"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="31"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#010101;}
|
||||
.st2{fill:#AF9CBA;}
|
||||
.st3{clip-path:url(#SVGID_2_);}
|
||||
.st4{clip-path:url(#SVGID_3_);}
|
||||
.st5{clip-path:url(#SVGID_4_);}
|
||||
.st6{clip-path:url(#SVGID_6_);}
|
||||
.st7{clip-path:url(#SVGID_7_);}
|
||||
.st8{clip-path:url(#SVGID_8_);}
|
||||
.st9{clip-path:url(#SVGID_10_);}
|
||||
.st10{clip-path:url(#SVGID_11_);}
|
||||
.st11{clip-path:url(#SVGID_12_);}
|
||||
.st12{clip-path:url(#SVGID_14_);}
|
||||
.st13{clip-path:url(#SVGID_15_);}
|
||||
.st14{clip-path:url(#SVGID_16_);}
|
||||
.st15{clip-path:url(#SVGID_18_);}
|
||||
.st16{clip-path:url(#SVGID_19_);}
|
||||
.st17{clip-path:url(#SVGID_20_);}
|
||||
.st18{clip-path:url(#SVGID_22_);}
|
||||
.st19{clip-path:url(#SVGID_23_);}
|
||||
.st20{clip-path:url(#SVGID_24_);}
|
||||
.st21{clip-path:url(#SVGID_26_);}
|
||||
.st22{clip-path:url(#SVGID_27_);}
|
||||
.st23{clip-path:url(#SVGID_28_);}
|
||||
.st24{clip-path:url(#SVGID_30_);}
|
||||
.st25{clip-path:url(#SVGID_31_);}
|
||||
.st26{clip-path:url(#SVGID_32_);}
|
||||
.st27{clip-path:url(#SVGID_34_);}
|
||||
.st28{clip-path:url(#SVGID_35_);}
|
||||
.st29{clip-path:url(#SVGID_36_);}
|
||||
.st30{clip-path:url(#SVGID_38_);}
|
||||
.st31{clip-path:url(#SVGID_39_);}
|
||||
.st32{clip-path:url(#SVGID_40_);}
|
||||
.st33{clip-path:url(#SVGID_42_);}
|
||||
.st34{clip-path:url(#SVGID_43_);}
|
||||
.st35{clip-path:url(#SVGID_44_);}
|
||||
.st36{clip-path:url(#SVGID_46_);}
|
||||
.st37{clip-path:url(#SVGID_47_);}
|
||||
.st38{clip-path:url(#SVGID_48_);}
|
||||
.st39{clip-path:url(#SVGID_50_);}
|
||||
.st40{clip-path:url(#SVGID_51_);}
|
||||
.st41{clip-path:url(#SVGID_52_);}
|
||||
.st42{clip-path:url(#SVGID_54_);}
|
||||
.st43{clip-path:url(#SVGID_55_);}
|
||||
.st44{clip-path:url(#SVGID_56_);}
|
||||
.st45{clip-path:url(#SVGID_58_);}
|
||||
.st46{clip-path:url(#SVGID_59_);}
|
||||
.st47{clip-path:url(#SVGID_60_);}
|
||||
.st48{clip-path:url(#SVGID_62_);}
|
||||
.st49{clip-path:url(#SVGID_63_);}
|
||||
.st50{clip-path:url(#SVGID_64_);}
|
||||
.st51{clip-path:url(#SVGID_66_);}
|
||||
.st52{clip-path:url(#SVGID_67_);}
|
||||
.st53{clip-path:url(#SVGID_68_);}
|
||||
.st54{clip-path:url(#SVGID_70_);}
|
||||
.st55{clip-path:url(#SVGID_71_);}
|
||||
.st56{clip-path:url(#SVGID_72_);}
|
||||
.st57{clip-path:url(#SVGID_74_);}
|
||||
.st58{clip-path:url(#SVGID_75_);}
|
||||
.st59{clip-path:url(#SVGID_76_);}
|
||||
.st60{clip-path:url(#SVGID_78_);}
|
||||
.st61{clip-path:url(#SVGID_79_);}
|
||||
.st62{clip-path:url(#SVGID_80_);}
|
||||
.st63{clip-path:url(#SVGID_82_);}
|
||||
.st64{clip-path:url(#SVGID_83_);}
|
||||
.st65{clip-path:url(#SVGID_84_);}
|
||||
.st66{clip-path:url(#SVGID_86_);}
|
||||
.st67{clip-path:url(#SVGID_87_);}
|
||||
.st68{clip-path:url(#SVGID_88_);}
|
||||
.st69{clip-path:url(#SVGID_90_);}
|
||||
.st70{clip-path:url(#SVGID_91_);}
|
||||
.st71{clip-path:url(#SVGID_92_);}
|
||||
.st72{clip-path:url(#SVGID_94_);}
|
||||
.st73{clip-path:url(#SVGID_95_);}
|
||||
.st74{clip-path:url(#SVGID_96_);}
|
||||
.st75{clip-path:url(#SVGID_98_);}
|
||||
.st76{clip-path:url(#SVGID_99_);}
|
||||
.st77{clip-path:url(#SVGID_100_);}
|
||||
.st78{clip-path:url(#SVGID_102_);}
|
||||
.st79{clip-path:url(#SVGID_103_);}
|
||||
.st80{clip-path:url(#SVGID_104_);}
|
||||
.st81{clip-path:url(#SVGID_106_);}
|
||||
.st82{clip-path:url(#SVGID_107_);}
|
||||
.st83{clip-path:url(#SVGID_108_);}
|
||||
.st84{clip-path:url(#SVGID_110_);}
|
||||
.st85{clip-path:url(#SVGID_111_);}
|
||||
.st86{clip-path:url(#SVGID_112_);}
|
||||
.st87{clip-path:url(#SVGID_114_);}
|
||||
.st88{clip-path:url(#SVGID_115_);}
|
||||
.st89{clip-path:url(#SVGID_116_);}
|
||||
.st90{clip-path:url(#SVGID_118_);}
|
||||
.st91{clip-path:url(#SVGID_119_);}
|
||||
.st92{clip-path:url(#SVGID_120_);}
|
||||
.st93{clip-path:url(#SVGID_122_);}
|
||||
.st94{clip-path:url(#SVGID_123_);}
|
||||
.st95{clip-path:url(#SVGID_124_);}
|
||||
.st96{clip-path:url(#SVGID_126_);}
|
||||
.st97{clip-path:url(#SVGID_127_);}
|
||||
.st98{clip-path:url(#SVGID_128_);}
|
||||
.st99{fill:#64317C;}
|
||||
.st100{clip-path:url(#SVGID_130_);}
|
||||
.st101{clip-path:url(#SVGID_131_);}
|
||||
.st102{clip-path:url(#SVGID_132_);}
|
||||
.st103{clip-path:url(#SVGID_134_);}
|
||||
.st104{clip-path:url(#SVGID_135_);}
|
||||
.st105{clip-path:url(#SVGID_136_);}
|
||||
.st106{clip-path:url(#SVGID_138_);}
|
||||
.st107{clip-path:url(#SVGID_139_);}
|
||||
.st108{clip-path:url(#SVGID_140_);}
|
||||
.st109{clip-path:url(#SVGID_142_);}
|
||||
.st110{clip-path:url(#SVGID_143_);}
|
||||
.st111{clip-path:url(#SVGID_144_);}
|
||||
.st112{clip-path:url(#SVGID_146_);}
|
||||
.st113{clip-path:url(#SVGID_147_);}
|
||||
.st114{clip-path:url(#SVGID_148_);}
|
||||
.st115{clip-path:url(#SVGID_150_);}
|
||||
.st116{clip-path:url(#SVGID_151_);}
|
||||
.st117{clip-path:url(#SVGID_152_);}
|
||||
.st118{clip-path:url(#SVGID_154_);}
|
||||
.st119{clip-path:url(#SVGID_155_);}
|
||||
.st120{clip-path:url(#SVGID_156_);}
|
||||
.st121{clip-path:url(#SVGID_158_);}
|
||||
.st122{clip-path:url(#SVGID_159_);}
|
||||
.st123{clip-path:url(#SVGID_160_);}
|
||||
.st124{clip-path:url(#SVGID_162_);}
|
||||
.st125{clip-path:url(#SVGID_163_);}
|
||||
.st126{clip-path:url(#SVGID_164_);}
|
||||
.st127{clip-path:url(#SVGID_166_);}
|
||||
.st128{clip-path:url(#SVGID_167_);}
|
||||
.st129{clip-path:url(#SVGID_168_);}
|
||||
.st130{clip-path:url(#SVGID_170_);}
|
||||
.st131{clip-path:url(#SVGID_171_);}
|
||||
.st132{clip-path:url(#SVGID_172_);}
|
||||
.st133{clip-path:url(#SVGID_174_);}
|
||||
.st134{clip-path:url(#SVGID_175_);}
|
||||
.st135{clip-path:url(#SVGID_176_);}
|
||||
.st136{clip-path:url(#SVGID_178_);}
|
||||
.st137{clip-path:url(#SVGID_179_);}
|
||||
.st138{clip-path:url(#SVGID_180_);}
|
||||
.st139{clip-path:url(#SVGID_182_);}
|
||||
.st140{clip-path:url(#SVGID_183_);}
|
||||
.st141{clip-path:url(#SVGID_184_);}
|
||||
.st142{clip-path:url(#SVGID_186_);}
|
||||
.st143{clip-path:url(#SVGID_187_);}
|
||||
.st144{clip-path:url(#SVGID_188_);}
|
||||
.st145{clip-path:url(#SVGID_190_);}
|
||||
.st146{clip-path:url(#SVGID_191_);}
|
||||
.st147{clip-path:url(#SVGID_192_);}
|
||||
.st148{clip-path:url(#SVGID_194_);}
|
||||
.st149{clip-path:url(#SVGID_195_);}
|
||||
.st150{clip-path:url(#SVGID_196_);}
|
||||
.st151{clip-path:url(#SVGID_198_);}
|
||||
.st152{clip-path:url(#SVGID_199_);}
|
||||
.st153{clip-path:url(#SVGID_200_);}
|
||||
.st154{clip-path:url(#SVGID_202_);}
|
||||
.st155{clip-path:url(#SVGID_203_);}
|
||||
.st156{clip-path:url(#SVGID_204_);}
|
||||
.st157{clip-path:url(#SVGID_206_);}
|
||||
.st158{clip-path:url(#SVGID_207_);}
|
||||
.st159{clip-path:url(#SVGID_208_);}
|
||||
.st160{clip-path:url(#SVGID_210_);}
|
||||
.st161{clip-path:url(#SVGID_211_);}
|
||||
.st162{clip-path:url(#SVGID_212_);}
|
||||
.st163{clip-path:url(#SVGID_214_);}
|
||||
.st164{clip-path:url(#SVGID_215_);}
|
||||
.st165{clip-path:url(#SVGID_216_);}
|
||||
.st166{clip-path:url(#SVGID_218_);}
|
||||
.st167{clip-path:url(#SVGID_219_);}
|
||||
.st168{clip-path:url(#SVGID_220_);}
|
||||
.st169{clip-path:url(#SVGID_222_);}
|
||||
.st170{clip-path:url(#SVGID_223_);}
|
||||
.st171{clip-path:url(#SVGID_224_);}
|
||||
.st172{clip-path:url(#SVGID_226_);}
|
||||
.st173{clip-path:url(#SVGID_227_);}
|
||||
.st174{clip-path:url(#SVGID_228_);}
|
||||
.st175{clip-path:url(#SVGID_230_);}
|
||||
.st176{clip-path:url(#SVGID_231_);}
|
||||
.st177{clip-path:url(#SVGID_232_);}
|
||||
.st178{clip-path:url(#SVGID_234_);}
|
||||
.st179{clip-path:url(#SVGID_235_);}
|
||||
.st180{clip-path:url(#SVGID_236_);}
|
||||
.st181{clip-path:url(#SVGID_238_);}
|
||||
.st182{clip-path:url(#SVGID_239_);}
|
||||
.st183{clip-path:url(#SVGID_240_);}
|
||||
.st184{clip-path:url(#SVGID_242_);}
|
||||
.st185{clip-path:url(#SVGID_243_);}
|
||||
.st186{clip-path:url(#SVGID_244_);}
|
||||
.st187{clip-path:url(#SVGID_246_);}
|
||||
.st188{clip-path:url(#SVGID_247_);}
|
||||
.st189{clip-path:url(#SVGID_248_);}
|
||||
.st190{clip-path:url(#SVGID_250_);}
|
||||
.st191{clip-path:url(#SVGID_251_);}
|
||||
.st192{clip-path:url(#SVGID_252_);}
|
||||
.st193{clip-path:url(#SVGID_254_);}
|
||||
.st194{clip-path:url(#SVGID_255_);}
|
||||
.st195{clip-path:url(#SVGID_256_);}
|
||||
</style>
|
||||
<g
|
||||
transform="translate(2.1186441)"
|
||||
id="g61"
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none">
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path37"
|
||||
d="m 465.94,209.46 c -0.72,3.49 -2.35,7.45 -4.95,12.03 l 21.16,21.16 c 2.83,-5.51 7.14,-15.16 9.47,-26.61 4.76,-23.39 -0.6,-43.02 -15.93,-58.35 -28.01,-28.01 -57.26,-30.61 -94.84,-8.43 -9.04,5.34 -18.64,12.24 -28.55,20.51 -22.58,18.86 -47.88,45.85 -75.22,80.23 17.91,22.52 35,41.95 50.83,57.79 7.26,7.26 14.4,13.91 21.27,19.81 l 18.76,-18.76 c -6.83,-5.78 -13.98,-12.41 -21.32,-19.75 -10.39,-10.39 -21.5,-22.57 -32.99,-36.19 l -2.43,-2.9 2.44,-2.89 c 21.03,-24.91 40.59,-44.8 58.14,-59.09 11.14,-9.08 21.56,-15.98 30.98,-20.53 8.14,-3.92 15.55,-6.11 22.03,-6.49 3.16,-0.19 8.05,-0.15 14,2.3 5.84,2.4 11.79,6.68 18.2,13.09 2.39,2.39 5.82,6.25 7.91,12.08 2.16,6.02 2.51,12.85 1.07,20.88 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path39"
|
||||
d="M 355.44,371.4 450,276.85 c -5.56,-8.23 -12.85,-16.22 -16.93,-20.49 l -93.37,93.37 c 6.02,7.42 11.29,14.68 15.74,21.67 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path41"
|
||||
d="m 475.69,342.31 c 15.32,-15.32 20.68,-34.95 15.93,-58.34 -3.3,-16.23 -10.57,-28.84 -11.98,-31.18 l -75.82,-75.82 c -6.95,3.64 -14.5,8.63 -22.52,14.89 l 58.13,58.14 -0.03,0.03 c 4.8,5.01 13.98,15.11 20.13,25.07 3.75,6.08 5.87,11.21 6.46,15.66 1.42,7.98 1.06,14.77 -1.09,20.77 -2.09,5.83 -5.51,9.68 -7.91,12.08 -6.83,6.83 -13.16,11.26 -19.37,13.55 -6.33,2.34 -11.55,2.09 -15.39,1.6 -5.05,-0.63 -10.62,-2.28 -16.58,-4.9 L 385.9,353.6 c 35.17,19.04 63.04,15.46 89.79,-11.29 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path43"
|
||||
d="m 311.53,35.11 c 5.83,2.09 9.68,5.51 12.08,7.91 6.83,6.83 11.26,13.16 13.55,19.37 2.34,6.33 2.09,11.55 1.6,15.39 -0.63,5.05 -2.28,10.62 -4.9,16.58 L 353.6,114.1 C 372.64,78.93 369.06,51.06 342.31,24.31 326.99,8.99 307.36,3.63 283.97,8.38 c -16.23,3.3 -28.84,10.57 -31.18,11.98 l -75.82,75.82 c 3.64,6.95 8.63,14.5 14.89,22.52 L 250,60.57 l 0.03,0.03 c 5.01,-4.8 15.11,-13.98 25.07,-20.13 6.08,-3.75 11.21,-5.87 15.66,-6.46 7.98,-1.41 14.77,-1.05 20.77,1.1 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path45"
|
||||
d="m 157.69,24.31 c -28.01,28.01 -30.61,57.26 -8.43,94.84 5.34,9.04 12.24,18.64 20.51,28.54 18.86,22.58 45.85,47.88 80.23,75.22 22.52,-17.91 41.95,-35 57.79,-50.83 7.26,-7.26 13.91,-14.4 19.81,-21.27 l -18.76,-18.76 c -5.78,6.83 -12.41,13.98 -19.75,21.32 -10.39,10.39 -22.57,21.49 -36.19,32.99 l -2.9,2.44 -2.89,-2.44 C 222.2,165.33 202.31,145.77 188.02,128.22 178.94,117.08 172.04,106.66 167.49,97.24 163.57,89.1 161.38,81.69 161,75.21 c -0.19,-3.16 -0.15,-8.05 2.3,-14 2.4,-5.84 6.68,-11.79 13.09,-18.2 2.39,-2.39 6.25,-5.82 12.08,-7.91 6.02,-2.16 12.85,-2.51 20.87,-1.07 l 0.11,0.02 c 3.49,0.72 7.45,2.35 12.03,4.95 L 242.64,17.84 C 237.13,15.01 227.48,10.7 216.02,8.37 192.64,3.63 173.01,8.99 157.69,24.31 Z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path47"
|
||||
d="m 276.85,50 c -8.23,5.56 -16.22,12.85 -20.49,16.92 l 93.37,93.37 c 7.42,-6.02 14.68,-11.29 21.67,-15.74 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path49"
|
||||
d="m 24.31,342.31 c 28.01,28.01 57.26,30.61 94.84,8.43 9.04,-5.34 18.64,-12.24 28.54,-20.51 22.58,-18.86 47.88,-45.85 75.22,-80.23 -17.91,-22.52 -35,-41.95 -50.83,-57.79 -7.26,-7.26 -14.4,-13.91 -21.27,-19.81 l -18.76,18.76 c 6.83,5.78 13.98,12.41 21.32,19.75 10.39,10.39 21.5,22.57 32.99,36.19 l 2.44,2.89 -2.44,2.89 c -21.03,24.91 -40.59,44.8 -58.14,59.09 -11.14,9.08 -21.56,15.98 -30.98,20.53 -8.14,3.92 -15.55,6.11 -22.03,6.49 -3.16,0.19 -8.05,0.15 -14,-2.3 -5.84,-2.4 -11.79,-6.68 -18.2,-13.09 -2.39,-2.39 -5.82,-6.25 -7.91,-12.08 -2.16,-6.02 -2.51,-12.85 -1.07,-20.88 l 0.02,-0.11 C 34.77,287.04 36.4,283.08 39,278.5 L 17.84,257.34 c -2.83,5.51 -7.14,15.17 -9.47,26.63 -4.74,23.39 0.62,43.02 15.94,58.34 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path51"
|
||||
d="m 60.57,250 0.03,-0.03 c -4.8,-5.02 -13.98,-15.11 -20.13,-25.07 -3.75,-6.08 -5.87,-11.21 -6.46,-15.66 -1.42,-7.98 -1.06,-14.77 1.09,-20.77 2.09,-5.83 5.51,-9.68 7.91,-12.08 6.83,-6.83 13.16,-11.26 19.37,-13.55 6.33,-2.34 11.55,-2.09 15.39,-1.6 5.05,0.63 10.62,2.28 16.58,4.9 L 114.09,146.4 C 78.92,127.36 51.05,130.94 24.3,157.69 8.99,173.01 3.63,192.64 8.38,216.03 c 3.3,16.23 10.57,28.84 11.98,31.18 l 75.82,75.82 c 6.95,-3.64 14.5,-8.63 22.52,-14.89 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path53"
|
||||
d="m 66.93,243.64 93.37,-93.37 c -6.02,-7.42 -11.29,-14.68 -15.74,-21.67 L 50,223.15 c 5.57,8.23 12.85,16.23 16.93,20.49 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path55"
|
||||
d="m 342.31,475.69 c 28.01,-28.01 30.61,-57.26 8.43,-94.84 -5.34,-9.04 -12.24,-18.64 -20.51,-28.54 -18.86,-22.58 -45.85,-47.88 -80.23,-75.22 -22.52,17.91 -41.95,35 -57.79,50.83 -7.26,7.26 -13.91,14.4 -19.81,21.27 l 18.76,18.76 c 5.78,-6.83 12.41,-13.98 19.75,-21.32 10.39,-10.39 22.57,-21.49 36.19,-32.99 l 2.89,-2.44 2.89,2.44 c 24.91,21.03 44.8,40.59 59.09,58.14 9.08,11.14 15.98,21.56 20.53,30.98 3.93,8.14 6.11,15.56 6.49,22.03 0.19,3.16 0.15,8.05 -2.3,14 -2.4,5.84 -6.68,11.79 -13.09,18.2 -2.39,2.39 -6.25,5.82 -12.08,7.91 -6.02,2.16 -12.85,2.51 -20.88,1.07 l -0.11,-0.02 c -3.49,-0.72 -7.45,-2.35 -12.03,-4.95 l -21.16,21.16 c 5.5,2.83 15.15,7.13 26.59,9.46 23.41,4.76 43.05,-0.6 58.38,-15.93 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path57"
|
||||
d="M 243.64,433.07 150.27,339.7 c -7.42,6.02 -14.68,11.29 -21.67,15.74 L 223.15,450 c 8.23,-5.57 16.23,-12.85 20.49,-16.93 z"
|
||||
class="st0" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:7.4999999;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path59"
|
||||
d="m 157.69,475.69 c 15.32,15.32 34.95,20.68 58.33,15.93 16.24,-3.3 28.85,-10.58 31.19,-11.98 l 75.82,-75.82 c -3.64,-6.95 -8.63,-14.5 -14.89,-22.52 L 250,439.43 249.97,439.4 c -5.01,4.8 -15.11,13.98 -25.07,20.13 -6.08,3.75 -11.21,5.87 -15.66,6.46 -7.98,1.42 -14.77,1.06 -20.77,-1.09 -5.83,-2.09 -9.68,-5.51 -12.08,-7.91 -6.83,-6.83 -11.26,-13.16 -13.55,-19.37 -2.34,-6.33 -2.09,-11.55 -1.6,-15.39 0.63,-5.05 2.28,-10.62 4.9,-16.58 L 146.4,385.9 c -19.04,35.17 -15.46,63.04 11.29,89.79 z"
|
||||
class="st0" />
|
||||
</g><g
|
||||
id="g28"
|
||||
transform="translate(2.1186441)">
|
||||
<path
|
||||
class="st0"
|
||||
d="m 465.94,209.46 c -0.72,3.49 -2.35,7.45 -4.95,12.03 l 21.16,21.16 c 2.83,-5.51 7.14,-15.16 9.47,-26.61 4.76,-23.39 -0.6,-43.02 -15.93,-58.35 -28.01,-28.01 -57.26,-30.61 -94.84,-8.43 -9.04,5.34 -18.64,12.24 -28.55,20.51 -22.58,18.86 -47.88,45.85 -75.22,80.23 17.91,22.52 35,41.95 50.83,57.79 7.26,7.26 14.4,13.91 21.27,19.81 l 18.76,-18.76 c -6.83,-5.78 -13.98,-12.41 -21.32,-19.75 -10.39,-10.39 -21.5,-22.57 -32.99,-36.19 l -2.43,-2.9 2.44,-2.89 c 21.03,-24.91 40.59,-44.8 58.14,-59.09 11.14,-9.08 21.56,-15.98 30.98,-20.53 8.14,-3.92 15.55,-6.11 22.03,-6.49 3.16,-0.19 8.05,-0.15 14,2.3 5.84,2.4 11.79,6.68 18.2,13.09 2.39,2.39 5.82,6.25 7.91,12.08 2.16,6.02 2.51,12.85 1.07,20.88 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="M 355.44,371.4 450,276.85 c -5.56,-8.23 -12.85,-16.22 -16.93,-20.49 l -93.37,93.37 c 6.02,7.42 11.29,14.68 15.74,21.67 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 475.69,342.31 c 15.32,-15.32 20.68,-34.95 15.93,-58.34 -3.3,-16.23 -10.57,-28.84 -11.98,-31.18 l -75.82,-75.82 c -6.95,3.64 -14.5,8.63 -22.52,14.89 l 58.13,58.14 -0.03,0.03 c 4.8,5.01 13.98,15.11 20.13,25.07 3.75,6.08 5.87,11.21 6.46,15.66 1.42,7.98 1.06,14.77 -1.09,20.77 -2.09,5.83 -5.51,9.68 -7.91,12.08 -6.83,6.83 -13.16,11.26 -19.37,13.55 -6.33,2.34 -11.55,2.09 -15.39,1.6 -5.05,-0.63 -10.62,-2.28 -16.58,-4.9 L 385.9,353.6 c 35.17,19.04 63.04,15.46 89.79,-11.29 z"
|
||||
id="path8"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 311.53,35.11 c 5.83,2.09 9.68,5.51 12.08,7.91 6.83,6.83 11.26,13.16 13.55,19.37 2.34,6.33 2.09,11.55 1.6,15.39 -0.63,5.05 -2.28,10.62 -4.9,16.58 L 353.6,114.1 C 372.64,78.93 369.06,51.06 342.31,24.31 326.99,8.99 307.36,3.63 283.97,8.38 c -16.23,3.3 -28.84,10.57 -31.18,11.98 l -75.82,75.82 c 3.64,6.95 8.63,14.5 14.89,22.52 L 250,60.57 l 0.03,0.03 c 5.01,-4.8 15.11,-13.98 25.07,-20.13 6.08,-3.75 11.21,-5.87 15.66,-6.46 7.98,-1.41 14.77,-1.05 20.77,1.1 z"
|
||||
id="path10"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 157.69,24.31 c -28.01,28.01 -30.61,57.26 -8.43,94.84 5.34,9.04 12.24,18.64 20.51,28.54 18.86,22.58 45.85,47.88 80.23,75.22 22.52,-17.91 41.95,-35 57.79,-50.83 7.26,-7.26 13.91,-14.4 19.81,-21.27 l -18.76,-18.76 c -5.78,6.83 -12.41,13.98 -19.75,21.32 -10.39,10.39 -22.57,21.49 -36.19,32.99 l -2.9,2.44 -2.89,-2.44 C 222.2,165.33 202.31,145.77 188.02,128.22 178.94,117.08 172.04,106.66 167.49,97.24 163.57,89.1 161.38,81.69 161,75.21 c -0.19,-3.16 -0.15,-8.05 2.3,-14 2.4,-5.84 6.68,-11.79 13.09,-18.2 2.39,-2.39 6.25,-5.82 12.08,-7.91 6.02,-2.16 12.85,-2.51 20.87,-1.07 l 0.11,0.02 c 3.49,0.72 7.45,2.35 12.03,4.95 L 242.64,17.84 C 237.13,15.01 227.48,10.7 216.02,8.37 192.64,3.63 173.01,8.99 157.69,24.31 Z"
|
||||
id="path12"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 276.85,50 c -8.23,5.56 -16.22,12.85 -20.49,16.92 l 93.37,93.37 c 7.42,-6.02 14.68,-11.29 21.67,-15.74 z"
|
||||
id="path14"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 24.31,342.31 c 28.01,28.01 57.26,30.61 94.84,8.43 9.04,-5.34 18.64,-12.24 28.54,-20.51 22.58,-18.86 47.88,-45.85 75.22,-80.23 -17.91,-22.52 -35,-41.95 -50.83,-57.79 -7.26,-7.26 -14.4,-13.91 -21.27,-19.81 l -18.76,18.76 c 6.83,5.78 13.98,12.41 21.32,19.75 10.39,10.39 21.5,22.57 32.99,36.19 l 2.44,2.89 -2.44,2.89 c -21.03,24.91 -40.59,44.8 -58.14,59.09 -11.14,9.08 -21.56,15.98 -30.98,20.53 -8.14,3.92 -15.55,6.11 -22.03,6.49 -3.16,0.19 -8.05,0.15 -14,-2.3 -5.84,-2.4 -11.79,-6.68 -18.2,-13.09 -2.39,-2.39 -5.82,-6.25 -7.91,-12.08 -2.16,-6.02 -2.51,-12.85 -1.07,-20.88 l 0.02,-0.11 C 34.77,287.04 36.4,283.08 39,278.5 L 17.84,257.34 c -2.83,5.51 -7.14,15.17 -9.47,26.63 -4.74,23.39 0.62,43.02 15.94,58.34 z"
|
||||
id="path16"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 60.57,250 0.03,-0.03 c -4.8,-5.02 -13.98,-15.11 -20.13,-25.07 -3.75,-6.08 -5.87,-11.21 -6.46,-15.66 -1.42,-7.98 -1.06,-14.77 1.09,-20.77 2.09,-5.83 5.51,-9.68 7.91,-12.08 6.83,-6.83 13.16,-11.26 19.37,-13.55 6.33,-2.34 11.55,-2.09 15.39,-1.6 5.05,0.63 10.62,2.28 16.58,4.9 L 114.09,146.4 C 78.92,127.36 51.05,130.94 24.3,157.69 8.99,173.01 3.63,192.64 8.38,216.03 c 3.3,16.23 10.57,28.84 11.98,31.18 l 75.82,75.82 c 6.95,-3.64 14.5,-8.63 22.52,-14.89 z"
|
||||
id="path18"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 66.93,243.64 93.37,-93.37 c -6.02,-7.42 -11.29,-14.68 -15.74,-21.67 L 50,223.15 c 5.57,8.23 12.85,16.23 16.93,20.49 z"
|
||||
id="path20"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 342.31,475.69 c 28.01,-28.01 30.61,-57.26 8.43,-94.84 -5.34,-9.04 -12.24,-18.64 -20.51,-28.54 -18.86,-22.58 -45.85,-47.88 -80.23,-75.22 -22.52,17.91 -41.95,35 -57.79,50.83 -7.26,7.26 -13.91,14.4 -19.81,21.27 l 18.76,18.76 c 5.78,-6.83 12.41,-13.98 19.75,-21.32 10.39,-10.39 22.57,-21.49 36.19,-32.99 l 2.89,-2.44 2.89,2.44 c 24.91,21.03 44.8,40.59 59.09,58.14 9.08,11.14 15.98,21.56 20.53,30.98 3.93,8.14 6.11,15.56 6.49,22.03 0.19,3.16 0.15,8.05 -2.3,14 -2.4,5.84 -6.68,11.79 -13.09,18.2 -2.39,2.39 -6.25,5.82 -12.08,7.91 -6.02,2.16 -12.85,2.51 -20.88,1.07 l -0.11,-0.02 c -3.49,-0.72 -7.45,-2.35 -12.03,-4.95 l -21.16,21.16 c 5.5,2.83 15.15,7.13 26.59,9.46 23.41,4.76 43.05,-0.6 58.38,-15.93 z"
|
||||
id="path22"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="M 243.64,433.07 150.27,339.7 c -7.42,6.02 -14.68,11.29 -21.67,15.74 L 223.15,450 c 8.23,-5.57 16.23,-12.85 20.49,-16.93 z"
|
||||
id="path24"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 157.69,475.69 c 15.32,15.32 34.95,20.68 58.33,15.93 16.24,-3.3 28.85,-10.58 31.19,-11.98 l 75.82,-75.82 c -3.64,-6.95 -8.63,-14.5 -14.89,-22.52 L 250,439.43 249.97,439.4 c -5.01,4.8 -15.11,13.98 -25.07,20.13 -6.08,3.75 -11.21,5.87 -15.66,6.46 -7.98,1.42 -14.77,1.06 -20.77,-1.09 -5.83,-2.09 -9.68,-5.51 -12.08,-7.91 -6.83,-6.83 -11.26,-13.16 -13.55,-19.37 -2.34,-6.33 -2.09,-11.55 -1.6,-15.39 0.63,-5.05 2.28,-10.62 4.9,-16.58 L 146.4,385.9 c -19.04,35.17 -15.46,63.04 11.29,89.79 z"
|
||||
id="path26"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 75 KiB |
BIN
cwtch.png
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 45 KiB |
|
@ -26,24 +26,29 @@ abstract class Cwtch {
|
|||
void DebugResetContact(String profileOnion, String contactHandle);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> ACNEvents();
|
||||
Future<dynamic> ACNEvents();
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> ContactEvents();
|
||||
Future<dynamic> ContactEvents();
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetProfiles();
|
||||
Future<dynamic> GetProfiles();
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<int> NumMessages(String profile, String handle);
|
||||
Future<dynamic> NumMessages(String profile, String handle);
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetMessage(String profile, String handle, int index);
|
||||
Future<dynamic> GetMessage(String profile, String handle, int index);
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetMessages(String profile, String handle, int start, int end);
|
||||
Future<dynamic> GetMessages(String profile, String handle, int start, int end);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendMessage(String profile, String handle, String message);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendInvitation(String profile, String handle, String target);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateGroup(String profile, String server, String groupName);
|
||||
// ignore: non_constant_identifier_names
|
||||
void LeaveGroup(String profile, String groupID);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ImportBundle(String profile, String bundle);
|
||||
// ignore: non_constant_identifier_names
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cwtch/notification_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:cwtch/torstatus.dart';
|
||||
|
@ -10,16 +11,18 @@ import '../settings.dart';
|
|||
// Class that handles libcwtch-go events (received either via ffi with an isolate or gomobile over a method channel from kotlin)
|
||||
// Takes Notifiers and triggers them on appropriate events
|
||||
class CwtchNotifier {
|
||||
ProfileListState profileCN;
|
||||
Settings settings;
|
||||
ErrorHandler error;
|
||||
TorStatus torStatus;
|
||||
late ProfileListState profileCN;
|
||||
late Settings settings;
|
||||
late ErrorHandler error;
|
||||
late TorStatus torStatus;
|
||||
late NotificationsManager notificationManager;
|
||||
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN) {
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP) {
|
||||
profileCN = pcn;
|
||||
settings = settingsCN;
|
||||
error = errorCN;
|
||||
torStatus = torStatusCN;
|
||||
notificationManager = notificationManagerP;
|
||||
}
|
||||
|
||||
void handleMessage(String type, dynamic data) {
|
||||
|
@ -29,7 +32,7 @@ class CwtchNotifier {
|
|||
onion: data["Identity"], nickname: data["name"], imagePath: data["picture"], contactsJson: data["ContactsJson"], serversJson: data["ServerList"], online: data["Online"] == "true"));
|
||||
break;
|
||||
case "PeerCreated":
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState(
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(
|
||||
data["ProfileOnion"],
|
||||
data["RemotePeer"],
|
||||
nickname: data["nick"],
|
||||
|
@ -45,8 +48,18 @@ class CwtchNotifier {
|
|||
lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet
|
||||
));
|
||||
break;
|
||||
case "GroupCreated":
|
||||
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"], server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
}
|
||||
break;
|
||||
case "DeleteGroup":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["GroupID"]);
|
||||
break;
|
||||
case "PeerStateChange":
|
||||
ContactInfoState contact = profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]);
|
||||
ContactInfoState? contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]);
|
||||
if (contact != null) {
|
||||
if (data["ConnectionState"] != null) {
|
||||
contact.status = data["ConnectionState"];
|
||||
|
@ -56,13 +69,14 @@ class CwtchNotifier {
|
|||
contact.isBlocked = data["authorization"] == "blocked";
|
||||
}
|
||||
// contact.[status/isBlocked] might change the list's sort order
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.resort();
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
|
||||
}
|
||||
break;
|
||||
case "NewMessageFromPeer":
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).unreadMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).totalMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
||||
notificationManager.notify("New Message From Peer!");
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
||||
break;
|
||||
case "PeerAcknowledgement":
|
||||
// We don't use these anymore, IndexedAcknowledgement is more suited to the UI front end...
|
||||
|
@ -71,10 +85,10 @@ class CwtchNotifier {
|
|||
var idx = data["Index"];
|
||||
// We return -1 for protocol message acks if there is no message
|
||||
if (idx == "-1") break;
|
||||
var key = profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).getMessageKey(idx);
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||
if (key == null) break;
|
||||
try {
|
||||
var message = Provider.of<MessageState>(key.currentContext, listen: false);
|
||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
||||
if (message == null) break;
|
||||
message.ackd = true;
|
||||
} catch (e) {
|
||||
|
@ -85,16 +99,16 @@ class CwtchNotifier {
|
|||
case "NewMessageFromGroup":
|
||||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||
//not from me
|
||||
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());
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
} else {
|
||||
// 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<MessageState>(key.currentContext, listen: false);
|
||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
||||
if (message == null) break;
|
||||
message.ackd = true;
|
||||
} catch (e) {
|
||||
|
@ -102,14 +116,25 @@ class CwtchNotifier {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case "IndexedFailure":
|
||||
print("IndexedFailure: $data");
|
||||
var idx = data["Index"];
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||
try {
|
||||
var message = Provider.of<MessageState>(key!.currentContext!, listen: false);
|
||||
message.error = true;
|
||||
} catch (e) {
|
||||
// ignore, we likely have an old key that has been replaced with an actual signature
|
||||
}
|
||||
break;
|
||||
case "SendMessageToGroupError":
|
||||
// from me (already displayed - do not update counter)
|
||||
print("SendMessageToGroupError: $data");
|
||||
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<MessageState>(key.currentContext, listen: false);
|
||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
||||
if (message == null) break;
|
||||
message.error = true;
|
||||
} catch (e) {
|
||||
|
@ -118,28 +143,30 @@ class CwtchNotifier {
|
|||
break;
|
||||
case "AppError":
|
||||
print("New App Error: $data");
|
||||
error.handleUpdate(data["Data"]);
|
||||
if (data["Data"] != null) {
|
||||
error.handleUpdate(data["Data"]);
|
||||
}
|
||||
break;
|
||||
case "UpdateGlobalSettings":
|
||||
settings.handleUpdate(jsonDecode(data["Data"]));
|
||||
break;
|
||||
case "SetAttribute":
|
||||
if (data["Key"] == "public.name") {
|
||||
profileCN.getProfile(data["ProfileOnion"]).nickname = data["Data"];
|
||||
profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"];
|
||||
} else {
|
||||
print("unhandled set attribute event: $type");
|
||||
print("unhandled set attribute event: $type $data");
|
||||
}
|
||||
break;
|
||||
case "NetworkError":
|
||||
var isOnline = data["Status"] == "Success";
|
||||
profileCN.getProfile(data["ProfileOnion"]).isOnline = isOnline;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.isOnline = isOnline;
|
||||
break;
|
||||
case "ACNStatus":
|
||||
print("acn status: $data");
|
||||
torStatus.handleUpdate(int.parse(data["Progress"]), data["Status"]);
|
||||
break;
|
||||
case "UpdateServerInfo":
|
||||
profileCN.getProfile(data["ProfileOnion"]).replaceServers(data["ServerList"]);
|
||||
profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
|
||||
break;
|
||||
case "NewGroup":
|
||||
print("new group invite: $data");
|
||||
|
@ -148,30 +175,38 @@ class CwtchNotifier {
|
|||
String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5)));
|
||||
dynamic groupInvite = jsonDecode(inviteJson);
|
||||
print("new group invite: $groupInvite");
|
||||
if (profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(groupInvite["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
||||
isInvitation: true, imagePath: data["PicturePath"], nickname: groupInvite["GroupName"], server: groupInvite["ServerHost"], isGroup: true, lastMessageTime: DateTime.now()));
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.now());
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.now());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "AcceptGroupInvite":
|
||||
print("accept group invite: $data");
|
||||
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"])!.isInvitation = false;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
break;
|
||||
case "ServerStateChange":
|
||||
print("server state change: $data");
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.contacts.forEach((contact) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.contacts.forEach((contact) {
|
||||
if (contact.isGroup == true && contact.server == data["GroupServer"]) {
|
||||
contact.status = data["ConnectionState"];
|
||||
}
|
||||
});
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.resort();
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
|
||||
break;
|
||||
case "SetGroupAttribute":
|
||||
if (data["Key"] == "local.name") {
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.nickname = data["Data"];
|
||||
}
|
||||
} else {
|
||||
print("unhandled set group attribute event: $type $data");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
print("unhandled event: $type");
|
||||
print("unhandled event: $type $data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,9 +56,9 @@ typedef acn_events_function = Pointer<Utf8> Function();
|
|||
typedef ACNEventsFn = Pointer<Utf8> Function();
|
||||
|
||||
class CwtchFfi implements Cwtch {
|
||||
DynamicLibrary library;
|
||||
CwtchNotifier cwtchNotifier;
|
||||
Isolate cwtchIsolate;
|
||||
late DynamicLibrary library;
|
||||
late CwtchNotifier cwtchNotifier;
|
||||
late Isolate cwtchIsolate;
|
||||
|
||||
CwtchFfi(CwtchNotifier _cwtchNotifier) {
|
||||
if (Platform.isWindows) {
|
||||
|
@ -79,9 +79,9 @@ class CwtchFfi implements Cwtch {
|
|||
String bundledTor = "";
|
||||
Map<String, String> envVars = Platform.environment;
|
||||
if (Platform.isLinux) {
|
||||
home = envVars['HOME'];
|
||||
home = (envVars['HOME'])!;
|
||||
} else if (Platform.isWindows) {
|
||||
home = envVars['UserProfile'];
|
||||
home = (envVars['UserProfile'])!;
|
||||
bundledTor = "Tor\\Tor\\tor.exe";
|
||||
}
|
||||
var cwtchDir = path.join(home, ".cwtch/dev/");
|
||||
|
@ -121,7 +121,7 @@ class CwtchFfi implements Cwtch {
|
|||
|
||||
// Steam of appbus events. Call blocks in libcwtch-go GetAppbusEvent. Static so the isolate can use it
|
||||
static Stream<String> pollAppbusEvents() async* {
|
||||
DynamicLibrary library;
|
||||
late DynamicLibrary library;
|
||||
if (Platform.isWindows) {
|
||||
library = DynamicLibrary.open("libCwtch.dll");
|
||||
} else if (Platform.isLinux) {
|
||||
|
@ -356,4 +356,26 @@ class CwtchFfi implements Cwtch {
|
|||
final u2 = groupHandle.toNativeUtf8();
|
||||
RejectInvite(u1, u1.length, u2, u2.length);
|
||||
}
|
||||
|
||||
@override
|
||||
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
|
||||
final CreateGroup = createGroup.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = server.toNativeUtf8();
|
||||
final u3 = groupName.toNativeUtf8();
|
||||
CreateGroup(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LeaveGroup(String profileOnion, String groupHandle) {
|
||||
var leaveGroup = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveGroup");
|
||||
// ignore: non_constant_identifier_names
|
||||
final RejectInvite = leaveGroup.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = groupHandle.toNativeUtf8();
|
||||
RejectInvite(u1, u1.length, u2, u2.length);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@ class CwtchGomobile implements Cwtch {
|
|||
|
||||
final appbusEventChannelName = 'test.flutter.dev/eventBus';
|
||||
|
||||
Future<String> androidLibraryDir;
|
||||
Future<Directory> androidHomeDirectory;
|
||||
CwtchNotifier cwtchNotifier;
|
||||
late Future<dynamic> androidLibraryDir;
|
||||
late Future<dynamic> androidHomeDirectory;
|
||||
late CwtchNotifier cwtchNotifier;
|
||||
|
||||
CwtchGomobile(CwtchNotifier _cwtchNotifier) {
|
||||
print("gomobile.dart: CwtchGomobile()");
|
||||
|
@ -73,34 +73,34 @@ class CwtchGomobile implements Cwtch {
|
|||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> ACNEvents() {
|
||||
Future<dynamic> ACNEvents() {
|
||||
return cwtchPlatform.invokeMethod("ACNEvents");
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> ContactEvents() {
|
||||
Future<dynamic> ContactEvents() {
|
||||
return cwtchPlatform.invokeMethod("ContactEvents");
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetProfiles() {
|
||||
Future<dynamic> GetProfiles() {
|
||||
print("gomobile.dart: GetProfiles()");
|
||||
return cwtchPlatform.invokeMethod("GetProfiles");
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<int> NumMessages(String profile, String handle) {
|
||||
Future<dynamic> NumMessages(String profile, String handle) {
|
||||
return cwtchPlatform.invokeMethod("NumMessages", {"profile": profile, "contact": handle});
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetMessage(String profile, String handle, int index) {
|
||||
Future<dynamic> GetMessage(String profile, String handle, int index) {
|
||||
print("gomobile.dart GetMessage " + index.toString());
|
||||
return cwtchPlatform.invokeMethod("GetMessage", {"profile": profile, "contact": handle, "index": index});
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetMessages(String profile, String handle, int start, int end) {
|
||||
Future<dynamic> GetMessages(String profile, String handle, int start, int end) {
|
||||
return cwtchPlatform.invokeMethod("GetMessage", {"profile": profile, "contact": handle, "start": start, "end": end});
|
||||
}
|
||||
|
||||
|
@ -172,4 +172,15 @@ class CwtchGomobile implements Cwtch {
|
|||
void RejectInvite(String profileOnion, String groupHandle) {
|
||||
cwtchPlatform.invokeMethod("RejectInvite", {"ProfileOnion": profileOnion, "handle": groupHandle});
|
||||
}
|
||||
|
||||
@override
|
||||
void CreateGroup(String profileOnion, String server, String groupName) {
|
||||
cwtchPlatform.invokeMethod("CreateGroup", {"ProfileOnion": profileOnion, "server": server, "groupName": groupName});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LeaveGroup(String profileOnion, String groupHandle) {
|
||||
cwtchPlatform.invokeMethod("LeaveGroup", {"ProfileOnion": profileOnion, "handle": groupHandle});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cwtch/notification_manager.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cwtch/cwtch/ffi.dart';
|
||||
import 'package:cwtch/cwtch/gomobile.dart';
|
||||
|
@ -35,14 +36,14 @@ class Flwtch extends StatefulWidget {
|
|||
|
||||
class FlwtchState extends State<Flwtch> {
|
||||
final TextStyle biggerFont = const TextStyle(fontSize: 18);
|
||||
Cwtch cwtch;
|
||||
late Cwtch cwtch;
|
||||
bool cwtchInit = false;
|
||||
ProfileInfoState selectedProfile;
|
||||
late ProfileInfoState selectedProfile;
|
||||
String selectedConversation = "";
|
||||
var columns = [1]; // default or 'single column' mode
|
||||
//var columns = [1, 1, 2];
|
||||
AppModel appStatus;
|
||||
ProfileListState profs;
|
||||
late AppModel appStatus;
|
||||
late ProfileListState profs;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
|
@ -50,11 +51,15 @@ class FlwtchState extends State<Flwtch> {
|
|||
cwtchInit = false;
|
||||
|
||||
profs = ProfileListState();
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus);
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
|
||||
cwtch = CwtchGomobile(cwtchNotifier);
|
||||
} else if (Platform.isLinux) {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, LinuxNotificationsManager());
|
||||
cwtch = CwtchFfi(cwtchNotifier);
|
||||
} else {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
|
||||
cwtch = CwtchFfi(cwtchNotifier);
|
||||
}
|
||||
|
||||
|
@ -76,19 +81,24 @@ class FlwtchState extends State<Flwtch> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//appStatus = AppModel(cwtch: cwtch);
|
||||
|
||||
globalSettings.initPackageInfo();
|
||||
return MultiProvider(
|
||||
providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider(), getTorStatusProvider()],
|
||||
providers: [
|
||||
getFlwtchStateProvider(),
|
||||
getProfileListProvider(),
|
||||
getSettingsProvider(),
|
||||
getErrorHandlerProvider(),
|
||||
getTorStatusProvider(),
|
||||
],
|
||||
builder: (context, widget) {
|
||||
Provider.of<Settings>(context).initPackageInfo();
|
||||
return Consumer<Settings>(
|
||||
builder: (context, opaque, child) => MaterialApp(
|
||||
builder: (context, settings, child) => MaterialApp(
|
||||
key: Key('app'),
|
||||
locale: Provider.of<Settings>(context).locale,
|
||||
locale: settings.locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
title: 'Cwtch',
|
||||
theme: mkThemeData(opaque),
|
||||
theme: mkThemeData(settings),
|
||||
// from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(),
|
||||
// from erinn: home: columns.length == 3 ? TripleColumnView() : ProfileMgrView(),
|
||||
home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ProfileMgrView()) : SplashView(),
|
||||
|
|
|
@ -63,6 +63,6 @@ class DiskAssetBundle extends CachingAssetBundle {
|
|||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
return _cache[key];
|
||||
return _cache[key]!;
|
||||
}
|
||||
}
|
||||
|
|
135
lib/model.dart
|
@ -14,33 +14,11 @@ import 'main.dart';
|
|||
/// UI State ///
|
||||
////////////////////
|
||||
|
||||
//todo: delete
|
||||
class ProfileModel {
|
||||
String onion;
|
||||
String nickname;
|
||||
String creationDate;
|
||||
String imagePath;
|
||||
HashMap<String, ContactModel> contacts;
|
||||
}
|
||||
|
||||
//todo: delete
|
||||
class ContactModel {
|
||||
String onion;
|
||||
String nickname;
|
||||
bool isGroup;
|
||||
bool isInvitation;
|
||||
bool isBlocked;
|
||||
String status;
|
||||
String imagePath;
|
||||
|
||||
ContactModel({this.onion, this.nickname, this.status, this.isInvitation, this.isBlocked, this.imagePath});
|
||||
}
|
||||
|
||||
class ChatMessage {
|
||||
final int o;
|
||||
final String d;
|
||||
|
||||
ChatMessage({this.o, this.d});
|
||||
ChatMessage({required this.o, required this.d});
|
||||
|
||||
ChatMessage.fromJson(Map<String, dynamic> json)
|
||||
: o = json['o'],
|
||||
|
@ -72,7 +50,7 @@ class ProfileListState extends ChangeNotifier {
|
|||
|
||||
List<ProfileInfoState> get profiles => _profiles.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
ProfileInfoState getProfile(String onion) {
|
||||
ProfileInfoState? getProfile(String onion) {
|
||||
int idx = _profiles.indexWhere((element) => element.onion == onion);
|
||||
return idx >= 0 ? _profiles[idx] : null;
|
||||
}
|
||||
|
@ -80,10 +58,10 @@ class ProfileListState extends ChangeNotifier {
|
|||
|
||||
class ContactListState extends ChangeNotifier {
|
||||
List<ContactInfoState> _contacts = [];
|
||||
String _filter;
|
||||
String _filter = "";
|
||||
int get num => _contacts.length;
|
||||
int get numFiltered => isFiltered ? filteredList().length : num;
|
||||
bool get isFiltered => _filter != null && _filter != "";
|
||||
bool get isFiltered => _filter != "";
|
||||
String get filter => _filter;
|
||||
set filter(String newVal) {
|
||||
_filter = newVal;
|
||||
|
@ -92,7 +70,7 @@ class ContactListState extends ChangeNotifier {
|
|||
|
||||
List<ContactInfoState> filteredList() {
|
||||
if (!isFiltered) return contacts;
|
||||
return _contacts.where((ContactInfoState c) => c.onion.contains(_filter) || (c.nickname != null && c.nickname.contains(_filter))).toList();
|
||||
return _contacts.where((ContactInfoState c) => c.onion.contains(_filter) || (c.nickname.contains(_filter))).toList();
|
||||
}
|
||||
|
||||
void addAll(Iterable<ContactInfoState> newContacts) {
|
||||
|
@ -140,10 +118,18 @@ class ContactListState extends ChangeNotifier {
|
|||
|
||||
List<ContactInfoState> get contacts => _contacts.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
ContactInfoState getContact(String onion) {
|
||||
ContactInfoState? getContact(String onion) {
|
||||
int idx = _contacts.indexWhere((element) => element.onion == onion);
|
||||
return idx >= 0 ? _contacts[idx] : null;
|
||||
}
|
||||
|
||||
void removeContact(String onion) {
|
||||
int idx = _contacts.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_contacts.removeAt(idx);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileInfoState extends ChangeNotifier {
|
||||
|
@ -156,7 +142,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
bool _online = false;
|
||||
|
||||
ProfileInfoState({
|
||||
this.onion,
|
||||
required this.onion,
|
||||
nickname = "",
|
||||
imagePath = "",
|
||||
unreadMessages = 0,
|
||||
|
@ -253,21 +239,21 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
class ContactInfoState extends ChangeNotifier {
|
||||
final String profileOnion;
|
||||
final String onion;
|
||||
String _nickname;
|
||||
late String _nickname;
|
||||
|
||||
bool _isInvitation;
|
||||
bool _isBlocked;
|
||||
String _status;
|
||||
String _imagePath;
|
||||
String _savePeerHistory;
|
||||
int _unreadMessages = 0;
|
||||
int _totalMessages = 0;
|
||||
DateTime _lastMessageTime;
|
||||
Map<String, GlobalKey> keys;
|
||||
late bool _isInvitation;
|
||||
late bool _isBlocked;
|
||||
late String _status;
|
||||
late String _imagePath;
|
||||
late String _savePeerHistory;
|
||||
late int _unreadMessages = 0;
|
||||
late int _totalMessages = 0;
|
||||
late DateTime _lastMessageTime;
|
||||
late Map<String, GlobalKey<MessageBubbleState>> keys;
|
||||
|
||||
// todo: a nicer way to model contacts, groups and other "entities"
|
||||
bool _isGroup;
|
||||
String _server;
|
||||
late bool _isGroup;
|
||||
late String _server;
|
||||
|
||||
ContactInfoState(
|
||||
this.profileOnion,
|
||||
|
@ -293,14 +279,14 @@ class ContactInfoState extends ChangeNotifier {
|
|||
this._totalMessages = numMessages;
|
||||
this._unreadMessages = numUnread;
|
||||
this._savePeerHistory = savePeerHistory;
|
||||
this._lastMessageTime = lastMessageTime;
|
||||
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
||||
this._server = server;
|
||||
keys = Map<String, GlobalKey>();
|
||||
keys = Map<String, GlobalKey<MessageBubbleState>>();
|
||||
}
|
||||
|
||||
get nickname => this._nickname;
|
||||
String get nickname => this._nickname;
|
||||
|
||||
get savePeerHistory => this._savePeerHistory;
|
||||
String get savePeerHistory => this._savePeerHistory;
|
||||
set savePeerHistory(String newVal) {
|
||||
this._savePeerHistory = newVal;
|
||||
notifyListeners();
|
||||
|
@ -311,49 +297,49 @@ class ContactInfoState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
get isGroup => this._isGroup;
|
||||
bool get isGroup => this._isGroup;
|
||||
set isGroup(bool newVal) {
|
||||
this._isGroup = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
get isBlocked => this._isBlocked;
|
||||
bool get isBlocked => this._isBlocked;
|
||||
set isBlocked(bool newVal) {
|
||||
this._isBlocked = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
get isInvitation => this._isInvitation;
|
||||
bool get isInvitation => this._isInvitation;
|
||||
set isInvitation(bool newVal) {
|
||||
this._isInvitation = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
get status => this._status;
|
||||
String get status => this._status;
|
||||
set status(String newVal) {
|
||||
this._status = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
get unreadMessages => this._unreadMessages;
|
||||
int get unreadMessages => this._unreadMessages;
|
||||
set unreadMessages(int newVal) {
|
||||
this._unreadMessages = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
get totalMessages => this._totalMessages;
|
||||
int get totalMessages => this._totalMessages;
|
||||
set totalMessages(int newVal) {
|
||||
this._totalMessages = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
get imagePath => this._imagePath;
|
||||
String get imagePath => this._imagePath;
|
||||
set imagePath(String newVal) {
|
||||
this._imagePath = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
get lastMessageTime => this._lastMessageTime;
|
||||
DateTime get lastMessageTime => this._lastMessageTime;
|
||||
set lastMessageTime(DateTime newVal) {
|
||||
this._lastMessageTime = newVal;
|
||||
notifyListeners();
|
||||
|
@ -374,7 +360,8 @@ class ContactInfoState extends ChangeNotifier {
|
|||
if (keys[index] == null) {
|
||||
keys[index] = GlobalKey<MessageBubbleState>();
|
||||
}
|
||||
return keys[index];
|
||||
GlobalKey<MessageBubbleState> ret = keys[index]!;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -382,24 +369,24 @@ class MessageState extends ChangeNotifier {
|
|||
final String profileOnion;
|
||||
final String contactHandle;
|
||||
final int messageIndex;
|
||||
String _message;
|
||||
int _overlay;
|
||||
String _inviteTarget;
|
||||
String _inviteNick;
|
||||
DateTime _timestamp;
|
||||
String _senderOnion;
|
||||
String _senderImage;
|
||||
String _signature = "";
|
||||
bool _ackd = false;
|
||||
bool _error = false;
|
||||
bool _loaded = false;
|
||||
bool _malformed = false;
|
||||
late String _message;
|
||||
late int _overlay;
|
||||
late String _inviteTarget;
|
||||
late String _inviteNick;
|
||||
late DateTime _timestamp;
|
||||
late String _senderOnion;
|
||||
late String _senderImage;
|
||||
late String _signature = "";
|
||||
late bool _ackd = false;
|
||||
late bool _error = false;
|
||||
late bool _loaded = false;
|
||||
late bool _malformed = false;
|
||||
|
||||
MessageState({
|
||||
BuildContext context,
|
||||
this.profileOnion,
|
||||
this.contactHandle,
|
||||
this.messageIndex,
|
||||
required BuildContext context,
|
||||
required this.profileOnion,
|
||||
required this.contactHandle,
|
||||
required this.messageIndex,
|
||||
}) {
|
||||
this._senderOnion = profileOnion;
|
||||
tryLoad(context);
|
||||
|
@ -408,8 +395,8 @@ class MessageState extends ChangeNotifier {
|
|||
get message => this._message;
|
||||
get overlay => this._overlay;
|
||||
get timestamp => this._timestamp;
|
||||
get ackd => this._ackd;
|
||||
get error => this._error;
|
||||
bool get ackd => this._ackd;
|
||||
bool get error => this._error;
|
||||
get malformed => this._malformed;
|
||||
get senderOnion => this._senderOnion;
|
||||
get senderImage => this._senderImage;
|
||||
|
@ -444,7 +431,7 @@ class MessageState extends ChangeNotifier {
|
|||
dynamic message = jsonDecode(messageWrapper['Message']);
|
||||
this._message = message['d'];
|
||||
this._overlay = int.parse(message['o'].toString());
|
||||
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp']);
|
||||
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
||||
this._senderOnion = messageWrapper['PeerID'];
|
||||
this._senderImage = messageWrapper['ContactImage'];
|
||||
|
||||
|
@ -490,7 +477,7 @@ class MessageState extends ChangeNotifier {
|
|||
|
||||
class AppModel {
|
||||
final Cwtch cwtch;
|
||||
AppModel({this.cwtch});
|
||||
AppModel({required this.cwtch});
|
||||
|
||||
Stream<String> contactEvents() async* {
|
||||
while (true) {
|
||||
|
|
|
@ -9,7 +9,7 @@ class ServerListState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
ServerInfoState getServer(String onion) {
|
||||
ServerInfoState? getServer(String onion) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
return idx >= 0 ? _servers[idx] : null;
|
||||
}
|
||||
|
@ -22,5 +22,5 @@ class ServerInfoState extends ChangeNotifier {
|
|||
final String onion;
|
||||
final String status;
|
||||
|
||||
ServerInfoState({this.onion, this.status});
|
||||
ServerInfoState({required this.onion, required this.status});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import 'package:desktop_notifications/desktop_notifications.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
// NotificationsManager provides a wrapper around platform specific notifications logic.
|
||||
abstract class NotificationsManager {
|
||||
Future<void> notify(String message);
|
||||
}
|
||||
|
||||
// NullNotificationsManager ignores all notification requests
|
||||
class NullNotificationsManager implements NotificationsManager {
|
||||
@override
|
||||
Future<void> notify(String message) async {}
|
||||
}
|
||||
|
||||
// LinuxNotificationsManager uses the desktop_notifications package to implement
|
||||
// the standard dbus-powered linux desktop notifications.
|
||||
class LinuxNotificationsManager implements NotificationsManager {
|
||||
int previous_id = 0;
|
||||
LinuxNotificationsManager() {}
|
||||
Future<void> notify(String message) async {
|
||||
var client = NotificationsClient();
|
||||
var icon_path = Uri.file(path.join(path.current, "cwtch.png"));
|
||||
client.notify('New Message from Peer!', appName: "cwtch", appIcon: icon_path.toString(), replacesId: this.previous_id).then((Notification value) => previous_id = value.id);
|
||||
client.close();
|
||||
}
|
||||
}
|
|
@ -690,7 +690,7 @@ class CwtchLight extends OpaqueThemeType {
|
|||
}
|
||||
|
||||
Color textfieldBackgroundColor() {
|
||||
return whitePurple;
|
||||
return purple;
|
||||
}
|
||||
|
||||
Color textfieldBorderColor() {
|
||||
|
@ -722,11 +722,11 @@ class CwtchLight extends OpaqueThemeType {
|
|||
}
|
||||
|
||||
Color portraitOnlineBorderColor() {
|
||||
return darkPurple;
|
||||
return greyPurple;
|
||||
}
|
||||
|
||||
Color portraitOnlineBackgroundColor() {
|
||||
return darkPurple;
|
||||
return greyPurple;
|
||||
}
|
||||
|
||||
Color portraitOnlineTextColor() {
|
||||
|
@ -1225,7 +1225,7 @@ class Opaque extends OpaqueThemeType {
|
|||
return sidePaneMinSize() + chatPaneMinSize();
|
||||
}
|
||||
|
||||
static OpaqueThemeType _current;
|
||||
static late OpaqueThemeType _current;
|
||||
static final OpaqueThemeType dark = CwtchDark();
|
||||
static final OpaqueThemeType light = CwtchLight();
|
||||
static void setDark() {
|
||||
|
@ -1345,16 +1345,26 @@ ThemeData mkThemeData(Settings opaque) {
|
|||
return ThemeData(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
primarySwatch: Colors.red,
|
||||
primaryIconTheme: IconThemeData(
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
primaryColor: opaque.current().backgroundMainColor(),
|
||||
canvasColor: opaque.current().backgroundPaneColor(),
|
||||
accentColor: opaque.current().defaultButtonColor(),
|
||||
buttonColor: opaque.current().defaultButtonColor(),
|
||||
backgroundColor: opaque.current().backgroundMainColor(),
|
||||
highlightColor: opaque.current().hilightElementTextColor(),
|
||||
iconTheme: IconThemeData(
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
cardColor: opaque.current().backgroundMainColor(),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: opaque.current().backgroundPaneColor(),
|
||||
titleTextStyle: TextStyle(
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
actionsIconTheme: IconThemeData(
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
|
||||
|
@ -1364,11 +1374,16 @@ ThemeData mkThemeData(Settings opaque) {
|
|||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
|
||||
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()),
|
||||
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
||||
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
|
||||
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()),
|
||||
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
||||
padding: MaterialStateProperty.all(EdgeInsets.all(20)),
|
||||
shape: MaterialStateProperty.all(RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18.0),
|
||||
)),
|
||||
),
|
||||
),
|
||||
tabBarTheme: TabBarTheme(indicator: UnderlineTabIndicator(borderSide: BorderSide(color: opaque.current().defaultButtonActiveColor()))),
|
||||
dialogTheme: DialogTheme(
|
||||
backgroundColor: opaque.current().backgroundPaneColor(),
|
||||
titleTextStyle: TextStyle(color: opaque.current().mainTextColor()),
|
||||
|
@ -1387,5 +1402,13 @@ ThemeData mkThemeData(Settings opaque) {
|
|||
caption: TextStyle(color: opaque.current().mainTextColor()),
|
||||
button: TextStyle(color: opaque.current().mainTextColor()),
|
||||
overline: TextStyle(color: opaque.current().mainTextColor())),
|
||||
switchTheme: SwitchThemeData(
|
||||
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
|
||||
thumbColor: MaterialStateProperty.all(opaque.current().mainTextColor()),
|
||||
trackColor: MaterialStateProperty.all(opaque.current().dropShadowColor()),
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(backgroundColor: opaque.current().defaultButtonColor(), hoverColor: opaque.current().defaultButtonActiveColor()),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: opaque.current().defaultButtonActiveColor(), selectionColor: opaque.current().defaultButtonActiveColor(), selectionHandleColor: opaque.current().defaultButtonActiveColor()),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,12 +14,12 @@ const TapirGroupsExperiment = "tapir-groups-experiment";
|
|||
/// Settings Pane.
|
||||
class Settings extends ChangeNotifier {
|
||||
Locale locale;
|
||||
PackageInfo packageInfo;
|
||||
late PackageInfo packageInfo;
|
||||
OpaqueThemeType theme;
|
||||
bool experimentsEnabled;
|
||||
late bool experimentsEnabled;
|
||||
HashMap<String, bool> experiments = HashMap.identity();
|
||||
|
||||
bool blockUnknownConnections;
|
||||
late bool blockUnknownConnections;
|
||||
|
||||
/// Set the dark theme.
|
||||
void setDark() {
|
||||
|
@ -112,11 +112,13 @@ class Settings extends ChangeNotifier {
|
|||
/// Turn on a specific experiment.
|
||||
enableExperiment(String key) {
|
||||
experiments.update(key, (value) => true, ifAbsent: () => true);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Turn off a specific experiment
|
||||
disableExperiment(String key) {
|
||||
experiments.update(key, (value) => false, ifAbsent: () => false);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Construct a default settings object.
|
||||
|
|
|
@ -5,6 +5,8 @@ class TorStatus extends ChangeNotifier {
|
|||
String status;
|
||||
bool connected;
|
||||
|
||||
TorStatus({this.connected = false, this.progress = 0, this.status = ""});
|
||||
|
||||
/// Called by the event bus.
|
||||
handleUpdate(int new_progress, String new_status) {
|
||||
if (progress == 100) {
|
||||
|
|
|
@ -26,7 +26,6 @@ class AddContactView extends StatefulWidget {
|
|||
class _AddContactViewState extends State<AddContactView> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _createGroupFormKey = GlobalKey<FormState>();
|
||||
final _joinGroupFormKey = GlobalKey<FormState>();
|
||||
final ctrlrOnion = TextEditingController(text: "");
|
||||
final ctrlrContact = TextEditingController(text: "");
|
||||
final ctrlrGroupName = TextEditingController(text: "");
|
||||
|
@ -34,9 +33,14 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// if we haven't picked a server yet, pick the first one in the list...
|
||||
if (server.isEmpty) {
|
||||
server = Provider.of<ProfileInfoState>(context).serverList.servers.first.onion;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context).titleManageContacts),
|
||||
title: Text(AppLocalizations.of(context)!.titleManageContacts),
|
||||
),
|
||||
body: _buildForm(),
|
||||
);
|
||||
|
@ -46,15 +50,20 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
|
||||
|
||||
/// We display a different number of tabs dependening on the experiment setup
|
||||
bool groupsEnabled = Provider.of<Settings>(context).experimentsEnabled && Provider.of<Settings>(context).experiments[TapirGroupsExperiment];
|
||||
bool groupsEnabled = Provider.of<Settings>(context).experimentsEnabled && Provider.of<Settings>(context).experiments[TapirGroupsExperiment]!;
|
||||
return Consumer<ErrorHandler>(builder: (context, globalErrorHandler, child) {
|
||||
return DefaultTabController(
|
||||
length: groupsEnabled ? 4 : 1,
|
||||
length: groupsEnabled ? 2 : 1,
|
||||
child: Column(children: [
|
||||
(groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: (groupsEnabled ? [addPeerTab(), manageServersTab(), addGroupTab(), joinGroupTab()] : [addPeerTab()]),
|
||||
children: (groupsEnabled
|
||||
? [
|
||||
addPeerTab(),
|
||||
addGroupTab(),
|
||||
]
|
||||
: [addPeerTab()]),
|
||||
)),
|
||||
]));
|
||||
});
|
||||
|
@ -62,7 +71,7 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
|
||||
void _copyOnion() {
|
||||
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).copiedClipboardNotification));
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedClipboardNotification));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
|
@ -72,7 +81,7 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(Icons.person_add_rounded),
|
||||
text: AppLocalizations.of(context).addPeer,
|
||||
text: AppLocalizations.of(context)!.addPeer,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -84,11 +93,10 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(Icons.person_add_rounded),
|
||||
text: AppLocalizations.of(context).addPeer,
|
||||
text: AppLocalizations.of(context)!.tooltipAddContact,
|
||||
),
|
||||
Tab(icon: Icon(Icons.backup), text: AppLocalizations.of(context).titleManageServers),
|
||||
Tab(icon: Icon(Icons.group), text: AppLocalizations.of(context).createGroup),
|
||||
Tab(icon: Icon(Icons.group_add), text: AppLocalizations.of(context).joinGroup),
|
||||
//Tab(icon: Icon(Icons.backup), text: AppLocalizations.of(context)!.titleManageServers),
|
||||
Tab(icon: Icon(Icons.group), text: AppLocalizations.of(context)!.createGroup),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -103,7 +111,7 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
autovalidateMode: AutovalidateMode.always,
|
||||
key: _formKey,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context).profileOnionLabel),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.profileOnionLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -111,12 +119,12 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
controller: ctrlrOnion,
|
||||
onPressed: _copyOnion,
|
||||
icon: Icon(Icons.copy),
|
||||
tooltip: AppLocalizations.of(context).copyBtn,
|
||||
tooltip: AppLocalizations.of(context)!.copyBtn,
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).pasteAddressToAddContact),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.pasteAddressToAddContact),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -127,29 +135,25 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
return null;
|
||||
}
|
||||
if (globalErrorHandler.invalidImportStringError) {
|
||||
return AppLocalizations.of(context).invalidImportString;
|
||||
return AppLocalizations.of(context)!.invalidImportString;
|
||||
} else if (globalErrorHandler.contactAlreadyExistsError) {
|
||||
return AppLocalizations.of(context).contactAlreadyExists;
|
||||
return AppLocalizations.of(context)!.contactAlreadyExists;
|
||||
} else if (globalErrorHandler.explicitAddContactSuccess) {}
|
||||
return null;
|
||||
},
|
||||
onChanged: (String peerAddr) async {
|
||||
onChanged: (String importBundle) async {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
final setPeerAttribute = {
|
||||
"EventType": "AddContact",
|
||||
"Data": {"ImportString": peerAddr},
|
||||
};
|
||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, importBundle);
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (globalErrorHandler.explicitAddContactSuccess) {
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).successfullAddedContact + peerAddr));
|
||||
if (globalErrorHandler.importBundleSuccess) {
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + importBundle));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
},
|
||||
labelText: '',
|
||||
)
|
||||
])));
|
||||
}
|
||||
|
@ -161,11 +165,6 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
return Text("You need to add a server before you can create a group.");
|
||||
}
|
||||
|
||||
// if we haven't picked a server yet, pick the first one in the list...
|
||||
if (server.isEmpty) {
|
||||
server = Provider.of<ProfileInfoState>(context).serverList.servers.first.onion;
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.all(30),
|
||||
padding: EdgeInsets.all(20),
|
||||
|
@ -176,80 +175,59 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context).server),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.server),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
DropdownButton(
|
||||
onChanged: (newServer) {
|
||||
server = newServer;
|
||||
onChanged: (String? newServer) {
|
||||
setState(() {
|
||||
server = newServer!;
|
||||
});
|
||||
},
|
||||
isExpanded: true, // magic property
|
||||
value: server,
|
||||
items: Provider.of<ProfileInfoState>(context).serverList.servers.map<DropdownMenuItem<String>>((ServerInfoState serverInfo) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: serverInfo.onion,
|
||||
child: Text(serverInfo.onion),
|
||||
child: Text(
|
||||
serverInfo.onion,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}).toList()),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).groupName),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.groupName),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(controller: ctrlrGroupName, labelText: AppLocalizations.of(context).groupNameLabel, onChanged: (newValue) {}),
|
||||
CwtchTextField(
|
||||
controller: ctrlrGroupName,
|
||||
labelText: AppLocalizations.of(context)!.groupNameLabel,
|
||||
onChanged: (newValue) {},
|
||||
validator: (value) {},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
child: Text(AppLocalizations.of(context).createGroupBtn),
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateGroup(profileOnion, server, ctrlrGroupName.text);
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + " " + ctrlrGroupName.text));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
Navigator.pop(context);
|
||||
});
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.createGroupBtn),
|
||||
),
|
||||
],
|
||||
)));
|
||||
}
|
||||
|
||||
/// TODO Join Group Pane
|
||||
Widget joinGroupTab() {
|
||||
return Container(
|
||||
margin: EdgeInsets.all(30),
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Form(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
key: _joinGroupFormKey,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context).joinGroupTab),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrContact,
|
||||
validator: (value) {
|
||||
if (value == "") {
|
||||
return null;
|
||||
}
|
||||
if (globalErrorHandler.importBundleError) {
|
||||
return AppLocalizations.of(context).invalidImportString;
|
||||
} else if (globalErrorHandler.importBundleSuccess) {}
|
||||
return null;
|
||||
},
|
||||
onChanged: (String importBundle) async {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, importBundle);
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (globalErrorHandler.importBundleSuccess) {
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).successfullAddedContact + importBundle));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
])));
|
||||
}
|
||||
|
||||
/// TODO Manage Servers Tab
|
||||
Widget manageServersTab() {
|
||||
final tiles = Provider.of<ProfileInfoState>(context).serverList.servers.map((ServerInfoState server) {
|
||||
|
|
|
@ -16,7 +16,7 @@ import '../opaque.dart';
|
|||
import '../settings.dart';
|
||||
|
||||
class AddEditProfileView extends StatefulWidget {
|
||||
const AddEditProfileView({Key key}) : super(key: key);
|
||||
const AddEditProfileView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AddEditProfileViewState createState() => _AddEditProfileViewState();
|
||||
|
@ -30,8 +30,8 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
final ctrlrPass = TextEditingController(text: "");
|
||||
final ctrlrPass2 = TextEditingController(text: "");
|
||||
final ctrlrOnion = TextEditingController(text: "");
|
||||
bool usePassword;
|
||||
bool deleted;
|
||||
late bool usePassword;
|
||||
late bool deleted;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -48,15 +48,15 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context).addProfileTitle : AppLocalizations.of(context).editProfileTitle),
|
||||
title: Text(Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context)!.addProfileTitle : AppLocalizations.of(context)!.editProfileTitle),
|
||||
),
|
||||
body: _buildForm(),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleSwitchPassword(bool value) {
|
||||
void _handleSwitchPassword(bool? value) {
|
||||
setState(() {
|
||||
usePassword = value;
|
||||
usePassword = value!;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -88,16 +88,18 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
diameter: 120,
|
||||
maskOut: false,
|
||||
border: theme.theme.portraitOnlineBorderColor(),
|
||||
badgeTextColor: Colors.red,
|
||||
badgeColor: Colors.red,
|
||||
)
|
||||
])),
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context).displayNameLabel),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrNick,
|
||||
labelText: AppLocalizations.of(context).yourDisplayName,
|
||||
labelText: AppLocalizations.of(context)!.yourDisplayName,
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return "Please enter a display name";
|
||||
|
@ -112,7 +114,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).addressLabel),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -120,29 +122,21 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
controller: ctrlrOnion,
|
||||
onPressed: _copyOnion,
|
||||
icon: Icon(Icons.copy),
|
||||
tooltip: AppLocalizations.of(context).copyBtn,
|
||||
tooltip: AppLocalizations.of(context)!.copyBtn,
|
||||
)
|
||||
])),
|
||||
// We only allow setting password types on profile creation
|
||||
Visibility(
|
||||
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Radio(
|
||||
value: false,
|
||||
groupValue: usePassword,
|
||||
Checkbox(
|
||||
value: usePassword,
|
||||
fillColor: MaterialStateProperty.all(theme.current().defaultButtonColor()),
|
||||
activeColor: theme.current().defaultButtonActiveColor(),
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).radioNoPassword,
|
||||
style: TextStyle(color: theme.current().mainTextColor()),
|
||||
),
|
||||
Radio(
|
||||
value: true,
|
||||
groupValue: usePassword,
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).radioUsePassword,
|
||||
AppLocalizations.of(context)!.radioUsePassword,
|
||||
style: TextStyle(color: theme.current().mainTextColor()),
|
||||
),
|
||||
])),
|
||||
|
@ -155,7 +149,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
Visibility(
|
||||
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context).currentPasswordLabel),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -164,7 +158,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context).passwordErrorEmpty;
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
@ -173,7 +167,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
height: 20,
|
||||
),
|
||||
])),
|
||||
CwtchLabel(label: AppLocalizations.of(context).password1Label),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.password1Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -182,10 +176,10 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context).passwordErrorEmpty;
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass2.value.text) {
|
||||
return AppLocalizations.of(context).passwordErrorMatch;
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
@ -193,7 +187,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).password2Label),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -202,10 +196,10 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context).passwordErrorEmpty;
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass.value.text) {
|
||||
return AppLocalizations.of(context).passwordErrorMatch;
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
|
@ -214,10 +208,19 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _createPressed,
|
||||
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
|
||||
child: Text(Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context).addNewProfileBtn : AppLocalizations.of(context).saveProfileBtn),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: _createPressed,
|
||||
child: Text(
|
||||
Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context)!.addNewProfileBtn : AppLocalizations.of(context)!.saveProfileBtn,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Visibility(
|
||||
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
|
||||
|
@ -226,7 +229,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
height: 20,
|
||||
),
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context).enterCurrentPasswordForDelete,
|
||||
message: AppLocalizations.of(context)!.enterCurrentPasswordForDelete,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: checkCurrentPassword()
|
||||
? null
|
||||
|
@ -235,7 +238,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
},
|
||||
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
|
||||
icon: Icon(Icons.delete_forever),
|
||||
label: Text(AppLocalizations.of(context).deleteBtn),
|
||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||
))
|
||||
]))
|
||||
]))))));
|
||||
|
@ -252,7 +255,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
// This will run all the validations in the form including
|
||||
// checking that display name is not empty, and an actual check that the passwords
|
||||
// match (and are provided if the user has requested an encrypted profile).
|
||||
if (_formKey.currentState.validate()) {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty) {
|
||||
if (usePassword == true) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text);
|
||||
|
@ -323,7 +326,7 @@ showAlertDialog(BuildContext context) {
|
|||
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()),
|
||||
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()),
|
||||
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
child: Text(AppLocalizations.of(context).deleteProfileConfirmBtn),
|
||||
child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
||||
onPressed: () {
|
||||
// TODO Actually Delete the Peer
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
|
@ -332,7 +335,7 @@ showAlertDialog(BuildContext context) {
|
|||
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(AppLocalizations.of(context).deleteProfileConfirmBtn),
|
||||
title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
||||
actions: [
|
||||
cancelButton,
|
||||
continueButton,
|
||||
|
|
|
@ -12,14 +12,14 @@ import '../model.dart';
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class ContactsView extends StatefulWidget {
|
||||
const ContactsView({Key key}) : super(key: key);
|
||||
const ContactsView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ContactsViewState createState() => _ContactsViewState();
|
||||
}
|
||||
|
||||
class _ContactsViewState extends State<ContactsView> {
|
||||
TextEditingController ctrlrFilter;
|
||||
late TextEditingController ctrlrFilter;
|
||||
bool showSearchBar = false;
|
||||
|
||||
@override
|
||||
|
@ -31,67 +31,66 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Row(children: [
|
||||
ProfileImage(
|
||||
imagePath: Provider.of<ProfileInfoState>(context).imagePath,
|
||||
diameter: 42,
|
||||
border: Provider.of<Settings>(context).theme.portraitOnlineBorderColor(),
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname ?? Provider.of<ProfileInfoState>(context).onion ?? '').replaceAll("%2", "Contacts"),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)), //todo
|
||||
]),
|
||||
actions: [
|
||||
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
|
||||
IconButton(
|
||||
icon: Icon(Icons.copy),
|
||||
onPressed: _copyOnion,
|
||||
),
|
||||
IconButton(
|
||||
// need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
|
||||
icon: Icon(showSearchBar || Provider.of<ContactListState>(context).isFiltered ? Icons.search_off : Icons.search),
|
||||
onPressed: () {
|
||||
Provider.of<ContactListState>(context, listen: false).filter = "";
|
||||
setState(() {
|
||||
showSearchBar = !showSearchBar;
|
||||
});
|
||||
})
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddContact,
|
||||
tooltip: AppLocalizations.of(context).tooltipAddContact,
|
||||
child: const Icon(Icons.person_add_sharp),
|
||||
),
|
||||
body: showSearchBar || Provider.of<ContactListState>(context).isFiltered ? _buildFilterable() : _buildContactList(),
|
||||
);
|
||||
endDrawerEnableOpenDragGesture: false,
|
||||
drawerEnableOpenDragGesture: false,
|
||||
appBar: AppBar(
|
||||
title: RepaintBoundary(
|
||||
child: Row(children: [
|
||||
ProfileImage(
|
||||
imagePath: Provider.of<ProfileInfoState>(context).imagePath,
|
||||
diameter: 42,
|
||||
border: Provider.of<Settings>(context).current().portraitOnlineBorderColor(),
|
||||
badgeTextColor: Colors.red,
|
||||
badgeColor: Colors.red,
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Text("%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", "Contacts"),
|
||||
overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor()))), //todo
|
||||
])),
|
||||
actions: [
|
||||
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
|
||||
IconButton(
|
||||
// need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
|
||||
icon: Icon(showSearchBar || Provider.of<ContactListState>(context).isFiltered ? Icons.search_off : Icons.search),
|
||||
onPressed: () {
|
||||
Provider.of<ContactListState>(context, listen: false).filter = "";
|
||||
setState(() {
|
||||
showSearchBar = !showSearchBar;
|
||||
});
|
||||
})
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddContact,
|
||||
tooltip: AppLocalizations.of(context)!.tooltipAddContact,
|
||||
child: const Icon(Icons.person_add_sharp),
|
||||
),
|
||||
body: showSearchBar || Provider.of<ContactListState>(context).isFiltered ? _buildFilterable() : _buildContactList());
|
||||
}
|
||||
|
||||
Widget _buildFilterable() {
|
||||
Widget txtfield = CwtchTextField(
|
||||
controller: ctrlrFilter,
|
||||
labelText: AppLocalizations.of(context).search,
|
||||
onChanged: (newVal) {
|
||||
Provider.of<ContactListState>(context, listen: false).filter = newVal;
|
||||
});
|
||||
controller: ctrlrFilter,
|
||||
labelText: AppLocalizations.of(context)!.search,
|
||||
onChanged: (newVal) {
|
||||
Provider.of<ContactListState>(context, listen: false).filter = newVal;
|
||||
},
|
||||
);
|
||||
return Column(children: [Padding(padding: EdgeInsets.all(8), child: txtfield), Expanded(child: _buildContactList())]);
|
||||
}
|
||||
|
||||
Widget _buildContactList() {
|
||||
final tiles = Provider.of<ContactListState>(context).filteredList().map((ContactInfoState contact) {
|
||||
return ChangeNotifierProvider<ContactInfoState>.value(key: ValueKey(contact.profileOnion+""+contact.onion), value: contact, builder: (_, __) => ContactRow());
|
||||
return ChangeNotifierProvider<ContactInfoState>.value(key: ValueKey(contact.profileOnion + "" + contact.onion), value: contact, builder: (_, __) => RepaintBoundary(child: ContactRow()));
|
||||
});
|
||||
final divided = ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: tiles,
|
||||
).toList();
|
||||
return ListView(children: divided);
|
||||
return RepaintBoundary(child: ListView(children: divided));
|
||||
}
|
||||
|
||||
void _pushAddContact() {
|
||||
|
@ -117,10 +116,4 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
},
|
||||
));
|
||||
}
|
||||
|
||||
void _copyOnion() {
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).copiedClipboardNotification)); //todo
|
||||
// Find the Scaffold in the widget tree and use it to show a SnackBar.
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
|
|||
children: <Widget>[
|
||||
Flexible(
|
||||
flex: flwtch.columns[0],
|
||||
child: ContactsView(),
|
||||
child: ContactsView(
|
||||
key: widget.key,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
flex: flwtch.columns[1],
|
||||
|
|
|
@ -24,7 +24,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context).cwtchSettingsTitle),
|
||||
title: Text(AppLocalizations.of(context)!.cwtchSettingsTitle),
|
||||
),
|
||||
body: _buildSettingsList(),
|
||||
);
|
||||
|
@ -43,13 +43,13 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
),
|
||||
child: Column(children: [
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context).settingLanguage, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
title: Text(AppLocalizations.of(context)!.settingLanguage, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
leading: Icon(Icons.language, color: settings.current().mainTextColor()),
|
||||
trailing: DropdownButton(
|
||||
value: Provider.of<Settings>(context).locale.languageCode,
|
||||
onChanged: (String newValue) {
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
settings.switchLocale(Locale(newValue, ''));
|
||||
settings.switchLocale(Locale(newValue!, ''));
|
||||
saveSettings(context);
|
||||
});
|
||||
},
|
||||
|
@ -60,7 +60,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
);
|
||||
}).toList())),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context).settingTheme, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
title: Text(AppLocalizations.of(context)!.settingTheme, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
value: settings.current() == Opaque.light,
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
|
@ -75,11 +75,11 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
secondary: Icon(Icons.lightbulb_outline, color: settings.current().mainTextColor()),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(/*AppLocalizations.of(context).settingLanguage*/ "UI Columns", style: TextStyle(color: settings.current().mainTextColor())),
|
||||
title: Text(/*AppLocalizations.of(context)!.settingLanguage*/ "UI Columns", style: TextStyle(color: settings.current().mainTextColor())),
|
||||
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
||||
trailing: DropdownButton(
|
||||
value: "Single",
|
||||
onChanged: (String newValue) {
|
||||
onChanged: (String? newValue) {
|
||||
if (newValue == "Double (1:2)") {
|
||||
Provider.of<FlwtchState>(context).columns = [1, 2];
|
||||
} else if (newValue == "Double (1:4)") {
|
||||
|
@ -95,8 +95,8 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
);
|
||||
}).toList())),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context).blockUnknownLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context).descriptionBlockUnknownConnections),
|
||||
title: Text(AppLocalizations.of(context)!.blockUnknownLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionBlockUnknownConnections),
|
||||
value: settings.blockUnknownConnections,
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
|
@ -111,8 +111,8 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
secondary: Icon(Icons.app_blocking, color: settings.current().mainTextColor()),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context).experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context).descriptionExperiments),
|
||||
title: Text(AppLocalizations.of(context)!.experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionExperiments),
|
||||
value: settings.experimentsEnabled,
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
|
@ -130,9 +130,9 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
child: Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context).enableGroups, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context).descriptionExperimentsGroups),
|
||||
value: settings.experiments.containsKey(TapirGroupsExperiment) && settings.experiments[TapirGroupsExperiment],
|
||||
title: Text(AppLocalizations.of(context)!.enableGroups, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionExperimentsGroups),
|
||||
value: settings.experiments.containsKey(TapirGroupsExperiment) && settings.experiments[TapirGroupsExperiment]!,
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
settings.enableExperiment(TapirGroupsExperiment);
|
||||
|
@ -156,7 +156,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
height: 128,
|
||||
)),
|
||||
applicationName: "Cwtch (Flutter UI)",
|
||||
applicationVersion: AppLocalizations.of(context).version.replaceAll("%1", constructVersionString(Provider.of<Settings>(context).packageInfo)),
|
||||
applicationVersion: AppLocalizations.of(context)!.version.replaceAll("%1", constructVersionString(Provider.of<Settings>(context).packageInfo)),
|
||||
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
|
||||
),
|
||||
]))));
|
||||
|
@ -177,22 +177,22 @@ String constructVersionString(PackageInfo pinfo) {
|
|||
/// an individual language code. There might be a more efficient way of doing this.
|
||||
String getLanguageFull(context, String languageCode) {
|
||||
if (languageCode == "en") {
|
||||
return AppLocalizations.of(context).localeEn;
|
||||
return AppLocalizations.of(context)!.localeEn;
|
||||
}
|
||||
if (languageCode == "es") {
|
||||
return AppLocalizations.of(context).localeEs;
|
||||
return AppLocalizations.of(context)!.localeEs;
|
||||
}
|
||||
if (languageCode == "fr") {
|
||||
return AppLocalizations.of(context).localeFr;
|
||||
return AppLocalizations.of(context)!.localeFr;
|
||||
}
|
||||
if (languageCode == "pt") {
|
||||
return AppLocalizations.of(context).localePt;
|
||||
return AppLocalizations.of(context)!.localePt;
|
||||
}
|
||||
if (languageCode == "de") {
|
||||
return AppLocalizations.of(context).localeDe;
|
||||
return AppLocalizations.of(context)!.localeDe;
|
||||
}
|
||||
if (languageCode == "it") {
|
||||
return AppLocalizations.of(context).localeIt;
|
||||
return AppLocalizations.of(context)!.localeIt;
|
||||
}
|
||||
return languageCode;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(Provider.of<ContactInfoState>(context).nickname + " " + AppLocalizations.of(context).conversationSettings),
|
||||
title: Text(Provider.of<ContactInfoState>(context).nickname + " " + AppLocalizations.of(context)!.conversationSettings),
|
||||
),
|
||||
body: _buildSettingsList(),
|
||||
);
|
||||
|
@ -60,40 +60,15 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(30),
|
||||
padding: EdgeInsets.all(20),
|
||||
margin: EdgeInsets.all(10),
|
||||
padding: EdgeInsets.all(2),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
// Address Copy Button
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).groupAddr),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrGroupAddr,
|
||||
)
|
||||
]),
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).server),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).server),
|
||||
)
|
||||
]),
|
||||
// Nickname Save Button
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).displayNameLabel),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -105,21 +80,70 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
Provider.of<ContactInfoState>(context, listen: false).nickname = ctrlrNick.text;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetGroupAttribute(profileOnion, handle, "local.name", ctrlrNick.text);
|
||||
// todo translations
|
||||
final snackBar = SnackBar(content: Text("Group Nickname changed successfully"));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
},
|
||||
icon: Icon(Icons.save),
|
||||
tooltip: AppLocalizations.of(context).saveBtn,
|
||||
tooltip: AppLocalizations.of(context)!.saveBtn,
|
||||
)
|
||||
]),
|
||||
|
||||
// Address Copy Button
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.groupAddr),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrGroupAddr,
|
||||
labelText: '',
|
||||
validator: (value) {},
|
||||
)
|
||||
]),
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).conversationSettings),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.server),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).server),
|
||||
validator: (value) {},
|
||||
labelText: '',
|
||||
)
|
||||
]),
|
||||
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
// TODO
|
||||
]),
|
||||
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.rejectGroupBtn,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
},
|
||||
icon: Icon(Icons.delete),
|
||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||
))
|
||||
])
|
||||
])))));
|
||||
});
|
||||
});
|
||||
|
@ -127,7 +151,47 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
|
||||
void _copyOnion() {
|
||||
Clipboard.setData(new ClipboardData(text: Provider.of<ContactInfoState>(context, listen: false).onion));
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).copiedClipboardNotification));
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedClipboardNotification));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
showAlertDialog(BuildContext context) {
|
||||
// set up the buttons
|
||||
Widget cancelButton = TextButton(
|
||||
child: Text("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)!.deleteProfileConfirmBtn),
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LeaveGroup(profileOnion, handle);
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
||||
actions: [
|
||||
cancelButton,
|
||||
continueButton,
|
||||
],
|
||||
);
|
||||
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/views/peersettingsview.dart';
|
||||
import 'package:cwtch/widgets/DropdownContacts.dart';
|
||||
|
@ -86,7 +87,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
));
|
||||
}
|
||||
|
||||
void _sendMessage([String ignoredParam]) {
|
||||
void _sendMessage([String? ignoredParam]) {
|
||||
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
|
@ -94,7 +95,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
_sendMessageHelper();
|
||||
}
|
||||
|
||||
void _sendInvitation([String ignoredParam]) {
|
||||
void _sendInvitation([String? ignoredParam]) {
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.SendInvitation(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, this.selectedContact);
|
||||
|
@ -114,37 +115,36 @@ class _MessageViewState extends State<MessageView> {
|
|||
Widget _buildComposeBox() {
|
||||
return Container(
|
||||
color: Provider.of<Settings>(context).theme.backgroundMainColor(),
|
||||
padding: EdgeInsets.all(2),
|
||||
margin: EdgeInsets.all(2),
|
||||
height: 100,
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: TextField(
|
||||
key: Key('txtCompose'),
|
||||
controller: ctrlrCompose,
|
||||
focusNode: focusNode,
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: _sendMessage,
|
||||
)),
|
||||
Column(children: [
|
||||
SizedBox(
|
||||
width: 100,
|
||||
height: 50,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(2, 2, 2, 2),
|
||||
child: ElevatedButton(
|
||||
child: Icon(Icons.send, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
style: ButtonStyle(
|
||||
fixedSize: MaterialStateProperty.all(Size(86, 50)),
|
||||
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor()))),
|
||||
child: TextFormField(
|
||||
key: Key('txtCompose'),
|
||||
controller: ctrlrCompose,
|
||||
autofocus: true,
|
||||
focusNode: focusNode,
|
||||
textInputAction: TextInputAction.send,
|
||||
onFieldSubmitted: _sendMessage,
|
||||
decoration: InputDecoration(
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabled: true,
|
||||
prefixIcon: IconButton(
|
||||
icon: Icon(Icons.insert_invitation, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
tooltip: "Send a contact or group invite",
|
||||
onPressed: () => _modalSendInvitation(context)),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(Icons.send, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
tooltip: "Send Message",
|
||||
onPressed: _sendMessage,
|
||||
),
|
||||
onPressed: _sendMessage,
|
||||
))),
|
||||
SizedBox(
|
||||
width: 86,
|
||||
height: 40,
|
||||
child: IconButton(icon: Icon(Icons.insert_invitation, size: 12, color: Provider.of<Settings>(context).theme.mainTextColor()), onPressed: () => _modalSendInvitation(context))),
|
||||
])
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -168,7 +168,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(bcontext).invitationLabel),
|
||||
Text(AppLocalizations.of(bcontext)!.invitationLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -183,7 +183,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
height: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
child: Text(AppLocalizations.of(bcontext).inviteBtn, semanticsLabel: AppLocalizations.of(bcontext).inviteBtn),
|
||||
child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn),
|
||||
onPressed: () {
|
||||
this._sendInvitation();
|
||||
Navigator.pop(bcontext);
|
||||
|
|
|
@ -55,31 +55,11 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(30),
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
// Address Copy Button
|
||||
margin: EdgeInsets.all(10),
|
||||
padding: EdgeInsets.all(2),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).addressLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchButtonTextField(
|
||||
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).onion),
|
||||
onPressed: _copyOnion,
|
||||
icon: Icon(Icons.copy),
|
||||
tooltip: AppLocalizations.of(context).copyBtn,
|
||||
)
|
||||
]),
|
||||
// Nickname Save Button
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).displayNameLabel),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -96,21 +76,41 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
};
|
||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||
// todo translations
|
||||
final snackBar = SnackBar(content: Text("Nickname changed successfully"));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
},
|
||||
icon: Icon(Icons.save),
|
||||
tooltip: AppLocalizations.of(context).saveBtn,
|
||||
tooltip: AppLocalizations.of(context)!.saveBtn,
|
||||
)
|
||||
]),
|
||||
|
||||
// Address Copy Button
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchButtonTextField(
|
||||
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).onion),
|
||||
onPressed: _copyOnion,
|
||||
icon: Icon(Icons.copy),
|
||||
tooltip: AppLocalizations.of(context)!.copyBtn,
|
||||
)
|
||||
]),
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).conversationSettings),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context).blockBtn, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
title: Text(AppLocalizations.of(context)!.blockBtn, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
value: Provider.of<ContactInfoState>(context).isBlocked,
|
||||
onChanged: (bool blocked) {
|
||||
// Save local blocked status
|
||||
|
@ -141,15 +141,15 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
secondary: Icon(Icons.block, color: settings.current().mainTextColor()),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context).savePeerHistory, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context).savePeerHistoryDescription),
|
||||
title: Text(AppLocalizations.of(context)!.savePeerHistory, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.savePeerHistoryDescription),
|
||||
leading: Icon(Icons.history_sharp, color: settings.current().mainTextColor()),
|
||||
trailing: DropdownButton(
|
||||
value: Provider.of<ContactInfoState>(context).savePeerHistory == "DefaultDeleteHistory"
|
||||
? AppLocalizations.of(context).dontSavePeerHistory
|
||||
? AppLocalizations.of(context)!.dontSavePeerHistory
|
||||
: (Provider.of<ContactInfoState>(context).savePeerHistory == "SaveHistory"
|
||||
? AppLocalizations.of(context).savePeerHistory
|
||||
: AppLocalizations.of(context).dontSavePeerHistory),
|
||||
? AppLocalizations.of(context)!.savePeerHistory
|
||||
: AppLocalizations.of(context)!.dontSavePeerHistory),
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
// Set whether or not to dave the Contact History...
|
||||
|
@ -157,7 +157,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
var onion = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
const SaveHistoryKey = "SavePeerHistory";
|
||||
|
||||
if (newValue == AppLocalizations.of(context).savePeerHistory) {
|
||||
if (newValue == AppLocalizations.of(context)!.savePeerHistory) {
|
||||
Provider.of<ContactInfoState>(context, listen: false).savePeerHistory = "SaveHistory";
|
||||
final setPeerAttribute = {
|
||||
"EventType": "SetPeerAttribute",
|
||||
|
@ -177,7 +177,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
}
|
||||
});
|
||||
},
|
||||
items: [AppLocalizations.of(context).savePeerHistory, AppLocalizations.of(context).dontSavePeerHistory].map<DropdownMenuItem<String>>((String value) {
|
||||
items: [AppLocalizations.of(context)!.savePeerHistory, AppLocalizations.of(context)!.dontSavePeerHistory].map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
|
@ -191,7 +191,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
|
||||
void _copyOnion() {
|
||||
Clipboard.setData(new ClipboardData(text: Provider.of<ContactInfoState>(context, listen: false).onion));
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).copiedClipboardNotification));
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedClipboardNotification));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:cwtch/widgets/profilerow.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../opaque.dart';
|
||||
import 'addeditprofileview.dart';
|
||||
import 'globalsettingsview.dart';
|
||||
|
||||
|
@ -31,36 +32,52 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 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?
|
||||
return new WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: Scaffold(
|
||||
backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor(),
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context).titleManageProfiles),
|
||||
actions: [
|
||||
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
|
||||
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug),
|
||||
IconButton(
|
||||
icon: Icon(Icons.lock_open),
|
||||
tooltip: AppLocalizations.of(context).tooltipUnlockProfiles,
|
||||
onPressed: _modalUnlockProfiles,
|
||||
),
|
||||
IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context).tooltipOpenSettings, onPressed: _pushGlobalSettings),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddEditProfile,
|
||||
tooltip: AppLocalizations.of(context).addNewProfileBtn,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
semanticLabel: AppLocalizations.of(context).addNewProfileBtn,
|
||||
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 => false,
|
||||
child: Scaffold(
|
||||
backgroundColor: settings.theme.backgroundMainColor(),
|
||||
appBar: AppBar(
|
||||
title: Row(children: [
|
||||
Image(
|
||||
image: AssetImage("assets/core/knott-white.png"),
|
||||
filterQuality: FilterQuality.medium,
|
||||
isAntiAlias: true,
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorBlendMode: BlendMode.dstIn,
|
||||
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor())))
|
||||
]),
|
||||
actions: [
|
||||
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
|
||||
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug),
|
||||
IconButton(
|
||||
icon: Icon(Icons.lock_open),
|
||||
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
|
||||
onPressed: _modalUnlockProfiles,
|
||||
),
|
||||
IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: _buildProfileManager(), //_buildSuggestions(),
|
||||
));
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddEditProfile,
|
||||
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||
),
|
||||
),
|
||||
body: _buildProfileManager(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void _setLoggingLevelDebug() {
|
||||
|
@ -112,55 +129,83 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
void _modalUnlockProfiles() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
height: 200, // bespoke value courtesy of the [TextField] docs
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(context).enterProfilePassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPassword,
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context).unlock, semanticsLabel: AppLocalizations.of(context).unlock),
|
||||
onPressed: () {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text);
|
||||
ctrlrPassword.text = "";
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
));
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
height: 200, // bespoke value courtesy of the [TextField] docs
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(context)!.enterProfilePassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
autofocus: true,
|
||||
controller: ctrlrPassword,
|
||||
action: unlock,
|
||||
validator: (value) {},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
|
||||
onPressed: () {
|
||||
unlock(ctrlrPassword.value.text);
|
||||
},
|
||||
)),
|
||||
Spacer()
|
||||
]),
|
||||
],
|
||||
))),
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
void unlock(String password) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(password);
|
||||
ctrlrPassword.text = "";
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
Widget _buildProfileManager() {
|
||||
final tiles = Provider.of<ProfileListState>(context).profiles.map(
|
||||
(ProfileInfoState profile) {
|
||||
return ChangeNotifierProvider<ProfileInfoState>.value(
|
||||
value: profile,
|
||||
builder: (context, child) => ProfileRow(),
|
||||
return Consumer<ProfileListState>(
|
||||
builder: (context, pls, child) {
|
||||
final tiles = pls.profiles.map(
|
||||
(ProfileInfoState profile) {
|
||||
return ChangeNotifierProvider<ProfileInfoState>.value(
|
||||
value: profile,
|
||||
builder: (context, child) => RepaintBoundary(child: ProfileRow()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final divided = ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: tiles,
|
||||
).toList();
|
||||
|
||||
if (tiles.isEmpty) {
|
||||
return const Center(
|
||||
child: const Text(
|
||||
"Please create or unlock a profile to begin!",
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
||||
return ListView(children: divided);
|
||||
},
|
||||
);
|
||||
|
||||
final divided = ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: tiles,
|
||||
).toList();
|
||||
|
||||
return ListView(children: divided);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,8 @@ import 'package:flutter/material.dart';
|
|||
class SplashView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print("SplashView build()");
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text("Cwtch")),
|
||||
body: Center(child: Column(children: <Widget>[Text("Loading Cwtch...")])),
|
||||
return const Scaffold(
|
||||
body: const Center(child: const Text("Loading Cwtch...")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class _TorStatusView extends State<TorStatusView> {
|
|||
ListTile(
|
||||
leading: TorIcon(),
|
||||
title: Text("Tor Status"),
|
||||
subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context).networkStatusOnline : torStatus.status),
|
||||
subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context)!.networkStatusOnline : torStatus.status),
|
||||
trailing: ElevatedButton(
|
||||
child: Text("Reset"),
|
||||
onPressed: () {
|
||||
|
|
|
@ -9,7 +9,7 @@ import '../model.dart';
|
|||
// Pass an onChanged handler to access value
|
||||
class DropdownContacts extends StatefulWidget {
|
||||
DropdownContacts({
|
||||
this.onChanged,
|
||||
required this.onChanged,
|
||||
});
|
||||
final Function(dynamic) onChanged;
|
||||
|
||||
|
@ -18,22 +18,20 @@ class DropdownContacts extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _DropdownContactsState extends State<DropdownContacts> {
|
||||
String selected;
|
||||
String? selected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DropdownButton(
|
||||
value: this.selected,
|
||||
items: Provider.of<ProfileInfoState>(context, listen: false).contactList.contacts.map<DropdownMenuItem<String>>((ContactInfoState contact) {
|
||||
return DropdownMenuItem<String>(value: contact.onion, child: Text(contact.nickname ?? contact.onion));
|
||||
return DropdownMenuItem<String>(value: contact.onion, child: Text(contact.nickname));
|
||||
}).toList(),
|
||||
onChanged: (newVal) {
|
||||
onChanged: (String? newVal) {
|
||||
setState(() {
|
||||
this.selected = newVal;
|
||||
});
|
||||
if (widget.onChanged != null) {
|
||||
widget.onChanged(newVal);
|
||||
}
|
||||
widget.onChanged(newVal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import 'package:provider/provider.dart';
|
|||
// Provides a styled Text Field for use in Form Widgets.
|
||||
// Callers must provide a text controller, label helper text and a validator.
|
||||
class CwtchButtonTextField extends StatefulWidget {
|
||||
CwtchButtonTextField({this.controller, this.onPressed, this.icon, this.tooltip, this.readonly = true});
|
||||
CwtchButtonTextField({required this.controller, required this.onPressed, required this.icon, required this.tooltip, this.readonly = true});
|
||||
final TextEditingController controller;
|
||||
final Function onPressed;
|
||||
final Function()? onPressed;
|
||||
final Icon icon;
|
||||
final String tooltip;
|
||||
final bool readonly;
|
||||
|
@ -37,6 +37,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
|
|||
),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
filled: true,
|
||||
fillColor: theme.current().textfieldBackgroundColor(),
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)),
|
||||
focusedErrorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)),
|
||||
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)),
|
||||
|
@ -44,10 +45,8 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
|
|||
color: theme.current().textfieldErrorColor(),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
fillColor: theme.current().textfieldBackgroundColor(),
|
||||
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0))),
|
||||
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -87,18 +87,19 @@ class _ContactRowState extends State<ContactRow> {
|
|||
}
|
||||
|
||||
void _pushMessageView(String handle) {
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle).unreadMessages = 0;
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0;
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext builderContext) {
|
||||
var profile = Provider.of<FlwtchState>(builderContext, listen: false).profs.getProfile(profileOnion);
|
||||
// assert we have an actual profile...
|
||||
var profile = Provider.of<FlwtchState>(builderContext, listen: false).profs.getProfile(profileOnion)!;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: profile),
|
||||
ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)),
|
||||
ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!),
|
||||
],
|
||||
builder:(context, child) => MessageView(),
|
||||
builder: (context, child) => MessageView(),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -123,8 +124,13 @@ class _ContactRowState extends State<ContactRow> {
|
|||
|
||||
String dateToNiceString(DateTime date) {
|
||||
if (date.millisecondsSinceEpoch == 0) {
|
||||
return AppLocalizations.of(context).dateNever;
|
||||
return AppLocalizations.of(context)!.dateNever;
|
||||
}
|
||||
return DateFormat.yMd().add_jm().format(date.toLocal());
|
||||
// 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());
|
||||
}
|
||||
// Otherwise just state the time.
|
||||
return DateFormat.Hms().format(date.toLocal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import '../settings.dart';
|
|||
// Callers must provide a label text
|
||||
// TODO: Integrate this with a settings "zoom" / accessibility setting
|
||||
class CwtchLabel extends StatefulWidget {
|
||||
CwtchLabel({this.label});
|
||||
CwtchLabel({required this.label});
|
||||
final String label;
|
||||
|
||||
@override
|
||||
|
|
|
@ -37,11 +37,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
var senderDisplayStr = "";
|
||||
if (Provider.of<MessageState>(context).senderOnion != null) {
|
||||
var contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
||||
if (contact == null) {
|
||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
||||
} else {
|
||||
senderDisplayStr = contact.nickname ?? contact.onion;
|
||||
}
|
||||
senderDisplayStr = contact!.nickname;
|
||||
}
|
||||
var wdgSender = Center(
|
||||
widthFactor: 1,
|
||||
|
|
|
@ -22,16 +22,18 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
|
||||
if (Provider.of<MessageState>(context).timestamp != null) {
|
||||
// user-configurable timestamps prolly ideal? #todo
|
||||
prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp);
|
||||
DateTime messageDate = Provider.of<MessageState>(context).timestamp;
|
||||
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
||||
}
|
||||
|
||||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
if (Provider.of<MessageState>(context).senderOnion != null) {
|
||||
var contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
||||
if (contact == null) {
|
||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
||||
if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
senderDisplayStr = contact.nickname ?? contact.onion;
|
||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
||||
}
|
||||
}
|
||||
var wdgSender = SelectableText(senderDisplayStr,
|
||||
|
@ -71,26 +73,27 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
||||
return Container(
|
||||
return RepaintBoundary(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
|
||||
border:
|
||||
Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(), width: 1),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(borderRadiousEh),
|
||||
topRight: Radius.circular(borderRadiousEh),
|
||||
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
|
||||
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(9.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]))));
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
|
||||
border: Border.all(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(), width: 1),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(borderRadiousEh),
|
||||
topRight: Radius.circular(borderRadiousEh),
|
||||
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
|
||||
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(9.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,41 +15,42 @@ class _MessageListState extends State<MessageList> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext outerContext) {
|
||||
return Card(
|
||||
child: Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
controller: ctrlr1,
|
||||
child: Container(
|
||||
// Only show broken heart is the contact is offline...
|
||||
decoration: BoxDecoration(
|
||||
image: Provider.of<ContactInfoState>(outerContext).isOnline()
|
||||
? null
|
||||
: DecorationImage(
|
||||
fit: BoxFit.contain,
|
||||
image: AssetImage("assets/core/negative_heart_512px.png"),
|
||||
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.mainTextColor(), BlendMode.srcIn))),
|
||||
child: ListView.builder(
|
||||
return RepaintBoundary(
|
||||
child: Container(
|
||||
child: Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
controller: ctrlr1,
|
||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||
reverse: true,
|
||||
itemBuilder: (itemBuilderContext, index) {
|
||||
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
||||
return ChangeNotifierProvider(
|
||||
key: ValueKey(trueIndex),
|
||||
create: (x) => MessageState(
|
||||
context: itemBuilderContext,
|
||||
profileOnion: Provider.of<ProfileInfoState>(outerContext, listen: false).onion,
|
||||
contactHandle: Provider.of<ContactInfoState>(x, listen: false).onion,
|
||||
messageIndex: trueIndex,
|
||||
),
|
||||
builder: (bcontext, child) {
|
||||
String idx = Provider.of<ContactInfoState>(outerContext).isGroup == true && Provider.of<MessageState>(bcontext).signature.isEmpty == false
|
||||
? Provider.of<MessageState>(bcontext).signature
|
||||
: trueIndex.toString();
|
||||
return MessageRow(key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
});
|
||||
},
|
||||
),
|
||||
)));
|
||||
child: Container(
|
||||
// Only show broken heart is the contact is offline...
|
||||
decoration: BoxDecoration(
|
||||
image: Provider.of<ContactInfoState>(outerContext).isOnline()
|
||||
? null
|
||||
: DecorationImage(
|
||||
fit: BoxFit.contain,
|
||||
image: AssetImage("assets/core/negative_heart_512px.png"),
|
||||
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.mainTextColor(), BlendMode.srcIn))),
|
||||
child: ListView.builder(
|
||||
controller: ctrlr1,
|
||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||
reverse: true,
|
||||
itemBuilder: (itemBuilderContext, index) {
|
||||
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
||||
return ChangeNotifierProvider(
|
||||
key: ValueKey(trueIndex),
|
||||
create: (x) => MessageState(
|
||||
context: itemBuilderContext,
|
||||
profileOnion: Provider.of<ProfileInfoState>(outerContext, listen: false).onion,
|
||||
contactHandle: Provider.of<ContactInfoState>(x, listen: false).onion,
|
||||
messageIndex: trueIndex,
|
||||
),
|
||||
builder: (bcontext, child) {
|
||||
String idx = Provider.of<ContactInfoState>(outerContext).isGroup == true && Provider.of<MessageState>(bcontext).signature.isEmpty == false
|
||||
? Provider.of<MessageState>(bcontext).signature
|
||||
: trueIndex.toString();
|
||||
return RepaintBoundary(child: MessageRow(key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx)));
|
||||
});
|
||||
},
|
||||
),
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import 'messagebubble.dart';
|
|||
import 'messageloadingbubble.dart';
|
||||
|
||||
class MessageRow extends StatefulWidget {
|
||||
MessageRow({Key key}) : super(key: key);
|
||||
MessageRow({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_MessageRowState createState() => _MessageRowState();
|
||||
|
@ -52,10 +52,12 @@ class _MessageRowState extends State<MessageRow> {
|
|||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: ProfileImage(
|
||||
diameter: 48.0,
|
||||
imagePath: Provider.of<MessageState>(context).senderImage ?? contact.imagePath,
|
||||
//maskOut: contact.status != "Authenticated",
|
||||
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor())));
|
||||
diameter: 48.0,
|
||||
imagePath: Provider.of<MessageState>(context).senderImage ?? contact.imagePath,
|
||||
//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,
|
||||
)));
|
||||
|
||||
widgetRow = <Widget>[
|
||||
wdgPortrait,
|
||||
|
@ -76,7 +78,7 @@ class _MessageRowState extends State<MessageRow> {
|
|||
case 101:
|
||||
return InvitationBubble();
|
||||
}
|
||||
return null;
|
||||
return MalformedBubble();
|
||||
}
|
||||
|
||||
void _btnAdd() {
|
||||
|
@ -94,7 +96,7 @@ class _MessageRowState extends State<MessageRow> {
|
|||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).successfullAddedContact));
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,25 +5,49 @@ import '../settings.dart';
|
|||
// Provides a styled Password Input Field for use in Form Widgets.
|
||||
// Callers must provide a text controller, label helper text and a validator.
|
||||
class CwtchPasswordField extends StatefulWidget {
|
||||
CwtchPasswordField({this.controller, this.validator});
|
||||
CwtchPasswordField({required this.controller, required this.validator, this.action, this.autofocus = true});
|
||||
final TextEditingController controller;
|
||||
final FormFieldValidator validator;
|
||||
final Function(String)? action;
|
||||
final bool autofocus;
|
||||
|
||||
@override
|
||||
_CwtchTextFieldState createState() => _CwtchTextFieldState();
|
||||
}
|
||||
|
||||
class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
||||
bool obscureText = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// todo: translations
|
||||
var label = "View Password";
|
||||
if (!obscureText) {
|
||||
label = "Hide Password";
|
||||
}
|
||||
|
||||
return Consumer<Settings>(builder: (context, theme, child) {
|
||||
return TextFormField(
|
||||
autofocus: widget.autofocus,
|
||||
controller: widget.controller,
|
||||
validator: widget.validator,
|
||||
obscureText: true,
|
||||
obscureText: obscureText,
|
||||
onFieldSubmitted: widget.action,
|
||||
textInputAction: TextInputAction.unspecified,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
obscureText = !obscureText;
|
||||
},
|
||||
icon: Icon((obscureText ? Icons.remove_red_eye : Icons.remove_red_eye_outlined), semanticLabel: label),
|
||||
tooltip: label,
|
||||
color: theme.current().mainTextColor(),
|
||||
highlightColor: theme.current().defaultButtonColor(),
|
||||
focusColor: theme.current().defaultButtonActiveColor(),
|
||||
splashColor: theme.current().defaultButtonActiveColor(),
|
||||
),
|
||||
errorStyle: TextStyle(
|
||||
color: theme.current().textfieldErrorColor(),
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -35,7 +59,6 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
|||
fillColor: theme.current().textfieldBackgroundColor(),
|
||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)),
|
||||
),
|
||||
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:provider/provider.dart';
|
|||
import '../settings.dart';
|
||||
|
||||
class ProfileImage extends StatefulWidget {
|
||||
ProfileImage({this.imagePath, this.diameter, this.border, this.badgeCount = 0, this.badgeColor, 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});
|
||||
final double diameter;
|
||||
final String imagePath;
|
||||
final Color border;
|
||||
|
@ -21,7 +21,8 @@ class ProfileImage extends StatefulWidget {
|
|||
class _ProfileImageState extends State<ProfileImage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(children: [
|
||||
return RepaintBoundary(
|
||||
child: Stack(children: [
|
||||
ClipOval(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Container(
|
||||
|
@ -57,6 +58,6 @@ class _ProfileImageState extends State<ProfileImage> {
|
|||
child: Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0)),
|
||||
),
|
||||
)),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
)),
|
||||
IconButton(
|
||||
enableFeedback: true,
|
||||
tooltip: AppLocalizations.of(context).editProfile + " " + profile.nickname,
|
||||
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname,
|
||||
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||
onPressed: () {
|
||||
_pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath);
|
||||
|
@ -88,6 +88,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
void _pushContactList(ProfileInfoState profile, bool includeDoublePane) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
settings: RouteSettings(name: "conversations"),
|
||||
builder: (BuildContext buildcontext) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
|
|
|
@ -7,10 +7,10 @@ doNothing(String x) {}
|
|||
// Provides a styled Text Field for use in Form Widgets.
|
||||
// Callers must provide a text controller, label helper text and a validator.
|
||||
class CwtchTextField extends StatefulWidget {
|
||||
CwtchTextField({this.controller, this.labelText, this.validator, this.onChanged = doNothing});
|
||||
CwtchTextField({required this.controller, required this.labelText, this.validator = null, this.onChanged = doNothing});
|
||||
final TextEditingController controller;
|
||||
final String labelText;
|
||||
final FormFieldValidator validator;
|
||||
final FormFieldValidator? validator;
|
||||
final Function(String) onChanged;
|
||||
|
||||
@override
|
||||
|
@ -40,7 +40,6 @@ class _CwtchTextFieldState extends State<CwtchTextField> {
|
|||
fillColor: theme.current().textfieldBackgroundColor(),
|
||||
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0))),
|
||||
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ class TorIcon extends StatefulWidget {
|
|||
class _TorIconState extends State<TorIcon> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Image(
|
||||
return RepaintBoundary(
|
||||
child: Image(
|
||||
image: AssetImage(Provider.of<TorStatus>(context).progress == 0
|
||||
? "assets/core/Tor_OFF.png"
|
||||
: (Provider.of<TorStatus>(context).progress == 100 ? "assets/core/Tor_icon.png" : "assets/core/Tor_Booting_up.png")),
|
||||
|
@ -23,8 +24,8 @@ class _TorIconState extends State<TorIcon> {
|
|||
color: Provider.of<Settings>(context).theme.mainTextColor(),
|
||||
colorBlendMode: BlendMode.srcIn,
|
||||
semanticLabel: Provider.of<TorStatus>(context).progress == 100
|
||||
? AppLocalizations.of(context).networkStatusOnline
|
||||
: (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context).networkStatusDisconnected : AppLocalizations.of(context).networkStatusAttemptingTor),
|
||||
);
|
||||
? AppLocalizations.of(context)!.networkStatusOnline
|
||||
: (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class _TorStatusState extends State<TorStatusLabel> {
|
|||
stream: Provider.of<FlwtchState>(context).appStatus.torStatus(),
|
||||
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
|
||||
return Text(
|
||||
snapshot.hasData ? snapshot.data : AppLocalizations.of(context).loadingTor,
|
||||
snapshot.hasData ? snapshot.data! : AppLocalizations.of(context)!.loadingTor,
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
);
|
||||
},
|
||||
|
|
BIN
linux/cwtch.png
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 45 KiB |
|
@ -4,6 +4,10 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <window_size/window_size_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) window_size_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
|
||||
window_size_plugin_register_with_registrar(window_size_registrar);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
window_size
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
70
pubspec.lock
|
@ -8,6 +8,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -63,7 +70,21 @@ packages:
|
|||
name: cupertino_icons
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.0.3"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
desktop_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: desktop_notifications
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -84,7 +105,7 @@ packages:
|
|||
name: file
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
version: "6.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -128,7 +149,7 @@ packages:
|
|||
name: http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.1"
|
||||
version: "0.13.3"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -184,42 +205,42 @@ packages:
|
|||
name: package_info_plus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.1"
|
||||
package_info_plus_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.2"
|
||||
package_info_plus_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.1.1"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.1"
|
||||
package_info_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.1"
|
||||
package_info_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -261,7 +282,7 @@ packages:
|
|||
name: path_provider_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.0.1"
|
||||
pedantic:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -269,6 +290,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -296,7 +324,7 @@ packages:
|
|||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.3.2+3"
|
||||
version: "5.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -385,7 +413,16 @@ packages:
|
|||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
version: "2.1.1"
|
||||
window_size:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "plugins/window_size"
|
||||
ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
|
||||
resolved-ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
|
||||
url: "git://github.com/google/flutter-desktop-embedding.git"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -393,6 +430,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
sdks:
|
||||
dart: ">=2.12.0 <3.0.0"
|
||||
dart: ">=2.13.0 <3.0.0"
|
||||
flutter: ">=1.20.0"
|
||||
|
|
11
pubspec.yaml
|
@ -18,12 +18,12 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.7.0 <3.0.0"
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
provider: "4.3.2+3"
|
||||
provider: 5.0.0
|
||||
package_info_plus: ^1.0.0
|
||||
#intl_translation: any
|
||||
flutter_localizations:
|
||||
|
@ -34,6 +34,7 @@ dependencies:
|
|||
cupertino_icons: ^1.0.0
|
||||
ffi: ^1.0.0
|
||||
path_provider: ^2.0.0
|
||||
desktop_notifications: 0.5.0
|
||||
|
||||
glob: any
|
||||
# todo: flutter_driver causes version conflict. eg https://github.com/flutter/flutter/issues/44829
|
||||
|
@ -44,6 +45,12 @@ dependencies:
|
|||
flutter_driver:
|
||||
sdk: flutter
|
||||
|
||||
window_size:
|
||||
git:
|
||||
url: git://github.com/google/flutter-desktop-embedding.git
|
||||
path: plugins/window_size
|
||||
ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
|
||||
|
||||
#dev_dependencies:
|
||||
# flutter_lokalise: any
|
||||
|
||||
|
|
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 219 B |
|
@ -39,7 +39,7 @@ void main() {
|
|||
home: Card(child: CwtchButtonTextField(
|
||||
icon: Icon(Icons.bug_report_outlined),
|
||||
tooltip: testingStr,
|
||||
controller: ctrlr1,
|
||||
controller: ctrlr1, onPressed: () { },
|
||||
)),
|
||||
);}
|
||||
));
|
||||
|
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
@ -27,7 +27,7 @@ void main() {
|
|||
tester.binding.window.physicalSizeTestValue = Size(800, 300);
|
||||
final TextEditingController ctrlr1 = TextEditingController();
|
||||
|
||||
Widget testWidget = CwtchTextField(controller: ctrlr1);
|
||||
Widget testWidget = CwtchTextField(controller: ctrlr1, validator: (value) { }, labelText: '',);
|
||||
|
||||
Widget testHarness = MultiProvider(
|
||||
providers:[getSettingsEnglishDark()],
|
||||
|
@ -76,7 +76,7 @@ void main() {
|
|||
if (number == null) return strFail2;
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) => formKey.currentState.validate(),
|
||||
onChanged: (value) => formKey.currentState!.validate(),
|
||||
);
|
||||
|
||||
Widget testHarness = MultiProvider(
|
||||
|
@ -125,7 +125,7 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
// empty string
|
||||
formKey.currentState.validate(); //(ctrlr1.clear() doesn't trigger validate like keypress does)
|
||||
formKey.currentState!.validate(); //(ctrlr1.clear() doesn't trigger validate like keypress does)
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(find.byWidget(testHarness), matchesGoldenFile(file('form_final')));
|
||||
expect(find.text(strFail1), findsOneWidget);
|
||||
|
|