edit displayName/descriptionName of channel
This commit is contained in:
parent
1f9b65652d
commit
89d1e0f641
@ -7,6 +7,7 @@ import 'package:simplecloudnotifier/models/client.dart';
|
|||||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
import 'package:simplecloudnotifier/models/keytoken.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/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||||
@ -211,6 +212,20 @@ class APIClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<ChannelWithSubscription> updateChannel(AppAuth auth, String cid, {String? displayName, String? descriptionName}) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'updateChannel',
|
||||||
|
method: 'PATCH',
|
||||||
|
relURL: 'users/${auth.getUserID()}/channels/${cid}',
|
||||||
|
jsonBody: {
|
||||||
|
if (displayName != null) 'display_name': displayName,
|
||||||
|
if (descriptionName != null) 'description_name': descriptionName,
|
||||||
|
},
|
||||||
|
fn: ChannelWithSubscription.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<(String, List<SCNMessage>)> getMessageList(TokenSource auth, String pageToken, {int? pageSize, List<String>? channelIDs}) async {
|
static Future<(String, List<SCNMessage>)> getMessageList(TokenSource auth, String pageToken, {int? pageSize, List<String>? channelIDs}) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getMessageList',
|
name: 'getMessageList',
|
||||||
|
@ -19,11 +19,13 @@ class ChannelRootPage extends StatefulWidget {
|
|||||||
State<ChannelRootPage> createState() => _ChannelRootPageState();
|
State<ChannelRootPage> createState() => _ChannelRootPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelRootPageState extends State<ChannelRootPage> {
|
class _ChannelRootPageState extends State<ChannelRootPage> with RouteAware {
|
||||||
final PagingController<int, ChannelWithSubscription> _pagingController = PagingController.fromValue(PagingState(nextPageKey: null, itemList: [], error: null), firstPageKey: 0);
|
final PagingController<int, ChannelWithSubscription> _pagingController = PagingController.fromValue(PagingState(nextPageKey: null, itemList: [], error: null), firstPageKey: 0);
|
||||||
|
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
|
|
||||||
|
bool _reloadEnqueued = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -33,10 +35,17 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
if (widget.isVisiblePage && !_isInitialized) _realInitState();
|
if (widget.isVisiblePage && !_isInitialized) _realInitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
Navi.modalRouteObserver.subscribe(this, ModalRoute.of(context)!);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
ApplicationLog.debug('ChannelRootPage::dispose');
|
ApplicationLog.debug('ChannelRootPage::dispose');
|
||||||
_pagingController.dispose();
|
_pagingController.dispose();
|
||||||
|
Navi.modalRouteObserver.unsubscribe(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +62,24 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPush() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPopNext() {
|
||||||
|
if (_reloadEnqueued) {
|
||||||
|
ApplicationLog.debug('[ChannelList::RouteObserver] --> didPopNext (will background-refresh) (_reloadEnqueued == true)');
|
||||||
|
() async {
|
||||||
|
_reloadEnqueued = false;
|
||||||
|
AppBarState().setLoadingIndeterminate(true);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500)); // prevents flutter bug where the whole process crashes ?!?
|
||||||
|
await _backgroundRefresh();
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _realInitState() {
|
void _realInitState() {
|
||||||
ApplicationLog.debug('ChannelRootPage::_realInitState');
|
ApplicationLog.debug('ChannelRootPage::_realInitState');
|
||||||
_pagingController.refresh();
|
_pagingController.refresh();
|
||||||
@ -100,9 +127,13 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
|
|
||||||
items.sort((a, b) => -1 * (a.channel.timestampLastSent ?? '').compareTo(b.channel.timestampLastSent ?? ''));
|
items.sort((a, b) => -1 * (a.channel.timestampLastSent ?? '').compareTo(b.channel.timestampLastSent ?? ''));
|
||||||
|
|
||||||
|
setState(() {
|
||||||
_pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null);
|
_pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null);
|
||||||
|
});
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
|
setState(() {
|
||||||
_pagingController.error = exc.toString();
|
_pagingController.error = exc.toString();
|
||||||
|
});
|
||||||
ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace);
|
||||||
} finally {
|
} finally {
|
||||||
AppBarState().setLoadingIndeterminate(false);
|
AppBarState().setLoadingIndeterminate(false);
|
||||||
@ -122,11 +153,15 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
channel: item.channel,
|
channel: item.channel,
|
||||||
subscription: item.subscription,
|
subscription: item.subscription,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navi.push(context, () => ChannelViewPage(channel: item.channel, subscription: item.subscription));
|
Navi.push(context, () => ChannelViewPage(channel: item.channel, subscription: item.subscription, needsReload: _enqueueReload));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _enqueueReload() {
|
||||||
|
_reloadEnqueued = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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';
|
||||||
@ -9,7 +10,9 @@ import 'package:simplecloudnotifier/models/subscription.dart';
|
|||||||
import 'package:simplecloudnotifier/models/user.dart';
|
import 'package:simplecloudnotifier/models/user.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/types/immediate_future.dart';
|
import 'package:simplecloudnotifier/types/immediate_future.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';
|
||||||
|
|
||||||
@ -17,23 +20,37 @@ class ChannelViewPage extends StatefulWidget {
|
|||||||
const ChannelViewPage({
|
const ChannelViewPage({
|
||||||
required this.channel,
|
required this.channel,
|
||||||
required this.subscription,
|
required this.subscription,
|
||||||
|
required this.needsReload,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
final Subscription? subscription;
|
final Subscription? subscription;
|
||||||
|
|
||||||
|
final void Function()? needsReload;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChannelViewPage> createState() => _ChannelViewPageState();
|
State<ChannelViewPage> createState() => _ChannelViewPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum EditState { none, editing, saving }
|
||||||
|
|
||||||
class _ChannelViewPageState extends State<ChannelViewPage> {
|
class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||||
late ImmediateFuture<String?> _futureSubscribeKey;
|
late ImmediateFuture<String?> _futureSubscribeKey;
|
||||||
late ImmediateFuture<List<Subscription>> _futureSubscriptions;
|
late ImmediateFuture<List<Subscription>> _futureSubscriptions;
|
||||||
late ImmediateFuture<UserPreview> _futureOwner;
|
late ImmediateFuture<UserPreview> _futureOwner;
|
||||||
|
|
||||||
|
final TextEditingController _ctrlDisplayName = TextEditingController();
|
||||||
|
final TextEditingController _ctrlDescriptionName = TextEditingController();
|
||||||
|
|
||||||
int _loadingIndeterminateCounter = 0;
|
int _loadingIndeterminateCounter = 0;
|
||||||
|
|
||||||
|
EditState _editDisplayName = EditState.none;
|
||||||
|
String? _displayNameOverride = null;
|
||||||
|
|
||||||
|
EditState _editDescriptionName = EditState.none;
|
||||||
|
String? _descriptionNameOverride = null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
@ -66,6 +83,8 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_ctrlDisplayName.dispose();
|
||||||
|
_ctrlDescriptionName.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,13 +124,8 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
title: 'InternalName',
|
title: 'InternalName',
|
||||||
values: [widget.channel.internalName],
|
values: [widget.channel.internalName],
|
||||||
),
|
),
|
||||||
UI.metaCard(
|
_buildDisplayNameCard(context, isOwned),
|
||||||
context: context,
|
_buildDescriptionNameCard(context, isOwned),
|
||||||
icon: FontAwesomeIcons.solidInputText,
|
|
||||||
title: 'DisplayName',
|
|
||||||
values: [widget.channel.displayName],
|
|
||||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _rename)] : [],
|
|
||||||
),
|
|
||||||
UI.metaCard(
|
UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||||
@ -190,7 +204,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
var text = 'TODO' + '\n' + widget.channel.channelID + '\n' + snapshot.data!; //TODO deeplink-y (also perhaps just bas64 everything together?)
|
var text = 'TODO' + '\n' + widget.channel.channelID + '\n' + snapshot.data!; //TODO deeplink-y (also perhaps just bas64 everything together?)
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Share.share(text, subject: widget.channel.displayName);
|
Share.share(text, subject: _displayNameOverride ?? widget.channel.displayName);
|
||||||
},
|
},
|
||||||
child: Center(
|
child: Center(
|
||||||
child: QrImageView(
|
child: QrImageView(
|
||||||
@ -225,8 +239,116 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _rename() {
|
Widget _buildDisplayNameCard(BuildContext context, bool isOwned) {
|
||||||
//TODO
|
if (_editDisplayName == EditState.editing) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||||
|
child: UI.box(
|
||||||
|
context: context,
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(child: Center(child: FaIcon(FontAwesomeIcons.solidInputText, size: 18)), height: 43),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: _ctrlDisplayName,
|
||||||
|
decoration: new InputDecoration.collapsed(hintText: 'DisplayName'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
IconButton(icon: FaIcon(FontAwesomeIcons.solidFloppyDisk), onPressed: _saveDisplayName),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (_editDisplayName == EditState.none) {
|
||||||
|
return UI.metaCard(
|
||||||
|
context: context,
|
||||||
|
icon: FontAwesomeIcons.solidInputText,
|
||||||
|
title: 'DisplayName',
|
||||||
|
values: [_displayNameOverride ?? widget.channel.displayName],
|
||||||
|
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDisplayName)] : [],
|
||||||
|
);
|
||||||
|
} else if (_editDisplayName == EditState.saving) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||||
|
child: UI.box(
|
||||||
|
context: context,
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(child: Center(child: FaIcon(FontAwesomeIcons.solidInputText, size: 18)), height: 43),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(child: SizedBox()),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Padding(padding: const EdgeInsets.all(8.0), child: SizedBox(width: 18, height: 18, child: CircularProgressIndicator())),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw 'Invalid EditDisplayNameState: $_editDisplayName';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDescriptionNameCard(BuildContext context, bool isOwned) {
|
||||||
|
if (_editDescriptionName == EditState.editing) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||||
|
child: UI.box(
|
||||||
|
context: context,
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(child: Center(child: FaIcon(FontAwesomeIcons.solidInputPipe, size: 18)), height: 43),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: _ctrlDescriptionName,
|
||||||
|
decoration: new InputDecoration.collapsed(hintText: 'Description'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
IconButton(icon: FaIcon(FontAwesomeIcons.solidFloppyDisk), onPressed: _saveDescriptionName),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (_editDescriptionName == EditState.none) {
|
||||||
|
return UI.metaCard(
|
||||||
|
context: context,
|
||||||
|
icon: FontAwesomeIcons.solidInputPipe,
|
||||||
|
title: 'Description',
|
||||||
|
values: [_descriptionNameOverride ?? widget.channel.descriptionName ?? ''],
|
||||||
|
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _showEditDescriptionName)] : [],
|
||||||
|
);
|
||||||
|
} else if (_editDescriptionName == EditState.saving) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||||
|
child: UI.box(
|
||||||
|
context: context,
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(child: Center(child: FaIcon(FontAwesomeIcons.solidInputPipe, size: 18)), height: 43),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(child: SizedBox()),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Padding(padding: const EdgeInsets.all(8.0), child: SizedBox(width: 18, height: 18, child: CircularProgressIndicator())),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw 'Invalid EditDescriptionNameState: $_editDescriptionName';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _subscribe() {
|
void _subscribe() {
|
||||||
@ -237,6 +359,70 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showEditDisplayName() {
|
||||||
|
setState(() {
|
||||||
|
_ctrlDisplayName.text = _displayNameOverride ?? widget.channel.displayName;
|
||||||
|
_editDisplayName = EditState.editing;
|
||||||
|
if (_editDescriptionName == EditState.editing) _editDescriptionName = EditState.none;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveDisplayName() async {
|
||||||
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
|
final newName = _ctrlDisplayName.text;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
_editDisplayName = EditState.saving;
|
||||||
|
});
|
||||||
|
|
||||||
|
final newChannel = await APIClient.updateChannel(userAcc, widget.channel.channelID, displayName: newName);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_editDisplayName = EditState.none;
|
||||||
|
_displayNameOverride = newChannel.channel.displayName;
|
||||||
|
});
|
||||||
|
|
||||||
|
widget.needsReload?.call();
|
||||||
|
} catch (exc, trace) {
|
||||||
|
ApplicationLog.error('Failed to save DisplayName: ' + exc.toString(), trace: trace);
|
||||||
|
Toaster.error("Error", 'Failed to save DisplayName');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showEditDescriptionName() {
|
||||||
|
setState(() {
|
||||||
|
_ctrlDescriptionName.text = _descriptionNameOverride ?? widget.channel.descriptionName ?? '';
|
||||||
|
_editDescriptionName = EditState.editing;
|
||||||
|
if (_editDisplayName == EditState.editing) _editDisplayName = EditState.none;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveDescriptionName() async {
|
||||||
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
|
final newName = _ctrlDescriptionName.text;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
_editDescriptionName = EditState.saving;
|
||||||
|
});
|
||||||
|
|
||||||
|
final newChannel = await APIClient.updateChannel(userAcc, widget.channel.channelID, descriptionName: newName);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_editDescriptionName = EditState.none;
|
||||||
|
_descriptionNameOverride = newChannel.channel.descriptionName ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
|
widget.needsReload?.call();
|
||||||
|
} catch (exc, trace) {
|
||||||
|
ApplicationLog.error('Failed to save DescriptionName: ' + exc.toString(), trace: trace);
|
||||||
|
Toaster.error("Error", 'Failed to save DescriptionName');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _cancelForeignSubscription(Subscription sub) {
|
void _cancelForeignSubscription(Subscription sub) {
|
||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user