Performance Debugging
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
Sarah Jamie Lewis 2021-05-25 13:43:13 -07:00
parent 7fba47a1e0
commit 05779c49e2
13 changed files with 246 additions and 142 deletions

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'package:cwtch/notification_manager.dart';
import 'package:provider/provider.dart';
import 'package:cwtch/torstatus.dart';
@ -14,12 +15,14 @@ class CwtchNotifier {
late Settings settings;
late ErrorHandler error;
late TorStatus torStatus;
late NotificationsManager notificationManager;
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN) {
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP) {
profileCN = pcn;
settings = settingsCN;
error = errorCN;
torStatus = torStatusCN;
notificationManager = notificationManagerP;
}
void handleMessage(String type, dynamic data) {
@ -60,6 +63,7 @@ class CwtchNotifier {
}
break;
case "NewMessageFromPeer":
notificationManager.notify("New Message From Peer!");
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).unreadMessages++;
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).totalMessages++;
profileCN.getProfile(data["ProfileOnion"]).contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());

View File

@ -1,3 +1,4 @@
import 'package:cwtch/notification_manager.dart';
import 'package:flutter/foundation.dart';
import 'package:cwtch/cwtch/ffi.dart';
import 'package:cwtch/cwtch/gomobile.dart';
@ -50,11 +51,15 @@ class FlwtchState extends State<Flwtch> {
cwtchInit = false;
profs = ProfileListState();
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus);
if (Platform.isAndroid) {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
cwtch = CwtchGomobile(cwtchNotifier);
} else if (Platform.isLinux) {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, LinuxNotificationsManager());
cwtch = CwtchFfi(cwtchNotifier);
} else {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
cwtch = CwtchFfi(cwtchNotifier);
}
@ -78,7 +83,13 @@ class FlwtchState extends State<Flwtch> {
//appStatus = AppModel(cwtch: cwtch);
return MultiProvider(
providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider(), getTorStatusProvider()],
providers: [
getFlwtchStateProvider(),
getProfileListProvider(),
getSettingsProvider(),
getErrorHandlerProvider(),
getTorStatusProvider(),
],
builder: (context, widget) {
Provider.of<Settings>(context).initPackageInfo();
return Consumer<Settings>(

View File

@ -0,0 +1,26 @@
import 'package:desktop_notifications/desktop_notifications.dart';
import 'package:path/path.dart' as path;
// NotificationsManager provides a wrapper around platform specific notifications logic.
abstract class NotificationsManager {
Future<void> notify(String message);
}
// NullNotificationsManager ignores all notification requests
class NullNotificationsManager implements NotificationsManager {
@override
Future<void> notify(String message) async {}
}
// LinuxNotificationsManager uses the desktop_notifications package to implement
// the standard dbus-powered linux desktop notifications.
class LinuxNotificationsManager implements NotificationsManager {
int previous_id = 0;
LinuxNotificationsManager() {}
Future<void> notify(String message) async {
var client = NotificationsClient();
var icon_path = Uri.file(path.join(path.current, "cwtch.png"));
client.notify('New Message from Peer!', appName: "cwtch", appIcon: icon_path.toString(), replacesId: this.previous_id).then((Notification value) => previous_id = value.id);
client.close();
}
}

View File

@ -31,48 +31,50 @@ class _ContactsViewState extends State<ContactsView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(children: [
ProfileImage(
imagePath: Provider.of<ProfileInfoState>(context).imagePath,
diameter: 42,
border: Provider.of<Settings>(context).theme.portraitOnlineBorderColor(),
badgeTextColor: Colors.red,
badgeColor: Colors.red,
),
SizedBox(
width: 10,
),
Expanded(
child: Text(
"%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", "Contacts"),
overflow: TextOverflow.ellipsis,
)), //todo
]),
actions: [
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
IconButton(
icon: Icon(Icons.copy),
onPressed: _copyOnion,
),
IconButton(
// need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
icon: Icon(showSearchBar || Provider.of<ContactListState>(context).isFiltered ? Icons.search_off : Icons.search),
onPressed: () {
Provider.of<ContactListState>(context, listen: false).filter = "";
setState(() {
showSearchBar = !showSearchBar;
});
})
],
),
floatingActionButton: FloatingActionButton(
onPressed: _pushAddContact,
tooltip: AppLocalizations.of(context)!.tooltipAddContact,
child: const Icon(Icons.person_add_sharp),
),
body: showSearchBar || Provider.of<ContactListState>(context).isFiltered ? _buildFilterable() : _buildContactList(),
);
endDrawerEnableOpenDragGesture: false,
drawerEnableOpenDragGesture: false,
appBar: AppBar(
title: RepaintBoundary(
child: Row(children: [
ProfileImage(
imagePath: Provider.of<ProfileInfoState>(context).imagePath,
diameter: 42,
border: Provider.of<Settings>(context).theme.portraitOnlineBorderColor(),
badgeTextColor: Colors.red,
badgeColor: Colors.red,
),
SizedBox(
width: 10,
),
Expanded(
child: Text(
"%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", "Contacts"),
overflow: TextOverflow.ellipsis,
)), //todo
])),
actions: [
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
IconButton(
icon: Icon(Icons.copy),
onPressed: _copyOnion,
),
IconButton(
// need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
icon: Icon(showSearchBar || Provider.of<ContactListState>(context).isFiltered ? Icons.search_off : Icons.search),
onPressed: () {
Provider.of<ContactListState>(context, listen: false).filter = "";
setState(() {
showSearchBar = !showSearchBar;
});
})
],
),
floatingActionButton: FloatingActionButton(
onPressed: _pushAddContact,
tooltip: AppLocalizations.of(context)!.tooltipAddContact,
child: const Icon(Icons.person_add_sharp),
),
body: showSearchBar || Provider.of<ContactListState>(context).isFiltered ? _buildFilterable() : _buildContactList());
}
Widget _buildFilterable() {
@ -89,13 +91,13 @@ class _ContactsViewState extends State<ContactsView> {
Widget _buildContactList() {
final tiles = Provider.of<ContactListState>(context).contacts.map((ContactInfoState contact) {
return ChangeNotifierProvider<ContactInfoState>.value(key: ValueKey(contact.profileOnion + "" + contact.onion), value: contact, builder: (_, __) => ContactRow());
return ChangeNotifierProvider<ContactInfoState>.value(key: ValueKey(contact.profileOnion + "" + contact.onion), value: contact, builder: (_, __) => RepaintBoundary(child: ContactRow()));
});
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return ListView(children: divided);
return RepaintBoundary(child: ListView(children: divided));
}
void _pushAddContact() {

View File

@ -113,42 +113,43 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(AppLocalizations.of(context)!.enterProfilePassword),
SizedBox(
height: 20,
),
CwtchPasswordField(
controller: ctrlrPassword,
validator: (value) {},
),
SizedBox(
height: 20,
),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Spacer(),
Expanded(
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
onPressed: () {
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text);
ctrlrPassword.text = "";
Navigator.pop(context);
},
)),
Spacer()
]),
],
)),
));
return RepaintBoundary(
child: Container(
height: 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(AppLocalizations.of(context)!.enterProfilePassword),
SizedBox(
height: 20,
),
CwtchPasswordField(
controller: ctrlrPassword,
validator: (value) {},
),
SizedBox(
height: 20,
),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Spacer(),
Expanded(
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
onPressed: () {
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text);
ctrlrPassword.text = "";
Navigator.pop(context);
},
)),
Spacer()
]),
],
)),
)));
});
}
@ -157,7 +158,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
(ProfileInfoState profile) {
return ChangeNotifierProvider<ProfileInfoState>.value(
value: profile,
builder: (context, child) => ProfileRow(),
builder: (context, child) => RepaintBoundary(child: ProfileRow()),
);
},
);

