diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 4ccb42939..3aed42f1a 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3875,5 +3875,15 @@ "placeholders": {} }, "define": "Define", - "listen": "Listen" + "listen": "Listen", + "addConversationBot": "Enable Conversation Bot", + "addConversationBotDesc": "Add a bot to this group chat that will ask questions on a specific topic", + "convoBotSettingsTitle": "Conversation Bot Settings", + "convoBotSettingsDescription": "Edit conversation topic and difficulty", + "enterAConversationTopic": "Enter a conversation topic", + "conversationTopic": "Conversation topic", + "enableModeration": "Enable moderation", + "enableModerationDesc": "Enable automatic moderation to review messages before they are sent", + "conversationLanguageLevel": "What is the language level of this conversation?", + "showDefinition": "Show Definition" } \ No newline at end of file diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 471e79f4c..71daf4635 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -2872,8 +2872,8 @@ "type": "text", "placeholders": {} }, - "langaugeLevel": "Nivel de lengua", - "@langaugeLevel": { + "languageLevel": "Nivel de lengua", + "@languageLevel": { "type": "text", "placeholders": {} }, @@ -4534,5 +4534,14 @@ "placeholders": { "roomName": {} } - } + }, + "addConversationBot": "Añadir bot de conversación", + "addConversationBotDesc": "Añadir un bot de conversación para enviar mensajes automáticos a este chat", + "convoBotSettingsTitle": "Configuración del bot de conversación", + "convoBotSettingsDescription": "Editar tema de conversación y dificultad", + "enterAConversationTopic": "Introducir un tema de conversación", + "conversationTopic": "Tema de conversación", + "enableModeration": "Activar la moderación", + "enableModerationDesc": "Activar la moderación automática para revisar los mensajes antes de enviarlos", + "conversationLanguageLevel": "¿Cuál es el nivel lingüístico de esta conversación?" } \ No newline at end of file diff --git a/ios/FluffyChat Share/FluffyChat Share.entitlements b/ios/FluffyChat Share/FluffyChat Share.entitlements index 932f3e01e..2eb7e333a 100644 --- a/ios/FluffyChat Share/FluffyChat Share.entitlements +++ b/ios/FluffyChat Share/FluffyChat Share.entitlements @@ -3,8 +3,6 @@ com.apple.security.application-groups - - group.im.fluffychat.app - + diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d245..8c6e56146 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e5c2d6a2b..73d46da15 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -457,7 +457,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -546,7 +546,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -595,7 +595,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index b52b2e698..a6b826db2 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -27,8 +27,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + - - { // #Pangea final GlobalKey addToSpaceKey = GlobalKey(); + final GlobalKey addConversationBotKey = + GlobalKey(); bool displayAddStudentOptions = false; void toggleAddStudentOptions() => @@ -424,5 +425,11 @@ class ChatDetailsController extends State { bool showEditNameIcon = false; void hoverEditNameIcon(bool hovering) => setState(() => showEditNameIcon = !showEditNameIcon); + + @override + void initState() { + super.initState(); + MatrixState.pangeaController.classController.addMissingRoomRules(roomId); + } // Pangea# } diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index d51c488c4..a095d49fd 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/utils/archive_space.dart'; import 'package:fluffychat/pangea/utils/lock_room.dart'; import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart'; import 'package:fluffychat/pangea/widgets/space/class_settings.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -489,6 +490,13 @@ class ChatDetailsView extends StatelessWidget { if (controller.displayAddStudentOptions && room.showClassEditOptions) ClassInvitationButtons(roomId: controller.roomId!), + const Divider(height: 1), + if (!room.isSpace && room.canInvite) + ConversationBotSettings( + key: controller.addConversationBotKey, + room: room, + ), + const Divider(height: 1), if (!room.isPangeaClass) AddToSpaceToggles( roomId: room.id, diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index b01186a53..b4633ea7d 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -1,20 +1,20 @@ import 'dart:typed_data'; -import 'package:flutter/material.dart'; - import 'package:file_picker/file_picker.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart' as sdk; - import 'package:fluffychat/pages/new_group/new_group_view.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/chat_topic_model.dart'; import 'package:fluffychat/pangea/models/lemma.dart'; +import 'package:fluffychat/pangea/utils/bot_name.dart'; import 'package:fluffychat/pangea/utils/class_chat_power_levels.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; +import 'package:matrix/matrix.dart' as sdk; class NewGroup extends StatefulWidget { const NewGroup({super.key}); @@ -42,6 +42,8 @@ class NewGroupController extends State { // #Pangea PangeaController pangeaController = MatrixState.pangeaController; final GlobalKey addToSpaceKey = GlobalKey(); + final GlobalKey addConversationBotKey = + GlobalKey(); ChatTopic chatTopic = ChatTopic.empty; @@ -121,6 +123,10 @@ class NewGroupController extends State { .map((suggestionStatus) => suggestionStatus.room) .toList(), ), + invite: [ + if (addConversationBotKey.currentState?.addBot ?? false) + BotName.byEnvironment, + ], // Pangea# ); if (!mounted) return; diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index 72d663618..eb0fa605d 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -1,14 +1,13 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/new_group/new_group.dart'; import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; class NewGroupView extends StatelessWidget { final NewGroupController controller; @@ -85,6 +84,10 @@ class NewGroupView extends StatelessWidget { // ), // ), // ), + ConversationBotSettings( + key: controller.addConversationBotKey, + ), + const Divider(height: 1), AddToSpaceToggles( key: controller.addToSpaceKey, startOpen: false, diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index 094ccaa84..d027bce1d 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -89,4 +89,10 @@ class ModelKey { static const String feedbackLang = "feedback_lang"; static const String transcription = "transcription"; + + // bot options + static const String languageLevel = "difficulty"; + static const String conversationTopic = "conversation_topic"; + static const String keywords = "keywords"; + static const String safetyModeration = "safety_moderation"; } diff --git a/lib/pangea/constants/pangea_event_types.dart b/lib/pangea/constants/pangea_event_types.dart index b98492871..3eb642a0c 100644 --- a/lib/pangea/constants/pangea_event_types.dart +++ b/lib/pangea/constants/pangea_event_types.dart @@ -15,4 +15,5 @@ class PangeaEventTypes { static const roomInfo = "pangea.roomtopic"; static const audio = "p.audio"; + static const botOptions = "pangea.bot_options"; } diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart index c91eab1c0..6d028fda2 100644 --- a/lib/pangea/controllers/class_controller.dart +++ b/lib/pangea/controllers/class_controller.dart @@ -3,9 +3,11 @@ import 'dart:developer'; import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/local.key.dart'; +import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/extensions/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/models/class_model.dart'; import 'package:fluffychat/pangea/utils/class_code.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; @@ -156,4 +158,24 @@ class ClassController extends BaseController { // BE - check class code and if class code is correct, invite student to room // FE - look for invite from room and automatically accept } + + Future addMissingRoomRules(String? roomId) async { + if (roomId == null) return; + final Room? room = _pangeaController.matrixState.client.getRoomById(roomId); + if (room == null) return; + + if (room.classSettings != null && room.pangeaRoomRules == null) { + try { + await _pangeaController.matrixState.client.setRoomStateWithKey( + roomId, + PangeaEventTypes.rules, + '', + PangeaRoomRules().toJson(), + ); + } catch (err, stack) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: stack); + } + } + } } diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index f15a9e87d..8901085db 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; +import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/models/class_model.dart'; import 'package:fluffychat/pangea/models/pangea_message_event.dart'; import 'package:fluffychat/pangea/utils/bot_name.dart'; @@ -980,4 +981,18 @@ extension PangeaRoom on Room { if (!isSpace) return null; return pangeaRoomRulesStateEvent?.originServerTs ?? creationTime; } + + Future get isBotRoom async { + final List participants = await requestParticipants(); + return participants.any( + (User user) => user.id == BotName.byEnvironment, + ); + } + + BotOptionsModel? get botOptions { + if (isSpace) return null; + return BotOptionsModel.fromJson( + getState(PangeaEventTypes.botOptions)?.content ?? {}, + ); + } } diff --git a/lib/pangea/models/bot_options_model.dart b/lib/pangea/models/bot_options_model.dart new file mode 100644 index 000000000..0cba5fd78 --- /dev/null +++ b/lib/pangea/models/bot_options_model.dart @@ -0,0 +1,72 @@ +import 'dart:developer'; + +import 'package:fluffychat/pangea/constants/model_keys.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:flutter/foundation.dart'; +import 'package:matrix/matrix.dart'; + +import '../constants/pangea_event_types.dart'; + +class BotOptionsModel { + int? languageLevel; + String topic; + List keywords; + bool safetyModeration; + + BotOptionsModel({ + this.languageLevel, + this.topic = "General Conversation", + this.keywords = const [], + this.safetyModeration = true, + }); + + factory BotOptionsModel.fromJson(json) { + return BotOptionsModel( + languageLevel: json[ModelKey.languageLevel], + topic: json[ModelKey.conversationTopic] ?? "General Conversation", + keywords: (json[ModelKey.keywords] ?? []).cast(), + safetyModeration: json[ModelKey.safetyModeration] ?? true, + ); + } + + Map toJson() { + final data = {}; + try { + // data[ModelKey.isConversationBotChat] = isConversationBotChat; + data[ModelKey.languageLevel] = languageLevel; + data[ModelKey.conversationTopic] = topic; + data[ModelKey.keywords] = keywords; + data[ModelKey.safetyModeration] = safetyModeration; + return data; + } catch (e, s) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: e, s: s); + return data; + } + } + + //TODO: define enum with all possible values + updateBotOption(String key, dynamic value) { + switch (key) { + case ModelKey.languageLevel: + languageLevel = value; + break; + case ModelKey.conversationTopic: + topic = value; + break; + case ModelKey.keywords: + keywords = value; + break; + case ModelKey.safetyModeration: + safetyModeration = value; + break; + default: + throw Exception('Invalid key for bot options - $key'); + } + } + + StateEvent get toStateEvent => StateEvent( + content: toJson(), + type: PangeaEventTypes.botOptions, + ); +} diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 4dc2ac4b6..690593fcc 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/models/pangea_message_event.dart'; @@ -161,8 +162,8 @@ class MessageToolbarState extends State { case MessageMode.play: return true; case MessageMode.definition: - debugPrint("checking"); - return widget.textSelection.selectedText != null; + // return widget.textSelection.selectedText != null; + return true; default: return false; } diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 068908ba6..8bc1b5cf0 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -1,8 +1,9 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/events/message_content.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; +import 'package:fluffychat/pangea/enum/use_type.dart'; import 'package:fluffychat/pangea/models/pangea_message_event.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; +import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; @@ -102,16 +103,64 @@ class OverlayMessage extends StatelessWidget { constraints: BoxConstraints( maxWidth: width ?? FluffyThemes.columnWidth * 1.25, ), - child: MessageContent( - event, - textColor: textColor, - borderRadius: borderRadius, - selected: selected, - pangeaMessageEvent: pangeaMessageEvent, - // selectedDisplayLang: selectedDisplayLang, - immersionMode: immersionMode, - // definitions: definitions, - toolbarController: toolbarController, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MessageContent( + event, + textColor: textColor, + borderRadius: borderRadius, + selected: selected, + pangeaMessageEvent: pangeaMessageEvent, + immersionMode: immersionMode, + toolbarController: toolbarController, + ), + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + ) // #Pangea + || + (pangeaMessageEvent.showUseType) + // Pangea# + ) + Padding( + padding: const EdgeInsets.only( + top: 4.0, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // #Pangea + if (pangeaMessageEvent.showUseType) ...[ + pangeaMessageEvent.useType.iconView( + context, + textColor.withAlpha(164), + ), + const SizedBox(width: 4), + ], + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + )) ...[ + // Pangea# + Icon( + Icons.edit_outlined, + color: textColor.withAlpha(164), + size: 14, + ), + Text( + ' - ${event.originServerTs.localizedTimeShort(context)}', + style: TextStyle( + color: textColor.withAlpha(164), + fontSize: 12, + ), + ), + ], + ], + ), + ), + ], ), ), ); diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart new file mode 100644 index 000000000..a9171a11d --- /dev/null +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart @@ -0,0 +1,245 @@ +import 'dart:developer'; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/models/bot_options_model.dart'; +import 'package:fluffychat/pangea/utils/bot_name.dart'; +import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart'; +import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; +import 'package:matrix/matrix.dart'; + +import '../../../widgets/matrix.dart'; +import '../../constants/pangea_event_types.dart'; +import '../../extensions/pangea_room_extension.dart'; +import '../../utils/error_handler.dart'; + +class ConversationBotSettings extends StatefulWidget { + final Room? room; + final bool startOpen; + // final ClassSettingsModel? initialSettings; + + const ConversationBotSettings({ + super.key, + this.room, + this.startOpen = false, + // this.initialSettings, + }); + + @override + ConversationBotSettingsState createState() => ConversationBotSettingsState(); +} + +class ConversationBotSettingsState extends State { + late BotOptionsModel botOptions; + late bool isOpen; + bool addBot = false; + + ConversationBotSettingsState({Key? key}); + + @override + void initState() { + super.initState(); + isOpen = widget.startOpen; + botOptions = widget.room?.botOptions ?? BotOptionsModel(); + widget.room?.isBotRoom.then((bool isBotRoom) { + setState(() { + addBot = isBotRoom; + }); + }); + } + + Future updateBotOption(void Function() makeLocalChange) async { + makeLocalChange(); + await showFutureLoadingDialog( + context: context, + future: () async { + try { + await setBotOption(); + } catch (err, stack) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: stack); + } + setState(() {}); + }, + ); + } + + Future setBotOption() async { + if (widget.room == null) return; + try { + await Matrix.of(context).client.setRoomStateWithKey( + widget.room!.id, + PangeaEventTypes.botOptions, + '', + botOptions.toJson(), + ); + } catch (err, stack) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: stack); + } + } + + @override + Widget build(BuildContext context) => Column( + children: [ + ListTile( + title: Text( + L10n.of(context)!.convoBotSettingsTitle, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text(L10n.of(context)!.convoBotSettingsDescription), + leading: CircleAvatar( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + foregroundColor: Theme.of(context).textTheme.bodyLarge!.color, + child: const Icon(Icons.psychology_outlined), + ), + trailing: Icon( + isOpen + ? Icons.keyboard_arrow_down_outlined + : Icons.keyboard_arrow_right_outlined, + ), + onTap: () => setState(() => isOpen = !isOpen), + ), + if (isOpen) + AnimatedContainer( + duration: const Duration(milliseconds: 300), + height: isOpen ? null : 0, + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16), + child: SwitchListTile.adaptive( + title: Text( + L10n.of(context)!.addConversationBot, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text(L10n.of(context)!.addConversationBotDesc), + secondary: CircleAvatar( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + foregroundColor: + Theme.of(context).textTheme.bodyLarge!.color, + child: const BotFace( + width: 30.0, + expression: BotExpression.right, + ), + ), + activeColor: AppConfig.activeToggleColor, + value: addBot, + onChanged: (bool add) { + setState(() => addBot = add); + add + ? widget.room?.invite(BotName.byEnvironment) + : widget.room?.kick(BotName.byEnvironment); + }, + ), + ), + if (addBot) ...[ + Padding( + padding: const EdgeInsets.only(left: 16), + child: ListTile( + onTap: () async { + final topic = await showTextInputDialog( + context: context, + textFields: [ + DialogTextField( + initialText: botOptions.topic.isEmpty + ? "" + : botOptions.topic, + hintText: + L10n.of(context)!.enterAConversationTopic, + ), + ], + title: L10n.of(context)!.conversationTopic, + ); + if (topic == null) return; + updateBotOption(() { + botOptions.topic = topic.single; + }); + }, + leading: CircleAvatar( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + foregroundColor: + Theme.of(context).textTheme.bodyLarge!.color, + child: const Icon(Icons.topic_outlined), + ), + subtitle: Text( + botOptions.topic.isEmpty + ? L10n.of(context)!.enterAConversationTopic + : botOptions.topic, + ), + title: Text( + L10n.of(context)!.conversationTopic, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 16), + child: SwitchListTile.adaptive( + title: Text( + L10n.of(context)!.enableModeration, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text(L10n.of(context)!.enableModerationDesc), + secondary: CircleAvatar( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + foregroundColor: + Theme.of(context).textTheme.bodyLarge!.color, + child: const Icon(Icons.shield_outlined), + ), + activeColor: AppConfig.activeToggleColor, + value: botOptions.safetyModeration, + onChanged: (bool newValue) => updateBotOption(() { + botOptions.safetyModeration = newValue; + }), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(32, 16, 0, 0), + child: Text( + L10n.of(context)!.conversationLanguageLevel, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 16), + child: LanguageLevelDropdown( + initialLevel: botOptions.languageLevel, + onChanged: (int? newValue) => updateBotOption(() { + botOptions.languageLevel = newValue!; + }), + ), + ), + const SizedBox(height: 16), + ], + ], + ), + ), + ], + ); +} diff --git a/lib/pangea/widgets/space/class_settings.dart b/lib/pangea/widgets/space/class_settings.dart index 15025be32..c9851ff03 100644 --- a/lib/pangea/widgets/space/class_settings.dart +++ b/lib/pangea/widgets/space/class_settings.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'package:fluffychat/pangea/models/class_model.dart'; +import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -9,14 +10,12 @@ import 'package:matrix/matrix.dart'; import '../../../widgets/matrix.dart'; import '../../constants/language_keys.dart'; -import '../../constants/language_level_type.dart'; import '../../constants/pangea_event_types.dart'; import '../../controllers/language_list_controller.dart'; import '../../controllers/pangea_controller.dart'; import '../../extensions/pangea_room_extension.dart'; import '../../models/language_model.dart'; import '../../utils/error_handler.dart'; -import '../../utils/language_level_copy.dart'; import '../user_settings/p_language_dropdown.dart'; import '../user_settings/p_question_container.dart'; @@ -169,72 +168,11 @@ class ClassSettingsState extends State { PQuestionContainer( title: L10n.of(context)!.whatIsYourClassLanguageLevel, ), - Padding( - padding: const EdgeInsets.all(12.0), - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.secondary, - width: 0.5, - ), - borderRadius: - const BorderRadius.all(Radius.circular(10)), - ), - child: DropdownButton( - // Initial Value - hint: Padding( - padding: const EdgeInsets.only(left: 15), - child: Text( - classSettings.languageLevel == null - ? L10n.of(context)!.selectLanguageLevel - : LanguageLevelTextPicker.languageLevelText( - context, - classSettings.languageLevel!, - ), - style: const TextStyle().copyWith( - color: Theme.of(context) - .textTheme - .bodyLarge! - .color, - fontSize: 14, - ), - overflow: TextOverflow.clip, - textAlign: TextAlign.center, - ), - ), - isExpanded: true, - underline: Container(), - // Down Arrow Icon - icon: const Icon(Icons.keyboard_arrow_down), - // Array list of items - items: - LanguageLevelType.allInts.map((int levelOption) { - return DropdownMenuItem( - value: levelOption, - child: Text( - LanguageLevelTextPicker.languageLevelText( - context, - levelOption, - ), - style: const TextStyle().copyWith( - color: Theme.of(context) - .textTheme - .bodyLarge! - .color, - fontSize: 14, - ), - overflow: TextOverflow.clip, - textAlign: TextAlign.center, - ), - ); - }).toList(), - // After selecting the desired option,it will - // change button value to selected value - onChanged: (int? newValue) => updatePermission(() { - classSettings.languageLevel = newValue!; - }), - ), - ), + LanguageLevelDropdown( + initialLevel: classSettings.languageLevel, + onChanged: (int? newValue) => updatePermission(() { + classSettings.languageLevel = newValue!; + }), ), ], ), diff --git a/lib/pangea/widgets/space/language_level_dropdown.dart b/lib/pangea/widgets/space/language_level_dropdown.dart new file mode 100644 index 000000000..3b2678e29 --- /dev/null +++ b/lib/pangea/widgets/space/language_level_dropdown.dart @@ -0,0 +1,76 @@ +import 'package:fluffychat/pangea/constants/language_level_type.dart'; +import 'package:fluffychat/pangea/utils/language_level_copy.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class LanguageLevelDropdown extends StatelessWidget { + final int? initialLevel; + final void Function(int?)? onChanged; + + const LanguageLevelDropdown({ + super.key, + this.initialLevel, + this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(12.0), + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.secondary, + width: 0.5, + ), + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + child: DropdownButton( + // Initial Value + hint: Padding( + padding: const EdgeInsets.only(left: 15), + child: Text( + initialLevel == null + ? L10n.of(context)!.selectLanguageLevel + : LanguageLevelTextPicker.languageLevelText( + context, + initialLevel!, + ), + style: const TextStyle().copyWith( + color: Theme.of(context).textTheme.bodyLarge!.color, + fontSize: 14, + ), + overflow: TextOverflow.clip, + textAlign: TextAlign.center, + ), + ), + isExpanded: true, + underline: Container(), + // Down Arrow Icon + icon: const Icon(Icons.keyboard_arrow_down), + // Array list of items + items: LanguageLevelType.allInts.map((int levelOption) { + return DropdownMenuItem( + value: levelOption, + child: Text( + LanguageLevelTextPicker.languageLevelText( + context, + levelOption, + ), + style: const TextStyle().copyWith( + color: Theme.of(context).textTheme.bodyLarge!.color, + fontSize: 14, + ), + overflow: TextOverflow.clip, + textAlign: TextAlign.center, + ), + ); + }).toList(), + // After selecting the desired option,it will + // change button value to selected value + onChanged: onChanged, + ), + ), + ); + } +} diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 517c852ec..52f6a968c 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -115,6 +115,7 @@ abstract class ClientManager { PangeaEventTypes.classSettings, PangeaEventTypes.rules, PangeaEventTypes.vocab, + PangeaEventTypes.botOptions, EventTypes.RoomTopic, EventTypes.RoomAvatar, // Pangea# diff --git a/pubspec.yaml b/pubspec.yaml index 24f5e0ac8..b36f4c64c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -142,7 +142,6 @@ flutter: generate: true uses-material-design: true assets: - - .env - assets/ # #Pangea - assets/pangea/