More background refreshing
This commit is contained in:
parent
35ab9a26c0
commit
eea219a205
@ -7,9 +7,11 @@ 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/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';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
||||||
import 'package:simplecloudnotifier/utils/navi.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';
|
||||||
@ -25,11 +27,12 @@ class AccountRootPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AccountRootPageState extends State<AccountRootPage> {
|
class _AccountRootPageState extends State<AccountRootPage> {
|
||||||
late Future<int>? futureSubscriptionCount;
|
late ImmediateFuture<int>? futureSubscriptionCount;
|
||||||
late Future<int>? futureClientCount;
|
late ImmediateFuture<int>? futureClientCount;
|
||||||
late Future<int>? futureKeyCount;
|
late ImmediateFuture<int>? futureKeyCount;
|
||||||
late Future<int>? futureChannelAllCount;
|
late ImmediateFuture<int>? futureChannelAllCount;
|
||||||
late Future<int>? futureChannelSubscribedCount;
|
late ImmediateFuture<int>? futureChannelSubscribedCount;
|
||||||
|
late ImmediateFuture<User>? futureUser;
|
||||||
|
|
||||||
late AppAuth userAcc;
|
late AppAuth userAcc;
|
||||||
|
|
||||||
@ -44,7 +47,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
userAcc = Provider.of<AppAuth>(context, listen: false);
|
userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
userAcc.addListener(_onAuthStateChanged);
|
userAcc.addListener(_onAuthStateChanged);
|
||||||
|
|
||||||
if (widget.isVisiblePage && !_isInitialized) realInitState();
|
if (widget.isVisiblePage && !_isInitialized) _realInitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -53,25 +56,32 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
|
|
||||||
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
||||||
if (!_isInitialized) {
|
if (!_isInitialized) {
|
||||||
realInitState();
|
_realInitState();
|
||||||
} else {
|
} else {
|
||||||
//TODO background refresh
|
_backgroundRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void realInitState() {
|
void _realInitState() {
|
||||||
|
ApplicationLog.debug('AccountRootPage::_realInitState');
|
||||||
_onAuthStateChanged();
|
_onAuthStateChanged();
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
ApplicationLog.debug('AccountRootPage::dispose');
|
||||||
userAcc.removeListener(_onAuthStateChanged);
|
userAcc.removeListener(_onAuthStateChanged);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAuthStateChanged() {
|
void _onAuthStateChanged() {
|
||||||
|
ApplicationLog.debug('AccountRootPage::_onAuthStateChanged');
|
||||||
|
_createFutures();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _createFutures() {
|
||||||
futureSubscriptionCount = null;
|
futureSubscriptionCount = null;
|
||||||
futureClientCount = null;
|
futureClientCount = null;
|
||||||
futureKeyCount = null;
|
futureKeyCount = null;
|
||||||
@ -79,35 +89,70 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
futureChannelSubscribedCount = null;
|
futureChannelSubscribedCount = null;
|
||||||
|
|
||||||
if (userAcc.isAuth()) {
|
if (userAcc.isAuth()) {
|
||||||
futureChannelAllCount = () async {
|
futureChannelAllCount = ImmediateFuture.ofFuture(() async {
|
||||||
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final channels = await APIClient.getChannelList(userAcc, ChannelSelector.all);
|
final channels = await APIClient.getChannelList(userAcc, ChannelSelector.all);
|
||||||
return channels.length;
|
return channels.length;
|
||||||
}();
|
}());
|
||||||
|
|
||||||
futureChannelSubscribedCount = () async {
|
futureChannelSubscribedCount = ImmediateFuture.ofFuture(() async {
|
||||||
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final channels = await APIClient.getChannelList(userAcc, ChannelSelector.subscribed);
|
final channels = await APIClient.getChannelList(userAcc, ChannelSelector.subscribed);
|
||||||
return channels.length;
|
return channels.length;
|
||||||
}();
|
}());
|
||||||
|
|
||||||
futureSubscriptionCount = () async {
|
futureSubscriptionCount = ImmediateFuture.ofFuture(() async {
|
||||||
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final subs = await APIClient.getSubscriptionList(userAcc);
|
final subs = await APIClient.getSubscriptionList(userAcc);
|
||||||
return subs.length;
|
return subs.length;
|
||||||
}();
|
}());
|
||||||
|
|
||||||
futureClientCount = () async {
|
futureClientCount = ImmediateFuture.ofFuture(() async {
|
||||||
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final clients = await APIClient.getClientList(userAcc);
|
final clients = await APIClient.getClientList(userAcc);
|
||||||
return clients.length;
|
return clients.length;
|
||||||
}();
|
}());
|
||||||
|
|
||||||
futureKeyCount = () async {
|
futureKeyCount = ImmediateFuture.ofFuture(() async {
|
||||||
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final keys = await APIClient.getKeyTokenList(userAcc);
|
final keys = await APIClient.getKeyTokenList(userAcc);
|
||||||
return keys.length;
|
return keys.length;
|
||||||
}();
|
}());
|
||||||
|
|
||||||
|
futureUser = ImmediateFuture.ofFuture(userAcc.loadUser(force: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _backgroundRefresh() async {
|
||||||
|
if (userAcc.isAuth()) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// refresh all data and then replace teh futures used in build()
|
||||||
|
|
||||||
|
final channelsAll = await APIClient.getChannelList(userAcc, ChannelSelector.all);
|
||||||
|
final channelsSubscribed = await APIClient.getChannelList(userAcc, ChannelSelector.subscribed);
|
||||||
|
final subs = await APIClient.getSubscriptionList(userAcc);
|
||||||
|
final clients = await APIClient.getClientList(userAcc);
|
||||||
|
final keys = await APIClient.getKeyTokenList(userAcc);
|
||||||
|
final user = await userAcc.loadUser(force: true);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
futureChannelAllCount = ImmediateFuture.ofValue(channelsAll.length);
|
||||||
|
futureChannelSubscribedCount = ImmediateFuture.ofValue(channelsSubscribed.length);
|
||||||
|
futureSubscriptionCount = ImmediateFuture.ofValue(subs.length);
|
||||||
|
futureClientCount = ImmediateFuture.ofValue(clients.length);
|
||||||
|
futureKeyCount = ImmediateFuture.ofValue(keys.length);
|
||||||
|
futureUser = ImmediateFuture.ofValue(user);
|
||||||
|
});
|
||||||
|
} catch (exc, trace) {
|
||||||
|
ApplicationLog.error('Failed to refresh account data: ' + exc.toString(), trace: trace);
|
||||||
|
Toaster.error("Error", 'Failed to refresh account data');
|
||||||
|
} finally {
|
||||||
|
AppBarState().setLoadingIndeterminate(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,15 +166,17 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
return _buildNoAuth(context);
|
return _buildNoAuth(context);
|
||||||
} else {
|
} else {
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: acc.loadUser(force: false),
|
future: futureUser!.future,
|
||||||
builder: ((context, snapshot) {
|
builder: ((context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (futureUser?.value != null) {
|
||||||
if (snapshot.hasError) {
|
return _buildShowAccount(context, acc, futureUser!.value!);
|
||||||
|
} else if (snapshot.connectionState == ConnectionState.done && snapshot.hasError) {
|
||||||
return Text('Error: ${snapshot.error}'); //TODO better error display
|
return Text('Error: ${snapshot.error}'); //TODO better error display
|
||||||
}
|
} else if (snapshot.connectionState == ConnectionState.done) {
|
||||||
return _buildShowAccount(context, acc, snapshot.data!);
|
return _buildShowAccount(context, acc, snapshot.data!);
|
||||||
}
|
} else {
|
||||||
return Center(child: CircularProgressIndicator());
|
return Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -281,12 +328,15 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(width: 80, child: Text("Channels", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)))),
|
SizedBox(width: 80, child: Text("Channels", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)))),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: futureChannelAllCount,
|
future: futureChannelAllCount!.future,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (futureChannelAllCount?.value != null) {
|
||||||
|
return Text('${futureChannelAllCount!.value}');
|
||||||
|
} else if (snapshot.connectionState == ConnectionState.done) {
|
||||||
return Text('${snapshot.data}');
|
return Text('${snapshot.data}');
|
||||||
}
|
} else {
|
||||||
return const SizedBox(width: 8, height: 8, child: Center(child: CircularProgressIndicator()));
|
return const SizedBox(width: 8, height: 8, child: Center(child: CircularProgressIndicator()));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -315,86 +365,10 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
|
|
||||||
List<Widget> _buildCards(BuildContext context, User user) {
|
List<Widget> _buildCards(BuildContext context, User user) {
|
||||||
return [
|
return [
|
||||||
UI.buttonCard(
|
_buildNumberCard(context, 'Subscriptions', futureSubscriptionCount, () {/*TODO*/}),
|
||||||
context: context,
|
_buildNumberCard(context, 'Clients', futureClientCount, () {/*TODO*/}),
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
_buildNumberCard(context, 'Keys', futureKeyCount, () {/*TODO*/}),
|
||||||
child: Row(
|
_buildNumberCard(context, 'Channels', futureChannelSubscribedCount, () {/*TODO*/}),
|
||||||
children: [
|
|
||||||
FutureBuilder(
|
|
||||||
future: futureSubscriptionCount,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
|
|
||||||
}
|
|
||||||
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text('Subscriptions', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {/*TODO*/},
|
|
||||||
),
|
|
||||||
UI.buttonCard(
|
|
||||||
context: context,
|
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
FutureBuilder(
|
|
||||||
future: futureClientCount,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
|
|
||||||
}
|
|
||||||
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text('Clients', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {/*TODO*/},
|
|
||||||
),
|
|
||||||
UI.buttonCard(
|
|
||||||
context: context,
|
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
FutureBuilder(
|
|
||||||
future: futureKeyCount,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
|
|
||||||
}
|
|
||||||
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text('Keys', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {/*TODO*/},
|
|
||||||
),
|
|
||||||
UI.buttonCard(
|
|
||||||
context: context,
|
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
FutureBuilder(
|
|
||||||
future: futureChannelSubscribedCount,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
|
|
||||||
}
|
|
||||||
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text('Channels', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {/*TODO*/},
|
|
||||||
),
|
|
||||||
UI.buttonCard(
|
UI.buttonCard(
|
||||||
context: context,
|
context: context,
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
@ -410,6 +384,32 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildNumberCard(BuildContext context, String txt, ImmediateFuture<int>? future, void Function() action) {
|
||||||
|
return UI.buttonCard(
|
||||||
|
context: context,
|
||||||
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
FutureBuilder(
|
||||||
|
future: future?.future,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (future?.value != null) {
|
||||||
|
return Text('${future?.value}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
|
||||||
|
} else if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
|
||||||
|
} else {
|
||||||
|
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(txt, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: action,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildFooter(BuildContext context, User user) {
|
Widget _buildFooter(BuildContext context, User user) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||||
|
@ -3,6 +3,7 @@ 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/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';
|
||||||
import 'package:simplecloudnotifier/pages/channel_list/channel_list_item.dart';
|
import 'package:simplecloudnotifier/pages/channel_list/channel_list_item.dart';
|
||||||
@ -27,11 +28,12 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
|
|
||||||
_pagingController.addPageRequestListener(_fetchPage);
|
_pagingController.addPageRequestListener(_fetchPage);
|
||||||
|
|
||||||
if (widget.isVisiblePage && !_isInitialized) realInitState();
|
if (widget.isVisiblePage && !_isInitialized) _realInitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
ApplicationLog.debug('ChannelRootPage::dispose');
|
||||||
_pagingController.dispose();
|
_pagingController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -42,14 +44,15 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
|
|
||||||
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
||||||
if (!_isInitialized) {
|
if (!_isInitialized) {
|
||||||
realInitState();
|
_realInitState();
|
||||||
} else {
|
} else {
|
||||||
//TODO background refresh
|
_backgroundRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void realInitState() {
|
void _realInitState() {
|
||||||
|
ApplicationLog.debug('ChannelRootPage::_realInitState');
|
||||||
_pagingController.refresh();
|
_pagingController.refresh();
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
@ -69,13 +72,41 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
|
|
||||||
items.sort((a, b) => -1 * (a.timestampLastSent ?? '').compareTo(b.timestampLastSent ?? ''));
|
items.sort((a, b) => -1 * (a.timestampLastSent ?? '').compareTo(b.timestampLastSent ?? ''));
|
||||||
|
|
||||||
_pagingController.appendLastPage(items);
|
_pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null);
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
_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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)).map((p) => p.channel).toList();
|
||||||
|
|
||||||
|
items.sort((a, b) => -1 * (a.timestampLastSent ?? '').compareTo(b.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);
|
||||||
|
} finally {
|
||||||
|
AppBarState().setLoadingIndeterminate(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
|
@ -43,7 +43,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(width: 30, child: Text('')),
|
SizedBox(width: 30, child: Text('')),
|
||||||
Expanded(child: Text('Shared Preferences', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
Expanded(child: Text('Shared Preferences', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
||||||
SizedBox(width: 30, child: Text('${prefs?.getKeys().length.toString()}', textAlign: TextAlign.end)),
|
SizedBox(width: 40, child: Text('${prefs?.getKeys().length.toString()}', textAlign: TextAlign.end)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -61,7 +61,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(width: 30, child: Text('')),
|
SizedBox(width: 30, child: Text('')),
|
||||||
Expanded(child: Text('Hive [scn-requests]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
Expanded(child: Text('Hive [scn-requests]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
||||||
SizedBox(width: 30, child: Text('${Hive.box<SCNRequest>('scn-requests').length.toString()}', textAlign: TextAlign.end)),
|
SizedBox(width: 40, child: Text('${Hive.box<SCNRequest>('scn-requests').length.toString()}', textAlign: TextAlign.end)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -79,7 +79,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(width: 30, child: Text('')),
|
SizedBox(width: 30, child: Text('')),
|
||||||
Expanded(child: Text('Hive [scn-logs]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
Expanded(child: Text('Hive [scn-logs]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
||||||
SizedBox(width: 30, child: Text('${Hive.box<SCNLog>('scn-logs').length.toString()}', textAlign: TextAlign.end)),
|
SizedBox(width: 40, child: Text('${Hive.box<SCNLog>('scn-logs').length.toString()}', textAlign: TextAlign.end)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -97,7 +97,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(width: 30, child: Text('')),
|
SizedBox(width: 30, child: Text('')),
|
||||||
Expanded(child: Text('Hive [scn-message-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
Expanded(child: Text('Hive [scn-message-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
||||||
SizedBox(width: 30, child: Text('${Hive.box<Message>('scn-message-cache').length.toString()}', textAlign: TextAlign.end)),
|
SizedBox(width: 40, child: Text('${Hive.box<Message>('scn-message-cache').length.toString()}', textAlign: TextAlign.end)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -115,7 +115,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(width: 30, child: Text('')),
|
SizedBox(width: 30, child: Text('')),
|
||||||
Expanded(child: Text('Hive [scn-channel-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
Expanded(child: Text('Hive [scn-channel-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
|
||||||
SizedBox(width: 30, child: Text('${Hive.box<Channel>('scn-channel-cache').length.toString()}', textAlign: TextAlign.end)),
|
SizedBox(width: 40, child: Text('${Hive.box<Channel>('scn-channel-cache').length.toString()}', textAlign: TextAlign.end)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -39,7 +39,7 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
|||||||
|
|
||||||
_pagingController.addPageRequestListener(_fetchPage);
|
_pagingController.addPageRequestListener(_fetchPage);
|
||||||
|
|
||||||
if (widget.isVisiblePage && !_isInitialized) realInitState();
|
if (widget.isVisiblePage && !_isInitialized) _realInitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -48,14 +48,16 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
|||||||
|
|
||||||
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
||||||
if (!_isInitialized) {
|
if (!_isInitialized) {
|
||||||
realInitState();
|
_realInitState();
|
||||||
} else {
|
} else {
|
||||||
_backgroundRefresh(false);
|
_backgroundRefresh(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void realInitState() {
|
void _realInitState() {
|
||||||
|
ApplicationLog.debug('MessageListPage::_realInitState');
|
||||||
|
|
||||||
final chnCache = Hive.box<Channel>('scn-channel-cache');
|
final chnCache = Hive.box<Channel>('scn-channel-cache');
|
||||||
final msgCache = Hive.box<Message>('scn-message-cache');
|
final msgCache = Hive.box<Message>('scn-message-cache');
|
||||||
|
|
||||||
@ -86,6 +88,7 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
ApplicationLog.debug('MessageListPage::dispose');
|
||||||
Navi.modalRouteObserver.unsubscribe(this);
|
Navi.modalRouteObserver.unsubscribe(this);
|
||||||
_pagingController.dispose();
|
_pagingController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -93,14 +96,13 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didPush() {
|
void didPush() {
|
||||||
// Route was pushed onto navigator and is now the topmost route.
|
// ...
|
||||||
ApplicationLog.debug('[MessageList::RouteObserver] --> didPush');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didPopNext() {
|
void didPopNext() {
|
||||||
// Covering route was popped off the navigator.
|
ApplicationLog.debug('[MessageList::RouteObserver] --> didPopNext (will background-refresh)');
|
||||||
ApplicationLog.debug('[MessageList::RouteObserver] --> didPopNext');
|
_backgroundRefresh(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchPage(String thisPageToken) async {
|
Future<void> _fetchPage(String thisPageToken) async {
|
||||||
|
@ -118,7 +118,6 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
|||||||
final user = await APIClient.getUser(this, _userID!);
|
final user = await APIClient.getUser(this, _userID!);
|
||||||
|
|
||||||
_user = user;
|
_user = user;
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
await save();
|
await save();
|
||||||
|
|
||||||
@ -142,14 +141,12 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
|||||||
final client = await APIClient.getClient(this, _clientID!);
|
final client = await APIClient.getClient(this, _clientID!);
|
||||||
|
|
||||||
_client = client;
|
_client = client;
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
await save();
|
await save();
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
} on APIException catch (_) {
|
} on APIException catch (_) {
|
||||||
_client = null;
|
_client = null;
|
||||||
notifyListeners();
|
|
||||||
return null;
|
return null;
|
||||||
} catch (exc) {
|
} catch (exc) {
|
||||||
_client = null;
|
_client = null;
|
||||||
|
@ -5,6 +5,8 @@ import 'package:xid/xid.dart';
|
|||||||
part 'application_log.g.dart';
|
part 'application_log.g.dart';
|
||||||
|
|
||||||
class ApplicationLog {
|
class ApplicationLog {
|
||||||
|
//TODO max size, auto clear old
|
||||||
|
|
||||||
static void debug(String message, {String? additional, StackTrace? trace}) {
|
static void debug(String message, {String? additional, StackTrace? trace}) {
|
||||||
(additional != null && additional != '') ? print('[DEBUG] ${message}: ${additional}') : print('[DEBUG] ${message}');
|
(additional != null && additional != '') ? print('[DEBUG] ${message}: ${additional}') : print('[DEBUG] ${message}');
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ import 'package:xid/xid.dart';
|
|||||||
part 'request_log.g.dart';
|
part 'request_log.g.dart';
|
||||||
|
|
||||||
class RequestLog {
|
class RequestLog {
|
||||||
|
//TODO max size, auto clear old
|
||||||
|
|
||||||
static void addRequestException(String name, DateTime tStart, String method, Uri uri, String reqbody, Map<String, String> reqheaders, dynamic e, StackTrace trace) {
|
static void addRequestException(String name, DateTime tStart, String method, Uri uri, String reqbody, Map<String, String> reqheaders, dynamic e, StackTrace trace) {
|
||||||
Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
|
Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
|
||||||
id: Xid().toString(),
|
id: Xid().toString(),
|
||||||
|
18
flutter/lib/types/immediate_future.dart
Normal file
18
flutter/lib/types/immediate_future.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// This class is useful togther with FutureBuilder
|
||||||
|
// Unfortunately Future.value(x) in FutureBuilder always results in one frame were snapshot.connectionState is waiting
|
||||||
|
// Whit way we can set the ImmediateFuture.value directly and circumvent that.
|
||||||
|
|
||||||
|
class ImmediateFuture<T> {
|
||||||
|
final Future<T> future;
|
||||||
|
final T? value;
|
||||||
|
|
||||||
|
ImmediateFuture(this.future, this.value);
|
||||||
|
|
||||||
|
ImmediateFuture.ofFuture(Future<T> v)
|
||||||
|
: future = v,
|
||||||
|
value = null;
|
||||||
|
|
||||||
|
ImmediateFuture.ofValue(T v)
|
||||||
|
: future = Future.value(v),
|
||||||
|
value = v;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user