Merge branch 'trunk' into tests
continuous-integration/drone/pr Build is failing Details

This commit is contained in:
erinn 2021-03-29 15:45:31 -07:00
commit f101874ac2
19 changed files with 372 additions and 28 deletions

119
.drone.yml Normal file
View File

@ -0,0 +1,119 @@
---
kind: pipeline
type: docker
name: default
steps:
- name: fetch
image: cirrusci/flutter:dev
volumes:
- name: deps
path: /root/.pub-cache
commands:
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
- chmod a+x tor
- git fetch --tags
- echo `git describe --tags` > VERSION
- echo `date +%G-%m-%d-%H-%M` > BUILDDATE
- flutter pub get
- mkdir deploy
#- name: quality
# image: golang
# volumes:
# - name: deps
# path: /go
# commands:
# - go list ./... | xargs go vet
# - go list ./... | xargs golint
# #Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting
- name: build-linux
image: cirrusci/flutter:dev
volumes:
- name: deps
path: /root/.pub-cache
commands:
- flutter config --enable-linux-desktop
- apt-get update
- apt-get install -y --no-install-recommends cmake ninja-build clang build-essential pkg-config libgtk-3-dev liblzma-dev
- flutter build linux
- mkdir deploy/linux
- cp -r build/linux/x64/release/bundle/* deploy/linux
- cp /sdks/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat deploy/linux
- name: build-android
image: cirrusci/flutter:dev
volumes:
- name: deps
path: /root/.pub-cache
commands:
- flutter build appbundle
# or build apk --split-per-abi ?
- mkdir deploy/android
- cp build/app/outputs/bundle/release/app-release.aab deploy/android
# Todo: gonna need more work on container
# https://flutter.dev/desktop
# requirements: Visual Studio 2019 (not to be confused with Visual Studio Code) with the “Desktop development with C++” workload installed, including all of its default components
#- name: build-windows
# image: cirrusci/flutter:dev
#- volumes:
# - name: deps
# path: /root/.pub-cache
# commands:
# - flutter config --enable-windows-desktop
# - flutter build windows
- name: deploy-buildfiles
image: kroniak/ssh-client
environment:
BUILDFILES_KEY:
from_secret: buildfiles_key
secrets: [gogs_account_token]
when:
event: push
status: [ success ]
commands:
- echo $BUILDFILES_KEY > ~/id_rsab64
- base64 -d ~/id_rsab64 > ~/id_rsa
- chmod 400 ~/id_rsa
- export DIR=flwtch-`cat VERSION`-`cat BUILDDATE`
- mv deploy $DIR
- cd $DIR
- find . -type f -exec sha256sum {} \; > ./../sha256s.txt
- mv ./../sha256s.txt .
- cd ..
# TODO: do deployment once files actaully compile
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@openprivacy.ca:/home/buildfiles/buildfiles/
- name: notify-email
image: drillster/drone-email
host: build.openprivacy.ca
port: 25
skip_verify: true
from: drone@openprivacy.ca
when:
status: [ failure ]
- name: notify-gogs
image: openpriv/drone-gogs
when:
event: pull_request
status: [ success, changed, failure ]
environment:
GOGS_ACCOUNT_TOKEN:
from_secret: gogs_account_token
gogs_url: https://git.openprivacy.ca
volumes:
- name: deps
temp: {}
trigger:
repo: flutter/flutter_app
branch: trunk
event:
- push
- pull_request

View File

@ -1,5 +1,6 @@
import 'dart:convert';
import '../errorHandler.dart';
import '../model.dart';
import '../settings.dart';
@ -8,10 +9,12 @@ import '../settings.dart';
class CwtchNotifier {
ProfileListState profileCN;
Settings settings;
ErrorHandler error;
CwtchNotifier(ProfileListState pcn, Settings settingsCN) {
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN) {
profileCN = pcn;
settings = settingsCN;
error = errorCN;
}
void handleMessage(String type, dynamic data) {
@ -25,12 +28,13 @@ class CwtchNotifier {
));
break;
case "PeerCreated":
print("xx peercreated");
print("xx peercreated $data");
profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState(
profileOnion: data["ProfileOnion"],
onion: data["RemotePeer"],
nickname: data["nick"],
status: data["status"],
imagePath: data["picture"],
));
break;
case "PeerStateChange":
@ -44,6 +48,10 @@ class CwtchNotifier {
contact.status = data["ConnectionState"];
}
break;
case "AppError":
print("New App Error: $data");
error.handleUpdate(data["Data"]);
break;
case "UpdateGlobalSettings":
settings.handleUpdate(jsonDecode(data["Data"]));
break;

View File

@ -8,7 +8,6 @@ import 'package:path/path.dart' as path;
import 'package:ffi/ffi.dart';
import 'package:flutter_app/cwtch/cwtch.dart';
/////////////////////
/// Cwtch API ///
/////////////////////

48
lib/errorHandler.dart Normal file
View File

@ -0,0 +1,48 @@
import 'package:flutter/cupertino.dart';
class ErrorHandler extends ChangeNotifier {
// General Error Types
static const String successErrorType = "success";
// Add Contact Specific Errors...
static const String addContactErrorPrefix = "addcontact";
static const String invalidImportStringErrorType = "invalid_import_string";
static const String contactAlreadyExistsErrorType = "contact_already_exists";
bool invalidImportStringError = false;
bool contactAlreadyExistsError = false;
bool explicitAddContactSuccess = false;
/// Called by the event bus.
handleUpdate(String error) {
var parts = error.split(".");
String prefix = parts[0];
String errorType = parts[1];
switch (prefix) {
case addContactErrorPrefix:
handleAddContactError(errorType);
break;
}
notifyListeners();
}
handleAddContactError(String errorType) {
// Reset add contact errors
invalidImportStringError = false;
contactAlreadyExistsError = false;
explicitAddContactSuccess = false;
switch (errorType) {
case invalidImportStringErrorType:
invalidImportStringError = true;
break;
case contactAlreadyExistsErrorType:
contactAlreadyExistsError = true;
break;
case successErrorType:
explicitAddContactSuccess = true;
break;
}
}
}

View File

@ -17,6 +17,7 @@
"builddate": "Aufgebaut auf: %2",
"bulletinsBtn": "Meldungen",
"chatBtn": "Chat",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "in die Zwischenablage kopiert",
"copiedToClipboardNotification": "in die Zwischenablage kopiert",
@ -56,6 +57,7 @@
"groupAddr": "Adresse",
"groupName": "Gruppenname",
"groupNameLabel": "Gruppenname",
"invalidImportString": "",
"invitation": "Einladung",
"invitationLabel": "Einladung",
"inviteBtn": "Einladen",

View File

@ -17,6 +17,7 @@
"builddate": "Built on: %2",
"bulletinsBtn": "Bulletins",
"chatBtn": "Chat",
"contactAlreadyExists": "Contact Already Exists",
"conversationSettings": "Conversation Settings",
"copiedClipboardNotification": "Copied to clipboard",
"copiedToClipboardNotification": "Copied to Clipboard",
@ -56,6 +57,7 @@
"groupAddr": "Address",
"groupName": "Group name",
"groupNameLabel": "Group Name",
"invalidImportString": "Invalid import string",
"invitation": "Invitation",
"invitationLabel": "Invitation",
"inviteBtn": "Invite",

View File

@ -17,6 +17,7 @@
"builddate": "Basado en: %2",
"bulletinsBtn": "Boletines",
"chatBtn": "Chat",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "Copiado al portapapeles",
"copiedToClipboardNotification": "Copiado al portapapeles",
@ -56,6 +57,7 @@
"groupAddr": "Dirección",
"groupName": "Nombre del grupo",
"groupNameLabel": "Nombre del grupo",
"invalidImportString": "",
"invitation": "Invitación",
"invitationLabel": "Invitación",
"inviteBtn": "Invitar",

View File

@ -17,6 +17,7 @@
"builddate": "",
"bulletinsBtn": "Bulletins",
"chatBtn": "Discuter",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "Copié dans le presse-papier",
"copiedToClipboardNotification": "Copié dans le presse-papier",
@ -56,6 +57,7 @@
"groupAddr": "",
"groupName": "",
"groupNameLabel": "Nom du groupe",
"invalidImportString": "",
"invitation": "",
"invitationLabel": "Invitation",
"inviteBtn": "Invitation",

View File

@ -17,6 +17,7 @@
"builddate": "Costruito il: %2",
"bulletinsBtn": "Bollettini",
"chatBtn": "Chat",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "Copiato negli Appunti",
"copiedToClipboardNotification": "Copiato negli Appunti",
@ -56,6 +57,7 @@
"groupAddr": "Indirizzo",
"groupName": "Nome del gruppo",
"groupNameLabel": "Nome del gruppo",
"invalidImportString": "",
"invitation": "Invito",
"invitationLabel": "Invito",
"inviteBtn": "Invitare",

View File

@ -17,6 +17,7 @@
"builddate": "",
"bulletinsBtn": "Boletins",
"chatBtn": "Chat",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "Copiado",
"copiedToClipboardNotification": "Copiado",
@ -56,6 +57,7 @@
"groupAddr": "",
"groupName": "",
"groupNameLabel": "Nome do Grupo",
"invalidImportString": "",
"invitation": "",
"invitationLabel": "Convite",
"inviteBtn": "Convidar",

View File

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_app/cwtch/ffi.dart';
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/views/triplecolview.dart';
import 'package:provider/provider.dart';
@ -16,6 +17,7 @@ import 'opaque.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
var globalSettings = Settings(Locale("en", ''), Opaque.dark);
var globalErrorHandler = ErrorHandler();
void main() {
LicenseRegistry.addLicense(() => licenses());
@ -46,7 +48,7 @@ class FlwtchState extends State<Flwtch> {
cwtchInit = false;
profs = ProfileListState();
var cwtchNotifier = new CwtchNotifier(profs, globalSettings);
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler);
if (Platform.isAndroid) {
cwtch = CwtchGomobile(cwtchNotifier);
@ -63,6 +65,7 @@ class FlwtchState extends State<Flwtch> {
appStatus = AppModel(cwtch: cwtch);
}
ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler);
ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings);
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
@ -72,7 +75,7 @@ class FlwtchState extends State<Flwtch> {
//appStatus = AppModel(cwtch: cwtch);
return MultiProvider(
providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider()],
providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider()],
builder: (context, widget) {
Provider.of<Settings>(context).initPackageInfo();
return Consumer<Settings>(

View File

@ -192,7 +192,19 @@ class ContactInfoState extends ChangeNotifier {
int _unreadMessages = 0;
int _totalMessages = 0;
ContactInfoState({this.profileOnion, this.onion, nickname = "", isGroup = false, isInvitation = false, isBlocked = false, status = "", imagePath = "", savePeerHistory = "DeleteHistoryConfirmed", numMessages = 0, numUnread = 0,}) {
ContactInfoState({
this.profileOnion,
this.onion,
nickname = "",
isGroup = false,
isInvitation = false,
isBlocked = false,
status = "",
imagePath = "",
savePeerHistory = "DeleteHistoryConfirmed",
numMessages = 0,
numUnread = 0,
}) {
this._nickname = nickname;
this._isGroup = isGroup;
this._isInvitation = isInvitation;

View File

@ -7,6 +7,8 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'opaque.dart';
const TapirGroupsExperiment = "tapir-groups-experiment";
/// Settings govern the *Globally* relevant settings like Locale, Theme and Experiments.
/// We also provide access to the version information here as it is also accessed from the
/// Settings Pane.

View File

@ -1,12 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app/errorHandler.dart';
import 'package:flutter_app/settings.dart';
import 'package:flutter_app/widgets/buttontextfield.dart';
import 'package:flutter_app/widgets/cwtchlabel.dart';
import 'package:flutter_app/widgets/textfield.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../model.dart';
/// Add Contact View is the one-stop shop for adding public keys to a Profiles contact list.
/// We support both Peers and Groups (experiment-pending).
/// NOTE: This view makes use of the global Error Handler to receive events from the Cwtch Library (for validating
/// error states caused by incorrect import string or duplicate requests to add a specific contact)
class AddContactView extends StatefulWidget {
@override
_AddContactViewState createState() => _AddContactViewState();
}
class _AddContactViewState extends State<AddContactView> {
final _formKey = GlobalKey<FormState>();
final ctrlrOnion = TextEditingController(text: "");
final ctrlrContact = TextEditingController(text: "");
@override
Widget build(BuildContext context) {
return Scaffold(
@ -18,16 +39,119 @@ class _AddContactViewState extends State<AddContactView> {
}
Widget _buildForm() {
return Center(
child: Wrap(
direction: Axis.vertical,
spacing: 20.0,
runSpacing: 20.0,
children: <Widget>[
Text(AppLocalizations.of(context).profileName),
Text("peer handle or group invite or server bundle"), //todo
Text(AppLocalizations.of(context).createGroupBtn),
ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
/// We display a different number of tabs dependening on the experiment setup
bool groupsEnabled = Provider.of<Settings>(context).experimentsEnabled && Provider.of<Settings>(context).experiments[TapirGroupsExperiment];
return Consumer<ErrorHandler>(builder: (context, globalErrorHandler, child) {
return DefaultTabController(
length: groupsEnabled ? 3 : 1,
child: Column(children: [
(groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()),
Expanded(
child: TabBarView(
children: (groupsEnabled ? [addPeerTab(), addGroupTab(), joinGroupTab()] : [addPeerTab()]),
)),
]));
});
}
void _copyOnion() {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
// TODO Toast
}
/// A Tab Bar with only the Add Peer Tab
TabBar getTabBarWithAddPeerOnly() {
return TabBar(
tabs: [
Tab(
icon: Icon(Icons.person_add_rounded),
text: AppLocalizations.of(context).addPeer,
),
],
));
);
}
/// The full tab bar with Join and Add Groups
TabBar getTabBarWithGroups() {
return TabBar(
tabs: [
Tab(
icon: Icon(Icons.person_add_rounded),
text: AppLocalizations.of(context).addPeer,
),
Tab(icon: Icon(Icons.group), text: AppLocalizations.of(context).createGroup),
Tab(icon: Icon(Icons.group_add), text: AppLocalizations.of(context).joinGroup),
],
);
}
/// The Add Peer Tab allows a peer to add a specific non-group peer to their contact lists
/// We also provide a convenient way to copy their onion.
Widget addPeerTab() {
return Container(
margin: EdgeInsets.all(30),
padding: EdgeInsets.all(20),
child: Form(
autovalidateMode: AutovalidateMode.always,
key: _formKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context).profileOnionLabel),
SizedBox(
height: 20,
),
CwtchButtonTextField(
controller: ctrlrOnion,
onPressed: _copyOnion,
icon: Icon(Icons.copy),
tooltip: AppLocalizations.of(context).copyBtn,
),
SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context).pasteAddressToAddContact),
SizedBox(
height: 20,
),
CwtchTextField(
controller: ctrlrContact,
validator: (value) {
if (value == "") {
return null;
} if (globalErrorHandler.invalidImportStringError) {
return AppLocalizations.of(context).invalidImportString;
} else if (globalErrorHandler.contactAlreadyExistsError) {
return AppLocalizations.of(context).contactAlreadyExists;
} else if (globalErrorHandler.explicitAddContactSuccess) {
}
return null;
},
onChanged: (String peerAddr) async {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
final setPeerAttribute = {
"EventType": "AddContact",
"Data": {"ImportString": peerAddr},
};
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
Future.delayed(const Duration(milliseconds: 500), () {
if (globalErrorHandler.explicitAddContactSuccess) {
Navigator.pop(context);
}
});
},
)
])));
}
/// TODO Add Group Pane
Widget addGroupTab() {
return Icon(Icons.group_add);
}
/// TODO Join Group Pane
Widget joinGroupTab() {
return Icon(Icons.group);
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_app/widgets/contactrow.dart';
import 'package:provider/provider.dart';
import '../errorHandler.dart';
import '../main.dart';
import 'addcontactview.dart';
import '../model.dart';
@ -48,9 +49,11 @@ class _ContactsViewState extends State<ContactsView> {
void _pushAddContact() {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider(
create: (_) => Provider.of<FlwtchState>(context),
builder: (BuildContext bcontext) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
],
child: AddContactView(),
);
},

View File

@ -94,12 +94,12 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
children: [
SwitchListTile(
title: Text(AppLocalizations.of(context).enableGroups, style: TextStyle(color: settings.current().mainTextColor())),
value: settings.experiments.containsKey("tapir-groups-experiment") && settings.experiments["tapir-groups-experiment"],
value: settings.experiments.containsKey(TapirGroupsExperiment) && settings.experiments[TapirGroupsExperiment],
onChanged: (bool value) {
if (value) {
settings.enableExperiment("tapir-groups-experiment");
settings.enableExperiment(TapirGroupsExperiment);
} else {
settings.disableExperiment("tapir-groups-experiment");
settings.disableExperiment(TapirGroupsExperiment);
}
// Save Settings...
saveSettings(context);

View File

@ -81,8 +81,18 @@ class _MessageViewState extends State<MessageView> {
onPressed: _sendMessage,
),
Row(children: <Widget>[
SizedBox(width: 45, child: ElevatedButton(child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()), onPressed: placeHolder,)),
SizedBox(width: 45, child: ElevatedButton(child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()), onPressed: placeHolder,)),
SizedBox(
width: 45,
child: ElevatedButton(
child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()),
onPressed: placeHolder,
)),
SizedBox(
width: 45,
child: ElevatedButton(
child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()),
onPressed: placeHolder,
)),
])
]),
),

View File

@ -23,4 +23,4 @@ class _MessageListState extends State<MessageList> {
},
);
}
}
}

View File

@ -2,13 +2,16 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../settings.dart';
doNothing(String x) {}
// Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator.
class CwtchTextField extends StatefulWidget {
CwtchTextField({this.controller, this.labelText, this.validator});
CwtchTextField({this.controller, this.labelText, this.validator, this.onChanged = doNothing});
final TextEditingController controller;
final String labelText;
final FormFieldValidator validator;
final Function(String) onChanged;
@override
_CwtchTextFieldState createState() => _CwtchTextFieldState();
@ -21,6 +24,7 @@ class _CwtchTextFieldState extends State<CwtchTextField> {
return TextFormField(
controller: widget.controller,
validator: widget.validator,
onChanged: widget.onChanged,
decoration: InputDecoration(
labelText: widget.labelText,
labelStyle: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),