feat: activity dropdown menu

pull/2245/head
ggurdin 2 months ago committed by GitHub
parent 5ce2a787b4
commit b45541d826
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -100,8 +100,12 @@ abstract class FluffyThemes {
toolbarHeight: isColumnMode ? 72 : 56,
shadowColor:
isColumnMode ? colorScheme.surfaceContainer.withAlpha(128) : null,
surfaceTintColor: isColumnMode ? colorScheme.surface : null,
backgroundColor: isColumnMode ? colorScheme.surface : null,
// #Pangea
// surfaceTintColor: isColumnMode ? colorScheme.surface : null,
// backgroundColor: isColumnMode ? colorScheme.surface : null,
surfaceTintColor: colorScheme.surface,
backgroundColor: colorScheme.surface,
// Pangea#
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: brightness.reversed,

@ -5197,6 +5197,31 @@
"type": "String",
"course": {}
},
"activityComplete": "This activity has been completed. The activity summary should be available below.",
"haventChattedMuch": "It looks like you haven't chatted much, try using some more vocab words! If you feel like you've completed your objective, you can end the activity below.",
"haveChatted": "It looks like you've been chatting for a while! If you feel like you've completed your objective, wrap up to finish the activity and we'll generate you a summary in the chat!",
"userDoneAndWaiting": "{num1}/{num2} participants have wrapped up. Wait for everyone to finish, and we'll generate you a summary in the chat! \n\nIf you'd like to rejoin the conversation, click the continue button in the chat.",
"@userDoneAndWaiting": {
"placeholders": {
"num1": {
"type": "int"
},
"num2": {
"type": "int"
}
}
},
"othersDoneAndWaiting": "${num1}/{num2} are done. Have you completed your objective?",
"@othersDoneAndWaiting": {
"placeholders": {
"num1": {
"type": "int"
},
"num2": {
"type": "int"
}
}
},
"startNewSession": "Start new session",
"joinOpenSession": "Join open session",
"less": "less",

@ -2218,6 +2218,11 @@ class ChatController extends State<ChatPageWithRoom>
void toggleShowInstructions() {
if (mounted) setState(() => showInstructions = !showInstructions);
}
bool showActivityDropdown = false;
void setShowDropdown(bool show) async {
setState(() => showActivityDropdown = show);
}
// Pangea#
late final ValueNotifier<bool> _displayChatDetailsColumn;

@ -6,6 +6,8 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_stats_button.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/sync_status_localization.dart';
@ -28,6 +30,23 @@ class ChatAppBarTitle extends StatelessWidget {
// ),
// );
// }
if (controller.room.showActivityChatUI) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
spacing: 4.0,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
controller.room.getLocalizedDisplayname(),
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
ActivityStatsButton(controller: controller),
],
),
);
}
// Pangea#
return InkWell(
hoverColor: Colors.transparent,

@ -15,7 +15,7 @@ import 'package:fluffychat/pages/chat/chat_event_list.dart';
import 'package:fluffychat/pages/chat/pinned_events.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_pinned_message.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/load_activity_summary_widget.dart';
import 'package:fluffychat/pangea/chat/widgets/chat_input_bar.dart';
import 'package:fluffychat/pangea/chat/widgets/chat_input_bar_header.dart';
@ -216,6 +216,9 @@ class ChatView extends StatelessWidget {
// backgroundColor: controller.selectedEvents.isEmpty
// ? null
// : theme.colorScheme.tertiaryContainer,
toolbarHeight:
controller.room.showActivityChatUI ? 106.0 : null,
centerTitle: controller.room.showActivityChatUI,
// Pangea#
automaticallyImplyLeading: false,
leading: controller.selectMode
@ -254,6 +257,13 @@ class ChatView extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// #Pangea
if (!controller.showActivityDropdown)
Divider(
height: 1,
color: theme.dividerColor,
),
// Pangea#
PinnedEvents(controller),
if (scrollUpBannerEventId != null)
ChatAppBarListTile(
@ -480,7 +490,7 @@ class ChatView extends StatelessWidget {
],
),
),
ActivityPinnedMessage(controller),
ActivityStatsMenu(controller),
// Pangea#
],
),

