Files
social-app/apps/lib/features/home/ui/widgets/home_input_host.dart
T
qzl 8d4a14150b feat(apps): update UI screens and shared components
- Update home screen with new composer and interactions
- Update settings screens with new profile flow
- Update calendar share dialog
- Update contacts screen
- Add new shared widgets: confirm_sheet, phone_prefix_selector
- Add new formatters: phone_display_formatter
- Update tests for modified components
2026-03-19 18:43:08 +08:00

181 lines
5.0 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'home_composer_stack.dart';
class HomeInputHost extends StatefulWidget {
const HomeInputHost({
super.key,
required this.selectedImages,
required this.onRemoveImage,
required this.isRecording,
required this.isCancelGestureActive,
required this.isTranscribing,
required this.isWaitingAgent,
required this.messageController,
required this.onTapPlus,
required this.onStopGenerating,
required this.onHoldToSpeakStart,
required this.onHoldToSpeakEnd,
required this.onHoldToSpeakMoveUpdate,
required this.onHoldToSpeakCancel,
required this.onSubmitText,
required this.keyboardInset,
});
final List<XFile> selectedImages;
final ValueChanged<int> onRemoveImage;
final bool isRecording;
final bool isCancelGestureActive;
final bool isTranscribing;
final bool isWaitingAgent;
final TextEditingController messageController;
final VoidCallback onTapPlus;
final VoidCallback onStopGenerating;
final VoidCallback onHoldToSpeakStart;
final VoidCallback onHoldToSpeakEnd;
final ValueChanged<LongPressMoveUpdateDetails> onHoldToSpeakMoveUpdate;
final VoidCallback onHoldToSpeakCancel;
final Future<void> Function(String text) onSubmitText;
final double keyboardInset;
@override
State<HomeInputHost> createState() => HomeInputHostState();
}
class HomeInputHostState extends State<HomeInputHost> {
final FocusNode _messageFocusNode = FocusNode();
Timer? _keyboardShowFallbackTimer;
bool _isHoldToSpeakMode = true;
void unfocusInput() {
_messageFocusNode.unfocus();
}
@override
void initState() {
super.initState();
_messageFocusNode.addListener(_handleMessageFocusChanged);
}
@override
void dispose() {
_keyboardShowFallbackTimer?.cancel();
_messageFocusNode.removeListener(_handleMessageFocusChanged);
_messageFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return HomeComposerStack(
selectedImages: widget.selectedImages,
onRemoveImage: widget.onRemoveImage,
isHoldToSpeakMode: _isHoldToSpeakMode,
isRecording: widget.isRecording,
isCancelGestureActive: widget.isCancelGestureActive,
isTranscribing: widget.isTranscribing,
isWaitingAgent: widget.isWaitingAgent,
messageController: widget.messageController,
messageFocusNode: _messageFocusNode,
onTapPlus: widget.onTapPlus,
onTapRightAction: _onRightActionTap,
onHoldToSpeakStart: widget.onHoldToSpeakStart,
onHoldToSpeakEnd: widget.onHoldToSpeakEnd,
onHoldToSpeakMoveUpdate: widget.onHoldToSpeakMoveUpdate,
onHoldToSpeakCancel: widget.onHoldToSpeakCancel,
onSubmit: _onSubmit,
keyboardInset: widget.keyboardInset,
);
}
void _onRightActionTap() {
if (widget.isTranscribing || widget.isRecording) {
return;
}
if (widget.isWaitingAgent) {
widget.onStopGenerating();
return;
}
final draft = widget.messageController.text.trim();
if (draft.isNotEmpty) {
_onSubmit();
return;
}
_toggleInputMode();
}
void _toggleInputMode() {
if (widget.isRecording || widget.isTranscribing) {
return;
}
final switchToText = _isHoldToSpeakMode;
setState(() {
_isHoldToSpeakMode = !_isHoldToSpeakMode;
});
if (!switchToText) {
_messageFocusNode.unfocus();
_keyboardShowFallbackTimer?.cancel();
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted || _isHoldToSpeakMode) {
return;
}
_messageFocusNode.requestFocus();
});
}
void _handleMessageFocusChanged() {
if (!_messageFocusNode.hasFocus || _isHoldToSpeakMode) {
_keyboardShowFallbackTimer?.cancel();
return;
}
_scheduleKeyboardShowFallback();
}
void _scheduleKeyboardShowFallback() {
if (!_supportsProgrammaticKeyboardShow() || _isKeyboardVisible()) {
return;
}
_keyboardShowFallbackTimer?.cancel();
_keyboardShowFallbackTimer = Timer(const Duration(milliseconds: 120), () {
if (!mounted || !_messageFocusNode.hasFocus || _isHoldToSpeakMode) {
return;
}
if (_isKeyboardVisible()) {
return;
}
SystemChannels.textInput.invokeMethod<void>('TextInput.show');
});
}
bool _supportsProgrammaticKeyboardShow() {
if (kIsWeb) {
return false;
}
return defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS;
}
bool _isKeyboardVisible() {
final mediaQuery = MediaQuery.maybeOf(context);
if (mediaQuery == null) {
return false;
}
return mediaQuery.viewInsets.bottom > 0;
}
void _onSubmit() {
final draft = widget.messageController.text.trim();
if (draft.isEmpty) {
return;
}
widget.onSubmitText(draft);
}
}