-
Notifications
You must be signed in to change notification settings - Fork 312
Support setting typing status #897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e6c9ac2
231d6ed
354ba85
0984e86
bd68c26
b4a61d8
92facbd
24531db
85e30ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -269,19 +269,100 @@ class ComposeContentController extends ComposeController<ContentValidationError> | |
} | ||
} | ||
|
||
class _ContentInput extends StatelessWidget { | ||
class _ContentInput extends StatefulWidget { | ||
const _ContentInput({ | ||
required this.narrow, | ||
required this.destination, | ||
required this.controller, | ||
required this.focusNode, | ||
required this.hintText, | ||
}); | ||
|
||
final Narrow narrow; | ||
final SendableNarrow destination; | ||
final ComposeContentController controller; | ||
final FocusNode focusNode; | ||
final String hintText; | ||
|
||
@override | ||
State<_ContentInput> createState() => _ContentInputState(); | ||
} | ||
|
||
class _ContentInputState extends State<_ContentInput> with WidgetsBindingObserver { | ||
@override | ||
void initState() { | ||
super.initState(); | ||
widget.controller.addListener(_contentChanged); | ||
widget.focusNode.addListener(_focusChanged); | ||
WidgetsBinding.instance.addObserver(this); | ||
} | ||
|
||
@override | ||
void didUpdateWidget(covariant _ContentInput oldWidget) { | ||
super.didUpdateWidget(oldWidget); | ||
if (widget.controller != oldWidget.controller) { | ||
oldWidget.controller.removeListener(_contentChanged); | ||
widget.controller.addListener(_contentChanged); | ||
} | ||
if (widget.focusNode != oldWidget.focusNode) { | ||
oldWidget.focusNode.removeListener(_focusChanged); | ||
widget.focusNode.addListener(_focusChanged); | ||
} | ||
} | ||
|
||
@override | ||
void dispose() { | ||
widget.controller.removeListener(_contentChanged); | ||
widget.focusNode.removeListener(_focusChanged); | ||
WidgetsBinding.instance.removeObserver(this); | ||
super.dispose(); | ||
} | ||
|
||
void _contentChanged() { | ||
final store = PerAccountStoreWidget.of(context); | ||
(widget.controller.text.isEmpty) | ||
? store.typingNotifier.stoppedComposing() | ||
: store.typingNotifier.keystroke(widget.destination); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rereading the API docs: the spec for what a client should do includes:
Thinking about that, here's a couple more situations where we should be sending such requests:
Of those, the second one can be squashed into the main commit here, since it's just removing some logic that that one adds; the other two should probably be separate commits, just like your existing followup commit for app lifecycle changes. |
||
} | ||
|
||
void _focusChanged() { | ||
if (widget.focusNode.hasFocus) { | ||
// Content input getting focus doesn't necessarily mean that | ||
// the user started typing, so do nothing. | ||
return; | ||
} | ||
final store = PerAccountStoreWidget.of(context); | ||
store.typingNotifier.stoppedComposing(); | ||
} | ||
|
||
@override | ||
void didChangeAppLifecycleState(AppLifecycleState state) { | ||
switch (state) { | ||
case AppLifecycleState.hidden: | ||
case AppLifecycleState.paused: | ||
case AppLifecycleState.detached: | ||
// Transition to either [hidden] or [paused] signals that | ||
// > [the] application is not currently visible to the user, and not | ||
// > responding to user input. | ||
// | ||
// When transitioning to [detached], the compose box can't exist: | ||
// > The application defaults to this state before it initializes, and | ||
// > can be in this state (applicable on Android, iOS, and web) after | ||
// > all views have been detached. | ||
// | ||
// For all these states, we can conclude that the user is not | ||
// composing a message. | ||
final store = PerAccountStoreWidget.of(context); | ||
store.typingNotifier.stoppedComposing(); | ||
case AppLifecycleState.inactive: | ||
// > At least one view of the application is visible, but none have | ||
// > input focus. The application is otherwise running normally. | ||
// For example, we expect this state when the user is selecting a file | ||
// to upload. | ||
case AppLifecycleState.resumed: | ||
} | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
ColorScheme colorScheme = Theme.of(context).colorScheme; | ||
|
@@ -296,15 +377,15 @@ class _ContentInput extends StatelessWidget { | |
maxHeight: 200, | ||
), | ||
child: ComposeAutocomplete( | ||
narrow: narrow, | ||
controller: controller, | ||
focusNode: focusNode, | ||
narrow: widget.narrow, | ||
controller: widget.controller, | ||
focusNode: widget.focusNode, | ||
fieldViewBuilder: (context) { | ||
return TextField( | ||
controller: controller, | ||
focusNode: focusNode, | ||
controller: widget.controller, | ||
focusNode: widget.focusNode, | ||
style: TextStyle(color: colorScheme.onSurface), | ||
decoration: InputDecoration.collapsed(hintText: hintText), | ||
decoration: InputDecoration.collapsed(hintText: widget.hintText), | ||
maxLines: null, | ||
textCapitalization: TextCapitalization.sentences, | ||
); | ||
|
@@ -370,6 +451,7 @@ class _StreamContentInputState extends State<_StreamContentInput> { | |
?? zulipLocalizations.composeBoxUnknownChannelName; | ||
return _ContentInput( | ||
narrow: widget.narrow, | ||
destination: TopicNarrow(widget.narrow.streamId, _topicTextNormalized), | ||
controller: widget.controller, | ||
focusNode: widget.focusNode, | ||
hintText: zulipLocalizations.composeBoxChannelContentHint(streamName, _topicTextNormalized)); | ||
|
@@ -446,6 +528,7 @@ class _FixedDestinationContentInput extends StatelessWidget { | |
Widget build(BuildContext context) { | ||
return _ContentInput( | ||
narrow: narrow, | ||
destination: narrow, | ||
controller: controller, | ||
focusNode: focusNode, | ||
hintText: _hintText(context)); | ||
|
@@ -818,6 +901,10 @@ class _SendButtonState extends State<_SendButton> { | |
final content = widget.contentController.textNormalized; | ||
|
||
widget.contentController.clear(); | ||
// The following `stoppedComposing` call is currently redundant, | ||
// because clearing input sends a "typing stopped" notice. | ||
// It will be necessary once we resolve #720. | ||
store.typingNotifier.stoppedComposing(); | ||
|
||
try { | ||
// TODO(#720) clear content input only on success response; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commit-message nit: I think there's a missing word or two in here: