From 3f832a7c058c8e3c2f460b1b38e2273241cb1031 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 23 Jan 2024 16:07:18 -0500 Subject: [PATCH 1/2] frontend elements of conversaton bot settings --- assets/l10n/intl_en.arb | 11 +- assets/l10n/intl_es.arb | 13 +- lib/pages/chat_details/chat_details.dart | 21 +- lib/pages/chat_details/chat_details_view.dart | 8 + lib/pages/new_group/new_group.dart | 18 +- lib/pages/new_group/new_group_view.dart | 11 +- lib/pangea/constants/model_keys.dart | 6 + lib/pangea/constants/pangea_event_types.dart | 1 + .../extensions/pangea_room_extension.dart | 15 ++ lib/pangea/models/bot_options_model.dart | 72 ++++++ .../conversation_bot_settings.dart | 229 ++++++++++++++++++ lib/pangea/widgets/space/class_settings.dart | 74 +----- .../space/language_level_dropdown.dart | 76 ++++++ lib/utils/client_manager.dart | 1 + 14 files changed, 465 insertions(+), 91 deletions(-) create mode 100644 lib/pangea/models/bot_options_model.dart create mode 100644 lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart create mode 100644 lib/pangea/widgets/space/language_level_dropdown.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 2f4bb1448..978b59ebb 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3872,5 +3872,14 @@ "@chooseAUsername": { "type": "text", "placeholders": {} - } + }, + "addConversationBot": "Add Conversation Bot", + "addConversationBotDesc": "Add a conversation bot to send automatic messages to this chat", + "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?" } \ No newline at end of file diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 471e79f4c..520db8fd3 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4534,5 +4534,14 @@ "placeholders": { "roomName": {} } - } -} \ No newline at end of file + }, + "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?" +} diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index 3bdefe8d5..dce775fa6 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -1,25 +1,24 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:collection/collection.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:go_router/go_router.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:matrix/matrix.dart' as matrix; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pages/chat_details/chat_details_view.dart'; import 'package:fluffychat/pages/settings/settings.dart'; import 'package:fluffychat/pangea/utils/set_class_name.dart'; import 'package:fluffychat/pangea/utils/set_class_topic.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/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_lock.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; +import 'package:go_router/go_router.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:matrix/matrix.dart' as matrix; +import 'package:matrix/matrix.dart'; enum AliasActions { copy, delete, setCanonical } @@ -45,6 +44,8 @@ class ChatDetailsController extends State { // #Pangea final GlobalKey addToSpaceKey = GlobalKey(); + final GlobalKey addConversationBotKey = + GlobalKey(); bool displayAddStudentOptions = false; void toggleAddStudentOptions() => diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index d51c488c4..716912e8e 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) + 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 acec26c80..33e7058c2 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -87,4 +87,10 @@ class ModelKey { static const String currentText = "current"; static const String bestContinuance = "best_continuance"; static const String feedbackLang = "feedback_lang"; + + // bot options + static const String languageLevel = "langauge_level"; + 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 68e849c88..e07b2bf9c 100644 --- a/lib/pangea/constants/pangea_event_types.dart +++ b/lib/pangea/constants/pangea_event_types.dart @@ -13,4 +13,5 @@ class PangeaEventTypes { static const vocab = "p.vocab"; static const roomInfo = "pangea.roomtopic"; + static const botOptions = "pangea.bot_options"; } diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 27c275484..bf26b474f 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'; @@ -976,4 +977,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/conversation_bot/conversation_bot_settings.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart new file mode 100644 index 000000000..c19c5d5db --- /dev/null +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart @@ -0,0 +1,229 @@ +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/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: [ + const Divider(height: 1), + 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 Icon(Icons.sms_outlined), + ), + activeColor: AppConfig.activeToggleColor, + value: addBot, + onChanged: (bool add) { + setState(() => addBot = add); + add + ? widget.room?.invite(BotName.byEnvironment) + : widget.room?.kick(BotName.byEnvironment); + }, + ), + if (addBot) ...[ + 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, + ), + ), + ), + 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(16, 16, 0, 0), + child: Text( + L10n.of(context)!.conversationLanguageLevel, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + LanguageLevelDropdown( + initialLevel: botOptions.languageLevel, + onChanged: (int? newValue) => updateBotOption(() { + botOptions.languageLevel = newValue!; + }), + ), + ], + ], + ), + ), + ], + ); +} 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# From fdaa9e92478e5435c896ba731e47525938f6f219 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 25 Jan 2024 10:59:25 -0500 Subject: [PATCH 2/2] some UI updates --- assets/l10n/intl_en.arb | 4 +- lib/pages/chat_details/chat_details_view.dart | 2 +- .../conversation_bot_settings.dart | 178 ++++++++++-------- 3 files changed, 100 insertions(+), 84 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 978b59ebb..eac6e15dd 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3873,8 +3873,8 @@ "type": "text", "placeholders": {} }, - "addConversationBot": "Add Conversation Bot", - "addConversationBotDesc": "Add a conversation bot to send automatic messages to this chat", + "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", diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 716912e8e..a095d49fd 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -491,7 +491,7 @@ class ChatDetailsView extends StatelessWidget { room.showClassEditOptions) ClassInvitationButtons(roomId: controller.roomId!), const Divider(height: 1), - if (!room.isSpace) + if (!room.isSpace && room.canInvite) ConversationBotSettings( key: controller.addConversationBotKey, room: room, diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart index c19c5d5db..a9171a11d 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart @@ -4,6 +4,7 @@ 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'; @@ -114,97 +115,108 @@ class ConversationBotSettingsState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Divider(height: 1), - 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 Icon(Icons.sms_outlined), - ), - activeColor: AppConfig.activeToggleColor, - value: addBot, - onChanged: (bool add) { - setState(() => addBot = add); - add - ? widget.room?.invite(BotName.byEnvironment) - : widget.room?.kick(BotName.byEnvironment); - }, - ), - if (addBot) ...[ - 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, - ), - ), - ), - SwitchListTile.adaptive( + Padding( + padding: const EdgeInsets.only(left: 16), + child: SwitchListTile.adaptive( title: Text( - L10n.of(context)!.enableModeration, + L10n.of(context)!.addConversationBot, style: TextStyle( color: Theme.of(context).colorScheme.secondary, fontWeight: FontWeight.bold, ), ), - subtitle: Text(L10n.of(context)!.enableModerationDesc), + subtitle: Text(L10n.of(context)!.addConversationBotDesc), secondary: CircleAvatar( backgroundColor: Theme.of(context).scaffoldBackgroundColor, foregroundColor: Theme.of(context).textTheme.bodyLarge!.color, - child: const Icon(Icons.shield_outlined), + child: const BotFace( + width: 30.0, + expression: BotExpression.right, + ), ), activeColor: AppConfig.activeToggleColor, - value: botOptions.safetyModeration, - onChanged: (bool newValue) => updateBotOption(() { - botOptions.safetyModeration = newValue; - }), + 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.fromLTRB(16, 16, 0, 0), + 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( @@ -214,12 +226,16 @@ class ConversationBotSettingsState extends State { ), ), ), - LanguageLevelDropdown( - initialLevel: botOptions.languageLevel, - onChanged: (int? newValue) => updateBotOption(() { - botOptions.languageLevel = newValue!; - }), + Padding( + padding: const EdgeInsets.only(left: 16), + child: LanguageLevelDropdown( + initialLevel: botOptions.languageLevel, + onChanged: (int? newValue) => updateBotOption(() { + botOptions.languageLevel = newValue!; + }), + ), ), + const SizedBox(height: 16), ], ], ),