implement keytoken list and all-messages list
This commit is contained in:
@@ -9,6 +9,8 @@ import 'package:simplecloudnotifier/models/user.dart';
|
||||
import 'package:simplecloudnotifier/pages/account/login.dart';
|
||||
import 'package:simplecloudnotifier/pages/channel_list/channel_list_extended.dart';
|
||||
import 'package:simplecloudnotifier/pages/client_list/client_list.dart';
|
||||
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
||||
import 'package:simplecloudnotifier/pages/keytoken_list/keytoken_list.dart';
|
||||
import 'package:simplecloudnotifier/pages/sender_list/sender_list.dart';
|
||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
@@ -384,8 +386,10 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
||||
_buildNumberCard(context, 'Subscriptions', futureSubscriptionCount, () {/*TODO*/}),
|
||||
_buildNumberCard(context, 'Clients', futureClientCount, () {
|
||||
Navi.push(context, () => ClientListPage());
|
||||
}),
|
||||
_buildNumberCard(context, 'Keys', futureKeyCount, () {/*TODO*/}),
|
||||
}),
|
||||
_buildNumberCard(context, 'Keys', futureKeyCount, () {
|
||||
Navi.push(context, () => KeyTokenListPage());
|
||||
}),
|
||||
_buildNumberCard(context, 'Channels', futureChannelSubscribedCount, () {
|
||||
Navi.push(context, () => ChannelListExtendedPage());
|
||||
}),
|
||||
@@ -402,7 +406,9 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
||||
Text('Messages', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||
],
|
||||
),
|
||||
onTap: () {/*TODO*/},
|
||||
onTap: () {
|
||||
Navi.push(context, () => FilteredMessageViewPage(title: "All Messages", filter: MessageFilter(senderUserID: [user.userID])));
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
}
|
||||
|
||||
Widget _buildOwnedChannelView(BuildContext context, Channel channel) {
|
||||
final userAccUserID = context.select<AppAuth, String?>((v) => v.userID);
|
||||
final isSubscribed = (subscription != null && subscription!.confirmed);
|
||||
|
||||
return SingleChildScrollView(
|
||||
@@ -208,6 +209,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
Navi.push(context, () => ChannelMessageViewPage(channel: channel));
|
||||
},
|
||||
),
|
||||
if (channel.ownerUserID == userAccUserID) UI.button(text: "Delete Channel", onPressed: () {/*TODO*/}, color: Colors.red[900]),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -64,7 +64,7 @@ class _ClientListPageState extends State<ClientListPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SCNScaffold(
|
||||
title: "Client",
|
||||
title: "Clients",
|
||||
showSearch: false,
|
||||
showShare: false,
|
||||
child: Padding(
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
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/models/client.dart';
|
||||
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
||||
import 'package:simplecloudnotifier/state/globals.dart';
|
||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||
|
||||
enum ClientListItemMode {
|
||||
Messages,
|
||||
|
||||
@@ -70,7 +70,7 @@ class _FilteredMessageViewPageState extends State<FilteredMessageViewPage> {
|
||||
});
|
||||
}
|
||||
|
||||
final (npt, newItems) = await APIClient.getMessageList(acc, thisPageToken, pageSize: cfg.messagePageSize, filter: this.widget.filter);
|
||||
final (npt, newItems) = await APIClient.getMessageList(acc, thisPageToken, pageSize: cfg.messagePageSize, filter: this.widget.filter, includeNonSuscribed: true);
|
||||
|
||||
SCNDataCache().addToMessageCache(newItems); // no await
|
||||
|
||||
|
||||
86
flutter/lib/pages/keytoken_list/keytoken_list.dart
Normal file
86
flutter/lib/pages/keytoken_list/keytoken_list.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||
import 'package:simplecloudnotifier/pages/keytoken_list/keytoken_list_item.dart';
|
||||
|
||||
class KeyTokenListPage extends StatefulWidget {
|
||||
const KeyTokenListPage({super.key});
|
||||
|
||||
@override
|
||||
State<KeyTokenListPage> createState() => _KeyTokenListPageState();
|
||||
}
|
||||
|
||||
class _KeyTokenListPageState extends State<KeyTokenListPage> {
|
||||
final PagingController<int, KeyToken> _pagingController = PagingController.fromValue(PagingState(nextPageKey: null, itemList: [], error: null), firstPageKey: 0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_pagingController.addPageRequestListener(_fetchPage);
|
||||
|
||||
_pagingController.refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ApplicationLog.debug('KeyTokenListPage::dispose');
|
||||
_pagingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _fetchPage(int pageKey) async {
|
||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||
|
||||
ApplicationLog.debug('Start KeyTokenListPage::_pagingController::_fetchPage [ ${pageKey} ]');
|
||||
|
||||
if (!acc.isAuth()) {
|
||||
_pagingController.error = 'Not logged in';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final items = (await APIClient.getKeyTokenList(acc)).toList();
|
||||
|
||||
items.sort((a, b) => -1 * a.timestampCreated.compareTo(b.timestampCreated));
|
||||
|
||||
_pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null);
|
||||
} catch (exc, trace) {
|
||||
_pagingController.error = exc.toString();
|
||||
ApplicationLog.error('Failed to list keytokens: ' + exc.toString(), trace: trace);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SCNScaffold(
|
||||
title: "Keys",
|
||||
showSearch: false,
|
||||
showShare: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => Future.sync(
|
||||
() => _pagingController.refresh(),
|
||||
),
|
||||
child: PagedListView<int, KeyToken>(
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<KeyToken>(
|
||||
itemBuilder: (context, item, index) => KeyTokenListItem(item: item),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
113
flutter/lib/pages/keytoken_list/keytoken_list_item.dart
Normal file
113
flutter/lib/pages/keytoken_list/keytoken_list_item.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
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/models/keytoken.dart';
|
||||
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
||||
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_view.dart';
|
||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||
|
||||
enum KeyTokenListItemMode {
|
||||
Messages,
|
||||
Extended,
|
||||
}
|
||||
|
||||
class KeyTokenListItem extends StatelessWidget {
|
||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
||||
|
||||
const KeyTokenListItem({
|
||||
required this.item,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final KeyToken item;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card.filled(
|
||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||
color: Theme.of(context).cardTheme.color,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navi.push(context, () => KeyTokenViewPage(keytokenID: item.keytokenID, preloadedData: item, needsReload: null));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(FontAwesomeIcons.solidGearCode, color: Theme.of(context).colorScheme.outline, size: 32),
|
||||
SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(item.timestampLastUsed == null) ? '' : KeyTokenListItem._dateFormat.format(DateTime.parse(item.timestampLastUsed!).toLocal()),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Permissions: " + _formatPermissions(item.permissions, item.allChannels, item.channels),
|
||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
||||
),
|
||||
),
|
||||
Text(item.messagesSent.toString(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navi.push(context, () => FilteredMessageViewPage(title: item.name, filter: MessageFilter(usedKeys: [item.keytokenID])));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Icon(FontAwesomeIcons.solidEnvelopes, color: Theme.of(context).colorScheme.onPrimaryContainer.withAlpha(128), size: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatPermissions(String v, bool allChannels, List<String> channels) {
|
||||
var splt = v.split(';');
|
||||
|
||||
if (splt.length == 0) return "None";
|
||||
|
||||
var a = splt.contains("A");
|
||||
var ur = splt.contains("UR");
|
||||
var cr = splt.contains("CR");
|
||||
var cs = splt.contains("CS");
|
||||
|
||||
if (a) return "Admin";
|
||||
if (cr && cs && allChannels) return "Read+Send";
|
||||
if (cr && cs && !allChannels) return "Read+Send (${channels.length} channel${channels.length == 1 ? '' : 's'})";
|
||||
if (ur && !cr && !cs) return "Account-Read";
|
||||
if (cr && !cs && !allChannels) return "Read-only (${channels.length} channel${channels.length == 1 ? '' : 's'})";
|
||||
if (cr && !cs && allChannels) return "Read-only";
|
||||
if (cs && !allChannels) return "Send-Only (${channels.length} channel${channels.length == 1 ? '' : 's'})";
|
||||
if (cs && allChannels) return "Send-Only";
|
||||
|
||||
return "{ " + v + " | " + (allChannels ? 'all' : '${channels.length}') + " }";
|
||||
}
|
||||
}
|
||||
435
flutter/lib/pages/keytoken_view/keytoken_view.dart
Normal file
435
flutter/lib/pages/keytoken_view/keytoken_view.dart
Normal file
@@ -0,0 +1,435 @@
|
||||
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/layout/scaffold.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/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/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
|
||||
|
||||
late ImmediateFuture<UserPreview> _futureOwner;
|
||||
|
||||
final TextEditingController _ctrlName = TextEditingController();
|
||||
|
||||
int _loadingIndeterminateCounter = 0;
|
||||
|
||||
EditState _editName = EditState.none;
|
||||
String? _nameOverride = null;
|
||||
|
||||
KeyTokenPreview? keytokenPreview;
|
||||
KeyToken? keytoken;
|
||||
|
||||
KeyTokenViewPageInitState loadingState = KeyTokenViewPageInitState.loading;
|
||||
String errorMessage = '';
|
||||
|
||||
@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<UserPreview>.ofValue(cacheUser.toPreview());
|
||||
} else {
|
||||
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(_getOwner(userAcc));
|
||||
}
|
||||
} else {
|
||||
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(APIClient.getUserPreview(userAcc, this.keytokenPreview!.ownerUserID));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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 = Center(child: Text('Error: ' + errorMessage)); //TODO better error
|
||||
} 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],
|
||||
),
|
||||
_buildNameCard(context, true),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.clock,
|
||||
title: 'Created',
|
||||
values: [_KeyTokenViewPageState._dateFormat.format(DateTime.parse(keytoken.timestampCreated).toLocal())],
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.clockTwo,
|
||||
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, _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;
|
||||
if (_editName == EditState.editing) _editName = EditState.none;
|
||||
});
|
||||
}
|
||||
|
||||
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.shieldKeyhole,
|
||||
title: 'Permissions',
|
||||
values: _formatPermissions(keyToken.permissions),
|
||||
iconActions: [(FontAwesomeIcons.penToSquare, _editPermissions)],
|
||||
);
|
||||
} else {
|
||||
w1 = UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.shieldKeyhole,
|
||||
title: 'Permissions',
|
||||
values: _formatPermissions(keyToken.permissions),
|
||||
);
|
||||
}
|
||||
|
||||
if (isOwned) {
|
||||
w2 = UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidSnake,
|
||||
title: 'Channels',
|
||||
values: (keyToken.allChannels) ? ['All Channels'] : keyToken.channels,
|
||||
iconActions: [(FontAwesomeIcons.penToSquare, _editChannels)],
|
||||
);
|
||||
} else {
|
||||
w2 = UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidSnake,
|
||||
title: 'Channels',
|
||||
values: (keyToken.allChannels) ? ['All Channels'] : 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() {
|
||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||
|
||||
//TODO prevent editing current admin/read token
|
||||
|
||||
//TODO
|
||||
|
||||
Toaster.info("Not implemented", "Currently not implemented");
|
||||
}
|
||||
|
||||
void _editChannels() {
|
||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||
|
||||
//TODO prevent editing current admin/read token
|
||||
|
||||
//TODO
|
||||
|
||||
Toaster.info("Not implemented", "Currently not implemented");
|
||||
}
|
||||
|
||||
void _deleteKey() {
|
||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||
|
||||
//TODO prevent deleting current admin/read token
|
||||
|
||||
//TODO
|
||||
|
||||
Toaster.info("Not implemented", "Currently not implemented");
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,9 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
||||
icon: FontAwesomeIcons.solidGearCode,
|
||||
title: 'KeyToken',
|
||||
values: [message.usedKeyID, token?.name ?? '...'],
|
||||
mainAction: () => {/*TODO*/},
|
||||
mainAction: () => {
|
||||
Navi.push(context, () => FilteredMessageViewPage(title: token?.name ?? message.usedKeyID, filter: MessageFilter(usedKeys: [message.usedKeyID])))
|
||||
},
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
|
||||
Reference in New Issue
Block a user