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

This commit is contained in:
ggurdin 2024-07-22 10:32:53 -04:00 committed by GitHub
commit 6f37cd014c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 214 additions and 133 deletions

View file

@ -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..."
}

View file

@ -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),
],
),

View file

@ -476,6 +476,8 @@ class ChatListController extends State<ChatList>
StreamSubscription? classStream;
StreamSubscription? _invitedSpaceSubscription;
StreamSubscription? _subscriptionStatusStream;
StreamSubscription? _spaceChildSubscription;
final Set<String> hasUpdates = {};
//Pangea#
@override
@ -567,6 +569,16 @@ class ChatListController extends State<ChatList>
showSubscribedSnackbar(context);
}
});
// listen for space child updates for any space that is not the active space
// so that when the user navigates to the space that was updated, it will
// reload any rooms that have been added / removed
final client = pangeaController.matrixState.client;
_spaceChildSubscription ??= client.onRoomState.stream.where((u) {
return u.state.type == EventTypes.SpaceChild && u.roomId != activeSpaceId;
}).listen((update) {
hasUpdates.add(update.roomId);
});
//Pangea#
super.initState();
@ -581,6 +593,7 @@ class ChatListController extends State<ChatList>
classStream?.cancel();
_invitedSpaceSubscription?.cancel();
_subscriptionStatusStream?.cancel();
_spaceChildSubscription?.cancel();
//Pangea#
scrollController.removeListener(_onScroll);
super.dispose();

View file

