Finish implementing send page
This commit is contained in:
parent
95353735b0
commit
e96be86314
@ -21,7 +21,7 @@
|
|||||||
- [ ] read + migrate old SharedPrefs (or not? - who uses SCN even??)
|
- [ ] read + migrate old SharedPrefs (or not? - who uses SCN even??)
|
||||||
- [ ] Account-Page
|
- [ ] Account-Page
|
||||||
- [ ] Logout
|
- [ ] Logout
|
||||||
- [ ] Send-page
|
- [x] Send-page
|
||||||
|
|
||||||
- [ ] Still @ERROR on scn-init, but no logs? - better persist error (write in SharedPrefs at error_$date=txt ?), also perhaps print first error line in scn-init notification?
|
- [ ] Still @ERROR on scn-init, but no logs? - better persist error (write in SharedPrefs at error_$date=txt ?), also perhaps print first error line in scn-init notification?
|
||||||
|
|
||||||
|
@ -178,11 +178,12 @@ class _ChannelScannerResultMessageSendState extends State<ChannelScannerResultMe
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
UI.button(
|
UI.buttonIconOnly(
|
||||||
text: 'Web',
|
icon: FontAwesomeIcons.earthAmericas,
|
||||||
onPressed: _onOpenWeb,
|
onPressed: _onOpenWeb,
|
||||||
|
square: true,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
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 {
|
try {
|
||||||
await APIClient.sendMessage(widget.value.userID, widget.value.userKey!, _ctrlMessage.text);
|
await APIClient.sendMessage(widget.value.userID, widget.value.userKey!, _ctrlMessage.text);
|
||||||
Toaster.success("Success", 'Message sent');
|
Toaster.success("Success", 'Message sent');
|
||||||
|
setState(() {
|
||||||
|
_ctrlMessage.clear();
|
||||||
|
});
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
Toaster.error("Error", 'Failed to send message: ${e.toString()}');
|
Toaster.error("Error", 'Failed to send message: ${e.toString()}');
|
||||||
ApplicationLog.error('Failed to send message', trace: stackTrace);
|
ApplicationLog.error('Failed to send message', trace: stackTrace);
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.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/application_log.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
|
||||||
class SendRootPage extends StatefulWidget {
|
class SendRootPage extends StatefulWidget {
|
||||||
const SendRootPage({super.key, required bool isVisiblePage});
|
const SendRootPage({super.key, required this.isVisiblePage});
|
||||||
|
|
||||||
|
final bool isVisiblePage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SendRootPage> createState() => _SendRootPageState();
|
State<SendRootPage> createState() => _SendRootPageState();
|
||||||
@ -16,18 +22,28 @@ class SendRootPage extends StatefulWidget {
|
|||||||
class _SendRootPageState extends State<SendRootPage> {
|
class _SendRootPageState extends State<SendRootPage> {
|
||||||
late TextEditingController _msgTitle;
|
late TextEditingController _msgTitle;
|
||||||
late TextEditingController _msgContent;
|
late TextEditingController _msgContent;
|
||||||
|
late TextEditingController _channelName;
|
||||||
|
late TextEditingController _senderName;
|
||||||
|
|
||||||
|
int _priority = 0;
|
||||||
|
|
||||||
|
bool _expanded = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_msgTitle = TextEditingController();
|
_msgTitle = TextEditingController();
|
||||||
_msgContent = TextEditingController();
|
_msgContent = TextEditingController();
|
||||||
|
_channelName = TextEditingController();
|
||||||
|
_senderName = TextEditingController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_msgTitle.dispose();
|
_msgTitle.dispose();
|
||||||
_msgContent.dispose();
|
_msgContent.dispose();
|
||||||
|
_channelName.dispose();
|
||||||
|
_senderName.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +54,15 @@ class _SendRootPageState extends State<SendRootPage> {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: _expanded ? _buildExpanded(context, acc) : _buildSimple(context, acc),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSimple(BuildContext context, AppAuth acc) {
|
||||||
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
_buildQRCode(context, acc),
|
_buildQRCode(context, acc),
|
||||||
@ -68,22 +92,124 @@ class _SendRootPageState extends State<SendRootPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FilledButton(
|
Row(
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
children: [
|
||||||
onPressed: _send,
|
Expanded(
|
||||||
child: const Text('Send'),
|
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),
|
const SizedBox(height: 32),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _send() {
|
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) {
|
Widget _buildQRCode(BuildContext context, AppAuth acc) {
|
||||||
@ -94,11 +220,22 @@ class _SendRootPageState extends State<SendRootPage> {
|
|||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: acc.loadUser(force: false),
|
future: acc.loadUser(force: false),
|
||||||
builder: ((context, snapshot) {
|
builder: ((context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.active || snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const SizedBox(
|
||||||
|
width: 300.0,
|
||||||
|
height: 300.0,
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Text('Error: ${snapshot.error}'); //TODO better error display
|
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}';
|
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(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_openWeb(url);
|
_openWeb(url);
|
||||||
@ -117,16 +254,48 @@ class _SendRootPageState extends State<SendRootPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return const SizedBox(
|
|
||||||
width: 300.0,
|
|
||||||
height: 300.0,
|
|
||||||
child: Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
void _openWeb(String url) async {
|
||||||
try {
|
try {
|
||||||
final Uri uri = Uri.parse(url);
|
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);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text('Settings'),
|
child: Text('(coming soon...)'), //TODO
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isAuth() {
|
bool isAuth() {
|
||||||
return _userID != null && _tokenAdmin != null;
|
return _userID != null && _tokenAdmin != null && _tokenSend != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set(User user, Client client, String tokenAdmin, String tokenSend) {
|
void set(User user, Client client, String tokenAdmin, String tokenSend) {
|
||||||
|
@ -25,6 +25,7 @@ class Globals {
|
|||||||
String hostname = '';
|
String hostname = '';
|
||||||
String clientType = '';
|
String clientType = '';
|
||||||
String deviceModel = '';
|
String deviceModel = '';
|
||||||
|
String deviceName = '';
|
||||||
|
|
||||||
late SharedPreferences sharedPrefs;
|
late SharedPreferences sharedPrefs;
|
||||||
|
|
||||||
@ -48,18 +49,23 @@ class Globals {
|
|||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
this.clientType = 'ANDROID';
|
this.clientType = 'ANDROID';
|
||||||
this.deviceModel = (await DeviceInfoPlugin().androidInfo).model;
|
this.deviceModel = (await DeviceInfoPlugin().androidInfo).model;
|
||||||
|
this.deviceName = (await DeviceInfoPlugin().androidInfo).name;
|
||||||
} else if (Platform.isIOS) {
|
} else if (Platform.isIOS) {
|
||||||
this.clientType = 'IOS';
|
this.clientType = 'IOS';
|
||||||
this.deviceModel = (await DeviceInfoPlugin().iosInfo).model;
|
this.deviceModel = (await DeviceInfoPlugin().iosInfo).model;
|
||||||
|
this.deviceName = (await DeviceInfoPlugin().iosInfo).name;
|
||||||
} else if (Platform.isLinux) {
|
} else if (Platform.isLinux) {
|
||||||
this.clientType = 'LINUX';
|
this.clientType = 'LINUX';
|
||||||
this.deviceModel = (await DeviceInfoPlugin().linuxInfo).prettyName;
|
this.deviceModel = (await DeviceInfoPlugin().linuxInfo).prettyName;
|
||||||
|
this.deviceName = (await DeviceInfoPlugin().linuxInfo).name;
|
||||||
} else if (Platform.isWindows) {
|
} else if (Platform.isWindows) {
|
||||||
this.clientType = 'WINDOWS';
|
this.clientType = 'WINDOWS';
|
||||||
this.deviceModel = (await DeviceInfoPlugin().windowsInfo).productName;
|
this.deviceModel = (await DeviceInfoPlugin().windowsInfo).productName;
|
||||||
|
this.deviceName = (await DeviceInfoPlugin().windowsInfo).computerName;
|
||||||
} else if (Platform.isMacOS) {
|
} else if (Platform.isMacOS) {
|
||||||
this.clientType = 'MACOS';
|
this.clientType = 'MACOS';
|
||||||
this.deviceModel = (await DeviceInfoPlugin().macOsInfo).model;
|
this.deviceModel = (await DeviceInfoPlugin().macOsInfo).model;
|
||||||
|
this.deviceName = (await DeviceInfoPlugin().macOsInfo).computerName;
|
||||||
} else {
|
} else {
|
||||||
this.clientType = '?';
|
this.clientType = '?';
|
||||||
}
|
}
|
||||||
|
@ -49,20 +49,36 @@ class UI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Widget buttonIconOnly({
|
static Widget buttonIconOnly({required void Function() onPressed, required IconData icon, double? iconSize = null, bool? square, Color? color = null, Color? iconColor = null}) {
|
||||||
required void Function() onPressed,
|
final style = ButtonStyle(
|
||||||
required IconData icon,
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
double? iconSize = null,
|
backgroundColor: (color != null) ? WidgetStateProperty.resolveWith<Color?>((states) => color) : null,
|
||||||
}) {
|
padding: (square ?? false) ? WidgetStateProperty.resolveWith<EdgeInsetsGeometry?>((states) => EdgeInsets.all(10)) : null,
|
||||||
|
shape: (square ?? false) ? WidgetStateProperty.resolveWith<OutlinedBorder?>((states) => RoundedRectangleBorder(borderRadius: BorderRadius.circular(DefaultBorderRadius))) : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (color != null) {
|
||||||
|
return IconButton.filled(
|
||||||
|
icon: FaIcon(icon),
|
||||||
|
iconSize: iconSize ?? 18,
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
style: style,
|
||||||
|
onPressed: onPressed,
|
||||||
|
color: iconColor,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: FaIcon(icon),
|
icon: FaIcon(icon),
|
||||||
iconSize: iconSize ?? 18,
|
iconSize: iconSize ?? 18,
|
||||||
padding: EdgeInsets.all(4),
|
padding: EdgeInsets.all(4),
|
||||||
constraints: BoxConstraints(),
|
constraints: BoxConstraints(),
|
||||||
style: ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
style: style,
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
|
color: iconColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Widget buttonCard({required BuildContext context, required Widget child, required void Function() onTap, EdgeInsets? margin = null}) {
|
static Widget buttonCard({required BuildContext context, required Widget child, required void Function() onTap, EdgeInsets? margin = null}) {
|
||||||
return Card.filled(
|
return Card.filled(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user