0% found this document useful (0 votes)
19 views20 pages

Main Dart

Uploaded by

tempemail1376
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views20 pages

Main Dart

Uploaded by

tempemail1376
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 20

import 'dart:convert';

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:record/record.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {


const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Kafgram',
theme: ThemeData(
primaryColor: const Color(0xFF527DA3), // Telegram blue
scaffoldBackgroundColor:
const Color(0xFFEFEFEF), // Light grey background
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF527DA3),
foregroundColor: Colors.white,
elevation: 0,
),
textTheme: const TextTheme(
bodyMedium: TextStyle(fontFamily: 'Roboto', color: Colors.black87),
titleLarge:
TextStyle(fontFamily: 'Roboto', fontWeight: FontWeight.bold),
),
fontFamily: 'Roboto',
colorScheme: ColorScheme.fromSwatch().copyWith(
secondary: const Color(0xFF65AADD), // Accent color
),
),
home: const AuthCheckPage(),
);
}
}

// Page to check Telegram session and decide navigation


class AuthCheckPage extends StatefulWidget {
const AuthCheckPage({super.key});

@override
_AuthCheckPageState createState() => _AuthCheckPageState();
}

class _AuthCheckPageState extends State<AuthCheckPage> {


@override
void initState() {
super.initState();
_checkSession();
}
Future<void> _checkSession() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? phone = prefs.getString('phone');

if (phone != null && phone.startsWith('+')) {


try {
final response = await http.get(
Uri.parse(
'http://192.168.1.2:8000/check_session?phone=$
{Uri.encodeComponent(phone)}'),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['status'] == 'authenticated') {
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => ChatsPage(phone: phone)),
);
}
return;
}
} else {
final data = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Session check failed: ${data['detail'] ?? 'Unknown error'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
await prefs.remove('phone');
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Network error. Please check your connection.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
} else {
if (phone != null) {
await prefs.remove('phone');
}
}
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginPage()),
);
}
}

@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}

// Login Page for Telegram authentication


class LoginPage extends StatefulWidget {
const LoginPage({super.key});

@override
_LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {


final TextEditingController _phoneController = TextEditingController();
final TextEditingController _codeController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
bool _isLoading = false;
bool _codeSent = false;
String? _phoneCodeHash;

Future<void> _sendPhoneNumber() async {


setState(() {
_isLoading = true;
});

final phone = _phoneController.text.trim();


if (phone.isEmpty || !phone.startsWith('+')) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Please enter a valid phone number (e.g., +1234567890)',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
setState(() {
_isLoading = false;
});
return;
}

try {
final response = await http.post(
Uri.parse('http://192.168.1.2:8000/request_code'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'phone': phone}),
);

if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['status'] == 'code_sent') {
setState(() {
_codeSent = true;
_phoneCodeHash = data['phone_code_hash'];
_isLoading = false;
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Failed to send code',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
setState(() {
_isLoading = false;
});
}
} else {
final data = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error: ${data['detail'] ?? 'Unknown error'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
setState(() {
_isLoading = false;
});
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Network error. Please try again.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
setState(() {
_isLoading = false;
});
}
}

Future<void> _verifyCode() async {


setState(() {
_isLoading = true;
});

final phone = _phoneController.text.trim();


final code = _codeController.text.trim();
final password = _passwordController.text.trim();
if (code.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Please enter the verification code',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
setState(() {
_isLoading = false;
});
return;
}

try {
final response = await http.post(
Uri.parse('http://192.168.1.2:8000/login'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'phone': phone,
'code': code,
'phone_code_hash': _phoneCodeHash,
'password': password.isEmpty ? null : password,
}),
);

if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['status'] == 'logged_in') {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('phone', phone);
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => ChatsPage(phone: phone)),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Login failed: ${data['detail'] ?? 'Unknown error'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
} else {
final data = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error: ${data['detail'] ?? 'Login failed'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Network error. Please try again.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}

setState(() {
_isLoading = false;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Kafgram',
style: TextStyle(fontFamily: 'Roboto', fontWeight: FontWeight.w500),
),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (!_codeSent) ...[
TextField(
controller: _phoneController,
decoration: InputDecoration(
labelText: 'Phone Number',
hintText: 'e.g., +1234567890',
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
),
keyboardType: TextInputType.phone,
),
const SizedBox(height: 20),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF65AADD),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.symmetric(
horizontal: 40, vertical: 15),
),
onPressed: _sendPhoneNumber,
child: const Text(
'Send Code',
style: TextStyle(fontFamily: 'Roboto', fontSize: 16),
),
),
] else ...[
TextField(
controller: _codeController,
decoration: InputDecoration(
labelText: 'Verification Code',
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 20),
TextField(
controller: _passwordController,
decoration: InputDecoration(
labelText: '2FA Password (optional)',
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
),
obscureText: true,
),
const SizedBox(height: 20),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF65AADD),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.symmetric(
horizontal: 40, vertical: 15),
),
onPressed: _verifyCode,
child: const Text(
'Verify Code',
style: TextStyle(fontFamily: 'Roboto', fontSize: 16),
),
),
],
],
),
),
);
}
@override
void dispose() {
_phoneController.dispose();
_codeController.dispose();
_passwordController.dispose();
super.dispose();
}
}

