Merge branch 'trunk' of git.openprivacy.ca:flutter/flutter_app into integtests
continuous-integration/drone/pr Build is passing
Details
28
.drone.yml
|
@ -144,17 +144,37 @@ steps:
|
|||
image: openpriv/flutter-desktop:windows-dev
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- git describe --tags > VERSION
|
||||
- date +%G-%m-%d-%H-%M > BUILDDATE
|
||||
- powershell -command "git describe --tags > VERSION"
|
||||
- powershell -command "Get-Date -Format 'yyyy-MM-dd-HH-mm' >.\BUILDDATE"
|
||||
- name: build-windows
|
||||
image: openpriv/flutter-desktop:windows-dev
|
||||
commands:
|
||||
- flutter pub get
|
||||
- mkdir deploy
|
||||
- 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:
|
||||
repo: flutter/flutter_app
|
||||
branch: trunk
|
||||
event:
|
||||
- push
|
||||
- push
|
||||
|
|
|
@ -44,3 +44,4 @@ libCwtch.so
|
|||
android/cwtch/cwtch.aar
|
||||
coverage
|
||||
test/failures
|
||||
.gradle
|
|
@ -157,6 +157,9 @@ class MainActivity: FlutterActivity() {
|
|||
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
|
||||
Cwtch.sendAppEvent(jsonEvent);
|
||||
}
|
||||
"ResetTor" -> {
|
||||
Cwtch.resetTor();
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 575 B |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 622 B |
After Width: | Height: | Size: 7.0 KiB |
|
@ -1,13 +1,70 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;}
|
||||
</style>
|
||||
<path class="st0" d="M0,0h24v24H0V0z"/>
|
||||
<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
|
||||
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"/>
|
||||
<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
|
||||
l2.3,2L20.2,18.6z"/>
|
||||
</svg>
|
||||
<?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) -->
|
||||
|
||||
<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 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 |
After Width: | Height: | Size: 22 KiB |
|
@ -9,6 +9,9 @@ abstract class Cwtch {
|
|||
// ignore: non_constant_identifier_names
|
||||
void LoadProfiles(String pass);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor();
|
||||
|
||||
// todo: remove these
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendProfileEvent(String onion, String jsonEvent);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter_app/torstatus.dart';
|
||||
|
||||
import '../errorHandler.dart';
|
||||
import '../model.dart';
|
||||
import '../settings.dart';
|
||||
|
@ -11,17 +13,20 @@ class CwtchNotifier {
|
|||
ProfileListState profileCN;
|
||||
Settings settings;
|
||||
ErrorHandler error;
|
||||
TorStatus torStatus;
|
||||
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN) {
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN) {
|
||||
profileCN = pcn;
|
||||
settings = settingsCN;
|
||||
error = errorCN;
|
||||
torStatus = torStatusCN;
|
||||
}
|
||||
|
||||
void handleMessage(String type, dynamic data) {
|
||||
switch (type) {
|
||||
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;
|
||||
case "PeerCreated":
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState(
|
||||
|
@ -35,7 +40,8 @@ class CwtchNotifier {
|
|||
savePeerHistory: data["saveConversationHistory"],
|
||||
numMessages: int.parse(data["numMessages"]),
|
||||
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;
|
||||
case "PeerStateChange":
|
||||
|
@ -75,6 +81,10 @@ class CwtchNotifier {
|
|||
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"]);
|
||||
break;
|
||||
default:
|
||||
print("unhandled event: $type");
|
||||
|
|
|
@ -297,4 +297,11 @@ class CwtchFfi implements Cwtch {
|
|||
final u3 = message.toNativeUtf8();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,4 +142,10 @@ class CwtchGomobile implements Cwtch {
|
|||
void SendMessage(String profileOnion, String contactHandle, String message) {
|
||||
cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "handle": contactHandle, "message": message});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor() {
|
||||
cwtchPlatform.invokeMethod("ResetTor", {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,17 @@
|
|||
"cycleColoursDesktop": "",
|
||||
"cycleMorphsAndroid": "",
|
||||
"cycleMorphsDesktop": "",
|
||||
"dateDaysAgo": "",
|
||||
"dateHoursAgo": "",
|
||||
"dateLastMonth": "",
|
||||
"dateLastYear": "",
|
||||
"dateMinutesAgo": "",
|
||||
"dateMonthsAgo": "",
|
||||
"dateNever": "",
|
||||
"dateRightNow": "",
|
||||
"dateWeeksAgo": "",
|
||||
"dateYearsAgo": "",
|
||||
"dateYesterday": "",
|
||||
"defaultGroupName": "Tolle Gruppe",
|
||||
"defaultProfileName": "Alice",
|
||||
"defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:",
|
||||
|
@ -123,10 +134,12 @@
|
|||
"settingLanguage": "Sprache",
|
||||
"settingTheme": "Thema",
|
||||
"smallTextLabel": "Klein",
|
||||
"successfullAddedContact": "",
|
||||
"themeDark": "Dunkel",
|
||||
"themeLight": "Licht",
|
||||
"titleManageContacts": "",
|
||||
"titleManageProfiles": "",
|
||||
"titleManageServers": "",
|
||||
"titlePlaceholder": "Titel...",
|
||||
"todoPlaceholder": "noch zu erledigen",
|
||||
"tooltipAddContact": "",
|
||||
|
|
|
@ -36,6 +36,17 @@
|
|||
"cycleColoursDesktop": "Click to cycle colours.\\nRight-click to reset.",
|
||||
"cycleMorphsAndroid": "Click to cycle morphs.\\nLong-press 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",
|
||||
"defaultProfileName": "Alice",
|
||||
"defaultScalingText": "Default size text (scale factor:",
|
||||
|
@ -123,10 +134,12 @@
|
|||
"settingLanguage": "Language",
|
||||
"settingTheme": "Theme",
|
||||
"smallTextLabel": "Small",
|
||||
"successfullAddedContact": "Successfully added ",
|
||||
"themeDark": "Dark",
|
||||
"themeLight": "Light",
|
||||
"titleManageContacts": "Manage Contacts",
|
||||
"titleManageProfiles": "Manage Cwtch Profiles",
|
||||
"titleManageServers": "Manage Servers",
|
||||
"titlePlaceholder": "title...",
|
||||
"todoPlaceholder": "Todo...",
|
||||
"tooltipAddContact": "Add a new contact",
|
||||
|
|
|
@ -36,6 +36,17 @@
|
|||
"cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.",
|
||||
"cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado 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",
|
||||
"defaultProfileName": "Alicia",
|
||||
"defaultScalingText": "Tamaño predeterminado de texto (factor de escala:",
|
||||
|
@ -123,10 +134,12 @@
|
|||
"settingLanguage": "Idioma",
|
||||
"settingTheme": "Tema",
|
||||
"smallTextLabel": "Pequeño",
|
||||
"successfullAddedContact": "",
|
||||
"themeDark": "Oscuro",
|
||||
"themeLight": "Claro",
|
||||
"titleManageContacts": "",
|
||||
"titleManageProfiles": "",
|
||||
"titleManageServers": "",
|
||||
"titlePlaceholder": "título...",
|
||||
"todoPlaceholder": "Por hacer...",
|
||||
"tooltipAddContact": "",
|
||||
|
|
|
@ -36,6 +36,17 @@
|
|||
"cycleColoursDesktop": "",
|
||||
"cycleMorphsAndroid": "",
|
||||
"cycleMorphsDesktop": "",
|
||||
"dateDaysAgo": "",
|
||||
"dateHoursAgo": "",
|
||||
"dateLastMonth": "",
|
||||
"dateLastYear": "",
|
||||
"dateMinutesAgo": "",
|
||||
"dateMonthsAgo": "",
|
||||
"dateNever": "",
|
||||
"dateRightNow": "",
|
||||
"dateWeeksAgo": "",
|
||||
"dateYearsAgo": "",
|
||||
"dateYesterday": "",
|
||||
"defaultGroupName": "Un super groupe",
|
||||
"defaultProfileName": "",
|
||||
"defaultScalingText": "Taille par défaut du texte (échelle:",
|
||||
|
@ -123,10 +134,12 @@
|
|||
"settingLanguage": "",
|
||||
"settingTheme": "",
|
||||
"smallTextLabel": "Petit",
|
||||
"successfullAddedContact": "",
|
||||
"themeDark": "",
|
||||
"themeLight": "",
|
||||
"titleManageContacts": "",
|
||||
"titleManageProfiles": "",
|
||||
"titleManageServers": "",
|
||||
"titlePlaceholder": "titre...",
|
||||
"todoPlaceholder": "A faire...",
|
||||
"tooltipAddContact": "",
|
||||
|
|
|
@ -36,6 +36,17 @@
|
|||
"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.",
|
||||
"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",
|
||||
"defaultProfileName": "Alice",
|
||||
"defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:",
|
||||
|
@ -123,10 +134,12 @@
|
|||
"settingLanguage": "Lingua",
|
||||
"settingTheme": "Tema",
|
||||
"smallTextLabel": "Piccolo",
|
||||
"successfullAddedContact": "",
|
||||
"themeDark": "Scuro",
|
||||
"themeLight": "Chiaro",
|
||||
"titleManageContacts": "",
|
||||
"titleManageProfiles": "",
|
||||
"titleManageServers": "",
|
||||
"titlePlaceholder": "titolo...",
|
||||
"todoPlaceholder": "Da fare...",
|
||||
"tooltipAddContact": "",
|
||||
|
|
|
@ -36,6 +36,17 @@
|
|||
"cycleColoursDesktop": "",
|
||||
"cycleMorphsAndroid": "",
|
||||
"cycleMorphsDesktop": "",
|
||||
"dateDaysAgo": "",
|
||||
"dateHoursAgo": "",
|
||||
"dateLastMonth": "",
|
||||
"dateLastYear": "",
|
||||
"dateMinutesAgo": "",
|
||||
"dateMonthsAgo": "",
|
||||
"dateNever": "",
|
||||
"dateRightNow": "",
|
||||
"dateWeeksAgo": "",
|
||||
"dateYearsAgo": "",
|
||||
"dateYesterday": "",
|
||||
"defaultGroupName": "Grupo incrível",
|
||||
"defaultProfileName": "",
|
||||
"defaultScalingText": "Texto tamanho padrão (fator de escala: ",
|
||||
|
@ -123,10 +134,12 @@
|
|||
"settingLanguage": "",
|
||||
"settingTheme": "",
|
||||
"smallTextLabel": "Pequeno",
|
||||
"successfullAddedContact": "",
|
||||
"themeDark": "",
|
||||
"themeLight": "",
|
||||
"titleManageContacts": "",
|
||||
"titleManageProfiles": "",
|
||||
"titleManageServers": "",
|
||||
"titlePlaceholder": "título…",
|
||||
"todoPlaceholder": "Afazer…",
|
||||
"tooltipAddContact": "",
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter_app/cwtch/gomobile.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_app/errorHandler.dart';
|
||||
import 'package:flutter_app/settings.dart';
|
||||
import 'package:flutter_app/torstatus.dart';
|
||||
import 'package:flutter_app/views/triplecolview.dart';
|
||||
import 'package:provider/provider.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 globalErrorHandler = ErrorHandler();
|
||||
var globalTorStatus = TorStatus();
|
||||
|
||||
void main() {
|
||||
LicenseRegistry.addLicense(() => licenses());
|
||||
|
@ -48,7 +50,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
cwtchInit = false;
|
||||
|
||||
profs = ProfileListState();
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler);
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus);
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
cwtch = CwtchGomobile(cwtchNotifier);
|
||||
|
@ -65,6 +67,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
appStatus = AppModel(cwtch: cwtch);
|
||||
}
|
||||
|
||||
ChangeNotifierProvider<TorStatus> getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);
|
||||
ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler);
|
||||
ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings);
|
||||
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
|
||||
|
@ -75,7 +78,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
//appStatus = AppModel(cwtch: cwtch);
|
||||
|
||||
return MultiProvider(
|
||||
providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider()],
|
||||
providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider(), getTorStatusProvider()],
|
||||
builder: (context, widget) {
|
||||
Provider.of<Settings>(context).initPackageInfo();
|
||||
return Consumer<Settings>(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_app/models/servers.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
@ -34,16 +35,6 @@ class ContactModel {
|
|||
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 {
|
||||
final int o;
|
||||
final String d;
|
||||
|
@ -123,6 +114,7 @@ class ContactListState extends ChangeNotifier {
|
|||
|
||||
class ProfileInfoState extends ChangeNotifier {
|
||||
ContactListState _contacts = ContactListState();
|
||||
ServerListState _servers = ServerListState();
|
||||
final String onion;
|
||||
String _nickname = "";
|
||||
String _imagePath = "";
|
||||
|
@ -135,6 +127,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
imagePath = "",
|
||||
unreadMessages = 0,
|
||||
contactsJson = "",
|
||||
serversJson = "",
|
||||
online = false,
|
||||
}) {
|
||||
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.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
|
||||
|
@ -192,6 +199,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
ContactListState get contactList => this._contacts;
|
||||
ServerListState get serverList => this._servers;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
|
|
@ -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});
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_app/errorHandler.dart';
|
||||
import 'package:flutter_app/models/servers.dart';
|
||||
import 'package:flutter_app/settings.dart';
|
||||
import 'package:flutter_app/widgets/buttontextfield.dart';
|
||||
import 'package:flutter_app/widgets/cwtchlabel.dart';
|
||||
|
@ -24,8 +25,11 @@ class AddContactView extends StatefulWidget {
|
|||
|
||||
class _AddContactViewState extends State<AddContactView> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _createGroupFormKey = GlobalKey<FormState>();
|
||||
final ctrlrOnion = TextEditingController(text: "");
|
||||
final ctrlrContact = TextEditingController(text: "");
|
||||
final ctrlrGroupName = TextEditingController(text: "");
|
||||
String server = "";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -49,7 +53,7 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
(groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()),
|
||||
Expanded(
|
||||
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() {
|
||||
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
|
||||
|
@ -80,6 +85,7 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
icon: Icon(Icons.person_add_rounded),
|
||||
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_add), text: AppLocalizations.of(context).joinGroup),
|
||||
],
|
||||
|
@ -137,6 +143,8 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (globalErrorHandler.explicitAddContactSuccess) {
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).successfullAddedContact + peerAddr));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
|
@ -147,11 +155,78 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
|
||||
/// TODO Add Group Pane
|
||||
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
|
||||
Widget joinGroupTab() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
|||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../opaque.dart';
|
||||
import '../settings.dart';
|
||||
import '../widgets/messagelist.dart';
|
||||
|
||||
class MessageView extends StatefulWidget {
|
||||
|
@ -87,7 +88,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
|
||||
Widget _buildComposeBox() {
|
||||
return Container(
|
||||
color: Opaque.current().backgroundMainColor(),
|
||||
color: Provider.of<Settings>(context).theme.backgroundMainColor(),
|
||||
height: 100,
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
|
@ -107,10 +108,10 @@ class _MessageViewState extends State<MessageView> {
|
|||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(2, 2, 2, 2),
|
||||
child: ElevatedButton(
|
||||
child: Icon(Icons.send, color: Opaque.current().mainTextColor()),
|
||||
child: Icon(Icons.send, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
style: ButtonStyle(
|
||||
fixedSize: MaterialStateProperty.all(Size(86, 40)),
|
||||
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
|
||||
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
|
||||
),
|
||||
onPressed: _sendMessage,
|
||||
)),
|
||||
|
@ -120,10 +121,10 @@ class _MessageViewState extends State<MessageView> {
|
|||
child: SizedBox(
|
||||
width: 41,
|
||||
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(
|
||||
fixedSize: MaterialStateProperty.all(Size(41, 40)),
|
||||
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
|
||||
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
|
||||
),
|
||||
onPressed: placeHolder,
|
||||
))),
|
||||
|
@ -132,10 +133,10 @@ class _MessageViewState extends State<MessageView> {
|
|||
child: SizedBox(
|
||||
width: 41,
|
||||
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(
|
||||
fixedSize: MaterialStateProperty.all(Size(41, 40)),
|
||||
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
|
||||
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
|
||||
),
|
||||
onPressed: placeHolder,
|
||||
))),
|
||||
|
|
|
@ -2,6 +2,8 @@ import 'dart:convert';
|
|||
|
||||
import 'package:flutter/material.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_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_app/widgets/profilerow.dart';
|
||||
|
@ -34,6 +36,15 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context).titleManageProfiles),
|
||||
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.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: ""}) {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
),
|
||||
)
|
||||
]))));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter_app/views/messageview.dart';
|
||||
import 'package:flutter_app/widgets/profileimage.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../opaque.dart';
|
||||
import '../settings.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ContactRow extends StatefulWidget {
|
||||
@override
|
||||
|
@ -29,21 +30,24 @@ class _ContactRowState extends State<ContactRow> {
|
|||
badgeTextColor: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor(),
|
||||
diameter: 64.0,
|
||||
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()),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
contact.nickname, //(contact.isInvitation ? "invite " : "non-invite ") + (contact.isBlocked ? "blokt" : "nonblokt"),//
|
||||
style: Provider.of<FlwtchState>(context).biggerFont,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.visible,
|
||||
),
|
||||
Text(contact.status),
|
||||
],
|
||||
)),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
contact.nickname, //(contact.isInvitation ? "invite " : "non-invite ") + (contact.isBlocked ? "blokt" : "nonblokt"),//
|
||||
style: Provider.of<FlwtchState>(context).biggerFont,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.visible,
|
||||
),
|
||||
Text(contact.onion),
|
||||
],
|
||||
))),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: contact.isInvitation != null && contact.isInvitation
|
||||
|
@ -51,13 +55,13 @@ class _ContactRowState extends State<ContactRow> {
|
|||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
iconSize: 16,
|
||||
icon: Icon(Icons.favorite, color: Opaque.current().mainTextColor()),
|
||||
icon: Icon(Icons.favorite, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
onPressed: _btnApprove,
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
iconSize: 16,
|
||||
icon: Icon(Icons.delete, color: Opaque.current().mainTextColor()),
|
||||
icon: Icon(Icons.delete, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
onPressed: _btnReject,
|
||||
)
|
||||
])
|
||||
|
@ -65,10 +69,10 @@ class _ContactRowState extends State<ContactRow> {
|
|||
? IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
iconSize: 16,
|
||||
icon: Icon(Icons.block, color: Opaque.current().mainTextColor()),
|
||||
icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||
onPressed: () {},
|
||||
)
|
||||
: Text(contact.unreadMessages.toString())),
|
||||
: Text(dateToNiceString(contact.lastMessageTime))),
|
||||
),
|
||||
]),
|
||||
onTap: () {
|
||||
|
@ -109,4 +113,11 @@ class _ContactRowState extends State<ContactRow> {
|
|||
.cwtch
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../model.dart';
|
||||
import '../opaque.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
|
||||
class MessageBubble extends StatefulWidget {
|
||||
@override
|
||||
_MessageBubbleState createState() => _MessageBubbleState();
|
||||
|
@ -21,28 +22,40 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||
prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp);
|
||||
}
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: fromMe ? Opaque.current().messageFromMeBackgroundColor() : Opaque.current().messageFromOtherBackgroundColor(),
|
||||
border: Border.all(color: fromMe ? Opaque.current().messageFromMeBackgroundColor() : Opaque.current().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),
|
||||
),
|
||||
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, children: [SelectableText(
|
||||
Provider.of<MessageState>(context).message,
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(prettyDate, style: TextStyle(fontSize: 9.0), textAlign: fromMe ? TextAlign.right : TextAlign.left),
|
||||
Provider.of<MessageState>(context).ackd
|
||||
? Icon(Icons.check_circle_outline, color: Opaque.current().mainTextColor(), size: 12)
|
||||
: Icon(Icons.hourglass_bottom_outlined, color: Opaque.current().mainTextColor(), size: 12)
|
||||
],
|
||||
)])),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(9.0),
|
||||
child: Column(crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [
|
||||
SelectableText(
|
||||
Provider.of<MessageState>(context).message,
|
||||
style: TextStyle(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||
),
|
||||
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)
|
||||
],
|
||||
)
|
||||
])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../model.dart';
|
||||
import 'messagebubble.dart';
|
||||
import '../settings.dart';
|
||||
import 'messagerow.dart';
|
||||
|
||||
class MessageList extends StatefulWidget {
|
||||
|
@ -43,22 +43,31 @@ class _MessageListState extends State<MessageList> {
|
|||
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom(false));
|
||||
return Card(
|
||||
child: Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
controller: ctrlr1,
|
||||
child: ListView.builder(
|
||||
controller: ctrlr1,
|
||||
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());
|
||||
},
|
||||
),
|
||||
));
|
||||
isAlwaysShown: true,
|
||||
controller: ctrlr1,
|
||||
child: Container(
|
||||
// Only show broken heart is the contact is offline...
|
||||
decoration: Provider.of<ContactInfoState>(outerContext).status == "Authenticated"
|
||||
? null
|
||||
: BoxDecoration(
|
||||
image: 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>(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());
|
||||
},
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../opaque.dart';
|
||||
import '../settings.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;
|
||||
|
||||
Widget wdgBubble = MessageBubble();
|
||||
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Opaque.current().dropShadowColor());
|
||||
Widget wdgSpacer = Expanded(child:SizedBox(width: 60, height: 10));
|
||||
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Provider.of<Settings>(context).theme.dropShadowColor());
|
||||
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
||||
var widgetRow = <Widget>[];
|
||||
|
||||
if (fromMe) {
|
||||
|
@ -36,8 +35,7 @@ class _MessageRowState extends State<MessageRow> {
|
|||
diameter: 48.0,
|
||||
imagePath: contact.imagePath,
|
||||
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>[
|
||||
wdgPortrait,
|
||||
|
|
|
@ -142,7 +142,7 @@ packages:
|
|||
name: integration_test
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2+2"
|
||||
version: "1.0.2+3"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -84,7 +84,9 @@ flutter:
|
|||
|
||||
assets:
|
||||
- assets/
|
||||
- assets/core/
|
||||
- assets/profiles/
|
||||
- assets/servers/
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
|
|
After Width: | Height: | Size: 6.5 KiB |
|
@ -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);
|
||||
});
|
||||
}
|