import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show ByteData, rootBundle; import 'package:image_picker/image_picker.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:uuid/uuid.dart'; import 'dart:async'; void main() { runApp( ChangeNotifierProvider( create: (_) => SharedPreferencesProvider(), child: const MyApp(), ), ); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override MyAppState createState() => MyAppState(); } class MyAppState extends State { Future? _prefsReady; @override void initState() { super.initState(); _prefsReady = Provider.of(context, listen: false).ready; } @override Widget build(BuildContext context) { return MaterialApp( title: 'Pogdark', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), home: FutureBuilder( future: _prefsReady, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (Provider.of(context).getUserName() != '') { return const ChatPage(); } else { return const ProfileScreen(isEditing: false); } } else { return const Center(child: CircularProgressIndicator()); } }, ), ); } } class SharedPreferencesProvider extends ChangeNotifier { late SharedPreferences prefs; final Completer _initCompleter = Completer(); SharedPreferencesProvider() { _initPrefs(); } Future _initPrefs() async { prefs = await SharedPreferences.getInstance(); if (prefs.getString('userLogo') == null) { ByteData bytes = await rootBundle.load('assets/default_logo.png'); List imageBytes = bytes.buffer.asUint8List(); prefs.setString('userLogo', base64Encode(imageBytes)); prefs.setString('id', const Uuid().v4()); } _initCompleter.complete(); notifyListeners(); } Future get ready => _initCompleter.future; String getUserName() { return prefs.getString('userName') ?? ''; } Future setUserName(String name) async { await prefs.setString('userName', name); notifyListeners(); } String getUserId() { return prefs.getString('id') ?? ''; } String? getUserLogo() { final userLogo = prefs.getString('userLogo'); return userLogo; } Future setUserLogo(String? image) async { await prefs.setString('userLogo', image!); notifyListeners(); } } class ProfileScreen extends StatefulWidget { final bool isEditing; const ProfileScreen({super.key, required this.isEditing}); @override ProfileScreenState createState() => ProfileScreenState(); } class ProfileScreenState extends State { final TextEditingController _nameController = TextEditingController(); late String? imageData; final ImagePicker _picker = ImagePicker(); @override void initState() { super.initState(); final prefsProvider = Provider.of(context, listen: false); _nameController.text = prefsProvider.getUserName(); imageData = prefsProvider.getUserLogo(); } Future _pickImage() async { final pickedFile = await _picker.pickImage(source: ImageSource.gallery); if (pickedFile != null) { late final Uint8List imageBytes; if (kIsWeb) { imageBytes = await pickedFile.readAsBytes(); } else { imageBytes = await File(pickedFile.path).readAsBytes(); } setState(() { if (mounted) { final prefsProvider = Provider.of(context, listen: false); prefsProvider .setUserLogo(base64Encode(imageBytes)); // Cache the image imageData = base64Encode(imageBytes); } }); } } void _saveProfile() async { String name = _nameController.text.trim(); if (name.isNotEmpty) { final prefsProvider = Provider.of(context, listen: false); await prefsProvider.setUserName(name); await prefsProvider.setUserLogo(imageData); if (widget.isEditing) { if (mounted) { Navigator.pop(context); // Go back to the chat screen if editing } } else { _navigateToChatScreen(); // Go to chat screen if this is the initial entry } } } void _navigateToChatScreen() { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const ChatPage()), ); } @override void dispose() { _nameController.dispose(); super.dispose(); } Image getLogoImage(String? logo) { if (logo != null) { return Image.memory(base64Decode(logo)); } else { return Image.asset('assets/default_logo.png'); } } @override Widget build(BuildContext context) { final prefsProvider = Provider.of(context); final userLogo = prefsProvider.getUserLogo(); return Scaffold( appBar: AppBar( title: Text( widget.isEditing ? 'Edit Profile' : 'Enter Your Name and Logo'), backgroundColor: Colors.blueAccent, ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Profile Image Display Center( child: CircleAvatar( radius: 50, backgroundImage: getLogoImage(userLogo).image, ), ), const SizedBox(height: 20), TextField( controller: _nameController, decoration: const InputDecoration( labelText: 'Name', border: OutlineInputBorder(), ), ), const SizedBox(height: 20), ElevatedButton( onPressed: _pickImage, child: const Text('Upload Logo'), ), const SizedBox(height: 20), ElevatedButton( onPressed: _saveProfile, child: Text(widget.isEditing ? 'Save Changes' : 'Continue'), ), ], ), ), ); } } class ChatPage extends StatefulWidget { const ChatPage({super.key}); @override ChatPageState createState() => ChatPageState(); } class ChatPageState extends State { final WebSocketChannel channel = WebSocketChannel.connect( Uri.parse( 'ws://localhost:8080/ws'), // Ensure this matches your server's address ); // Convert the stream to a broadcast stream late final Stream broadcastStream; late StreamController controller; List> messages = []; // To hold user messages with names and timestamps @override void initState() { super.initState(); controller = StreamController.broadcast(); controller.addStream(channel.stream); broadcastStream = channel.stream.asBroadcastStream(); } List> _getMessagesByStatus(String status) { return messages.where((message) => message['Status'] == status).toList(); } void _navigateToEditProfile() { Navigator.push( context, MaterialPageRoute( builder: (context) => const ProfileScreen(isEditing: true)), ); } void _sendStatus(String id, String name, String? image, String status) { final message = jsonEncode({ 'Id': id, 'Name': name, 'Image': image, 'Status': status, 'Timestamp': DateTime.now().toIso8601String(), }); channel.sink.add(message); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Status "$status" sent!')), ); } Widget _buildMessageItem(Map message) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Align( alignment: Alignment.centerLeft, child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( radius: 20, backgroundImage: message['Image'] != null ? Image.memory(base64Decode(message['Image'])).image : const AssetImage('assets/default_logo.png'), ), const SizedBox(width: 8), Text( "${message['Name']}", style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 4), Text( "Received at: ${message['Timestamp']}", style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), ), ); } @override Widget build(BuildContext context) { final prefsProvider = Provider.of(context); final userName = prefsProvider.getUserName(); final userLogo = prefsProvider.getUserLogo(); final userId = prefsProvider.getUserId(); return Scaffold( appBar: AppBar( title: const Text('Pogdark'), backgroundColor: Colors.blueAccent, actions: [ IconButton( icon: const Icon(Icons.edit), onPressed: _navigateToEditProfile, ), ], ), body: StreamBuilder( stream: controller.stream, builder: (context, snapshot) { if (snapshot.hasData) { final newMessage = jsonDecode(snapshot.data as String) as Map; // Update messages only if new or modified final incomingId = newMessage['Id']; messages.removeWhere((msg) => msg['Id'] == incomingId); if (newMessage['Status'] != 'expired' || newMessage['Status'] != "is leaving") { messages.add(newMessage); } } // Separate messages by status final onTheWayMessages = _getMessagesByStatus('is on the way'); final arrivedMessages = _getMessagesByStatus('has arrived'); return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Expanded( child: Row( children: [ // Column for "On the way" messages Expanded( child: Column( children: [ const Text( 'On the Way', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), Expanded( child: ListView.builder( itemCount: onTheWayMessages.length, itemBuilder: (context, index) { return _buildMessageItem( onTheWayMessages[index]); }, ), ), ], ), ), // Column for "Arrived" messages Expanded( child: Column( children: [ const Text( 'Arrived', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), Expanded( child: ListView.builder( itemCount: arrivedMessages.length, itemBuilder: (context, index) { return _buildMessageItem( arrivedMessages[index]); }, ), ), ], ), ), ], ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( onPressed: () => _sendStatus( userId, userName, userLogo, 'is on the way'), child: const Text('On the Way'), ), ElevatedButton( onPressed: () => _sendStatus( userId, userName, userLogo, 'has arrived'), child: const Text('Arrived'), ), ElevatedButton( onPressed: () => _sendStatus(userId, userName, userLogo, 'is leaving'), child: const Text('Leaving'), ), ], ), ], ), ); }, ), ); } @override void dispose() { channel.sink.close(); super.dispose(); } }