Implement proper handling for inactive/active subscriptions
This commit is contained in:
parent
a43a3b441f
commit
63bc71c405
@ -524,6 +524,32 @@ class APIClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<Subscription> activateSubscription(TokenSource auth, String channelID, String subID) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'activateSubscription',
|
||||||
|
method: 'PATCH',
|
||||||
|
relURL: 'users/${auth.getUserID()}/subscriptions/${subID}',
|
||||||
|
jsonBody: {
|
||||||
|
'active': true,
|
||||||
|
},
|
||||||
|
fn: Subscription.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Subscription> deactivateSubscription(TokenSource auth, String channelID, String subID) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'deactivateSubscription',
|
||||||
|
method: 'PATCH',
|
||||||
|
relURL: 'users/${auth.getUserID()}/subscriptions/${subID}',
|
||||||
|
jsonBody: {
|
||||||
|
'active': false,
|
||||||
|
},
|
||||||
|
fn: Subscription.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<SendMessageResponse> sendMessage(String userid, String keytoken, String text, {String? channel, String? content, String? messageID, int? priority, String? senderName, DateTime? timestamp}) async {
|
static Future<SendMessageResponse> sendMessage(String userid, String keytoken, String text, {String? channel, String? content, String? messageID, int? priority, String? senderName, DateTime? timestamp}) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'sendMessage',
|
name: 'sendMessage',
|
||||||
|
@ -6,6 +6,7 @@ class Subscription {
|
|||||||
final String channelInternalName;
|
final String channelInternalName;
|
||||||
final String timestampCreated;
|
final String timestampCreated;
|
||||||
final bool confirmed;
|
final bool confirmed;
|
||||||
|
final bool active;
|
||||||
|
|
||||||
const Subscription({
|
const Subscription({
|
||||||
required this.subscriptionID,
|
required this.subscriptionID,
|
||||||
@ -15,6 +16,7 @@ class Subscription {
|
|||||||
required this.channelInternalName,
|
required this.channelInternalName,
|
||||||
required this.timestampCreated,
|
required this.timestampCreated,
|
||||||
required this.confirmed,
|
required this.confirmed,
|
||||||
|
required this.active,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Subscription.fromJson(Map<String, dynamic> json) {
|
factory Subscription.fromJson(Map<String, dynamic> json) {
|
||||||
@ -26,6 +28,7 @@ class Subscription {
|
|||||||
channelInternalName: json['channel_internal_name'] as String,
|
channelInternalName: json['channel_internal_name'] as String,
|
||||||
timestampCreated: json['timestamp_created'] as String,
|
timestampCreated: json['timestamp_created'] as String,
|
||||||
confirmed: json['confirmed'] as bool,
|
confirmed: json['confirmed'] as bool,
|
||||||
|
active: json['active'] as bool,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,11 +145,11 @@ class _ChannelListExtendedPageState extends State<ChannelListExtendedPage> with
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
heroTag: 'fab_channel_list_qr',
|
heroTag: 'fab_channel_list_extended-plus',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navi.push(context, () => ChannelScannerPage());
|
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) {
|
Widget _buildIcon(BuildContext context) {
|
||||||
if (widget.subscription == null) {
|
final acc = AppAuth();
|
||||||
Widget result = Icon(FontAwesomeIcons.solidSquareDashed, color: Theme.of(context).colorScheme.outline, size: 32); // not-subscribed
|
|
||||||
|
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);
|
result = GestureDetector(onTap: () => _subscribe(), child: result);
|
||||||
return result;
|
return result;
|
||||||
} else if (widget.subscription!.confirmed && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
} else if (widget.subscription == null) {
|
||||||
Widget result = Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (own channel)
|
// not-subscribed (foreign channel)
|
||||||
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
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 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);
|
|
||||||
return result;
|
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) {
|
Widget _buildSubscriptionStateText(BuildContext context) {
|
||||||
if (widget.subscription == null) {
|
if (widget.subscription == null) {
|
||||||
return Text("", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
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)));
|
return Text("subscribed", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||||
} else if (widget.subscription!.confirmed) {
|
} else if (widget.subscription!.confirmed && !widget.subscription!.active && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||||
return Text("subscripted (foreign channe)", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
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 {
|
} else {
|
||||||
return Text("subscription requested", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
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 {
|
void _unsubscribe(Subscription sub) async {
|
||||||
final acc = AppAuth();
|
final acc = AppAuth();
|
||||||
|
|
||||||
if (acc.isAuth() && widget.channel.ownerUserID == acc.getUserID() && widget.subscription != null) {
|
if (acc.isAuth()) {
|
||||||
try {
|
try {
|
||||||
await APIClient.deleteSubscription(acc, widget.channel.channelID, widget.subscription!.subscriptionID);
|
await APIClient.deleteSubscription(acc, sub.channelID, sub.subscriptionID);
|
||||||
widget.onChannelListReloadTrigger.call();
|
widget.onChannelListReloadTrigger.call();
|
||||||
|
|
||||||
widget.onSubscriptionChanged.call(widget.channel.channelID, null);
|
widget.onSubscriptionChanged.call(sub.channelID, null);
|
||||||
|
|
||||||
Toaster.success("Success", 'Unsubscribed from channel');
|
Toaster.success("Success", 'Unsubscribed from channel');
|
||||||
} catch (exc, trace) {
|
} 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) {
|
if (sub == null) {
|
||||||
return "Not Subscribed";
|
return "Not Subscribed";
|
||||||
} else if (sub.confirmed) {
|
} else if (sub.confirmed) {
|
||||||
return "Already Subscribed";
|
if (sub.active) {
|
||||||
|
return "Already Subscribed";
|
||||||
|
} else {
|
||||||
|
return "Already Subscribed (inactive)";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return "Unconfirmed Subscription";
|
return "Unconfirmed Subscription";
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,11 @@ class _ChannelScannerResultChannelViewState extends State<ChannelScannerResultCh
|
|||||||
if (sub == null) {
|
if (sub == null) {
|
||||||
return "Not Subscribed";
|
return "Not Subscribed";
|
||||||
} else if (sub.confirmed) {
|
} else if (sub.confirmed) {
|
||||||
return "Already Subscribed";
|
if (sub.active) {
|
||||||
|
return "Already Subscribed";
|
||||||
|
} else {
|
||||||
|
return "Already Subscribed (inactive)";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return "Unconfirmed Subscription";
|
return "Unconfirmed Subscription";
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,12 @@ import 'package:simplecloudnotifier/models/scan_result.dart';
|
|||||||
import 'package:simplecloudnotifier/models/subscription.dart';
|
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||||
import 'package:simplecloudnotifier/models/user.dart';
|
import 'package:simplecloudnotifier/models/user.dart';
|
||||||
import 'package:simplecloudnotifier/pages/channel_message_view/channel_message_view.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_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/dialogs.dart';
|
||||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
@ -196,7 +198,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||||
title: 'Subscription (own)',
|
title: 'Subscription (own)',
|
||||||
values: [_formatSubscriptionStatus(this.subscription)],
|
values: [_formatSubscriptionStatus(this.subscription)],
|
||||||
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, null, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, null, _subscribe)],
|
||||||
),
|
),
|
||||||
_buildForeignSubscriptions(context),
|
_buildForeignSubscriptions(context),
|
||||||
_buildOwnerCard(context, true),
|
_buildOwnerCard(context, true),
|
||||||
@ -217,7 +219,48 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildForeignChannelView(BuildContext context, ChannelPreview channel) {
|
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(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -240,15 +283,16 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
),
|
),
|
||||||
_buildDisplayNameCard(context, false),
|
_buildDisplayNameCard(context, false),
|
||||||
_buildDescriptionNameCard(context, false),
|
_buildDescriptionNameCard(context, false),
|
||||||
UI.metaCard(
|
subCard,
|
||||||
context: context,
|
|
||||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
|
||||||
title: 'Subscription (foreign)',
|
|
||||||
values: [_formatSubscriptionStatus(subscription)],
|
|
||||||
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
|
||||||
),
|
|
||||||
_buildForeignSubscriptions(context),
|
_buildForeignSubscriptions(context),
|
||||||
_buildOwnerCard(context, false),
|
_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,
|
icon: FontAwesomeIcons.solidDiagramSuccessor,
|
||||||
title: 'Subscription (' + (user?.username ?? user?.userID ?? 'other') + ')',
|
title: 'Subscription (' + (user?.username ?? user?.userID ?? 'other') + ')',
|
||||||
values: [_formatSubscriptionStatus(sub)],
|
values: [_formatSubscriptionStatus(sub)],
|
||||||
iconActions: _getForeignSubActions(sub),
|
iconActions: _getForeignIncomingSubActions(sub),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -371,7 +415,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
icon: FontAwesomeIcons.solidInputText,
|
icon: FontAwesomeIcons.solidInputText,
|
||||||
title: 'DisplayName',
|
title: 'DisplayName',
|
||||||
values: [_displayNameOverride ?? channelPreview!.displayName],
|
values: [_displayNameOverride ?? channelPreview!.displayName],
|
||||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDisplayName)] : [],
|
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, null, _showEditDisplayName)] : [],
|
||||||
);
|
);
|
||||||
} else if (_editDisplayName == EditState.saving) {
|
} else if (_editDisplayName == EditState.saving) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -427,7 +471,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
icon: FontAwesomeIcons.solidInputPipe,
|
icon: FontAwesomeIcons.solidInputPipe,
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
values: [_descriptionNameOverride ?? channelPreview?.descriptionName ?? ''],
|
values: [_descriptionNameOverride ?? channelPreview?.descriptionName ?? ''],
|
||||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDescriptionName)] : [],
|
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, null, _showEditDescriptionName)] : [],
|
||||||
);
|
);
|
||||||
} else if (_editDescriptionName == EditState.saving) {
|
} else if (_editDescriptionName == EditState.saving) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -536,11 +580,16 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unsubscribe() async {
|
void _unsubscribe({String? confirm = null}) async {
|
||||||
final acc = AppAuth();
|
final acc = AppAuth();
|
||||||
|
|
||||||
if (subscription == null) return;
|
if (subscription == null) return;
|
||||||
|
|
||||||
|
if (confirm != null) {
|
||||||
|
final r = await UIDialogs.showConfirmDialog(context, confirm, okText: 'Unsubscribe', cancelText: 'Cancel');
|
||||||
|
if (!r) return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await APIClient.deleteSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
await APIClient.deleteSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
||||||
widget.needsReload?.call();
|
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 {
|
void _cancelForeignSubscription(Subscription sub) async {
|
||||||
final acc = AppAuth();
|
final acc = AppAuth();
|
||||||
|
|
||||||
@ -605,10 +690,14 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
String _formatSubscriptionStatus(Subscription? subscription) {
|
String _formatSubscriptionStatus(Subscription? subscription) {
|
||||||
if (subscription == null) {
|
if (subscription == null) {
|
||||||
return 'Not Subscribed';
|
return 'Not Subscribed';
|
||||||
} else if (subscription.confirmed) {
|
} else if (subscription.confirmed && subscription.active) {
|
||||||
return 'Subscribed';
|
return 'Subscribed & Active';
|
||||||
} else {
|
} else if (subscription.confirmed && !subscription.active) {
|
||||||
|
return 'Subscribed & Inactive';
|
||||||
|
} else if (!subscription.confirmed) {
|
||||||
return 'Requested';
|
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) {
|
if (sub.confirmed) {
|
||||||
return [(FontAwesomeIcons.solidSquareXmark, () => _cancelForeignSubscription(sub))];
|
return [(FontAwesomeIcons.solidSquareXmark, Colors.red[900], () => _cancelForeignSubscription(sub))];
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
(FontAwesomeIcons.solidSquareCheck, () => _confirmForeignSubscription(sub)),
|
(FontAwesomeIcons.solidSquareCheck, Colors.green[900], () => _confirmForeignSubscription(sub)),
|
||||||
(FontAwesomeIcons.solidSquareXmark, () => _denyForeignSubscription(sub)),
|
(FontAwesomeIcons.solidSquareXmark, Colors.red[900], () => _denyForeignSubscription(sub)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
|||||||
icon: FontAwesomeIcons.solidInputText,
|
icon: FontAwesomeIcons.solidInputText,
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
values: [_nameOverride ?? keytokenPreview!.name],
|
values: [_nameOverride ?? keytokenPreview!.name],
|
||||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditName)] : [],
|
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, null, _showEditName)] : [],
|
||||||
);
|
);
|
||||||
} else if (_editName == EditState.saving) {
|
} else if (_editName == EditState.saving) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -357,7 +357,7 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
|||||||
icon: FontAwesomeIcons.shieldKeyhole,
|
icon: FontAwesomeIcons.shieldKeyhole,
|
||||||
title: 'Permissions',
|
title: 'Permissions',
|
||||||
values: _formatPermissions(keyToken.permissions),
|
values: _formatPermissions(keyToken.permissions),
|
||||||
iconActions: [(FontAwesomeIcons.penToSquare, _editPermissions)],
|
iconActions: [(FontAwesomeIcons.penToSquare, null, _editPermissions)],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
w1 = UI.metaCard(
|
w1 = UI.metaCard(
|
||||||
@ -374,7 +374,7 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
|||||||
icon: FontAwesomeIcons.solidSnake,
|
icon: FontAwesomeIcons.solidSnake,
|
||||||
title: 'Channels',
|
title: 'Channels',
|
||||||
values: (keyToken.allChannels) ? ['All Channels'] : keyToken.channels, //TODO show channel names
|
values: (keyToken.allChannels) ? ['All Channels'] : keyToken.channels, //TODO show channel names
|
||||||
iconActions: [(FontAwesomeIcons.penToSquare, _editChannels)],
|
iconActions: [(FontAwesomeIcons.penToSquare, null, _editChannels)],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
w2 = UI.metaCard(
|
w2 = UI.metaCard(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class UIDialogs {
|
class UIDialogs {
|
||||||
|
|
||||||
static Future<String?> showTextInput(BuildContext context, String title, String hintText) {
|
static Future<String?> showTextInput(BuildContext context, String title, String hintText) {
|
||||||
var _textFieldController = TextEditingController();
|
var _textFieldController = TextEditingController();
|
||||||
|
|
||||||
@ -26,4 +27,25 @@ class UIDialogs {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<bool> showConfirmDialog(BuildContext context, String title, {String? text, String? okText, String? cancelText}) {
|
||||||
|
return showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: (text != null) ? Text(text) : null,
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: Text(cancelText ?? 'Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: Text(okText ?? 'OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).then((value) => value ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ class UI {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Widget metaCard({required BuildContext context, required IconData icon, required String title, required List<String> values, void Function()? mainAction, List<(IconData, void Function())>? iconActions}) {
|
static Widget metaCard({required BuildContext context, required IconData icon, required String title, required List<String> values, void Function()? mainAction, List<(IconData, Color?, void Function())>? iconActions}) {
|
||||||
final container = UI.box(
|
final container = UI.box(
|
||||||
context: context,
|
context: context,
|
||||||
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||||
@ -145,7 +145,7 @@ class UI {
|
|||||||
SizedBox(width: 12),
|
SizedBox(width: 12),
|
||||||
for (final iconAction in iconActions) ...[
|
for (final iconAction in iconActions) ...[
|
||||||
SizedBox(width: 4),
|
SizedBox(width: 4),
|
||||||
IconButton(icon: FaIcon(iconAction.$1), onPressed: iconAction.$2),
|
IconButton(icon: FaIcon(iconAction.$1), color: iconAction.$2, onPressed: iconAction.$3),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user