Merge branch 'main' into 481-use-forked-matrix-sdk

pull/1384/head
ggurdin 1 year ago committed by GitHub
commit e304bb5aa4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -599,12 +599,23 @@ class ChatListController extends State<ChatList>
super.dispose(); super.dispose();
} }
// #Pangea
final StreamController<String> selectionsStream =
StreamController.broadcast();
// Pangea#
void toggleSelection(String roomId) { void toggleSelection(String roomId) {
setState( // #Pangea
() => selectedRoomIds.contains(roomId) // setState(
? selectedRoomIds.remove(roomId) // () => selectedRoomIds.contains(roomId)
: selectedRoomIds.add(roomId), // ? selectedRoomIds.remove(roomId)
); // : selectedRoomIds.add(roomId),
// );
selectedRoomIds.contains(roomId)
? selectedRoomIds.remove(roomId)
: selectedRoomIds.add(roomId);
selectionsStream.add(roomId);
// Pangea#
} }
Future<void> toggleUnread() async { Future<void> toggleUnread() async {
@ -676,8 +687,8 @@ class ChatListController extends State<ChatList>
context: context, context: context,
future: () => _archiveSelectedRooms(), future: () => _archiveSelectedRooms(),
); );
setState(() {});
// #Pangea // #Pangea
// setState(() {});
if (archivedActiveRoom) { if (archivedActiveRoom) {
context.go('/rooms'); context.go('/rooms');
} }
@ -709,7 +720,6 @@ class ChatListController extends State<ChatList>
context: context, context: context,
future: () => _leaveSelectedRooms(onlyAdmin), future: () => _leaveSelectedRooms(onlyAdmin),
); );
setState(() {});
if (leftActiveRoom) { if (leftActiveRoom) {
context.go('/rooms'); context.go('/rooms');
} }
@ -832,8 +842,7 @@ class ChatListController extends State<ChatList>
label: space.nameIncludingParents(context), label: space.nameIncludingParents(context),
// If user is not admin of space, button is grayed out // If user is not admin of space, button is grayed out
textStyle: TextStyle( textStyle: TextStyle(
color: (firstSelectedRoom == null || color: (firstSelectedRoom == null)
(firstSelectedRoom.isSpace && !space.isRoomAdmin))
? Theme.of(context).colorScheme.outline ? Theme.of(context).colorScheme.outline
: Theme.of(context).colorScheme.surfaceTint, : Theme.of(context).colorScheme.surfaceTint,
), ),
@ -851,10 +860,6 @@ class ChatListController extends State<ChatList>
if (firstSelectedRoom == null) { if (firstSelectedRoom == null) {
throw L10n.of(context)!.nonexistentSelection; throw L10n.of(context)!.nonexistentSelection;
} }
// If user is not admin of the would-be parent space, does not allow
if (firstSelectedRoom.isSpace && !space.isRoomAdmin) {
throw L10n.of(context)!.cantAddSpaceChild;
}
if (space.canSendDefaultStates) { if (space.canSendDefaultStates) {
for (final roomId in selectedRoomIds) { for (final roomId in selectedRoomIds) {
@ -876,7 +881,12 @@ class ChatListController extends State<ChatList>
); );
} }
setState(() => selectedRoomIds.clear()); // #Pangea
// setState(() => selectedRoomIds.clear());
if (firstSelectedRoom != null) {
toggleSelection(firstSelectedRoom.id);
}
// Pangea#
} }
bool get anySelectedRoomNotMarkedUnread => selectedRoomIds.any( bool get anySelectedRoomNotMarkedUnread => selectedRoomIds.any(
@ -946,7 +956,12 @@ class ChatListController extends State<ChatList>
if (selectMode == SelectMode.share) { if (selectMode == SelectMode.share) {
setState(() => Matrix.of(context).shareContent = null); setState(() => Matrix.of(context).shareContent = null);
} else { } else {
setState(() => selectedRoomIds.clear()); // #Pangea
// setState(() => selectedRoomIds.clear());
for (final roomId in selectedRoomIds.toList()) {
toggleSelection(roomId);
}
// Pangea#
} }
} }