@ -118,7 +118,14 @@ class ActivityPlanModel {
/// use target emoji for learning objective
/// use step emoji for instructions
String get markdown {
String markdown = '''🎯 $learningObjective \n🪜 $instructions \n\n📖''';
final String markdown =
'''🎯 $learningObjective \n🪜 $instructions \n\n📖 $vocabString''';
return markdown;
}
String get vocabString {
final List<String> vocabList = [];
String vocabString = "";
// cycle through vocab with index
for (var i = 0; i < vocab.length; i++) {
// if the lemma appears more than once in the vocab list, show the pos
@ -126,10 +133,25 @@ class ActivityPlanModel {
final v = vocab[i];
final bool showPos =
vocab.where((vocab) => vocab.lemma == v.lemma).length > 1;
markdown +=
vocabString +=
'${v.lemma}${showPos ? ' (${v.pos})' : ''}${i + 1 < vocab.length ? ', ' : ''}';
vocabList.add("${v.lemma}${showPos ? ' (${v.pos})' : ''}");
}
return markdown;
return vocabString;
}
List get vocabList {
final List<String> vocabList = [];
// cycle through vocab with index
for (var i = 0; i < vocab.length; i++) {
// if the lemma appears more than once in the vocab list, show the pos
// vocab is a wrapped list of string, separated by commas
final v = vocab[i];
final bool showPos =
vocab.where((vocab) => vocab.lemma == v.lemma).length > 1;
vocabList.add("${v.lemma}${showPos ? ' (${v.pos})' : ''}");
}
return vocabList;
}
@override

@ -1,216 +0,0 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.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/chat_app_bar_list_tile.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_constants.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 ActivityPinnedMessage extends StatefulWidget {
final ChatController controller;
const ActivityPinnedMessage(this.controller, {super.key});
@override
State<ActivityPinnedMessage> createState() => ActivityPinnedMessageState();
}
class ActivityPinnedMessageState extends State<ActivityPinnedMessage> {
bool _showDropdown = false;
Room get room => widget.controller.room;
void _scrollToActivity() {
final eventId = widget.controller.timeline?.events
.firstWhereOrNull(
(e) => e.type == PangeaEventTypes.activityPlan,
)
?.eventId;
if (eventId == null) return;
widget.controller.scrollToEventId(eventId);
}
void _setShowDropdown(bool value) {
if (value != _showDropdown) {
setState(() {
_showDropdown = value;
});
}
}
Future<void> _finishActivity({bool forAll = false}) async {
await showFutureLoadingDialog(
context: context,
future: () async {
forAll
? await room.finishActivityForAll()
: await room.finishActivity();
if (mounted) {
_setShowDropdown(false);
}
},
);
}
@override
Widget build(BuildContext context) {
// if the room has no activity, or if it doesn't have the permission
// levels for sending the required events, don't show the pinned message
if (!room.isActiveInActivity) {
return const SizedBox.shrink();
}
final theme = Theme.of(context);
final isColumnMode = FluffyThemes.isColumnMode(context);
return Positioned(
top: 0,
left: 0,
right: 0,
bottom: _showDropdown ? 0 : null,
child: Column(
children: [
AnimatedContainer(
duration: FluffyThemes.animationDuration,
decoration: BoxDecoration(
color: _showDropdown
? theme.colorScheme.surfaceContainerHighest
: theme.colorScheme.surface,
),
child: ChatAppBarListTile(
title: "🎯 ${room.activityPlan!.learningObjective}",
leading: const SizedBox(width: 18.0),
trailing: Padding(
padding: const EdgeInsets.only(right: 12.0),
child: ElevatedButton(
onPressed:
_showDropdown ? null : () => _setShowDropdown(true),
style: ElevatedButton.styleFrom(
minimumSize: Size.zero,
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 4.0,
),
backgroundColor: AppConfig.yellowDark,
foregroundColor: theme.colorScheme.surface,
disabledBackgroundColor:
AppConfig.yellowDark.withAlpha(100),
disabledForegroundColor:
theme.colorScheme.surface.withAlpha(100),
),
child: Text(
L10n.of(context).endActivityTitle,
style: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w900,
),
),
),
),
onTap: _scrollToActivity,
),
),
AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: Curves.easeInOut,
child: ClipRect(
child: _showDropdown
? Container(
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHighest,
),
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 16.0,
),
child: Column(
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
children: [
Text(
L10n.of(context).endActivityDesc,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: isColumnMode ? 16.0 : 12.0,
),
),
CachedNetworkImage(
imageUrl:
"${AppConfig.assetsBaseURL}/${ActivitySessionConstants.endActivityAssetPath}",
width: isColumnMode ? 240.0 : 120.0,
),
Row(
spacing: 12.0,
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
foregroundColor:
theme.colorScheme.onSecondary,
backgroundColor:
theme.colorScheme.secondary,
),
onPressed: _finishActivity,
child: Text(
L10n.of(context).endActivityTitle,
style: TextStyle(
fontSize: isColumnMode ? 16.0 : 12.0,
),
),
),
),
if (room.isRoomAdmin)
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
foregroundColor:
theme.colorScheme.onErrorContainer,
backgroundColor:
theme.colorScheme.errorContainer,
),
onPressed: () =>
_finishActivity(forAll: true),
child: Text(
L10n.of(context).endForAll,
style: TextStyle(
fontSize: isColumnMode ? 16.0 : 12.0,
),
),
),
),
],
),
],
),
)
: const SizedBox.shrink(),
),
),
if (_showDropdown)
Expanded(
child: GestureDetector(
onTap: () => _setShowDropdown(false),
child: Container(color: Colors.black.withAlpha(100)),
),
),
],
),
);
}
}

