Merge pull request #778 from pangeachat/bot-settings-design
initial work on updating bot settings UIpull/1428/head
commit
f87128af77
@ -1,95 +0,0 @@
|
|||||||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
|
|
||||||
class ConversationBotCustomSystemPromptInput extends StatelessWidget {
|
|
||||||
final BotOptionsModel initialBotOptions;
|
|
||||||
// call this to update propagate changes to parents
|
|
||||||
final void Function(BotOptionsModel) onChanged;
|
|
||||||
|
|
||||||
const ConversationBotCustomSystemPromptInput({
|
|
||||||
super.key,
|
|
||||||
required this.initialBotOptions,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
String customSystemPrompt = initialBotOptions.customSystemPrompt ?? "";
|
|
||||||
|
|
||||||
final TextEditingController textFieldController =
|
|
||||||
TextEditingController(text: customSystemPrompt);
|
|
||||||
|
|
||||||
final GlobalKey<FormState> customSystemPromptFormKey =
|
|
||||||
GlobalKey<FormState>();
|
|
||||||
|
|
||||||
void setBotCustomSystemPromptAction() async {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
useRootNavigator: false,
|
|
||||||
builder: (BuildContext context) => AlertDialog(
|
|
||||||
title: Text(
|
|
||||||
L10n.of(context)!.conversationBotCustomZone_customSystemPromptLabel,
|
|
||||||
),
|
|
||||||
content: Form(
|
|
||||||
key: customSystemPromptFormKey,
|
|
||||||
child: TextFormField(
|
|
||||||
minLines: 1,
|
|
||||||
maxLines: 10,
|
|
||||||
maxLength: 1000,
|
|
||||||
controller: textFieldController,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value.isNotEmpty) {
|
|
||||||
customSystemPrompt = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'This field cannot be empty';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child: Text(L10n.of(context)!.cancel),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: Text(L10n.of(context)!.ok),
|
|
||||||
onPressed: () {
|
|
||||||
if (customSystemPromptFormKey.currentState!.validate()) {
|
|
||||||
if (customSystemPrompt !=
|
|
||||||
initialBotOptions.customSystemPrompt) {
|
|
||||||
initialBotOptions.customSystemPrompt = customSystemPrompt;
|
|
||||||
onChanged.call(initialBotOptions);
|
|
||||||
}
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
onTap: setBotCustomSystemPromptAction,
|
|
||||||
title: Text(
|
|
||||||
initialBotOptions.customSystemPrompt ??
|
|
||||||
L10n.of(context)!
|
|
||||||
.conversationBotCustomZone_customSystemPromptPlaceholder,
|
|
||||||
),
|
|
||||||
subtitle: customSystemPrompt.isEmpty
|
|
||||||
? Text(
|
|
||||||
L10n.of(context)!
|
|
||||||
.conversationBotCustomZone_customSystemPromptEmptyError,
|
|
||||||
style: const TextStyle(color: Colors.red),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
|
||||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_custom_system_prompt_input.dart';
|
|
||||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart';
|
|
||||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
|
|
||||||
class ConversationBotCustomZone extends StatelessWidget {
|
|
||||||
final BotOptionsModel initialBotOptions;
|
|
||||||
// call this to update propagate changes to parents
|
|
||||||
final void Function(BotOptionsModel) onChanged;
|
|
||||||
|
|
||||||
const ConversationBotCustomZone({
|
|
||||||
super.key,
|
|
||||||
required this.initialBotOptions,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
ConversationBotDynamicZoneTitle(
|
|
||||||
title: L10n.of(context)!.conversationBotCustomZone_title,
|
|
||||||
),
|
|
||||||
ConversationBotDynamicZoneLabel(
|
|
||||||
label: L10n.of(context)!
|
|
||||||
.conversationBotCustomZone_customSystemPromptLabel,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: ConversationBotCustomSystemPromptInput(
|
|
||||||
initialBotOptions: initialBotOptions,
|
|
||||||
onChanged: onChanged,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
CheckboxListTile(
|
|
||||||
title: Text(
|
|
||||||
L10n.of(context)!
|
|
||||||
.conversationBotCustomZone_customTriggerReactionEnabledLabel,
|
|
||||||
),
|
|
||||||
enabled: false,
|
|
||||||
value: initialBotOptions.customTriggerReactionEnabled ?? true,
|
|
||||||
onChanged: (value) {
|
|
||||||
initialBotOptions.customTriggerReactionEnabled = value ?? true;
|
|
||||||
initialBotOptions.customTriggerReactionKey =
|
|
||||||
"⏩"; // hard code this for now
|
|
||||||
onChanged.call(initialBotOptions);
|
|
||||||
},
|
|
||||||
// make this input disabled always
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
|
|
||||||
class ConversationBotDiscussionKeywordsInput extends StatelessWidget {
|
|
||||||
final BotOptionsModel initialBotOptions;
|
|
||||||
// call this to update propagate changes to parents
|
|
||||||
final void Function(BotOptionsModel) onChanged;
|
|
||||||
|
|
||||||
const ConversationBotDiscussionKeywordsInput({
|
|
||||||
super.key,
|
|
||||||
required this.initialBotOptions,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
String discussionKeywords = initialBotOptions.discussionKeywords ?? "";
|
|
||||||
|
|
||||||
final TextEditingController textFieldController =
|
|
||||||
TextEditingController(text: discussionKeywords);
|
|
||||||
|
|
||||||
void setBotDiscussionKeywordsAction() async {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
useRootNavigator: false,
|
|
||||||
builder: (BuildContext context) => AlertDialog(
|
|
||||||
title: Text(
|
|
||||||
L10n.of(context)!
|
|
||||||
.conversationBotDiscussionZone_discussionKeywordsLabel,
|
|
||||||
),
|
|
||||||
content: TextField(
|
|
||||||
minLines: 1,
|
|
||||||
maxLines: 10,
|
|
||||||
maxLength: 1000,
|
|
||||||
controller: textFieldController,
|
|
||||||
onChanged: (value) {
|
|
||||||
discussionKeywords = value;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child: Text(L10n.of(context)!.cancel),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: Text(L10n.of(context)!.ok),
|
|
||||||
onPressed: () {
|
|
||||||
if (discussionKeywords == "") return;
|
|
||||||
if (discussionKeywords !=
|
|
||||||
initialBotOptions.discussionKeywords) {
|
|
||||||
initialBotOptions.discussionKeywords = discussionKeywords;
|
|
||||||
onChanged.call(initialBotOptions);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
onTap: setBotDiscussionKeywordsAction,
|
|
||||||
title: Text(
|
|
||||||
initialBotOptions.discussionKeywords ??
|
|
||||||
L10n.of(context)!
|
|
||||||
.conversationBotDiscussionZone_discussionKeywordsPlaceholder,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
|
|
||||||
class ConversationBotDiscussionTopicInput extends StatelessWidget {
|
|
||||||
final BotOptionsModel initialBotOptions;
|
|
||||||
// call this to update propagate changes to parents
|
|
||||||
final void Function(BotOptionsModel) onChanged;
|
|
||||||
|
|
||||||
const ConversationBotDiscussionTopicInput({
|
|
||||||
super.key,
|
|
||||||
required this.initialBotOptions,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
String discussionTopic = initialBotOptions.discussionTopic ?? "";
|
|
||||||
|
|
||||||
final TextEditingController textFieldController =
|
|
||||||
TextEditingController(text: discussionTopic);
|
|
||||||
|
|
||||||
void setBotDiscussionTopicAction() async {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
useRootNavigator: false,
|
|
||||||
builder: (BuildContext context) => AlertDialog(
|
|
||||||
title: Text(
|
|
||||||
L10n.of(context)!
|
|
||||||
.conversationBotDiscussionZone_discussionTopicLabel,
|
|
||||||
),
|
|
||||||
content: TextField(
|
|
||||||
minLines: 1,
|
|
||||||
maxLines: 10,
|
|
||||||
maxLength: 1000,
|
|
||||||
controller: textFieldController,
|
|
||||||
onChanged: (value) {
|
|
||||||
discussionTopic = value;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child: Text(L10n.of(context)!.cancel),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: Text(L10n.of(context)!.ok),
|
|
||||||
onPressed: () {
|
|
||||||
if (discussionTopic == "") return;
|
|
||||||
if (discussionTopic != initialBotOptions.discussionTopic) {
|
|
||||||
initialBotOptions.discussionTopic = discussionTopic;
|
|
||||||
onChanged.call(initialBotOptions);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
onTap: setBotDiscussionTopicAction,
|
|
||||||
title: Text(
|
|
||||||
initialBotOptions.discussionTopic ??
|
|
||||||
L10n.of(context)!
|
|
||||||
.conversationBotDiscussionZone_discussionTopicPlaceholder,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
|
||||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_discussion_keywords_input.dart';
|
|
||||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_discussion_topic_input.dart';
|
|
||||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_label.dart';
|
|
||||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_dynamic_zone_title.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
|
|
||||||
class ConversationBotDiscussionZone extends StatelessWidget {
|
|
||||||
final BotOptionsModel initialBotOptions;
|
|
||||||
// call this to update propagate changes to parents
|
|
||||||
final void Function(BotOptionsModel) onChanged;
|
|
||||||
|
|
||||||
const ConversationBotDiscussionZone({
|
|
||||||
super.key,
|
|
||||||
required this.initialBotOptions,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
ConversationBotDynamicZoneTitle(
|
|
||||||
title: L10n.of(context)!.conversationBotDiscussionZone_title,
|
|
||||||
),
|
|
||||||
ConversationBotDynamicZoneLabel(
|
|
||||||
label: L10n.of(context)!
|
|
||||||
.conversationBotDiscussionZone_discussionTopicLabel,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: ConversationBotDiscussionTopicInput(
|
|
||||||
initialBotOptions: initialBotOptions,
|
|
||||||
onChanged: onChanged,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
ConversationBotDynamicZoneLabel(
|
|
||||||
label: L10n.of(context)!
|
|
||||||
.conversationBotDiscussionZone_discussionKeywordsLabel,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: ConversationBotDiscussionKeywordsInput(
|
|
||||||
initialBotOptions: initialBotOptions,
|
|
||||||
onChanged: onChanged,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
CheckboxListTile(
|
|
||||||
title: Text(
|
|
||||||
L10n.of(context)!
|
|
||||||
.conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel,
|
|
||||||
),
|
|
||||||
enabled: false,
|
|
||||||
value: initialBotOptions.discussionTriggerReactionEnabled ?? true,
|
|
||||||
onChanged: (value) {
|
|
||||||
initialBotOptions.discussionTriggerReactionEnabled = value ?? true;
|
|
||||||
initialBotOptions.discussionTriggerReactionKey =
|
|
||||||
"⏩"; // hard code this for now
|
|
||||||
onChanged.call(initialBotOptions);
|
|
||||||
},
|
|
||||||
// make this input disabled always
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ConversationBotDynamicZoneLabel extends StatelessWidget {
|
|
||||||
final String label;
|
|
||||||
|
|
||||||
const ConversationBotDynamicZoneLabel({
|
|
||||||
super.key,
|
|
||||||
required this.label,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(12, 0, 0, 0),
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ConversationBotDynamicZoneTitle extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
const ConversationBotDynamicZoneTitle({
|
|
||||||
super.key,
|
|
||||||
required this.title,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(
|
|
||||||
color: Colors.grey,
|
|
||||||
thickness: 1,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +1,77 @@
|
|||||||
import 'package:fluffychat/pangea/constants/bot_mode.dart';
|
import 'package:fluffychat/pangea/constants/bot_mode.dart';
|
||||||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'conversation_bot_discussion_zone.dart';
|
|
||||||
|
|
||||||
class ConversationBotModeDynamicZone extends StatelessWidget {
|
class ConversationBotModeDynamicZone extends StatelessWidget {
|
||||||
final BotOptionsModel initialBotOptions;
|
final BotOptionsModel initialBotOptions;
|
||||||
final void Function(BotOptionsModel) onChanged;
|
final GlobalKey<FormState> formKey;
|
||||||
|
|
||||||
|
final TextEditingController discussionTopicController;
|
||||||
|
final TextEditingController discussionKeywordsController;
|
||||||
|
final TextEditingController customSystemPromptController;
|
||||||
|
|
||||||
const ConversationBotModeDynamicZone({
|
const ConversationBotModeDynamicZone({
|
||||||
super.key,
|
super.key,
|
||||||
required this.initialBotOptions,
|
required this.initialBotOptions,
|
||||||
required this.onChanged,
|
required this.formKey,
|
||||||
|
required this.discussionTopicController,
|
||||||
|
required this.discussionKeywordsController,
|
||||||
|
required this.customSystemPromptController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final zoneMap = {
|
final discussionChildren = [
|
||||||
BotMode.discussion: ConversationBotDiscussionZone(
|
TextFormField(
|
||||||
initialBotOptions: initialBotOptions,
|
decoration: InputDecoration(
|
||||||
onChanged: onChanged,
|
hintText: L10n.of(context)!
|
||||||
|
.conversationBotDiscussionZone_discussionTopicPlaceholder,
|
||||||
|
),
|
||||||
|
controller: discussionTopicController,
|
||||||
|
validator: (value) => value == null || value.isEmpty
|
||||||
|
? L10n.of(context)!.enterDiscussionTopic
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
BotMode.custom: ConversationBotCustomZone(
|
const SizedBox(height: 12),
|
||||||
initialBotOptions: initialBotOptions,
|
TextFormField(
|
||||||
onChanged: onChanged,
|
decoration: InputDecoration(
|
||||||
|
hintText: L10n.of(context)!
|
||||||
|
.conversationBotDiscussionZone_discussionKeywordsPlaceholder,
|
||||||
|
),
|
||||||
|
controller: discussionKeywordsController,
|
||||||
),
|
),
|
||||||
};
|
];
|
||||||
if (!zoneMap.containsKey(initialBotOptions.mode)) {
|
|
||||||
return Container();
|
final customChildren = [
|
||||||
}
|
TextFormField(
|
||||||
return Container(
|
decoration: InputDecoration(
|
||||||
decoration: BoxDecoration(
|
hintText: L10n.of(context)!
|
||||||
border: Border.all(
|
.conversationBotCustomZone_customSystemPromptPlaceholder,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
width: 0.5,
|
|
||||||
),
|
),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
validator: (value) => value == null || value.isEmpty
|
||||||
|
? L10n.of(context)!.enterPrompt
|
||||||
|
: null,
|
||||||
|
controller: customSystemPromptController,
|
||||||
),
|
),
|
||||||
child: zoneMap[initialBotOptions.mode],
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
if (initialBotOptions.mode == BotMode.discussion) ...discussionChildren,
|
||||||
|
if (initialBotOptions.mode == BotMode.custom) ...customChildren,
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: Text(
|
||||||
|
L10n.of(context)!
|
||||||
|
.conversationBotCustomZone_customTriggerReactionEnabledLabel,
|
||||||
|
),
|
||||||
|
enabled: false,
|
||||||
|
value: initialBotOptions.customTriggerReactionEnabled ?? true,
|
||||||
|
onChanged: null,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue