Chat input expansiable section basis

This commit is contained in:
2025-11-16 21:42:10 +08:00
parent 96a919cc4e
commit 1cc34d3073
3 changed files with 264 additions and 40 deletions

View File

@@ -27,6 +27,85 @@ import "package:material_symbols_icons/symbols.dart";
import "package:island/widgets/stickers/sticker_picker.dart";
import "package:island/pods/chat/chat_subscribe.dart";
void _insertPlaceholder(TextEditingController controller, String placeholder) {
final text = controller.text;
final selection = controller.selection;
final start = selection.start >= 0 ? selection.start : text.length;
final end = selection.end >= 0 ? selection.end : text.length;
final newText = text.replaceRange(start, end, placeholder);
controller.value = TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: start + placeholder.length),
);
}
const kInputDrawerExpandedHeight = 180.0;
class _ExpandedSection extends StatelessWidget {
final TextEditingController messageController;
const _ExpandedSection({required this.messageController});
@override
Widget build(BuildContext context) {
return Container(
key: const ValueKey('expanded'),
decoration: BoxDecoration(
border: Border.all(width: 1, color: Theme.of(context).dividerColor),
borderRadius: const BorderRadius.all(Radius.circular(32)),
),
margin: const EdgeInsets.only(top: 8, bottom: 3),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(32)),
child: DefaultTabController(
length: 2,
child: Column(
children: [
TabBar(
splashBorderRadius: const BorderRadius.all(Radius.circular(40)),
tabs: [Tab(text: 'Features'), Tab(text: 'Stickers')],
),
SizedBox(
height: kInputDrawerExpandedHeight,
child: TabBarView(
children: [
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Symbols.poll),
tooltip: 'Poll',
onPressed: () {},
),
const Gap(16),
IconButton(
icon: Icon(Symbols.currency_exchange),
tooltip: 'Fund',
onPressed: () {},
),
],
),
),
StickerPickerEmbedded(
height: kInputDrawerExpandedHeight,
onPick:
(placeholder) => _insertPlaceholder(
messageController,
placeholder,
),
),
],
),
),
],
),
),
),
);
}
}
class ChatInput extends HookConsumerWidget {
final TextEditingController messageController;
final SnChatRoom chatRoom;
@@ -71,6 +150,7 @@ class ChatInput extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final inputFocusNode = useFocusNode();
final chatSubscribe = ref.watch(chatSubscribeNotifierProvider(chatRoom.id));
final isExpanded = useState(false);
void send() {
inputFocusNode.requestFocus();
@@ -426,43 +506,28 @@ class ChatInput extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
tooltip: 'stickers'.tr(),
icon: const Icon(Symbols.add_reaction),
tooltip:
isExpanded.value ? 'collapse'.tr() : 'more'.tr(),
icon: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder:
(child, animation) => FadeTransition(
opacity: animation,
child: child,
),
child:
isExpanded.value
? const Icon(
Symbols.close,
key: ValueKey('close'),
)
: const Icon(
Symbols.add,
key: ValueKey('add'),
),
),
onPressed: () {
final size = MediaQuery.of(context).size;
showStickerPickerPopover(
context,
Offset(
20,
size.height -
480 -
MediaQuery.of(context).padding.bottom,
),
onPick: (placeholder) {
// Insert placeholder at current cursor position
final text = messageController.text;
final selection = messageController.selection;
final start =
selection.start >= 0
? selection.start
: text.length;
final end =
selection.end >= 0
? selection.end
: text.length;
final newText = text.replaceRange(
start,
end,
placeholder,
);
messageController.value = TextEditingValue(
text: newText,
selection: TextSelection.collapsed(
offset: start + placeholder.length,
),
);
},
);
isExpanded.value = !isExpanded.value;
},
),
UploadMenu(
@@ -659,6 +724,31 @@ class ChatInput extends HookConsumerWidget {
),
],
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
transitionBuilder: (Widget child, Animation<double> animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.1),
end: Offset.zero,
).animate(animation),
child: FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
axisAlignment: -1.0,
child: child,
),
),
);
},
child:
isExpanded.value
? _ExpandedSection(messageController: messageController)
: const SizedBox.shrink(key: ValueKey('collapsed')),
),
],
),
),