View File

@ -71,26 +71,27 @@ class MessageBubbleState extends State<MessageBubble> {
return LayoutBuilder(builder: (context, constraints) {
//print(constraints.toString()+", "+constraints.maxWidth.toString());
return Container(
return RepaintBoundary(
child: Container(
decoration: BoxDecoration(
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
border:
Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(), width: 1),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(borderRadiousEh),
topRight: Radius.circular(borderRadiousEh),
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
),
),
child: Padding(
padding: EdgeInsets.all(9.0),
child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]))));
child: Container(
decoration: BoxDecoration(
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
border: Border.all(
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(), width: 1),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(borderRadiousEh),
topRight: Radius.circular(borderRadiousEh),
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
),
),
child: Padding(
padding: EdgeInsets.all(9.0),
child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))));
});
}
}

View File

@ -15,41 +15,42 @@ class _MessageListState extends State<MessageList> {
@override
Widget build(BuildContext outerContext) {
return Container(
child: Scrollbar(
isAlwaysShown: true,
controller: ctrlr1,
child: Container(
// Only show broken heart is the contact is offline...
decoration: BoxDecoration(
image: Provider.of<ContactInfoState>(outerContext).isOnline()
? null
: DecorationImage(
fit: BoxFit.contain,
image: AssetImage("assets/core/negative_heart_512px.png"),
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.mainTextColor(), BlendMode.srcIn))),
child: ListView.builder(
return RepaintBoundary(
child: Container(
child: Scrollbar(
isAlwaysShown: true,
controller: ctrlr1,
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
reverse: true,
itemBuilder: (itemBuilderContext, index) {
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
return ChangeNotifierProvider(
key: ValueKey(trueIndex),
create: (x) => MessageState(
context: itemBuilderContext,
profileOnion: Provider.of<ProfileInfoState>(outerContext, listen: false).onion,
contactHandle: Provider.of<ContactInfoState>(x, listen: false).onion,
messageIndex: trueIndex,
),
builder: (bcontext, child) {
String idx = Provider.of<ContactInfoState>(outerContext).isGroup == true && Provider.of<MessageState>(bcontext).signature.isEmpty == false
? Provider.of<MessageState>(bcontext).signature
: trueIndex.toString();
return MessageRow(key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
});
},
),
)));
child: Container(
// Only show broken heart is the contact is offline...
decoration: BoxDecoration(
image: Provider.of<ContactInfoState>(outerContext).isOnline()
? null
: DecorationImage(
fit: BoxFit.contain,
image: AssetImage("assets/core/negative_heart_512px.png"),
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.mainTextColor(), BlendMode.srcIn))),
child: ListView.builder(
controller: ctrlr1,
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
reverse: true,
itemBuilder: (itemBuilderContext, index) {
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
return ChangeNotifierProvider(
key: ValueKey(trueIndex),
create: (x) => MessageState(
context: itemBuilderContext,
profileOnion: Provider.of<ProfileInfoState>(outerContext, listen: false).onion,
contactHandle: Provider.of<ContactInfoState>(x, listen: false).onion,
messageIndex: trueIndex,
),
builder: (bcontext, child) {
String idx = Provider.of<ContactInfoState>(outerContext).isGroup == true && Provider.of<MessageState>(bcontext).signature.isEmpty == false
? Provider.of<MessageState>(bcontext).signature
: trueIndex.toString();
return RepaintBoundary(child: MessageRow(key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx)));
});
},
),
))));
}
}

