Finish implementing send page
This commit is contained in:
@@ -178,11 +178,12 @@ class _ChannelScannerResultMessageSendState extends State<ChannelScannerResultMe
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
UI.button(
|
||||
text: 'Web',
|
||||
UI.buttonIconOnly(
|
||||
icon: FontAwesomeIcons.earthAmericas,
|
||||
onPressed: _onOpenWeb,
|
||||
square: true,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
textColor: Theme.of(context).colorScheme.onSecondary,
|
||||
iconColor: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -200,6 +201,9 @@ class _ChannelScannerResultMessageSendState extends State<ChannelScannerResultMe
|
||||
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);
|
||||
|
@@ -1,13 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
import 'package:simplecloudnotifier/state/globals.dart';
|
||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||
|
||||
class SendRootPage extends StatefulWidget {
|
||||
const SendRootPage({super.key, required bool isVisiblePage});
|
||||
const SendRootPage({super.key, required this.isVisiblePage});
|
||||
|
||||
final bool isVisiblePage;
|
||||
|
||||
@override
|
||||
State<SendRootPage> createState() => _SendRootPageState();
|
||||
@@ -16,18 +22,28 @@ class SendRootPage extends StatefulWidget {
|
||||
class _SendRootPageState extends State<SendRootPage> {
|
||||
late TextEditingController _msgTitle;
|
||||
late TextEditingController _msgContent;
|
||||
late TextEditingController _channelName;
|
||||
late TextEditingController _senderName;
|
||||
|
||||
int _priority = 0;
|
||||
|
||||
bool _expanded = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_msgTitle = TextEditingController();
|
||||
_msgContent = TextEditingController();
|
||||
_channelName = TextEditingController();
|
||||
_senderName = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_msgTitle.dispose();
|
||||
_msgContent.dispose();
|
||||
_channelName.dispose();
|
||||
_senderName.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -38,52 +54,162 @@ class _SendRootPageState extends State<SendRootPage> {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildQRCode(context, acc),
|
||||
const SizedBox(height: 16),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1.0,
|
||||
child: TextField(
|
||||
controller: _msgTitle,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Title',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1.0,
|
||||
child: TextField(
|
||||
controller: _msgContent,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Text',
|
||||
),
|
||||
minLines: 2,
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
||||
onPressed: _send,
|
||||
child: const Text('Send'),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
child: _expanded ? _buildExpanded(context, acc) : _buildSimple(context, acc),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _send() {
|
||||
//...
|
||||
Widget _buildSimple(BuildContext context, AppAuth acc) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildQRCode(context, acc),
|
||||
const SizedBox(height: 16),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1.0,
|
||||
child: TextField(
|
||||
controller: _msgTitle,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Title',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1.0,
|
||||
child: TextField(
|
||||
controller: _msgContent,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Text',
|
||||
),
|
||||
minLines: 2,
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: UI.button(
|
||||
text: 'Send',
|
||||
onPressed: () {
|
||||
_sendSimple(acc);
|
||||
},
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
UI.buttonIconOnly(
|
||||
icon: FontAwesomeIcons.layerPlus,
|
||||
onPressed: _openExpanded,
|
||||
square: true,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
iconColor: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExpanded(BuildContext context, AppAuth acc) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1.0,
|
||||
child: TextField(
|
||||
controller: _channelName,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Channel',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1.0,
|
||||
child: TextField(
|
||||
controller: _msgTitle,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Title',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1.0,
|
||||
child: TextField(
|
||||
controller: _senderName,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'SenderName',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SegmentedButton<int>(
|
||||
showSelectedIcon: false,
|
||||
segments: const <ButtonSegment<int>>[
|
||||
ButtonSegment<int>(value: 0, label: Text('Low Priority')),
|
||||
ButtonSegment<int>(value: 1, label: Text('Normal')),
|
||||
ButtonSegment<int>(value: 2, label: Text('High Priority')),
|
||||
],
|
||||
selected: {_priority},
|
||||
onSelectionChanged: (Set<int> newSelection) {
|
||||
setState(() {
|
||||
_priority = newSelection.isEmpty ? 1 : newSelection.first;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1.0,
|
||||
child: TextField(
|
||||
controller: _msgContent,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Text',
|
||||
),
|
||||
minLines: 6,
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: UI.button(
|
||||
text: 'Send',
|
||||
onPressed: () {
|
||||
_sendExpanded(acc);
|
||||
},
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
UI.buttonIconOnly(
|
||||
icon: FontAwesomeIcons.squareDashed,
|
||||
onPressed: _closeExpanded,
|
||||
square: true,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
iconColor: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQRCode(BuildContext context, AppAuth acc) {
|
||||
@@ -94,39 +220,82 @@ class _SendRootPageState extends State<SendRootPage> {
|
||||
return FutureBuilder(
|
||||
future: acc.loadUser(force: false),
|
||||
builder: ((context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.hasError) {
|
||||
return Text('Error: ${snapshot.error}'); //TODO better error display
|
||||
}
|
||||
var url = (acc.tokenSend == null) ? 'https://simplecloudnotifier.de?preset_user_id=${acc.userID}' : 'https://simplecloudnotifier.de?preset_user_id=${acc.userID}&preset_user_key=${acc.tokenSend}';
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_openWeb(url);
|
||||
},
|
||||
child: QrImageView(
|
||||
data: url,
|
||||
version: QrVersions.auto,
|
||||
size: 300.0,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
),
|
||||
),
|
||||
if (snapshot.connectionState == ConnectionState.active || snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const SizedBox(
|
||||
width: 300.0,
|
||||
height: 300.0,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
return const SizedBox(
|
||||
width: 300.0,
|
||||
height: 300.0,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
if (snapshot.hasError) {
|
||||
return Text('Error: ${snapshot.error}'); //TODO better error display
|
||||
}
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return Text('...'); //?
|
||||
}
|
||||
|
||||
var url = (acc.tokenSend == null) ? 'https://simplecloudnotifier.de?preset_user_id=${acc.userID}' : 'https://simplecloudnotifier.de?preset_user_id=${acc.userID}&preset_user_key=${acc.tokenSend}';
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_openWeb(url);
|
||||
},
|
||||
child: QrImageView(
|
||||
data: url,
|
||||
version: QrVersions.auto,
|
||||
size: 300.0,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void _sendSimple(AppAuth acc) async {
|
||||
if (!acc.isAuth()) {
|
||||
Toaster.error("Error", 'Must be logged in to send messages');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await APIClient.sendMessage(acc.userID!, acc.tokenSend!, _msgContent.text);
|
||||
Toaster.success("Success", 'Message sent');
|
||||
setState(() {
|
||||
_msgTitle.clear();
|
||||
_msgContent.clear();
|
||||
});
|
||||
} catch (e, stackTrace) {
|
||||
Toaster.error("Error", 'Failed to send message: ${e.toString()}');
|
||||
ApplicationLog.error('Failed to send message', trace: stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _sendExpanded(AppAuth acc) async {
|
||||
if (!acc.isAuth()) {
|
||||
Toaster.error("Error", 'Must be logged in to send messages');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await APIClient.sendMessage(acc.userID!, acc.tokenSend!, _msgContent.text, channel: _channelName.text, senderName: _senderName.text, priority: _priority);
|
||||
Toaster.success("Success", 'Message sent');
|
||||
setState(() {
|
||||
_msgTitle.clear();
|
||||
_msgContent.clear();
|
||||
});
|
||||
} catch (e, stackTrace) {
|
||||
Toaster.error("Error", 'Failed to send message: ${e.toString()}');
|
||||
ApplicationLog.error('Failed to send message', trace: stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _openWeb(String url) async {
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
@@ -142,4 +311,24 @@ class _SendRootPageState extends State<SendRootPage> {
|
||||
ApplicationLog.error('Failed to open URL: ' + exc.toString(), additional: 'URL: ${url}', trace: trace);
|
||||
}
|
||||
}
|
||||
|
||||
void _closeExpanded() {
|
||||
setState(() {
|
||||
_expanded = false;
|
||||
_channelName.clear();
|
||||
_priority = 1;
|
||||
_senderName.clear();
|
||||
});
|
||||
}
|
||||
|
||||
void _openExpanded() {
|
||||
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||
|
||||
setState(() {
|
||||
_expanded = true;
|
||||
_channelName.text = userAcc.getUserOrNull()?.defaultChannel ?? 'main';
|
||||
_priority = 1;
|
||||
_senderName.text = Globals().deviceName;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ class _SettingsRootPageState extends State<SettingsRootPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Text('Settings'),
|
||||
child: Text('(coming soon...)'), //TODO
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user