Merge branch 'main' of https://github.com/pangeachat/client into move-chat-buttons

pull/1384/head
Kelrap 1 year ago
commit 276f3ee913

@ -4111,5 +4111,7 @@
"deleteSubscriptionWarningBody": "Deleting your account will not automatically cancel your subscription.", "deleteSubscriptionWarningBody": "Deleting your account will not automatically cancel your subscription.",
"manageSubscription": "Manage Subscription", "manageSubscription": "Manage Subscription",
"createSpace": "Create space", "createSpace": "Create space",
"createChat": "Create chat" "createChat": "Create chat",
"error520Title": "Please try again.",
"error520Desc": "Sorry, we could not understand your message..."
} }

@ -298,7 +298,10 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
final statusText = this.statusText ??= _durationString ?? '00:00'; final statusText = this.statusText ??= _durationString ?? '00:00';
final audioPlayer = this.audioPlayer; final audioPlayer = this.audioPlayer;
return Padding( return Padding(
padding: const EdgeInsets.all(12.0), // #Pangea
// padding: const EdgeInsets.all(12.0),
padding: const EdgeInsets.all(5.0),
// Pangea#
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
@ -332,7 +335,10 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
}, },
), ),
), ),
const SizedBox(width: 8), // #Pangea
// const SizedBox(width: 8),
const SizedBox(width: 5),
// Pangea#
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -368,7 +374,10 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
), ),
], ],
), ),
const SizedBox(width: 8), // #Pangea
// const SizedBox(width: 8),
const SizedBox(width: 5),
// Pangea#
SizedBox( SizedBox(
width: 36, width: 36,
child: Text( child: Text(

@ -314,8 +314,9 @@ class Message extends StatelessWidget {
padding: const EdgeInsets.only(left: 8), padding: const EdgeInsets.only(left: 8),
child: GestureDetector( child: GestureDetector(
// #Pangea // #Pangea
onTap: () => onTap: () => toolbarController?.showToolbar(
toolbarController?.showToolbar(context), context,
),
onDoubleTap: () => onDoubleTap: () =>
toolbarController?.showToolbar(context), toolbarController?.showToolbar(context),
// Pangea# // Pangea#
@ -585,7 +586,9 @@ class Message extends StatelessWidget {
: MainAxisAlignment.start, : MainAxisAlignment.start,
children: [ children: [
if (pangeaMessageEvent?.showMessageButtons ?? false) if (pangeaMessageEvent?.showMessageButtons ?? false)
MessageButtons(toolbarController: toolbarController), MessageButtons(
toolbarController: toolbarController,
),
MessageReactions(event, timeline), MessageReactions(event, timeline),
], ],
), ),

@ -1,21 +1,20 @@
import 'dart:io'; import 'dart:io';
import 'package:chewie/chewie.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/events/image_bubble.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/blur_hash.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/events/image_bubble.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/blur_hash.dart';
import '../../../utils/error_reporter.dart'; import '../../../utils/error_reporter.dart';
class EventVideoPlayer extends StatefulWidget { class EventVideoPlayer extends StatefulWidget {
@ -71,7 +70,7 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
autoInitialize: true, autoInitialize: true,
); );
} }
} on MatrixConnectionException catch (e) { } on Exception catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(e.toLocalizedString(context)), content: Text(e.toLocalizedString(context)),

@ -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.contains(roomId)
// ? selectedRoomIds.remove(roomId)
// : selectedRoomIds.add(roomId),
// );
selectedRoomIds.contains(roomId)
? selectedRoomIds.remove(roomId) ? selectedRoomIds.remove(roomId)
: selectedRoomIds.add(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;
@ -53,6 +53,25 @@ class _SpaceViewState extends State<SpaceView> {
widget.controller.pangeaController.pStoreService.read(_chatCountsKey) ?? widget.controller.pangeaController.pStoreService.read(_chatCountsKey) ??
{}, {},
); );
/// Used to filter out sync updates with hierarchy updates for the active
/// space so that the view can be auto-reloaded in the room subscription
bool hasHierarchyUpdate(SyncUpdate update) {
final joinTimeline =
update.rooms?.join?[widget.controller.activeSpaceId]?.timeline;
final leaveTimeline =
update.rooms?.leave?[widget.controller.activeSpaceId]?.timeline;
if (joinTimeline == null && leaveTimeline == null) return false;
final bool hasJoinUpdate = joinTimeline?.events?.any(
(event) => event.type == EventTypes.SpaceChild,
) ??
false;
final bool hasLeaveUpdate = leaveTimeline?.events?.any(
(event) => event.type == EventTypes.SpaceChild,
) ??
false;
return hasJoinUpdate || hasLeaveUpdate;
}
// Pangea# // Pangea#
@override @override
@ -78,12 +97,9 @@ class _SpaceViewState extends State<SpaceView> {
// Listen for changes to the activeSpace's hierarchy, // Listen for changes to the activeSpace's hierarchy,
// and reload the hierarchy when they come through // and reload the hierarchy when they come through
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
_roomSubscription ??= client.onRoomState.stream.where((u) { _roomSubscription ??= client.onSync.stream
return u.state.type == EventTypes.SpaceChild && .where(hasHierarchyUpdate)
u.roomId == widget.controller.activeSpaceId; .listen((update) => loadHierarchy(hasUpdate: true));
}).listen((update) {
loadHierarchy(hasUpdate: true);
});
// Pangea# // Pangea#
super.initState(); super.initState();
} }
@ -709,7 +725,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 +808,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 +936,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,8 +54,9 @@ 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) {
@ -69,7 +70,6 @@ extension AnalyticsRoomExtension on Room {
); );
} }
} }
}
/// Add analytics room to all spaces the user is a student in (1 analytics room to all spaces). /// Add analytics room to all spaces the user is a student in (1 analytics room to all spaces).
/// Enables teachers to join student analytics rooms via space hierarchy. /// Enables teachers to join student analytics rooms via space hierarchy.