// Chats Page
class ChatsPage extends StatefulWidget {
final String phone;

const ChatsPage({super.key, required this.phone});

@override
_ChatsPageState createState() => _ChatsPageState();
}

class _ChatsPageState extends State<ChatsPage> {


List<dynamic> _chats = [];
bool _isLoading = true;

@override
void initState() {
super.initState();
_fetchChats();
}

Future<void> _fetchChats() async {


setState(() {
_isLoading = true;
});

try {
final response = await http.get(
Uri.parse(
'http://192.168.1.2:8000/chats?phone=$
{Uri.encodeComponent(widget.phone)}'),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
// Sort chats: pinned first, then by timestamp (oldest first)
List<dynamic> sortedChats = data['chats'];
sortedChats.sort((a, b) {
// Prioritize pinned chats
if (a['is_pinned'] && !b['is_pinned']) return -1;
if (!a['is_pinned'] && b['is_pinned']) return 1;
// If both are pinned or both are not pinned, sort by timestamp (oldest
first)
int aTimestamp = a['timestamp'] ?? 0;
int bTimestamp = b['timestamp'] ?? 0;
if (aTimestamp == 0 && bTimestamp == 0) {
// Fallback to last_message presence (chats with messages come first)
if (a['last_message'] != '' && b['last_message'] == '') return -1;
if (a['last_message'] == '' && b['last_message'] != '') return 1;
return 0;
}
// Ensure chats with no timestamp go to the end
if (aTimestamp == 0) return 1;
if (bTimestamp == 0) return -1;
return aTimestamp.compareTo(bTimestamp); // Oldest first
});
setState(() {
_chats = sortedChats;
_isLoading = false;
});
} else {
final data = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error: ${data['detail'] ?? 'Failed to fetch chats'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
if (response.statusCode == 401) {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('phone');
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginPage()),
);
}
}
setState(() {
_isLoading = false;
});
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Failed to fetch chats. Please check your connection.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
setState(() {
_isLoading = false;
});
}
}

