244 lines
8.7 KiB
Dart
244 lines
8.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
|
import 'package:simplecloudnotifier/models/channel.dart';
|
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
|
import 'package:simplecloudnotifier/models/scan_result.dart';
|
|
import 'package:simplecloudnotifier/models/user.dart';
|
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
class ChannelScannerResultMessageSend extends StatefulWidget {
|
|
final ScanResultMessageSend value;
|
|
|
|
const ChannelScannerResultMessageSend({required this.value}) : super();
|
|
|
|
@override
|
|
State<ChannelScannerResultMessageSend> createState() => _ChannelScannerResultMessageSendState();
|
|
}
|
|
|
|
class _ChannelScannerResultMessageSendState extends State<ChannelScannerResultMessageSend> {
|
|
Future<(UserPreview, KeyTokenPreview?)?> _fetchDataFuture;
|
|
|
|
_ChannelScannerResultMessageSendState() : _fetchDataFuture = Future.value(null); // Initial dummy future
|
|
|
|
late TextEditingController _ctrlMessage;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_ctrlMessage = TextEditingController();
|
|
|
|
final auth = Provider.of<AppAuth>(context, listen: false);
|
|
setState(() {
|
|
_fetchDataFuture = _fetchData(auth);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_ctrlMessage.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<(UserPreview, KeyTokenPreview?)?> _fetchData(AppAuth auth) async {
|
|
UserPreview? user = null;
|
|
try {
|
|
user = await APIClient.getUserPreview(auth, widget.value.userID);
|
|
} catch (e, stackTrace) {
|
|
Toaster.error("Error", 'Failed to fetch user preview: ${e.toString()}');
|
|
ApplicationLog.error('Failed to fetch user (preview) for ${widget.value.userID}', trace: stackTrace);
|
|
return null;
|
|
}
|
|
|
|
KeyTokenPreview? key = null;
|
|
if (widget.value.userKey != null) {
|
|
try {
|
|
key = await APIClient.getKeyTokenPreviewByToken(auth, widget.value.userKey!);
|
|
} catch (e, stackTrace) {
|
|
Toaster.error("Error", 'Failed to fetch keytoken preview: ${e.toString()}');
|
|
ApplicationLog.error('Failed to fetch keytoken (preview) for ${widget.value.userID}', trace: stackTrace);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return (user, key);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return FutureBuilder<(UserPreview, KeyTokenPreview?)?>(
|
|
future: _fetchDataFuture,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
if (snapshot.hasError) {
|
|
return Text('Error: ${snapshot.error}'); //TODO better error display
|
|
}
|
|
|
|
if (snapshot.data == null) {
|
|
return Column(
|
|
spacing: 32,
|
|
children: [
|
|
Icon(FontAwesomeIcons.solidTriangleExclamation, size: 64, color: Colors.red[900]),
|
|
Text("Failed to parse QR", textAlign: TextAlign.center),
|
|
],
|
|
);
|
|
}
|
|
|
|
final (user, key) = snapshot.data!;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Text((widget.value.userKey == null) ? "SCN User" : "SCN User & Key", style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20), textAlign: TextAlign.center),
|
|
const SizedBox(height: 16),
|
|
if (user.username != null)
|
|
Row(
|
|
children: [
|
|
ConstrainedBox(child: Text("Name: ", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)),
|
|
Expanded(child: SingleChildScrollView(child: Text(user.username!), scrollDirection: Axis.horizontal)),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
ConstrainedBox(child: Text("UserID:", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)),
|
|
Expanded(child: SingleChildScrollView(child: Text(user.userID, style: const TextStyle(fontStyle: FontStyle.italic)), scrollDirection: Axis.horizontal)),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
if (key != null) ...[
|
|
Row(
|
|
children: [
|
|
ConstrainedBox(child: Text("KeyID:", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)),
|
|
Expanded(child: SingleChildScrollView(child: Text(key.keytokenID, style: const TextStyle(fontStyle: FontStyle.italic)), scrollDirection: Axis.horizontal)),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
ConstrainedBox(child: Text("KeyName:", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)),
|
|
Expanded(child: SingleChildScrollView(child: Text(key.name), scrollDirection: Axis.horizontal)),
|
|
],
|
|
),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
ConstrainedBox(child: Text("Permissions:", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
child: Text(_formatPermissions(key.permissions) + "\n" + (key.allChannels ? "(all channels)" : '(${key.channels.length} channels)')),
|
|
scrollDirection: Axis.horizontal,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
const SizedBox(height: 16),
|
|
if (widget.value.userKey == null)
|
|
Text(
|
|
'QR Code contains no key\nCannot send messages',
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
|
),
|
|
if (widget.value.userKey != null) ..._buildSend(context),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
List<Widget> _buildSend(BuildContext context) {
|
|
return [
|
|
FractionallySizedBox(
|
|
widthFactor: 1.0,
|
|
child: TextField(
|
|
controller: _ctrlMessage,
|
|
decoration: const InputDecoration(
|
|
border: OutlineInputBorder(),
|
|
labelText: 'Text',
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: UI.button(
|
|
text: 'Send Message',
|
|
onPressed: _onSend,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
UI.buttonIconOnly(
|
|
icon: FontAwesomeIcons.earthAmericas,
|
|
onPressed: _onOpenWeb,
|
|
square: true,
|
|
color: Theme.of(context).colorScheme.secondary,
|
|
iconColor: Theme.of(context).colorScheme.onSecondary,
|
|
),
|
|
],
|
|
),
|
|
];
|
|
}
|
|
|
|
void _onSend() async {
|
|
if (_ctrlMessage.text.isEmpty) {
|
|
Toaster.error("Error", 'Please enter a message');
|
|
return;
|
|
}
|
|
|
|
if (widget.value.userKey == null) return;
|
|
|
|
try {
|
|
await APIClient.sendMessage(widget.value.userID, widget.value.userKey!, _ctrlMessage.text);
|
|
Toaster.success("Success", 'Message sent');
|
|
setState(() {
|
|
_ctrlMessage.clear();
|
|
});
|
|
} catch (e, stackTrace) {
|
|
Toaster.error("Error", 'Failed to send message: ${e.toString()}');
|
|
ApplicationLog.error('Failed to send message', trace: stackTrace);
|
|
}
|
|
}
|
|
|
|
void _onOpenWeb() async {
|
|
try {
|
|
final Uri uri = Uri.parse(widget.value.url);
|
|
|
|
ApplicationLog.debug('Opening URL: [ ${uri.toString()} ]');
|
|
|
|
if (await canLaunchUrl(uri)) {
|
|
await launchUrl(uri);
|
|
} else {
|
|
Toaster.error("Error", 'Cannot open URL on this system');
|
|
}
|
|
} catch (exc, trace) {
|
|
ApplicationLog.error('Failed to open URL: ' + exc.toString(), additional: 'URL: ${widget.value.url}', trace: trace);
|
|
}
|
|
}
|
|
|
|
String _formatPermissions(String v) {
|
|
var splt = v.split(';');
|
|
|
|
if (splt.length == 0) return "None";
|
|
|
|
List<String> result = [];
|
|
|
|
if (splt.contains("A")) result.add(" - Admin");
|
|
if (splt.contains("UR")) result.add(" - Read Account");
|
|
if (splt.contains("CR")) result.add(" - Read Messages");
|
|
if (splt.contains("CS")) result.add(" - Send Messages");
|
|
|
|
return result.join("\n");
|
|
}
|
|
}
|