@ -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;

@ -122,6 +122,10 @@ class ErrorCopy {
title = l10n.error502504Title; title = l10n.error502504Title;
body = l10n.error502504Desc; body = l10n.error502504Desc;
break; break;
case 520:
title = l10n.error520Title;
body = l10n.error520Desc;
break;
case 404: case 404:
title = l10n.error404Title; title = l10n.error404Title;
body = l10n.error404Desc; body = l10n.error404Desc;

@ -1,5 +1,6 @@
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// Utility to save and read data both in the matrix profile (this is the default /// Utility to save and read data both in the matrix profile (this is the default
/// behavior) and in the local storage (local needs to be specificied). An /// behavior) and in the local storage (local needs to be specificied). An
@ -66,6 +67,9 @@ class PStore {
/// Clears the storage by erasing all data in the box. /// Clears the storage by erasing all data in the box.
void clearStorage() { void clearStorage() {
// this could potenitally be interfering with openning database
// at the start of the session, which is causing auto log outs on iOS
Sentry.addBreadcrumb(Breadcrumb(message: 'Clearing local storage'));
_box.erase(); _box.erase();
} }
} }

@ -58,7 +58,10 @@ class ToolbarDisplayController {
); );
} }
void showToolbar(BuildContext context, {MessageMode? mode}) { void showToolbar(
BuildContext context, {
MessageMode? mode,
}) {
bool toolbarUp = true; bool toolbarUp = true;
if (highlighted) return; if (highlighted) return;
if (controller.selectMode) { if (controller.selectMode) {
@ -78,8 +81,51 @@ class ToolbarDisplayController {
final Size transformTargetSize = (targetRenderBox as RenderBox).size; final Size transformTargetSize = (targetRenderBox as RenderBox).size;
messageWidth = transformTargetSize.width; messageWidth = transformTargetSize.width;
final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero);
final double screenHeight = MediaQuery.of(context).size.height;
toolbarUp = targetOffset.dy >= screenHeight / 2; // If there is enough space above, procede as normal
// Else if there is enough space below, show toolbar underneath
if (targetOffset.dy < 320) {
final spaceBeneath = MediaQuery.of(context).size.height -
(targetOffset.dy + transformTargetSize.height);
if (spaceBeneath >= 320) {
toolbarUp = false;
}
// See if it's possible to scroll up to make space
else if (controller.scrollController.offset - targetOffset.dy + 320 >=
controller.scrollController.position.minScrollExtent &&
controller.scrollController.offset - targetOffset.dy + 320 <=
controller.scrollController.position.maxScrollExtent) {
controller.scrollController.animateTo(
controller.scrollController.offset - targetOffset.dy + 320,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
);
}
// See if it's possible to scroll down to make space
else if (controller.scrollController.offset + spaceBeneath - 320 >=
controller.scrollController.position.minScrollExtent &&
controller.scrollController.offset + spaceBeneath - 320 <=
controller.scrollController.position.maxScrollExtent) {
controller.scrollController.animateTo(
controller.scrollController.offset + spaceBeneath - 320,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
);
toolbarUp = false;
}
// If message is too big and can't scroll either way
// Scroll up as much as possible, and show toolbar above
else {
controller.scrollController.animateTo(
controller.scrollController.position.minScrollExtent,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
);
}
}
} }
final Widget overlayMessage = OverlayMessage( final Widget overlayMessage = OverlayMessage(
@ -106,7 +152,13 @@ class ToolbarDisplayController {
? CrossAxisAlignment.end ? CrossAxisAlignment.end
: CrossAxisAlignment.start, : CrossAxisAlignment.start,
children: [ children: [
toolbarUp ? toolbar! : overlayMessage, toolbarUp
// Column is limited to screen height
// If message portion is too tall, decrease toolbar height
// as necessary to prevent toolbar from acting strange
// Problems may still occur if toolbar height is decreased too much
? toolbar!
: overlayMessage,
const SizedBox(height: 6), const SizedBox(height: 6),
toolbarUp ? overlayMessage : toolbar!, toolbarUp ? overlayMessage : toolbar!,
], ],
@ -367,7 +419,8 @@ class MessageToolbarState extends State<MessageToolbar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Flexible(
child: Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: Container( child: Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
@ -446,6 +499,7 @@ class MessageToolbarState extends State<MessageToolbar> {
], ],
), ),
), ),
),
); );
} }
} }

