Files
m8chat-app2/lib/features/chat/presentation/message_input.dart
help4bis 8f13c725a4 feat: Phase 1 complete — Matrix login, rooms, chat, profile
- Direct m.login.password auth against matrix.m8chat.au
- Room list with unread badges, last message, timestamps
- Chat timeline (text, images, files, replies, reactions)
- Profile screen with expandable Notifications and Security sections
- Olm E2EE initialisation (web WASM bootstrap)
- Global error handler preventing Matrix SDK crashes
- GoRouter with refreshListenable (no recreation on auth change)
- Feature-first clean architecture: Riverpod + GoRouter + Drift
- Deployed to https://app2.m8chat.au

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 06:26:57 +10:00

116 lines
3.2 KiB
Dart

// Version: 1.0.0 | Created: 2026-04-01
// Message input bar. Text field + send button.
import 'package:flutter/material.dart';
class MessageInput extends StatefulWidget {
const MessageInput({
super.key,
required this.onSend,
required this.isSending,
});
final Future<void> Function(String text) onSend;
final bool isSending;
@override
State<MessageInput> createState() => _MessageInputState();
}
class _MessageInputState extends State<MessageInput> {
final _controller = TextEditingController();
bool _hasText = false;
@override
void initState() {
super.initState();
_controller.addListener(() {
final hasText = _controller.text.trim().isNotEmpty;
if (hasText != _hasText) {
setState(() => _hasText = hasText);
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _submit() async {
final text = _controller.text.trim();
if (text.isEmpty) return;
_controller.clear();
await widget.onSend(text);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 12),
decoration: BoxDecoration(
color: theme.colorScheme.surface,
border: Border(
top: BorderSide(color: theme.colorScheme.outline.withAlpha(51)),
),
),
child: SafeArea(
top: false,
child: Row(
children: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Attach file (Phase 2)',
onPressed: null, // Phase 2
color: theme.colorScheme.onSurface.withAlpha(153),
),
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Message',
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
),
textInputAction: TextInputAction.send,
onSubmitted: (_) => _submit(),
maxLines: null,
keyboardType: TextInputType.multiline,
),
),
const SizedBox(width: 8),
AnimatedSwitcher(
duration: const Duration(milliseconds: 150),
child: widget.isSending
? const SizedBox(
width: 40,
height: 40,
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2.5),
),
),
)
: IconButton(
icon: const Icon(Icons.send_rounded),
onPressed: _hasText ? _submit : null,
color: _hasText
? theme.colorScheme.primary
: theme.colorScheme.onSurface.withAlpha(77),
tooltip: 'Send message',
),
),
],
),
),
);
}
}