Replace ListTile with Custom Card/Inkwell + New ProfileImage
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
Sarah Jamie Lewis 2021-04-09 21:01:12 -07:00
parent 3dbfd9deb9
commit e356254ff8
11 changed files with 248 additions and 162 deletions

View File

@ -20,12 +20,7 @@ class CwtchNotifier {
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"],
));
profileCN.add(ProfileInfoState(onion: data["Identity"], nickname: data["name"], imagePath: data["picture"], contactsJson: data["ContactsJson"], online: data["Online"] == "true"));
break;
case "PeerCreated":
profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState(
@ -64,6 +59,17 @@ class CwtchNotifier {
case "UpdateGlobalSettings":
settings.handleUpdate(jsonDecode(data["Data"]));
break;
case "SetAttribute":
if (data["Key"] == "public.name") {
profileCN.getProfile(data["ProfileOnion"]).nickname = data["Data"];
} else {
print("unhandled set attribute event: $type");
}
break;
case "NetworkError":
var isOnline = data["Status"] == "Success";
profileCN.getProfile(data["ProfileOnion"]).isOnline = isOnline;
break;
default:
print("unhandled event: $type");
}

View File

@ -124,6 +124,7 @@ class ProfileInfoState extends ChangeNotifier {
String _nickname = "";
String _imagePath = "";
int _unreadMessages = 0;
bool _online = false;
ProfileInfoState({
this.onion,
@ -131,10 +132,12 @@ class ProfileInfoState extends ChangeNotifier {
imagePath = "",
unreadMessages = 0,
contactsJson = "",
online = false,
}) {
this._nickname = nickname;
this._imagePath = imagePath;
this._unreadMessages = unreadMessages;
this._online = online;
if (contactsJson != null && contactsJson != "" && contactsJson != "null") {
print("decoding " + contactsJson);
@ -155,6 +158,13 @@ class ProfileInfoState extends ChangeNotifier {
}
}
// Getters and Setters for Online Status
bool get isOnline => this._online;
set isOnline(bool newValue) {
this._online = newValue;
notifyListeners();
}
String get nickname => this._nickname;
set nickname(String newValue) {
this._nickname = newValue;
@ -275,7 +285,6 @@ class ContactInfoState extends ChangeNotifier {
}
}
class MessageState extends ChangeNotifier {
final String profileOnion;
final String contactHandle;
@ -309,7 +318,6 @@ class MessageState extends ChangeNotifier {
}
}
/////////////
/// ACN ///
/////////////

View File

@ -1381,4 +1381,4 @@ ThemeData mkThemeData(Settings opaque) {
button: TextStyle(color: opaque.current().mainTextColor()),
overline: TextStyle(color: opaque.current().mainTextColor())),
);
}
}

View File

@ -77,7 +77,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
SwitchListTile(
title: Text(AppLocalizations.of(context).blockUnknownLabel, style: TextStyle(color: settings.current().mainTextColor())),
subtitle: Text(AppLocalizations.of(context).descriptionBlockUnknownConnections),
value: settings.blockUnknownConnections,
value: settings.blockUnknownConnections,
onChanged: (bool value) {
if (value) {
settings.forbidUnknownConnections();

View File

@ -44,9 +44,9 @@ class _MessageViewState extends State<MessageView> {
}
void _debugResetContact() {
Provider.of<FlwtchState>(context, listen: false).cwtch.DebugResetContact(
Provider.of<ContactInfoState>(context, listen: false).profileOnion,
Provider.of<ContactInfoState>(context, listen: false).onion);
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.DebugResetContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion);
}
void _pushContactSettings() {
@ -69,13 +69,11 @@ class _MessageViewState extends State<MessageView> {
void _sendMessage([String ignoredParam]) {
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
Provider.of<FlwtchState>(context, listen:false).cwtch.SendMessage(
Provider.of<ContactInfoState>(context, listen:false).profileOnion,
Provider.of<ContactInfoState>(context, listen:false).onion,
jsonEncode(cm));
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
ctrlrCompose.clear();
focusNode.requestFocus();
Provider.of<ContactInfoState>(context, listen:false).totalMessages++;
Provider.of<ContactInfoState>(context, listen: false).totalMessages++;
}
Widget _buildComposeBox() {
@ -85,45 +83,52 @@ class _MessageViewState extends State<MessageView> {
padding: EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Expanded(child: TextField(
controller: ctrlrCompose,
focusNode: focusNode,
textInputAction: TextInputAction.send,
onSubmitted: _sendMessage,
Expanded(
child: TextField(
controller: ctrlrCompose,
focusNode: focusNode,
textInputAction: TextInputAction.send,
onSubmitted: _sendMessage,
)),
SizedBox(
width: 90,
height: 80,
child: Column(children: <Widget>[
Padding(padding: EdgeInsets.fromLTRB(2, 2, 2, 2), child: ElevatedButton(
child: Icon(Icons.send, color: Opaque.current().mainTextColor()),
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(86, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
),
onPressed: _sendMessage,
)),
Padding(
padding: EdgeInsets.fromLTRB(2, 2, 2, 2),
child: ElevatedButton(
child: Icon(Icons.send, color: Opaque.current().mainTextColor()),
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(86, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
),
onPressed: _sendMessage,
)),
Row(children: <Widget>[
Padding(padding: EdgeInsets.all(2), child: SizedBox(
width: 41,
child: ElevatedButton(
child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()),
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(41, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
),
onPressed: placeHolder,
))),
Padding(padding: EdgeInsets.all(2), child: SizedBox(
width: 41,
child: ElevatedButton(
child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()),
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(41, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
),
onPressed: placeHolder,
))),
Padding(
padding: EdgeInsets.all(2),
child: SizedBox(
width: 41,
child: ElevatedButton(
child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()),
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(41, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
),
onPressed: placeHolder,
))),
Padding(
padding: EdgeInsets.all(2),
child: SizedBox(
width: 41,
child: ElevatedButton(
child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()),
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(Size(41, 40)),
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
),
onPressed: placeHolder,
))),
])
]),
),

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_app/settings.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_app/widgets/profilerow.dart';
import 'package:provider/provider.dart';
@ -28,6 +29,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor(),
appBar: AppBar(
title: Text(AppLocalizations.of(context).titleManageProfiles),
actions: [

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_app/views/messageview.dart';
import 'package:flutter_app/widgets/profileimage.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../model.dart';
import '../opaque.dart';
import '../settings.dart';
class ContactRow extends StatefulWidget {
@override
@ -15,45 +17,58 @@ class _ContactRowState extends State<ContactRow> {
@override
Widget build(BuildContext context) {
var contact = Provider.of<ContactInfoState>(context);
return ListTile(
leading: SizedBox(
width: 60,
height: 60,
child: ClipOval(
child: SizedBox(
width: 60,
height: 60,
child: Container(
color: Colors.white,
width: 60,
height: 60,
child: Image(
image: AssetImage("assets/" + contact.imagePath),
width: 50,
height: 50,
))),
),
),
trailing: contact.isInvitation != null && contact.isInvitation
? Wrap(direction: Axis.vertical, children: <Widget>[
IconButton(padding: EdgeInsets.zero, iconSize: 16, icon: Icon(Icons.favorite, color: Opaque.current().mainTextColor()), onPressed: _btnApprove,),
IconButton(padding: EdgeInsets.zero, iconSize: 16, icon: Icon(Icons.delete, color: Opaque.current().mainTextColor()), onPressed: _btnReject,)])
: Text(contact.unreadMessages.toString()), //(nb: Icons.create is a pencil and we use it for "edit", not create)
title: Text(
contact.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
),
subtitle: Text(contact.status),
onTap: () {
setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen: false);
flwtch.setState(() => flwtch.selectedConversation = contact.onion);
// case 2/3 handled by Double/TripleColumnView respectively
if (flwtch.columns.length == 1) _pushMessageView(contact.onion);
});
},
);
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Padding(
padding: const EdgeInsets.all(2.0), //border size
child: ProfileImage(
diameter: 64.0,
imagePath: contact.imagePath,
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),
],
)),
Padding(
padding: const EdgeInsets.all(5.0),
child: contact.isInvitation != null && contact.isInvitation
? Wrap(direction: Axis.vertical, children: <Widget>[
IconButton(
padding: EdgeInsets.zero,
iconSize: 16,
icon: Icon(Icons.favorite, color: Opaque.current().mainTextColor()),
onPressed: _btnApprove,
),
IconButton(
padding: EdgeInsets.zero,
iconSize: 16,
icon: Icon(Icons.delete, color: Opaque.current().mainTextColor()),
onPressed: _btnReject,
)
])
: Text(contact.unreadMessages.toString()), //(nb: Icons.create is a pencil and we use it for "edit", not create)
),
]),
onTap: () {
setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen: false);
flwtch.setState(() => flwtch.selectedConversation = contact.onion);
// case 2/3 handled by Double/TripleColumnView respectively
if (flwtch.columns.length == 1) _pushMessageView(contact.onion);
});
},
));
}
void _pushMessageView(String handle) {
@ -72,16 +87,15 @@ class _ContactRowState extends State<ContactRow> {
);
}
void _btnApprove() {
Provider.of<FlwtchState>(context, listen: false).cwtch.AcceptContact(
Provider.of<ContactInfoState>(context, listen: false).profileOnion,
Provider.of<ContactInfoState>(context, listen: false).onion);
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.AcceptContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion);
}
void _btnReject() {
Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(
Provider.of<ContactInfoState>(context, listen: false).profileOnion,
Provider.of<ContactInfoState>(context, listen: false).onion);
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.BlockContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion);
}
}