@ -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 {

@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -67,9 +66,7 @@ extension LocalizedExceptionExtension on Object {
supportedVersions, supportedVersions,
); );
} }
if (this is MatrixConnectionException || if (this is SocketException || this is SyncConnectionException) {
this is SocketException ||
this is SyncConnectionException) {
return L10n.of(context)!.noConnectionToTheServer; return L10n.of(context)!.noConnectionToTheServer;
} }
if (this is String) return toString(); if (this is String) return toString();

@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
@ -80,6 +81,9 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
} }
final cipher = await getDatabaseCipher(); final cipher = await getDatabaseCipher();
// #Pangea
Sentry.addBreadcrumb(Breadcrumb(message: 'Database cipher: $cipher'));
// Pangea#
Directory? fileStorageLocation; Directory? fileStorageLocation;
try { try {
@ -97,6 +101,9 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
// import the SQLite / SQLCipher shared objects / dynamic libraries // import the SQLite / SQLCipher shared objects / dynamic libraries
final factory = final factory =
createDatabaseFactoryFfi(ffiInit: SQfLiteEncryptionHelper.ffiInit); createDatabaseFactoryFfi(ffiInit: SQfLiteEncryptionHelper.ffiInit);
// #Pangea
Sentry.addBreadcrumb(Breadcrumb(message: 'Database path: $path'));
// Pangea#
// migrate from potential previous SQLite database path to current one // migrate from potential previous SQLite database path to current one
await _migrateLegacyLocation(path, client.clientName); await _migrateLegacyLocation(path, client.clientName);
@ -113,6 +120,9 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
path: path, path: path,
cipher: cipher, cipher: cipher,
); );
// #Pangea
Sentry.addBreadcrumb(Breadcrumb(message: 'Database cipher helper: $helper'));
// Pangea#
// check whether the DB is already encrypted and otherwise do so // check whether the DB is already encrypted and otherwise do so
await helper?.ensureDatabaseFileEncrypted(); await helper?.ensureDatabaseFileEncrypted();

@ -5,6 +5,7 @@ import 'package:fluffychat/config/setting_keys.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
const _passwordStorageKey = 'database_password'; const _passwordStorageKey = 'database_password';
@ -58,6 +59,12 @@ void _sendNoEncryptionWarning(Object exception) async {
// l10n.noDatabaseEncryption, // l10n.noDatabaseEncryption,
// exception.toString(), // exception.toString(),
// ); // );
Sentry.addBreadcrumb(
Breadcrumb(
message: 'No database encryption',
data: {'exception': exception},
),
);
// Pangea# // Pangea#
await store.setBool(SettingKeys.noEncryptionWarningShown, true); await store.setBool(SettingKeys.noEncryptionWarningShown, true);

@ -344,4 +344,10 @@ class MatrixLocals extends MatrixLocalizations {
@override @override
String startedKeyVerification(String senderName) => String startedKeyVerification(String senderName) =>
l10n.startedKeyVerification(senderName); l10n.startedKeyVerification(senderName);
@override
String invitedBy(String senderName) {
// TODO: implement invitedBy
throw UnimplementedError();
}
} }

@ -1432,11 +1432,12 @@ packages:
matrix: matrix:
dependency: "direct main" dependency: "direct main"
description: description:
name: matrix path: "."
sha256: bb6de59d0f69e10bb6893130a967f1ffcbfa3d3ffed3864f0736ce3d968e669c ref: main
url: "https://pub.dev" resolved-ref: "0a95cd8f3cfac8c9b0b59d6ee7fdbdb159949ca3"
source: hosted url: "https://github.com/pangeachat/matrix-dart-sdk.git"
version: "0.29.12" source: git
version: "0.30.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:

@ -70,7 +70,10 @@ dependencies:
keyboard_shortcuts: ^0.1.4 keyboard_shortcuts: ^0.1.4
latlong2: ^0.9.1 latlong2: ^0.9.1
linkify: ^5.0.0 linkify: ^5.0.0
matrix: ^0.29.12 matrix:
git:
url: https://github.com/pangeachat/matrix-dart-sdk.git # repo
ref: main # branch
native_imaging: ^0.1.1 native_imaging: ^0.1.1
package_info_plus: ^6.0.0 package_info_plus: ^6.0.0
pasteboard: ^0.2.0 pasteboard: ^0.2.0

Loading…
Cancel
Save