Cache messages, use cache if exists, load in background
This commit is contained in:
parent
9c366399df
commit
35ab9a26c0
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
dart run build_runner build
|
flutter pub run build_runner build
|
||||||
flutter run
|
flutter run
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@ -11,7 +11,7 @@ fix:
|
|||||||
dart fix --apply
|
dart fix --apply
|
||||||
|
|
||||||
gen:
|
gen:
|
||||||
dart run build_runner build
|
flutter pub run build_runner build
|
||||||
|
|
||||||
autoreload:
|
autoreload:
|
||||||
@# run `make run` in another terminal (or another variant of flutter run)
|
@# run `make run` in another terminal (or another variant of flutter run)
|
||||||
|
@ -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:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/components/layout/app_bar_progress_indicator.dart';
|
import 'package:simplecloudnotifier/components/layout/app_bar_progress_indicator.dart';
|
||||||
@ -28,6 +29,16 @@ class SCNAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var actions = <Widget>[];
|
var actions = <Widget>[];
|
||||||
|
|
||||||
|
if (showDebug) {
|
||||||
|
actions.add(IconButton(
|
||||||
|
icon: const Icon(FontAwesomeIcons.solidSpiderBlackWidow),
|
||||||
|
tooltip: 'Debug',
|
||||||
|
onPressed: () {
|
||||||
|
Navi.push(context, () => DebugMainPage());
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if (showThemeSwitch) {
|
if (showThemeSwitch) {
|
||||||
actions.add(Consumer<AppTheme>(
|
actions.add(Consumer<AppTheme>(
|
||||||
builder: (context, appTheme, child) => IconButton(
|
builder: (context, appTheme, child) => IconButton(
|
||||||
@ -37,19 +48,16 @@ class SCNAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
actions.add(SizedBox.square(dimension: 40));
|
actions.add(Visibility(
|
||||||
}
|
visible: false,
|
||||||
|
maintainSize: true,
|
||||||
if (showDebug) {
|
maintainAnimation: true,
|
||||||
actions.add(IconButton(
|
maintainState: true,
|
||||||
icon: const Icon(FontAwesomeIcons.solidSpiderBlackWidow),
|
child: IconButton(
|
||||||
tooltip: 'Debug',
|
icon: const Icon(FontAwesomeIcons.square),
|
||||||
onPressed: () {
|
onPressed: () {/*TODO*/},
|
||||||
Navi.push(context, () => DebugMainPage());
|
),
|
||||||
},
|
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
actions.add(SizedBox.square(dimension: 40));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showSearch) {
|
if (showSearch) {
|
||||||
@ -65,7 +73,16 @@ class SCNAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
onPressed: onShare ?? () {},
|
onPressed: onShare ?? () {},
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
actions.add(SizedBox.square(dimension: 40));
|
actions.add(Visibility(
|
||||||
|
visible: false,
|
||||||
|
maintainSize: true,
|
||||||
|
maintainAnimation: true,
|
||||||
|
maintainState: true,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(FontAwesomeIcons.square),
|
||||||
|
onPressed: () {/*TODO*/},
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppBar(
|
return AppBar(
|
||||||
|
@ -5,7 +5,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.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/client.dart';
|
import 'package:simplecloudnotifier/models/client.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/message.dart';
|
||||||
import 'package:simplecloudnotifier/nav_layout.dart';
|
import 'package:simplecloudnotifier/nav_layout.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_theme.dart';
|
import 'package:simplecloudnotifier/state/app_theme.dart';
|
||||||
@ -36,6 +38,8 @@ void main() async {
|
|||||||
Hive.registerAdapter(SCNRequestAdapter());
|
Hive.registerAdapter(SCNRequestAdapter());
|
||||||
Hive.registerAdapter(SCNLogAdapter());
|
Hive.registerAdapter(SCNLogAdapter());
|
||||||
Hive.registerAdapter(SCNLogLevelAdapter());
|
Hive.registerAdapter(SCNLogLevelAdapter());
|
||||||
|
Hive.registerAdapter(MessageAdapter());
|
||||||
|
Hive.registerAdapter(ChannelAdapter());
|
||||||
|
|
||||||
print('[INIT] Load Hive<scn-requests>...');
|
print('[INIT] Load Hive<scn-requests>...');
|
||||||
|
|
||||||
@ -57,6 +61,26 @@ void main() async {
|
|||||||
ApplicationLog.error('Failed to open Hive-Box: scn-logs: ' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to open Hive-Box: scn-logs: ' + exc.toString(), trace: trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print('[INIT] Load Hive<scn-message-cache>...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Hive.openBox<Message>('scn-message-cache');
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Hive.deleteBoxFromDisk('scn-message-cache');
|
||||||
|
await Hive.openBox<Message>('scn-message-cache');
|
||||||
|
ApplicationLog.error('Failed to open Hive-Box: scn-message-cache' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
|
|
||||||
|
print('[INIT] Load Hive<scn-channel-cache>...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Hive.openBox<Channel>('scn-channel-cache');
|
||||||
|
} catch (exc, trace) {
|
||||||
|
Hive.deleteBoxFromDisk('scn-channel-cache');
|
||||||
|
await Hive.openBox<Channel>('scn-channel-cache');
|
||||||
|
ApplicationLog.error('Failed to open Hive-Box: scn-channel-cache' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
|
|
||||||
print('[INIT] Load AppAuth...');
|
print('[INIT] Load AppAuth...');
|
||||||
|
|
||||||
final appAuth = AppAuth(); // ensure UserAccount is loaded
|
final appAuth = AppAuth(); // ensure UserAccount is loaded
|
||||||
@ -135,7 +159,7 @@ void setFirebaseToken(String fcmToken) async {
|
|||||||
|
|
||||||
Client? client;
|
Client? client;
|
||||||
try {
|
try {
|
||||||
client = await acc.loadClient(force: true);
|
client = await acc.loadClient(forceIfOlder: Duration(seconds: 60));
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.error('Failed to get client: ' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to get client: ' + exc.toString(), trace: trace);
|
||||||
return;
|
return;
|
||||||
@ -172,7 +196,7 @@ class SCNApp extends StatelessWidget {
|
|||||||
child: Consumer<AppTheme>(
|
child: Consumer<AppTheme>(
|
||||||
builder: (context, appTheme, child) => MaterialApp(
|
builder: (context, appTheme, child) => MaterialApp(
|
||||||
title: 'SimpleCloudNotifier',
|
title: 'SimpleCloudNotifier',
|
||||||
navigatorObservers: [Navi.routeObserver],
|
navigatorObservers: [Navi.routeObserver, Navi.modalRouteObserver],
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
//TODO color settings
|
//TODO color settings
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light),
|
||||||
|
@ -1,17 +1,32 @@
|
|||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:simplecloudnotifier/models/subscription.dart';
|
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/interfaces.dart';
|
||||||
|
|
||||||
class Channel {
|
part 'channel.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 104)
|
||||||
|
class Channel extends HiveObject implements FieldDebuggable {
|
||||||
|
@HiveField(0)
|
||||||
final String channelID;
|
final String channelID;
|
||||||
|
|
||||||
|
@HiveField(10)
|
||||||
final String ownerUserID;
|
final String ownerUserID;
|
||||||
|
@HiveField(11)
|
||||||
final String internalName;
|
final String internalName;
|
||||||
|
@HiveField(12)
|
||||||
final String displayName;
|
final String displayName;
|
||||||
|
@HiveField(13)
|
||||||
final String? descriptionName;
|
final String? descriptionName;
|
||||||
|
@HiveField(14)
|
||||||
final String? subscribeKey;
|
final String? subscribeKey;
|
||||||
|
@HiveField(15)
|
||||||
final String timestampCreated;
|
final String timestampCreated;
|
||||||
|
@HiveField(16)
|
||||||
final String? timestampLastSent;
|
final String? timestampLastSent;
|
||||||
|
@HiveField(17)
|
||||||
final int messagesSent;
|
final int messagesSent;
|
||||||
|
|
||||||
const Channel({
|
Channel({
|
||||||
required this.channelID,
|
required this.channelID,
|
||||||
required this.ownerUserID,
|
required this.ownerUserID,
|
||||||
required this.internalName,
|
required this.internalName,
|
||||||
@ -36,6 +51,25 @@ class Channel {
|
|||||||
messagesSent: json['messages_sent'] as int,
|
messagesSent: json['messages_sent'] as int,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Channel[${this.channelID}]';
|
||||||
|
}
|
||||||
|
|
||||||
|
List<(String, String)> debugFieldList() {
|
||||||
|
return [
|
||||||
|
('channelID', this.channelID),
|
||||||
|
('ownerUserID', this.ownerUserID),
|
||||||
|
('internalName', this.internalName),
|
||||||
|
('displayName', this.displayName),
|
||||||
|
('descriptionName', this.descriptionName ?? ''),
|
||||||
|
('subscribeKey', this.subscribeKey ?? ''),
|
||||||
|
('timestampCreated', this.timestampCreated),
|
||||||
|
('timestampLastSent', this.timestampLastSent ?? ''),
|
||||||
|
('messagesSent', '${this.messagesSent}'),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelWithSubscription {
|
class ChannelWithSubscription {
|
||||||
|
65
flutter/lib/models/channel.g.dart
Normal file
65
flutter/lib/models/channel.g.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'channel.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class ChannelAdapter extends TypeAdapter<Channel> {
|
||||||
|
@override
|
||||||
|
final int typeId = 104;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Channel read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return Channel(
|
||||||
|
channelID: fields[0] as String,
|
||||||
|
ownerUserID: fields[10] as String,
|
||||||
|
internalName: fields[11] as String,
|
||||||
|
displayName: fields[12] as String,
|
||||||
|
descriptionName: fields[13] as String?,
|
||||||
|
subscribeKey: fields[14] as String?,
|
||||||
|
timestampCreated: fields[15] as String,
|
||||||
|
timestampLastSent: fields[16] as String?,
|
||||||
|
messagesSent: fields[17] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, Channel obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(9)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.channelID)
|
||||||
|
..writeByte(10)
|
||||||
|
..write(obj.ownerUserID)
|
||||||
|
..writeByte(11)
|
||||||
|
..write(obj.internalName)
|
||||||
|
..writeByte(12)
|
||||||
|
..write(obj.displayName)
|
||||||
|
..writeByte(13)
|
||||||
|
..write(obj.descriptionName)
|
||||||
|
..writeByte(14)
|
||||||
|
..write(obj.subscribeKey)
|
||||||
|
..writeByte(15)
|
||||||
|
..write(obj.timestampCreated)
|
||||||
|
..writeByte(16)
|
||||||
|
..write(obj.timestampLastSent)
|
||||||
|
..writeByte(17)
|
||||||
|
..write(obj.messagesSent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is ChannelAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
@ -1,19 +1,39 @@
|
|||||||
class Message {
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/interfaces.dart';
|
||||||
|
|
||||||
|
part 'message.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 105)
|
||||||
|
class Message extends HiveObject implements FieldDebuggable {
|
||||||
|
@HiveField(0)
|
||||||
final String messageID;
|
final String messageID;
|
||||||
|
|
||||||
|
@HiveField(10)
|
||||||
final String senderUserID;
|
final String senderUserID;
|
||||||
|
@HiveField(11)
|
||||||
final String channelInternalName;
|
final String channelInternalName;
|
||||||
|
@HiveField(12)
|
||||||
final String channelID;
|
final String channelID;
|
||||||
|
@HiveField(13)
|
||||||
final String? senderName;
|
final String? senderName;
|
||||||
|
@HiveField(14)
|
||||||
final String senderIP;
|
final String senderIP;
|
||||||
|
@HiveField(15)
|
||||||
final String timestamp;
|
final String timestamp;
|
||||||
|
@HiveField(16)
|
||||||
final String title;
|
final String title;
|
||||||
|
@HiveField(17)
|
||||||
final String? content;
|
final String? content;
|
||||||
|
@HiveField(18)
|
||||||
final int priority;
|
final int priority;
|
||||||
|
@HiveField(19)
|
||||||
final String? userMessageID;
|
final String? userMessageID;
|
||||||
|
@HiveField(20)
|
||||||
final String usedKeyID;
|
final String usedKeyID;
|
||||||
|
@HiveField(21)
|
||||||
final bool trimmed;
|
final bool trimmed;
|
||||||
|
|
||||||
const Message({
|
Message({
|
||||||
required this.messageID,
|
required this.messageID,
|
||||||
required this.senderUserID,
|
required this.senderUserID,
|
||||||
required this.channelInternalName,
|
required this.channelInternalName,
|
||||||
@ -54,4 +74,27 @@ class Message {
|
|||||||
|
|
||||||
return (npt, messages);
|
return (npt, messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Message[${this.messageID}]';
|
||||||
|
}
|
||||||
|
|
||||||
|
List<(String, String)> debugFieldList() {
|
||||||
|
return [
|
||||||
|
('messageID', this.messageID),
|
||||||
|
('senderUserID', this.senderUserID),
|
||||||
|
('channelInternalName', this.channelInternalName),
|
||||||
|
('channelID', this.channelID),
|
||||||
|
('senderName', this.senderName ?? ''),
|
||||||
|
('senderIP', this.senderIP),
|
||||||
|
('timestamp', this.timestamp),
|
||||||
|
('title', this.title),
|
||||||
|
('content', this.content ?? ''),
|
||||||
|
('priority', '${this.priority}'),
|
||||||
|
('userMessageID', this.userMessageID ?? ''),
|
||||||
|
('usedKeyID', this.usedKeyID),
|
||||||
|
('trimmed', '${this.trimmed}'),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
77
flutter/lib/models/message.g.dart
Normal file
77
flutter/lib/models/message.g.dart
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'message.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class MessageAdapter extends TypeAdapter<Message> {
|
||||||
|
@override
|
||||||
|
final int typeId = 105;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Message read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return Message(
|
||||||
|
messageID: fields[0] as String,
|
||||||
|
senderUserID: fields[10] as String,
|
||||||
|
channelInternalName: fields[11] as String,
|
||||||
|
channelID: fields[12] as String,
|
||||||
|
senderName: fields[13] as String?,
|
||||||
|
senderIP: fields[14] as String,
|
||||||
|
timestamp: fields[15] as String,
|
||||||
|
title: fields[16] as String,
|
||||||
|
content: fields[17] as String?,
|
||||||
|
priority: fields[18] as int,
|
||||||
|
userMessageID: fields[19] as String?,
|
||||||
|
usedKeyID: fields[20] as String,
|
||||||
|
trimmed: fields[21] as bool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, Message obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(13)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.messageID)
|
||||||
|
..writeByte(10)
|
||||||
|
..write(obj.senderUserID)
|
||||||
|
..writeByte(11)
|
||||||
|
..write(obj.channelInternalName)
|
||||||
|
..writeByte(12)
|
||||||
|
..write(obj.channelID)
|
||||||
|
..writeByte(13)
|
||||||
|
..write(obj.senderName)
|
||||||
|
..writeByte(14)
|
||||||
|
..write(obj.senderIP)
|
||||||
|
..writeByte(15)
|
||||||
|
..write(obj.timestamp)
|
||||||
|
..writeByte(16)
|
||||||
|
..write(obj.title)
|
||||||
|
..writeByte(17)
|
||||||
|
..write(obj.content)
|
||||||
|
..writeByte(18)
|
||||||
|
..write(obj.priority)
|
||||||
|
..writeByte(19)
|
||||||
|
..write(obj.userMessageID)
|
||||||
|
..writeByte(20)
|
||||||
|
..write(obj.usedKeyID)
|
||||||
|
..writeByte(21)
|
||||||
|
..write(obj.trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is MessageAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
@ -66,11 +66,11 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
|
|||||||
),
|
),
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
children: [
|
children: [
|
||||||
ExcludeFocus(excluding: _selectedIndex != 0, child: MessageListPage()),
|
ExcludeFocus(excluding: _selectedIndex != 0, child: MessageListPage(isVisiblePage: _selectedIndex == 0)),
|
||||||
ExcludeFocus(excluding: _selectedIndex != 1, child: ChannelRootPage()),
|
ExcludeFocus(excluding: _selectedIndex != 1, child: ChannelRootPage(isVisiblePage: _selectedIndex == 1)),
|
||||||
ExcludeFocus(excluding: _selectedIndex != 2, child: AccountRootPage()),
|
ExcludeFocus(excluding: _selectedIndex != 2, child: AccountRootPage(isVisiblePage: _selectedIndex == 2)),
|
||||||
ExcludeFocus(excluding: _selectedIndex != 3, child: SettingsRootPage()),
|
ExcludeFocus(excluding: _selectedIndex != 3, child: SettingsRootPage(isVisiblePage: _selectedIndex == 3)),
|
||||||
ExcludeFocus(excluding: _selectedIndex != 4, child: SendRootPage()),
|
ExcludeFocus(excluding: _selectedIndex != 4, child: SendRootPage(isVisiblePage: _selectedIndex == 4)),
|
||||||
],
|
],
|
||||||
index: _selectedIndex,
|
index: _selectedIndex,
|
||||||
),
|
),
|
||||||
|
@ -16,7 +16,9 @@ import 'package:simplecloudnotifier/utils/ui.dart';
|
|||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class AccountRootPage extends StatefulWidget {
|
class AccountRootPage extends StatefulWidget {
|
||||||
const AccountRootPage({super.key});
|
const AccountRootPage({super.key, required this.isVisiblePage});
|
||||||
|
|
||||||
|
final bool isVisiblePage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AccountRootPage> createState() => _AccountRootPageState();
|
State<AccountRootPage> createState() => _AccountRootPageState();
|
||||||
@ -33,13 +35,34 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
|
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
|
|
||||||
|
bool _isInitialized = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(AccountRootPage oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
||||||
|
if (!_isInitialized) {
|
||||||
|
realInitState();
|
||||||
|
} else {
|
||||||
|
//TODO background refresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void realInitState() {
|
||||||
_onAuthStateChanged();
|
_onAuthStateChanged();
|
||||||
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -92,6 +115,8 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<AppAuth>(
|
return Consumer<AppAuth>(
|
||||||
builder: (context, acc, child) {
|
builder: (context, acc, child) {
|
||||||
|
if (!_isInitialized) return SizedBox();
|
||||||
|
|
||||||
if (!userAcc.isAuth()) {
|
if (!userAcc.isAuth()) {
|
||||||
return _buildNoAuth(context);
|
return _buildNoAuth(context);
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,21 +8,26 @@ 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';
|
||||||
|
|
||||||
class ChannelRootPage extends StatefulWidget {
|
class ChannelRootPage extends StatefulWidget {
|
||||||
const ChannelRootPage({super.key});
|
const ChannelRootPage({super.key, required this.isVisiblePage});
|
||||||
|
|
||||||
|
final bool isVisiblePage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChannelRootPage> createState() => _ChannelRootPageState();
|
State<ChannelRootPage> createState() => _ChannelRootPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelRootPageState extends State<ChannelRootPage> {
|
class _ChannelRootPageState extends State<ChannelRootPage> {
|
||||||
final PagingController<int, Channel> _pagingController = PagingController(firstPageKey: 0);
|
final PagingController<int, Channel> _pagingController = PagingController.fromValue(PagingState(nextPageKey: null, itemList: [], error: null), firstPageKey: 0);
|
||||||
|
|
||||||
|
bool _isInitialized = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_pagingController.addPageRequestListener((pageKey) {
|
|
||||||
_fetchPage(pageKey);
|
|
||||||
});
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
_pagingController.addPageRequestListener(_fetchPage);
|
||||||
|
|
||||||
|
if (widget.isVisiblePage && !_isInitialized) realInitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -31,9 +36,29 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(ChannelRootPage oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
||||||
|
if (!_isInitialized) {
|
||||||
|
realInitState();
|
||||||
|
} else {
|
||||||
|
//TODO background refresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void realInitState() {
|
||||||
|
_pagingController.refresh();
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchPage(int pageKey) async {
|
Future<void> _fetchPage(int pageKey) async {
|
||||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
|
ApplicationLog.debug('Start ChannelList::_pagingController::_fetchPage [ ${pageKey} ]');
|
||||||
|
|
||||||
if (!acc.isAuth()) {
|
if (!acc.isAuth()) {
|
||||||
_pagingController.error = 'Not logged in';
|
_pagingController.error = 'Not logged in';
|
||||||
return;
|
return;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/message.dart';
|
||||||
import 'package:simplecloudnotifier/pages/debug/debug_persistence_hive.dart';
|
import 'package:simplecloudnotifier/pages/debug/debug_persistence_hive.dart';
|
||||||
import 'package:simplecloudnotifier/pages/debug/debug_persistence_sharedprefs.dart';
|
import 'package:simplecloudnotifier/pages/debug/debug_persistence_sharedprefs.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
@ -83,6 +85,42 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Card.outlined(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navi.push(context, () => DebugHiveBoxPage(boxName: 'scn-message-cache', box: Hive.box<Message>('scn-message-cache')));
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 30, child: Text('')),
|
||||||
|
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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card.outlined(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navi.push(context, () => DebugHiveBoxPage(boxName: 'scn-channel-cache', box: Hive.box<Channel>('scn-channel-cache')));
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 30, child: Text('')),
|
||||||
|
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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
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/models/message.dart';
|
import 'package:simplecloudnotifier/models/message.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
import 'package:simplecloudnotifier/pages/message_view/message_view.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/message_list/message_list_item.dart';
|
import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart';
|
||||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
|
|
||||||
class MessageListPage extends StatefulWidget {
|
class MessageListPage extends StatefulWidget {
|
||||||
const MessageListPage({super.key});
|
const MessageListPage({super.key, required this.isVisiblePage});
|
||||||
|
|
||||||
|
final bool isVisiblePage;
|
||||||
|
|
||||||
//TODO reload on switch to tab
|
//TODO reload on switch to tab
|
||||||
//TODO reload on app to foreground
|
//TODO reload on app to foreground
|
||||||
@ -20,31 +24,90 @@ class MessageListPage extends StatefulWidget {
|
|||||||
State<MessageListPage> createState() => _MessageListPageState();
|
State<MessageListPage> createState() => _MessageListPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MessageListPageState extends State<MessageListPage> {
|
class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
||||||
static const _pageSize = 128;
|
static const _pageSize = 128;
|
||||||
|
|
||||||
final PagingController<String, Message> _pagingController = PagingController(firstPageKey: '@start');
|
PagingController<String, Message> _pagingController = PagingController.fromValue(PagingState(nextPageKey: null, itemList: [], error: null), firstPageKey: '@start');
|
||||||
|
|
||||||
Map<String, Channel>? _channels = null;
|
Map<String, Channel>? _channels = null;
|
||||||
|
|
||||||
|
bool _isInitialized = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
//TODO init with state from cache - also allow tho show cache on error
|
|
||||||
_pagingController.addPageRequestListener((pageKey) {
|
|
||||||
_fetchPage(pageKey);
|
|
||||||
});
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
_pagingController.addPageRequestListener(_fetchPage);
|
||||||
|
|
||||||
|
if (widget.isVisiblePage && !_isInitialized) realInitState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(MessageListPage oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
|
||||||
|
if (!_isInitialized) {
|
||||||
|
realInitState();
|
||||||
|
} else {
|
||||||
|
_backgroundRefresh(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void realInitState() {
|
||||||
|
final chnCache = Hive.box<Channel>('scn-channel-cache');
|
||||||
|
final msgCache = Hive.box<Message>('scn-message-cache');
|
||||||
|
|
||||||
|
if (chnCache.isNotEmpty && msgCache.isNotEmpty) {
|
||||||
|
// ==== Use cache values - and refresh in background
|
||||||
|
|
||||||
|
_channels = <String, Channel>{for (var v in chnCache.values) v.channelID: v};
|
||||||
|
|
||||||
|
final cacheMessages = msgCache.values.toList();
|
||||||
|
cacheMessages.sort((a, b) => -1 * a.timestamp.compareTo(b.timestamp));
|
||||||
|
|
||||||
|
_pagingController.value = PagingState(nextPageKey: null, itemList: cacheMessages, error: null);
|
||||||
|
|
||||||
|
_backgroundRefresh(true);
|
||||||
|
} else {
|
||||||
|
// ==== Full refresh - no cache available
|
||||||
|
_pagingController.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
Navi.modalRouteObserver.subscribe(this, ModalRoute.of(context)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
Navi.modalRouteObserver.unsubscribe(this);
|
||||||
_pagingController.dispose();
|
_pagingController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPush() {
|
||||||
|
// Route was pushed onto navigator and is now the topmost route.
|
||||||
|
ApplicationLog.debug('[MessageList::RouteObserver] --> didPush');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPopNext() {
|
||||||
|
// Covering route was popped off the navigator.
|
||||||
|
ApplicationLog.debug('[MessageList::RouteObserver] --> didPopNext');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchPage(String thisPageToken) async {
|
Future<void> _fetchPage(String thisPageToken) async {
|
||||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
|
ApplicationLog.debug('Start MessageList::_pagingController::_fetchPage [ ${thisPageToken} ]');
|
||||||
|
|
||||||
if (!acc.isAuth()) {
|
if (!acc.isAuth()) {
|
||||||
_pagingController.error = 'Not logged in';
|
_pagingController.error = 'Not logged in';
|
||||||
return;
|
return;
|
||||||
@ -54,10 +117,16 @@ class _MessageListPageState extends State<MessageListPage> {
|
|||||||
if (_channels == null) {
|
if (_channels == null) {
|
||||||
final channels = await APIClient.getChannelList(acc, ChannelSelector.allAny);
|
final channels = await APIClient.getChannelList(acc, ChannelSelector.allAny);
|
||||||
_channels = <String, Channel>{for (var v in channels) v.channel.channelID: v.channel};
|
_channels = <String, Channel>{for (var v in channels) v.channel.channelID: v.channel};
|
||||||
|
|
||||||
|
_setChannelCache(channels); // no await
|
||||||
}
|
}
|
||||||
|
|
||||||
final (npt, newItems) = await APIClient.getMessageList(acc, thisPageToken, pageSize: _pageSize);
|
final (npt, newItems) = await APIClient.getMessageList(acc, thisPageToken, pageSize: _pageSize);
|
||||||
|
|
||||||
|
_addToMessageCache(newItems); // no await
|
||||||
|
|
||||||
|
ApplicationLog.debug('Finished MessageList::_pagingController::_fetchPage [ ${newItems.length} items and npt: ${thisPageToken} --> ${npt} ]');
|
||||||
|
|
||||||
if (npt == '@end') {
|
if (npt == '@end') {
|
||||||
_pagingController.appendLastPage(newItems);
|
_pagingController.appendLastPage(newItems);
|
||||||
} else {
|
} else {
|
||||||
@ -69,6 +138,71 @@ class _MessageListPageState extends State<MessageListPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _backgroundRefresh(bool fullReplaceState) async {
|
||||||
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
|
ApplicationLog.debug('Start background refresh of message list (fullReplaceState: $fullReplaceState)');
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (_channels == null || fullReplaceState) {
|
||||||
|
final channels = await APIClient.getChannelList(acc, ChannelSelector.allAny);
|
||||||
|
setState(() {
|
||||||
|
_channels = <String, Channel>{for (var v in channels) v.channel.channelID: v.channel};
|
||||||
|
});
|
||||||
|
_setChannelCache(channels); // no await
|
||||||
|
}
|
||||||
|
|
||||||
|
final (npt, newItems) = await APIClient.getMessageList(acc, '@start', pageSize: _pageSize);
|
||||||
|
|
||||||
|
_addToMessageCache(newItems); // no await
|
||||||
|
|
||||||
|
if (fullReplaceState) {
|
||||||
|
// fully replace/reset state
|
||||||
|
ApplicationLog.debug('Background-refresh finished (fullReplaceState) - replace state with ${newItems.length} items and npt: [ $npt ]');
|
||||||
|
setState(() {
|
||||||
|
if (npt == '@end')
|
||||||
|
_pagingController.value = PagingState(nextPageKey: null, itemList: newItems, error: null);
|
||||||
|
else
|
||||||
|
_pagingController.value = PagingState(nextPageKey: npt, itemList: newItems, error: null);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
final itemsToBeAdded = newItems.where((p1) => !(_pagingController.itemList ?? []).any((p2) => p1.messageID == p2.messageID)).toList();
|
||||||
|
if (itemsToBeAdded.isEmpty) {
|
||||||
|
// nothing to do - no new items...
|
||||||
|
// ....
|
||||||
|
ApplicationLog.debug('Background-refresh returned no new items - nothing to do.');
|
||||||
|
} else if (itemsToBeAdded.length == newItems.length) {
|
||||||
|
// all items are new ?!?, the current state is completely fucked - full replace
|
||||||
|
ApplicationLog.debug('Background-refresh found only new items ?!? - fully replace state with ${newItems.length} items');
|
||||||
|
setState(() {
|
||||||
|
if (npt == '@end')
|
||||||
|
_pagingController.value = PagingState(nextPageKey: null, itemList: newItems, error: null);
|
||||||
|
else
|
||||||
|
_pagingController.value = PagingState(nextPageKey: npt, itemList: newItems, error: null);
|
||||||
|
_pagingController.itemList = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// add new items to the front
|
||||||
|
ApplicationLog.debug('Background-refresh found ${newItems.length} new items - add to front');
|
||||||
|
setState(() {
|
||||||
|
_pagingController.itemList = itemsToBeAdded + (_pagingController.itemList ?? []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exc, trace) {
|
||||||
|
setState(() {
|
||||||
|
_pagingController.error = exc.toString();
|
||||||
|
});
|
||||||
|
ApplicationLog.error('Failed to list messages: ' + exc.toString(), trace: trace);
|
||||||
|
} finally {
|
||||||
|
AppBarState().setLoadingIndeterminate(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -92,4 +226,30 @@ class _MessageListPageState extends State<MessageListPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _setChannelCache(List<ChannelWithSubscription> channels) async {
|
||||||
|
final cache = Hive.box<Channel>('scn-channel-cache');
|
||||||
|
|
||||||
|
if (cache.length != channels.length) await cache.clear();
|
||||||
|
|
||||||
|
for (var chn in channels) await cache.put(chn.channel.channelID, chn.channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _addToMessageCache(List<Message> newItems) async {
|
||||||
|
final cache = Hive.box<Message>('scn-message-cache');
|
||||||
|
|
||||||
|
for (var msg in newItems) await cache.put(msg.messageID, msg);
|
||||||
|
|
||||||
|
// delete all but the newest 128 messages
|
||||||
|
|
||||||
|
if (cache.length < _pageSize) return;
|
||||||
|
|
||||||
|
final allValues = cache.values.toList();
|
||||||
|
|
||||||
|
allValues.sort((a, b) => -1 * a.timestamp.compareTo(b.timestamp));
|
||||||
|
|
||||||
|
for (var val in allValues.sublist(_pageSize)) {
|
||||||
|
await cache.delete(val.messageID);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
|
||||||
class SendRootPage extends StatefulWidget {
|
class SendRootPage extends StatefulWidget {
|
||||||
const SendRootPage({super.key});
|
const SendRootPage({super.key, required bool isVisiblePage});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SendRootPage> createState() => _SendRootPageState();
|
State<SendRootPage> createState() => _SendRootPageState();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class SettingsRootPage extends StatefulWidget {
|
class SettingsRootPage extends StatefulWidget {
|
||||||
const SettingsRootPage({super.key});
|
const SettingsRootPage({super.key, required bool isVisiblePage});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SettingsRootPage> createState() => _SettingsRootPageState();
|
State<SettingsRootPage> createState() => _SettingsRootPageState();
|
||||||
|
@ -14,6 +14,7 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
|||||||
|
|
||||||
User? _user;
|
User? _user;
|
||||||
Client? _client;
|
Client? _client;
|
||||||
|
DateTime? _clientQueryTime;
|
||||||
|
|
||||||
String? get userID => _userID;
|
String? get userID => _userID;
|
||||||
String? get tokenAdmin => _tokenAdmin;
|
String? get tokenAdmin => _tokenAdmin;
|
||||||
@ -124,7 +125,11 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Client?> loadClient({bool force = false}) async {
|
Future<Client?> loadClient({bool force = false, Duration? forceIfOlder = null}) async {
|
||||||
|
if (forceIfOlder != null && _clientQueryTime != null && _clientQueryTime!.difference(DateTime.now()) > forceIfOlder) {
|
||||||
|
force = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!force && _client != null && _client!.clientID == _clientID) {
|
if (!force && _client != null && _client!.clientID == _clientID) {
|
||||||
return _client!;
|
return _client!;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
|||||||
|
|
||||||
class Navi {
|
class Navi {
|
||||||
static final SCNRouteObserver routeObserver = SCNRouteObserver();
|
static final SCNRouteObserver routeObserver = SCNRouteObserver();
|
||||||
|
static final RouteObserver<ModalRoute<void>> modalRouteObserver = RouteObserver<ModalRoute<void>>();
|
||||||
|
|
||||||
static void push<T extends Widget>(BuildContext context, T Function() builder) {
|
static void push<T extends Widget>(BuildContext context, T Function() builder) {
|
||||||
Provider.of<AppBarState>(context, listen: false).setLoadingIndeterminate(false);
|
Provider.of<AppBarState>(context, listen: false).setLoadingIndeterminate(false);
|
||||||
|
Loading…
Reference in New Issue
Block a user