Future<void> _logout() async {


try {
final response = await http.get(
Uri.parse(
'http://192.168.1.2:8000/logout?phone=$
{Uri.encodeComponent(widget.phone)}'),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('phone');
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginPage()),
);
}
} else {
final data = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error: ${data['detail'] ?? 'Logout failed'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Logout failed. Please check your connection.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Chats',
style: TextStyle(fontFamily: 'Roboto', fontWeight: FontWeight.w500),
),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: _logout,
tooltip: 'Logout',
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _chats.isEmpty
? const Center(
child: Text(
'No chats available',
style: TextStyle(fontFamily: 'Roboto', fontSize: 16),
),
)
: ListView.builder(
itemCount: _chats.length,
itemBuilder: (context, index) {
final chat = _chats[index];
return ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
leading: CircleAvatar(
radius: 28,
backgroundColor: Colors.grey[300],
child: chat['photo'] != ''
? ClipOval(
child: Image.network(
'http://192.168.1.2:8000/${chat['photo']}',
width: 56,
height: 56,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) =>
Text(
chat['name'][0],
style: const TextStyle(
fontFamily: 'Roboto',
fontSize: 20,
color: Colors.white),
),
),
)
: Text(
chat['name'][0],
style: const TextStyle(
fontFamily: 'Roboto',
fontSize: 20,
color: Colors.white),
),
),
title: Text(
chat['name'],
style: const TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w500,
fontSize: 16,
),
),
subtitle: Text(
chat['last_message'] ?? 'No messages',
style: TextStyle(
fontFamily: 'Roboto',
color: Colors.grey[600],
fontSize: 14,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: chat['unread_count'] > 0
? CircleAvatar(
radius: 12,
backgroundColor: const Color(0xFF65AADD),
child: Text(
'${chat['unread_count']}',
style: const TextStyle(
fontFamily: 'Roboto',
color: Colors.white,
fontSize: 12,
),
),
)
: null,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatPage(
phone: widget.phone,
chatId: chat['id'],
chatName: chat['name'],
onMessageSent: _fetchChats,
),
),
);
},
);
},
),
);
}
}

// Chat Page to display messages