View File

@ -29,7 +29,9 @@ class _MessageBubbleState extends State<MessageBubble> {
subtitle: Row(
children: [
Text(Provider.of<MessageState>(context).timestamp),
Provider.of<MessageState>(context).ackd ? Icon(Icons.check_circle_outline, color: Opaque.current().mainTextColor()) : Icon(Icons.hourglass_bottom_outlined, color: Opaque.current().mainTextColor())
Provider.of<MessageState>(context).ackd
? Icon(Icons.check_circle_outline, color: Opaque.current().mainTextColor())
: Icon(Icons.hourglass_bottom_outlined, color: Opaque.current().mainTextColor())
],
),
),

View File

@ -40,19 +40,22 @@ class _MessageListState extends State<MessageList> {
@override
Widget build(BuildContext outerContext) {
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom(false));
return Card(child: Scrollbar(
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: MessageBubble());
return ChangeNotifierProvider(
create: (_) => MessageState(
context: context,
profileOnion: Provider.of<ProfileInfoState>(outerContext).onion,
contactHandle: Provider.of<ContactInfoState>(outerContext).onion,
messageIndex: index,
),
child: MessageBubble());
},
),
));

View File

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_app/opaque.dart';
import 'package:provider/provider.dart';
import '../settings.dart';
class ProfileImage extends StatefulWidget {
ProfileImage({this.imagePath, this.diameter, this.border});
final double diameter;
final String imagePath;
final Color border;
@override
_ProfileImageState createState() => _ProfileImageState();
}
class _ProfileImageState extends State<ProfileImage> {
@override
Widget build(BuildContext context) {
return ClipOval(
clipBehavior: Clip.antiAlias,
child: Container(
width: widget.diameter,
height: widget.diameter,
color: widget.border,
child: Padding(
padding: const EdgeInsets.all(2.0), //border size
child: ClipOval(
clipBehavior: Clip.antiAlias,
child: Image(
image: AssetImage("assets/" + widget.imagePath),
filterQuality: FilterQuality.medium,
// We need some theme specific blending here...we might want to consider making this a theme level attribute
colorBlendMode: Provider.of<Settings>(context).theme == Opaque.dark ? BlendMode.softLight : BlendMode.darken,
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
isAntiAlias: true,
width: widget.diameter,
height: widget.diameter,
))),
));
}
}

