feat: Enhance roomlist context menu

onboarding
Christian Pauly 5 years ago
parent bd0b2d4e56
commit dbef6b3975

@ -1,7 +1,6 @@
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/chat.dart'; import 'package:fluffychat/views/chat.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
@ -18,11 +17,20 @@ import '../dialogs/send_file_dialog.dart';
class ChatListItem extends StatelessWidget { class ChatListItem extends StatelessWidget {
final Room room; final Room room;
final bool activeChat; final bool activeChat;
final bool selected;
final Function onForget; final Function onForget;
final Function onTap;
final Function onLongPress;
const ChatListItem(this.room, {this.activeChat = false, this.onForget}); const ChatListItem(this.room,
{this.activeChat = false,
this.selected = false,
this.onTap,
this.onLongPress,
this.onForget});
void clickAction(BuildContext context) async { void clickAction(BuildContext context) async {
if (onTap != null) return onTap();
if (!activeChat) { if (!activeChat) {
if (room.membership == Membership.invite && if (room.membership == Membership.invite &&
await SimpleDialogs(context) await SimpleDialogs(context)
@ -94,19 +102,7 @@ class ChatListItem extends StatelessWidget {
} }
} }
Future<void> _toggleFavouriteRoom(BuildContext context) => Future<void> archiveAction(BuildContext context) async {
SimpleDialogs(context).tryRequestWithLoadingDialog(
room.setFavourite(!room.isFavourite),
);
Future<void> _toggleMuted(BuildContext context) =>
SimpleDialogs(context).tryRequestWithLoadingDialog(
room.setPushRuleState(room.pushRuleState == PushRuleState.notify
? PushRuleState.mentions_only
: PushRuleState.notify),
);
Future<bool> archiveAction(BuildContext context) async {
{ {
if ([Membership.leave, Membership.ban].contains(room.membership)) { if ([Membership.leave, Membership.ban].contains(room.membership)) {
final success = await SimpleDialogs(context) final success = await SimpleDialogs(context)
@ -117,163 +113,115 @@ class ChatListItem extends StatelessWidget {
return success; return success;
} }
final confirmed = await SimpleDialogs(context).askConfirmation(); final confirmed = await SimpleDialogs(context).askConfirmation();
if (!confirmed) { if (!confirmed) return;
return false; await SimpleDialogs(context).tryRequestWithLoadingDialog(room.leave());
} return;
final success = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(room.leave());
if (success == false) {
return false;
}
return true;
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isMuted = room.pushRuleState != PushRuleState.notify; final isMuted = room.pushRuleState != PushRuleState.notify;
final slideableKey = GlobalKey(); return Center(
return Slidable( child: Material(
key: slideableKey, color: chatListItemColor(context, activeChat, selected),
secondaryActions: <Widget>[ child: ListTile(
if ([Membership.join, Membership.invite].contains(room.membership)) onLongPress: onLongPress,
IconSlideAction( leading: Avatar(room.avatar, room.displayname),
caption: isMuted title: Row(
? L10n.of(context).unmuteChat children: <Widget>[
: L10n.of(context).muteChat, Expanded(
color: Colors.blueGrey, child: Text(
icon: room.getLocalizedDisplayname(L10n.of(context)),
isMuted ? Icons.notifications_active : Icons.notifications_off, maxLines: 1,
onTap: () => _toggleMuted(context), overflow: TextOverflow.ellipsis,
), softWrap: false,
if ([Membership.join, Membership.invite].contains(room.membership))
IconSlideAction(
caption: room.isFavourite
? L10n.of(context).unpin
: L10n.of(context).pin,
color: Colors.blue,
icon: room.isFavourite ? Icons.favorite_border : Icons.favorite,
onTap: () => _toggleFavouriteRoom(context),
),
if ([Membership.join, Membership.invite].contains(room.membership))
IconSlideAction(
caption: L10n.of(context).leave,
color: Colors.red,
icon: Icons.archive,
onTap: () => archiveAction(context),
),
if ([Membership.leave, Membership.ban].contains(room.membership))
IconSlideAction(
caption: L10n.of(context).delete,
color: Colors.red,
icon: Icons.delete_forever,
onTap: () => archiveAction(context),
),
],
actionPane: SlidableDrawerActionPane(),
child: Center(
child: Material(
color: chatListItemColor(context, activeChat),
child: ListTile(
onLongPress: () => (slideableKey.currentState as SlidableState)
.open(actionType: SlideActionType.secondary),
leading: Avatar(room.avatar, room.displayname),
title: Row(
children: <Widget>[
Expanded(
child: Text(
room.getLocalizedDisplayname(L10n.of(context)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
),
), ),
room.isFavourite ),
? Padding( room.isFavourite
padding: const EdgeInsets.only(left: 4.0), ? Padding(
child: Icon( padding: const EdgeInsets.only(left: 4.0),
Icons.favorite, child: Icon(
color: Colors.grey[400], Icons.favorite,
size: 16, color: Colors.grey[400],
), size: 16,
) ),
: Container(), )
isMuted : Container(),
? Padding( isMuted
padding: const EdgeInsets.only(left: 4.0), ? Padding(
child: Icon( padding: const EdgeInsets.only(left: 4.0),
Icons.notifications_off, child: Icon(
color: Colors.grey[400], Icons.notifications_off,
size: 16, color: Colors.grey[400],
), size: 16,
) ),
: Container(), )
Padding( : Container(),
padding: const EdgeInsets.only(left: 4.0), Padding(
child: Text( padding: const EdgeInsets.only(left: 4.0),
room.timeCreated.localizedTimeShort(context), child: Text(
style: TextStyle( room.timeCreated.localizedTimeShort(context),
color: Color(0xFF555555), style: TextStyle(
fontSize: 13, color: Color(0xFF555555),
), fontSize: 13,
), ),
), ),
], ),
), ],
subtitle: Row( ),
mainAxisAlignment: MainAxisAlignment.center, subtitle: Row(
children: <Widget>[ mainAxisAlignment: MainAxisAlignment.center,
Expanded( children: <Widget>[
child: room.membership == Membership.invite Expanded(
? Text( child: room.membership == Membership.invite
L10n.of(context).youAreInvitedToThisChat, ? Text(
style: TextStyle( L10n.of(context).youAreInvitedToThisChat,
color: Theme.of(context).primaryColor, style: TextStyle(
), color: Theme.of(context).primaryColor,
softWrap: false,
)
: Text(
room.lastEvent?.getLocalizedBody(
L10n.of(context),
withSenderNamePrefix: !room.isDirectChat ||
room.lastEvent.senderId ==
room.client.userID,
hideReply: true,
) ??
'',
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: room.lastEvent?.redacted == true
? TextDecoration.lineThrough
: null,
),
), ),
), softWrap: false,
SizedBox(width: 8), )
room.notificationCount > 0 : Text(
? Container( room.lastEvent?.getLocalizedBody(
padding: EdgeInsets.symmetric(horizontal: 5), L10n.of(context),
height: 20, withSenderNamePrefix: !room.isDirectChat ||
decoration: BoxDecoration( room.lastEvent.senderId == room.client.userID,
color: room.highlightCount > 0 hideReply: true,
? Colors.red ) ??
: Theme.of(context).primaryColor, '',
borderRadius: BorderRadius.circular(20), softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: room.lastEvent?.redacted == true
? TextDecoration.lineThrough
: null,
), ),
child: Center( ),
child: Text( ),
room.notificationCount.toString(), SizedBox(width: 8),
style: TextStyle(color: Colors.white), room.notificationCount > 0
), ? Container(
padding: EdgeInsets.symmetric(horizontal: 5),
height: 20,
decoration: BoxDecoration(
color: room.highlightCount > 0
? Colors.red
: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(20),
),
child: Center(
child: Text(
room.notificationCount.toString(),
style: TextStyle(color: Colors.white),
), ),
) ),
: Text(' '), )
], : Text(' '),
), ],
onTap: () => clickAction(context),
), ),
onTap: () => clickAction(context),
), ),
), ),
); );

@ -112,18 +112,20 @@ final ThemeData amoledTheme = ThemeData.dark().copyWith(
), ),
); );
Color chatListItemColor(BuildContext context, bool activeChat) => Color chatListItemColor(BuildContext context, bool activeChat, bool selected) =>
Theme.of(context).brightness == Brightness.light selected
? activeChat ? Theme.of(context).primaryColor.withAlpha(50)
? Color(0xFFE8E8E8) : Theme.of(context).brightness == Brightness.light
: Colors.white ? activeChat
: activeChat ? Color(0xFFE8E8E8)
? ThemeSwitcherWidget.of(context).amoledEnabled : Colors.white
? Color(0xff121212) : activeChat
: Colors.black ? ThemeSwitcherWidget.of(context).amoledEnabled
: ThemeSwitcherWidget.of(context).amoledEnabled ? Color(0xff121212)
? Colors.black : Colors.black
: Color(0xff121212); : ThemeSwitcherWidget.of(context).amoledEnabled
? Colors.black
: Color(0xff121212);
Color blackWhiteColor(BuildContext context) => Color blackWhiteColor(BuildContext context) =>
Theme.of(context).brightness == Brightness.light Theme.of(context).brightness == Brightness.light

@ -25,7 +25,7 @@ import 'new_group.dart';
import 'new_private_chat.dart'; import 'new_private_chat.dart';
import 'settings.dart'; import 'settings.dart';
enum SelectMode { normal, share } enum SelectMode { normal, share, select }
class ChatListView extends StatelessWidget { class ChatListView extends StatelessWidget {
@override @override
@ -59,9 +59,15 @@ class _ChatListState extends State<ChatList> {
PublicRoomsResponse publicRoomsResponse; PublicRoomsResponse publicRoomsResponse;
bool loadingPublicRooms = false; bool loadingPublicRooms = false;
String searchServer; String searchServer;
final _selectedRoomIds = <String>{};
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
void _toggleSelection(String roomId) =>
setState(() => _selectedRoomIds.contains(roomId)
? _selectedRoomIds.remove(roomId)
: _selectedRoomIds.add(roomId));
Future<void> waitForFirstSync(BuildContext context) async { Future<void> waitForFirstSync(BuildContext context) async {
var client = Matrix.of(context).client; var client = Matrix.of(context).client;
if (client.prevBatch?.isEmpty ?? true) { if (client.prevBatch?.isEmpty ?? true) {
@ -215,6 +221,39 @@ class _ChatListState extends State<ChatList> {
super.dispose(); super.dispose();
} }
Future<void> _toggleFavouriteRoom(BuildContext context) {
final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
return SimpleDialogs(context).tryRequestWithLoadingDialog(
room.setFavourite(!room.isFavourite),
);
}
Future<void> _toggleMuted(BuildContext context) {
final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
return SimpleDialogs(context).tryRequestWithLoadingDialog(
room.setPushRuleState(room.pushRuleState == PushRuleState.notify
? PushRuleState.mentions_only
: PushRuleState.notify),
);
}
Future<void> _archiveAction(BuildContext context) async {
final confirmed = await SimpleDialogs(context).askConfirmation();
if (!confirmed) return;
await SimpleDialogs(context)
.tryRequestWithLoadingDialog(_archiveSelectedRooms(context));
setState(() => null);
}
Future<void> _archiveSelectedRooms(BuildContext context) async {
final client = Matrix.of(context).client;
while (_selectedRoomIds.isNotEmpty) {
final roomId = _selectedRoomIds.first;
await client.getRoomById(roomId).leave();
_selectedRoomIds.remove(roomId);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder<LoginState>( return StreamBuilder<LoginState>(
@ -232,10 +271,15 @@ class _ChatListState extends State<ChatList> {
stream: Matrix.of(context).onShareContentChanged.stream, stream: Matrix.of(context).onShareContentChanged.stream,
builder: (context, snapshot) { builder: (context, snapshot) {
final selectMode = Matrix.of(context).shareContent == null final selectMode = Matrix.of(context).shareContent == null
? SelectMode.normal ? _selectedRoomIds.isEmpty
? SelectMode.normal
: SelectMode.select
: SelectMode.share; : SelectMode.share;
if (selectMode == SelectMode.share) {
_selectedRoomIds.clear();
}
return Scaffold( return Scaffold(
drawer: selectMode == SelectMode.share drawer: selectMode != SelectMode.normal
? null ? null
: Drawer( : Drawer(
child: SafeArea( child: SafeArea(
@ -290,54 +334,81 @@ class _ChatListState extends State<ChatList> {
), ),
), ),
appBar: AppBar( appBar: AppBar(
centerTitle: false,
elevation: _scrolledToTop ? 0 : null, elevation: _scrolledToTop ? 0 : null,
leading: selectMode != SelectMode.share leading: selectMode == SelectMode.share
? null ? IconButton(
: IconButton(
icon: Icon(Icons.close), icon: Icon(Icons.close),
onPressed: () => onPressed: () =>
Matrix.of(context).shareContent = null, Matrix.of(context).shareContent = null,
), )
: selectMode == SelectMode.select
? IconButton(
icon: Icon(Icons.close),
onPressed: () =>
setState(_selectedRoomIds.clear),
)
: null,
titleSpacing: 0, titleSpacing: 0,
actions: selectMode != SelectMode.select
? null
: [
if (_selectedRoomIds.length == 1)
IconButton(
icon: Icon(Icons.favorite_border_outlined),
onPressed: () => _toggleFavouriteRoom(context),
),
if (_selectedRoomIds.length == 1)
IconButton(
icon: Icon(Icons.notifications_none),
onPressed: () => _toggleMuted(context),
),
IconButton(
icon: Icon(Icons.archive),
onPressed: () => _archiveAction(context),
),
],
title: selectMode == SelectMode.share title: selectMode == SelectMode.share
? Text(L10n.of(context).share) ? Text(L10n.of(context).share)
: Container( : selectMode == SelectMode.select
height: 40, ? Text(_selectedRoomIds.length.toString())
padding: EdgeInsets.only(right: 8), : Container(
child: Material( height: 40,
color: Theme.of(context).secondaryHeaderColor, padding: EdgeInsets.only(right: 8),
borderRadius: BorderRadius.circular(32), child: Material(
child: TextField( color: Theme.of(context).secondaryHeaderColor,
autocorrect: false, borderRadius: BorderRadius.circular(32),
controller: searchController, child: TextField(
focusNode: _searchFocusNode, autocorrect: false,
decoration: InputDecoration( controller: searchController,
contentPadding: EdgeInsets.only( focusNode: _searchFocusNode,
top: 8, decoration: InputDecoration(
bottom: 8, contentPadding: EdgeInsets.only(
left: 16, top: 8,
), bottom: 8,
border: OutlineInputBorder( left: 16,
borderRadius: BorderRadius.circular(32), ),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(32),
),
hintText: L10n.of(context).searchForAChat,
suffixIcon: searchMode
? IconButton(
icon: Icon(Icons.backspace),
onPressed: () => setState(() {
searchController.clear();
_searchFocusNode.unfocus();
}),
)
: null,
),
), ),
hintText: L10n.of(context).searchForAChat,
suffixIcon: searchMode
? IconButton(
icon: Icon(Icons.backspace),
onPressed: () => setState(() {
searchController.clear();
_searchFocusNode.unfocus();
}),
)
: null,
), ),
), ),
),
),
), ),
floatingActionButton: floatingActionButton:
(AdaptivePageLayout.columnMode(context) || (AdaptivePageLayout.columnMode(context) ||
selectMode == SelectMode.share) selectMode != SelectMode.normal)
? null ? null
: FloatingActionButton( : FloatingActionButton(
child: Icon(Icons.add), child: Icon(Icons.add),
@ -436,34 +507,32 @@ class _ChatListState extends State<ChatList> {
itemBuilder: itemBuilder:
(BuildContext context, int i) { (BuildContext context, int i) {
if (i == 0) { if (i == 0) {
final displayPresences = directChats
.isNotEmpty &&
selectMode == SelectMode.normal;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
(directChats.isEmpty || AnimatedContainer(
selectMode == duration: Duration(
SelectMode.share) milliseconds: 500),
? Container() height:
: PreferredSize( displayPresences ? 78 : 0,
preferredSize: child: !displayPresences
Size.fromHeight(82), ? null
child: Container( : ListView.builder(
height: 78, scrollDirection:
child: Axis.horizontal,
ListView.builder( itemCount: directChats
scrollDirection: .length,
Axis.horizontal, itemBuilder: (BuildContext
itemCount: context,
directChats int i) =>
.length, PresenceListItem(
itemBuilder: (BuildContext directChats[
context, i]),
int i) =>
PresenceListItem(
directChats[
i]),
),
), ),
), ),
], ],
); );
} }
@ -471,6 +540,18 @@ class _ChatListState extends State<ChatList> {
return i < rooms.length return i < rooms.length
? ChatListItem( ? ChatListItem(
rooms[i], rooms[i],
selected: _selectedRoomIds
.contains(rooms[i].id),
onTap: selectMode ==
SelectMode.select
? () => _toggleSelection(
rooms[i].id)
: null,
onLongPress: selectMode !=
SelectMode.share
? () => _toggleSelection(
rooms[i].id)
: null,
activeChat: activeChat:
widget.activeChat == widget.activeChat ==
rooms[i].id, rooms[i].id,

Loading…
Cancel
Save