pogdark-app-flutter/lib/status_page.dart
whysman 15f780ead6
All checks were successful
Build Flutter Web and Docker Image for Local Registry / Build Flutter Web App (push) Successful in 3m22s
Continuing to troubleshoot ws
2024-11-23 14:09:45 -05:00

485 lines
16 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:http/http.dart' as http;
import 'shared_preferences_provider.dart';
import 'custom_menu.dart';
class StatusPage extends StatefulWidget {
final VoidCallback toggleProfile;
const StatusPage({super.key, required this.toggleProfile});
@override
StatusPageState createState() => StatusPageState();
}
class StatusPageState extends State<StatusPage> with WidgetsBindingObserver {
late final WebSocketChannel channel;
List<Map<String, dynamic>> messages = [];
final Map<String, ImageProvider> _imageCache = {};
static const wsBaseUrl = String.fromEnvironment('WS_BASE_URL',
defaultValue: 'ws://localhost:8080');
static const restBaseUrl = String.fromEnvironment('REST_BASE_URL',
defaultValue: 'http://localhost:8080');
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initializeWebSocket();
}
void _initializeWebSocket() {
channel = WebSocketChannel.connect(
Uri.parse('$wsBaseUrl/ws'),
);
debugPrint("WebSocket initialized at: $wsBaseUrl/ws");
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
channel.sink.close();
debugPrint("WebSocket connection closed.");
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
debugPrint("App resumed");
updateStatus();
updateMessages();
}
}
void updateStatus() async {
final url = Uri.parse('$restBaseUrl/get');
final prefsProvider =
Provider.of<SharedPreferencesProvider>(context, listen: false);
try {
debugPrint("Updating status...");
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({"Id: $prefsProvider.getUserId()"}),
);
debugPrint(response.statusCode.toString());
if (response.statusCode == 200) {
final jsonResponse = jsonDecode(response.body) as Map<String, dynamic>;
// Extract the 'Status' field or other relevant fields from the response
String newStatus = jsonResponse['Status'] ?? 'none';
debugPrint("Starting status update: $newStatus");
// Store the new status in SharedPreferences
await prefsProvider.setCurrentStatus(newStatus);
debugPrint("Status updated: $newStatus");
} else {
debugPrint("Failed to update status: ${response.statusCode}");
}
} catch (e) {
debugPrint("Error in updateMessages: $e");
}
}
void updateMessages() async {
final url = Uri.parse('$restBaseUrl/update');
try {
final response = await http.get(
url,
headers: {'Content-Type': 'application/json'},
);
debugPrint("Response body: ${response.body}\n");
if (response.statusCode == 200) {
final parsed = jsonDecode(response.body);
// Ensure 'parsed' is a List, otherwise handle gracefully
if (parsed is List<dynamic>) {
debugPrint("Messages to update: ${parsed.length}");
// Ensure each item is a Map<String, dynamic>
final List<Map<String, dynamic>> jsonResponse = parsed
.where((item) => item is Map<String, dynamic>)
.map((item) => item as Map<String, dynamic>)
.toList();
setState(() {
messages.clear(); // Clear the existing messages
messages.addAll(jsonResponse); // Add new messages
});
} else {
debugPrint("API returned unexpected format: ${response.body}");
setState(() {
messages.clear(); // Clear messages if format is invalid
});
}
} else {
debugPrint("Failed to fetch messages: ${response.statusCode}");
}
} catch (e) {
debugPrint("Error in updateMessages: $e");
}
}
List<Map<String, dynamic>> _getMessagesByStatus(String status) {
return messages.where((message) => message['Status'] == status).toList();
}
void _sendStatus(String id, String name, String? image, String status) async {
debugPrint("Entering _sendStatus");
final prefsProvider =
Provider.of<SharedPreferencesProvider>(context, listen: false);
debugPrint("Is mounted: $mounted");
if (!mounted) return;
debugPrint("Sending status: $status");
final isStatusActive = prefsProvider.getCurrentStatus() == status;
debugPrint("Is status active: $isStatusActive");
final newStatus = isStatusActive ? 'none' : status;
debugPrint("New status: $newStatus");
// Update local status in SharedPreferences
await prefsProvider.setCurrentStatus(newStatus);
debugPrint(prefsProvider.getCurrentStatus());
// Create the status message
final message = {
'Id': id,
'Name': name,
'Image': image,
'Status': newStatus.isEmpty ? 'none' : newStatus,
'Timestamp': DateTime.now().toIso8601String(),
};
// Update the local messages list with the new status message
setState(() {
// Remove existing message from the same user to avoid duplicates
messages.removeWhere((msg) => msg['Id'] == id);
// Add the updated message to the local messages list
if (newStatus != 'none') {
messages.add(message);
debugPrint("Adding local message: $newStatus");
}
});
// Send the updated status message to the WebSocket for other users
//channel?.sink.add(jsonEncode(message));
// Send the status message to the REST API as well
final url = Uri.parse('$restBaseUrl/set');
try {
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(message),
);
if (!mounted) return; // Check if widget is still in the tree
if (response.statusCode == 200) {
Fluttertoast.showToast(
msg: 'Status "${newStatus == 'none' ? 'cleared' : newStatus}" sent!',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.blueAccent,
webBgColor: "#0000FF",
textColor: Colors.white,
fontSize: 16.0,
timeInSecForIosWeb: 1,
);
} else {
Fluttertoast.showToast(
msg: 'Failed to send status. Please try again.',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.redAccent,
webBgColor: "#FF0000",
textColor: Colors.white,
fontSize: 16.0,
timeInSecForIosWeb: 1,
);
}
} catch (e) {
if (!mounted) return;
Fluttertoast.showToast(
msg: 'Error sending status. Please check your connection.',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.redAccent,
webBgColor: "#FF0000",
textColor: Colors.white,
fontSize: 16.0,
timeInSecForIosWeb: 1,
);
debugPrint("Error in _sendStatus: $e");
}
}
void _handleIncomingMessage(Map<String, dynamic> message) async {
final prefsProvider =
Provider.of<SharedPreferencesProvider>(context, listen: false);
final status = message['Status'];
final incomingId = message['Id'];
final image = message['Image'];
final timeStamp = message['Timestamp'];
final prefsId = prefsProvider.getUserId();
debugPrint(
"Incoming id $incomingId, status: $status, prefsID: $prefsId, timestamp: $timeStamp");
if (incomingId == prefsProvider.getUserId()) {
if (status == 'removed' || status == 'none') {
debugPrint("Clearing local message: $status");
await prefsProvider.setCurrentStatus('none');
} else {
debugPrint("Ignoring own message: $status");
return;
}
} else {
if (status == 'removed' || status == 'none') {
debugPrint("Checking for messages from user: $incomingId");
if (messages.any((msg) => msg['Id'] == incomingId)) {
debugPrint("Removing message from user: $incomingId");
messages.removeWhere((msg) => msg['Id'] == incomingId);
}
} else {
debugPrint("Adding incoming message: $status");
messages.add(message);
_cacheImage(incomingId, image);
}
}
}
void _cacheImage(String id, String? base64Image) {
if (base64Image != null) {
_imageCache[id] = Image.memory(base64Decode(base64Image)).image;
}
}
void _showImageDialog(BuildContext context, ImageProvider imageProvider) {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
child: GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.black87,
),
padding: const EdgeInsets.all(8.0),
child: Image(image: imageProvider, fit: BoxFit.contain),
),
),
);
},
);
}
Widget _buildMessageItem(Map<String, dynamic> message) {
final imageId = message['Id'];
final imageProvider = _imageCache[imageId] ??
const AssetImage('assets/default_profile_image.png');
return GestureDetector(
onTap: () {
if (_imageCache.containsKey(imageId)) {
_showImageDialog(context, imageProvider);
}
},
child: 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: imageProvider,
),
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),
),
],
),
),
),
),
);
}
Color getButtonColor(String buttonStatus) {
final prefsProvider = Provider.of<SharedPreferencesProvider>(context);
final currentStatus = prefsProvider.getCurrentStatus();
return currentStatus == buttonStatus ? Colors.blueAccent : Colors.grey;
}
Color getButtonTextColor(String buttonStatus) {
final prefsProvider = Provider.of<SharedPreferencesProvider>(context);
final currentStatus = prefsProvider.getCurrentStatus();
return currentStatus == buttonStatus ? Colors.white : Colors.black;
}
@override
Widget build(BuildContext context) {
final prefsProvider = Provider.of<SharedPreferencesProvider>(context);
final userName = prefsProvider.getUserName();
final userLogo = prefsProvider.getUserLogo();
final userId = prefsProvider.getUserId();
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blueAccent,
centerTitle: true,
title: Stack(
alignment: Alignment.center,
children: [
// Menu icon in the top left
Align(
alignment: Alignment.centerLeft,
child: CustomMenu(),
),
// Centered Pogdark logo
Align(
alignment: Alignment.center,
child: Image.asset(
'assets/pogdark_logo.png',
height: 40,
),
),
],
),
),
body: StreamBuilder(
stream: channel.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final newMessage =
jsonDecode(snapshot.data as String) as Map<String, dynamic>;
final id = newMessage['Id'];
debugPrint("Handling incoming message: $id");
_handleIncomingMessage(newMessage);
}
var status = prefsProvider.getCurrentStatus();
final onTheWayMessages = _getMessagesByStatus('otw');
final arrivedMessages = _getMessagesByStatus('here');
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Expanded(
child: Row(
children: [
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]);
},
),
),
],
),
),
Expanded(
child: Column(
children: [
const Text(
'At the Pogdark',
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(
style: ElevatedButton.styleFrom(
backgroundColor: getButtonColor('otw'),
),
onPressed: () =>
_sendStatus(userId, userName, userLogo, 'otw'),
child: Text(
'On the Way',
style: TextStyle(color: getButtonTextColor('otw')),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: getButtonColor('here'),
),
onPressed: () =>
_sendStatus(userId, userName, userLogo, 'here'),
child: Text(
'Arrived',
style: TextStyle(color: getButtonTextColor('here')),
),
),
IconButton(
icon: const Icon(Icons.edit, color: Colors.blueAccent),
onPressed: widget.toggleProfile,
tooltip: 'Edit Profile',
),
if (kDebugMode) ...[
const SizedBox(height: 20),
Text(
status,
style: TextStyle(color: Colors.red),
),
],
],
),
],
),
);
},
),
);
}
}