channel_message_list

This commit is contained in:
Mike Schwörer 2024-07-13 00:11:13 +02:00
parent 778451fa4c
commit be7035978b
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
9 changed files with 281 additions and 127 deletions

View File

@ -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<IconData> icons;
final ValueChanged<int> onIconTapped;
@override
State createState() => FabWithIconsState();
}
class FabWithIconsState extends State<FabWithIcons> 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);
}
}

View File

@ -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,

View File

@ -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 {

View File

@ -76,6 +76,7 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
bottomNavigationBar: _buildNavBar(context),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: HidableFAB(
heroTag: 'fab_main',
onPressed: _onFABTapped,
icon: FontAwesomeIcons.solidPaperPlaneTop,
),

View File

@ -155,17 +155,17 @@ class _ChannelRootPageState extends State<ChannelRootPage> 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),
),
);

View File

@ -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<ChannelListItem> {
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<ChannelListItem> {
],
),
),
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),
),
),
],
),
),

View File

@ -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<ChannelMessageViewPage> createState() => _ChannelMessageViewPageState();
}
class _ChannelMessageViewPageState extends State<ChannelMessageViewPage> {
PagingController<String, SCNMessage> _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<void> _fetchPage(String thisPageToken) async {
final acc = Provider.of<AppAuth>(context, listen: false);
final cfg = Provider.of<AppSettings>(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<String, SCNMessage>(
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<SCNMessage>(
itemBuilder: (context, item, index) => MessageListItem(
message: item,
allChannels: {this.widget.channel.channelID: this.widget.channel},
onPressed: () {
Navi.push(context, () => MessageViewPage(message: item));
},
),
),
),
),
);
}
}

View File

@ -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<ChannelViewPage> {
late ImmediateFuture<String?> _futureSubscribeKey;
late ImmediateFuture<List<(Subscription, UserPreview?)>> _futureSubscriptions;
@ -51,15 +54,58 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
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<AppAuth>(context, listen: false);
if (widget.channel.ownerUserID == userAcc.userID) {
if (widget.channel.subscribeKey != null) {
_futureSubscribeKey = ImmediateFuture<String?>.ofValue(widget.channel.subscribeKey);
if (widget.preloadedData != null) {
channelPreview = widget.preloadedData!.$1.toPreview();
channel = widget.preloadedData!.$1;
subscription = widget.preloadedData!.$2;
} else {
_futureSubscribeKey = ImmediateFuture<String?>.ofFuture(_getSubScribeKey(userAcc));
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<String?>.ofValue(this.channel!.subscribeKey);
} else {
_futureSubscribeKey = ImmediateFuture<String?>.ofFuture(_getSubscribeKey(userAcc));
}
_futureSubscriptions = ImmediateFuture<List<(Subscription, UserPreview?)>>.ofFuture(_listSubscriptions(userAcc));
} else {
@ -67,7 +113,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
_futureSubscriptions = ImmediateFuture<List<(Subscription, UserPreview?)>>.ofValue([]);
}
if (widget.channel.ownerUserID == userAcc.userID) {
if (this.channelPreview!.ownerUserID == userAcc.userID) {
var cacheUser = userAcc.getUserOrNull();
if (cacheUser != null) {
_futureOwner = ImmediateFuture<UserPreview>.ofValue(cacheUser.toPreview());
@ -75,10 +121,8 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(_getOwner(userAcc));
}
} else {
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(APIClient.getUserPreview(userAcc, widget.channel.ownerUserID));
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(APIClient.getUserPreview(userAcc, this.channelPreview!.ownerUserID));
}
super.initState();
}
@override
@ -90,19 +134,30 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
@override
Widget build(BuildContext context) {
final userAcc = Provider.of<AppAuth>(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<AppAuth, String?>((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<ChannelViewPage> {
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<ChannelViewPage> {
);
}
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<ChannelViewPage> {
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<ChannelViewPage> {
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<ChannelViewPage> {
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<ChannelViewPage> {
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<ChannelViewPage> {
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<ChannelViewPage> {
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<ChannelViewPage> {
_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<ChannelViewPage> {
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<ChannelViewPage> {
_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<ChannelViewPage> {
}
}
Future<String?> _getSubScribeKey(AppAuth auth) async {
Future<String?> _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<ChannelViewPage> {
_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<ChannelViewPage> {
_incLoadingIndeterminateCounter(1);
final owner = APIClient.getUserPreview(auth, widget.channel.ownerUserID);
final owner = APIClient.getUserPreview(auth, channelPreview!.ownerUserID);
//await Future.delayed(const Duration(seconds: 10), () {});

View File

@ -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<MessageViewPage> {
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,