diff --git a/flutter/lib/pages/account/account.dart b/flutter/lib/pages/account/account.dart index 274570f..6aae648 100644 --- a/flutter/lib/pages/account/account.dart +++ b/flutter/lib/pages/account/account.dart @@ -8,6 +8,7 @@ import 'package:simplecloudnotifier/api/api_client.dart'; 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/sender_list/sender_list.dart'; import 'package:simplecloudnotifier/state/app_bar_state.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; @@ -381,7 +382,9 @@ class _AccountRootPageState extends State { List _buildCards(BuildContext context, User user) { return [ _buildNumberCard(context, 'Subscriptions', futureSubscriptionCount, () {/*TODO*/}), - _buildNumberCard(context, 'Clients', futureClientCount, () {/*TODO*/}), + _buildNumberCard(context, 'Clients', futureClientCount, () { + Navi.push(context, () => ClientListPage()); + }), _buildNumberCard(context, 'Keys', futureKeyCount, () {/*TODO*/}), _buildNumberCard(context, 'Channels', futureChannelSubscribedCount, () { Navi.push(context, () => ChannelListExtendedPage()); diff --git a/flutter/lib/pages/client_list/client_list.dart b/flutter/lib/pages/client_list/client_list.dart new file mode 100644 index 0000000..3ee12e4 --- /dev/null +++ b/flutter/lib/pages/client_list/client_list.dart @@ -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/client.dart'; +import 'package:simplecloudnotifier/state/application_log.dart'; +import 'package:simplecloudnotifier/state/app_auth.dart'; +import 'package:simplecloudnotifier/pages/client_list/client_list_item.dart'; + +class ClientListPage extends StatefulWidget { + const ClientListPage({super.key}); + + @override + State createState() => _ClientListPageState(); +} + +class _ClientListPageState extends State { + final PagingController _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('ClientListPage::dispose'); + _pagingController.dispose(); + super.dispose(); + } + + Future _fetchPage(int pageKey) async { + final acc = Provider.of(context, listen: false); + + ApplicationLog.debug('Start ClientListPage::_pagingController::_fetchPage [ ${pageKey} ]'); + + if (!acc.isAuth()) { + _pagingController.error = 'Not logged in'; + return; + } + + try { + final items = (await APIClient.getClientList(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 clients: ' + exc.toString(), trace: trace); + } + } + + @override + Widget build(BuildContext context) { + return SCNScaffold( + title: "Client", + showSearch: false, + showShare: false, + child: Padding( + padding: EdgeInsets.fromLTRB(8, 4, 8, 4), + child: RefreshIndicator( + onRefresh: () => Future.sync( + () => _pagingController.refresh(), + ), + child: PagedListView( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) => ClientListItem(item: item), + ), + ), + ), + ), + ); + } +} diff --git a/flutter/lib/pages/client_list/client_list_item.dart b/flutter/lib/pages/client_list/client_list_item.dart new file mode 100644 index 0000000..25a7666 --- /dev/null +++ b/flutter/lib/pages/client_list/client_list_item.dart @@ -0,0 +1,82 @@ +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, + Extended, +} + +class ClientListItem extends StatelessWidget { + static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting + + const ClientListItem({ + required this.item, + super.key, + }); + + final Client 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: Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + _buildIcon(context), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Expanded( + child: Text( + item.name ?? item.clientID, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + Text( + ClientListItem._dateFormat.format(DateTime.parse(item.timestampCreated).toLocal()), + style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)), + ), + ], + ), + SizedBox(height: 4), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Text(item.agentModel.toString() + " " + item.agentVersion.toString()), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildIcon(BuildContext context) { + if (item.type == "ANDROID") return Icon(FontAwesomeIcons.android, color: Theme.of(context).colorScheme.outline, size: 32); + if (item.type == "IOS") return Icon(FontAwesomeIcons.apple, color: Theme.of(context).colorScheme.outline, size: 32); + if (item.type == "LINUX") return Icon(FontAwesomeIcons.linux, color: Theme.of(context).colorScheme.outline, size: 32); + if (item.type == "MACOS") return Icon(FontAwesomeIcons.appleWhole, color: Theme.of(context).colorScheme.outline, size: 32); + if (item.type == "WINDOWS") return Icon(FontAwesomeIcons.windows, color: Theme.of(context).colorScheme.outline, size: 32); + + return Icon(FontAwesomeIcons.solidSignature, color: Theme.of(context).colorScheme.outline, size: 32); + } +}