@ -0,0 +1,145 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_summary/activity_summary_analytics_model.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
class ActivityStatsButton extends StatefulWidget {
final ChatController controller;
const ActivityStatsButton({
super.key,
required this.controller,
});
@override
State<ActivityStatsButton> createState() => _ActivityStatsButtonState();
}
class _ActivityStatsButtonState extends State<ActivityStatsButton> {
StreamSubscription? _analyticsSubscription;
ActivitySummaryAnalyticsModel analytics = ActivitySummaryAnalyticsModel();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) => _updateAnalytics(),
);
_analyticsSubscription = widget
.controller.pangeaController.getAnalytics.analyticsStream.stream
.listen((_) {
_updateAnalytics();
});
}
@override
void dispose() {
_analyticsSubscription?.cancel();
super.dispose();
}
int get xpCount => analytics.totalXPForUser(
Matrix.of(context).client.userID ?? '',
);
int get vocabCount => analytics.uniqueConstructCountForUser(
widget.controller.room.client.userID!,
ConstructTypeEnum.vocab,
);
int get grammarCount => analytics.uniqueConstructCountForUser(
widget.controller.room.client.userID!,
ConstructTypeEnum.morph,
);
Future<void> _updateAnalytics() async {
final analytics = await widget.controller.room.getActivityAnalytics();
if (mounted) {
setState(() => this.analytics = analytics);
}
}
@override
Widget build(BuildContext context) {
return Container(
width: 350,
height: 55,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => widget.controller.setShowDropdown(
!widget.controller.showActivityDropdown,
),
child: Container(
decoration: BoxDecoration(
color: AppConfig.goldLight.withAlpha(100),
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_StatsBadge(icon: Icons.radar, value: "$xpCount XP"),
_StatsBadge(icon: Symbols.dictionary, value: "$vocabCount"),
_StatsBadge(
icon: Symbols.toys_and_games,
value: "$grammarCount",
),
],
),
),
),
);
}
}
class _StatsBadge extends StatelessWidget {
final IconData icon;
final String value;
const _StatsBadge({
required this.icon,
required this.value,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final screenWidth = MediaQuery.of(context).size.width;
final baseStyle = theme.textTheme.bodyMedium;
final double fontSize = (screenWidth < 400) ? 10 : 14;
final double iconSize = (screenWidth < 400) ? 14 : 18;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: iconSize,
color: theme.colorScheme.onSurface,
),
const SizedBox(width: 4),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
value,
style: baseStyle?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface,
fontSize: fontSize,
),
),
],
),
],
);
}
}

