diff --git a/flutter/lib/components/bottom_fab/fab_with_icons.dart b/flutter/lib/components/bottom_fab/fab_with_icons.dart deleted file mode 100644 index ff4735e..0000000 --- a/flutter/lib/components/bottom_fab/fab_with_icons.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; - -// https://stackoverflow.com/questions/46480221/flutter-floating-action-button-with-speed-dail - -class FabWithIcons extends StatefulWidget { - FabWithIcons({super.key, required this.icons, required this.onIconTapped}); - final List icons; - final ValueChanged onIconTapped; - - @override - State createState() => FabWithIconsState(); -} - -class FabWithIconsState extends State with TickerProviderStateMixin { - late AnimationController _controller; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 250), - ); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: List.generate(widget.icons.length, (int index) { - return _buildChild(index); - }).toList() - ..add( - _buildFab(), - ), - ); - } - - Widget _buildChild(int index) { - Color backgroundColor = Theme.of(context).cardColor; - Color foregroundColor = Theme.of(context).secondaryHeaderColor; - return Container( - height: 70.0, - width: 56.0, - alignment: FractionalOffset.topCenter, - child: ScaleTransition( - scale: CurvedAnimation( - parent: _controller, - curve: Interval(0.0, 1.0 - index / widget.icons.length / 2.0, curve: Curves.easeOut), - ), - child: FloatingActionButton( - backgroundColor: backgroundColor, - mini: true, - child: Icon(widget.icons[index], color: foregroundColor), - onPressed: () => _onTapped(index), - ), - ), - ); - } - - Widget _buildFab() { - return FloatingActionButton( - onPressed: () { - if (_controller.isDismissed) { - _controller.forward(); - } else { - _controller.reverse(); - } - }, - tooltip: 'Increment', - elevation: 2.0, - child: const Icon(Icons.add), - ); - } - - void _onTapped(int index) { - _controller.reverse(); - widget.onIconTapped(index); - } -} diff --git a/flutter/lib/components/hidable_fab/hidable_fab.dart b/flutter/lib/components/hidable_fab/hidable_fab.dart index 3d0f577..fd7b4ef 100644 --- a/flutter/lib/components/hidable_fab/hidable_fab.dart +++ b/flutter/lib/components/hidable_fab/hidable_fab.dart @@ -3,17 +3,20 @@ import 'package:flutter/material.dart'; class HidableFAB extends StatelessWidget { final VoidCallback? onPressed; final IconData icon; + final Object heroTag; const HidableFAB({ super.key, this.onPressed, required this.icon, + required this.heroTag, }); Widget build(BuildContext context) { return Visibility( visible: MediaQuery.viewInsetsOf(context).bottom == 0.0, // hide when keyboard is shown child: FloatingActionButton( + heroTag: this.heroTag, onPressed: onPressed, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(17))), elevation: 2.0, diff --git a/flutter/lib/models/channel.dart b/flutter/lib/models/channel.dart index a2d1c6f..c912977 100644 --- a/flutter/lib/models/channel.dart +++ b/flutter/lib/models/channel.dart @@ -70,6 +70,16 @@ class Channel extends HiveObject implements FieldDebuggable { ('messagesSent', '${this.messagesSent}'), ]; } + + ChannelPreview toPreview() { + return ChannelPreview( + channelID: this.channelID, + ownerUserID: this.ownerUserID, + internalName: this.internalName, + displayName: this.displayName, + descriptionName: this.descriptionName, + ); + } } class ChannelWithSubscription { diff --git a/flutter/lib/nav_layout.dart b/flutter/lib/nav_layout.dart index cd9dafd..55e1687 100644 --- a/flutter/lib/nav_layout.dart +++ b/flutter/lib/nav_layout.dart @@ -76,6 +76,7 @@ class _SCNNavLayoutState extends State { bottomNavigationBar: _buildNavBar(context), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: HidableFAB( + heroTag: 'fab_main', onPressed: _onFABTapped, icon: FontAwesomeIcons.solidPaperPlaneTop, ), diff --git a/flutter/lib/pages/channel_list/channel_list.dart b/flutter/lib/pages/channel_list/channel_list.dart index f3149a8..cf1e884 100644 --- a/flutter/lib/pages/channel_list/channel_list.dart +++ b/flutter/lib/pages/channel_list/channel_list.dart @@ -155,17 +155,17 @@ class _ChannelRootPageState extends State with RouteAware { channel: item.channel, subscription: item.subscription, onPressed: () { - Navi.push(context, () => ChannelViewPage(channel: item.channel, subscription: item.subscription, needsReload: _enqueueReload)); + Navi.push(context, () => ChannelViewPage(channelID: item.channel.channelID, preloadedData: (item.channel, item.subscription), needsReload: _enqueueReload)); }, ), ), ), ), floatingActionButton: FloatingActionButton( + heroTag: 'fab_channel_list_qr', onPressed: () { //TODO scan qr code to subscribe channel }, - backgroundColor: , child: const Icon(FontAwesomeIcons.qrcode), ), ); diff --git a/flutter/lib/pages/channel_list/channel_list_item.dart b/flutter/lib/pages/channel_list/channel_list_item.dart index 8dff3de..5f43148 100644 --- a/flutter/lib/pages/channel_list/channel_list_item.dart +++ b/flutter/lib/pages/channel_list/channel_list_item.dart @@ -1,4 +1,6 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -6,8 +8,11 @@ import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/models/channel.dart'; import 'package:simplecloudnotifier/models/scn_message.dart'; import 'package:simplecloudnotifier/models/subscription.dart'; +import 'package:simplecloudnotifier/pages/channel_message_view/channel_message_view.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:simplecloudnotifier/state/scn_data_cache.dart'; +import 'package:simplecloudnotifier/utils/navi.dart'; +import 'package:simplecloudnotifier/utils/toaster.dart'; class ChannelListItem extends StatefulWidget { static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm'); @@ -56,7 +61,6 @@ class _ChannelListItemState extends State { shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)), color: Theme.of(context).cardTheme.color, child: InkWell( - splashColor: Theme.of(context).splashColor, onTap: widget.onPressed, child: Padding( padding: const EdgeInsets.all(8), @@ -98,6 +102,16 @@ class _ChannelListItemState extends State { ], ), ), + SizedBox(width: 4), + GestureDetector( + onTap: () { + Navi.push(context, () => ChannelMessageViewPage(channel: this.widget.channel)); + }, + child: Padding( + padding: const EdgeInsets.all(8), + child: Icon(FontAwesomeIcons.solidEnvelopes, color: Theme.of(context).colorScheme.onPrimaryContainer.withAlpha(128), size: 24), + ), + ), ], ), ), diff --git a/flutter/lib/pages/channel_message_view/channel_message_view.dart b/flutter/lib/pages/channel_message_view/channel_message_view.dart new file mode 100644 index 0000000..2146dac --- /dev/null +++ b/flutter/lib/pages/channel_message_view/channel_message_view.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:simplecloudnotifier/api/api_client.dart'; +import 'package:simplecloudnotifier/components/layout/scaffold.dart'; +import 'package:simplecloudnotifier/models/channel.dart'; +import 'package:simplecloudnotifier/models/scn_message.dart'; +import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart'; +import 'package:simplecloudnotifier/pages/message_view/message_view.dart'; +import 'package:simplecloudnotifier/settings/app_settings.dart'; +import 'package:simplecloudnotifier/state/app_auth.dart'; +import 'package:simplecloudnotifier/state/application_log.dart'; +import 'package:simplecloudnotifier/state/scn_data_cache.dart'; +import 'package:simplecloudnotifier/utils/navi.dart'; +import 'package:provider/provider.dart'; + +class ChannelMessageViewPage extends StatefulWidget { + const ChannelMessageViewPage({ + required this.channel, + super.key, + }); + + final Channel channel; + + @override + State createState() => _ChannelMessageViewPageState(); +} + +class _ChannelMessageViewPageState extends State { + PagingController _pagingController = PagingController.fromValue(PagingState(nextPageKey: null, itemList: [], error: null), firstPageKey: '@start'); + + @override + void initState() { + super.initState(); + + _pagingController.addPageRequestListener(_fetchPage); + + _pagingController.refresh(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + Future _fetchPage(String thisPageToken) async { + final acc = Provider.of(context, listen: false); + final cfg = Provider.of(context, listen: false); + + ApplicationLog.debug('Start ChannelMessageViewPage::_pagingController::_fetchPage [ ${thisPageToken} ]'); + + if (!acc.isAuth()) { + _pagingController.error = 'Not logged in'; + return; + } + + try { + final (npt, newItems) = await APIClient.getMessageList(acc, thisPageToken, pageSize: cfg.messagePageSize, channelIDs: [this.widget.channel.channelID]); + + SCNDataCache().addToMessageCache(newItems); // no await + + if (npt == '@end') { + _pagingController.appendLastPage(newItems); + } else { + _pagingController.appendPage(newItems, npt); + } + } catch (exc, trace) { + _pagingController.error = exc.toString(); + ApplicationLog.error('Failed to list channel-messages: ' + exc.toString(), trace: trace); + } + } + + @override + Widget build(BuildContext context) { + return SCNScaffold( + title: this.widget.channel.displayName, + showSearch: false, + showShare: false, + child: _buildMessageList(context), + ); + } + + Widget _buildMessageList(BuildContext context) { + return 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) => MessageListItem( + message: item, + allChannels: {this.widget.channel.channelID: this.widget.channel}, + onPressed: () { + Navi.push(context, () => MessageViewPage(message: item)); + }, + ), + ), + ), + ), + ); + } +} diff --git a/flutter/lib/pages/channel_view/channel_view.dart b/flutter/lib/pages/channel_view/channel_view.dart index 6b2a77e..7613c83 100644 --- a/flutter/lib/pages/channel_view/channel_view.dart +++ b/flutter/lib/pages/channel_view/channel_view.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; @@ -8,24 +7,26 @@ import 'package:simplecloudnotifier/components/layout/scaffold.dart'; import 'package:simplecloudnotifier/models/channel.dart'; import 'package:simplecloudnotifier/models/subscription.dart'; import 'package:simplecloudnotifier/models/user.dart'; +import 'package:simplecloudnotifier/pages/channel_message_view/channel_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 ChannelViewPage extends StatefulWidget { const ChannelViewPage({ - required this.channel, - required this.subscription, + required this.channelID, + required this.preloadedData, required this.needsReload, super.key, }); - final Channel channel; - final Subscription? subscription; + final String channelID; + final (Channel, Subscription?)? preloadedData; final void Function()? needsReload; @@ -35,6 +36,8 @@ class ChannelViewPage extends StatefulWidget { enum EditState { none, editing, saving } +enum ChannelViewPageInitState { loading, okay, error } + class _ChannelViewPageState extends State { late ImmediateFuture _futureSubscribeKey; late ImmediateFuture> _futureSubscriptions; @@ -51,15 +54,58 @@ class _ChannelViewPageState extends State { EditState _editDescriptionName = EditState.none; String? _descriptionNameOverride = null; + ChannelPreview? channelPreview; + Channel? channel; + Subscription? subscription; + + ChannelViewPageInitState loadingState = ChannelViewPageInitState.loading; + String errorMessage = ''; + @override void initState() { + _initStateAsync(); + + super.initState(); + } + + @override + void _initStateAsync() async { final userAcc = Provider.of(context, listen: false); - if (widget.channel.ownerUserID == userAcc.userID) { - if (widget.channel.subscribeKey != null) { - _futureSubscribeKey = ImmediateFuture.ofValue(widget.channel.subscribeKey); + if (widget.preloadedData != null) { + channelPreview = widget.preloadedData!.$1.toPreview(); + channel = widget.preloadedData!.$1; + subscription = widget.preloadedData!.$2; + } else { + try { + var p = await APIClient.getChannelPreview(userAcc, widget.channelID); + channelPreview = p; + if (p.ownerUserID == userAcc.userID) { + var r = await APIClient.getChannel(userAcc, widget.channelID); + channel = r.channel; + subscription = r.subscription; + } else { + channel = null; + subscription = null; //TODO get own subscription on this channel, even though its foreign channel + } + } 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 = ChannelViewPageInitState.error; + return; + } + } + + this.loadingState = ChannelViewPageInitState.okay; + + assert(channelPreview != null); + + if (this.channelPreview!.ownerUserID == userAcc.userID) { + if (this.channel != null && this.channel!.subscribeKey != null) { + _futureSubscribeKey = ImmediateFuture.ofValue(this.channel!.subscribeKey); } else { - _futureSubscribeKey = ImmediateFuture.ofFuture(_getSubScribeKey(userAcc)); + _futureSubscribeKey = ImmediateFuture.ofFuture(_getSubscribeKey(userAcc)); } _futureSubscriptions = ImmediateFuture>.ofFuture(_listSubscriptions(userAcc)); } else { @@ -67,7 +113,7 @@ class _ChannelViewPageState extends State { _futureSubscriptions = ImmediateFuture>.ofValue([]); } - if (widget.channel.ownerUserID == userAcc.userID) { + if (this.channelPreview!.ownerUserID == userAcc.userID) { var cacheUser = userAcc.getUserOrNull(); if (cacheUser != null) { _futureOwner = ImmediateFuture.ofValue(cacheUser.toPreview()); @@ -75,10 +121,8 @@ class _ChannelViewPageState extends State { _futureOwner = ImmediateFuture.ofFuture(_getOwner(userAcc)); } } else { - _futureOwner = ImmediateFuture.ofFuture(APIClient.getUserPreview(userAcc, widget.channel.ownerUserID)); + _futureOwner = ImmediateFuture.ofFuture(APIClient.getUserPreview(userAcc, this.channelPreview!.ownerUserID)); } - - super.initState(); } @override @@ -90,19 +134,30 @@ class _ChannelViewPageState extends State { @override Widget build(BuildContext context) { + final userAcc = Provider.of(context, listen: false); + + Widget child; + + if (loadingState == ChannelViewPageInitState.loading) { + child = Center(child: CircularProgressIndicator()); + } else if (loadingState == ChannelViewPageInitState.error) { + child = Center(child: Text('Error: ' + errorMessage)); //TODO better error + } else if (loadingState == ChannelViewPageInitState.okay && channelPreview!.ownerUserID == userAcc.userID) { + child = _buildOwnedChannelView(context, this.channel!); + } else { + child = _buildForeignChannelView(context, this.channelPreview!); + } + return SCNScaffold( title: 'Channel', showSearch: false, showShare: false, - child: _buildChannelView(context), + child: child, ); } - Widget _buildChannelView(BuildContext context) { - final userAccUserID = context.select((v) => v.userID); - - final isOwned = (widget.channel.ownerUserID == userAccUserID); - final isSubscribed = (widget.subscription != null && widget.subscription!.confirmed); + Widget _buildOwnedChannelView(BuildContext context, Channel channel) { + final isSubscribed = (subscription != null && subscription!.confirmed); return SingleChildScrollView( child: Padding( @@ -116,31 +171,33 @@ class _ChannelViewPageState extends State { context: context, icon: FontAwesomeIcons.solidIdCardClip, title: 'ChannelID', - values: [widget.channel.channelID], + values: [channel.channelID], ), UI.metaCard( context: context, icon: FontAwesomeIcons.solidInputNumeric, title: 'InternalName', - values: [widget.channel.internalName], + values: [channel.internalName], ), - _buildDisplayNameCard(context, isOwned), - _buildDescriptionNameCard(context, isOwned), + _buildDisplayNameCard(context, true), + _buildDescriptionNameCard(context, true), UI.metaCard( context: context, icon: FontAwesomeIcons.solidDiagramSubtask, title: 'Subscription (own)', - values: [_formatSubscriptionStatus(widget.subscription)], + values: [_formatSubscriptionStatus(this.subscription)], iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)], ), _buildForeignSubscriptions(context), - _buildOwnerCard(context, isOwned), + _buildOwnerCard(context, true), UI.metaCard( context: context, icon: FontAwesomeIcons.solidEnvelope, title: 'Messages', - values: [widget.channel.messagesSent.toString()], - mainAction: () {/*TODO*/}, + values: [channel.messagesSent.toString()], + mainAction: () { + Navi.push(context, () => ChannelMessageViewPage(channel: channel)); + }, ), ], ), @@ -148,6 +205,45 @@ class _ChannelViewPageState extends State { ); } + Widget _buildForeignChannelView(BuildContext context, ChannelPreview channel) { + final isSubscribed = (subscription != null && subscription!.confirmed); + + 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: 'ChannelID', + values: [channel.channelID], + ), + UI.metaCard( + context: context, + icon: FontAwesomeIcons.solidInputNumeric, + title: 'InternalName', + values: [channel.internalName], + ), + _buildDisplayNameCard(context, false), + _buildDescriptionNameCard(context, false), + UI.metaCard( + context: context, + icon: FontAwesomeIcons.solidDiagramSubtask, + title: 'Subscription (own)', + values: [_formatSubscriptionStatus(subscription)], + iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)], + ), + _buildForeignSubscriptions(context), + _buildOwnerCard(context, false), + ], + ), + ), + ); + } + Widget _buildForeignSubscriptions(BuildContext context) { return FutureBuilder( future: _futureSubscriptions.future, @@ -156,7 +252,7 @@ class _ChannelViewPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - for (final (sub, user) in snapshot.data!.where((v) => v.$1.subscriptionID != widget.subscription?.subscriptionID)) + for (final (sub, user) in snapshot.data!.where((v) => v.$1.subscriptionID != subscription?.subscriptionID)) UI.metaCard( context: context, icon: FontAwesomeIcons.solidDiagramSuccessor, @@ -182,14 +278,14 @@ class _ChannelViewPageState extends State { context: context, icon: FontAwesomeIcons.solidUser, title: 'Owner', - values: [widget.channel.ownerUserID + (isOwned ? ' (you)' : ''), if (snapshot.data?.username != null) snapshot.data!.username!], + values: [channelPreview!.ownerUserID + (isOwned ? ' (you)' : ''), if (snapshot.data?.username != null) snapshot.data!.username!], ); } else { return UI.metaCard( context: context, icon: FontAwesomeIcons.solidUser, title: 'Owner', - values: [widget.channel.ownerUserID + (isOwned ? ' (you)' : '')], + values: [channelPreview!.ownerUserID + (isOwned ? ' (you)' : '')], ); } }, @@ -201,10 +297,10 @@ class _ChannelViewPageState extends State { future: _futureSubscribeKey.future, builder: (context, snapshot) { if (snapshot.hasData && snapshot.data != null) { - var text = 'TODO' + '\n' + widget.channel.channelID + '\n' + snapshot.data!; //TODO deeplink-y (also perhaps just bas64 everything together?) + var text = 'TODO' + '\n' + channel!.channelID + '\n' + snapshot.data!; //TODO deeplink-y (also perhaps just bas64 everything together?) return GestureDetector( onTap: () { - Share.share(text, subject: _displayNameOverride ?? widget.channel.displayName); + Share.share(text, subject: _displayNameOverride ?? channel!.displayName); }, child: Center( child: QrImageView( @@ -269,7 +365,7 @@ class _ChannelViewPageState extends State { context: context, icon: FontAwesomeIcons.solidInputText, title: 'DisplayName', - values: [_displayNameOverride ?? widget.channel.displayName], + values: [_displayNameOverride ?? channelPreview!.displayName], iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDisplayName)] : [], ); } else if (_editDisplayName == EditState.saving) { @@ -325,7 +421,7 @@ class _ChannelViewPageState extends State { context: context, icon: FontAwesomeIcons.solidInputPipe, title: 'Description', - values: [_descriptionNameOverride ?? widget.channel.descriptionName ?? ''], + values: [_descriptionNameOverride ?? channelPreview?.descriptionName ?? ''], iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDescriptionName)] : [], ); } else if (_editDescriptionName == EditState.saving) { @@ -361,7 +457,7 @@ class _ChannelViewPageState extends State { void _showEditDisplayName() { setState(() { - _ctrlDisplayName.text = _displayNameOverride ?? widget.channel.displayName; + _ctrlDisplayName.text = _displayNameOverride ?? channelPreview?.displayName ?? ''; _editDisplayName = EditState.editing; if (_editDescriptionName == EditState.editing) _editDescriptionName = EditState.none; }); @@ -377,7 +473,7 @@ class _ChannelViewPageState extends State { _editDisplayName = EditState.saving; }); - final newChannel = await APIClient.updateChannel(userAcc, widget.channel.channelID, displayName: newName); + final newChannel = await APIClient.updateChannel(userAcc, widget.channelID, displayName: newName); setState(() { _editDisplayName = EditState.none; @@ -393,7 +489,7 @@ class _ChannelViewPageState extends State { void _showEditDescriptionName() { setState(() { - _ctrlDescriptionName.text = _descriptionNameOverride ?? widget.channel.descriptionName ?? ''; + _ctrlDescriptionName.text = _descriptionNameOverride ?? channelPreview?.descriptionName ?? ''; _editDescriptionName = EditState.editing; if (_editDisplayName == EditState.editing) _editDisplayName = EditState.none; }); @@ -409,7 +505,7 @@ class _ChannelViewPageState extends State { _editDescriptionName = EditState.saving; }); - final newChannel = await APIClient.updateChannel(userAcc, widget.channel.channelID, descriptionName: newName); + final newChannel = await APIClient.updateChannel(userAcc, widget.channelID, descriptionName: newName); setState(() { _editDescriptionName = EditState.none; @@ -445,13 +541,13 @@ class _ChannelViewPageState extends State { } } - Future _getSubScribeKey(AppAuth auth) async { + Future _getSubscribeKey(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); - var channel = await APIClient.getChannel(auth, widget.channel.channelID); + var channel = await APIClient.getChannel(auth, widget.channelID); //await Future.delayed(const Duration(seconds: 10), () {}); @@ -467,7 +563,7 @@ class _ChannelViewPageState extends State { _incLoadingIndeterminateCounter(1); - var subs = await APIClient.getChannelSubscriptions(auth, widget.channel.channelID); + var subs = await APIClient.getChannelSubscriptions(auth, widget.channelID); var userMap = {for (var v in (await Future.wait(subs.map((e) => e.subscriberUserID).toSet().map((e) => APIClient.getUserPreview(auth, e)).toList()))) v.userID: v}; @@ -485,7 +581,7 @@ class _ChannelViewPageState extends State { _incLoadingIndeterminateCounter(1); - final owner = APIClient.getUserPreview(auth, widget.channel.ownerUserID); + final owner = APIClient.getUserPreview(auth, channelPreview!.ownerUserID); //await Future.delayed(const Duration(seconds: 10), () {}); diff --git a/flutter/lib/pages/message_view/message_view.dart b/flutter/lib/pages/message_view/message_view.dart index 2877051..bd3463f 100644 --- a/flutter/lib/pages/message_view/message_view.dart +++ b/flutter/lib/pages/message_view/message_view.dart @@ -10,8 +10,10 @@ import 'package:simplecloudnotifier/models/channel.dart'; import 'package:simplecloudnotifier/models/keytoken.dart'; import 'package:simplecloudnotifier/models/scn_message.dart'; import 'package:simplecloudnotifier/models/user.dart'; +import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:simplecloudnotifier/state/app_bar_state.dart'; +import 'package:simplecloudnotifier/utils/navi.dart'; import 'package:simplecloudnotifier/utils/toaster.dart'; import 'package:simplecloudnotifier/utils/ui.dart'; @@ -157,7 +159,11 @@ class _MessageViewPageState extends State { icon: FontAwesomeIcons.solidSnake, title: 'Channel', values: [message.channelID, channel?.displayName ?? message.channelInternalName], - mainAction: () => {/*TODO*/}, + mainAction: (channel != null) + ? () { + Navi.push(context, () => ChannelViewPage(channelID: channel.channelID, preloadedData: null, needsReload: null)); + } + : null, ), UI.metaCard( context: context,