class ChatPage extends StatefulWidget {
final String phone;
final String chatId;
final String chatName;
final VoidCallback onMessageSent;

const ChatPage({
super.key,
required this.phone,
required this.chatId,
required this.chatName,
required this.onMessageSent,
});

@override
_ChatPageState createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {


List<dynamic> _messages = [];
bool _isLoading = false;
bool _isRecording = false;
String? _recordedFilePath;
final TextEditingController _messageController = TextEditingController();
final AudioRecorder _audioRecorder = AudioRecorder();

@override
void initState() {
super.initState();
_fetchMessages();
_initRecorder();
}

Future<void> _initRecorder() async {


try {
final status = await Permission.microphone.status;
if (!status.isGranted) {
final result = await Permission.microphone.request();
if (!result.isGranted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Microphone permission is required to record voice messages.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Failed to initialize recorder: $e',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
}

Future<void> _startRecording() async {


try {
if (await Permission.microphone.isGranted) {
final directory = await getTemporaryDirectory();
final filePath =
'${directory.path}/voice_${DateTime.now().millisecondsSinceEpoch}.wav';
await _audioRecorder.start(
const RecordConfig(
encoder: AudioEncoder.wav,
bitRate: 128000,
sampleRate: 44100,
),
path: filePath,
);
setState(() {
_isRecording = true;
_recordedFilePath = filePath;
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Microphone permission denied.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Failed to start recording: $e',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
}

Future<void> _stopRecording() async {


try {
await _audioRecorder.stop();
setState(() {
_isRecording = false;
});
if (_recordedFilePath != null &&
await File(_recordedFilePath!).exists()) {
await _sendVoiceMessage();
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Failed to stop recording: $e',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
if (_recordedFilePath != null &&
await File(_recordedFilePath!).exists()) {
await File(_recordedFilePath!).delete();
setState(() {
_recordedFilePath = null;
});
}
}
}

Future<void> _fetchMessages() async {


setState(() {
_isLoading = true;
});

try {
final response = await http.get(
Uri.parse(
'http://192.168.1.2:8000/messages?phone=$
{Uri.encodeComponent(widget.phone)}&chat_id=${widget.chatId}'),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
setState(() {
_messages = data['messages'];
_isLoading = false;
});
} else {
final data = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error: ${data['detail'] ?? 'Failed to fetch messages'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
if (response.statusCode == 401) {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('phone');
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginPage()),
);
}
}
setState(() {
_isLoading = false;
});
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Failed to fetch messages. Please check your connection.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
setState(() {
_isLoading = false;
});
}
}

Future<void> _sendMessage() async {


final message = _messageController.text.trim();
if (message.isEmpty) {
return;
}

setState(() {
_isLoading = true;
});

try {
final response = await http.post(
Uri.parse('http://192.168.1.2:8000/send_message'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'phone': widget.phone,
'recipient': widget.chatId,
'message': message,
}),
);
if (response.statusCode == 200) {
_messageController.clear();
await _fetchMessages();
widget.onMessageSent();
} else {
final data = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error: ${data['detail'] ?? 'Failed to send message'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Failed to send message. Please check your connection.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}

setState(() {
_isLoading = false;
});
}

Future<void> _sendVoiceMessage() async {


if (_recordedFilePath == null || !await File(_recordedFilePath!).exists()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'No voice message recorded.',
style: TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
return;
}

setState(() {
_isLoading = true;
});

try {
var request = http.MultipartRequest(
'POST',
Uri.parse('http://192.168.1.2:8000/send_voice'),
);
request.fields['phone'] = widget.phone;
request.fields['recipient'] = widget.chatId;
request.files.add(
await http.MultipartFile.fromPath('file_path', _recordedFilePath!),
);
final response = await request.send();
final responseData = await response.stream.bytesToString();
if (response.statusCode == 200) {
await _fetchMessages();
widget.onMessageSent();
} else {
final data = jsonDecode(responseData);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error: ${data['detail'] ?? 'Failed to send voice message'}',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Failed to send voice message: $e',
style: const TextStyle(fontFamily: 'Roboto'),
),
backgroundColor: Colors.redAccent,
),
);
} finally {
if (_recordedFilePath != null &&
await File(_recordedFilePath!).exists()) {
await File(_recordedFilePath!).delete();
}
setState(() {
_recordedFilePath = null;
_isLoading = false;
});
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.chatName,
style: const TextStyle(
fontFamily: 'Roboto', fontWeight: FontWeight.w500),
),
),
body: Column(
children: [
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _messages.isEmpty
? const Center(
child: Text(
'No messages available',
style: TextStyle(fontFamily: 'Roboto', fontSize: 16),
),
)
: ListView.builder(
reverse: true,
itemCount: _messages.length,
itemBuilder: (context, index) {
final message =
_messages[_messages.length - 1 - index];
final isSentByUser =
message['is_sent_by_user'] ?? false;
return Align(
alignment: isSentByUser
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(
vertical: 4, horizontal: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isSentByUser
? const Color(0xFFD2E8FF)
: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: isSentByUser
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
if (message['is_voice'] &&
message['file_path'] != null)
Text(
'[Voice Message]',
style: const TextStyle(
fontFamily: 'Roboto',
fontSize: 15,
color: Colors.blueAccent,
),
)
else
Text(
message['text'],
style: const TextStyle(
fontFamily: 'Roboto',
fontSize: 15,
),
),
const SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
DateFormat('HH:mm').format(
DateTime.fromMillisecondsSinceEpoch(
message['timestamp']),
),
style: TextStyle(
fontFamily: 'Roboto',
fontSize: 12,
color: Colors.grey[600],
),
),
if (isSentByUser) ...[
const SizedBox(width: 4),
Icon(
message['is_read']
? Icons.done_all
: Icons.done,
size: 16,
color: Colors.grey[600],
),
],
],
),
],
),
),
);
},
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
color: Colors.white,
child: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: 'Message',
filled: true,
fillColor: const Color(0xFFF5F5F5),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 10),
),
style: const TextStyle(fontFamily: 'Roboto'),
onSubmitted: (_) => _sendMessage(),
),
),
GestureDetector(
onLongPress: _startRecording,
onLongPressUp: _stopRecording,
child: IconButton(
icon: Icon(
_isRecording ? Icons.mic : Icons.mic_none,
color:
_isRecording ? Colors.red : const Color(0xFF65AADD),
),
onPressed:
() {}, // Empty callback to satisfy IconButton requirement
tooltip: 'Hold to Record Voice',
),
),
IconButton(
icon: const Icon(
Icons.send,
color: Color(0xFF65AADD),
),
onPressed: _sendMessage,
tooltip: 'Send Message',
),
],
),
),
],
),
);
}

@override
void dispose() {
_messageController.dispose();
_audioRecorder.dispose(); // Dispose AudioRecorder
super.dispose();
}
}

You might also like