618 lines
20 KiB
Dart
618 lines
20 KiB
Dart
import 'dart:developer';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
|
import 'package:simplecloudnotifier/components/error_display/error_display.dart';
|
|
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
|
import 'package:simplecloudnotifier/models/channel.dart';
|
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
|
import 'package:simplecloudnotifier/models/user.dart';
|
|
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
|
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_channel_modal.dart';
|
|
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_permission_modal.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/state/scn_data_cache.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';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class KeyTokenViewPage extends StatefulWidget {
|
|
const KeyTokenViewPage({
|
|
required this.keytokenID,
|
|
required this.preloadedData,
|
|
required this.needsReload,
|
|
super.key,
|
|
});
|
|
|
|
final String keytokenID;
|
|
final KeyToken? preloadedData;
|
|
|
|
final void Function()? needsReload;
|
|
|
|
@override
|
|
State<KeyTokenViewPage> createState() => _KeyTokenViewPageState();
|
|
}
|
|
|
|
enum EditState { none, editing, saving }
|
|
|
|
enum KeyTokenViewPageInitState { loading, okay, error }
|
|
|
|
class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
|
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
|
|
ImmediateFuture<UserPreview> _futureOwner = ImmediateFuture.ofPending();
|
|
|
|
ImmediateFuture<Map<String, ChannelPreview>> _futureAllChannels = ImmediateFuture.ofPending();
|
|
|
|
ImmediateFuture<List<Channel>> _futureOwnedChannels = ImmediateFuture.ofPending();
|
|
|
|
final TextEditingController _ctrlName = TextEditingController();
|
|
|
|
int _loadingIndeterminateCounter = 0;
|
|
|
|
EditState _editName = EditState.none;
|
|
String? _nameOverride = null;
|
|
|
|
KeyTokenPreview? keytokenPreview;
|
|
KeyToken? keytoken;
|
|
|
|
KeyTokenViewPageInitState loadingState = KeyTokenViewPageInitState.loading;
|
|
String errorMessage = '';
|
|
|
|
KeyToken? keytokenUserAccAdmin;
|
|
KeyToken? keytokenUserAccSend;
|
|
|
|
@override
|
|
void initState() {
|
|
_initStateAsync(true);
|
|
|
|
super.initState();
|
|
}
|
|
|
|
Future<void> _initStateAsync(bool usePreload) async {
|
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
if (widget.preloadedData != null && usePreload) {
|
|
keytoken = widget.preloadedData!;
|
|
keytokenPreview = widget.preloadedData!.toPreview();
|
|
} else {
|
|
try {
|
|
var p = await APIClient.getKeyTokenPreviewByID(userAcc, widget.keytokenID);
|
|
setState(() {
|
|
keytokenPreview = p;
|
|
});
|
|
|
|
if (p.ownerUserID == userAcc.userID) {
|
|
var r = await APIClient.getKeyToken(userAcc, widget.keytokenID);
|
|
setState(() {
|
|
keytoken = r;
|
|
});
|
|
} else {
|
|
setState(() {
|
|
keytoken = null;
|
|
});
|
|
}
|
|
} catch (exc, trace) {
|
|
ApplicationLog.error('Failed to load data: ' + exc.toString(), trace: trace);
|
|
Toaster.error("Error", 'Failed to load data');
|
|
this.errorMessage = 'Failed to load data: ' + exc.toString();
|
|
this.loadingState = KeyTokenViewPageInitState.error;
|
|
return;
|
|
}
|
|
}
|
|
|
|
setState(() {
|
|
this.loadingState = KeyTokenViewPageInitState.okay;
|
|
|
|
assert(keytokenPreview != null);
|
|
|
|
if (this.keytokenPreview!.ownerUserID == userAcc.userID) {
|
|
var cacheUser = userAcc.getUserOrNull();
|
|
if (cacheUser != null) {
|
|
_futureOwner = ImmediateFuture.ofValue(cacheUser.toPreview());
|
|
} else {
|
|
_futureOwner = ImmediateFuture.ofFuture(_getOwner(userAcc));
|
|
}
|
|
} else {
|
|
_futureOwner = ImmediateFuture.ofFuture(APIClient.getUserPreview(userAcc, this.keytokenPreview!.ownerUserID));
|
|
}
|
|
});
|
|
|
|
setState(() {
|
|
_futureAllChannels = ImmediateFuture.ofFuture(APIClient.getChannelList(userAcc, ChannelSelector.allAny).then((lst) async {
|
|
Map<String, ChannelPreview> result = {};
|
|
|
|
for (var c in lst) result[c.channel.channelID] = c.channel.toPreview(c.subscription);
|
|
|
|
if (keytokenPreview != null) {
|
|
for (var cid in keytokenPreview!.channels) {
|
|
if (!result.containsKey(cid)) {
|
|
result[cid] = await APIClient.getChannelPreview(userAcc, cid);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}));
|
|
});
|
|
|
|
setState(() {
|
|
_futureOwnedChannels = ImmediateFuture.ofFuture(APIClient.getChannelList(userAcc, ChannelSelector.owned).then((p) => p.map((c) => c.channel).toList()));
|
|
});
|
|
|
|
SCNDataCache().getOrQueryTokenByValue(userAcc.userID!, userAcc.tokenAdmin!).then((token) {
|
|
setState(() {
|
|
keytokenUserAccAdmin = token;
|
|
});
|
|
});
|
|
|
|
SCNDataCache().getOrQueryTokenByValue(userAcc.userID!, userAcc.tokenSend!).then((token) {
|
|
setState(() {
|
|
keytokenUserAccSend = token;
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_ctrlName.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
var title = "Key";
|
|
|
|
Widget child;
|
|
|
|
if (loadingState == KeyTokenViewPageInitState.loading) {
|
|
child = Center(child: CircularProgressIndicator());
|
|
} else if (loadingState == KeyTokenViewPageInitState.error) {
|
|
child = ErrorDisplay(errorMessage: errorMessage);
|
|
} else if (loadingState == KeyTokenViewPageInitState.okay && keytokenPreview!.ownerUserID == userAcc.userID) {
|
|
child = _buildOwnedKeyTokenView(context, this.keytoken!);
|
|
title = this.keytoken!.name;
|
|
} else {
|
|
child = _buildForeignKeyTokenView(context, this.keytokenPreview!);
|
|
title = keytokenPreview!.name;
|
|
}
|
|
|
|
return SCNScaffold(
|
|
title: title,
|
|
showSearch: false,
|
|
showShare: false,
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
Widget _buildOwnedKeyTokenView(BuildContext context, KeyToken keytoken) {
|
|
return SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
SizedBox(height: 8),
|
|
UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidIdCardClip,
|
|
title: 'KeyTokenID',
|
|
values: [
|
|
keytoken.keytokenID,
|
|
if (keytokenUserAccAdmin?.keytokenID == keytoken.keytokenID) '(Currently used as Admin-Token)',
|
|
if (keytokenUserAccSend?.keytokenID == keytoken.keytokenID) '(Currently used as Send-Token)',
|
|
],
|
|
),
|
|
_buildNameCard(context, true),
|
|
UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidClock,
|
|
title: 'Created',
|
|
values: [_KeyTokenViewPageState._dateFormat.format(DateTime.parse(keytoken.timestampCreated).toLocal())],
|
|
),
|
|
UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidClockTwo,
|
|
title: 'Last Used',
|
|
values: [(keytoken.timestampLastUsed == null) ? 'Never' : _KeyTokenViewPageState._dateFormat.format(DateTime.parse(keytoken.timestampLastUsed!).toLocal())],
|
|
),
|
|
_buildOwnerCard(context, true),
|
|
UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidEnvelope,
|
|
title: 'Messages',
|
|
values: [keytoken.messagesSent.toString()],
|
|
mainAction: () {
|
|
Navi.push(context, () => FilteredMessageViewPage(title: keytoken.name, filter: MessageFilter(usedKeys: [keytoken.keytokenID])));
|
|
},
|
|
),
|
|
..._buildPermissionCard(context, true, keytoken.toPreview()),
|
|
UI.button(text: "Delete Key", onPressed: _deleteKey, color: Colors.red[900]),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildForeignKeyTokenView(BuildContext context, KeyTokenPreview keytoken) {
|
|
return SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
SizedBox(height: 8),
|
|
UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidIdCardClip,
|
|
title: 'KeyTokenID',
|
|
values: [keytoken.keytokenID],
|
|
),
|
|
_buildNameCard(context, false),
|
|
_buildOwnerCard(context, false),
|
|
..._buildPermissionCard(context, false, keytoken),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildOwnerCard(BuildContext context, bool isOwned) {
|
|
return FutureBuilder(
|
|
future: _futureOwner.future,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.hasData) {
|
|
return UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidUser,
|
|
title: 'Owner',
|
|
values: [keytokenPreview!.ownerUserID + (isOwned ? ' (you)' : ''), if (snapshot.data?.username != null) snapshot.data!.username!],
|
|
);
|
|
} else {
|
|
return UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidUser,
|
|
title: 'Owner',
|
|
values: [keytokenPreview!.ownerUserID + (isOwned ? ' (you)' : '')],
|
|
);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildNameCard(BuildContext context, bool isOwned) {
|
|
if (_editName == EditState.editing) {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
|
child: UI.box(
|
|
context: context,
|
|
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
|
child: Row(
|
|
children: [
|
|
Container(child: Center(child: FaIcon(FontAwesomeIcons.solidInputText, size: 18)), height: 43),
|
|
SizedBox(width: 16),
|
|
Expanded(
|
|
child: TextField(
|
|
autofocus: true,
|
|
controller: _ctrlName,
|
|
decoration: new InputDecoration.collapsed(hintText: 'Name'),
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
SizedBox(width: 4),
|
|
IconButton(icon: FaIcon(FontAwesomeIcons.solidFloppyDisk), onPressed: _saveName),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
} else if (_editName == EditState.none) {
|
|
return UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidInputText,
|
|
title: 'Name',
|
|
values: [_nameOverride ?? keytokenPreview!.name],
|
|
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, null, _showEditName)] : [],
|
|
);
|
|
} else if (_editName == EditState.saving) {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
|
child: UI.box(
|
|
context: context,
|
|
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
|
child: Row(
|
|
children: [
|
|
Container(child: Center(child: FaIcon(FontAwesomeIcons.solidInputText, size: 18)), height: 43),
|
|
SizedBox(width: 16),
|
|
Expanded(child: SizedBox()),
|
|
SizedBox(width: 12),
|
|
SizedBox(width: 4),
|
|
Padding(padding: const EdgeInsets.all(8.0), child: SizedBox(width: 18, height: 18, child: CircularProgressIndicator())),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
throw 'Invalid EditDisplayNameState: $_editName';
|
|
}
|
|
}
|
|
|
|
void _showEditName() {
|
|
setState(() {
|
|
_ctrlName.text = _nameOverride ?? keytokenPreview?.name ?? '';
|
|
_editName = EditState.editing;
|
|
});
|
|
}
|
|
|
|
void _saveName() async {
|
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
final newName = _ctrlName.text;
|
|
|
|
try {
|
|
setState(() {
|
|
_editName = EditState.saving;
|
|
});
|
|
|
|
final newKeyToken = await APIClient.updateKeyToken(userAcc, widget.keytokenID, name: newName);
|
|
|
|
setState(() {
|
|
_editName = EditState.none;
|
|
_nameOverride = newKeyToken.name;
|
|
});
|
|
|
|
widget.needsReload?.call();
|
|
} catch (exc, trace) {
|
|
ApplicationLog.error('Failed to save DisplayName: ' + exc.toString(), trace: trace);
|
|
Toaster.error("Error", 'Failed to save DisplayName');
|
|
}
|
|
}
|
|
|
|
Future<UserPreview> _getOwner(AppAuth auth) async {
|
|
try {
|
|
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
|
|
|
|
_incLoadingIndeterminateCounter(1);
|
|
|
|
final owner = APIClient.getUserPreview(auth, keytokenPreview!.ownerUserID);
|
|
|
|
//await Future.delayed(const Duration(seconds: 10), () {});
|
|
|
|
return owner;
|
|
} finally {
|
|
_incLoadingIndeterminateCounter(-1);
|
|
}
|
|
}
|
|
|
|
void _incLoadingIndeterminateCounter(int delta) {
|
|
setState(() {
|
|
_loadingIndeterminateCounter += delta;
|
|
AppBarState().setLoadingIndeterminate(_loadingIndeterminateCounter > 0);
|
|
});
|
|
}
|
|
|
|
List<Widget> _buildPermissionCard(BuildContext context, bool isOwned, KeyTokenPreview keyToken) {
|
|
Widget w1;
|
|
Widget w2;
|
|
|
|
if (isOwned) {
|
|
w1 = UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidShieldKeyhole,
|
|
title: 'Permissions',
|
|
values: _formatPermissions(keyToken.permissions),
|
|
iconActions: [(FontAwesomeIcons.penToSquare, null, _editPermissions)],
|
|
);
|
|
} else {
|
|
w1 = UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidShieldKeyhole,
|
|
title: 'Permissions',
|
|
values: _formatPermissions(keyToken.permissions),
|
|
);
|
|
}
|
|
|
|
w2 = FutureBuilder(
|
|
future: _futureAllChannels.future,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.hasData) {
|
|
var cmap = snapshot.data!;
|
|
if (isOwned) {
|
|
return UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidSnake,
|
|
title: 'Channels',
|
|
values: (keyToken.allChannels) ? (['All Channels']) : (keyToken.channels.isEmpty ? ['(None)'] : (keyToken.channels.map((c) => cmap[c]?.displayName ?? c).toList())),
|
|
iconActions: [(FontAwesomeIcons.penToSquare, null, _editChannels)],
|
|
);
|
|
} else {
|
|
return UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidSnake,
|
|
title: 'Channels',
|
|
values: (keyToken.allChannels) ? (['All Channels']) : (keyToken.channels.isEmpty ? ['(None)'] : (keyToken.channels.map((c) => cmap[c]?.displayName ?? c).toList())),
|
|
);
|
|
}
|
|
} else {
|
|
if (isOwned) {
|
|
return UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidSnake,
|
|
title: 'Channels',
|
|
values: (keyToken.allChannels) ? ['All Channels'] : (keyToken.channels.isEmpty ? ['(None)'] : keyToken.channels),
|
|
iconActions: [(FontAwesomeIcons.penToSquare, null, _editChannels)],
|
|
);
|
|
} else {
|
|
return UI.metaCard(
|
|
context: context,
|
|
icon: FontAwesomeIcons.solidSnake,
|
|
title: 'Channels',
|
|
values: (keyToken.allChannels) ? ['All Channels'] : (keyToken.channels.isEmpty ? ['(None)'] : keyToken.channels),
|
|
);
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
return [w1, w2];
|
|
}
|
|
|
|
List<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;
|
|
}
|
|
|
|
void _editPermissions() async {
|
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
if (keytokenUserAccAdmin == null || keytokenUserAccAdmin!.keytokenID == keytokenPreview!.keytokenID) {
|
|
Toaster.error("Error", "You cannot edit the currently used token");
|
|
return;
|
|
}
|
|
if (keytokenUserAccSend == null || keytokenUserAccSend!.keytokenID == keytokenPreview!.keytokenID) {
|
|
Toaster.error("Error", "You cannot edit the currently used token");
|
|
return;
|
|
}
|
|
|
|
await showDialog<void>(
|
|
context: context,
|
|
builder: (context) => EditKeyTokenPermissionsDialog(
|
|
keytoken: keytokenPreview!,
|
|
onUpdatePermissions: _updatePermissions,
|
|
),
|
|
);
|
|
}
|
|
|
|
void _editChannels() async {
|
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
if (keytokenUserAccAdmin == null || keytokenUserAccAdmin!.keytokenID == keytokenPreview!.keytokenID) {
|
|
Toaster.error("Error", "You cannot edit the currently used token");
|
|
return;
|
|
}
|
|
if (keytokenUserAccSend == null || keytokenUserAccSend!.keytokenID == keytokenPreview!.keytokenID) {
|
|
Toaster.error("Error", "You cannot edit the currently used token");
|
|
return;
|
|
}
|
|
|
|
var ownChannels = (await _futureOwnedChannels.future);
|
|
ownChannels.sort((a, b) => a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase()));
|
|
|
|
await showDialog<void>(
|
|
context: context,
|
|
builder: (context) => EditKeyTokenChannelsDialog(
|
|
ownedChannels: ownChannels,
|
|
keytoken: keytokenPreview!,
|
|
onUpdateChannels: _updateChannelsSelected,
|
|
onUpdateSetAllChannels: _updateChannelsAll,
|
|
),
|
|
);
|
|
}
|
|
|
|
void _deleteKey() async {
|
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
if (keytokenUserAccAdmin == null || keytokenUserAccAdmin!.keytokenID == keytokenPreview!.keytokenID) {
|
|
Toaster.error("Error", "You cannot delete the currently used token");
|
|
return;
|
|
}
|
|
if (keytokenUserAccSend == null || keytokenUserAccSend!.keytokenID == keytokenPreview!.keytokenID) {
|
|
Toaster.error("Error", "You cannot delete the currently used token");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final r = await UIDialogs.showConfirmDialog(context, 'Really (permanently) delete this Key?', okText: 'Unsubscribe', cancelText: 'Cancel');
|
|
if (!r) return;
|
|
|
|
await APIClient.deleteKeyToken(acc, keytokenPreview!.keytokenID);
|
|
widget.needsReload?.call();
|
|
|
|
Toaster.info('Logout', 'Successfully deleted the key');
|
|
|
|
Navi.pop(context);
|
|
} catch (exc, trace) {
|
|
Toaster.error("Error", 'Failed to delete key');
|
|
ApplicationLog.error('Failed to delete key: ' + exc.toString(), trace: trace);
|
|
}
|
|
}
|
|
|
|
void _updateChannelsSelected(Set<String> selectedEntries) async {
|
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
try {
|
|
final r = await APIClient.updateKeyToken(acc, widget.keytokenID, channels: selectedEntries.toList(), allChannels: false);
|
|
|
|
setState(() {
|
|
keytoken = r;
|
|
keytokenPreview = r.toPreview();
|
|
});
|
|
|
|
Toaster.info("Success", "Key updated");
|
|
|
|
widget.needsReload?.call();
|
|
} catch (exc, trace) {
|
|
ApplicationLog.error('Failed to update key: ' + exc.toString(), trace: trace);
|
|
Toaster.error("Error", 'Failed to update key');
|
|
}
|
|
}
|
|
|
|
void _updateChannelsAll() async {
|
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
try {
|
|
final r = await APIClient.updateKeyToken(acc, widget.keytokenID, channels: [], allChannels: true);
|
|
|
|
setState(() {
|
|
keytoken = r;
|
|
keytokenPreview = r.toPreview();
|
|
});
|
|
|
|
Toaster.info("Success", "Key updated");
|
|
|
|
widget.needsReload?.call();
|
|
} catch (exc, trace) {
|
|
ApplicationLog.error('Failed to update key: ' + exc.toString(), trace: trace);
|
|
Toaster.error("Error", 'Failed to update key');
|
|
}
|
|
}
|
|
|
|
void _updatePermissions(String perm) async {
|
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
|
|
|
try {
|
|
final r = await APIClient.updateKeyToken(acc, widget.keytokenID, permissions: perm);
|
|
|
|
setState(() {
|
|
keytoken = r;
|
|
keytokenPreview = r.toPreview();
|
|
});
|
|
|
|
Toaster.info("Success", "Key updated");
|
|
|
|
widget.needsReload?.call();
|
|
} catch (exc, trace) {
|
|
ApplicationLog.error('Failed to update key: ' + exc.toString(), trace: trace);
|
|
Toaster.error("Error", 'Failed to update key');
|
|
}
|
|
}
|
|
}
|