pogdark-app-flutter/lib/status_page.dart
whysman ad2c94c29a
Some checks failed
Build Flutter Web and Docker Image for Local Registry / Build Flutter Web App (push) Has been cancelled
Removing dotenv, using env variables at build
2024-11-12 23:57:33 -05:00

379 lines
12 KiB
Dart

import 'dart:async';
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';
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 {
WebSocketChannel? channel;
late final Stream<dynamic> broadcastStream;
late StreamController<dynamic> controller;
List<Map<String, dynamic>> messages = [];
final Map<String, ImageProvider> _imageCache = {};
final wsBaseUrl = String.fromEnvironment('WS_BASE_URL',
defaultValue: 'ws://localhost:8080');
final 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'),
);
controller = StreamController<dynamic>.broadcast();
controller.addStream(channel!.stream);
broadcastStream = channel!.stream.asBroadcastStream();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
channel?.sink.close();
controller.close();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_reconnectWebSocket();
}
}
void _reconnectWebSocket() {
if (channel != null) {
channel!.sink.close();
}
_initializeWebSocket();
}
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 {
final prefsProvider =
Provider.of<SharedPreferencesProvider>(context, listen: false);
if (!mounted) return;
final isStatusActive = prefsProvider.getCurrentStatus() == status;
final newStatus = isStatusActive ? 'none' : status;
await prefsProvider.setCurrentStatus(newStatus);
final message = {
'Id': id,
'Name': name,
'Image': image,
'Status': newStatus.isEmpty ? 'none' : newStatus,
'Timestamp': DateTime.now().toIso8601String(),
};
//final url = Uri.parse('http://localhost:8080/set');
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", // Only use hex values for web
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", // Only use hex values for web
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"); // Use debugPrint instead of exposing error
}
}
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'];
messages.removeWhere((msg) => msg['Id'] == incomingId);
if (status == 'expired' && incomingId == prefsProvider.getUserId()) {
await prefsProvider.setCurrentStatus('none');
} else {
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(
title: Image.asset(
'assets/pogdark_logo.png',
height: 40,
),
backgroundColor: Colors.blueAccent,
),
body: StreamBuilder(
stream: controller.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final newMessage =
jsonDecode(snapshot.data as String) as Map<String, dynamic>;
_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),
),
],
],
),
],
),
);
},
),
);
}
}