View File

@ -21,7 +21,8 @@ class ProfileImage extends StatefulWidget {
class _ProfileImageState extends State<ProfileImage> {
@override
Widget build(BuildContext context) {
return Stack(children: [
return RepaintBoundary(
child: Stack(children: [
ClipOval(
clipBehavior: Clip.antiAlias,
child: Container(
@ -57,6 +58,6 @@ class _ProfileImageState extends State<ProfileImage> {
child: Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0)),
),
)),
]);
]));
}
}

View File

@ -15,7 +15,8 @@ class TorIcon extends StatefulWidget {
class _TorIconState extends State<TorIcon> {
@override
Widget build(BuildContext context) {
return Image(
return RepaintBoundary(
child: Image(
image: AssetImage(Provider.of<TorStatus>(context).progress == 0
? "assets/core/Tor_OFF.png"
: (Provider.of<TorStatus>(context).progress == 100 ? "assets/core/Tor_icon.png" : "assets/core/Tor_Booting_up.png")),
@ -25,6 +26,6 @@ class _TorIconState extends State<TorIcon> {
semanticLabel: Provider.of<TorStatus>(context).progress == 100
? AppLocalizations.of(context)!.networkStatusOnline
: (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
);
));
}
}

View File

@ -4,6 +4,10 @@
#include "generated_plugin_registrant.h"
#include <window_size/window_size_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) window_size_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
window_size_plugin_register_with_registrar(window_size_registrar);
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
window_size
)
set(PLUGIN_BUNDLED_LIBRARIES)

View File

@ -8,6 +8,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.2"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
async:
dependency: transitive
description:
@ -64,6 +71,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
dbus:
dependency: transitive
description:
name: dbus
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
desktop_notifications:
dependency: "direct main"
description:
name: desktop_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
fake_async:
dependency: transitive
description:
@ -269,6 +290,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.0"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
platform:
dependency: transitive
description:
@ -386,6 +414,15 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
window_size:
dependency: "direct main"
description:
path: "plugins/window_size"
ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
resolved-ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
url: "git://github.com/google/flutter-desktop-embedding.git"
source: git
version: "0.1.0"
xdg_directories:
dependency: transitive
description:
@ -393,6 +430,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.1"
sdks:
dart: ">=2.13.0 <3.0.0"
flutter: ">=1.20.0"

View File

@ -34,6 +34,7 @@ dependencies:
cupertino_icons: ^1.0.0
ffi: ^1.0.0
path_provider: ^2.0.0
desktop_notifications: 0.5.0
glob: any
# todo: flutter_driver causes version conflict. eg https://github.com/flutter/flutter/issues/44829
@ -44,6 +45,12 @@ dependencies:
flutter_driver:
sdk: flutter
window_size:
git:
url: git://github.com/google/flutter-desktop-embedding.git
path: plugins/window_size
ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
#dev_dependencies:
# flutter_lokalise: any