merge conflicts
						commit
						8d77ca8951
					
				@ -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<String> 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<String>(),
 | 
			
		||||
      safetyModeration: json[ModelKey.safetyModeration] ?? true,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final data = <String, dynamic>{};
 | 
			
		||||
    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,
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
@ -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<ConversationBotSettings> {
 | 
			
		||||
  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<void> 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<void> 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),
 | 
			
		||||
                  ],
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
@ -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,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue