channel_message_list
This commit is contained in:
parent
778451fa4c
commit
be7035978b
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,17 +3,20 @@ import 'package:flutter/material.dart';
|
|||||||
class HidableFAB extends StatelessWidget {
|
class HidableFAB extends StatelessWidget {
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
|
final Object heroTag;
|
||||||
|
|
||||||
const HidableFAB({
|
const HidableFAB({
|
||||||
super.key,
|
super.key,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
|
required this.heroTag,
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Visibility(
|
return Visibility(
|
||||||
visible: MediaQuery.viewInsetsOf(context).bottom == 0.0, // hide when keyboard is shown
|
visible: MediaQuery.viewInsetsOf(context).bottom == 0.0, // hide when keyboard is shown
|
||||||
child: FloatingActionButton(
|
child: FloatingActionButton(
|
||||||
|
heroTag: this.heroTag,
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(17))),
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(17))),
|
||||||
elevation: 2.0,
|
elevation: 2.0,
|
||||||
|
@ -70,6 +70,16 @@ class Channel extends HiveObject implements FieldDebuggable {
|
|||||||
('messagesSent', '${this.messagesSent}'),
|
('messagesSent', '${this.messagesSent}'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChannelPreview toPreview() {
|
||||||
|
return ChannelPreview(
|
||||||
|
channelID: this.channelID,
|
||||||
|
ownerUserID: this.ownerUserID,
|
||||||
|
internalName: this.internalName,
|
||||||
|
displayName: this.displayName,
|
||||||
|
descriptionName: this.descriptionName,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelWithSubscription {
|
class ChannelWithSubscription {
|
||||||
|
@ -76,6 +76,7 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
|
|||||||
bottomNavigationBar: _buildNavBar(context),
|
bottomNavigationBar: _buildNavBar(context),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||||
floatingActionButton: HidableFAB(
|
floatingActionButton: HidableFAB(
|
||||||
|
heroTag: 'fab_main',
|
||||||
onPressed: _onFABTapped,
|
onPressed: _onFABTapped,
|
||||||
icon: FontAwesomeIcons.solidPaperPlaneTop,
|
icon: FontAwesomeIcons.solidPaperPlaneTop,
|
||||||
),
|
),
|
||||||
|
@ -155,17 +155,17 @@ class _ChannelRootPageState extends State<ChannelRootPage> with RouteAware {
|
|||||||
channel: item.channel,
|
channel: item.channel,
|
||||||
subscription: item.subscription,
|
subscription: item.subscription,
|
||||||
onPressed: () {
|
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(
|
floatingActionButton: FloatingActionButton(
|
||||||
|
heroTag: 'fab_channel_list_qr',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
//TODO scan qr code to subscribe channel
|
//TODO scan qr code to subscribe channel
|
||||||
},
|
},
|
||||||
backgroundColor: ,
|
|
||||||
child: const Icon(FontAwesomeIcons.qrcode),
|
child: const Icon(FontAwesomeIcons.qrcode),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.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/channel.dart';
|
||||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||||
import 'package:simplecloudnotifier/models/subscription.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/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/scn_data_cache.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 {
|
class ChannelListItem extends StatefulWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
||||||
@ -56,7 +61,6 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||||
color: Theme.of(context).cardTheme.color,
|
color: Theme.of(context).cardTheme.color,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
splashColor: Theme.of(context).splashColor,
|
|
||||||
onTap: widget.onPressed,
|
onTap: widget.onPressed,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
105
flutter/lib/pages/channel_message_view/channel_message_view.dart
Normal file
105
flutter/lib/pages/channel_message_view/channel_message_view.dart
Normal 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));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:share_plus/share_plus.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/channel.dart';
|
||||||
import 'package:simplecloudnotifier/models/subscription.dart';
|
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||||
import 'package:simplecloudnotifier/models/user.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_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ChannelViewPage extends StatefulWidget {
|
class ChannelViewPage extends StatefulWidget {
|
||||||
const ChannelViewPage({
|
const ChannelViewPage({
|
||||||
required this.channel,
|
required this.channelID,
|
||||||
required this.subscription,
|
required this.preloadedData,
|
||||||
required this.needsReload,
|
required this.needsReload,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Channel channel;
|
final String channelID;
|
||||||
final Subscription? subscription;
|
final (Channel, Subscription?)? preloadedData;
|
||||||
|
|
||||||
final void Function()? needsReload;
|
final void Function()? needsReload;
|
||||||
|
|
||||||
@ -35,6 +36,8 @@ class ChannelViewPage extends StatefulWidget {
|
|||||||
|
|
||||||
enum EditState { none, editing, saving }
|
enum EditState { none, editing, saving }
|
||||||
|
|
||||||
|
enum ChannelViewPageInitState { loading, okay, error }
|
||||||
|
|
||||||
class _ChannelViewPageState extends State<ChannelViewPage> {
|
class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||||
late ImmediateFuture<String?> _futureSubscribeKey;
|
late ImmediateFuture<String?> _futureSubscribeKey;
|
||||||
late ImmediateFuture<List<(Subscription, UserPreview?)>> _futureSubscriptions;
|
late ImmediateFuture<List<(Subscription, UserPreview?)>> _futureSubscriptions;
|
||||||
@ -51,15 +54,58 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
EditState _editDescriptionName = EditState.none;
|
EditState _editDescriptionName = EditState.none;
|
||||||
String? _descriptionNameOverride = null;
|
String? _descriptionNameOverride = null;
|
||||||
|
|
||||||
|
ChannelPreview? channelPreview;
|
||||||
|
Channel? channel;
|
||||||
|
Subscription? subscription;
|
||||||
|
|
||||||
|
ChannelViewPageInitState loadingState = ChannelViewPageInitState.loading;
|
||||||
|
String errorMessage = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
_initStateAsync();
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void _initStateAsync() async {
|
||||||
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
if (widget.channel.ownerUserID == userAcc.userID) {
|
if (widget.preloadedData != null) {
|
||||||
if (widget.channel.subscribeKey != null) {
|
channelPreview = widget.preloadedData!.$1.toPreview();
|
||||||
_futureSubscribeKey = ImmediateFuture<String?>.ofValue(widget.channel.subscribeKey);
|
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<String?>.ofValue(this.channel!.subscribeKey);
|
||||||
} else {
|
} else {
|
||||||
_futureSubscribeKey = ImmediateFuture<String?>.ofFuture(_getSubScribeKey(userAcc));
|
_futureSubscribeKey = ImmediateFuture<String?>.ofFuture(_getSubscribeKey(userAcc));
|
||||||
}
|
}
|
||||||
_futureSubscriptions = ImmediateFuture<List<(Subscription, UserPreview?)>>.ofFuture(_listSubscriptions(userAcc));
|
_futureSubscriptions = ImmediateFuture<List<(Subscription, UserPreview?)>>.ofFuture(_listSubscriptions(userAcc));
|
||||||
} else {
|
} else {
|
||||||
@ -67,7 +113,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
_futureSubscriptions = ImmediateFuture<List<(Subscription, UserPreview?)>>.ofValue([]);
|
_futureSubscriptions = ImmediateFuture<List<(Subscription, UserPreview?)>>.ofValue([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.channel.ownerUserID == userAcc.userID) {
|
if (this.channelPreview!.ownerUserID == userAcc.userID) {
|
||||||
var cacheUser = userAcc.getUserOrNull();
|
var cacheUser = userAcc.getUserOrNull();
|
||||||
if (cacheUser != null) {
|
if (cacheUser != null) {
|
||||||
_futureOwner = ImmediateFuture<UserPreview>.ofValue(cacheUser.toPreview());
|
_futureOwner = ImmediateFuture<UserPreview>.ofValue(cacheUser.toPreview());
|
||||||
@ -75,10 +121,8 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(_getOwner(userAcc));
|
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(_getOwner(userAcc));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(APIClient.getUserPreview(userAcc, widget.channel.ownerUserID));
|
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(APIClient.getUserPreview(userAcc, this.channelPreview!.ownerUserID));
|
||||||
}
|
}
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -90,19 +134,30 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return SCNScaffold(
|
||||||
title: 'Channel',
|
title: 'Channel',
|
||||||
showSearch: false,
|
showSearch: false,
|
||||||
showShare: false,
|
showShare: false,
|
||||||
child: _buildChannelView(context),
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildChannelView(BuildContext context) {
|
Widget _buildOwnedChannelView(BuildContext context, Channel channel) {
|
||||||
final userAccUserID = context.select<AppAuth, String?>((v) => v.userID);
|
final isSubscribed = (subscription != null && subscription!.confirmed);
|
||||||
|
|
||||||
final isOwned = (widget.channel.ownerUserID == userAccUserID);
|
|
||||||
final isSubscribed = (widget.subscription != null && widget.subscription!.confirmed);
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -116,31 +171,33 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidIdCardClip,
|
icon: FontAwesomeIcons.solidIdCardClip,
|
||||||
title: 'ChannelID',
|
title: 'ChannelID',
|
||||||
values: [widget.channel.channelID],
|
values: [channel.channelID],
|
||||||
),
|
),
|
||||||
UI.metaCard(
|
UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidInputNumeric,
|
icon: FontAwesomeIcons.solidInputNumeric,
|
||||||
title: 'InternalName',
|
title: 'InternalName',
|
||||||
values: [widget.channel.internalName],
|
values: [channel.internalName],
|
||||||
),
|
),
|
||||||
_buildDisplayNameCard(context, isOwned),
|
_buildDisplayNameCard(context, true),
|
||||||
_buildDescriptionNameCard(context, isOwned),
|
_buildDescriptionNameCard(context, true),
|
||||||
UI.metaCard(
|
UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||||
title: 'Subscription (own)',
|
title: 'Subscription (own)',
|
||||||
values: [_formatSubscriptionStatus(widget.subscription)],
|
values: [_formatSubscriptionStatus(this.subscription)],
|
||||||
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
||||||
),
|
),
|
||||||
_buildForeignSubscriptions(context),
|
_buildForeignSubscriptions(context),
|
||||||
_buildOwnerCard(context, isOwned),
|
_buildOwnerCard(context, true),
|
||||||
UI.metaCard(
|
UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidEnvelope,
|
icon: FontAwesomeIcons.solidEnvelope,
|
||||||
title: 'Messages',
|
title: 'Messages',
|
||||||
values: [widget.channel.messagesSent.toString()],
|
values: [channel.messagesSent.toString()],
|
||||||
mainAction: () {/*TODO*/},
|
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) {
|
Widget _buildForeignSubscriptions(BuildContext context) {
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: _futureSubscriptions.future,
|
future: _futureSubscriptions.future,
|
||||||
@ -156,7 +252,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
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(
|
UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidDiagramSuccessor,
|
icon: FontAwesomeIcons.solidDiagramSuccessor,
|
||||||
@ -182,14 +278,14 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidUser,
|
icon: FontAwesomeIcons.solidUser,
|
||||||
title: 'Owner',
|
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 {
|
} else {
|
||||||
return UI.metaCard(
|
return UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidUser,
|
icon: FontAwesomeIcons.solidUser,
|
||||||
title: 'Owner',
|
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,
|
future: _futureSubscribeKey.future,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData && snapshot.data != null) {
|
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(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Share.share(text, subject: _displayNameOverride ?? widget.channel.displayName);
|
Share.share(text, subject: _displayNameOverride ?? channel!.displayName);
|
||||||
},
|
},
|
||||||
child: Center(
|
child: Center(
|
||||||
child: QrImageView(
|
child: QrImageView(
|
||||||
@ -269,7 +365,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidInputText,
|
icon: FontAwesomeIcons.solidInputText,
|
||||||
title: 'DisplayName',
|
title: 'DisplayName',
|
||||||
values: [_displayNameOverride ?? widget.channel.displayName],
|
values: [_displayNameOverride ?? channelPreview!.displayName],
|
||||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDisplayName)] : [],
|
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDisplayName)] : [],
|
||||||
);
|
);
|
||||||
} else if (_editDisplayName == EditState.saving) {
|
} else if (_editDisplayName == EditState.saving) {
|
||||||
@ -325,7 +421,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidInputPipe,
|
icon: FontAwesomeIcons.solidInputPipe,
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
values: [_descriptionNameOverride ?? widget.channel.descriptionName ?? ''],
|
values: [_descriptionNameOverride ?? channelPreview?.descriptionName ?? ''],
|
||||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDescriptionName)] : [],
|
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDescriptionName)] : [],
|
||||||
);
|
);
|
||||||
} else if (_editDescriptionName == EditState.saving) {
|
} else if (_editDescriptionName == EditState.saving) {
|
||||||
@ -361,7 +457,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
|
|
||||||
void _showEditDisplayName() {
|
void _showEditDisplayName() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_ctrlDisplayName.text = _displayNameOverride ?? widget.channel.displayName;
|
_ctrlDisplayName.text = _displayNameOverride ?? channelPreview?.displayName ?? '';
|
||||||
_editDisplayName = EditState.editing;
|
_editDisplayName = EditState.editing;
|
||||||
if (_editDescriptionName == EditState.editing) _editDescriptionName = EditState.none;
|
if (_editDescriptionName == EditState.editing) _editDescriptionName = EditState.none;
|
||||||
});
|
});
|
||||||
@ -377,7 +473,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
_editDisplayName = EditState.saving;
|
_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(() {
|
setState(() {
|
||||||
_editDisplayName = EditState.none;
|
_editDisplayName = EditState.none;
|
||||||
@ -393,7 +489,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
|
|
||||||
void _showEditDescriptionName() {
|
void _showEditDescriptionName() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_ctrlDescriptionName.text = _descriptionNameOverride ?? widget.channel.descriptionName ?? '';
|
_ctrlDescriptionName.text = _descriptionNameOverride ?? channelPreview?.descriptionName ?? '';
|
||||||
_editDescriptionName = EditState.editing;
|
_editDescriptionName = EditState.editing;
|
||||||
if (_editDisplayName == EditState.editing) _editDisplayName = EditState.none;
|
if (_editDisplayName == EditState.editing) _editDisplayName = EditState.none;
|
||||||
});
|
});
|
||||||
@ -409,7 +505,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
_editDescriptionName = EditState.saving;
|
_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(() {
|
setState(() {
|
||||||
_editDescriptionName = EditState.none;
|
_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 {
|
try {
|
||||||
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
|
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
|
||||||
|
|
||||||
_incLoadingIndeterminateCounter(1);
|
_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), () {});
|
//await Future.delayed(const Duration(seconds: 10), () {});
|
||||||
|
|
||||||
@ -467,7 +563,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
|
|
||||||
_incLoadingIndeterminateCounter(1);
|
_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};
|
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);
|
_incLoadingIndeterminateCounter(1);
|
||||||
|
|
||||||
final owner = APIClient.getUserPreview(auth, widget.channel.ownerUserID);
|
final owner = APIClient.getUserPreview(auth, channelPreview!.ownerUserID);
|
||||||
|
|
||||||
//await Future.delayed(const Duration(seconds: 10), () {});
|
//await Future.delayed(const Duration(seconds: 10), () {});
|
||||||
|
|
||||||
|
@ -10,8 +10,10 @@ import 'package:simplecloudnotifier/models/channel.dart';
|
|||||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||||
import 'package:simplecloudnotifier/models/user.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_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.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/toaster.dart';
|
||||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
|
||||||
@ -157,7 +159,11 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
icon: FontAwesomeIcons.solidSnake,
|
icon: FontAwesomeIcons.solidSnake,
|
||||||
title: 'Channel',
|
title: 'Channel',
|
||||||
values: [message.channelID, channel?.displayName ?? message.channelInternalName],
|
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(
|
UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
|
Loading…
Reference in New Issue
Block a user