@ -0,0 +1,341 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.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/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_details_row.dart';
import 'package:fluffychat/pangea/activity_summary/activity_summary_analytics_model.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
class ActivityStatsMenu extends StatefulWidget {
final ChatController controller;
const ActivityStatsMenu(
this.controller, {
super.key,
});
@override
State<ActivityStatsMenu> createState() => ActivityStatsMenuState();
}
class ActivityStatsMenuState extends State<ActivityStatsMenu> {
ActivitySummaryAnalyticsModel? analytics;
Room get room => widget.controller.room;
@override
void dispose() {
super.dispose();
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateUsedVocab();
});
}
Set<String>? get _usedVocab => analytics?.constructs[room.client.userID!]
?.constructsOfType(ConstructTypeEnum.vocab)
.map((id) => id.lemma)
.toSet();
double get _percentVocabComplete {
final vocabList = room.activityPlan?.vocabList ?? [];
if (vocabList.isEmpty || _usedVocab == null) {
return 0;
}
return _usedVocab!.intersection(vocabList.toSet()).length /
vocabList.length;
}
Future<void> _updateUsedVocab() async {
final analytics = await room.getActivityAnalytics();
if (mounted) {
setState(() => this.analytics = analytics);
}
}
int _getAssignedRolesCount() {
final assignedRoles = room.assignedRoles;
if (assignedRoles == null) return 0;
final nonBotRoles = assignedRoles.values.where(
(role) => role.userId != BotName.byEnvironment,
);
return nonBotRoles.length;
}
int _getCompletedRolesCount() {
final assignedRoles = room.assignedRoles;
if (assignedRoles == null) return 0;
// Filter out the bot and count only finished non-bot roles
return assignedRoles.values
.where(
(role) => role.userId != BotName.byEnvironment && role.isFinished,
)
.length;
}
bool _isBotParticipant() {
final assignedRoles = room.assignedRoles;
if (assignedRoles == null) return false;
return assignedRoles.values.any(
(role) => role.userId == BotName.byEnvironment,
);
}
Future<void> _finishActivity({bool forAll = false}) async {
await showFutureLoadingDialog(
context: context,
future: () async {
forAll
? await room.finishActivityForAll()
: await room.finishActivity();
if (mounted) {
widget.controller.setShowDropdown(false);
}
},
);
}
@override
Widget build(BuildContext context) {
if (!room.showActivityChatUI) {
return const SizedBox.shrink();
}
final theme = Theme.of(context);
final isColumnMode = FluffyThemes.isColumnMode(context);
// Completion status variables
final bool userComplete = room.hasCompletedActivity;
final bool activityComplete = room.activityIsFinished;
bool shouldShowEndForAll = true;
bool shouldShowImDone = true;
String message = "";
if (!room.isRoomAdmin) {
shouldShowEndForAll = false;
}
//dont need endforall if only w bot
if ((_getAssignedRolesCount() == 1) && (_isBotParticipant() == true)) {
shouldShowEndForAll = false;
}
if (activityComplete) {
//activity is finished, no buttons
shouldShowImDone = false;
shouldShowEndForAll = false;
message = L10n.of(context).activityComplete;
} else {
//activity is ongoing
if (_getCompletedRolesCount() == 0 ||
(_getAssignedRolesCount() == 1) && (_isBotParticipant() == true)) {
//IF nobodys done or you're only playing with the bot,
//Then it should show tips about your progress and nudge you to continue/end
if ((_percentVocabComplete < .7) && (_usedVocab?.length ?? 0) < 50) {
message = L10n.of(context).haventChattedMuch;
} else {
message = L10n.of(context).haveChatted;
}
} else {
//user is in group with other users OR someone has wrapped up
if (userComplete) {
//user is done but group is ongoing, no buttons
message = L10n.of(context).userDoneAndWaiting(
_getCompletedRolesCount(),
_getAssignedRolesCount(),
);
} else {
//user is not done, buttons are present
message = L10n.of(context).othersDoneAndWaiting(
_getCompletedRolesCount(),
_getAssignedRolesCount(),
);
}
}
}
return Positioned(
top: 0,
left: 0,
right: 0,
bottom: widget.controller.showActivityDropdown ? 0 : null,
child: Column(
children: [
ClipRect(
child: AnimatedAlign(
duration: FluffyThemes.animationDuration,
curve: Curves.easeInOut,
heightFactor: widget.controller.showActivityDropdown ? 1.0 : 0.0,
alignment: Alignment.topCenter,
child: GestureDetector(
onPanUpdate: (details) {
if (details.delta.dy < -2) {
widget.controller.setShowDropdown(false);
}
},
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: theme.colorScheme.surface,
),
padding: const EdgeInsets.all(12.0),
child: Column(
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
children: [
Column(
spacing: 8.0,
mainAxisSize: MainAxisSize.min,
children: [
ActivitySessionDetailsRow(
icon: Symbols.radar,
iconSize: 16.0,
child: Text(
room.activityPlan!.learningObjective,
style: const TextStyle(fontSize: 12.0),
),
),
ActivitySessionDetailsRow(
icon: Symbols.dictionary,
iconSize: 16.0,
child: Wrap(
spacing: 4.0,
runSpacing: 4.0,
children: [
...room.activityPlan!.vocabList.map(
(vocabWord) => VocabTile(
vocabWord: vocabWord,
isUsed:
(_usedVocab ?? {}).contains(vocabWord),
),
),
],
),
),
],
),
Text(
message,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w600,
),
),
if (!userComplete) ...[
if (shouldShowEndForAll)
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
side: BorderSide(
color: theme.colorScheme.secondaryContainer,
width: 2,
),
foregroundColor: theme.colorScheme.primary,
backgroundColor: theme.colorScheme.surface,
),
onPressed: () => _finishActivity(forAll: true),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
L10n.of(context).endForAll,
style: TextStyle(
fontSize: isColumnMode ? 16.0 : 12.0,
),
),
],
),
),
if (shouldShowImDone)
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
),
onPressed: _finishActivity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
L10n.of(context).endActivityTitle,
style: TextStyle(
fontSize: isColumnMode ? 16.0 : 12.0,
),
),
],
),
),
],
],
),
),
),
),
),
if (widget.controller.showActivityDropdown)
Expanded(
child: GestureDetector(
onTap: () => widget.controller.setShowDropdown(false),
child: Container(color: Colors.black.withAlpha(100)),
),
),
],
),
);
}
}
class VocabTile extends StatelessWidget {
final String vocabWord;
final bool isUsed;
const VocabTile({
super.key,
required this.vocabWord,
required this.isUsed,
});
@override
Widget build(BuildContext context) {
final color =
isUsed ? AppConfig.goldLight.withAlpha(100) : Colors.transparent;
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(20),
),
child: Text(
vocabWord,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontSize: 14.0,
),
),
);
}
}