@ -10,7 +10,6 @@ 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/pangea_room_types.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/extensions/sync_update_extension.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
@ -46,8 +45,8 @@ class _SpaceViewState extends State<SpaceView> {
Object? error;
bool loading = false;
// #Pangea
StreamSubscription<SyncUpdate>? _roomSubscription;
bool refreshing = false;
StreamSubscription? _roomSubscription;
final String _chatCountsKey = 'chatCounts';
Map<String, int> get chatCounts => Map.from(
@ -58,9 +57,33 @@ class _SpaceViewState extends State<SpaceView> {
@override
void initState() {
loadHierarchy();
// #Pangea
// loadHierarchy();
// If, on launch, this room has had updates to its children,
// ensure the hierarchy is properly reloaded
final bool hasUpdate = widget.controller.hasUpdates.contains(
widget.controller.activeSpaceId,
);
loadHierarchy(hasUpdate: hasUpdate).then(
// remove this space ID from the set of space IDs with updates
(_) => widget.controller.hasUpdates.remove(
widget.controller.activeSpaceId,
),
);
loadChatCounts();
// 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);
});
// Pangea#
super.initState();
}
@ -76,11 +99,11 @@ class _SpaceViewState extends State<SpaceView> {
void _refresh() {
// #Pangea
// _lastResponse.remove(widget.controller.activseSpaceId);
if (mounted) {
// Pangea#
loadHierarchy();
// #Pangea
}
// loadHierarchy();
if (mounted) setState(() => refreshing = true);
loadHierarchy(hasUpdate: true).whenComplete(() {
if (mounted) setState(() => refreshing = false);
});
// Pangea#
}
@ -129,8 +152,10 @@ class _SpaceViewState extends State<SpaceView> {
/// spaceId, it will try to load the next batch and add the new rooms to the
/// already loaded ones. Displays a loading indicator while loading, and an error
/// message if an error occurs.
/// If hasUpdate is true, it will force the hierarchy to be reloaded.
Future<void> loadHierarchy({
String? spaceId,
bool hasUpdate = false,
}) async {
if ((widget.controller.activeSpaceId == null && spaceId == null) ||
loading) {
@ -142,7 +167,7 @@ class _SpaceViewState extends State<SpaceView> {
setState(() {});
try {
await _loadHierarchy(spaceId: spaceId);
await _loadHierarchy(spaceId: spaceId, hasUpdate: hasUpdate);
} catch (e, s) {
if (mounted) {
setState(() => error = e);
@ -159,6 +184,7 @@ class _SpaceViewState extends State<SpaceView> {
/// the active space id (or specified spaceId).
Future<void> _loadHierarchy({
String? spaceId,
bool hasUpdate = false,
}) async {
final client = Matrix.of(context).client;
final activeSpaceId = (widget.controller.activeSpaceId ?? spaceId)!;
@ -177,7 +203,7 @@ class _SpaceViewState extends State<SpaceView> {
await activeSpace.postLoad();
// The current number of rooms loaded for this space that are visible in the UI
final int prevLength = _lastResponse[activeSpaceId] != null
final int prevLength = _lastResponse[activeSpaceId] != null && !hasUpdate
? filterHierarchyResponse(
activeSpace,
_lastResponse[activeSpaceId]!.rooms,
@ -187,6 +213,9 @@ class _SpaceViewState extends State<SpaceView> {
// Failsafe to prevent too many calls to the server in a row
int callsToServer = 0;
GetSpaceHierarchyResponse? currentHierarchy =
hasUpdate ? null : _lastResponse[activeSpaceId];
// Makes repeated calls to the server until 10 new visible rooms have
// been loaded, or there are no rooms left to load. Using a loop here,
// rather than one single call to the endpoint, because some spaces have
@ -195,16 +224,15 @@ class _SpaceViewState extends State<SpaceView> {
// coming through from those calls are analytics rooms).
while (callsToServer < 5) {
// if this space has been loaded and there are no more rooms to load, break
if (_lastResponse[activeSpaceId] != null &&
_lastResponse[activeSpaceId]!.nextBatch == null) {
if (currentHierarchy != null && currentHierarchy.nextBatch == null) {
break;
}
// if this space has been loaded and 10 new rooms have been loaded, break
if (_lastResponse[activeSpaceId] != null) {
if (currentHierarchy != null) {
final int currentLength = filterHierarchyResponse(
activeSpace,
_lastResponse[activeSpaceId]!.rooms,
currentHierarchy.rooms,
).length;
if (currentLength - prevLength >= 10) {
@ -216,22 +244,26 @@ class _SpaceViewState extends State<SpaceView> {
final response = await client.getSpaceHierarchy(
activeSpaceId,
maxDepth: 1,
from: _lastResponse[activeSpaceId]?.nextBatch,
from: currentHierarchy?.nextBatch,
limit: 100,
);
callsToServer++;
// if rooms have earlier been loaded for this space, add those
// previously loaded rooms to the front of the response list
if (_lastResponse[activeSpaceId] != null) {
if (currentHierarchy != null) {
response.rooms.insertAll(
0,
_lastResponse[activeSpaceId]?.rooms ?? [],
currentHierarchy.rooms,
);
}
// finally, set the response to the last response for this space
_lastResponse[activeSpaceId] = response;
currentHierarchy = response;
}
if (currentHierarchy != null) {
_lastResponse[activeSpaceId] = currentHierarchy;
}
// After making those calls to the server, set the chat count for
@ -560,34 +592,6 @@ class _SpaceViewState extends State<SpaceView> {
}
}
void refreshOnUpdate(SyncUpdate event) {
/* refresh on leave, invite, and space child update
not join events, because there's already a listener on
onTapSpaceChild, and they interfere with each other */
if (widget.controller.activeSpaceId == null || !mounted || refreshing) {
return;
}
setState(() => refreshing = true);
final client = Matrix.of(context).client;
if (mounted &&
event.isMembershipUpdateByType(
Membership.leave,
client.userID!,
) ||
event.isMembershipUpdateByType(
Membership.invite,
client.userID!,
) ||
event.isSpaceChildUpdate(
widget.controller.activeSpaceId!,
)) {
debugPrint("refresh on update");
loadHierarchy().whenComplete(() {
if (mounted) setState(() => refreshing = false);
});
}
}
bool includeSpaceChild(
Room space,
SpaceRoomsChunk hierarchyMember,
@ -769,12 +773,6 @@ class _SpaceViewState extends State<SpaceView> {
);
}
// #Pangea
_roomSubscription ??= client.onSync.stream
.where((event) => event.hasRoomUpdate)
.listen(refreshOnUpdate);
// Pangea#
final parentSpace = allSpaces.firstWhereOrNull(
(space) =>
space.spaceChildren.any((child) => child.roomId == activeSpaceId),

View file

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

View file

@ -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,
),
),
],
),
],
],
),
],
),
),
),
);

View file

@ -10,6 +10,7 @@ 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;
@ -140,9 +141,15 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
return const CardErrorWidget();
}
final bool showWarning = l2Code != null &&
!widget.immersionMode &&
widget.messageEvent.originalSent?.langCode != l2Code &&
// 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(),
);