Implement proper handling for inactive/active subscriptions
This commit is contained in:
@@ -145,11 +145,11 @@ class _ChannelListExtendedPageState extends State<ChannelListExtendedPage> with
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: 'fab_channel_list_qr',
|
||||
heroTag: 'fab_channel_list_extended-plus',
|
||||
onPressed: () {
|
||||
Navi.push(context, () => ChannelScannerPage());
|
||||
},
|
||||
child: const Icon(FontAwesomeIcons.qrcode),
|
||||
child: const Icon(FontAwesomeIcons.plus),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,32 +138,68 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
||||
}
|
||||
|
||||
Widget _buildIcon(BuildContext context) {
|
||||
if (widget.subscription == null) {
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareDashed, color: Theme.of(context).colorScheme.outline, size: 32); // not-subscribed
|
||||
final acc = AppAuth();
|
||||
|
||||
if (widget.subscription == null && widget.channel.ownerUserID == acc.userID) {
|
||||
// not-subscribed (own channel)
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.3), size: 32);
|
||||
result = GestureDetector(onTap: () => _subscribe(), child: result);
|
||||
return result;
|
||||
} else if (widget.subscription!.confirmed && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (own channel)
|
||||
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
||||
return result;
|
||||
} else if (widget.subscription!.confirmed) {
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareShareNodes, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (foreign channel)
|
||||
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
||||
return result;
|
||||
} else {
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareEnvelope, color: Theme.of(context).colorScheme.tertiary, size: 32); // requested
|
||||
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
||||
} else if (widget.subscription == null) {
|
||||
// not-subscribed (foreign channel)
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareShareNodes, color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.3), size: 32);
|
||||
return result;
|
||||
} else if (widget.subscription!.confirmed && !widget.subscription!.active) {
|
||||
if (widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||
// inactive (own channel)
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.3), size: 32);
|
||||
result = GestureDetector(onTap: () => _activate(widget.subscription!), child: result);
|
||||
return result;
|
||||
} else {
|
||||
// inactive (foreign channel)
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareShareNodes, color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.3), size: 32);
|
||||
result = GestureDetector(onTap: () => _activate(widget.subscription!), child: result);
|
||||
return result;
|
||||
}
|
||||
} else if (widget.subscription!.confirmed && widget.subscription!.active) {
|
||||
if (widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||
// subscribed+active (own channel)
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32);
|
||||
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
||||
return result;
|
||||
} else {
|
||||
// subscribed+active (foreign channel)
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareShareNodes, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32);
|
||||
result = GestureDetector(onTap: () => _deactivate(widget.subscription!), child: result);
|
||||
return result;
|
||||
}
|
||||
} else if (!widget.subscription!.confirmed) {
|
||||
if (widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||
// requested (own channel)
|
||||
return SizedBox(width: 32, height: 32);
|
||||
} else {
|
||||
// requested (foreign channel)
|
||||
Widget result = Icon(FontAwesomeIcons.solidSquareEnvelope, color: Theme.of(context).colorScheme.tertiary, size: 32);
|
||||
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SizedBox(width: 32, height: 32);
|
||||
}
|
||||
|
||||
Widget _buildSubscriptionStateText(BuildContext context) {
|
||||
if (widget.subscription == null) {
|
||||
return Text("", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||
} else if (widget.subscription!.confirmed && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||
} else if (widget.subscription!.confirmed && widget.subscription!.active && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||
return Text("subscribed", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||
} else if (widget.subscription!.confirmed) {
|
||||
return Text("subscripted (foreign channe)", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||
} else if (widget.subscription!.confirmed && !widget.subscription!.active && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||
return Text("inactive (own channel)", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||
} else if (widget.subscription!.confirmed && widget.subscription!.active) {
|
||||
return Text("subscribed & active (foreign channel)", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||
} else if (widget.subscription!.confirmed && !widget.subscription!.active) {
|
||||
return Text("subscribed (foreign channel) (inactive)", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||
} else {
|
||||
return Text("subscription requested", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||
}
|
||||
@@ -194,12 +230,12 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
||||
void _unsubscribe(Subscription sub) async {
|
||||
final acc = AppAuth();
|
||||
|
||||
if (acc.isAuth() && widget.channel.ownerUserID == acc.getUserID() && widget.subscription != null) {
|
||||
if (acc.isAuth()) {
|
||||
try {
|
||||
await APIClient.deleteSubscription(acc, widget.channel.channelID, widget.subscription!.subscriptionID);
|
||||
await APIClient.deleteSubscription(acc, sub.channelID, sub.subscriptionID);
|
||||
widget.onChannelListReloadTrigger.call();
|
||||
|
||||
widget.onSubscriptionChanged.call(widget.channel.channelID, null);
|
||||
widget.onSubscriptionChanged.call(sub.channelID, null);
|
||||
|
||||
Toaster.success("Success", 'Unsubscribed from channel');
|
||||
} catch (exc, trace) {
|
||||
@@ -208,4 +244,40 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _deactivate(Subscription sub) async {
|
||||
final acc = AppAuth();
|
||||
|
||||
if (acc.isAuth()) {
|
||||
try {
|
||||
var newSub = await APIClient.deactivateSubscription(acc, sub.channelID, sub.subscriptionID);
|
||||
widget.onChannelListReloadTrigger.call();
|
||||
|
||||
widget.onSubscriptionChanged.call(sub.channelID, newSub);
|
||||
|
||||
Toaster.success("Success", 'Unsubscribed from channel');
|
||||
} catch (exc, trace) {
|
||||
Toaster.error("Error", 'Failed to unsubscribe from channel');
|
||||
ApplicationLog.error('Failed to unsubscribe from channel: ' + exc.toString(), trace: trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _activate(Subscription sub) async {
|
||||
final acc = AppAuth();
|
||||
|
||||
if (acc.isAuth()) {
|
||||
try {
|
||||
var newSub = await APIClient.activateSubscription(acc, sub.channelID, sub.subscriptionID);
|
||||
widget.onChannelListReloadTrigger.call();
|
||||
|
||||
widget.onSubscriptionChanged.call(sub.channelID, newSub);
|
||||
|
||||
Toaster.success("Success", 'Subscribed to channel');
|
||||
} catch (exc, trace) {
|
||||
Toaster.error("Error", 'Failed to subscribe to channel');
|
||||
ApplicationLog.error('Failed to subscribe to channel: ' + exc.toString(), trace: trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,11 @@ class _ChannelScannerResultChannelSubscribeState extends State<ChannelScannerRes
|
||||
if (sub == null) {
|
||||
return "Not Subscribed";
|
||||
} else if (sub.confirmed) {
|
||||
return "Already Subscribed";
|
||||
if (sub.active) {
|
||||
return "Already Subscribed";
|
||||
} else {
|
||||
return "Already Subscribed (inactive)";
|
||||
}
|
||||
} else {
|
||||
return "Unconfirmed Subscription";
|
||||
}
|
||||
|
||||
@@ -149,7 +149,11 @@ class _ChannelScannerResultChannelViewState extends State<ChannelScannerResultCh
|
||||
if (sub == null) {
|
||||
return "Not Subscribed";
|
||||
} else if (sub.confirmed) {
|
||||
return "Already Subscribed";
|
||||
if (sub.active) {
|
||||
return "Already Subscribed";
|
||||
} else {
|
||||
return "Already Subscribed (inactive)";
|
||||
}
|
||||
} else {
|
||||
return "Unconfirmed Subscription";
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import 'package:simplecloudnotifier/models/scan_result.dart';
|
||||
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||
import 'package:simplecloudnotifier/models/user.dart';
|
||||
import 'package:simplecloudnotifier/pages/channel_message_view/channel_message_view.dart';
|
||||
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
||||
import 'package:simplecloudnotifier/utils/dialogs.dart';
|
||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||
@@ -196,7 +198,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||
title: 'Subscription (own)',
|
||||
values: [_formatSubscriptionStatus(this.subscription)],
|
||||
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
||||
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, null, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, null, _subscribe)],
|
||||
),
|
||||
_buildForeignSubscriptions(context),
|
||||
_buildOwnerCard(context, true),
|
||||
@@ -217,7 +219,48 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
}
|
||||
|
||||
Widget _buildForeignChannelView(BuildContext context, ChannelPreview channel) {
|
||||
final isSubscribed = (subscription != null && subscription!.confirmed);
|
||||
Widget subCard;
|
||||
|
||||
if (subscription != null && subscription!.confirmed && subscription!.active) {
|
||||
subCard = UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||
title: 'Subscription (foreign)',
|
||||
values: [_formatSubscriptionStatus(subscription)],
|
||||
iconActions: [(FontAwesomeIcons.solidSquareXmark, null, _deactivate)],
|
||||
);
|
||||
} else if (subscription != null && subscription!.confirmed && !subscription!.active) {
|
||||
subCard = UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||
title: 'Subscription (foreign)',
|
||||
values: [_formatSubscriptionStatus(subscription)],
|
||||
iconActions: [(FontAwesomeIcons.solidSquareXmark, Colors.red[900], () => _unsubscribe(confirm: 'Really (permantenly) delete your subscription to this channel?')), (FontAwesomeIcons.solidSquareRss, null, _activate)],
|
||||
);
|
||||
} else if (subscription != null && !subscription!.confirmed) {
|
||||
subCard = UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||
title: 'Subscription (foreign)',
|
||||
values: [_formatSubscriptionStatus(subscription)],
|
||||
iconActions: [(FontAwesomeIcons.solidSquareXmark, Colors.red[900], () => _unsubscribe(confirm: 'Really withdraw your subscription-request to this channel?'))],
|
||||
);
|
||||
} else if (subscription == null) {
|
||||
subCard = UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||
title: 'Subscription (foreign)',
|
||||
values: [_formatSubscriptionStatus(subscription)],
|
||||
iconActions: [(FontAwesomeIcons.solidSquareRss, null, _subscribe)],
|
||||
);
|
||||
} else {
|
||||
subCard = UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||
title: 'Subscription (foreign)',
|
||||
values: [_formatSubscriptionStatus(subscription)],
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
@@ -240,15 +283,16 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
),
|
||||
_buildDisplayNameCard(context, false),
|
||||
_buildDescriptionNameCard(context, false),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||
title: 'Subscription (foreign)',
|
||||
values: [_formatSubscriptionStatus(subscription)],
|
||||
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
||||
),
|
||||
subCard,
|
||||
_buildForeignSubscriptions(context),
|
||||
_buildOwnerCard(context, false),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidEnvelope,
|
||||
title: 'Messages',
|
||||
values: [channel.messagesSent.toString()],
|
||||
mainAction: (subscription != null && subscription!.confirmed) ? () => Navi.push(context, () => FilteredMessageViewPage(title: channel.displayName, filter: MessageFilter(channelIDs: [channel.channelID]))) : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -269,7 +313,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
icon: FontAwesomeIcons.solidDiagramSuccessor,
|
||||
title: 'Subscription (' + (user?.username ?? user?.userID ?? 'other') + ')',
|
||||
values: [_formatSubscriptionStatus(sub)],
|
||||
iconActions: _getForeignSubActions(sub),
|
||||
iconActions: _getForeignIncomingSubActions(sub),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -371,7 +415,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
icon: FontAwesomeIcons.solidInputText,
|
||||
title: 'DisplayName',
|
||||
values: [_displayNameOverride ?? channelPreview!.displayName],
|
||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDisplayName)] : [],
|
||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, null, _showEditDisplayName)] : [],
|
||||
);
|
||||
} else if (_editDisplayName == EditState.saving) {
|
||||
return Padding(
|
||||
@@ -427,7 +471,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
icon: FontAwesomeIcons.solidInputPipe,
|
||||
title: 'Description',
|
||||
values: [_descriptionNameOverride ?? channelPreview?.descriptionName ?? ''],
|
||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDescriptionName)] : [],
|
||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, null, _showEditDescriptionName)] : [],
|
||||
);
|
||||
} else if (_editDescriptionName == EditState.saving) {
|
||||
return Padding(
|
||||
@@ -536,11 +580,16 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
}
|
||||
}
|
||||
|
||||
void _unsubscribe() async {
|
||||
void _unsubscribe({String? confirm = null}) async {
|
||||
final acc = AppAuth();
|
||||
|
||||
if (subscription == null) return;
|
||||
|
||||
if (confirm != null) {
|
||||
final r = await UIDialogs.showConfirmDialog(context, confirm, okText: 'Unsubscribe', cancelText: 'Cancel');
|
||||
if (!r) return;
|
||||
}
|
||||
|
||||
try {
|
||||
await APIClient.deleteSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
||||
widget.needsReload?.call();
|
||||
@@ -554,6 +603,42 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
}
|
||||
}
|
||||
|
||||
void _deactivate() async {
|
||||
final acc = AppAuth();
|
||||
|
||||
if (subscription == null) return;
|
||||
|
||||
try {
|
||||
await APIClient.deactivateSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
||||
widget.needsReload?.call();
|
||||
|
||||
await _initStateAsync(false);
|
||||
|
||||
Toaster.success("Success", 'Unsubscribed from channel');
|
||||
} catch (exc, trace) {
|
||||
Toaster.error("Error", 'Failed to unsubscribe from channel');
|
||||
ApplicationLog.error('Failed to unsubscribe from channel: ' + exc.toString(), trace: trace);
|
||||
}
|
||||
}
|
||||
|
||||
void _activate() async {
|
||||
final acc = AppAuth();
|
||||
|
||||
if (subscription == null) return;
|
||||
|
||||
try {
|
||||
await APIClient.activateSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
||||
widget.needsReload?.call();
|
||||
|
||||
await _initStateAsync(false);
|
||||
|
||||
Toaster.success("Success", 'Subscribed to channel');
|
||||
} catch (exc, trace) {
|
||||
Toaster.error("Error", 'Failed to subscribe to channel');
|
||||
ApplicationLog.error('Failed to subscribe to channel: ' + exc.toString(), trace: trace);
|
||||
}
|
||||
}
|
||||
|
||||
void _cancelForeignSubscription(Subscription sub) async {
|
||||
final acc = AppAuth();
|
||||
|
||||
@@ -605,10 +690,14 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
String _formatSubscriptionStatus(Subscription? subscription) {
|
||||
if (subscription == null) {
|
||||
return 'Not Subscribed';
|
||||
} else if (subscription.confirmed) {
|
||||
return 'Subscribed';
|
||||
} else {
|
||||
} else if (subscription.confirmed && subscription.active) {
|
||||
return 'Subscribed & Active';
|
||||
} else if (subscription.confirmed && !subscription.active) {
|
||||
return 'Subscribed & Inactive';
|
||||
} else if (!subscription.confirmed) {
|
||||
return 'Requested';
|
||||
} else {
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,13 +751,13 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
}
|
||||
}
|
||||
|
||||
List<(IconData, void Function())> _getForeignSubActions(Subscription sub) {
|
||||
List<(IconData, Color?, void Function())> _getForeignIncomingSubActions(Subscription sub) {
|
||||
if (sub.confirmed) {
|
||||
return [(FontAwesomeIcons.solidSquareXmark, () => _cancelForeignSubscription(sub))];
|
||||
return [(FontAwesomeIcons.solidSquareXmark, Colors.red[900], () => _cancelForeignSubscription(sub))];
|
||||
} else {
|
||||
return [
|
||||
(FontAwesomeIcons.solidSquareCheck, () => _confirmForeignSubscription(sub)),
|
||||
(FontAwesomeIcons.solidSquareXmark, () => _denyForeignSubscription(sub)),
|
||||
(FontAwesomeIcons.solidSquareCheck, Colors.green[900], () => _confirmForeignSubscription(sub)),
|
||||
(FontAwesomeIcons.solidSquareXmark, Colors.red[900], () => _denyForeignSubscription(sub)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
||||
icon: FontAwesomeIcons.solidInputText,
|
||||
title: 'Name',
|
||||
values: [_nameOverride ?? keytokenPreview!.name],
|
||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditName)] : [],
|
||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, null, _showEditName)] : [],
|
||||
);
|
||||
} else if (_editName == EditState.saving) {
|
||||
return Padding(
|
||||
@@ -357,7 +357,7 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
||||
icon: FontAwesomeIcons.shieldKeyhole,
|
||||
title: 'Permissions',
|
||||
values: _formatPermissions(keyToken.permissions),
|
||||
iconActions: [(FontAwesomeIcons.penToSquare, _editPermissions)],
|
||||
iconActions: [(FontAwesomeIcons.penToSquare, null, _editPermissions)],
|
||||
);
|
||||
} else {
|
||||
w1 = UI.metaCard(
|
||||
@@ -374,7 +374,7 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
||||
icon: FontAwesomeIcons.solidSnake,
|
||||
title: 'Channels',
|
||||
values: (keyToken.allChannels) ? ['All Channels'] : keyToken.channels, //TODO show channel names
|
||||
iconActions: [(FontAwesomeIcons.penToSquare, _editChannels)],
|
||||
iconActions: [(FontAwesomeIcons.penToSquare, null, _editChannels)],
|
||||
);
|
||||
} else {
|
||||
w2 = UI.metaCard(
|
||||
|
||||
Reference in New Issue
Block a user