feat: Enhance roomlist context menu

This commit is contained in:
Christian Pauly 2020-10-02 15:50:59 +02:00
parent 41ceb84b47
commit 493b7000c6
3 changed files with 268 additions and 237 deletions

View File

@ -1,7 +1,6 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/chat.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:pedantic/pedantic.dart';
@ -18,11 +17,20 @@ import '../dialogs/send_file_dialog.dart';
class ChatListItem extends StatelessWidget {
final Room room;
final bool activeChat;
final bool selected;
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 {
if (onTap != null) return onTap();
if (!activeChat) {
if (room.membership == Membership.invite &&
await SimpleDialogs(context)
@ -94,19 +102,7 @@ class ChatListItem extends StatelessWidget {
}
}
Future<void> _toggleFavouriteRoom(BuildContext context) =>
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 {
Future<void> archiveAction(BuildContext context) async {
{
if ([Membership.leave, Membership.ban].contains(room.membership)) {
final success = await SimpleDialogs(context)
@ -117,163 +113,115 @@ class ChatListItem extends StatelessWidget {
return success;
}
final confirmed = await SimpleDialogs(context).askConfirmation();
if (!confirmed) {
return false;
}
final success = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(room.leave());
if (success == false) {
return false;
}
return true;
if (!confirmed) return;
await SimpleDialogs(context).tryRequestWithLoadingDialog(room.leave());
return;
}
}
@override
Widget build(BuildContext context) {
final isMuted = room.pushRuleState != PushRuleState.notify;
final slideableKey = GlobalKey();
return Slidable(
key: slideableKey,
secondaryActions: <Widget>[
if ([Membership.join, Membership.invite].contains(room.membership))
IconSlideAction(
caption: isMuted
? L10n.of(context).unmuteChat
: L10n.of(context).muteChat,
color: Colors.blueGrey,
icon:
isMuted ? Icons.notifications_active : Icons.notifications_off,
onTap: () => _toggleMuted(context),
),
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,
return Center(
child: Material(
color: chatListItemColor(context, activeChat, selected),
child: ListTile(
onLongPress: onLongPress,
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(
padding: const EdgeInsets.only(left: 4.0),
child: Icon(
Icons.favorite,
color: Colors.grey[400],
size: 16,
),
)
: Container(),
isMuted
? Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Icon(
Icons.notifications_off,
color: Colors.grey[400],
size: 16,
),
)
: Container(),
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Text(
room.timeCreated.localizedTimeShort(context),
style: TextStyle(
color: Color(0xFF555555),
fontSize: 13,
),
),
room.isFavourite
? Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Icon(
Icons.favorite,
color: Colors.grey[400],
size: 16,
),
)
: Container(),
isMuted
? Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Icon(
Icons.notifications_off,
color: Colors.grey[400],
size: 16,
),
)
: Container(),
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Text(
room.timeCreated.localizedTimeShort(context),
style: TextStyle(
color: Color(0xFF555555),
fontSize: 13,
),
),
),
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: room.membership == Membership.invite
? Text(
L10n.of(context).youAreInvitedToThisChat,
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,
),
),
),
SizedBox(width: 8),
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(' '),
],
),
onTap: () => clickAction(context),
),
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: room.membership == Membership.invite
? Text(
L10n.of(context).youAreInvitedToThisChat,
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,
),
),
),
SizedBox(width: 8),
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(' '),
],
),
onTap: () => clickAction(context),
),
),
);

View File

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

View File

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