Merge branch 'trunk' of git.openprivacy.ca:flutter/flutter_app into integtests
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
erinn 2021-04-20 17:24:37 -07:00
commit 5a9621f562
36 changed files with 748 additions and 106 deletions

View File

@ -144,17 +144,37 @@ steps:
image: openpriv/flutter-desktop:windows-dev image: openpriv/flutter-desktop:windows-dev
commands: commands:
- git fetch --tags - git fetch --tags
- git describe --tags > VERSION - powershell -command "git describe --tags > VERSION"
- date +%G-%m-%d-%H-%M > BUILDDATE - powershell -command "Get-Date -Format 'yyyy-MM-dd-HH-mm' >.\BUILDDATE"
- name: build-windows - name: build-windows
image: openpriv/flutter-desktop:windows-dev image: openpriv/flutter-desktop:windows-dev
commands: commands:
- flutter pub get - flutter pub get
- mkdir deploy
- flutter build windows - flutter build windows
# flwtch-`cat VERSION`-`cat BUILDDATE`
- $Env:builddir = 'deploy\flwtch-win-'
- $Env:builddir += type .\VERSION
- $Env:builddir += '-'
- $Env:builddir += type .\BUILDDATE
- mkdir deploy
- move windows/runner/Release/ $Env:builddir
- name: deploy-windows
image: appleboy/drone-scp:v1.4.0-windows
when:
event: push
status: [ success ]
settings:
host: openprivacy.ca
username: buildfiles
key:
from_secret: buildfiles_key
port: 22
target: /home/buildfiles/buildfiles/
#/var/www/deploy/${DRONE_REPO_OWNER}/${DRONE_REPO_NAME}
source: deploy/*
trigger: trigger:
repo: flutter/flutter_app repo: flutter/flutter_app
branch: trunk branch: trunk
event: event:
- push - push

1
.gitignore vendored
View File

@ -44,3 +44,4 @@ libCwtch.so
android/cwtch/cwtch.aar android/cwtch/cwtch.aar
coverage coverage
test/failures test/failures
.gradle

View File

@ -157,6 +157,9 @@ class MainActivity: FlutterActivity() {
val jsonEvent = (call.argument("jsonEvent") as? String) ?: ""; val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
Cwtch.sendAppEvent(jsonEvent); Cwtch.sendAppEvent(jsonEvent);
} }
"ResetTor" -> {
Cwtch.resetTor();
}
else -> result.notImplemented() else -> result.notImplemented()
} }
} }

Binary file not shown.

BIN
assets/core/Tor_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

58
assets/core/Tor_icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -1,13 +1,70 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> <svg
<style type="text/css"> xmlns:dc="http://purl.org/dc/elements/1.1/"
.st0{fill:none;} xmlns:cc="http://creativecommons.org/ns#"
</style> xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
<path class="st0" d="M0,0h24v24H0V0z"/> xmlns:svg="http://www.w3.org/2000/svg"
<path id="Subtraction_1" d="M18.5,16L18.5,16L5.3,4.4C6.3,3.5,7.6,3,9,3c1.7,0,3.3,0.7,4.4,2c1.1-1.3,2.7-2,4.4-2 xmlns="http://www.w3.org/2000/svg"
C20.6,3,23,5.3,23,8.2c0,0,0,0.1,0,0.1c0,0.6-0.1,1.3-0.3,1.9c-0.2,0.7-0.5,1.3-0.9,1.9C21.1,13.2,20.1,14.4,18.5,16L18.5,16z"/> xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
<path d="M20.2,18.6L2.3,3.1L1,4.6l2.6,2.2C3.2,7.5,3,8.4,3,9.2c0,3.7,3.3,6.6,8.3,11.2l1.4,1.3l1.4-1.3c0.9-0.8,1.7-1.6,2.5-2.3 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
l2.3,2L20.2,18.6z"/> version="1.1"
</svg> id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 24 24"
style="enable-background:new 0 0 24 24;"
xml:space="preserve"
sodipodi:docname="negative_heart_24px.svg"
inkscape:export-filename="/home/sarah/AndroidStudioProjects/flutter_app/assets/core/negative_heart_512px.png"
inkscape:export-xdpi="4096"
inkscape:export-ydpi="4096"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata
id="metadata14"><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="defs12" /><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="1015"
id="namedview10"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="14.687942"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<style
type="text/css"
id="style2">
.st0{fill:none;}
</style>
<g
id="g824"
transform="translate(4.0677965,4.0677965)"><path
style="fill:none;stroke-width:0.66101694"
inkscape:connector-curvature="0"
id="path4"
d="M 0,0 H 15.864407 V 15.864407 H 0 Z"
class="st0" /><path
style="stroke-width:0.66101694"
inkscape:connector-curvature="0"
d="m 12.228814,10.576271 v 0 L 3.5033898,2.9084746 c 0.661017,-0.5949153 1.520339,-0.9254238 2.4457627,-0.9254238 1.1237289,0 2.181356,0.4627119 2.9084746,1.3220339 0.7271187,-0.859322 1.7847459,-1.3220339 2.9084749,-1.3220339 1.850847,0 3.437288,1.520339 3.437288,3.4372882 0,0 0,0.066102 0,0.066102 0,0.3966101 -0.0661,0.859322 -0.198305,1.2559322 -0.132204,0.4627118 -0.330509,0.859322 -0.594916,1.2559322 -0.462711,0.7271186 -1.123728,1.520339 -2.181355,2.5779656 z"
id="Subtraction_1" /><path
style="stroke-width:0.66101694"
inkscape:connector-curvature="0"
id="path7"
d="M 13.352542,12.294915 1.520339,2.0491525 0.66101695,3.040678 2.379661,4.4949153 C 2.1152542,4.9576271 1.9830508,5.5525424 1.9830508,6.0813559 c 0,2.4457627 2.181356,4.3627121 5.4864407,7.4033901 l 0.9254238,0.859322 0.9254237,-0.859322 c 0.5949152,-0.528814 1.123729,-1.057627 1.652542,-1.520339 l 1.520339,1.322034 z" /></g>
</svg>

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -9,6 +9,9 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void LoadProfiles(String pass); void LoadProfiles(String pass);
// ignore: non_constant_identifier_names
void ResetTor();
// todo: remove these // todo: remove these
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SendProfileEvent(String onion, String jsonEvent); void SendProfileEvent(String onion, String jsonEvent);

View File

@ -1,6 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:flutter_app/torstatus.dart';
import '../errorHandler.dart'; import '../errorHandler.dart';
import '../model.dart'; import '../model.dart';
import '../settings.dart'; import '../settings.dart';
@ -11,17 +13,20 @@ class CwtchNotifier {
ProfileListState profileCN; ProfileListState profileCN;
Settings settings; Settings settings;
ErrorHandler error; ErrorHandler error;
TorStatus torStatus;
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN) { CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN) {
profileCN = pcn; profileCN = pcn;
settings = settingsCN; settings = settingsCN;
error = errorCN; error = errorCN;
torStatus = torStatusCN;
} }
void handleMessage(String type, dynamic data) { void handleMessage(String type, dynamic data) {
switch (type) { switch (type) {
case "NewPeer": case "NewPeer":
profileCN.add(ProfileInfoState(onion: data["Identity"], nickname: data["name"], imagePath: data["picture"], contactsJson: data["ContactsJson"], online: data["Online"] == "true")); profileCN.add(ProfileInfoState(
onion: data["Identity"], nickname: data["name"], imagePath: data["picture"], contactsJson: data["ContactsJson"], serversJson: data["ServerList"], online: data["Online"] == "true"));
break; break;
case "PeerCreated": case "PeerCreated":
profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState( profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState(
@ -35,7 +40,8 @@ class CwtchNotifier {
savePeerHistory: data["saveConversationHistory"], savePeerHistory: data["saveConversationHistory"],
numMessages: int.parse(data["numMessages"]), numMessages: int.parse(data["numMessages"]),
numUnread: int.parse(data["unread"]), numUnread: int.parse(data["unread"]),
lastMessageTime: DateTime.now(),//show at the top of the contact list even if no messages yet isGroup: data["isGroup"],
lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet
)); ));
break; break;
case "PeerStateChange": case "PeerStateChange":
@ -75,6 +81,10 @@ class CwtchNotifier {
break; break;
case "ACNStatus": case "ACNStatus":
print("acn status: $data"); print("acn status: $data");
torStatus.handleUpdate(int.parse(data["Progress"]), data["Status"]);
break;
case "UpdateServerInfo":
profileCN.getProfile(data["ProfileOnion"]).replaceServers(data["ServerList"]);
break; break;
default: default:
print("unhandled event: $type"); print("unhandled event: $type");

View File

@ -297,4 +297,11 @@ class CwtchFfi implements Cwtch {
final u3 = message.toNativeUtf8(); final u3 = message.toNativeUtf8();
SendMessage(u1, u1.length, u2, u2.length, u3, u3.length); SendMessage(u1, u1.length, u2, u2.length, u3, u3.length);
} }
@override
void ResetTor() {
var resetTor = library.lookup<NativeFunction<Void Function()>>("c_ResetTor");
final ResetTor = resetTor.asFunction<void Function()>();
ResetTor();
}
} }

View File

@ -142,4 +142,10 @@ class CwtchGomobile implements Cwtch {
void SendMessage(String profileOnion, String contactHandle, String message) { void SendMessage(String profileOnion, String contactHandle, String message) {
cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "handle": contactHandle, "message": message}); cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "handle": contactHandle, "message": message});
} }
@override
// ignore: non_constant_identifier_names
void ResetTor() {
cwtchPlatform.invokeMethod("ResetTor", {});
}
} }

View File

@ -36,6 +36,17 @@
"cycleColoursDesktop": "", "cycleColoursDesktop": "",
"cycleMorphsAndroid": "", "cycleMorphsAndroid": "",
"cycleMorphsDesktop": "", "cycleMorphsDesktop": "",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "Tolle Gruppe", "defaultGroupName": "Tolle Gruppe",
"defaultProfileName": "Alice", "defaultProfileName": "Alice",
"defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:", "defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:",
@ -123,10 +134,12 @@
"settingLanguage": "Sprache", "settingLanguage": "Sprache",
"settingTheme": "Thema", "settingTheme": "Thema",
"smallTextLabel": "Klein", "smallTextLabel": "Klein",
"successfullAddedContact": "",
"themeDark": "Dunkel", "themeDark": "Dunkel",
"themeLight": "Licht", "themeLight": "Licht",
"titleManageContacts": "", "titleManageContacts": "",
"titleManageProfiles": "", "titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "Titel...", "titlePlaceholder": "Titel...",
"todoPlaceholder": "noch zu erledigen", "todoPlaceholder": "noch zu erledigen",
"tooltipAddContact": "", "tooltipAddContact": "",

View File

@ -36,6 +36,17 @@
"cycleColoursDesktop": "Click to cycle colours.\\nRight-click to reset.", "cycleColoursDesktop": "Click to cycle colours.\\nRight-click to reset.",
"cycleMorphsAndroid": "Click to cycle morphs.\\nLong-press to reset.", "cycleMorphsAndroid": "Click to cycle morphs.\\nLong-press to reset.",
"cycleMorphsDesktop": "Click to cycle morphs.\\nRight-click to reset.", "cycleMorphsDesktop": "Click to cycle morphs.\\nRight-click to reset.",
"dateDaysAgo": "Days Ago",
"dateHoursAgo": "Hours Ago",
"dateLastMonth": "Last Month",
"dateLastYear": "Last Year",
"dateMinutesAgo": "Minutes Ago",
"dateMonthsAgo": "Months Ago",
"dateNever": "Never",
"dateRightNow": "Right Now",
"dateWeeksAgo": "Weeks Ago",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateYesterday": "Yesterday",
"defaultGroupName": "Awesome Group", "defaultGroupName": "Awesome Group",
"defaultProfileName": "Alice", "defaultProfileName": "Alice",
"defaultScalingText": "Default size text (scale factor:", "defaultScalingText": "Default size text (scale factor:",
@ -123,10 +134,12 @@
"settingLanguage": "Language", "settingLanguage": "Language",
"settingTheme": "Theme", "settingTheme": "Theme",
"smallTextLabel": "Small", "smallTextLabel": "Small",
"successfullAddedContact": "Successfully added ",
"themeDark": "Dark", "themeDark": "Dark",
"themeLight": "Light", "themeLight": "Light",
"titleManageContacts": "Manage Contacts", "titleManageContacts": "Manage Contacts",
"titleManageProfiles": "Manage Cwtch Profiles", "titleManageProfiles": "Manage Cwtch Profiles",
"titleManageServers": "Manage Servers",
"titlePlaceholder": "title...", "titlePlaceholder": "title...",
"todoPlaceholder": "Todo...", "todoPlaceholder": "Todo...",
"tooltipAddContact": "Add a new contact", "tooltipAddContact": "Add a new contact",

View File

@ -36,6 +36,17 @@
"cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.", "cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.",
"cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.", "cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.",
"cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.", "cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "El Grupo Asombroso", "defaultGroupName": "El Grupo Asombroso",
"defaultProfileName": "Alicia", "defaultProfileName": "Alicia",
"defaultScalingText": "Tamaño predeterminado de texto (factor de escala:", "defaultScalingText": "Tamaño predeterminado de texto (factor de escala:",
@ -123,10 +134,12 @@
"settingLanguage": "Idioma", "settingLanguage": "Idioma",
"settingTheme": "Tema", "settingTheme": "Tema",
"smallTextLabel": "Pequeño", "smallTextLabel": "Pequeño",
"successfullAddedContact": "",
"themeDark": "Oscuro", "themeDark": "Oscuro",
"themeLight": "Claro", "themeLight": "Claro",
"titleManageContacts": "", "titleManageContacts": "",
"titleManageProfiles": "", "titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "título...", "titlePlaceholder": "título...",
"todoPlaceholder": "Por hacer...", "todoPlaceholder": "Por hacer...",
"tooltipAddContact": "", "tooltipAddContact": "",

View File

@ -36,6 +36,17 @@
"cycleColoursDesktop": "", "cycleColoursDesktop": "",
"cycleMorphsAndroid": "", "cycleMorphsAndroid": "",
"cycleMorphsDesktop": "", "cycleMorphsDesktop": "",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "Un super groupe", "defaultGroupName": "Un super groupe",
"defaultProfileName": "", "defaultProfileName": "",
"defaultScalingText": "Taille par défaut du texte (échelle:", "defaultScalingText": "Taille par défaut du texte (échelle:",
@ -123,10 +134,12 @@
"settingLanguage": "", "settingLanguage": "",
"settingTheme": "", "settingTheme": "",
"smallTextLabel": "Petit", "smallTextLabel": "Petit",
"successfullAddedContact": "",
"themeDark": "", "themeDark": "",
"themeLight": "", "themeLight": "",
"titleManageContacts": "", "titleManageContacts": "",
"titleManageProfiles": "", "titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "titre...", "titlePlaceholder": "titre...",
"todoPlaceholder": "A faire...", "todoPlaceholder": "A faire...",
"tooltipAddContact": "", "tooltipAddContact": "",

View File

@ -36,6 +36,17 @@
"cycleColoursDesktop": "Fare clic per scorrere i colori.\\nCliccare con il tasto destro per resettare.", "cycleColoursDesktop": "Fare clic per scorrere i colori.\\nCliccare con il tasto destro per resettare.",
"cycleMorphsAndroid": "Fare clic per scorrere i morph.\\nPressione lunga per resettare.", "cycleMorphsAndroid": "Fare clic per scorrere i morph.\\nPressione lunga per resettare.",
"cycleMorphsDesktop": "Fare clic per scorrere i morph.\\nCliccare con il tasto destro per resettare.", "cycleMorphsDesktop": "Fare clic per scorrere i morph.\\nCliccare con il tasto destro per resettare.",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "Gruppo fantastico", "defaultGroupName": "Gruppo fantastico",
"defaultProfileName": "Alice", "defaultProfileName": "Alice",
"defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:", "defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:",
@ -123,10 +134,12 @@
"settingLanguage": "Lingua", "settingLanguage": "Lingua",
"settingTheme": "Tema", "settingTheme": "Tema",
"smallTextLabel": "Piccolo", "smallTextLabel": "Piccolo",
"successfullAddedContact": "",
"themeDark": "Scuro", "themeDark": "Scuro",
"themeLight": "Chiaro", "themeLight": "Chiaro",
"titleManageContacts": "", "titleManageContacts": "",
"titleManageProfiles": "", "titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "titolo...", "titlePlaceholder": "titolo...",
"todoPlaceholder": "Da fare...", "todoPlaceholder": "Da fare...",
"tooltipAddContact": "", "tooltipAddContact": "",

View File

@ -36,6 +36,17 @@
"cycleColoursDesktop": "", "cycleColoursDesktop": "",
"cycleMorphsAndroid": "", "cycleMorphsAndroid": "",
"cycleMorphsDesktop": "", "cycleMorphsDesktop": "",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "Grupo incrível", "defaultGroupName": "Grupo incrível",
"defaultProfileName": "", "defaultProfileName": "",
"defaultScalingText": "Texto tamanho padrão (fator de escala: ", "defaultScalingText": "Texto tamanho padrão (fator de escala: ",
@ -123,10 +134,12 @@
"settingLanguage": "", "settingLanguage": "",
"settingTheme": "", "settingTheme": "",
"smallTextLabel": "Pequeno", "smallTextLabel": "Pequeno",
"successfullAddedContact": "",
"themeDark": "", "themeDark": "",
"themeLight": "", "themeLight": "",
"titleManageContacts": "", "titleManageContacts": "",
"titleManageProfiles": "", "titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "título…", "titlePlaceholder": "título…",
"todoPlaceholder": "Afazer…", "todoPlaceholder": "Afazer…",
"tooltipAddContact": "", "tooltipAddContact": "",

View File

@ -4,6 +4,7 @@ import 'package:flutter_app/cwtch/gomobile.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_app/errorHandler.dart'; import 'package:flutter_app/errorHandler.dart';
import 'package:flutter_app/settings.dart'; import 'package:flutter_app/settings.dart';
import 'package:flutter_app/torstatus.dart';
import 'package:flutter_app/views/triplecolview.dart'; import 'package:flutter_app/views/triplecolview.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'cwtch/cwtch.dart'; import 'cwtch/cwtch.dart';
@ -18,6 +19,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
var globalSettings = Settings(Locale("en", ''), Opaque.dark); var globalSettings = Settings(Locale("en", ''), Opaque.dark);
var globalErrorHandler = ErrorHandler(); var globalErrorHandler = ErrorHandler();
var globalTorStatus = TorStatus();
void main() { void main() {
LicenseRegistry.addLicense(() => licenses()); LicenseRegistry.addLicense(() => licenses());
@ -48,7 +50,7 @@ class FlwtchState extends State<Flwtch> {
cwtchInit = false; cwtchInit = false;
profs = ProfileListState(); profs = ProfileListState();
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler); var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus);
if (Platform.isAndroid) { if (Platform.isAndroid) {
cwtch = CwtchGomobile(cwtchNotifier); cwtch = CwtchGomobile(cwtchNotifier);
@ -65,6 +67,7 @@ class FlwtchState extends State<Flwtch> {
appStatus = AppModel(cwtch: cwtch); appStatus = AppModel(cwtch: cwtch);
} }
ChangeNotifierProvider<TorStatus> getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);
ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler); ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler);
ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings); ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings);
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this); Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
@ -75,7 +78,7 @@ class FlwtchState extends State<Flwtch> {
//appStatus = AppModel(cwtch: cwtch); //appStatus = AppModel(cwtch: cwtch);
return MultiProvider( return MultiProvider(
providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider()], providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider(), getTorStatusProvider()],
builder: (context, widget) { builder: (context, widget) {
Provider.of<Settings>(context).initPackageInfo(); Provider.of<Settings>(context).initPackageInfo();
return Consumer<Settings>( return Consumer<Settings>(

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_app/models/servers.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
@ -34,16 +35,6 @@ class ContactModel {
ContactModel({this.onion, this.nickname, this.status, this.isInvitation, this.isBlocked, this.imagePath}); ContactModel({this.onion, this.nickname, this.status, this.isInvitation, this.isBlocked, this.imagePath});
} }
//todo: delete
class DanMessageModel {
// ignore: non_constant_identifier_names
String Timestamp;
// ignore: non_constant_identifier_names
bool Acknowledged;
// ignore: non_constant_identifier_names
String Message;
}
class ChatMessage { class ChatMessage {
final int o; final int o;
final String d; final String d;
@ -123,6 +114,7 @@ class ContactListState extends ChangeNotifier {
class ProfileInfoState extends ChangeNotifier { class ProfileInfoState extends ChangeNotifier {
ContactListState _contacts = ContactListState(); ContactListState _contacts = ContactListState();
ServerListState _servers = ServerListState();
final String onion; final String onion;
String _nickname = ""; String _nickname = "";
String _imagePath = ""; String _imagePath = "";
@ -135,6 +127,7 @@ class ProfileInfoState extends ChangeNotifier {
imagePath = "", imagePath = "",
unreadMessages = 0, unreadMessages = 0,
contactsJson = "", contactsJson = "",
serversJson = "",
online = false, online = false,
}) { }) {
this._nickname = nickname; this._nickname = nickname;
@ -164,6 +157,20 @@ class ProfileInfoState extends ChangeNotifier {
this._contacts.updateLastMessageTime(this._contacts._contacts.first.onion, this._contacts._contacts.first.lastMessageTime); this._contacts.updateLastMessageTime(this._contacts._contacts.first.onion, this._contacts._contacts.first.lastMessageTime);
} }
} }
this.replaceServers(serversJson);
}
// Parse out the server list json into our server info state struct...
void replaceServers(String serversJson) {
if (serversJson != null && serversJson != "" && serversJson != "null") {
print("got servers $serversJson");
List<dynamic> servers = jsonDecode(serversJson);
this._servers.replace(servers.map((server) {
// TODO Keys...
return ServerInfoState(onion: server["onion"], status: server["status"]);
}));
}
} }
// Getters and Setters for Online Status // Getters and Setters for Online Status
@ -192,6 +199,7 @@ class ProfileInfoState extends ChangeNotifier {
} }
ContactListState get contactList => this._contacts; ContactListState get contactList => this._contacts;
ServerListState get serverList => this._servers;
@override @override
void dispose() { void dispose() {

26
lib/models/servers.dart Normal file
View File

@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
class ServerListState extends ChangeNotifier {
List<ServerInfoState> _servers = [];
void replace(Iterable<ServerInfoState> newServers) {
_servers.clear();
_servers.addAll(newServers);
notifyListeners();
}
ServerInfoState getServer(String onion) {
int idx = _servers.indexWhere((element) => element.onion == onion);
return idx >= 0 ? _servers[idx] : null;
}
List<ServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
}
class ServerInfoState extends ChangeNotifier {
final String onion;
final String status;
ServerInfoState({this.onion, this.status});
}

21
lib/torstatus.dart Normal file
View File

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class TorStatus extends ChangeNotifier {
int progress;
String status;
bool connected;
/// Called by the event bus.
handleUpdate(int new_progress, String new_status) {
if (progress == 100) {
connected = true;
} else {
connected = false;
}
progress = new_progress;
status = new_status;
notifyListeners();
}
}

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_app/errorHandler.dart'; import 'package:flutter_app/errorHandler.dart';
import 'package:flutter_app/models/servers.dart';
import 'package:flutter_app/settings.dart'; import 'package:flutter_app/settings.dart';
import 'package:flutter_app/widgets/buttontextfield.dart'; import 'package:flutter_app/widgets/buttontextfield.dart';
import 'package:flutter_app/widgets/cwtchlabel.dart'; import 'package:flutter_app/widgets/cwtchlabel.dart';
@ -24,8 +25,11 @@ class AddContactView extends StatefulWidget {
class _AddContactViewState extends State<AddContactView> { class _AddContactViewState extends State<AddContactView> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _createGroupFormKey = GlobalKey<FormState>();
final ctrlrOnion = TextEditingController(text: ""); final ctrlrOnion = TextEditingController(text: "");
final ctrlrContact = TextEditingController(text: ""); final ctrlrContact = TextEditingController(text: "");
final ctrlrGroupName = TextEditingController(text: "");
String server = "";
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -49,7 +53,7 @@ class _AddContactViewState extends State<AddContactView> {
(groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()), (groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()),
Expanded( Expanded(
child: TabBarView( child: TabBarView(
children: (groupsEnabled ? [addPeerTab(), addGroupTab(), joinGroupTab()] : [addPeerTab()]), children: (groupsEnabled ? [addPeerTab(), manageServersTab(), addGroupTab(), joinGroupTab()] : [addPeerTab()]),
)), )),
])); ]));
}); });
@ -57,7 +61,8 @@ class _AddContactViewState extends State<AddContactView> {
void _copyOnion() { void _copyOnion() {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion)); Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
// TODO Toast final snackBar = SnackBar(content: Text(AppLocalizations.of(context).copiedClipboardNotification));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
/// A Tab Bar with only the Add Peer Tab /// A Tab Bar with only the Add Peer Tab
@ -80,6 +85,7 @@ class _AddContactViewState extends State<AddContactView> {
icon: Icon(Icons.person_add_rounded), icon: Icon(Icons.person_add_rounded),
text: AppLocalizations.of(context).addPeer, text: AppLocalizations.of(context).addPeer,
), ),
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), text: AppLocalizations.of(context).createGroup),
Tab(icon: Icon(Icons.group_add), text: AppLocalizations.of(context).joinGroup), Tab(icon: Icon(Icons.group_add), text: AppLocalizations.of(context).joinGroup),
], ],
@ -137,6 +143,8 @@ class _AddContactViewState extends State<AddContactView> {
Future.delayed(const Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
if (globalErrorHandler.explicitAddContactSuccess) { if (globalErrorHandler.explicitAddContactSuccess) {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).successfullAddedContact + peerAddr));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.pop(context); Navigator.pop(context);
} }
}); });
@ -147,11 +155,78 @@ class _AddContactViewState extends State<AddContactView> {
/// TODO Add Group Pane /// TODO Add Group Pane
Widget addGroupTab() { Widget addGroupTab() {
return Icon(Icons.group_add); // TODO We should replace with with a "Paste in Server Key Bundle"
if (Provider.of<ProfileInfoState>(context).serverList.servers.isEmpty) {
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),
child: Form(
autovalidateMode: AutovalidateMode.always,
key: _createGroupFormKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CwtchLabel(label: AppLocalizations.of(context).server),
SizedBox(
height: 20,
),
DropdownButton(
onChanged: (newServer) {
server = newServer;
},
value: server,
items: Provider.of<ProfileInfoState>(context).serverList.servers.map<DropdownMenuItem<String>>((ServerInfoState serverInfo) {
return DropdownMenuItem<String>(
value: serverInfo.onion,
child: Text(serverInfo.onion),
);
}).toList()),
SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context).groupName),
SizedBox(
height: 20,
),
CwtchTextField(controller: ctrlrGroupName, labelText: AppLocalizations.of(context).groupNameLabel, onChanged: (newValue) {}),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {},
child: Text(AppLocalizations.of(context).createGroupBtn),
),
],
)));
} }
/// TODO Join Group Pane /// TODO Join Group Pane
Widget joinGroupTab() { Widget joinGroupTab() {
return Icon(Icons.group); return Icon(Icons.group);
} }
/// TODO Manage Servers Tab
Widget manageServersTab() {
final tiles = Provider.of<ProfileInfoState>(context).serverList.servers.map((ServerInfoState server) {
return ChangeNotifierProvider<ServerInfoState>.value(
value: server,
child: ListTile(
title: Text(server.onion),
));
});
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return ListView(children: divided);
}
} }

View File

@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../main.dart'; import '../main.dart';
import '../model.dart'; import '../model.dart';
import '../opaque.dart'; import '../opaque.dart';
import '../settings.dart';
import '../widgets/messagelist.dart'; import '../widgets/messagelist.dart';
class MessageView extends StatefulWidget { class MessageView extends StatefulWidget {
@ -87,7 +88,7 @@ class _MessageViewState extends State<MessageView> {
Widget _buildComposeBox() { Widget _buildComposeBox() {
return Container( return Container(
color: Opaque.current().backgroundMainColor(), color: Provider.of<Settings>(context).theme.backgroundMainColor(),
height: 100, height: 100,
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: Row( child: Row(
@ -107,10 +108,10 @@ class _MessageViewState extends State<MessageView> {
Padding( Padding(
padding: EdgeInsets.fromLTRB(2, 2, 2, 2), padding: EdgeInsets.fromLTRB(2, 2, 2, 2),
child: ElevatedButton( child: ElevatedButton(
child: Icon(Icons.send, color: Opaque.current().mainTextColor()), child: Icon(Icons.send, color: Provider.of<Settings>(context).theme.mainTextColor()),
style: ButtonStyle( style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(86, 40)), fixedSize: MaterialStateProperty.all(Size(86, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()), backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
), ),
onPressed: _sendMessage, onPressed: _sendMessage,
)), )),
@ -120,10 +121,10 @@ class _MessageViewState extends State<MessageView> {
child: SizedBox( child: SizedBox(
width: 41, width: 41,
child: ElevatedButton( child: ElevatedButton(
child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()), child: Icon(Icons.emoji_emotions_outlined, color: Provider.of<Settings>(context).theme.mainTextColor()),
style: ButtonStyle( style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(41, 40)), fixedSize: MaterialStateProperty.all(Size(41, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()), backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
), ),
onPressed: placeHolder, onPressed: placeHolder,
))), ))),
@ -132,10 +133,10 @@ class _MessageViewState extends State<MessageView> {
child: SizedBox( child: SizedBox(
width: 41, width: 41,
child: ElevatedButton( child: ElevatedButton(
child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()), child: Icon(Icons.attach_file, color: Provider.of<Settings>(context).theme.mainTextColor()),
style: ButtonStyle( style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(41, 40)), fixedSize: MaterialStateProperty.all(Size(41, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()), backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
), ),
onPressed: placeHolder, onPressed: placeHolder,
))), ))),

View File

@ -2,6 +2,8 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_app/settings.dart'; import 'package:flutter_app/settings.dart';
import 'package:flutter_app/torstatus.dart';
import 'package:flutter_app/views/torstatusview.dart';
import 'package:flutter_app/widgets/passwordfield.dart'; import 'package:flutter_app/widgets/passwordfield.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_app/widgets/profilerow.dart'; import 'package:flutter_app/widgets/profilerow.dart';
@ -34,6 +36,15 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context).titleManageProfiles), title: Text(AppLocalizations.of(context).titleManageProfiles),
actions: [ actions: [
IconButton(
icon: Image(
image: AssetImage(Provider.of<TorStatus>(context).progress == 100 ? "assets/core/Tor_icon.png" : "assets/core/Tor_icon_error.png"),
filterQuality: FilterQuality.low,
isAntiAlias: false,
color: Provider.of<Settings>(context).theme.mainTextColor(),
colorBlendMode: BlendMode.srcIn,
),
onPressed: _pushTorStatus),
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug), IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug),
IconButton( IconButton(
icon: Icon(Icons.lock_open), icon: Icon(Icons.lock_open),
@ -75,6 +86,17 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
)); ));
} }
void _pushTorStatus() {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return MultiProvider(
providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
child: TorStatusView(),
);
},
));
}
void _pushAddEditProfile({onion: ""}) { void _pushAddEditProfile({onion: ""}) {
Navigator.of(context).push(MaterialPageRoute<void>( Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) { builder: (BuildContext context) {

View File

@ -0,0 +1,68 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_app/settings.dart';
import 'package:flutter_app/torstatus.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../main.dart';
/// Tor Status View provides all info on Tor network state and the (future) ability to configure the network in a variety
/// of ways (restart, enable bridges, enable pluggable transports etc)
class TorStatusView extends StatefulWidget {
@override
_TorStatusView createState() => _TorStatusView();
}
class _TorStatusView extends State<TorStatusView> {
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Tor Network Status"),
),
body: _buildSettingsList(),
);
}
Widget _buildSettingsList() {
return Consumer<TorStatus>(builder: (context, torStatus, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar(
isAlwaysShown: true,
child: SingleChildScrollView(
clipBehavior: Clip.antiAlias,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Column(children: [
ListTile(
leading: Image(
image: AssetImage(torStatus.progress == 100 ? "assets/core/Tor_icon.png" : "assets/core/Tor_icon_error.png"),
filterQuality: FilterQuality.low,
isAntiAlias: false,
// Color the onion per the text color...
color: Provider.of<Settings>(context).theme.mainTextColor(),
colorBlendMode: BlendMode.srcIn,
),
title: Text("Tor Status"),
subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context).networkStatusOnline : torStatus.status),
trailing: ElevatedButton(
child: Text("Reset"),
onPressed: () {
Provider.of<FlwtchState>(context, listen: false).cwtch.ResetTor();
},
),
)
]))));
});
});
}
}

View File

@ -1,12 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter_app/views/messageview.dart'; import 'package:flutter_app/views/messageview.dart';
import 'package:flutter_app/widgets/profileimage.dart'; import 'package:flutter_app/widgets/profileimage.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../main.dart'; import '../main.dart';
import '../model.dart'; import '../model.dart';
import '../opaque.dart';
import '../settings.dart'; import '../settings.dart';
import 'package:intl/intl.dart';
class ContactRow extends StatefulWidget { class ContactRow extends StatefulWidget {
@override @override
@ -29,21 +30,24 @@ class _ContactRowState extends State<ContactRow> {
badgeTextColor: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor(), badgeTextColor: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor(),
diameter: 64.0, diameter: 64.0,
imagePath: contact.imagePath, imagePath: contact.imagePath,
maskOut: contact.status != "Authenticated", maskOut: contact.isGroup ? false : contact.status != "Authenticated",
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()), border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
), ),
Expanded( Expanded(
child: Column( child: Padding(
children: [ padding: EdgeInsets.all(10.0),
Text( child: Column(
contact.nickname, //(contact.isInvitation ? "invite " : "non-invite ") + (contact.isBlocked ? "blokt" : "nonblokt"),// crossAxisAlignment: CrossAxisAlignment.start,
style: Provider.of<FlwtchState>(context).biggerFont, children: [
softWrap: true, Text(
overflow: TextOverflow.visible, contact.nickname, //(contact.isInvitation ? "invite " : "non-invite ") + (contact.isBlocked ? "blokt" : "nonblokt"),//
), style: Provider.of<FlwtchState>(context).biggerFont,
Text(contact.status), softWrap: true,
], overflow: TextOverflow.visible,
)), ),
Text(contact.onion),
],
))),
Padding( Padding(
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: contact.isInvitation != null && contact.isInvitation child: contact.isInvitation != null && contact.isInvitation
@ -51,13 +55,13 @@ class _ContactRowState extends State<ContactRow> {
IconButton( IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
iconSize: 16, iconSize: 16,
icon: Icon(Icons.favorite, color: Opaque.current().mainTextColor()), icon: Icon(Icons.favorite, color: Provider.of<Settings>(context).theme.mainTextColor()),
onPressed: _btnApprove, onPressed: _btnApprove,
), ),
IconButton( IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
iconSize: 16, iconSize: 16,
icon: Icon(Icons.delete, color: Opaque.current().mainTextColor()), icon: Icon(Icons.delete, color: Provider.of<Settings>(context).theme.mainTextColor()),
onPressed: _btnReject, onPressed: _btnReject,
) )
]) ])
@ -65,10 +69,10 @@ class _ContactRowState extends State<ContactRow> {
? IconButton( ? IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
iconSize: 16, iconSize: 16,
icon: Icon(Icons.block, color: Opaque.current().mainTextColor()), icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor()),
onPressed: () {}, onPressed: () {},
) )
: Text(contact.unreadMessages.toString())), : Text(dateToNiceString(contact.lastMessageTime))),
), ),
]), ]),
onTap: () { onTap: () {
@ -109,4 +113,11 @@ class _ContactRowState extends State<ContactRow> {
.cwtch .cwtch
.BlockContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion); .BlockContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion);
} }
String dateToNiceString(DateTime date) {
if (date.millisecondsSinceEpoch == 0) {
return AppLocalizations.of(context).dateNever;
}
return DateFormat.yMd().add_jm().format(date.toLocal());
}
} }

View File

@ -1,9 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../model.dart'; import '../model.dart';
import '../opaque.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../settings.dart';
class MessageBubble extends StatefulWidget { class MessageBubble extends StatefulWidget {
@override @override
_MessageBubbleState createState() => _MessageBubbleState(); _MessageBubbleState createState() => _MessageBubbleState();
@ -21,28 +22,40 @@ class _MessageBubbleState extends State<MessageBubble> {
prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp); prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp);
} }
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: fromMe ? Opaque.current().messageFromMeBackgroundColor() : Opaque.current().messageFromOtherBackgroundColor(), color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
border: Border.all(color: fromMe ? Opaque.current().messageFromMeBackgroundColor() : Opaque.current().messageFromOtherBackgroundColor(), width: 1), border: Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(), width: 1),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(borderRadiousEh), topLeft: Radius.circular(borderRadiousEh),
topRight: Radius.circular(borderRadiousEh), topRight: Radius.circular(borderRadiousEh),
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
),
), ),
child: Padding(padding: EdgeInsets.all(9.0), child:Column(crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [SelectableText( ),
Provider.of<MessageState>(context).message, child: Padding(
textAlign: TextAlign.left, padding: EdgeInsets.all(9.0),
), child: Column(crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [
Row( SelectableText(
children: [ Provider.of<MessageState>(context).message,
Text(prettyDate, style: TextStyle(fontSize: 9.0), textAlign: fromMe ? TextAlign.right : TextAlign.left), style: TextStyle(
Provider.of<MessageState>(context).ackd color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
? Icon(Icons.check_circle_outline, color: Opaque.current().mainTextColor(), size: 12) ),
: Icon(Icons.hourglass_bottom_outlined, color: Opaque.current().mainTextColor(), size: 12) textAlign: TextAlign.left,
], ),
)])), Row(
children: [
Text(prettyDate,
style: TextStyle(
fontSize: 9.0,
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
),
textAlign: fromMe ? TextAlign.right : TextAlign.left),
Provider.of<MessageState>(context).ackd
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
],
)
])),
); );
} }
} }

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../model.dart'; import '../model.dart';
import 'messagebubble.dart'; import '../settings.dart';
import 'messagerow.dart'; import 'messagerow.dart';
class MessageList extends StatefulWidget { class MessageList extends StatefulWidget {
@ -43,22 +43,31 @@ class _MessageListState extends State<MessageList> {
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom(false)); WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom(false));
return Card( return Card(
child: Scrollbar( child: Scrollbar(
isAlwaysShown: true, isAlwaysShown: true,
controller: ctrlr1, controller: ctrlr1,
child: ListView.builder( child: Container(
controller: ctrlr1, // Only show broken heart is the contact is offline...
itemCount: Provider.of<ContactInfoState>(context).totalMessages, decoration: Provider.of<ContactInfoState>(outerContext).status == "Authenticated"
itemBuilder: (context, index) { ? null
return ChangeNotifierProvider( : BoxDecoration(
create: (_) => MessageState( image: DecorationImage(
context: context, fit: BoxFit.contain,
profileOnion: Provider.of<ProfileInfoState>(outerContext).onion, image: AssetImage("assets/core/negative_heart_512px.png"),
contactHandle: Provider.of<ContactInfoState>(outerContext).onion, colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.mainTextColor(), BlendMode.srcIn))),
messageIndex: index, child: ListView.builder(
), controller: ctrlr1,
child: MessageRow()); itemCount: Provider.of<ContactInfoState>(context).totalMessages,
}, itemBuilder: (context, index) {
), return ChangeNotifierProvider(
)); create: (_) => MessageState(
context: context,
profileOnion: Provider.of<ProfileInfoState>(outerContext).onion,
contactHandle: Provider.of<ContactInfoState>(outerContext).onion,
messageIndex: index,
),
child: MessageRow());
},
),
)));
} }
} }

View File

@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
import '../main.dart'; import '../main.dart';
import '../model.dart'; import '../model.dart';
import '../opaque.dart';
import '../settings.dart'; import '../settings.dart';
import 'messagebubble.dart'; import 'messagebubble.dart';
@ -20,8 +19,8 @@ class _MessageRowState extends State<MessageRow> {
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion; var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
Widget wdgBubble = MessageBubble(); Widget wdgBubble = MessageBubble();
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Opaque.current().dropShadowColor()); Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Provider.of<Settings>(context).theme.dropShadowColor());
Widget wdgSpacer = Expanded(child:SizedBox(width: 60, height: 10)); Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
var widgetRow = <Widget>[]; var widgetRow = <Widget>[];
if (fromMe) { if (fromMe) {
@ -36,8 +35,7 @@ class _MessageRowState extends State<MessageRow> {
diameter: 48.0, diameter: 48.0,
imagePath: contact.imagePath, imagePath: contact.imagePath,
maskOut: contact.status != "Authenticated", maskOut: contact.status != "Authenticated",
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor() border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor());
);
widgetRow = <Widget>[ widgetRow = <Widget>[
wdgPortrait, wdgPortrait,

View File

@ -142,7 +142,7 @@ packages:
name: integration_test name: integration_test
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.2+2" version: "1.0.2+3"
intl: intl:
dependency: transitive dependency: transitive
description: description:

View File

@ -84,7 +84,9 @@ flutter:
assets: assets:
- assets/ - assets/
- assets/core/
- assets/profiles/ - assets/profiles/
- assets/servers/
# To add custom fonts to your application, add a fonts section here, # To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a # in this "flutter" section. Each entry in this list should have a

BIN
test/profileimage_init.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -0,0 +1,72 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_app/opaque.dart';
import 'package:flutter_app/settings.dart';
import 'package:flutter_app/widgets/cwtchlabel.dart';
import 'package:flutter_app/widgets/profileimage.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
var settingsEnglishDark = Settings(Locale("en", ''), Opaque.dark);
var settingsEnglishLight = Settings(Locale("en", ''), Opaque.light);
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
String file(String slug) {
return "profileimage_" + slug + ".png";
}
void main() {
testWidgets('ProfileImage widget test', (WidgetTester tester) async {
tester.binding.window.physicalSizeTestValue = Size(200, 200);
// await tester.pumpWidget(MultiProvider(
// providers:[getSettingsEnglishDark()],
// child: Directionality(textDirection: TextDirection.ltr, child: CwtchLabel(label: testingStr))
// ));
Widget testWidget = ProfileImage(
imagePath: "profiles/001-centaur.png",
badgeTextColor: settingsEnglishDark.theme.portraitProfileBadgeTextColor(),
badgeColor: settingsEnglishDark.theme.portraitProfileBadgeColor(),
maskOut: false,
border: settingsEnglishDark.theme.portraitOfflineBorderColor(),
diameter: 64.0,
badgeCount: 10,
);
Widget testHarness = MultiProvider(
providers:[getSettingsEnglishDark()],
builder: (context, child) { return MaterialApp(
locale: Provider.of<Settings>(context).locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
title: 'Test',
theme: mkThemeData(Provider.of<Settings>(context)),
home: Card(child: testWidget),
);}
);
// Verify that our counter starts at 0.
//expect(find.text(testingStr), findsOneWidget);
//expect(find.text('1'), findsNothing);
await tester.pumpWidget(testHarness);
await expectLater(find.byWidget(testHarness), matchesGoldenFile(file('init')));
// Tap the '+' icon and trigger a frame.
// await tester.tap(find.byIcon(Icons.add));
// await tester.pump();
//
// // Verify that our counter has incremented.
// expect(find.text('0'), findsNothing);
// expect(find.text('1'), findsOneWidget);
});
}