View File

@ -3,6 +3,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter_app/views/addeditprofileview.dart';
import 'package:flutter_app/views/contactsview.dart';
import 'package:flutter_app/views/doublecolview.dart';
import 'package:flutter_app/widgets/profileimage.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -19,63 +20,66 @@ class _ProfileRowState extends State<ProfileRow> {
@override
Widget build(BuildContext context) {
var profile = Provider.of<ProfileInfoState>(context);
return ListTile(
leading: SizedBox(
width: 60,
height: 60,
child: ClipOval(
child: SizedBox(
width: 60,
height: 60,
child: Container(
color: Colors.white,
width: 60,
height: 60,
child: Image(
excludeFromSemantics: true,
image: AssetImage("assets/" + profile.imagePath),
width: 50,
height: 50,
))),
),
),
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(2.0), //border size
child: ProfileImage(
diameter: 64.0,
imagePath: profile.imagePath,
border: profile.isOnline ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor())),
Expanded(
child: Column(
children: [
Text(
profile.nickname,
semanticsLabel: profile.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
ExcludeSemantics(
child: Text(
profile.onion,
softWrap: true,
overflow: TextOverflow.ellipsis,
))
],
)),
IconButton(
enableFeedback: true,
tooltip: AppLocalizations.of(context).editProfile + " " + profile.nickname,
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
onPressed: () {
_pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath);
},
)
],
),
onTap: () {
setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen: false);
flwtch.cwtch.SelectProfile(profile.onion);
flwtch.setState(() {
flwtch.selectedProfile = profile;
flwtch.selectedConversation = "";
});
title: Text(
profile.nickname,
semanticsLabel: profile.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
),
subtitle: ExcludeSemantics(child: Text(profile.onion)),
trailing: IconButton(
enableFeedback: true,
tooltip: AppLocalizations.of(context).editProfile + " " + profile.nickname,
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
onPressed: () {
_pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath);
},
), //(nb: Icons.create is a pencil and we use it for "edit", not create)
onTap: () {
setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen: false);
flwtch.cwtch.SelectProfile(profile.onion);
flwtch.setState(() {
flwtch.selectedProfile = profile;
flwtch.selectedConversation = "";
});
switch (flwtch.columns.length) {
case 1:
_pushContactList(profile, false);
break;
case 2:
_pushContactList(profile, true);
break;
} // case 3: handled by TripleColumnView
});
},
);
switch (flwtch.columns.length) {
case 1:
_pushContactList(profile, false);
break;
case 2:
_pushContactList(profile, true);
break;
} // case 3: handled by TripleColumnView
});
},
));
}
void _pushContactList(ProfileInfoState profile, bool includeDoublePane) {