Files
JustAMassenger/client/lib/widgets/chat_input.dart
T
SashegDev 096c4d0a2d Initial commit: JustAMessenger v0.1.0
Серверная часть (Go):
- WebSocket сервер с бинарным протоколом
- XChaCha20-Poly1305 шифрование
- zstd сжатие с дедупликацией (64KB чанки)
- SQLite хранилище (WAL режим)
- Управление гильдиями, каналами, ролями
- Федерация между серверами (ed25519)
- REST API + WebSocket endpoints

Клиентская часть (Flutter):
- Material Design 3 тёмная тема (Discord-like)
- WebSocket соединение с сервером
- Экраны: сплэш, логин, домашний, гильдии, чат
- Модели: пользователи, гильдии, каналы, сообщения, роли
- Сервисы: соединение, API, криптография, тема
- Виджеты: иконки гильдий, сообщения, ввод чата
- Web сборка (PWA)

Документация:
- AGENTS.md — контекст для ИИ ассистентов
- docs/protocol.md — спецификация протокола
2026-06-06 22:39:14 +00:00

163 lines
5.0 KiB
Dart

import 'package:flutter/material.dart';
import '../services/theme_service.dart';
class ChatInput extends StatefulWidget {
final TextEditingController controller;
final VoidCallback? onSend;
final String channelId;
final void Function(String)? onSendMessage;
const ChatInput({
super.key,
required this.controller,
this.onSend,
this.onSendMessage,
required this.channelId,
});
@override
State<ChatInput> createState() => _ChatInputState();
}
class _ChatInputState extends State<ChatInput> {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: const BoxDecoration(
color: ThemeService.surfaceSecondary,
border: Border(top: BorderSide(color: Color(0xFF3F4147))),
),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.add_circle_outline,
color: ThemeService.textSecondary, size: 24),
onPressed: _showAttachmentMenu,
tooltip: 'Attach',
),
const SizedBox(width: 4),
Expanded(
child: TextField(
controller: widget.controller,
maxLines: 5,
minLines: 1,
textCapitalization: TextCapitalization.sentences,
style: const TextStyle(
color: ThemeService.textPrimary,
fontSize: 15,
),
decoration: InputDecoration(
hintText: 'Message #${widget.channelId.split('-').first}',
hintStyle: const TextStyle(
color: ThemeService.textMuted,
fontSize: 15,
),
filled: true,
fillColor: ThemeService.surfacePrimary,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 10),
isDense: true,
),
onSubmitted: (text) => widget.onSendMessage?.call(text),
),
),
const SizedBox(width: 4),
IconButton(
icon: const Icon(Icons.mic_none,
color: ThemeService.textSecondary, size: 24),
onPressed: _startVoiceRecording,
tooltip: 'Voice message',
),
IconButton(
icon: const Icon(Icons.send_rounded,
color: ThemeService.primary, size: 24),
onPressed: () => widget.onSendMessage
?.call(widget.controller.text),
tooltip: 'Send',
),
],
),
);
}
void _showAttachmentMenu() {
showModalBottomSheet(
context: context,
backgroundColor: ThemeService.surfaceSecondary,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
builder: (ctx) => SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Attach',
style: TextStyle(
color: ThemeService.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_attachmentButton(Icons.photo, 'Image', () => Navigator.pop(ctx)),
_attachmentButton(Icons.videocam, 'Video', () => Navigator.pop(ctx)),
_attachmentButton(Icons.description, 'File', () => Navigator.pop(ctx)),
_attachmentButton(Icons.mic, 'Voice', () => Navigator.pop(ctx)),
],
),
],
),
),
),
);
}
Widget _attachmentButton(IconData icon, String label, VoidCallback onTap) {
return InkWell(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: ThemeService.surfacePrimary,
borderRadius: BorderRadius.circular(14),
),
child: Icon(icon, color: ThemeService.textSecondary, size: 28),
),
const SizedBox(height: 6),
Text(
label,
style: const TextStyle(
color: ThemeService.textSecondary,
fontSize: 12,
),
),
],
),
);
}
void _startVoiceRecording() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Voice recording coming soon...'),
duration: Duration(seconds: 1),
),
);
}
}