diff --git a/.github/workflows/main_deploy.yaml b/.github/workflows/main_deploy.yaml index d60208739..51dfaa1fe 100644 --- a/.github/workflows/main_deploy.yaml +++ b/.github/workflows/main_deploy.yaml @@ -50,6 +50,11 @@ jobs: cp public/.env public/assets/.env touch public/assets/envs.json echo "$ENV_OVERRIDES" >> public/assets/envs.json + mkdir -p public/.well-known + curl https://app.pangea.chat/.well-known/apple-app-site-association \ + -o public/.well-known/apple-app-site-association + curl https://app.pangea.chat/.well-known/assetlinks.json \ + -o public/.well-known/assetlinks.json - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v4 with: diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 734869ecc..2106db95a 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -44,7 +44,6 @@ abstract class AppConfig { toolbarButtonsHeight + (chatInputRowOverlayPadding * 2) + toolbarSpacing; - static const double audioTranscriptionMaxHeight = 150.0; static TextStyle messageTextStyle( Event? event, diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0f6b2899e..c5c74d682 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5017,9 +5017,6 @@ "newDirectMessage": "New direct message", "speakingExercisesTooltip": "Speaking practice", "noChatsFoundHereYet": "No chats found here yet", - "endNow": "End now", - "setDuration": "Set duration", - "activityEnded": "That’s a wrap for this activity! Big thanks to everyone for chatting, learning, and making this space so lively. Language grows with conversation, and every word exchanged brings us closer to confidence and fluency.\n\nKeep practicing, stay curious, and don’t be shy to keep the conversation going!", "duration": "Duration", "transcriptionFailed": "Failed to transcribe audio", "aUserIsKnocking": "1 user is requesting to join your space", diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 030f81422..948c0578b 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -11,9 +10,7 @@ import 'package:fluffychat/pages/chat/events/message.dart'; import 'package:fluffychat/pages/chat/seen_by_row.dart'; import 'package:fluffychat/pages/chat/typing_indicators.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_message.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; @@ -43,30 +40,6 @@ class ChatEventList extends StatelessWidget { final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0; final events = timeline.events.filterByVisibleInGui(); - // #Pangea - if (timeline.room.activityPlan?.endAt != null && - timeline.room.activityPlan!.endAt!.isBefore(DateTime.now())) { - final eventIndex = events.indexWhere( - (e) => e.originServerTs.isBefore( - timeline.room.activityPlan!.endAt!, - ), - ); - - if (eventIndex != -1) { - events.insert( - eventIndex, - Event( - type: PangeaEventTypes.activityPlanEnd, - eventId: timeline.room.client.generateUniqueTransactionId(), - senderId: timeline.room.client.userID!, - originServerTs: timeline.room.activityPlan!.endAt!, - room: timeline.room, - content: {}, - ), - ); - } - } - // Pangea# final animateInEventIndex = controller.animateInEventIndex; // create a map of eventId --> index to greatly improve performance of diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 2f90e7f59..1436cb00b 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -13,11 +13,9 @@ import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; import 'package:fluffychat/pages/chat/chat_event_list.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; -import 'package:fluffychat/pangea/activities/pinned_activity_message.dart'; import 'package:fluffychat/pangea/chat/widgets/chat_input_bar.dart'; import 'package:fluffychat/pangea/chat/widgets/chat_input_bar_header.dart'; import 'package:fluffychat/pangea/chat/widgets/chat_view_background.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -190,13 +188,6 @@ class ChatView extends StatelessWidget { if (scrollUpBannerEventId != null) { appbarBottomHeight += ChatAppBarListTile.fixedHeight; } - // #Pangea - if (controller.room.activityPlan != null && - controller.room.activityPlan!.endAt != null && - controller.room.activityPlan!.endAt!.isAfter(DateTime.now())) { - appbarBottomHeight += ChatAppBarListTile.fixedHeight; - } - // Pangea# return Scaffold( appBar: AppBar( actionsIconTheme: IconThemeData( @@ -235,9 +226,6 @@ class ChatView extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ PinnedEvents(controller), - // #Pangea - PinnedActivityMessage(controller), - // Pangea# if (scrollUpBannerEventId != null) ChatAppBarListTile( leading: IconButton( diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 877ddf428..d322ffcae 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -155,7 +155,9 @@ class HtmlMessage extends StatelessWidget { // #Pangea List? get tokens => - pangeaMessageEvent?.messageDisplayRepresentation?.tokens; + pangeaMessageEvent?.messageDisplayRepresentation?.tokens + ?.where((t) => t.pos != "PUNCT") + .toList(); PangeaToken? getToken( String text, diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 30d787343..ada6cceb4 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -9,9 +9,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart'; -import 'package:fluffychat/pangea/activities/activity_state_event.dart'; import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/file_description.dart'; @@ -123,22 +121,6 @@ class Message extends StatelessWidget { if (event.type == EventTypes.RoomCreate) { return RoomCreationStateEvent(event: event); } - // #Pangea - if (event.type == PangeaEventTypes.activityPlan) { - final state = event.room.getState(PangeaEventTypes.activityPlan); - if (state == null || state is! Event) { - return const SizedBox.shrink(); - } - - return state.originServerTs == event.originServerTs - ? ActivityStateEvent(event: event) - : const SizedBox(); - } - - if (event.type == PangeaEventTypes.activityPlanEnd) { - return const ActivityFinishedEvent(); - } - // Pangea# return StateMessage(event); } diff --git a/lib/pangea/activities/activity_aware_builder.dart b/lib/pangea/activities/activity_aware_builder.dart deleted file mode 100644 index 68eea54af..000000000 --- a/lib/pangea/activities/activity_aware_builder.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -class ActivityAwareBuilder extends StatefulWidget { - final DateTime? deadline; - final Widget Function(bool) builder; - - const ActivityAwareBuilder({ - super.key, - required this.builder, - this.deadline, - }); - - @override - State createState() => ActivityAwareBuilderState(); -} - -class ActivityAwareBuilderState extends State { - Timer? _timer; - - @override - void initState() { - super.initState(); - _setTimer(); - } - - @override - void didUpdateWidget(covariant ActivityAwareBuilder oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.deadline != widget.deadline) { - _setTimer(); - } - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - void _setTimer() { - final now = DateTime.now(); - final delay = widget.deadline?.difference(now); - - if (delay != null && delay > Duration.zero) { - _timer?.cancel(); - _timer = Timer(delay, () { - _timer?.cancel(); - _timer = null; - if (mounted) setState(() {}); - }); - } - } - - @override - Widget build(BuildContext context) { - return widget.builder( - widget.deadline != null && widget.deadline!.isAfter(DateTime.now()), - ); - } -} diff --git a/lib/pangea/activities/activity_constants.dart b/lib/pangea/activities/activity_constants.dart deleted file mode 100644 index 41858d53a..000000000 --- a/lib/pangea/activities/activity_constants.dart +++ /dev/null @@ -1,3 +0,0 @@ -class ActivityConstants { - static const String activityFinishedAsset = "EndActivityMsg.png"; -} diff --git a/lib/pangea/activities/activity_duration_popup.dart b/lib/pangea/activities/activity_duration_popup.dart deleted file mode 100644 index c6cbb56ab..000000000 --- a/lib/pangea/activities/activity_duration_popup.dart +++ /dev/null @@ -1,282 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/l10n/l10n.dart'; - -class ActivityDurationPopup extends StatefulWidget { - final Duration initialValue; - const ActivityDurationPopup({ - super.key, - required this.initialValue, - }); - - @override - State createState() => ActivityDurationPopupState(); -} - -class ActivityDurationPopupState extends State { - final TextEditingController _daysController = TextEditingController(); - final TextEditingController _hoursController = TextEditingController(); - final TextEditingController _minutesController = TextEditingController(); - - String? error; - - final List _durations = [ - const Duration(minutes: 15), - const Duration(minutes: 30), - const Duration(minutes: 45), - const Duration(minutes: 60), - const Duration(hours: 1, minutes: 30), - const Duration(hours: 2), - const Duration(hours: 24), - const Duration(days: 2), - const Duration(days: 7), - ]; - - @override - void initState() { - super.initState(); - _daysController.text = widget.initialValue.inDays.toString(); - _hoursController.text = - widget.initialValue.inHours.remainder(24).toString(); - _minutesController.text = - widget.initialValue.inMinutes.remainder(60).toString(); - - _daysController.addListener(() => setState(() => error = null)); - _hoursController.addListener(() => setState(() => error = null)); - _minutesController.addListener(() => setState(() => error = null)); - } - - @override - void dispose() { - _daysController.dispose(); - _hoursController.dispose(); - _minutesController.dispose(); - super.dispose(); - } - - void _setDuration({int? days, int? hours, int? minutes}) { - setState(() { - if (days != null) _daysController.text = days.toString(); - if (hours != null) _hoursController.text = hours.toString(); - if (minutes != null) _minutesController.text = minutes.toString(); - }); - } - - String _formatDuration(Duration duration) { - final days = duration.inDays; - final hours = duration.inHours.remainder(24); - final minutes = duration.inMinutes.remainder(60); - - final List parts = []; - if (days > 0) parts.add("${days}d"); - if (hours > 0) parts.add("${hours}h"); - if (minutes > 0) parts.add("${minutes}m"); - if (parts.isEmpty) return "0m"; - - return parts.join(" "); - } - - Duration get _duration { - final days = int.tryParse(_daysController.text) ?? 0; - final hours = int.tryParse(_hoursController.text) ?? 0; - final minutes = int.tryParse(_minutesController.text) ?? 0; - return Duration(days: days, hours: hours, minutes: minutes); - } - - void _submit() { - final days = int.tryParse(_daysController.text); - final hours = int.tryParse(_hoursController.text); - final minutes = int.tryParse(_minutesController.text); - - if (days == null || hours == null || minutes == null) { - setState(() { - error = "Invalid duration"; - }); - return; - } - - Navigator.of(context).pop(_duration); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 350.0, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - spacing: 12.0, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - L10n.of(context).setDuration, - style: const TextStyle(fontSize: 20.0, height: 1.2), - ), - Column( - children: [ - Container( - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - side: BorderSide( - width: 2, - color: theme.colorScheme.primary.withAlpha(100), - ), - borderRadius: BorderRadius.circular(20), - ), - ), - padding: const EdgeInsets.only( - top: 12.0, - bottom: 12.0, - right: 24.0, - left: 8.0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SelectionArea( - child: Row( - spacing: 12.0, - children: [ - _DatePickerInput( - type: "d", - controller: _daysController, - ), - _DatePickerInput( - type: "h", - controller: _hoursController, - ), - _DatePickerInput( - type: "m", - controller: _minutesController, - ), - ], - ), - ), - const Icon( - Icons.alarm, - size: 24, - ), - ], - ), - ), - AnimatedSize( - duration: FluffyThemes.animationDuration, - child: error != null - ? Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - error!, - style: TextStyle( - color: theme.colorScheme.error, - fontSize: 14.0, - ), - ), - ) - : const SizedBox.shrink(), - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, - horizontal: 24.0, - ), - child: Wrap( - spacing: 10.0, - runSpacing: 10.0, - children: _durations - .map( - (d) => InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () { - _setDuration( - days: d.inDays, - hours: d.inHours.remainder(24), - minutes: d.inMinutes.remainder(60), - ); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 0.0, - ), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer - .withAlpha(_duration == d ? 200 : 100), - borderRadius: BorderRadius.circular(12), - ), - child: Text(_formatDuration(d)), - ), - ), - ) - .toList(), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - onPressed: _submit, - child: Text(L10n.of(context).confirm), - ), - ], - ), - ], - ), - ), - ), - ), - ); - } -} - -class _DatePickerInput extends StatelessWidget { - final String type; - final TextEditingController controller; - - const _DatePickerInput({ - required this.type, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - SizedBox( - width: 35.0, - child: TextField( - controller: controller, - textAlign: TextAlign.end, - decoration: InputDecoration( - isDense: true, - border: InputBorder.none, - contentPadding: const EdgeInsets.all(0.0), - hintText: "0", - hintStyle: TextStyle( - fontSize: 20.0, - color: theme.colorScheme.onSurfaceVariant.withAlpha(100), - ), - ), - style: const TextStyle( - fontSize: 20.0, - ), - keyboardType: TextInputType.number, - ), - ), - Text(type, style: const TextStyle(fontSize: 20.0)), - ], - ); - } -} diff --git a/lib/pangea/activities/activity_state_event.dart b/lib/pangea/activities/activity_state_event.dart deleted file mode 100644 index b59b8c233..000000000 --- a/lib/pangea/activities/activity_state_event.dart +++ /dev/null @@ -1,279 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/activities/activity_constants.dart'; -import 'package:fluffychat/pangea/activities/activity_duration_popup.dart'; -import 'package:fluffychat/pangea/activities/countdown.dart'; -import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/widgets/future_loading_dialog.dart'; -import 'package:fluffychat/widgets/mxc_image.dart'; - -class ActivityStateEvent extends StatefulWidget { - final Event event; - - const ActivityStateEvent({required this.event, super.key}); - - @override - State createState() => ActivityStateEventState(); -} - -class ActivityStateEventState extends State { - Timer? _timer; - - @override - void initState() { - super.initState(); - final now = DateTime.now(); - final delay = activityPlan?.endAt != null - ? activityPlan!.endAt!.difference(now) - : null; - - if (delay != null && delay > Duration.zero) { - _timer = Timer(delay, () { - setState(() {}); - }); - } - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - ActivityPlanModel? get activityPlan { - try { - return ActivityPlanModel.fromJson(widget.event.content); - } catch (e) { - return null; - } - } - - @override - Widget build(BuildContext context) { - if (activityPlan == null) { - return const SizedBox.shrink(); - } - - final theme = Theme.of(context); - final isColumnMode = FluffyThemes.isColumnMode(context); - - final double imageWidth = isColumnMode ? 240.0 : 175.0; - - return Center( - child: Container( - constraints: const BoxConstraints( - maxWidth: 400.0, - ), - margin: const EdgeInsets.all(18.0), - child: Column( - spacing: 12.0, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(18), - ), - child: AnimatedSize( - duration: FluffyThemes.animationDuration, - child: Text( - activityPlan!.markdown, - style: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - fontSize: - AppConfig.fontSizeFactor * AppConfig.messageFontSize, - ), - ), - ), - ), - AnimatedSize( - duration: FluffyThemes.animationDuration, - child: IntrinsicHeight( - child: Row( - spacing: 12.0, - children: [ - Container( - height: imageWidth, - width: imageWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: activityPlan!.imageURL != null - ? activityPlan!.imageURL!.startsWith("mxc") - ? MxcImage( - uri: Uri.parse( - activityPlan!.imageURL!, - ), - width: imageWidth, - height: imageWidth, - cacheKey: activityPlan!.bookmarkId, - fit: BoxFit.cover, - ) - : CachedNetworkImage( - imageUrl: activityPlan!.imageURL!, - fit: BoxFit.cover, - placeholder: (context, url) => const Center( - child: CircularProgressIndicator(), - ), - errorWidget: ( - context, - url, - error, - ) => - const SizedBox(), - ) - : const SizedBox(), - ), - ), - Expanded( - child: Column( - spacing: 9.0, - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: SizedBox.expand( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: - theme.colorScheme.primaryContainer, - foregroundColor: - theme.colorScheme.onPrimaryContainer, - ), - onPressed: () async { - final Duration? duration = await showDialog( - context: context, - builder: (context) { - return ActivityDurationPopup( - initialValue: activityPlan?.duration ?? - const Duration(days: 1), - ); - }, - ); - - if (duration == null) return; - - showFutureLoadingDialog( - context: context, - future: () => - widget.event.room.sendActivityPlan( - activityPlan!.copyWith( - endAt: DateTime.now().add(duration), - duration: duration, - ), - ), - ); - }, - child: CountDown( - deadline: activityPlan!.endAt, - iconSize: 20.0, - textSize: 16.0, - ), - ), - ), - ), // Optional spacing between buttons - Expanded( - child: SizedBox.expand( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: theme.colorScheme.error, - foregroundColor: theme.colorScheme.onPrimary, - ), - onPressed: () { - showFutureLoadingDialog( - context: context, - future: () => - widget.event.room.sendActivityPlan( - activityPlan!.copyWith( - endAt: DateTime.now(), - duration: Duration.zero, - ), - ), - ); - }, - child: Text( - L10n.of(context).endNow, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ], - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - } -} - -class ActivityFinishedEvent extends StatelessWidget { - const ActivityFinishedEvent({super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Center( - child: Container( - constraints: const BoxConstraints( - maxWidth: 400.0, - ), - margin: const EdgeInsets.all(18.0), - child: Container( - padding: const EdgeInsets.all(24.0), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(18), - ), - child: Column( - spacing: 12.0, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - L10n.of(context).activityEnded, - style: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - fontSize: 16.0, - ), - ), - CachedNetworkImage( - width: 120.0, - imageUrl: - "${AppConfig.assetsBaseURL}/${ActivityConstants.activityFinishedAsset}", - fit: BoxFit.cover, - placeholder: (context, url) => const Center( - child: CircularProgressIndicator(), - ), - errorWidget: (context, url, error) => const SizedBox(), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pangea/activities/countdown.dart b/lib/pangea/activities/countdown.dart deleted file mode 100644 index 17516cb23..000000000 --- a/lib/pangea/activities/countdown.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:fluffychat/l10n/l10n.dart'; - -class CountDown extends StatefulWidget { - final DateTime? deadline; - - final double? iconSize; - final double? textSize; - - const CountDown({ - super.key, - required this.deadline, - this.iconSize, - this.textSize, - }); - - @override - State createState() => CountDownState(); -} - -class CountDownState extends State { - Timer? _timer; - - @override - void initState() { - super.initState(); - _timer = Timer.periodic(const Duration(seconds: 1), (_) { - setState(() {}); - }); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - String? _formatDuration(Duration duration) { - final days = duration.inDays; - final hours = duration.inHours.remainder(24); - final minutes = duration.inMinutes.remainder(60); - final seconds = duration.inSeconds.remainder(60); - - final List parts = []; - if (days > 0) parts.add("${days}d"); - if (hours > 0) parts.add("${hours}h"); - if (minutes > 0) parts.add("${minutes}m"); - if (seconds > 0 && minutes <= 0) parts.add("${seconds}s"); - if (parts.isEmpty) return null; - - return parts.join(" "); - } - - Duration? get _remainingTime { - if (widget.deadline == null) { - return null; - } - - final now = DateTime.now(); - return widget.deadline!.difference(now); - } - - @override - Widget build(BuildContext context) { - final remainingTime = _remainingTime; - final durationString = _formatDuration(remainingTime ?? Duration.zero); - - return ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 250.0, - ), - child: Row( - spacing: 4.0, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.timer_outlined, - size: widget.iconSize ?? 28.0, - ), - Flexible( - child: Text( - remainingTime != null && - remainingTime >= Duration.zero && - durationString != null - ? durationString - : L10n.of(context).duration, - style: TextStyle(fontSize: widget.textSize ?? 20), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pangea/activities/pinned_activity_message.dart b/lib/pangea/activities/pinned_activity_message.dart deleted file mode 100644 index 8be5a492f..000000000 --- a/lib/pangea/activities/pinned_activity_message.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:collection/collection.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart'; -import 'package:fluffychat/pangea/activities/activity_aware_builder.dart'; -import 'package:fluffychat/pangea/activities/activity_duration_popup.dart'; -import 'package:fluffychat/pangea/activities/countdown.dart'; -import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/widgets/future_loading_dialog.dart'; - -class PinnedActivityMessage extends StatelessWidget { - final ChatController controller; - - const PinnedActivityMessage(this.controller, {super.key}); - - Future _scrollToEvent() async { - final eventId = _activityPlanEvent?.eventId; - if (eventId != null) controller.scrollToEventId(eventId); - } - - Event? get _activityPlanEvent => controller.timeline?.events.firstWhereOrNull( - (event) => event.type == PangeaEventTypes.activityPlan, - ); - - ActivityPlanModel? get _activityPlan => controller.room.activityPlan; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - return ActivityAwareBuilder( - deadline: _activityPlan?.endAt, - builder: (isActive) { - if (!isActive || _activityPlan == null) { - return const SizedBox.shrink(); - } - - return ChatAppBarListTile( - title: _activityPlan!.title, - leading: IconButton( - splashRadius: 18, - iconSize: 18, - color: theme.colorScheme.onSurfaceVariant, - icon: const Icon(Icons.push_pin), - onPressed: () {}, - ), - trailing: Padding( - padding: const EdgeInsets.only(right: 16.0), - child: InkWell( - borderRadius: BorderRadius.circular(20), - onTap: () async { - final Duration? duration = await showDialog( - context: context, - builder: (context) { - return ActivityDurationPopup( - initialValue: - _activityPlan?.duration ?? const Duration(days: 1), - ); - }, - ); - - if (duration == null) return; - - showFutureLoadingDialog( - context: context, - future: () => controller.room.sendActivityPlan( - _activityPlan!.copyWith( - endAt: DateTime.now().add(duration), - duration: duration, - ), - ), - ); - }, - child: Container( - padding: const EdgeInsets.all(4.0), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(20), - ), - child: CountDown( - deadline: _activityPlan!.endAt, - iconSize: 16.0, - textSize: 14.0, - ), - ), - ), - ), - onTap: _scrollToEvent, - ); - }, - ); - } -} diff --git a/lib/pangea/events/constants/pangea_event_types.dart b/lib/pangea/events/constants/pangea_event_types.dart index a385fe97d..fe2a47e18 100644 --- a/lib/pangea/events/constants/pangea_event_types.dart +++ b/lib/pangea/events/constants/pangea_event_types.dart @@ -26,7 +26,6 @@ class PangeaEventTypes { static const capacity = "pangea.capacity"; static const activityPlan = "pangea.activity_plan"; - static const activityPlanEnd = "pangea.activity.end"; static const userAge = "pangea.user_age"; diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 58616de36..316ef7199 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:html_unescape/html_unescape.dart'; +import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/markdown.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; diff --git a/lib/pangea/extensions/room_events_extension.dart b/lib/pangea/extensions/room_events_extension.dart index e24427ef3..799ebdb66 100644 --- a/lib/pangea/extensions/room_events_extension.dart +++ b/lib/pangea/extensions/room_events_extension.dart @@ -277,6 +277,57 @@ extension EventsRoomExtension on Room { }) async { BookmarkedActivitiesRepo.save(activity); + String? imageURL = activity.imageURL; + final eventId = await pangeaSendTextEvent( + activity.markdown, + messageTag: ModelKey.messageTagActivityPlan, + ); + + Uint8List? bytes = avatar; + if (imageURL != null && bytes == null) { + try { + final resp = await http + .get(Uri.parse(imageURL)) + .timeout(const Duration(seconds: 5)); + bytes = resp.bodyBytes; + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + "avatarURL": imageURL, + }, + ); + } + } + + if (bytes != null && imageURL == null) { + final url = await client.uploadContent( + bytes, + filename: filename, + ); + imageURL = url.toString(); + } + + MatrixFile? file; + if (filename != null && bytes != null) { + file = MatrixFile( + bytes: bytes, + name: filename, + ); + } + + if (file != null) { + final content = { + 'msgtype': file.msgType, + 'body': file.name, + 'filename': file.name, + 'url': imageURL, + ModelKey.messageTags: ModelKey.messageTagActivityPlan, + }; + await sendEvent(content); + } + if (canChangeStateEvent(PangeaEventTypes.activityPlan)) { await client.setRoomStateWithKey( id, @@ -284,6 +335,10 @@ extension EventsRoomExtension on Room { "", activity.toJson(), ); + + if (eventId != null && canChangeStateEvent(EventTypes.RoomPinnedEvents)) { + await setPinnedEvents([eventId]); + } } } diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index 8486a9991..ee9f64546 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -22,6 +22,8 @@ class PhoneticTranscriptionWidget extends StatefulWidget { final bool enabled; + final VoidCallback? onTranscriptionFetched; + const PhoneticTranscriptionWidget({ super.key, required this.text, @@ -30,6 +32,7 @@ class PhoneticTranscriptionWidget extends StatefulWidget { this.iconSize, this.iconColor, this.enabled = true, + this.onTranscriptionFetched, }); @override @@ -51,6 +54,17 @@ class _PhoneticTranscriptionWidgetState _fetchTranscription(); } + @override + void didUpdateWidget( + covariant PhoneticTranscriptionWidget oldWidget, + ) { + super.didUpdateWidget(oldWidget); + if (oldWidget.text != widget.text || + oldWidget.textLanguage != widget.textLanguage) { + _fetchTranscription(); + } + } + Future _fetchTranscription() async { try { setState(() { @@ -92,7 +106,12 @@ class _PhoneticTranscriptionWidgetState }, ); } finally { - if (mounted) setState(() => _isLoading = false); + if (mounted) { + setState(() { + _isLoading = false; + widget.onTranscriptionFetched?.call(); + }); + } } } diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index daaecd2ae..14da26467 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -101,6 +101,8 @@ class MessageOverlayController extends State bool showSpeechTranslation = false; String? speechTranslation; + final StreamController contentChangedStream = StreamController.broadcast(); + double maxWidth = AppConfig.toolbarMinWidth; ///////////////////////////////////// @@ -121,6 +123,7 @@ class MessageOverlayController extends State WidgetsBinding.instance.addPostFrameCallback( (_) => widget.chatController.clearSelectedEvents(), ); + contentChangedStream.close(); super.dispose(); } @@ -587,7 +590,10 @@ class MessageOverlayController extends State void setTranslation(String value) { if (mounted) { - setState(() => translation = value); + setState(() { + translation = value; + contentChangedStream.add(true); + }); } } @@ -598,12 +604,18 @@ class MessageOverlayController extends State } if (showTranslation == show) return; - setState(() => showTranslation = show); + setState(() { + showTranslation = show; + contentChangedStream.add(true); + }); } void setSpeechTranslation(String value) { if (mounted) { - setState(() => speechTranslation = value); + setState(() { + speechTranslation = value; + contentChangedStream.add(true); + }); } } @@ -614,7 +626,10 @@ class MessageOverlayController extends State } if (showSpeechTranslation == show) return; - setState(() => showSpeechTranslation = show); + setState(() { + showSpeechTranslation = show; + contentChangedStream.add(true); + }); } void setTranscription(SpeechToTextModel value) { @@ -622,13 +637,17 @@ class MessageOverlayController extends State setState(() { transcriptionError = null; transcription = value; + contentChangedStream.add(true); }); } } void setTranscriptionError(String value) { if (mounted) { - setState(() => transcriptionError = value); + setState(() { + transcriptionError = value; + contentChangedStream.add(true); + }); } } diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index f32f4d3ba..bbfad506d 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -71,6 +71,7 @@ class MessageSelectionPositionerState extends State Offset? _currentOffset; StreamSubscription? _reactionSubscription; + StreamSubscription? _contentChangedSubscription; final _animationDuration = const Duration( milliseconds: AppConfig.overlayAnimationDuration, @@ -106,6 +107,10 @@ class MessageSelectionPositionerState extends State }, ).listen((_) => setState(() {})); + _contentChangedSubscription = widget + .overlayController.contentChangedStream.stream + .listen(_onContentSizeChanged); + WidgetsBinding.instance.addPostFrameCallback((_) async { await _centeredMessageCompleter.future; if (!mounted) return; @@ -138,6 +143,7 @@ class MessageSelectionPositionerState extends State void dispose() { _animationController.dispose(); _reactionSubscription?.cancel(); + _contentChangedSubscription?.cancel(); MatrixState.pangeaController.matrixState.audioPlayer ?..stop() ..dispose(); @@ -196,34 +202,9 @@ class MessageSelectionPositionerState extends State } if (mode == ReadingAssistanceMode.selectMode) { - _overlayOffsetAnimation = Tween( - begin: _currentOffset, - end: _adjustedOriginalMessageOffset, - ).animate( - CurvedAnimation( - parent: _animationController, - curve: FluffyThemes.animationCurve, - ), - )..addListener(() { - if (mounted) { - setState(() => _currentOffset = _overlayOffsetAnimation?.value); - } - }); + _resetOffsetAnimation(_adjustedOriginalMessageOffset); } else if (mode == ReadingAssistanceMode.practiceMode) { - _overlayOffsetAnimation = Tween( - begin: _currentOffset, - end: _centeredMessageOffset!, - ).animate( - CurvedAnimation( - parent: _animationController, - curve: FluffyThemes.animationCurve, - ), - )..addListener(() { - if (mounted) { - setState(() => _currentOffset = _overlayOffsetAnimation?.value); - } - }); - + _resetOffsetAnimation(_centeredMessageOffset!); _messageSizeAnimation = Tween( begin: Size( _originalMessageSize.width, @@ -244,6 +225,40 @@ class MessageSelectionPositionerState extends State } } + void _onContentSizeChanged(_) { + Future.delayed(FluffyThemes.animationDuration, () { + final offset = _overlayMessageRenderBox?.localToGlobal(Offset.zero); + if (offset == null || !_overlayMessageRenderBox!.hasSize) { + return null; + } + + final newOffset = _adjustedMessageOffset( + _overlayMessageRenderBox!.size, + offset, + ); + + if (newOffset == _currentOffset) return; + _resetOffsetAnimation(newOffset); + _animationController.forward(from: 0); + }); + } + + void _resetOffsetAnimation(Offset offset) { + _overlayOffsetAnimation = Tween( + begin: _currentOffset, + end: offset, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: FluffyThemes.animationCurve, + ), + )..addListener(() { + if (mounted) { + setState(() => _currentOffset = _overlayOffsetAnimation?.value); + } + }); + } + T _runWithLogging( Function runner, String errorMessage, @@ -326,6 +341,14 @@ class MessageSelectionPositionerState extends State null, ); + RenderBox? get _overlayMessageRenderBox => _runWithLogging( + () => MatrixState.pAnyState.getRenderBox( + 'overlay_message_${widget.event.eventId}', + ), + "Error getting overlay message render box", + null, + ); + Size get _defaultMessageSize => const Size(FluffyThemes.columnWidth / 2, 100); /// The size of the message in the chat list (as opposed to the expanded size in the center overlay) @@ -394,17 +417,28 @@ class MessageSelectionPositionerState extends State } Offset get _adjustedOriginalMessageOffset { + return _adjustedMessageOffset( + _originalMessageSize, + _originalMessageOffset, + ); + } + + Offset _adjustedMessageOffset( + Size messageSize, + Offset messageOffset, + ) { if (_messageRenderBox == null || !_messageRenderBox!.hasSize) { return _defaultMessageOffset; } - final topOffset = _originalMessageOffset.dy; - final bottomOffset = _originalMessageBottomOffset - - _reactionsHeight - - _selectionButtonsHeight; + final topOffset = messageOffset.dy; + final bottomOffset = + (_mediaQuery!.size.height - topOffset - messageSize.height) - + _reactionsHeight - + _selectionButtonsHeight; - final hasHeaderOverflow = topOffset < - (_headerHeight + AppConfig.toolbarSpacing + _audioTranscriptionHeight); + final hasHeaderOverflow = + topOffset < (_headerHeight + AppConfig.toolbarSpacing); final hasFooterOverflow = bottomOffset < (_footerHeight + AppConfig.toolbarSpacing); @@ -416,15 +450,12 @@ class MessageSelectionPositionerState extends State } if (hasHeaderOverflow) { - final difference = topOffset - - (_headerHeight + - AppConfig.toolbarSpacing + - _audioTranscriptionHeight); + final difference = topOffset - (_headerHeight + AppConfig.toolbarSpacing); double newBottomOffset = _mediaQuery!.size.height - - _originalMessageOffset.dy + + topOffset + difference - - _originalMessageSize.height - + messageSize.height - _selectionButtonsHeight; if (newBottomOffset < _footerHeight + AppConfig.toolbarSpacing) { @@ -524,12 +555,6 @@ class MessageSelectionPositionerState extends State return showSelectionButtons ? AppConfig.toolbarButtonsHeight : 0; } - double get _audioTranscriptionHeight { - return widget.pangeaMessageEvent?.isAudioMessage ?? false - ? AppConfig.audioTranscriptionMaxHeight - : 0; - } - bool get _hasReactions { final reactionsEvents = widget.event.aggregatedEvents( widget.chatController.timeline!, diff --git a/lib/pangea/toolbar/widgets/overlay_center_content.dart b/lib/pangea/toolbar/widgets/overlay_center_content.dart index 835177fc5..c4d2810d9 100644 --- a/lib/pangea/toolbar/widgets/overlay_center_content.dart +++ b/lib/pangea/toolbar/widgets/overlay_center_content.dart @@ -9,6 +9,7 @@ import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dar import 'package:fluffychat/pangea/toolbar/widgets/measure_render_box.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/overlay_message.dart'; +import 'package:fluffychat/widgets/matrix.dart'; class OverlayCenterContent extends StatelessWidget { final Event event; @@ -69,6 +70,9 @@ class OverlayCenterContent extends StatelessWidget { MeasureRenderBox( onChange: onChangeMessageSize, child: OverlayMessage( + key: MatrixState.pAnyState + .layerLinkAndKey('overlay_message_${event.eventId}') + .key, event, pangeaMessageEvent: pangeaMessageEvent, immersionMode: chatController.choreographer.immersionMode, diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index e83e856d3..a5dda244c 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/message_content.dart'; @@ -149,9 +150,8 @@ class OverlayMessage extends StatelessWidget { final transcription = showTranscription ? Container( - width: messageWidth, constraints: const BoxConstraints( - maxHeight: AppConfig.audioTranscriptionMaxHeight, + maxWidth: FluffyThemes.columnWidth * 1.5, ), child: Padding( padding: const EdgeInsets.all(12.0), @@ -178,6 +178,7 @@ class OverlayMessage extends StatelessWidget { child: Column( spacing: 8.0, crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ SttTranscriptTokens( model: overlayController.transcription!, @@ -208,6 +209,9 @@ class OverlayMessage extends StatelessWidget { iconColor: textColor, enabled: event.senderId != BotName.byEnvironment, + onTranscriptionFetched: () => + overlayController.contentChangedStream + .add(true), ), ], ), @@ -226,9 +230,8 @@ class OverlayMessage extends StatelessWidget { final translation = showTranslation || showSpeechTranslation ? Container( - width: messageWidth, constraints: const BoxConstraints( - maxHeight: AppConfig.audioTranscriptionMaxHeight, + maxWidth: FluffyThemes.columnWidth * 1.5, ), child: Padding( padding: const EdgeInsets.fromLTRB( @@ -271,8 +274,6 @@ class OverlayMessage extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (readingAssistanceMode == ReadingAssistanceMode.transitionMode) - transcription, if (event.relationshipType == RelationshipTypes.reply) FutureBuilder( future: event.getReplyEvent( @@ -371,8 +372,6 @@ class OverlayMessage extends StatelessWidget { ], ), ), - if (readingAssistanceMode == ReadingAssistanceMode.transitionMode) - translation, ], ), ), @@ -386,26 +385,31 @@ class OverlayMessage extends StatelessWidget { color: noBubble ? Colors.transparent : color, borderRadius: borderRadius, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (readingAssistanceMode != ReadingAssistanceMode.transitionMode) + constraints: BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 1.5, + maxHeight: maxHeight, + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ transcription, - sizeAnimation != null - ? AnimatedBuilder( - animation: sizeAnimation!, - builder: (context, child) { - return SizedBox( - height: sizeAnimation!.value.height, - width: sizeAnimation!.value.width, - child: content, - ); - }, - ) - : content, - if (readingAssistanceMode != ReadingAssistanceMode.transitionMode) + sizeAnimation != null + ? AnimatedBuilder( + animation: sizeAnimation!, + builder: (context, child) { + return SizedBox( + height: sizeAnimation!.value.height, + width: sizeAnimation!.value.width, + child: content, + ); + }, + ) + : content, translation, - ], + ], + ), ), ), ); diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index d762a4dcc..fee1b3c62 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -1,13 +1,36 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/common/constants/model_keys.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import '../../config/app_config.dart'; extension VisibleInGuiExtension on List { - List filterByVisibleInGui({String? exceptionEventId}) => where( - (event) => event.isVisibleInGui || event.eventId == exceptionEventId, - ).toList(); + List filterByVisibleInGui({String? exceptionEventId}) { + final visibleEvents = + where((e) => e.isVisibleInGui || e.eventId == exceptionEventId) + .toList(); + + // Hide creation state events: + if (visibleEvents.isNotEmpty && + visibleEvents.last.type == EventTypes.RoomCreate) { + var i = visibleEvents.length - 2; + while (i > 0) { + final event = visibleEvents[i]; + if (!event.isState) break; + if (event.type == EventTypes.Encryption) { + i--; + continue; + } + if (event.type == EventTypes.RoomMember && + event.roomMemberChangeType == RoomMemberChangeType.acceptInvite) { + i--; + continue; + } + visibleEvents.removeAt(i); + i--; + } + } + return visibleEvents; + } } extension IsStateExtension on Event { @@ -23,12 +46,7 @@ extension IsStateExtension on Event { // if we enabled to hide all redacted events, don't show those (!AppConfig.hideRedactedEvents || !redacted) && // if we enabled to hide all unknown events, don't show those - // #Pangea - // (!AppConfig.hideUnknownEvents || isEventTypeKnown) && - (!AppConfig.hideUnknownEvents || - isEventTypeKnown || - importantStateEvents.contains(type)) && - // Pangea# + (!AppConfig.hideUnknownEvents || isEventTypeKnown) && // remove state events that we don't want to render (isState || !AppConfig.hideAllStateEvents) && // #Pangea @@ -64,8 +82,6 @@ extension IsStateExtension on Event { EventTypes.RoomMember, EventTypes.RoomTombstone, EventTypes.CallInvite, - PangeaEventTypes.activityPlan, - PangeaEventTypes.activityPlanEnd, }; // Pangea# }