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.",
"manageSubscription": "Manage Subscription",
"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 audioPlayer = this.audioPlayer;
return Padding(
padding: const EdgeInsets.all(12.0),
// #Pangea
// padding: const EdgeInsets.all(12.0),
padding: const EdgeInsets.all(5.0),
// Pangea#
child: Row(
mainAxisSize: MainAxisSize.min,
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(
mainAxisSize: MainAxisSize.min,
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(
width: 36,
child: Text(

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

@ -1,21 +1,20 @@
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/material.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:path_provider/path_provider.dart';
import 'package:universal_html/html.dart' as html;
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';
class EventVideoPlayer extends StatefulWidget {
@ -71,7 +70,7 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
autoInitialize: true,
);
}
} on MatrixConnectionException catch (e) {
} on Exception catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toLocalizedString(context)),

@ -599,12 +599,23 @@ class ChatListController extends State<ChatList>
super.dispose();
}
// #Pangea
final StreamController<String> selectionsStream =
StreamController.broadcast();
// Pangea#
void toggleSelection(String roomId) {
setState(
() => selectedRoomIds.contains(roomId)
? selectedRoomIds.remove(roomId)
: selectedRoomIds.add(roomId),
);
// #Pangea
// setState(
// () => selectedRoomIds.contains(roomId)
// ? selectedRoomIds.remove(roomId)
// : selectedRoomIds.add(roomId),
// );
selectedRoomIds.contains(roomId)
? selectedRoomIds.remove(roomId)
: selectedRoomIds.add(roomId);
selectionsStream.add(roomId);
// Pangea#
}
Future<void> toggleUnread() async {
@ -676,8 +687,8 @@ class ChatListController extends State<ChatList>
context: context,
future: () => _archiveSelectedRooms(),
);
setState(() {});
// #Pangea
// setState(() {});
if (archivedActiveRoom) {
context.go('/rooms');
}
@ -709,7 +720,6 @@ class ChatListController extends State<ChatList>
context: context,
future: () => _leaveSelectedRooms(onlyAdmin),
);
setState(() {});
if (leftActiveRoom) {
context.go('/rooms');
}
@ -832,8 +842,7 @@ class ChatListController extends State<ChatList>
label: space.nameIncludingParents(context),
// If user is not admin of space, button is grayed out
textStyle: TextStyle(
color: (firstSelectedRoom == null ||
(firstSelectedRoom.isSpace && !space.isRoomAdmin))
color: (firstSelectedRoom == null)
? Theme.of(context).colorScheme.outline
: Theme.of(context).colorScheme.surfaceTint,
),
@ -851,10 +860,6 @@ class ChatListController extends State<ChatList>
if (firstSelectedRoom == null) {
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) {
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(
@ -946,7 +956,12 @@ class ChatListController extends State<ChatList>
if (selectMode == SelectMode.share) {
setState(() => Matrix.of(context).shareContent = null);
} 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: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/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/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/stream_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
@ -17,7 +17,6 @@ import 'package:matrix/matrix.dart';
import '../../config/themes.dart';
import '../../widgets/connection_status_header.dart';
import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
class ChatListViewBody extends StatelessWidget {
final ChatListController controller;
@ -76,7 +75,10 @@ class ChatListViewBody extends StatelessWidget {
child: CustomScrollView(
controller: controller.scrollController,
slivers: [
ChatListHeader(controller: controller),
// #Pangea
// ChatListHeader(controller: controller),
ChatListHeaderWrapper(controller: controller),
// Pangea#
SliverList(
delegate: SliverChildListDelegate(
[
@ -247,17 +249,23 @@ class ChatListViewBody extends StatelessWidget {
SliverList.builder(
itemCount: rooms.length,
itemBuilder: (BuildContext context, int i) {
return ChatListItem(
// #Pangea
// return ChatListItem(
return ChatListItemWrapper(
controller: controller,
// Pangea#
rooms[i],
key: Key('chat_list_item_${rooms[i].id}'),
filter: filter,
selected:
controller.selectedRoomIds.contains(rooms[i].id),
onTap: controller.selectMode == SelectMode.select
? () => controller.toggleSelection(rooms[i].id)
: () => onChatTap(rooms[i], context),
onLongPress: () =>
controller.toggleSelection(rooms[i].id),
// #Pangea
// selected:
// controller.selectedRoomIds.contains(rooms[i].id),
// onTap: controller.selectMode == SelectMode.select
// ? () => controller.toggleSelection(rooms[i].id)
// : () => onChatTap(rooms[i], context),
// onLongPress: () =>
// controller.toggleSelection(rooms[i].id),
// Pangea#
activeChat: controller.activeChat == rooms[i].id,
);
},

@ -4,7 +4,6 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/config/themes.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/utils/on_chat_tap.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/utils/chat_list_handle_space_tap.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/widgets/avatar.dart';
import 'package:flutter/material.dart';
@ -23,7 +24,6 @@ import 'package:matrix/matrix.dart';
import '../../utils/localized_exception_extension.dart';
import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
class SpaceView extends StatefulWidget {
final ChatListController controller;
@ -53,6 +53,25 @@ class _SpaceViewState extends State<SpaceView> {
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#
@override
@ -78,12 +97,9 @@ class _SpaceViewState extends State<SpaceView> {
// Listen for changes to the activeSpace's hierarchy,
// and reload the hierarchy when they come through
final client = Matrix.of(context).client;
_roomSubscription ??= client.onRoomState.stream.where((u) {
return u.state.type == EventTypes.SpaceChild &&
u.roomId == widget.controller.activeSpaceId;
}).listen((update) {
loadHierarchy(hasUpdate: true);
});
_roomSubscription ??= client.onSync.stream
.where(hasHierarchyUpdate)
.listen((update) => loadHierarchy(hasUpdate: true));
// Pangea#
super.initState();
}
@ -709,7 +725,10 @@ class _SpaceViewState extends State<SpaceView> {
child: CustomScrollView(
controller: widget.scrollController,
slivers: [
ChatListHeader(controller: widget.controller),
// #Pangea
// ChatListHeader(controller: widget.controller),
ChatListHeaderWrapper(controller: widget.controller),
// Pangea#
SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) {
@ -789,7 +808,13 @@ class _SpaceViewState extends State<SpaceView> {
child: CustomScrollView(
controller: widget.scrollController,
slivers: [
ChatListHeader(controller: widget.controller, globalSearch: false),
// #Pangea
// ChatListHeader(controller: widget.controller, globalSearch: false),
ChatListHeaderWrapper(
controller: widget.controller,
globalSearch: false,
),
// Pangea#
SliverAppBar(
automaticallyImplyLeading: false,
primary: false,
@ -911,7 +936,11 @@ class _SpaceViewState extends State<SpaceView> {
room.membership != Membership.leave
// Pangea#
) {
return ChatListItem(
// #Pangea
// return ChatListItem(
return ChatListItemWrapper(
controller: widget.controller,
// Pangea#
room,
onLongPress: () =>
_onSpaceChildContextMenu(spaceChild, room),

@ -131,7 +131,8 @@ extension ChildrenAndParentsRoomExtension on Room {
spaceMode = child?.isSpace ?? spaceMode;
// 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 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 canIAddSpaceChild(Room? room, {bool spaceMode = false}) {
return _canIAddSpaceChild(room, spaceMode: spaceMode);
}
bool get canIAddSpaceParents => _canIAddSpaceParents;
bool pangeaCanSendEvent(String eventType) => _pangeaCanSendEvent(eventType);

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

@ -78,36 +78,10 @@ extension UserPermissionsRoomExtension on Room {
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 =>
_isRoomAdmin || pangeaCanSendEvent(EventTypes.SpaceParent);
//overriding the default canSendEvent to check power levels
// Overriding the default canSendEvent to check power levels
bool _pangeaCanSendEvent(String eventType) {
final powerLevelsMap = getState(EventTypes.RoomPowerLevels)?.content;
if (powerLevelsMap == null) return 0 <= ownPowerLevel;

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

@ -1,5 +1,6 @@
import 'package:fluffychat/pangea/controllers/pangea_controller.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
/// 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.
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();
}
}

@ -58,7 +58,10 @@ class ToolbarDisplayController {
);
}
void showToolbar(BuildContext context, {MessageMode? mode}) {
void showToolbar(
BuildContext context, {
MessageMode? mode,
}) {
bool toolbarUp = true;
if (highlighted) return;
if (controller.selectMode) {
@ -78,8 +81,51 @@ class ToolbarDisplayController {
final Size transformTargetSize = (targetRenderBox as RenderBox).size;
messageWidth = transformTargetSize.width;
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(
@ -106,7 +152,13 @@ class ToolbarDisplayController {
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
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),
toolbarUp ? overlayMessage : toolbar!,
],
@ -367,83 +419,85 @@ class MessageToolbarState extends State<MessageToolbar> {
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
border: Border.all(
width: 2,
color: Theme.of(context).colorScheme.primary,
return Flexible(
child: Material(
type: MaterialType.transparency,
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
border: Border.all(
width: 2,
color: Theme.of(context).colorScheme.primary,
),
borderRadius: const BorderRadius.all(
Radius.circular(25),
),
),
borderRadius: const BorderRadius.all(
Radius.circular(25),
constraints: const BoxConstraints(
maxWidth: 300,
minWidth: 300,
maxHeight: 300,
),
),
constraints: const BoxConstraints(
maxWidth: 300,
minWidth: 300,
maxHeight: 300,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: SingleChildScrollView(
child: AnimatedSize(
duration: FluffyThemes.animationDuration,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: toolbarContent ?? const SizedBox(),
),
SizedBox(height: toolbarContent == null ? 0 : 20),
],
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: SingleChildScrollView(
child: AnimatedSize(
duration: FluffyThemes.animationDuration,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: toolbarContent ?? const SizedBox(),
),
SizedBox(height: toolbarContent == null ? 0 : 20),
],
),
),
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: MessageMode.values.map((mode) {
if ([
MessageMode.definition,
MessageMode.textToSpeech,
MessageMode.translation,
].contains(mode) &&
widget.pangeaMessageEvent.isAudioMessage) {
return const SizedBox.shrink();
}
if (mode == MessageMode.speechToText &&
!widget.pangeaMessageEvent.isAudioMessage) {
return const SizedBox.shrink();
}
return Tooltip(
message: mode.tooltip(context),
child: IconButton(
icon: Icon(mode.icon),
color: mode.iconColor(
widget.pangeaMessageEvent,
currentMode,
context,
Row(
mainAxisSize: MainAxisSize.min,
children: MessageMode.values.map((mode) {
if ([
MessageMode.definition,
MessageMode.textToSpeech,
MessageMode.translation,
].contains(mode) &&
widget.pangeaMessageEvent.isAudioMessage) {
return const SizedBox.shrink();
}
if (mode == MessageMode.speechToText &&
!widget.pangeaMessageEvent.isAudioMessage) {
return const SizedBox.shrink();
}
return Tooltip(
message: mode.tooltip(context),
child: IconButton(
icon: Icon(mode.icon),
color: mode.iconColor(
widget.pangeaMessageEvent,
currentMode,
context,
),
onPressed: () => updateMode(mode),
),
);
}).toList() +
[
Tooltip(
message: L10n.of(context)!.more,
child: IconButton(
icon: const Icon(Icons.add_reaction_outlined),
onPressed: showMore,
),
onPressed: () => updateMode(mode),
),
);
}).toList() +
[
Tooltip(
message: L10n.of(context)!.more,
child: IconButton(
icon: const Icon(Icons.add_reaction_outlined),
onPressed: showMore,
),
),
],
),
],
],
),
],
),
),
),
);

@ -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/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
class MessageTranslationCard extends StatefulWidget {
final PangeaMessageEvent messageEvent;
@ -133,6 +132,22 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
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
Widget build(BuildContext context) {
if (!_fetchingRepresentation &&
@ -141,19 +156,6 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
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(
child: _fetchingRepresentation
? 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;
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
//if possibleParent in parents, put first
//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;
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 {

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

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

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

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

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

@ -70,7 +70,10 @@ dependencies:
keyboard_shortcuts: ^0.1.4
latlong2: ^0.9.1
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
package_info_plus: ^6.0.0
pasteboard: ^0.2.0

Loading…
Cancel
Save