@ -32,6 +32,17 @@ class ActivitySummaryAnalyticsModel {
return userAnalytics.constructsOfType(type).length;
}
int totalXPForUser(String userId) {
final userAnalytics = constructs[userId];
if (userAnalytics == null) return 0;
int totalXP = 0;
for (final usage in userAnalytics.usages.values) {
totalXP += usage.timesUsed;
}
return totalXP;
}
void addConstructs(PangeaMessageEvent event) {
final uses = event.originalSent?.vocabAndMorphUses();
if (uses == null || uses.isEmpty) return;

@ -9,6 +9,7 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart';
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
import 'package:fluffychat/pangea/chat_settings/pages/room_details_buttons.dart';
@ -128,13 +129,15 @@ class ChatDetailsButtonRowState extends State<ChatDetailsButtonRow> {
onSubmit: widget.controller.setBotOptions,
),
),
visible: !room.isDirectChat || room.botOptions != null,
visible: (!room.isDirectChat || room.botOptions != null) &&
!room.showActivityChatUI,
enabled: room.canInvite,
),
ButtonDetails(
title: l10n.chatCapacity,
icon: const Icon(Icons.reduce_capacity, size: 30.0),
onPressed: widget.controller.setRoomCapacity,
visible: !room.showActivityChatUI,
enabled: !room.isDirectChat && room.canSendDefaultStates,
showInMainView: false,
),

Loading…
Cancel
Save