commit
78c6eb18bf
18 changed files with 384 additions and 196 deletions
|
|
@ -21,10 +21,12 @@ import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dar
|
|||
import 'package:fluffychat/pangea/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/pages/games/story_game/round_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
import 'package:fluffychat/pangea/utils/report_message.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/round_timer.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart';
|
||||
import 'package:fluffychat/utils/error_reporter.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
||||
|
|
@ -112,9 +114,20 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
with WidgetsBindingObserver {
|
||||
// #Pangea
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
|
||||
late Choreographer choreographer = Choreographer(pangeaController, this);
|
||||
final GlobalKey<RoundTimerState> roundTimerStateKey =
|
||||
GlobalKey<RoundTimerState>();
|
||||
RoundTimer? timer;
|
||||
|
||||
final List<GameRoundModel> gameRounds = [];
|
||||
|
||||
List<String> get completedRoundEventIds => gameRounds
|
||||
.where((round) => round.isCompleted)
|
||||
.map((round) => round.messageIDs)
|
||||
.expand((x) => x)
|
||||
.toList();
|
||||
// Pangea#
|
||||
|
||||
Room get room => sendingClient.getRoomById(roomId) ?? widget.room;
|
||||
|
||||
late Client sendingClient;
|
||||
|
|
@ -294,6 +307,17 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
void addRound() {
|
||||
debugPrint("ADDING A ROUND. Rounds so far: ${gameRounds.length}");
|
||||
final newRound = GameRoundModel(controller: this, timer: timer!);
|
||||
gameRounds.add(newRound);
|
||||
newRound.roundCompleter.future.then((_) {
|
||||
if (mounted) addRound();
|
||||
});
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scrollController.addListener(_updateScrollController);
|
||||
|
|
@ -309,6 +333,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
sendingClient = Matrix.of(context).client;
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
// #Pangea
|
||||
timer = RoundTimer(key: roundTimerStateKey);
|
||||
addRound();
|
||||
if (!mounted) return;
|
||||
Future.delayed(const Duration(seconds: 1), () async {
|
||||
if (!mounted) return;
|
||||
|
|
@ -395,7 +421,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
List<Event> get visibleEvents =>
|
||||
timeline?.events
|
||||
.where(
|
||||
(x) => x.isVisibleInGui,
|
||||
(x) =>
|
||||
x.isVisibleInGui && !completedRoundEventIds.contains(x.eventId),
|
||||
)
|
||||
.toList() ??
|
||||
<Event>[];
|
||||
|
|
|
|||
|
|
@ -27,7 +27,15 @@ class ChatEventList extends StatelessWidget {
|
|||
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
|
||||
|
||||
final events = controller.timeline!.events
|
||||
.where((event) => event.isVisibleInGui)
|
||||
.where(
|
||||
(event) =>
|
||||
event.isVisibleInGui
|
||||
// #Pangea
|
||||
&&
|
||||
!controller.completedRoundEventIds.contains(event.eventId)
|
||||
// Pangea#
|
||||
,
|
||||
)
|
||||
.toList();
|
||||
final animateInEventIndex = controller.animateInEventIndex;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/round_timer.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
||||
import 'package:fluffychat/widgets/connection_status_header.dart';
|
||||
|
|
@ -119,6 +120,10 @@ class ChatView extends StatelessWidget {
|
|||
// #Pangea
|
||||
} else {
|
||||
return [
|
||||
controller.timer ?? const SizedBox(),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
ChatSettingsPopupMenu(
|
||||
controller.room,
|
||||
(!controller.room.isDirectChat && !controller.room.isArchived),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
extension EventInfoDialogExtension on Event {
|
||||
void showInfoDialog(BuildContext context) => showAdaptiveBottomSheet(
|
||||
|
|
@ -49,15 +47,16 @@ class EventInfoDialog extends StatelessWidget {
|
|||
children: [
|
||||
ListTile(
|
||||
leading: Avatar(
|
||||
mxContent: event.senderFromMemoryOrFallback.avatarUrl,
|
||||
name: event.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
// mxContent: event.senderFromMemoryOrFallback.avatarUrl,
|
||||
// name: event.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
name: "?",
|
||||
client: event.room.client,
|
||||
presenceUserId: event.senderId,
|
||||
// presenceUserId: event.senderId,
|
||||
),
|
||||
title: Text(L10n.of(context)!.sender),
|
||||
subtitle: Text(
|
||||
'${event.senderFromMemoryOrFallback.calcDisplayname()} [${event.senderId}]',
|
||||
),
|
||||
// subtitle: Text(
|
||||
// '${event.senderFromMemoryOrFallback.calcDisplayname()} [${event.senderId}]',
|
||||
// ),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.time),
|
||||
|
|
|
|||
|
|
@ -263,9 +263,10 @@ class Message extends StatelessWidget {
|
|||
final user = snapshot.data ??
|
||||
event.senderFromMemoryOrFallback;
|
||||
return Avatar(
|
||||
mxContent: user.avatarUrl,
|
||||
name: user.calcDisplayname(),
|
||||
presenceUserId: user.stateKey,
|
||||
// mxContent: user.avatarUrl,
|
||||
// name: user.calcDisplayname(),
|
||||
// presenceUserId: user.stateKey,
|
||||
name: "?",
|
||||
presenceBackgroundColor:
|
||||
avatarPresenceBackgroundColor,
|
||||
onTap: () => onAvatarTab(event),
|
||||
|
|
@ -288,10 +289,11 @@ class Message extends StatelessWidget {
|
|||
: FutureBuilder<User?>(
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
final displayname = snapshot.data
|
||||
?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback
|
||||
.calcDisplayname();
|
||||
// final displayname = snapshot.data
|
||||
// ?.calcDisplayname() ??
|
||||
// event.senderFromMemoryOrFallback
|
||||
// .calcDisplayname();
|
||||
const displayname = "?";
|
||||
return Text(
|
||||
displayname,
|
||||
style: TextStyle(
|
||||
|
|
|
|||
|
|
@ -96,12 +96,16 @@ class MessageContent extends StatelessWidget {
|
|||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Avatar(
|
||||
mxContent: sender.avatarUrl,
|
||||
name: sender.calcDisplayname(),
|
||||
presenceUserId: sender.stateKey,
|
||||
// mxContent: sender.avatarUrl,
|
||||
// name: sender.calcDisplayname(),
|
||||
// presenceUserId: sender.stateKey,
|
||||
name: "?",
|
||||
client: event.room.client,
|
||||
),
|
||||
title: Text(sender.calcDisplayname()),
|
||||
title: const Text(
|
||||
// sender.calcDisplayname(),
|
||||
"?",
|
||||
),
|
||||
subtitle: Text(event.originServerTs.localizedTime(context)),
|
||||
trailing: const Icon(Icons.lock_outlined),
|
||||
),
|
||||
|
|
@ -265,9 +269,10 @@ class MessageContent extends StatelessWidget {
|
|||
builder: (context, snapshot) {
|
||||
final reason =
|
||||
event.redactedBecause?.content.tryGet<String>('reason');
|
||||
final redactedBy = snapshot.data?.calcDisplayname() ??
|
||||
event.redactedBecause?.senderId.localpart ??
|
||||
L10n.of(context)!.user;
|
||||
// final redactedBy = snapshot.data?.calcDisplayname() ??
|
||||
// event.redactedBecause?.senderId.localpart ??
|
||||
// L10n.of(context)!.user;
|
||||
const redactedBy = "?";
|
||||
return _ButtonContent(
|
||||
label: reason == null
|
||||
? L10n.of(context)!.redactedBy(redactedBy)
|
||||
|
|
@ -385,8 +390,9 @@ class MessageContent extends StatelessWidget {
|
|||
builder: (context, snapshot) {
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)!.userSentUnknownEvent(
|
||||
snapshot.data?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
// snapshot.data?.calcDisplayname() ??
|
||||
// event.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
"?",
|
||||
event.type,
|
||||
),
|
||||
icon: 'ℹ️',
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ class ReplyContent extends StatelessWidget {
|
|||
future: displayEvent.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
'${snapshot.data?.calcDisplayname() ?? displayEvent.senderFromMemoryOrFallback.calcDisplayname()}:',
|
||||
// '${snapshot.data?.calcDisplayname() ?? displayEvent.senderFromMemoryOrFallback.calcDisplayname()}:',
|
||||
'?:',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
|
|
|
|||
|
|
@ -37,9 +37,10 @@ class SeenByRow extends StatelessWidget {
|
|||
? seenByUsers.sublist(0, maxAvatars)
|
||||
: seenByUsers)
|
||||
.map(
|
||||
(user) => Avatar(
|
||||
mxContent: user.avatarUrl,
|
||||
name: user.calcDisplayname(),
|
||||
(user) => const Avatar(
|
||||
// mxContent: user.avatarUrl,
|
||||
// name: user.calcDisplayname(),
|
||||
name: "?",
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TypingIndicators extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
|
@ -56,10 +55,11 @@ class TypingIndicators extends StatelessWidget {
|
|||
child: Stack(
|
||||
children: [
|
||||
if (typingUsers.isNotEmpty)
|
||||
Avatar(
|
||||
const Avatar(
|
||||
size: avatarSize,
|
||||
mxContent: typingUsers.first.avatarUrl,
|
||||
name: typingUsers.first.calcDisplayname(),
|
||||
// mxContent: typingUsers.first.avatarUrl,
|
||||
// name: typingUsers.first.calcDisplayname(),
|
||||
name: "?",
|
||||
),
|
||||
if (typingUsers.length == 2)
|
||||
Padding(
|
||||
|
|
@ -69,9 +69,10 @@ class TypingIndicators extends StatelessWidget {
|
|||
mxContent: typingUsers.length == 2
|
||||
? typingUsers.last.avatarUrl
|
||||
: null,
|
||||
name: typingUsers.length == 2
|
||||
? typingUsers.last.calcDisplayname()
|
||||
: '+${typingUsers.length - 1}',
|
||||
// name: typingUsers.length == 2
|
||||
// ? typingUsers.last.calcDisplayname()
|
||||
// : '+${typingUsers.length - 1}',
|
||||
name: "?",
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -39,9 +39,10 @@ class ParticipantListItem extends StatelessWidget {
|
|||
),
|
||||
title: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
const Expanded(
|
||||
child: Text(
|
||||
user.calcDisplayname(),
|
||||
// user.calcDisplayname(),
|
||||
"?",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
|
@ -88,8 +89,9 @@ class ParticipantListItem extends StatelessWidget {
|
|||
subtitle: Text(user.id),
|
||||
leading: Avatar(
|
||||
mxContent: user.avatarUrl,
|
||||
name: user.calcDisplayname(),
|
||||
presenceUserId: user.stateKey,
|
||||
// name: user.calcDisplayname(),
|
||||
// presenceUserId: user.stateKey,
|
||||
name: "?",
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/utils/get_chat_list_item_subtitle.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/room_status_extension.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
|
|
@ -240,51 +238,52 @@ class ChatListItem extends StatelessWidget {
|
|||
softWrap: false,
|
||||
)
|
||||
// #Pangea
|
||||
: FutureBuilder<String>(
|
||||
future: room.lastEvent != null
|
||||
? GetChatListItemSubtitle().getSubtitle(
|
||||
L10n.of(context)!,
|
||||
room.lastEvent,
|
||||
MatrixState.pangeaController,
|
||||
)
|
||||
: Future.value(L10n.of(context)!.emptyChat),
|
||||
builder: (context, snapshot) {
|
||||
// Pangea#
|
||||
return Text(
|
||||
room.membership == Membership.invite
|
||||
? isDirectChat
|
||||
? L10n.of(context)!.invitePrivateChat
|
||||
: L10n.of(context)!.inviteGroupChat
|
||||
// #Pangea
|
||||
: snapshot.data ??
|
||||
// Pangea#
|
||||
room.lastEvent
|
||||
?.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
hideReply: true,
|
||||
hideEdit: true,
|
||||
plaintextBody: true,
|
||||
removeMarkdown: true,
|
||||
withSenderNamePrefix: !isDirectChat ||
|
||||
directChatMatrixId !=
|
||||
room.lastEvent?.senderId,
|
||||
) ??
|
||||
L10n.of(context)!.emptyChat,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: unread || room.hasNewMessages
|
||||
? FontWeight.bold
|
||||
: null,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
decoration: room.lastEvent?.redacted == true
|
||||
? TextDecoration.lineThrough
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
: const SizedBox(),
|
||||
// FutureBuilder<String>(
|
||||
// future: room.lastEvent != null
|
||||
// ? GetChatListItemSubtitle().getSubtitle(
|
||||
// L10n.of(context)!,
|
||||
// room.lastEvent,
|
||||
// MatrixState.pangeaController,
|
||||
// )
|
||||
// : Future.value(L10n.of(context)!.emptyChat),
|
||||
// builder: (context, snapshot) {
|
||||
// // Pangea#
|
||||
// return Text(
|
||||
// room.membership == Membership.invite
|
||||
// ? isDirectChat
|
||||
// ? L10n.of(context)!.invitePrivateChat
|
||||
// : L10n.of(context)!.inviteGroupChat
|
||||
// // #Pangea
|
||||
// : snapshot.data ??
|
||||
// // Pangea#
|
||||
// room.lastEvent
|
||||
// ?.calcLocalizedBodyFallback(
|
||||
// MatrixLocals(L10n.of(context)!),
|
||||
// hideReply: true,
|
||||
// hideEdit: true,
|
||||
// plaintextBody: true,
|
||||
// removeMarkdown: true,
|
||||
// withSenderNamePrefix: !isDirectChat ||
|
||||
// directChatMatrixId !=
|
||||
// room.lastEvent?.senderId,
|
||||
// ) ??
|
||||
// L10n.of(context)!.emptyChat,
|
||||
// softWrap: false,
|
||||
// maxLines: 1,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// style: TextStyle(
|
||||
// fontWeight: unread || room.hasNewMessages
|
||||
// ? FontWeight.bold
|
||||
// : null,
|
||||
// color: theme.colorScheme.onSurfaceVariant,
|
||||
// decoration: room.lastEvent?.redacted == true
|
||||
// ? TextDecoration.lineThrough
|
||||
// : null,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// #Pangea
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class ChatSearchMessageTab extends StatelessWidget {
|
||||
final String searchQuery;
|
||||
|
|
@ -98,9 +96,10 @@ class ChatSearchMessageTab extends StatelessWidget {
|
|||
}
|
||||
final event = events[i];
|
||||
final sender = event.senderFromMemoryOrFallback;
|
||||
final displayname = sender.calcDisplayname(
|
||||
i18n: MatrixLocals(L10n.of(context)!),
|
||||
);
|
||||
// final displayname = sender.calcDisplayname(
|
||||
// i18n: MatrixLocals(L10n.of(context)!),
|
||||
// );
|
||||
const displayname = "?";
|
||||
return _MessageSearchResultListTile(
|
||||
sender: sender,
|
||||
displayname: displayname,
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final user = controller.widget.user;
|
||||
final userId = (user?.id ?? controller.widget.profile?.userId)!;
|
||||
final displayname = (user?.calcDisplayname() ??
|
||||
controller.widget.profile?.displayName ??
|
||||
controller.widget.profile?.userId.localpart)!;
|
||||
// final displayname = (user?.calcDisplayname() ??
|
||||
// controller.widget.profile?.displayName ??
|
||||
// controller.widget.profile?.userId.localpart)!;
|
||||
const displayname = "?";
|
||||
final avatarUrl = user?.avatarUrl ?? controller.widget.profile?.avatarUrl;
|
||||
|
||||
final client = Matrix.of(controller.widget.outerContext).client;
|
||||
|
|
@ -39,7 +40,7 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(displayname),
|
||||
const Text(displayname),
|
||||
PresenceBuilder(
|
||||
userId: userId,
|
||||
client: client,
|
||||
|
|
@ -213,7 +214,7 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
label: Text(
|
||||
label: const Text(
|
||||
displayname,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
|
|
|||
|
|
@ -1,85 +1,18 @@
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
extension MembershipUpdate on SyncUpdate {
|
||||
bool isMembershipUpdate(String userId) {
|
||||
return isMembershipUpdateByType(Membership.join, userId) ||
|
||||
isMembershipUpdateByType(Membership.leave, userId) ||
|
||||
isMembershipUpdateByType(Membership.invite, userId);
|
||||
}
|
||||
|
||||
bool isMembershipUpdateByType(Membership type, String userId) {
|
||||
final List<SyncRoomUpdate>? updates = getRoomUpdates(type);
|
||||
if (updates?.isEmpty ?? true) {
|
||||
return false;
|
||||
List<Event> messages(Room chat) {
|
||||
if (rooms?.join == null ||
|
||||
!rooms!.join!.containsKey(chat.id) ||
|
||||
rooms!.join![chat.id]!.timeline?.events == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (final SyncRoomUpdate update in updates!) {
|
||||
final List<dynamic>? events = getRoomUpdateEvents(type, update);
|
||||
if (hasMembershipUpdate(
|
||||
events,
|
||||
type.name,
|
||||
userId,
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
List<SyncRoomUpdate>? getRoomUpdates(Membership type) {
|
||||
switch (type) {
|
||||
case Membership.join:
|
||||
return rooms?.join?.values.toList();
|
||||
case Membership.leave:
|
||||
return rooms?.leave?.values.toList();
|
||||
case Membership.invite:
|
||||
return rooms?.invite?.values.toList();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
bool isSpaceChildUpdate(String activeSpaceId) {
|
||||
if (rooms?.join?.isEmpty ?? true) {
|
||||
return false;
|
||||
}
|
||||
for (final update in rooms!.join!.entries) {
|
||||
final String spaceId = update.key;
|
||||
final List<MatrixEvent>? timelineEvents = update.value.timeline?.events;
|
||||
final bool isUpdate = timelineEvents != null &&
|
||||
spaceId == activeSpaceId &&
|
||||
timelineEvents.any((event) => event.type == EventTypes.SpaceChild);
|
||||
if (isUpdate) return true;
|
||||
}
|
||||
return false;
|
||||
return rooms!.join![chat.id]!.timeline!.events!
|
||||
.where(
|
||||
(event) => event.type == EventTypes.Message,
|
||||
)
|
||||
.map((event) => Event.fromMatrixEvent(event, chat))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
List<dynamic>? getRoomUpdateEvents(Membership type, SyncRoomUpdate update) {
|
||||
switch (type) {
|
||||
case Membership.join:
|
||||
return (update as JoinedRoomUpdate).timeline?.events;
|
||||
case Membership.leave:
|
||||
return (update as LeftRoomUpdate).timeline?.events;
|
||||
case Membership.invite:
|
||||
return (update as InvitedRoomUpdate).inviteState;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasMembershipUpdate(
|
||||
List<dynamic>? events,
|
||||
String membershipType,
|
||||
String userId,
|
||||
) {
|
||||
if (events == null) {
|
||||
return false;
|
||||
}
|
||||
return events.any(
|
||||
(event) =>
|
||||
event.type == EventTypes.RoomMember &&
|
||||
event.stateKey == userId &&
|
||||
event.content['membership'] == membershipType,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
87
lib/pangea/pages/games/story_game/round_model.dart
Normal file
87
lib/pangea/pages/games/story_game/round_model.dart
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/extensions/sync_update_extension.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/round_timer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
enum RoundState { notStarted, inProgress, completed }
|
||||
|
||||
class GameRoundModel {
|
||||
static const int timerMaxSeconds = 180;
|
||||
|
||||
final ChatController controller;
|
||||
final Completer<void> roundCompleter = Completer<void>();
|
||||
late DateTime createdAt;
|
||||
RoundTimer timer;
|
||||
DateTime? startTime;
|
||||
DateTime? endTime;
|
||||
RoundState state = RoundState.notStarted;
|
||||
StreamSubscription? syncSubscription;
|
||||
final Set<String> messageIDs = {};
|
||||
|
||||
GameRoundModel({
|
||||
required this.controller,
|
||||
required this.timer,
|
||||
}) {
|
||||
createdAt = DateTime.now();
|
||||
debugPrint("timeline: ${controller.room.timeline}");
|
||||
syncSubscription ??= client.onSync.stream.listen((update) {
|
||||
final newMessages = update.messages(controller.room);
|
||||
final botMessages = newMessages
|
||||
.where((msg) => msg.senderId == BotName.byEnvironment)
|
||||
.toList();
|
||||
|
||||
if (botMessages.isNotEmpty &&
|
||||
botMessages.any(
|
||||
(msg) =>
|
||||
msg.originServerTs.isAfter(createdAt) &&
|
||||
!messageIDs.contains(msg.eventId),
|
||||
)) {
|
||||
if (state == RoundState.notStarted) {
|
||||
startRound();
|
||||
} else if (state == RoundState.inProgress) {
|
||||
endRound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (final message in newMessages) {
|
||||
if (message.originServerTs.isAfter(createdAt) &&
|
||||
!messageIDs.contains(message.eventId) &&
|
||||
!message.eventId.startsWith("Pangea Chat")) {
|
||||
messageIDs.add(message.eventId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Client get client => controller.pangeaController.matrixState.client;
|
||||
|
||||
bool get isCompleted => roundCompleter.isCompleted;
|
||||
|
||||
void startRound() {
|
||||
debugPrint("starting round");
|
||||
state = RoundState.inProgress;
|
||||
startTime = DateTime.now();
|
||||
controller.roundTimerStateKey.currentState?.resetTimer(
|
||||
roundLength: timerMaxSeconds,
|
||||
);
|
||||
controller.roundTimerStateKey.currentState?.startTimer();
|
||||
}
|
||||
|
||||
void endRound() {
|
||||
debugPrint("ending round, message IDs: $messageIDs");
|
||||
endTime = DateTime.now();
|
||||
state = RoundState.completed;
|
||||
controller.roundTimerStateKey.currentState?.resetTimer();
|
||||
syncSubscription?.cancel();
|
||||
roundCompleter.complete();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
syncSubscription?.cancel();
|
||||
}
|
||||
}
|
||||
113
lib/pangea/widgets/chat/round_timer.dart
Normal file
113
lib/pangea/widgets/chat/round_timer.dart
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Create a timer that counts down to the given time
|
||||
/// Default duration is 180 seconds
|
||||
class RoundTimer extends StatefulWidget {
|
||||
final int timerMaxSeconds;
|
||||
final Duration roundDuration;
|
||||
|
||||
const RoundTimer({
|
||||
super.key,
|
||||
this.timerMaxSeconds = 180,
|
||||
this.roundDuration = const Duration(seconds: 1),
|
||||
});
|
||||
|
||||
@override
|
||||
RoundTimerState createState() => RoundTimerState();
|
||||
}
|
||||
|
||||
class RoundTimerState extends State<RoundTimer> {
|
||||
int currentSeconds = 0;
|
||||
Timer? _timer;
|
||||
bool isTiming = false;
|
||||
Duration? duration;
|
||||
int timerMaxSeconds = 180;
|
||||
|
||||
void resetTimer({Duration? roundDuration, int? roundLength}) {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
isTiming = false;
|
||||
}
|
||||
if (roundDuration != null) {
|
||||
duration = roundDuration;
|
||||
}
|
||||
if (roundLength != null) {
|
||||
timerMaxSeconds = roundLength;
|
||||
}
|
||||
setState(() {
|
||||
currentSeconds = 0;
|
||||
});
|
||||
}
|
||||
|
||||
int get remainingTime => timerMaxSeconds - currentSeconds;
|
||||
|
||||
String get timerText =>
|
||||
'${(remainingTime ~/ 60).toString().padLeft(2, '0')}: ${(remainingTime % 60).toString().padLeft(2, '0')}';
|
||||
|
||||
startTimer() {
|
||||
_timer = Timer.periodic(duration ?? widget.roundDuration, (timer) {
|
||||
setState(() {
|
||||
currentSeconds++;
|
||||
if (currentSeconds >= timerMaxSeconds) timer.cancel();
|
||||
});
|
||||
});
|
||||
setState(() {
|
||||
isTiming = true;
|
||||
});
|
||||
}
|
||||
|
||||
stopTimer() {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
setState(() {
|
||||
isTiming = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
duration = widget.roundDuration;
|
||||
timerMaxSeconds = widget.timerMaxSeconds;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: const Color.fromARGB(255, 126, 22, 14),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(
|
||||
5,
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(timerText),
|
||||
// Row(
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
// children: [
|
||||
// IconButton(
|
||||
// onPressed: isTiming ? stopTimeout : startTimeout,
|
||||
// icon: Icon(isTiming ? Icons.pause_circle : Icons.play_circle),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -184,9 +184,9 @@ class MatrixLocals extends MatrixLocalizations {
|
|||
@override
|
||||
String redactedAnEvent(Event redactedEvent) {
|
||||
return l10n.redactedAnEvent(
|
||||
redactedEvent.redactedBecause?.senderFromMemoryOrFallback
|
||||
.calcDisplayname() ??
|
||||
l10n.user,
|
||||
// redactedEvent.redactedBecause?.senderFromMemoryOrFallback
|
||||
// .calcDisplayname() ??
|
||||
l10n.user,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +198,8 @@ class MatrixLocals extends MatrixLocalizations {
|
|||
@override
|
||||
String removedBy(Event redactedEvent) {
|
||||
return l10n.redactedBy(
|
||||
redactedEvent.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
// redactedEvent.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
"?",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
@ -20,17 +19,21 @@ extension RoomStatusExtension on Room {
|
|||
} else if (typingUsers.length == 1) {
|
||||
typingText = L10n.of(context)!.isTyping;
|
||||
if (typingUsers.first.id != directChatMatrixID) {
|
||||
typingText =
|
||||
L10n.of(context)!.userIsTyping(typingUsers.first.calcDisplayname());
|
||||
typingText = L10n.of(context)!.userIsTyping(
|
||||
// typingUsers.first.calcDisplayname(),
|
||||
"?",
|
||||
);
|
||||
}
|
||||
} else if (typingUsers.length == 2) {
|
||||
typingText = L10n.of(context)!.userAndUserAreTyping(
|
||||
typingUsers.first.calcDisplayname(),
|
||||
typingUsers[1].calcDisplayname(),
|
||||
// typingUsers.first.calcDisplayname(),
|
||||
// typingUsers[1].calcDisplayname(),
|
||||
"?", "?",
|
||||
);
|
||||
} else if (typingUsers.length > 2) {
|
||||
typingText = L10n.of(context)!.userAndOthersAreTyping(
|
||||
typingUsers.first.calcDisplayname(),
|
||||
// typingUsers.first.calcDisplayname(),
|
||||
"?",
|
||||
(typingUsers.length - 1).toString(),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue