Subscribe/unsubscribe from channels
This commit is contained in:
parent
9b2e429d3d
commit
1cf14e65a9
@ -5,6 +5,7 @@ import 'package:simplecloudnotifier/api/api_exception.dart';
|
|||||||
import 'package:simplecloudnotifier/models/api_error.dart';
|
import 'package:simplecloudnotifier/models/api_error.dart';
|
||||||
import 'package:simplecloudnotifier/models/client.dart';
|
import 'package:simplecloudnotifier/models/client.dart';
|
||||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/sender_name_statistics.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/app_auth.dart';
|
||||||
@ -101,19 +102,21 @@ class APIClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (responseStatusCode != 200) {
|
if (responseStatusCode != 200) {
|
||||||
try {
|
APIError apierr;
|
||||||
final apierr = APIError.fromJson(jsonDecode(responseBody) as Map<String, dynamic>);
|
|
||||||
|
|
||||||
RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr);
|
try {
|
||||||
Toaster.error("Error", 'Request "${name}" failed');
|
apierr = APIError.fromJson(jsonDecode(responseBody) as Map<String, dynamic>);
|
||||||
throw APIException(responseStatusCode, apierr.error, apierr.errhighlight, apierr.message);
|
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.warn('Failed to decode api response as error-object', additional: exc.toString() + "\nBody:\n" + responseBody, trace: trace);
|
ApplicationLog.warn('Failed to decode api response as error-object', additional: exc.toString() + "\nBody:\n" + responseBody, trace: trace);
|
||||||
|
|
||||||
|
RequestLog.addRequestErrorStatuscode(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
|
||||||
|
Toaster.error("Error", 'Request "${name}" failed');
|
||||||
|
throw Exception('API request failed with status code ${responseStatusCode}');
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestLog.addRequestErrorStatuscode(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
|
RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr);
|
||||||
Toaster.error("Error", 'Request "${name}" failed');
|
Toaster.error("Error", apierr.message);
|
||||||
throw Exception('API request failed with status code ${responseStatusCode}');
|
throw APIException(responseStatusCode, apierr.error, apierr.errhighlight, apierr.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -281,6 +284,20 @@ class APIClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<(String, List<SCNMessage>)> getChannelMessageList(TokenSource auth, String cid, String pageToken, {int? pageSize}) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'getChannelMessageList',
|
||||||
|
method: 'GET',
|
||||||
|
relURL: 'users/${auth.getUserID()}/channels/${cid}/messages',
|
||||||
|
query: {
|
||||||
|
'next_page_token': [pageToken],
|
||||||
|
if (pageSize != null) 'page_size': [pageSize.toString()],
|
||||||
|
},
|
||||||
|
fn: (json) => SCNMessage.fromPaginatedJsonArray(json, 'messages', 'next_page_token'),
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<List<Subscription>> getSubscriptionList(TokenSource auth) async {
|
static Future<List<Subscription>> getSubscriptionList(TokenSource auth) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getSubscriptionList',
|
name: 'getSubscriptionList',
|
||||||
@ -369,7 +386,62 @@ class APIClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<String>> getSenderNameList(AppAuth userAcc) {
|
static Future<List<SenderNameStatistics>> getSenderNameList(TokenSource auth) async {
|
||||||
return Future.value(['TODO']); //TODO
|
return await _request(
|
||||||
|
name: 'getSenderNameList',
|
||||||
|
method: 'GET',
|
||||||
|
relURL: 'users/${auth.getUserID()}/sender-names',
|
||||||
|
fn: (json) => SenderNameStatistics.fromJsonArray(json['sender_names'] as List<dynamic>),
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Subscription> subscribeToChannelbyID(TokenSource auth, String channelID) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'subscribeToChannelbyID',
|
||||||
|
method: 'POST',
|
||||||
|
relURL: 'users/${auth.getUserID()}/subscriptions',
|
||||||
|
jsonBody: {
|
||||||
|
'channel_id': channelID,
|
||||||
|
},
|
||||||
|
fn: Subscription.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Subscription> deleteSubscription(TokenSource auth, String channelID, String subID) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'deleteSubscription',
|
||||||
|
method: 'DELETE',
|
||||||
|
relURL: 'users/${auth.getUserID()}/subscriptions/${subID}',
|
||||||
|
fn: Subscription.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Subscription> confirmSubscription(TokenSource auth, String channelID, String subID) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'confirmSubscription',
|
||||||
|
method: 'PATCH',
|
||||||
|
relURL: 'users/${auth.getUserID()}/subscriptions/${subID}',
|
||||||
|
jsonBody: {
|
||||||
|
'confirmed': true,
|
||||||
|
},
|
||||||
|
fn: Subscription.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Subscription> unconfirmSubscription(TokenSource auth, String channelID, String subID) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'unconfirmSubscription',
|
||||||
|
method: 'PATCH',
|
||||||
|
relURL: 'users/${auth.getUserID()}/subscriptions/${subID}',
|
||||||
|
jsonBody: {
|
||||||
|
'confirmed': false,
|
||||||
|
},
|
||||||
|
fn: Subscription.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
class APIException implements Exception {
|
class APIException implements Exception {
|
||||||
final int httpStatus;
|
final int httpStatus;
|
||||||
final int error;
|
final int error;
|
||||||
final String errHighlight;
|
final int errHighlight;
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
APIException(this.httpStatus, this.error, this.errHighlight, this.message);
|
APIException(this.httpStatus, this.error, this.errHighlight, this.message);
|
||||||
|
@ -25,7 +25,7 @@ class _FilterModalSendernameState extends State<FilterModalSendername> {
|
|||||||
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
|
|
||||||
final senders = await APIClient.getSenderNameList(userAcc);
|
final senders = (await APIClient.getSenderNameList(userAcc)).map((p) => p.name).toList();
|
||||||
|
|
||||||
return senders;
|
return senders;
|
||||||
}());
|
}());
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
class APIError {
|
class APIError {
|
||||||
final bool success;
|
final bool success;
|
||||||
final int error;
|
final int error;
|
||||||
final String errhighlight;
|
final int errhighlight;
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
static final MISSING_UID = 1101;
|
static final MISSING_UID = 1101;
|
||||||
@ -67,7 +67,7 @@ class APIError {
|
|||||||
return APIError(
|
return APIError(
|
||||||
success: json['success'] as bool,
|
success: json['success'] as bool,
|
||||||
error: (json['error'] as num).toInt(),
|
error: (json['error'] as num).toInt(),
|
||||||
errhighlight: json['errhighlight'] as String,
|
errhighlight: (json['errhighlight'] as num).toInt(),
|
||||||
message: json['message'] as String,
|
message: json['message'] as String,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
26
flutter/lib/models/sender_name_statistics.dart
Normal file
26
flutter/lib/models/sender_name_statistics.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
class SenderNameStatistics {
|
||||||
|
final String name;
|
||||||
|
final String ts_last;
|
||||||
|
final String ts_first;
|
||||||
|
final int count;
|
||||||
|
|
||||||
|
const SenderNameStatistics({
|
||||||
|
required this.name,
|
||||||
|
required this.ts_last,
|
||||||
|
required this.ts_first,
|
||||||
|
required this.count,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SenderNameStatistics.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SenderNameStatistics(
|
||||||
|
name: json['name'] as String,
|
||||||
|
ts_last: json['ts_last'] as String,
|
||||||
|
ts_first: json['ts_first'] as String,
|
||||||
|
count: json['count'] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SenderNameStatistics> fromJsonArray(List<dynamic> jsonArr) {
|
||||||
|
return jsonArr.map<SenderNameStatistics>((e) => SenderNameStatistics.fromJson(e as Map<String, dynamic>)).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
import 'package:simplecloudnotifier/models/user.dart';
|
import 'package:simplecloudnotifier/models/user.dart';
|
||||||
import 'package:simplecloudnotifier/pages/account/login.dart';
|
import 'package:simplecloudnotifier/pages/account/login.dart';
|
||||||
|
import 'package:simplecloudnotifier/pages/channel_list/channel_list_extended.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/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
@ -32,6 +33,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
late ImmediateFuture<int>? futureKeyCount;
|
late ImmediateFuture<int>? futureKeyCount;
|
||||||
late ImmediateFuture<int>? futureChannelAllCount;
|
late ImmediateFuture<int>? futureChannelAllCount;
|
||||||
late ImmediateFuture<int>? futureChannelSubscribedCount;
|
late ImmediateFuture<int>? futureChannelSubscribedCount;
|
||||||
|
late ImmediateFuture<int>? futureSenderNamesCount;
|
||||||
late ImmediateFuture<User>? futureUser;
|
late ImmediateFuture<User>? futureUser;
|
||||||
|
|
||||||
late AppAuth userAcc;
|
late AppAuth userAcc;
|
||||||
@ -87,6 +89,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
futureKeyCount = null;
|
futureKeyCount = null;
|
||||||
futureChannelAllCount = null;
|
futureChannelAllCount = null;
|
||||||
futureChannelSubscribedCount = null;
|
futureChannelSubscribedCount = null;
|
||||||
|
futureSenderNamesCount = null;
|
||||||
|
|
||||||
if (userAcc.isAuth()) {
|
if (userAcc.isAuth()) {
|
||||||
futureChannelAllCount = ImmediateFuture.ofFuture(() async {
|
futureChannelAllCount = ImmediateFuture.ofFuture(() async {
|
||||||
@ -119,6 +122,12 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
return keys.length;
|
return keys.length;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
futureSenderNamesCount = ImmediateFuture.ofFuture(() async {
|
||||||
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
|
final senders = (await APIClient.getSenderNameList(userAcc)).map((p) => p.name).toList();
|
||||||
|
return senders.length;
|
||||||
|
}());
|
||||||
|
|
||||||
futureUser = ImmediateFuture.ofFuture(userAcc.loadUser(force: false));
|
futureUser = ImmediateFuture.ofFuture(userAcc.loadUser(force: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,6 +146,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
final subs = await APIClient.getSubscriptionList(userAcc);
|
final subs = await APIClient.getSubscriptionList(userAcc);
|
||||||
final clients = await APIClient.getClientList(userAcc);
|
final clients = await APIClient.getClientList(userAcc);
|
||||||
final keys = await APIClient.getKeyTokenList(userAcc);
|
final keys = await APIClient.getKeyTokenList(userAcc);
|
||||||
|
final senderNames = await APIClient.getSenderNameList(userAcc);
|
||||||
final user = await userAcc.loadUser(force: true);
|
final user = await userAcc.loadUser(force: true);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -145,6 +155,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
futureSubscriptionCount = ImmediateFuture.ofValue(subs.length);
|
futureSubscriptionCount = ImmediateFuture.ofValue(subs.length);
|
||||||
futureClientCount = ImmediateFuture.ofValue(clients.length);
|
futureClientCount = ImmediateFuture.ofValue(clients.length);
|
||||||
futureKeyCount = ImmediateFuture.ofValue(keys.length);
|
futureKeyCount = ImmediateFuture.ofValue(keys.length);
|
||||||
|
futureSenderNamesCount = ImmediateFuture.ofValue(senderNames.length);
|
||||||
futureUser = ImmediateFuture.ofValue(user);
|
futureUser = ImmediateFuture.ofValue(user);
|
||||||
});
|
});
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
@ -368,7 +379,10 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
_buildNumberCard(context, 'Subscriptions', futureSubscriptionCount, () {/*TODO*/}),
|
_buildNumberCard(context, 'Subscriptions', futureSubscriptionCount, () {/*TODO*/}),
|
||||||
_buildNumberCard(context, 'Clients', futureClientCount, () {/*TODO*/}),
|
_buildNumberCard(context, 'Clients', futureClientCount, () {/*TODO*/}),
|
||||||
_buildNumberCard(context, 'Keys', futureKeyCount, () {/*TODO*/}),
|
_buildNumberCard(context, 'Keys', futureKeyCount, () {/*TODO*/}),
|
||||||
_buildNumberCard(context, 'Channels', futureChannelSubscribedCount, () {/*TODO*/}),
|
_buildNumberCard(context, 'Channels', futureChannelSubscribedCount, () {
|
||||||
|
Navi.push(context, () => ChannelListExtendedPage());
|
||||||
|
}),
|
||||||
|
_buildNumberCard(context, 'Sender', futureSenderNamesCount, () {/*TODO*/}),
|
||||||
UI.buttonCard(
|
UI.buttonCard(
|
||||||
context: context,
|
context: context,
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
|
@ -4,7 +4,6 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
import 'package:simplecloudnotifier/models/channel.dart';
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
import 'package:simplecloudnotifier/pages/channel_view/channel_view.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/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
@ -154,8 +153,13 @@ class _ChannelRootPageState extends State<ChannelRootPage> with RouteAware {
|
|||||||
itemBuilder: (context, item, index) => ChannelListItem(
|
itemBuilder: (context, item, index) => ChannelListItem(
|
||||||
channel: item.channel,
|
channel: item.channel,
|
||||||
subscription: item.subscription,
|
subscription: item.subscription,
|
||||||
onPressed: () {
|
mode: ChannelListItemMode.Messages,
|
||||||
Navi.push(context, () => ChannelViewPage(channelID: item.channel.channelID, preloadedData: (item.channel, item.subscription), needsReload: _enqueueReload));
|
onChannelListReloadTrigger: _enqueueReload,
|
||||||
|
onSubscriptionChanged: (channelID, subscription) {
|
||||||
|
setState(() {
|
||||||
|
final idx = _pagingController.itemList?.indexWhere((p) => p.channel.channelID == channelID);
|
||||||
|
if (idx != null && idx >= 0) _pagingController.itemList![idx] = ChannelWithSubscription(channel: _pagingController.itemList![idx].channel, subscription: subscription);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
151
flutter/lib/pages/channel_list/channel_list_extended.dart
Normal file
151
flutter/lib/pages/channel_list/channel_list_extended.dart
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
|
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
import 'package:simplecloudnotifier/pages/channel_list/channel_list_item.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
|
|
||||||
|
class ChannelListExtendedPage extends StatefulWidget {
|
||||||
|
const ChannelListExtendedPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChannelListExtendedPage> createState() => _ChannelListExtendedPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelListExtendedPageState extends State<ChannelListExtendedPage> with RouteAware {
|
||||||
|
final PagingController<int, ChannelWithSubscription> _pagingController = PagingController.fromValue(PagingState(nextPageKey: null, itemList: [], error: null), firstPageKey: 0);
|
||||||
|
|
||||||
|
bool _reloadEnqueued = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_pagingController.addPageRequestListener(_fetchPage);
|
||||||
|
|
||||||
|
_pagingController.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
Navi.modalRouteObserver.subscribe(this, ModalRoute.of(context)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
ApplicationLog.debug('ChannelRootPage::dispose');
|
||||||
|
_pagingController.dispose();
|
||||||
|
Navi.modalRouteObserver.unsubscribe(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPopNext() {
|
||||||
|
if (_reloadEnqueued) {
|
||||||
|
ApplicationLog.debug('[ChannelList::RouteObserver] --> didPopNext (will background-refresh) (_reloadEnqueued == true)');
|
||||||
|
() async {
|
||||||
|
_reloadEnqueued = false;
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500), () {}); // prevents flutter bug where the whole process crashes ?!?
|
||||||
|
await _backgroundRefresh();
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchPage(int pageKey) async {
|
||||||
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
|
ApplicationLog.debug('Start ChannelList::_pagingController::_fetchPage [ ${pageKey} ]');
|
||||||
|
|
||||||
|
if (!acc.isAuth()) {
|
||||||
|
_pagingController.error = 'Not logged in';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final items = (await APIClient.getChannelList(acc, ChannelSelector.all)).toList();
|
||||||
|
|
||||||
|
items.sort((a, b) => -1 * (a.channel.timestampLastSent ?? '').compareTo(b.channel.timestampLastSent ?? ''));
|
||||||
|
|
||||||
|
_pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null);
|
||||||
|
} catch (exc, trace) {
|
||||||
|
_pagingController.error = exc.toString();
|
||||||
|
ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _backgroundRefresh() async {
|
||||||
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
|
ApplicationLog.debug('Start background refresh of channel list');
|
||||||
|
|
||||||
|
if (!acc.isAuth()) {
|
||||||
|
_pagingController.error = 'Not logged in';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
|
||||||
|
|
||||||
|
AppBarState().setLoadingIndeterminate(true);
|
||||||
|
|
||||||
|
final items = (await APIClient.getChannelList(acc, ChannelSelector.all)).toList();
|
||||||
|
|
||||||
|
items.sort((a, b) => -1 * (a.channel.timestampLastSent ?? '').compareTo(b.channel.timestampLastSent ?? ''));
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null);
|
||||||
|
});
|
||||||
|
} catch (exc, trace) {
|
||||||
|
setState(() {
|
||||||
|
_pagingController.error = exc.toString();
|
||||||
|
});
|
||||||
|
ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace);
|
||||||
|
} finally {
|
||||||
|
AppBarState().setLoadingIndeterminate(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SCNScaffold(
|
||||||
|
title: "Channels",
|
||||||
|
showSearch: false,
|
||||||
|
showShare: false,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: () => Future.sync(
|
||||||
|
() => _pagingController.refresh(),
|
||||||
|
),
|
||||||
|
child: PagedListView<int, ChannelWithSubscription>(
|
||||||
|
pagingController: _pagingController,
|
||||||
|
builderDelegate: PagedChildBuilderDelegate<ChannelWithSubscription>(
|
||||||
|
itemBuilder: (context, item, index) => ChannelListItem(
|
||||||
|
channel: item.channel,
|
||||||
|
subscription: item.subscription,
|
||||||
|
mode: ChannelListItemMode.Extended,
|
||||||
|
onChannelListReloadTrigger: _enqueueReload,
|
||||||
|
onSubscriptionChanged: (channelID, subscription) {
|
||||||
|
setState(() {
|
||||||
|
final idx = _pagingController.itemList?.indexWhere((p) => p.channel.channelID == channelID);
|
||||||
|
if (idx != null && idx >= 0) _pagingController.itemList![idx] = ChannelWithSubscription(channel: _pagingController.itemList![idx].channel, subscription: subscription);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _enqueueReload() {
|
||||||
|
_reloadEnqueued = true;
|
||||||
|
}
|
||||||
|
}
|
@ -7,23 +7,35 @@ 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/pages/channel_message_view/channel_message_view.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/application_log.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/navi.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
|
||||||
|
enum ChannelListItemMode {
|
||||||
|
Messages,
|
||||||
|
Extended,
|
||||||
|
}
|
||||||
|
|
||||||
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');
|
||||||
|
|
||||||
const ChannelListItem({
|
const ChannelListItem({
|
||||||
required this.channel,
|
required this.channel,
|
||||||
required this.onPressed,
|
required this.onChannelListReloadTrigger,
|
||||||
|
required this.onSubscriptionChanged,
|
||||||
required this.subscription,
|
required this.subscription,
|
||||||
|
required this.mode,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
final Subscription? subscription;
|
final Subscription? subscription;
|
||||||
final Null Function() onPressed;
|
final void Function() onChannelListReloadTrigger;
|
||||||
|
final ChannelListItemMode mode;
|
||||||
|
final void Function(String, Subscription?) onSubscriptionChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChannelListItem> createState() => _ChannelListItemState();
|
State<ChannelListItem> createState() => _ChannelListItemState();
|
||||||
@ -38,11 +50,11 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
|
|
||||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
if (acc.isAuth()) {
|
if (acc.isAuth() && widget.mode == ChannelListItemMode.Messages) {
|
||||||
lastMessage = SCNDataCache().getMessagesSorted().where((p) => p.channelID == widget.channel.channelID).firstOrNull;
|
lastMessage = SCNDataCache().getMessagesSorted().where((p) => p.channelID == widget.channel.channelID).firstOrNull;
|
||||||
|
|
||||||
() async {
|
() async {
|
||||||
final (_, channelMessages) = await APIClient.getMessageList(acc, '@start', pageSize: 1, filter: MessageFilter(channelIDs: [widget.channel.channelID]));
|
final (_, channelMessages) = await APIClient.getChannelMessageList(acc, widget.channel.channelID, '@start', pageSize: 1);
|
||||||
setState(() {
|
setState(() {
|
||||||
lastMessage = channelMessages.firstOrNull;
|
lastMessage = channelMessages.firstOrNull;
|
||||||
});
|
});
|
||||||
@ -52,13 +64,18 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
//TODO subscription status
|
|
||||||
return Card.filled(
|
return Card.filled(
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
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(
|
||||||
onTap: widget.onPressed,
|
onTap: () {
|
||||||
|
if (widget.mode == ChannelListItemMode.Messages) {
|
||||||
|
Navi.push(context, () => ChannelMessageViewPage(channel: widget.channel));
|
||||||
|
} else {
|
||||||
|
Navi.push(context, () => ChannelViewPage(channelID: widget.channel.channelID, preloadedData: (widget.channel, widget.subscription), needsReload: widget.onChannelListReloadTrigger));
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -87,13 +104,8 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(child: (widget.mode == ChannelListItemMode.Messages) ? Text(_preformatTitle(lastMessage), style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160))) : _buildSubscriptionStateText(context)),
|
||||||
child: Text(
|
(widget.mode == ChannelListItemMode.Messages) ? Text(widget.channel.messagesSent.toString(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)) : Text("", style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||||
_preformatTitle(lastMessage),
|
|
||||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(widget.channel.messagesSent.toString(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -102,11 +114,15 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
SizedBox(width: 4),
|
SizedBox(width: 4),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navi.push(context, () => ChannelMessageViewPage(channel: this.widget.channel));
|
if (widget.mode == ChannelListItemMode.Messages) {
|
||||||
|
Navi.push(context, () => ChannelViewPage(channelID: widget.channel.channelID, preloadedData: (widget.channel, widget.subscription), needsReload: widget.onChannelListReloadTrigger));
|
||||||
|
} else {
|
||||||
|
Navi.push(context, () => ChannelMessageViewPage(channel: widget.channel));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Icon(FontAwesomeIcons.solidEnvelopes, color: Theme.of(context).colorScheme.onPrimaryContainer.withAlpha(128), size: 24),
|
child: (widget.mode == ChannelListItemMode.Messages) ? Icon(FontAwesomeIcons.solidSquareInfo, color: Theme.of(context).colorScheme.onPrimaryContainer.withAlpha(128), size: 24) : Icon(FontAwesomeIcons.solidEnvelopes, color: Theme.of(context).colorScheme.onPrimaryContainer.withAlpha(128), size: 24),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -123,13 +139,73 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
|
|
||||||
Widget _buildIcon(BuildContext context) {
|
Widget _buildIcon(BuildContext context) {
|
||||||
if (widget.subscription == null) {
|
if (widget.subscription == null) {
|
||||||
return Icon(FontAwesomeIcons.solidSquareDashed, color: Theme.of(context).colorScheme.outline, size: 32); // not-subscribed
|
Widget result = Icon(FontAwesomeIcons.solidSquareDashed, color: Theme.of(context).colorScheme.outline, size: 32); // not-subscribed
|
||||||
|
result = GestureDetector(onTap: () => _subscribe(), child: result);
|
||||||
|
return result;
|
||||||
} else if (widget.subscription!.confirmed && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
} else if (widget.subscription!.confirmed && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||||
return Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (own channel)
|
Widget result = Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (own channel)
|
||||||
|
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
||||||
|
return result;
|
||||||
} else if (widget.subscription!.confirmed) {
|
} else if (widget.subscription!.confirmed) {
|
||||||
return Icon(FontAwesomeIcons.solidSquareShareNodes, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (foreign channel)
|
Widget result = Icon(FontAwesomeIcons.solidSquareShareNodes, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (foreign channel)
|
||||||
|
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
||||||
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return Icon(FontAwesomeIcons.solidSquareEnvelope, color: Theme.of(context).colorScheme.tertiary, size: 32); // requested
|
Widget result = Icon(FontAwesomeIcons.solidSquareEnvelope, color: Theme.of(context).colorScheme.tertiary, size: 32); // requested
|
||||||
|
result = GestureDetector(onTap: () => _unsubscribe(widget.subscription!), child: result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSubscriptionStateText(BuildContext context) {
|
||||||
|
if (widget.subscription == null) {
|
||||||
|
return Text("", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||||
|
} else if (widget.subscription!.confirmed && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||||
|
return Text("subscribed", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||||
|
} else if (widget.subscription!.confirmed) {
|
||||||
|
return Text("subscripted (foreign channe)", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||||
|
} else {
|
||||||
|
return Text("subscription requested", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _subscribe() async {
|
||||||
|
final acc = AppAuth();
|
||||||
|
|
||||||
|
if (acc.isAuth() && widget.channel.ownerUserID == acc.getUserID()) {
|
||||||
|
try {
|
||||||
|
var sub = await APIClient.subscribeToChannelbyID(acc, widget.channel.channelID);
|
||||||
|
widget.onChannelListReloadTrigger.call();
|
||||||
|
|
||||||
|
widget.onSubscriptionChanged(widget.channel.channelID, sub);
|
||||||
|
|
||||||
|
if (sub.confirmed) {
|
||||||
|
Toaster.success("Success", 'Subscribed to channel');
|
||||||
|
} else {
|
||||||
|
Toaster.success("Success", 'Requested widget.subscription to channel');
|
||||||
|
}
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Toaster.error("Error", 'Failed to subscribe to channel');
|
||||||
|
ApplicationLog.error('Failed to subscribe to channel: ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _unsubscribe(Subscription sub) async {
|
||||||
|
final acc = AppAuth();
|
||||||
|
|
||||||
|
if (acc.isAuth() && widget.channel.ownerUserID == acc.getUserID() && widget.subscription != null) {
|
||||||
|
try {
|
||||||
|
await APIClient.deleteSubscription(acc, widget.channel.channelID, widget.subscription!.subscriptionID);
|
||||||
|
widget.onChannelListReloadTrigger.call();
|
||||||
|
|
||||||
|
widget.onSubscriptionChanged?.call(widget.channel.channelID, null);
|
||||||
|
|
||||||
|
Toaster.success("Success", 'Unsubscribed from channel');
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Toaster.error("Error", 'Failed to unsubscribe from channel');
|
||||||
|
ApplicationLog.error('Failed to unsubscribe from channel: ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ class _ChannelMessageViewPageState extends State<ChannelMessageViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final (npt, newItems) = await APIClient.getMessageList(acc, thisPageToken, pageSize: cfg.messagePageSize, filter: MessageFilter(channelIDs: [this.widget.channel.channelID]));
|
final (npt, newItems) = await APIClient.getChannelMessageList(acc, this.widget.channel.channelID, thisPageToken, pageSize: cfg.messagePageSize);
|
||||||
|
|
||||||
SCNDataCache().addToMessageCache(newItems); // no await
|
SCNDataCache().addToMessageCache(newItems); // no await
|
||||||
|
|
||||||
|
@ -63,15 +63,15 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_initStateAsync();
|
_initStateAsync(true);
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initStateAsync() async {
|
Future<void> _initStateAsync(bool usePreload) async {
|
||||||
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
if (widget.preloadedData != null) {
|
if (widget.preloadedData != null && usePreload) {
|
||||||
channelPreview = widget.preloadedData!.$1.toPreview();
|
channelPreview = widget.preloadedData!.$1.toPreview();
|
||||||
channel = widget.preloadedData!.$1;
|
channel = widget.preloadedData!.$1;
|
||||||
subscription = widget.preloadedData!.$2;
|
subscription = widget.preloadedData!.$2;
|
||||||
@ -231,7 +231,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
UI.metaCard(
|
UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||||
title: 'Subscription (own)',
|
title: 'Subscription (foreign)',
|
||||||
values: [_formatSubscriptionStatus(subscription)],
|
values: [_formatSubscriptionStatus(subscription)],
|
||||||
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
||||||
),
|
),
|
||||||
@ -296,7 +296,7 @@ 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' + channel!.channelID + '\n' + snapshot.data!; //TODO deeplink-y (also perhaps just bas64 everything together?)
|
var text = '@scn.channel.subscribe' + '\n' + "v1" + '\n' + channel!.displayName + '\n' + channel!.ownerUserID + '\n' + channel!.channelID + '\n' + snapshot.data!;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Share.share(text, subject: _displayNameOverride ?? channel!.displayName);
|
Share.share(text, subject: _displayNameOverride ?? channel!.displayName);
|
||||||
@ -305,7 +305,7 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
child: QrImageView(
|
child: QrImageView(
|
||||||
data: text,
|
data: text,
|
||||||
version: QrVersions.auto,
|
version: QrVersions.auto,
|
||||||
size: 300.0,
|
size: 265.0,
|
||||||
eyeStyle: QrEyeStyle(
|
eyeStyle: QrEyeStyle(
|
||||||
eyeShape: QrEyeShape.square,
|
eyeShape: QrEyeShape.square,
|
||||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||||
@ -446,14 +446,6 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _subscribe() {
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
void _unsubscribe() {
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showEditDisplayName() {
|
void _showEditDisplayName() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_ctrlDisplayName.text = _displayNameOverride ?? channelPreview?.displayName ?? '';
|
_ctrlDisplayName.text = _displayNameOverride ?? channelPreview?.displayName ?? '';
|
||||||
@ -518,16 +510,90 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cancelForeignSubscription(Subscription sub) {
|
void _subscribe() async {
|
||||||
//TODO
|
final acc = AppAuth();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var sub = await APIClient.subscribeToChannelbyID(acc, widget.channelID);
|
||||||
|
widget.needsReload?.call();
|
||||||
|
|
||||||
|
await _initStateAsync(false);
|
||||||
|
|
||||||
|
if (sub.confirmed) {
|
||||||
|
Toaster.success("Success", 'Subscribed to channel');
|
||||||
|
} else {
|
||||||
|
Toaster.success("Success", 'Requested subscription to channel');
|
||||||
|
}
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Toaster.error("Error", 'Failed to subscribe to channel');
|
||||||
|
ApplicationLog.error('Failed to subscribe to channel: ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _confirmForeignSubscription(Subscription sub) {
|
void _unsubscribe() async {
|
||||||
//TODO
|
final acc = AppAuth();
|
||||||
|
|
||||||
|
if (subscription == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await APIClient.deleteSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
||||||
|
widget.needsReload?.call();
|
||||||
|
|
||||||
|
await _initStateAsync(false);
|
||||||
|
|
||||||
|
Toaster.success("Success", 'Unsubscribed from channel');
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Toaster.error("Error", 'Failed to unsubscribe from channel');
|
||||||
|
ApplicationLog.error('Failed to unsubscribe from channel: ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _denyForeignSubscription(Subscription sub) {
|
void _cancelForeignSubscription(Subscription sub) async {
|
||||||
//TODO
|
final acc = AppAuth();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await APIClient.unconfirmSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
||||||
|
widget.needsReload?.call();
|
||||||
|
|
||||||
|
await _initStateAsync(false);
|
||||||
|
|
||||||
|
Toaster.success("Success", 'Subscription succesfully revoked');
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Toaster.error("Error", 'Failed to revoke subscription');
|
||||||
|
ApplicationLog.error('Failed to revoke subscription: ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _confirmForeignSubscription(Subscription sub) async {
|
||||||
|
final acc = AppAuth();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await APIClient.confirmSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
||||||
|
widget.needsReload?.call();
|
||||||
|
|
||||||
|
await _initStateAsync(false);
|
||||||
|
|
||||||
|
Toaster.success("Success", 'Subscription succesfully confirmed');
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Toaster.error("Error", 'Failed to confirm subscription');
|
||||||
|
ApplicationLog.error('Failed to confirm subscription: ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _denyForeignSubscription(Subscription sub) async {
|
||||||
|
final acc = AppAuth();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await APIClient.deleteSubscription(acc, widget.channelID, subscription!.subscriptionID);
|
||||||
|
widget.needsReload?.call();
|
||||||
|
|
||||||
|
await _initStateAsync(false);
|
||||||
|
|
||||||
|
Toaster.success("Success", 'Subscription request succesfully denied');
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Toaster.error("Error", 'Failed to deny subscription');
|
||||||
|
ApplicationLog.error('Failed to deny subscription: ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatSubscriptionStatus(Subscription? subscription) {
|
String _formatSubscriptionStatus(Subscription? subscription) {
|
||||||
|
@ -72,7 +72,7 @@ class UI {
|
|||||||
splashColor: Theme.of(context).splashColor,
|
splashColor: Theme.of(context).splashColor,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -411,7 +411,6 @@ func (h APIHandler) ListChannelMessages(pctx ginext.PreContext) ginext.HTTPRespo
|
|||||||
type query struct {
|
type query struct {
|
||||||
PageSize *int `json:"page_size" form:"page_size"`
|
PageSize *int `json:"page_size" form:"page_size"`
|
||||||
NextPageToken *string `json:"next_page_token" form:"next_page_token"`
|
NextPageToken *string `json:"next_page_token" form:"next_page_token"`
|
||||||
Filter *string `json:"filter" form:"filter"`
|
|
||||||
Trimmed *bool `json:"trimmed" form:"trimmed"`
|
Trimmed *bool `json:"trimmed" form:"trimmed"`
|
||||||
}
|
}
|
||||||
type response struct {
|
type response struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user