@ -1,11 +1,11 @@
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart';
import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart';
import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_body_text.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_body_text.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart';
import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/avatar.dart';
@ -17,7 +17,6 @@ import 'package:matrix/matrix.dart';
import '../../config/themes.dart'; import '../../config/themes.dart';
import '../../widgets/connection_status_header.dart'; import '../../widgets/connection_status_header.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
class ChatListViewBody extends StatelessWidget { class ChatListViewBody extends StatelessWidget {
final ChatListController controller; final ChatListController controller;
@ -76,7 +75,10 @@ class ChatListViewBody extends StatelessWidget {
child: CustomScrollView( child: CustomScrollView(
controller: controller.scrollController, controller: controller.scrollController,
slivers: [ slivers: [
ChatListHeader(controller: controller), // #Pangea
// ChatListHeader(controller: controller),
ChatListHeaderWrapper(controller: controller),
// Pangea#
SliverList( SliverList(
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
[ [
@ -247,17 +249,23 @@ class ChatListViewBody extends StatelessWidget {
SliverList.builder( SliverList.builder(
itemCount: rooms.length, itemCount: rooms.length,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
return ChatListItem( // #Pangea
// return ChatListItem(
return ChatListItemWrapper(
controller: controller,
// Pangea#
rooms[i], rooms[i],
key: Key('chat_list_item_${rooms[i].id}'), key: Key('chat_list_item_${rooms[i].id}'),
filter: filter, filter: filter,
selected: // #Pangea
controller.selectedRoomIds.contains(rooms[i].id), // selected:
onTap: controller.selectMode == SelectMode.select // controller.selectedRoomIds.contains(rooms[i].id),
? () => controller.toggleSelection(rooms[i].id) // onTap: controller.selectMode == SelectMode.select
: () => onChatTap(rooms[i], context), // ? () => controller.toggleSelection(rooms[i].id)
onLongPress: () => // : () => onChatTap(rooms[i], context),
controller.toggleSelection(rooms[i].id), // onLongPress: () =>
// controller.toggleSelection(rooms[i].id),
// Pangea#
activeChat: controller.activeChat == rooms[i].id, activeChat: controller.activeChat == rooms[i].id,
); );
}, },

@ -4,7 +4,6 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart';
import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart'; import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart';
import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart';
@ -12,6 +11,8 @@ import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart'; import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/avatar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -23,7 +24,6 @@ import 'package:matrix/matrix.dart';
import '../../utils/localized_exception_extension.dart'; import '../../utils/localized_exception_extension.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
class SpaceView extends StatefulWidget { class SpaceView extends StatefulWidget {
final ChatListController controller; final ChatListController controller;
@ -709,7 +709,10 @@ class _SpaceViewState extends State<SpaceView> {
child: CustomScrollView( child: CustomScrollView(
controller: widget.scrollController, controller: widget.scrollController,
slivers: [ slivers: [
ChatListHeader(controller: widget.controller), // #Pangea
// ChatListHeader(controller: widget.controller),
ChatListHeaderWrapper(controller: widget.controller),
// Pangea#
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, i) { (context, i) {
@ -789,7 +792,13 @@ class _SpaceViewState extends State<SpaceView> {
child: CustomScrollView( child: CustomScrollView(
controller: widget.scrollController, controller: widget.scrollController,
slivers: [ slivers: [
ChatListHeader(controller: widget.controller, globalSearch: false), // #Pangea
// ChatListHeader(controller: widget.controller, globalSearch: false),
ChatListHeaderWrapper(
controller: widget.controller,
globalSearch: false,
),
// Pangea#
SliverAppBar( SliverAppBar(
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
primary: false, primary: false,
@ -911,7 +920,11 @@ class _SpaceViewState extends State<SpaceView> {
room.membership != Membership.leave room.membership != Membership.leave
// Pangea# // Pangea#
) { ) {
return ChatListItem( // #Pangea
// return ChatListItem(
return ChatListItemWrapper(
controller: widget.controller,
// Pangea#
room, room,
onLongPress: () => onLongPress: () =>
_onSpaceChildContextMenu(spaceChild, room), _onSpaceChildContextMenu(spaceChild, room),

@ -131,7 +131,8 @@ extension ChildrenAndParentsRoomExtension on Room {
spaceMode = child?.isSpace ?? spaceMode; spaceMode = child?.isSpace ?? spaceMode;
// get the bool for adding chats to spaces // get the bool for adding chats to spaces
final bool canAddChild = _canIAddSpaceChild(child, spaceMode: spaceMode); final bool canAddChild =
(child?.isRoomAdmin ?? true) && canSendEvent(EventTypes.SpaceChild);
if (!spaceMode) return canAddChild; if (!spaceMode) return canAddChild;
// if adding space to a space, check if the child is an ancestor // if adding space to a space, check if the child is an ancestor

@ -306,10 +306,6 @@ extension PangeaRoom on Room {
bool get canDelete => _canDelete; bool get canDelete => _canDelete;
bool canIAddSpaceChild(Room? room, {bool spaceMode = false}) {
return _canIAddSpaceChild(room, spaceMode: spaceMode);
}
bool get canIAddSpaceParents => _canIAddSpaceParents; bool get canIAddSpaceParents => _canIAddSpaceParents;
bool pangeaCanSendEvent(String eventType) => _pangeaCanSendEvent(eventType); bool pangeaCanSendEvent(String eventType) => _pangeaCanSendEvent(eventType);

@ -54,20 +54,20 @@ extension AnalyticsRoomExtension on Room {
return Future.value(); return Future.value();
} }
if (!canSendEvent(EventTypes.SpaceChild)) return;
if (spaceChildren.any((sc) => sc.roomId == analyticsRoom.id)) return; if (spaceChildren.any((sc) => sc.roomId == analyticsRoom.id)) return;
if (canIAddSpaceChild(null)) {
try { try {
await setSpaceChild(analyticsRoom.id); await setSpaceChild(analyticsRoom.id);
} catch (err) { } catch (err) {
debugPrint( debugPrint(
"Failed to add analytics room ${analyticsRoom.id} for student to space $id", "Failed to add analytics room ${analyticsRoom.id} for student to space $id",
); );
Sentry.addBreadcrumb( Sentry.addBreadcrumb(
Breadcrumb( Breadcrumb(
message: "Failed to add analytics room to space $id", message: "Failed to add analytics room to space $id",
), ),
); );
}
} }
} }

@ -78,36 +78,10 @@ extension UserPermissionsRoomExtension on Room {
bool get _canDelete => isSpaceAdmin; bool get _canDelete => isSpaceAdmin;
bool _canIAddSpaceChild(Room? room, {bool spaceMode = false}) {
if (!isSpace) {
ErrorHandler.logError(
m: "should not call canIAddSpaceChildren on non-space room. Room id: $id",
data: toJson(),
s: StackTrace.current,
);
return false;
}
final isSpaceAdmin = isRoomAdmin;
final isChildRoomAdmin = room?.isRoomAdmin ?? true;
// if user is not admin of child room, return false
if (!isChildRoomAdmin) return false;
// if the child room is a space, or will be a space,
// then the user must be an admin of the parent space
if (room?.isSpace ?? spaceMode) return isSpaceAdmin;
// otherwise, the user can add the child room to the parent
// if they're the admin of the parent or if the parent creation
// of group chats
return isSpaceAdmin || (pangeaRoomRules?.isCreateRooms ?? false);
}
bool get _canIAddSpaceParents => bool get _canIAddSpaceParents =>
_isRoomAdmin || pangeaCanSendEvent(EventTypes.SpaceParent); _isRoomAdmin || pangeaCanSendEvent(EventTypes.SpaceParent);
//overriding the default canSendEvent to check power levels // Overriding the default canSendEvent to check power levels
bool _pangeaCanSendEvent(String eventType) { bool _pangeaCanSendEvent(String eventType) {
final powerLevelsMap = getState(EventTypes.RoomPowerLevels)?.content; final powerLevelsMap = getState(EventTypes.RoomPowerLevels)?.content;
if (powerLevelsMap == null) return 0 <= ownPowerLevel; if (powerLevelsMap == null) return 0 <= ownPowerLevel;

@ -10,7 +10,6 @@ import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator
import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
class MessageTranslationCard extends StatefulWidget { class MessageTranslationCard extends StatefulWidget {
final PangeaMessageEvent messageEvent; final PangeaMessageEvent messageEvent;
@ -133,6 +132,22 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
setState(() {}); setState(() {});
} }
/// Show warning if message's language code is user's L1
/// or if translated text is same as original text.
/// Warning does not show if was previously closed
bool get showWarning {
if (MatrixState.pangeaController.instructions.wereInstructionsTurnedOff(
InlineInstructions.l1Translation.toString(),
)) return false;
final bool isWrittenInL1 =
l1Code != null && widget.messageEvent.originalSent?.langCode == l1Code;
final bool isTextIdentical = selectionTranslation != null &&
widget.messageEvent.originalSent?.text == selectionTranslation;
return isWrittenInL1 || isTextIdentical;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!_fetchingRepresentation && if (!_fetchingRepresentation &&
@ -141,19 +156,6 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
return const CardErrorWidget(); return const CardErrorWidget();
} }
// Show warning if message's language code is user's L1
// or if translated text is same as original text
// Warning does not show if was previously closed
final bool showWarning = widget.messageEvent.originalSent != null &&
((!widget.immersionMode &&
widget.messageEvent.originalSent!.langCode.equals(l1Code)) ||
(selectionTranslation == null ||
widget.messageEvent.originalSent!.text
.equals(selectionTranslation))) &&
!MatrixState.pangeaController.instructions.wereInstructionsTurnedOff(
InlineInstructions.l1Translation.toString(),
);
return Container( return Container(
child: _fetchingRepresentation child: _fetchingRepresentation
? const ToolbarContentLoadingIndicator() ? const ToolbarContentLoadingIndicator()

@ -0,0 +1,47 @@
import 'dart:async';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_header.dart';
import 'package:flutter/material.dart';
/// A wrapper around ChatListHeader to allow rebuilding on state changes.
/// Prevents having to rebuild the entire ChatList when a single item changes.
class ChatListHeaderWrapper extends StatefulWidget {
final ChatListController controller;
final bool globalSearch;
const ChatListHeaderWrapper({
super.key,
required this.controller,
this.globalSearch = true,
});
@override
ChatListHeaderWrapperState createState() => ChatListHeaderWrapperState();
}
class ChatListHeaderWrapperState extends State<ChatListHeaderWrapper> {
StreamSubscription? stateSub;
@override
void initState() {
super.initState();
stateSub = widget.controller.selectionsStream.stream.listen((roomID) {
setState(() {});
});
}
@override
void dispose() {
stateSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChatListHeader(
controller: widget.controller,
globalSearch: widget.globalSearch,
);
}
}

@ -0,0 +1,71 @@
import 'dart:async';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
/// A wrapper around ChatListItem to allow rebuilding on state changes.
/// Prevents having to rebuild the entire ChatList when a single item changes.
class ChatListItemWrapper extends StatefulWidget {
final Room room;
final bool activeChat;
final void Function()? onForget;
final String? filter;
final ChatListController controller;
final void Function()? onLongPress;
final void Function()? onTap;
const ChatListItemWrapper(
this.room, {
this.activeChat = false,
this.onForget,
this.filter,
required this.controller,
this.onLongPress,
this.onTap,
super.key,
});
@override
ChatListItemWrapperState createState() => ChatListItemWrapperState();
}
class ChatListItemWrapperState extends State<ChatListItemWrapper> {
StreamSubscription? stateSub;
@override
void initState() {
super.initState();
stateSub = widget.controller.selectionsStream.stream.listen((roomID) {
if (roomID == widget.room.id) {
setState(() {});
}
});
}
@override
void dispose() {
stateSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChatListItem(
widget.room,
activeChat: widget.activeChat,
selected: widget.controller.selectedRoomIds.contains(widget.room.id),
onTap: widget.onTap ??
(widget.controller.selectMode == SelectMode.select
? () => widget.controller.toggleSelection(widget.room.id)
: () => onChatTap(widget.room, context)),
onLongPress: widget.onLongPress ??
() => widget.controller.toggleSelection(widget.room.id),
onForget: widget.onForget,
filter: widget.filter,
);
}
}

@ -76,18 +76,6 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
) )
: null; : null;
if (widget.activeSpaceId != null) {
final activeSpace =
Matrix.of(context).client.getRoomById(widget.activeSpaceId!);
if (activeSpace != null && activeSpace.canIAddSpaceChild(null)) {
parent = activeSpace;
} else {
ErrorHandler.logError(
e: Exception('activeSpaceId ${widget.activeSpaceId} not found'),
);
}
}
//sort possibleParents //sort possibleParents
//if possibleParent in parents, put first //if possibleParent in parents, put first
//use sort but use any instead of contains because contains uses == and we want to compare by id //use sort but use any instead of contains because contains uses == and we want to compare by id
@ -102,6 +90,20 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
}); });
isOpen = widget.startOpen; isOpen = widget.startOpen;
if (widget.activeSpaceId != null) {
final activeSpace =
Matrix.of(context).client.getRoomById(widget.activeSpaceId!);
if (activeSpace == null) {
ErrorHandler.logError(
e: Exception('activeSpaceId ${widget.activeSpaceId} not found'),
);
return;
}
if (activeSpace.canSendEvent(EventTypes.SpaceChild)) {
parent = activeSpace;
}
}
} }
Future<void> _addSingleSpace(String roomToAddId, Room newParent) async { Future<void> _addSingleSpace(String roomToAddId, Room newParent) async {

Loading…
Cancel
Save