diff --git a/flutter/lib/api/api_client.dart b/flutter/lib/api/api_client.dart index c2056d2..0d0a664 100644 --- a/flutter/lib/api/api_client.dart +++ b/flutter/lib/api/api_client.dart @@ -4,7 +4,10 @@ import 'package:fl_toast/fl_toast.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:simplecloudnotifier/models/api_error.dart'; +import 'package:simplecloudnotifier/models/client.dart'; import 'package:simplecloudnotifier/models/key_token_auth.dart'; +import 'package:simplecloudnotifier/models/keytoken.dart'; +import 'package:simplecloudnotifier/models/subscription.dart'; import 'package:simplecloudnotifier/models/user.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/globals.dart'; @@ -14,10 +17,10 @@ import 'package:simplecloudnotifier/models/message.dart'; enum ChannelSelector { owned(apiKey: 'owned'), // Return all channels of the user - subscribedAny(apiKey: 'subscribed_any'), // Return all channels that the user is subscribing to - allAny(apiKey: 'all_any'), // Return channels that the user owns or is subscribing - subscribed(apiKey: 'subscribed'), // Return all channels that the user is subscribing to (even unconfirmed) - all(apiKey: 'all'); // Return channels that the user owns or is subscribing (even unconfirmed) + subscribedAny(apiKey: 'subscribed_any'), // Return all channels that the user is subscribing to (even unconfirmed) + allAny(apiKey: 'all_any'), // Return channels that the user owns or is subscribing (even unconfirmed) + subscribed(apiKey: 'subscribed'), // Return all channels that the user is subscribing to + all(apiKey: 'all'); // Return channels that the user owns or is subscribing const ChannelSelector({required this.apiKey}); final String apiKey; @@ -171,4 +174,34 @@ class APIClient { auth: auth, ); } + + static Future> getSubscriptionList(KeyTokenAuth auth) async { + return await _request( + name: 'getSubscriptionList', + method: 'GET', + relURL: 'users/${auth.userId}/subscriptions', + fn: (json) => Subscription.fromJsonArray(json['subscriptions'] as List), + auth: auth, + ); + } + + static Future> getClientList(KeyTokenAuth auth) async { + return await _request( + name: 'getClientList', + method: 'GET', + relURL: 'users/${auth.userId}/clients', + fn: (json) => Client.fromJsonArray(json['clients'] as List), + auth: auth, + ); + } + + static Future> getKeyTokenList(KeyTokenAuth auth) async { + return await _request( + name: 'getKeyTokenList', + method: 'GET', + relURL: 'users/${auth.userId}/keys', + fn: (json) => KeyToken.fromJsonArray(json['keys'] as List), + auth: auth, + ); + } } diff --git a/flutter/lib/models/client.dart b/flutter/lib/models/client.dart new file mode 100644 index 0000000..cc14851 --- /dev/null +++ b/flutter/lib/models/client.dart @@ -0,0 +1,35 @@ +class Client { + final String clientID; + final String userID; + final String type; + final String fcmToken; + final String timestampCreated; + final String agentModel; + final String agentVersion; + + const Client({ + required this.clientID, + required this.userID, + required this.type, + required this.fcmToken, + required this.timestampCreated, + required this.agentModel, + required this.agentVersion, + }); + + factory Client.fromJson(Map json) { + return Client( + clientID: json['client_id'] as String, + userID: json['user_id'] as String, + type: json['type'] as String, + fcmToken: json['fcm_token'] as String, + timestampCreated: json['timestamp_created'] as String, + agentModel: json['agent_model'] as String, + agentVersion: json['agent_version'] as String, + ); + } + + static List fromJsonArray(List jsonArr) { + return jsonArr.map((e) => Client.fromJson(e as Map)).toList(); + } +} diff --git a/flutter/lib/models/keytoken.dart b/flutter/lib/models/keytoken.dart new file mode 100644 index 0000000..c8f70a2 --- /dev/null +++ b/flutter/lib/models/keytoken.dart @@ -0,0 +1,41 @@ +class KeyToken { + final String keytokenID; + final String name; + final String timestampCreated; + final String? timestampLastused; + final String ownerUserID; + final bool allChannels; + final List channels; + final String permissions; + final int messagesSent; + + const KeyToken({ + required this.keytokenID, + required this.name, + required this.timestampCreated, + required this.timestampLastused, + required this.ownerUserID, + required this.allChannels, + required this.channels, + required this.permissions, + required this.messagesSent, + }); + + factory KeyToken.fromJson(Map json) { + return KeyToken( + keytokenID: json['keytoken_id'] as String, + name: json['name'] as String, + timestampCreated: json['timestamp_created'] as String, + timestampLastused: json['timestamp_lastused'] as String?, + ownerUserID: json['owner_user_id'] as String, + allChannels: json['all_channels'] as bool, + channels: (json['channels'] as List).map((e) => e as String).toList(), + permissions: json['permissions'] as String, + messagesSent: json['messages_sent'] as int, + ); + } + + static List fromJsonArray(List jsonArr) { + return jsonArr.map((e) => KeyToken.fromJson(e as Map)).toList(); + } +} diff --git a/flutter/lib/models/subscription.dart b/flutter/lib/models/subscription.dart index f927565..f5ff16e 100644 --- a/flutter/lib/models/subscription.dart +++ b/flutter/lib/models/subscription.dart @@ -28,4 +28,8 @@ class Subscription { confirmed: json['confirmed'] as bool, ); } + + static List fromJsonArray(List jsonArr) { + return jsonArr.map((e) => Subscription.fromJson(e as Map)).toList(); + } } diff --git a/flutter/lib/nav_layout.dart b/flutter/lib/nav_layout.dart index 778342c..3596320 100644 --- a/flutter/lib/nav_layout.dart +++ b/flutter/lib/nav_layout.dart @@ -1,12 +1,16 @@ +import 'package:fl_toast/fl_toast.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_lazy_indexed_stack/flutter_lazy_indexed_stack.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; import 'package:simplecloudnotifier/components/layout/app_bar.dart'; import 'package:simplecloudnotifier/pages/channel_list/channel_list.dart'; import 'package:simplecloudnotifier/pages/send/root.dart'; import 'package:simplecloudnotifier/components/bottom_fab/fab_bottom_app_bar.dart'; -import 'package:simplecloudnotifier/pages/account/root.dart'; +import 'package:simplecloudnotifier/pages/account/account.dart'; import 'package:simplecloudnotifier/pages/message_list/message_list.dart'; import 'package:simplecloudnotifier/pages/settings/root.dart'; +import 'package:simplecloudnotifier/state/user_account.dart'; class SCNNavLayout extends StatefulWidget { const SCNNavLayout({super.key}); @@ -18,13 +22,33 @@ class SCNNavLayout extends StatefulWidget { class _SCNNavLayoutState extends State { int _selectedIndex = 0; // 4 == FAB + @override + initState() { + final userAcc = Provider.of(context, listen: false); + if (userAcc.auth == null) _selectedIndex = 2; + + super.initState(); + } + void _onItemTapped(int index) { + final userAcc = Provider.of(context, listen: false); + if (userAcc.auth == null) { + showPlatformToast(child: Text('Please login first or create a new account'), context: ToastProvider.context); + return; + } + setState(() { _selectedIndex = index; }); } void _onFABTapped() { + final userAcc = Provider.of(context, listen: false); + if (userAcc.auth == null) { + showPlatformToast(child: Text('Please login first or create a new account'), context: ToastProvider.context); + return; + } + setState(() { _selectedIndex = 4; }); @@ -39,7 +63,7 @@ class _SCNNavLayoutState extends State { showSearch: _selectedIndex == 0 || _selectedIndex == 1, showThemeSwitch: true, ), - body: IndexedStack( + body: LazyIndexedStack( children: [ MessageListPage(), ChannelRootPage(), diff --git a/flutter/lib/pages/account/account.dart b/flutter/lib/pages/account/account.dart new file mode 100644 index 0000000..967d491 --- /dev/null +++ b/flutter/lib/pages/account/account.dart @@ -0,0 +1,392 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; +import 'package:simplecloudnotifier/api/api_client.dart'; +import 'package:simplecloudnotifier/models/user.dart'; +import 'package:simplecloudnotifier/state/user_account.dart'; + +class AccountRootPage extends StatefulWidget { + const AccountRootPage({super.key}); + + @override + State createState() => _AccountRootPageState(); +} + +class _AccountRootPageState extends State { + late Future? futureSubscriptionCount; + late Future? futureClientCount; + late Future? futureKeyCount; + late Future? futureChannelAllCount; + late Future? futureChannelSubscribedCount; + + late UserAccount userAcc; + + @override + void initState() { + super.initState(); + + userAcc = Provider.of(context, listen: false); + userAcc.addListener(_onAuthStateChanged); + _onAuthStateChanged(); + } + + @override + void dispose() { + userAcc.removeListener(_onAuthStateChanged); + super.dispose(); + } + + void _onAuthStateChanged() { + futureSubscriptionCount = null; + futureClientCount = null; + futureKeyCount = null; + futureChannelAllCount = null; + futureChannelSubscribedCount = null; + + futureChannelAllCount = () async { + if (userAcc.auth == null) throw new Exception('not logged in'); + final channels = await APIClient.getChannelList(userAcc.auth!, ChannelSelector.all); + return channels.length; + }(); + + futureChannelSubscribedCount = () async { + if (userAcc.auth == null) throw new Exception('not logged in'); + final channels = await APIClient.getChannelList(userAcc.auth!, ChannelSelector.subscribed); + return channels.length; + }(); + + futureSubscriptionCount = () async { + if (userAcc.auth == null) throw new Exception('not logged in'); + final subs = await APIClient.getSubscriptionList(userAcc.auth!); + return subs.length; + }(); + + futureClientCount = () async { + if (userAcc.auth == null) throw new Exception('not logged in'); + final clients = await APIClient.getClientList(userAcc.auth!); + return clients.length; + }(); + + futureKeyCount = () async { + if (userAcc.auth == null) throw new Exception('not logged in'); + final keys = await APIClient.getKeyTokenList(userAcc.auth!); + return keys.length; + }(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, acc, child) { + if (acc.auth == null) { + return buildNoAuth(context); + } else { + return FutureBuilder( + future: acc.loadUser(false), + builder: ((context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); //TODO better error display + } + return buildShowAccount(context, acc, snapshot.data!); + } + return Center(child: CircularProgressIndicator()); + }), + ); + } + }, + ); + } + + Widget buildNoAuth(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)), + onPressed: () { + //TODO + }, + child: const Text('Use existing account'), + ), + const SizedBox(height: 32), + ElevatedButton( + style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)), + onPressed: () { + //TODO + }, + child: const Text('Create new account'), + ), + ], + ), + ); + } + + Widget buildShowAccount(BuildContext context, UserAccount acc, User user) { + //TODO better layout + return Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.fromLTRB(8.0, 24.0, 8.0, 8.0), + child: Column( + children: [ + buildHeader(context, user), + const SizedBox(height: 16), + Text(user.username ?? user.userID, overflow: TextOverflow.ellipsis, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), + const SizedBox(height: 16), + ...buildCards(context, user), + ], + ), + ), + ), + const Expanded(child: SizedBox(height: 16)), + buildFooter(context, user), + SizedBox(height: 40) + ], + ); + } + + Row buildHeader(BuildContext context, User user) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80, + height: 80, + child: Stack( + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(8), + ), + child: Center(child: FaIcon(FontAwesomeIcons.addressCard, size: 55, color: Colors.white))), + if (user.isPro) + Align( + alignment: Alignment.bottomRight, + child: Container( + child: Text('PRO', style: TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.bold)), + padding: const EdgeInsets.fromLTRB(4, 1, 4, 1), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.only(topLeft: Radius.circular(8)), + ), + ), + ), + if (!user.isPro) + Align( + alignment: Alignment.bottomRight, + child: Container( + child: Text('FREE', style: TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.bold)), + padding: const EdgeInsets.fromLTRB(4, 1, 4, 1), + decoration: BoxDecoration( + color: Colors.purple, + borderRadius: BorderRadius.only(topLeft: Radius.circular(8)), + ), + ), + ), + ], + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: Text(user.username ?? user.userID, overflow: TextOverflow.ellipsis)), + IconButton( + icon: FaIcon(FontAwesomeIcons.pen), + iconSize: 18, + padding: EdgeInsets.fromLTRB(0, 0, 4, 0), + constraints: BoxConstraints(), + onPressed: () {/*TODO*/}, + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + SizedBox(width: 80, child: Text("Quota", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)))), + Expanded(child: Text('${user.quotaUsed} / ${user.quotaPerDay}')), + ], + ), + Row( + children: [ + SizedBox(width: 80, child: Text("Messages", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)))), + Expanded(child: Text('${user.messagesSent}')), + ], + ), + Row( + children: [ + SizedBox(width: 80, child: Text("Channels", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)))), + FutureBuilder( + future: futureChannelAllCount, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return Text('${snapshot.data}'); + } + return const SizedBox(width: 8, height: 8, child: Center(child: CircularProgressIndicator())); + }, + ) + ], + ), + ], + ), + ), + ], + ); + } + + List buildCards(BuildContext context, User user) { + return [ + Card.filled( + margin: EdgeInsets.fromLTRB(0, 4, 0, 4), + shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)), + color: Theme.of(context).cardTheme.color, + child: InkWell( + splashColor: Theme.of(context).splashColor, + onTap: () {/*TODO*/}, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + FutureBuilder( + future: futureSubscriptionCount, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)); + } + return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator())); + }, + ), + const SizedBox(width: 12), + Text('Subscriptions', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), + ], + ), + ), + ), + ), + Card.filled( + margin: EdgeInsets.fromLTRB(0, 4, 0, 4), + shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)), + color: Theme.of(context).cardTheme.color, + child: InkWell( + splashColor: Theme.of(context).splashColor, + onTap: () {/*TODO*/}, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + FutureBuilder( + future: futureClientCount, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)); + } + return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator())); + }, + ), + const SizedBox(width: 12), + Text('Clients', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), + ], + ), + ), + ), + ), + Card.filled( + margin: EdgeInsets.fromLTRB(0, 4, 0, 4), + shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)), + color: Theme.of(context).cardTheme.color, + child: InkWell( + splashColor: Theme.of(context).splashColor, + onTap: () {/*TODO*/}, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + FutureBuilder( + future: futureKeyCount, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)); + } + return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator())); + }, + ), + const SizedBox(width: 12), + Text('Keys', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), + ], + ), + ), + ), + ), + Card.filled( + margin: EdgeInsets.fromLTRB(0, 4, 0, 4), + shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)), + color: Theme.of(context).cardTheme.color, + child: InkWell( + splashColor: Theme.of(context).splashColor, + onTap: () {/*TODO*/}, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + FutureBuilder( + future: futureChannelSubscribedCount, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)); + } + return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator())); + }, + ), + const SizedBox(width: 12), + Text('Channels', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), + ], + ), + ), + ), + ), + Card.filled( + margin: EdgeInsets.fromLTRB(0, 4, 0, 4), + shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)), + color: Theme.of(context).cardTheme.color, + child: InkWell( + splashColor: Theme.of(context).splashColor, + onTap: () {/*TODO*/}, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Text('${user.messagesSent}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), + const SizedBox(width: 12), + Text('Messages', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), + ], + ), + ), + ), + ), + ]; + } + + Widget buildFooter(BuildContext context, User user) { + return Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), + child: Row( + children: [ + Expanded(child: FilledButton(onPressed: () {/*TODO*/}, child: Text('Logout'), style: TextButton.styleFrom(backgroundColor: Colors.orange))), + const SizedBox(width: 8), + Expanded(child: FilledButton(onPressed: () {/*TODO*/}, child: Text('Delete Account'), style: TextButton.styleFrom(backgroundColor: Colors.red))), + ], + ), + ); + } +} diff --git a/flutter/lib/pages/account/choose_auth.dart b/flutter/lib/pages/account/choose_auth.dart deleted file mode 100644 index 05749e0..0000000 --- a/flutter/lib/pages/account/choose_auth.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; - -class AccountChoosePage extends StatelessWidget { - final void Function()? onLogin; - final void Function()? onCreateAccount; - - const AccountChoosePage({super.key, this.onLogin, this.onCreateAccount}); - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)), - onPressed: () { - onLogin?.call(); - }, - child: const Text('Use existing account'), - ), - const SizedBox(height: 32), - ElevatedButton( - style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)), - onPressed: () { - onCreateAccount?.call(); - }, - child: const Text('Create new account'), - ), - ], - ), - ); - } -} diff --git a/flutter/lib/pages/account/root.dart b/flutter/lib/pages/account/root.dart deleted file mode 100644 index 078a4ef..0000000 --- a/flutter/lib/pages/account/root.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:simplecloudnotifier/pages/account/login.dart'; -import 'package:simplecloudnotifier/state/user_account.dart'; -import 'package:simplecloudnotifier/pages/account/choose_auth.dart'; - -class AccountRootPage extends StatefulWidget { - const AccountRootPage({super.key}); - - @override - State createState() => _AccountRootPageState(); -} - -enum _SubPage { chooseAuth, login, main } - -class _AccountRootPageState extends State { - late _SubPage _page; - late UserAccount userAcc; - - @override - void initState() { - super.initState(); - - userAcc = Provider.of(context, listen: false); - - _page = (userAcc.auth != null) ? _SubPage.main : _SubPage.chooseAuth; - - userAcc.addListener(_onAuthStateChanged); - } - - @override - void dispose() { - userAcc.removeListener(_onAuthStateChanged); - super.dispose(); - } - - void _onAuthStateChanged() { - if (Provider.of(context, listen: false).auth != null && _page != _SubPage.main) { - setState(() { - _page = _SubPage.main; - }); - } - } - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (context, acc, child) { - switch (_page) { - case _SubPage.main: - return Center( - child: Text( - 'Logged In: ${acc.auth?.userId}', - style: const TextStyle(fontSize: 24), - ), - ); - case _SubPage.chooseAuth: - return AccountChoosePage( - onLogin: () => setState(() { - _page = _SubPage.login; - }), - onCreateAccount: () => setState(() { - //TODO - }), - ); - case _SubPage.login: - return AccountLoginPage( - onLogin: () => setState(() { - _page = _SubPage.main; - }), - ); - } - }, - ); - } -} diff --git a/flutter/lib/pages/message_list/message_list_item.dart b/flutter/lib/pages/message_list/message_list_item.dart index a76d3ac..b81daf9 100644 --- a/flutter/lib/pages/message_list/message_list_item.dart +++ b/flutter/lib/pages/message_list/message_list_item.dart @@ -47,9 +47,9 @@ class MessageListItem extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (message.priority == 2) FaIcon(FontAwesomeIcons.solidTriangleExclamation, size: 16, color: Theme.of(context).colorScheme.error), + if (message.priority == 2) FaIcon(FontAwesomeIcons.solidTriangleExclamation, size: 16, color: Colors.red[900]), if (message.priority == 2) SizedBox(width: 4), - if (message.priority == 0) FaIcon(FontAwesomeIcons.solidDown, size: 16, color: Theme.of(context).colorScheme.primary), + if (message.priority == 0) FaIcon(FontAwesomeIcons.solidDown, size: 16, color: Colors.lightBlue[900]), if (message.priority == 0) SizedBox(width: 4), Expanded( child: Text( @@ -98,9 +98,9 @@ class MessageListItem extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (message.priority == 2) FaIcon(FontAwesomeIcons.solidTriangleExclamation, size: 16, color: Theme.of(context).colorScheme.error), + if (message.priority == 2) FaIcon(FontAwesomeIcons.solidTriangleExclamation, size: 16, color: Colors.red[900]), if (message.priority == 2) SizedBox(width: 4), - if (message.priority == 0) FaIcon(FontAwesomeIcons.solidDown, size: 16, color: Theme.of(context).colorScheme.primary), + if (message.priority == 0) FaIcon(FontAwesomeIcons.solidDown, size: 16, color: Colors.lightBlue[900]), if (message.priority == 0) SizedBox(width: 4), Container( padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), diff --git a/flutter/lib/pages/send/root.dart b/flutter/lib/pages/send/root.dart index b2b496d..14f8894 100644 --- a/flutter/lib/pages/send/root.dart +++ b/flutter/lib/pages/send/root.dart @@ -90,7 +90,7 @@ class _SendRootPageState extends State { builder: ((context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); + return Text('Error: ${snapshot.error}'); //TODO better error display } var url = 'https://simplecloudnotifier.com?preset_user_id=${acc.user!.userID}&preset_user_key=TODO'; // TODO get send-only key return GestureDetector( diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 20ac11e..70d3d03 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -222,6 +222,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_lazy_indexed_stack: + dependency: "direct main" + description: + name: flutter_lazy_indexed_stack + sha256: e5529b516890475465c8c34b23d611e9c46b23c745c08edf471d1b6f899f5c9f + url: "https://pub.dev" + source: hosted + version: "0.0.6" flutter_lints: dependency: "direct dev" description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index bf0513d..25956c0 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: package_info_plus: ^8.0.0 fl_toast: ^3.2.0 xid: ^1.2.1 + flutter_lazy_indexed_stack: ^0.0.6 dependency_overrides: